Zum Inhalt springen

Wie man die Server-Validierung für Bootstrap 3 ein wenig aufhübschen kann, habe ich ja bereits erklärt.

Nun wollte ich auch schauen, dass die Client-Variante etwas _bootstrappiger_ aussieht und bin auf folgenden Beitrag gestoßen. Sehr gut finde ich, dass hier sämtliche Standardmethoden genutzt werden und das Rad nicht neu erfunden wird. Das ganze ist nur für Bootstrap 2, daher habe ich noch ein wenig dran geschraubt.

jQuery.validator.setDefaults({
    highlight: function (element, errorClass, validClass) {
        if (element.type === 'radio') {
            this.findByName(element.name).addClass(errorClass).removeClass(validClass);
        } else {
            $(element).addClass(errorClass).removeClass(validClass);
            $(element).closest('.form-group').removeClass('has-success').addClass('has-error');
        }
    },
    unhighlight: function (element, errorClass, validClass) {
        if (element.type === 'radio') {
            this.findByName(element.name).removeClass(errorClass).addClass(validClass);
        } else {
            $(element).removeClass(errorClass).addClass(validClass);
            $(element).closest('.form-group').removeClass('has-error').addClass('has-success');
        }
    }
});

$(function() {
    $('span.field-validation-valid, span.field-validation-error').each(function () {
        $(this).addClass('help-block');
    });
});

Im Prinzip habe ich wieder nur die CSS-Klassen angepasst und aus den Kommentaren die letzte Methode gezogen, um die Bootstrap-CSS-Klasse für die Hinweise anzufügen.
Damit das ganze auch funktioniert, muss darauf geachtet werden, dass der Code nach dem Laden der jQuery-Methoden erfolgt. Dazu habe ich in der BundleConfig.cs einfach das ScriptBundle angepasst:

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
	"~/Scripts/jquery.unobtrusive*",
	"~/Scripts/jquery.validate*").Include("~/Scripts/wys.jquery.validate.bootstrap3.js"));

Ich habe die Datei bewusst mit einem Prefix versehen, denn nur mit dem Namen „jquery.validate.bootstrap3.js“ wäre die Datei vor den anderen jquery.validate-Javascript-Dateien geladen worden und hätte zu einem Javascript-Fehler geführt.

Update: Es gab noch einen kleinen Bug im Code, den ich noch mal eben behoben habe!


Ursprünglicher Beitrag:

Ich habe mal spaßeshalber ein MVC Projekt angefangen und dort ebenfalls das vor wenigen Tagen erschienene Bootstrap 3 implementiert. Nun wollte ich die bekannte ValidationSummary von MVC ein wenig mit Bootstrap stylen, jedoch scheint dies nicht so einfach.

Bei stackoverflow (wo auch sonst) bin ich auf eine eigene Implementierung der ValidationSummary-Ausgabe speziell für Bootstrap gestoßen. Diese ist jedoch nur für die Server-seitige Validierung geeignet, und da ich mich sowieso noch nicht entschlossen hatte, welche Validierung ich nutzen möchte, habe ich kurzerhand die Client-seitige deaktiviert.

Neben der bootstrap-gestylten ValidationSummary zeigte „PeteGo“ noch, wie er seine Eingabefelder für den Benutzer fehlerhaft markiert. Dies ist mir allerdings zu aufwendig, da man natürlich mit jedem neuen Feld auch auch an die Prüfung denken muss. Ja ich weiß, ich kann ja Editor-Templates nutzen, will ich aber nicht 😉

Da ich die Erweiterung von PeteGo sowieso anfassen musste, sie war nur für Bootstrap 2 geeignet, habe ich die einzelnen Fehlermeldungen auch noch einem data--Attribute versehen und markiere per jQuery die Felder als fehlerhaft.

Die Erweiterung:

using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Web.Mvc
{
    public static class HtmlExtensionMethods
    {
        /// <summary>
        /// Returns an error alert that lists each model error, much like the standard ValidationSummary only with
        /// altered markup for the Twitter bootstrap styles.
        /// </summary>
        public static MvcHtmlString ValidationSummaryBootstrap(this HtmlHelper helper, bool closeable)
        {
            string errorMessage = "Please fix the errors listed below and try again.";
            string validationTitle = "Validation error";

            ModelStateDictionary modelErrors = helper.ViewContext.ViewData.ModelState;

            if (!modelErrors.Any(error => error.Value.Errors.Any()))
            {
                return new MvcHtmlString(string.Empty);
            }

            StringBuilder divContent = new StringBuilder();
            
            if (closeable)
            {
                TagBuilder button = new TagBuilder("button");
                button.AddCssClass("close");
                button.MergeAttribute("type", "button");
                button.MergeAttribute("data-dismiss", "alert");
                button.SetInnerText("x");
                divContent.Append(button);
            }

            TagBuilder validationTitleTag = new TagBuilder("strong");
            validationTitleTag.SetInnerText(validationTitle);
            divContent.Append(validationTitleTag);

            divContent.Append(" ");
            divContent.Append(errorMessage);

            StringBuilder ulContent = new StringBuilder();
            
            foreach (KeyValuePair<string, ModelState> modelError in modelErrors)
            {
                string id = modelError.Key;
                foreach(ModelError item in modelError.Value.Errors)
                {
                    TagBuilder li = new TagBuilder("li");
                    li.SetInnerText(item.ErrorMessage);
                    li.MergeAttribute("data-bootstrap-has-error", id);
                    ulContent.Append(li);
                }
            }
            TagBuilder ul = new TagBuilder("ul") {InnerHtml = ulContent.ToString()};
            divContent.Append(ul);

            TagBuilder div = new TagBuilder("div");
            div.AddCssClass("alert");
            div.AddCssClass("alert-error");
            div.AddCssClass("alert-block");
            div.InnerHtml = divContent.ToString();

            return new MvcHtmlString(div.ToString());
        }

        /// <summary>
        /// Overload allowing no arguments.
        /// </summary>
        public static MvcHtmlString ValidationSummaryBootstrap(this HtmlHelper helper)
        {
            return ValidationSummaryBootstrap(helper, true);
        }
    }
}

Und nun die paar Zeilen Javascript, welche dann die Felder entsprechend markieren:

$(function () {
    $('div.alert').each(function () {
        $(this).find('li[data-bootstrap-has-error]').each(
            function () {
                var id = $(this).attr('data-bootstrap-has-error');
                $('input[id=' + id + '],select[id=' + id + ']').parent().addClass('has-error');
            });
    });
});

Vielleicht kann es ja jemand gebrauchen 🙂

Ach ja, falls jemand ein paar Pros / Contras bezüglich Client-/Server-Validierung hat, kann er diese gerne in den Kommentaren hinterlassen.

Der Team Foundation Server bringt ein sehr nettes Feature mit, er informiert mich über bestimmte Änderungen per Email.
Finden tut ihr den „Alert Explorer“ in Visual Studio 2010 unter „Team“ -> „Alert Explorer“.

Alert Explorer Visual Studio 2010

Alert hinzufügen in Visual Studio 2010 Ich habe hier ein Alert auf meine Work Items und Tasks sowie wenn jemand bestimmte Dateien eincheckt. Möglich sind aber auch noch Meldungen über Builds.
Mit einem Klick auf „New Alert“ findet ihr Vorlagen für die verschiedenen Alert-Arten sowie einige vordefinierten Templates.
Wenn ihr eine Vorlage auswählt, seid ihr automatisch als Empfänger vorbelegt. Ihr könnt allerdings auch mehrere Benutzer informieren lassen. Der Name dient ledeglich zur Organisation im Alert Explorer, den Betreff oder den Inhalt der Emails kann man leider nicht weiter beeinflussen. Dies wäre durchaus praktisch um in Outlook spezielle Regeln greifen zu lassen.
Unterhalt der „Alert Definition“ werden die gewünschten Filterkreterien angegeben, hier mal ein Beispiel um mich über alle Änderungen an meinen Workpackages / Tasks / Bugs / etc. informieren zu lassen, die nicht ich gemacht habe.

Wer über Änderungen an Dateien informiert werden will, hat über den Source Control Explorer zusätzlich die Möglichkeit mit einem Rechtsklick auf die gewünschte Datei oder Ordner einen Alert zu erstellen. Hier bei wird nur eine Datei in die Alert Definition hinzugefügt. Markiert man mehrere Dateien im SCE, so bleibt der Punkt disabled. Allerdings kann man die Defintition logisch mit OR verknüpfen und so mehrer Dateien mit einer Defintition traken.

Meiner Meinung nach ein großartiges Feature. Nicht selten kommt es vor, dass Workpackages angelegt oder geändert werden, ich dies aber erst spät mitbekomme. Oder aber ich kann verfolgen, ob die Kollegen in den getrakten Dateien auch nur die Zeilen ändern, die sie ändern dürfen 🙂

Achtung: Spoilergefahr!!

Mal ein kleiner Quicktipp, welcher mich gestern doch eine ordentliche Anzahl an Nervenenden gekostet hat.
Wenn man die Geschichte mit den Heiligtümern des Todes nachspielt, kommt man an eine Stelle, bei der man sich mit dem Umhang am Tod vorbeischleichen muss und ihn mit Kürbissen bewerfen muss. Dazu lasst ihr sie schweben und fangt an sie zu drehen. Allerdings müsst ihr den Stick vom Nunchuck nach links und nicht nach oben drücken. Die drehen sich zwar auch aber kommen nicht an.

Muss man erstmal darauf kommen 🙂

Ansonsten, gutes Spiel!

Vor einigen Monaten habe ich im Blog mal ein wenig Dampf abgelassen und mich über mein Palm Pre aufgeregt. Mittlerweile habe ich mich davon getrennt und es gammelt in der Schublade vor sich hin und habe mir auf Empfehlung eines Kollegen und meines Cousins ein Google Nexus S zugelegt. Es hat zwar schon ein paar Tage auf dem Buckel, war für aber rund 230€ auch verhältnismäßig günstig in der Anschaffung.

Android?

Was mich ja immer ein wenig abgeschreckt hat, mir ein Android Smartphone zuzulegen ist die Updatepolitik mancher Hersteller / Anbieter. Google bringt eine neue Version rum, Motorola (beispielsweise) macht ihr Logo rein, spielt hier noch ein wenig, da noch ein wenig rum. Dann kommt noch T-Mobile und will auch noch ein bischen was ändern. Wenn es glatt läuft habe ich nach rund 6 Monaten die neue Version auf meinem Telefon. Das Nexus S hingegen ist im Prinzip das Google Entwickler Phone, so dass dort die Updates als erstes erscheinen.
Sehr gut finde ich auch die Einbindung sämtlicher Google Dienste. Über Mails werde ich Just in Time benachrichtigt und ich aber immer und überall Zugriff auf meine Daten bei Google Docs oder den Kalender.

Fast wunschlos glücklich

Wenn ich mal die ganzen Kritikpunkte in meinem alten Beitrag durchgehe, so wurden sie alle mit dem Geräte- und Betriebssystemwechsel gelöst.
Der Desktop hat mehrere Seiten, fünf an der Zahl. Auf der Hauptseite habe ich meinen Kalender und Verknüpfungen zu den wichtigsten Anwendungen, wie Mail oder SMS. Auf einer weiteren Seite habe ich noch ein paar Verknüpfungen, die ich eher selten benötige, 3 Seiten sind praktisch leer.

In ca. 2 1/2 Monaten ist mein Telefon erst einmal komplett abgestürzt, während mein Pre damals sicherlich zig Fehlermeldungen geschmissen hätte, weil kein Speicher mehr frei war.
Probleme mit der SIM-Karte gehören der Vergangenheit an, ab und an gibt es aber Empfangsschwierigkeiten, die ich allerdings auf o2 schiebe (Stichwort: „Einzelfall“). Beim Pre waren die Probleme öfter und ausgeprägter.

Sehr nervig war damals die Akkulaufzeit. Ich hatte alles deaktiviert, dennoch hat der Akku nicht lange gehalten. Beim Nexus habe ich alles aktiviert, spiele viel mehr mit rum und dennoch ist der Akku abends noch halb voll. Wenn ich 3G und WLan deaktivieren würde, hält der Akku problemlos mehrere Tage.

Was konnte das Pre besser?

Es gibt doch tatsächlich etwas, was mir am Pre richtig gut gefiel und mir jetzt leider fehlt. Das Pre hatte unter dem Display einen Button (wie der Home-Button beim iPhone), welcher leuchtete wenn man eine Nachricht empfangen oder einen Anruf verpasst hat. Wäre ein nettes Feature, wenn beim Nexus S der Hardware-Home-Button sporadisch aufleuchten würde o.ä. Lässt sich allerdings wohl mit Android 4.0 nicht umsetzen, da die neuren Telefone wohl keine Hardware-Tasen mehr besitzen.

Fazit

Auch wenn das Nexus S ein paar Tage alt ist, der Nachfolger Nexus Galaxy mittlerweile auf dem Markt ist, habe ich mir ein sehr robustes und nutzbares Smartphone zugelegt, mit dem ich noch einige Zeit meine Freude haben werde.