Archiv der Kategorie: Twitter Bootstrap

Bootstrap 4 Alpha – Released


Das es eine neue Bootstrap Version geben wird, war nur eine Frage der Zeit. Vor allem wurde die Alpha Bereits vor einer ganzen Weile angekündigt und nun ist es endlich soweit. Eine erste Alpha Version von Bootstrap 4 wurde veröffentlicht und kann ausprobiert werden.

Sollte es noch Webentwickler geben, die nicht wissen was Bootstrap ist, dann kann ich diesen nur empfehlen, sich spätestens ab Version 4 einmal damit zu beschäftigen. Ich für meine Zwecke setze Bootstrap sehr gerne in kleinen Privaten Projekten ein oder für interne Webseiten. Denn mit Bootstrap kann man sich auf das wesentliche als Entwickler konzentrieren und dabei handelt es sich nun einmal um das Entwickeln und nicht das Stylen. Aber mit Bootstrap sieht das ganze dann trotzdem sehr elegant aus, egal wie klein das Webprojekt auch ist.

Mit Bootstrap Version 4 hat sich einiges geändert, aber zum Glück nicht komplett alles. Leider entfallen die Panels komplett und werden durch so genannte Cards ersetzt. Da ich aber ein Exzessiver Nutzer von Panels bin, muss ich hier mal genau untersuchen wie gut sich die Panels zu Cards Migrieren lassen. Sonst scheint es aber keine großen Überraschungen zu geben, was neue Controls oder Styles angeht, hier scheint beim ersten durchgehen der neuen Dokumentation vieles beim alten zu bleiben, bis auf ein paar kleine Namensanpassungen. Außerdem setzt Bootstrap 4 jetzt in der neuen Version auf Sass statt auf Less, außerdem setzt man jetzt vermehrt auf “rem” und “em” als Maßeinheit statt auf Pixel.

Daher bin ich mal gespannt wie lange der Finale Release jetzt noch auf sich warten lässt und freue mich schon meine Webseiten mit Bootstrap 4 zu erstellen.

AngularUI – benutzerdefinierte Direktive für eine Multiselect Dropdownliste im Bootstrap Style inkl. AngularJS Form Validation


Leider war ich vergeblich auf der Suche nach einer brauchbaren Lösung für eine Multiselect Dropdownliste für AngularJS im Twitter Bootstrap Style die meinen Anforderungen entspricht. Daher habe ich AngularUI als Vorlage für meine “eigene” Mutliselect Dropdownliste genommen und von AngularUI die Dropdown Komponente als Grundlage verwendet. Denn die Dropdownkomponente bietet schon alle wichtigen Grundlagen für eine Multiselect Dropdownliste. Das ganze habe ich dann in eine eigene Direktive verpackt und kann entsprechend in AngularJS verwendet werden.

Voraussetzungen und was die Multiselect Dropdownliste alles können soll:

  • Benötigt wird AngularJS und  AngularUI als Grundlage (daher wird auch kein jQuery benötigt!)
  • Das Basic Styling übernimmt Twitter Bootstrap, aber die Icons verwende ich von Font Awesome
  • Ein paar Custom Styles um die Dropdown Komponenten entsprechend anzupassen für die Multiselect Liste
  • Automatisches Anpassen der Breite des Controls an z.B. “col-md-12” von Bootstrap
  • eine eigene Direktive inkl. Template für die Multiselect Liste
  • Integrierte Form Validation für das “required” Attribut sowie “$dirty” und “$pristine” Modus des Formulars in dem die Liste verwendet wird
  • Die Möglichkeit alle Elemente an und abzuwählen
  • Über Änderungen der Auswahl “bescheid” geben wie bei “ng-change”

1. Custom Styles

/* Multiselect Dropdown Styles - Start */
.sq-dropdown .dropdown-multiselect {
	max-height: 300px; 
	overflow-y: auto; 
	overflow-x: hidden;
	width: 100%;
}

.sq-dropdown .ddl-icon {
    display: inline-block;
    margin-top: 2.5px;
    position: absolute;
    right: 15px;
}

.sq-dropdown.bootstrap-select:not([class*="span"]):not([class*="col-"]):not([class*="form-control"]), .bootstrap-select > .btn {
    width: 100%;
}

.sq-dropdown.bootstrap-select.btn-group .btn .filter-option {
    left: 12px;
    overflow: hidden;
    position: absolute;
	text-overflow:ellipsis;
    right: 25px;
    text-align: left;
}

.sq-dropdown.bootstrap-select.btn-group .btn .caret {
    margin-top: -2px;
    position: absolute;
    right: 12px;
    top: 50%;
    vertical-align: middle;
}
/* Multiselect Dropdown Styles - Ende */

2. Benutzerdefinierte Direktive

wichtig ist hier, das die Liste die angezeigt werden soll, die Properties “Selected” und “Text” enthält. Denn das Property “Text” wird angezeigt und in “Selected” merken wir uns welches Element ausgewählt wurde. Die Funktionen “validators” und das $watch für “listEntries” wird beides benötigt, damit das Control mit der Form Validierung von AngularJS funktioniert. Denn wenn sich die Auswahl ändert oder das “required” Attribut gesetzt wird, kann man über die Standard AngularJS Methoden prüfen ob die Eingaben valide sind z.B. “frm.$valid” oder “frm.$dirty” oder auch “frm.$pristine” oder auch direkt das Control ansprechen. Damit die Validierung überhaupt verwendet werden kann, muss auch “required: ‘ngModel’ gesetzt, werden denn nur dann steht uns das Model in der Link Funktion zur Verfügung und wir können auf “$setValidity” zugreifen für die “required” Validierung.

Damit das alles Funktioniert, darf man natürlich nicht vergessen, dem Control das “name” Attribut zu verpassen inkl. eines passenden Namens.

angular.module("select.directives", [])
    /*
    Direktive zum Anzeigen einer MultiSelect Box als Dropdownliste. Achtung Es wird hier ebenfalls AnguluarUi benötigt!
    Es werden die Properties "Selected" und "Text" benötigt in der übergebenen Anzeigeliste, damit die Multiselect Liste richtig funktioniert.

    Verwendung:

    html ...
    <div sq-multiselect ng-model="ModelData.Listendaten" value-changed="LogChange(selectedValue)" required></div>
    ... html
    */
    .directive("sqMultiselect", function () {
        return {
            restrict: 'AE',
            replace: true,
            require: 'ngModel',
            scope: {
                listEntries: "=ngModel", //unsere Liste mit den Einträgen
                valueChanged: "&" //Funktion die aufgerufen wird, wenn sich der Wert geändert hat.
            },
            //ACHTUNG funktioniert nur in Verbindung mit AngularUi und dem dort enthaltenen "dropdown" Control!
            //dies muss auch mit in den Abhängigkeiten geladen werden mit "ui.bootstrap".
            template: '<div class="sq-dropdown bootstrap-select btn-group" dropdown>' +
                '<button type="button" class="btn btn-default dropdown-toggle" title="{{Fct.selectedString(listEntries)}}">' +
                '<span class="filter-option" ng-bind="Fct.selectedString(listEntries)"></span>&nbsp;' +
                '<span class="caret"></span></button>' +
                '<ul class="dropdown-menu dropdown-multiselect" role="menu">' +
                '<li ng-show="listEntries.length > 0"><a href="" ng-click="Fct.selectAll(listEntries)">' +
                '<span style="margin-right: 30px;"><strong ng-bind="Locals.selectAll"></strong></span>' +
                '<i ng-show="Fct.allSelected(listEntries)" class="fa fa-check ddl-icon"></i>' +
                '</a>' +
                '</li>' +
                '<li ng-repeat="item in listEntries">' +
                '<a ng-click="item.Selected = !item.Selected" href="">' +
                '<span style="margin-right: 30px;" ng-bind="item.Text"></span> ' +
                '<i ng-show="item.Selected" class="fa fa-check ddl-icon"></i>' +
                '</a>' +
                '</li>' +
                '</ul>' +
                '</div>',
            link: function(scope, elem, attr, ngModel) {
                scope.Fct = {};
                scope.Locals = {};
                scope.Locals.selectAll = "Alle auswählen";

                //String zusammenbauen für die ausgewählten Elemente
                scope.Fct.selectedString = function(data) {
                    var anz = "-- Bitte wählen --", count = 0;
                    if (data !== undefined && data !== null) {
                        for (var i = 0; i < data.length; i++) {
                            //Nur wenn das Element auch ausgewählt wurde anzeigen.
                            if (data[i].Selected) {
                                if (count === 0) {
                                    anz = "";
                                }
                                if (anz.length > 0) {
                                    anz += "; ";
                                }
                                //Text zusammenbauen
                                anz += data[i].Text;
                                count++;
                            }
                        }
                    }
                    return anz;
                };

                //Funktion die alle Werte auswählt und wenn alle Werte ausgewählt wurden, dann
                //werden alle wieder abgewählt
                scope.Fct.selectAll = function(data) {
                    var select = true;
                    //Wenn alle ausgewählt sind, dann alle abwählen.
                    if (scope.Fct.allSelected(data)) {
                        select = false;
                    }
                    if (data !== undefined && data !== null) {
                        for (var i = 0; i < data.length; i++) {
                            data[i].Selected = select;
                        }
                    }
                };

                //Gibt zurück ob alle Werte in der Liste ausgewählt wurden.
                scope.Fct.allSelected = function(data) {
                    if (data !== undefined && data !== null) {
                        for (var i = 0; i < data.length; i++) {
                            if (!data[i].Selected) {
                                return false;
                            }
                        }
                    }
                    return true;
                };

                //Gibt True zurück, wenn kein Wert in der Liste ausgewählt wurde.
                scope.Fct.noneSelected = function(data) {
                    if (data !== undefined && data !== null) {
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].Selected) {
                                return false;
                            }
                        }
                    }
                    return true;
                }

                //Custom Validation einbinden für Required - Das value ist hier ngModel was immer übergeben wird.
                //vom $parsers bzw. $formatters
                var validator = function (value) {
                    //Einbinden einer Required Validierung, es wird das "required" Attribut unterstützt
                    //und nur wenn auch ein Wert im ngModel (selectedValue) gesetzt ist, ist die Validation True!
                    if (attr.required !== undefined) {
                        if (scope.Fct.noneSelected(value)) {
                            ngModel.$setValidity("required", false);
                        } else {
                            ngModel.$setValidity("required", true);
                        }
                    }
                    //WICHTIG den Eingabewert auch zurückgeben, sonst landet im Model kein Wert mehr!
                    return value;
                }

                //Unseren Validator den passenden Listen hinzufügen.
                ngModel.$parsers.unshift(validator);    //view-to-model direction
                ngModel.$formatters.unshift(validator); //model-to-view direction

                //Prüfen ob sich die Liste ändert
                //http://www.bennadel.com/blog/2566-scope-watch-vs-watchcollection-in-angularjs.htm
                scope.$watch('listEntries', function (newVal, oldVal) {
                    if (newVal !== undefined && newVal !== oldVal) {
                        //Wenn die Werte unterschiedlich sind, dann ebenfalls die Methode für Change aufrufen!
                        scope.valueChanged();
                        //Wenn der Wert einfach nur "geändert" wird, muss auch $dirty und $pristine "gesetzt" werden
                        //dies passiert automatisch über "ngModel.$setViewValue(ngModel.$viewValue);"
                        ngModel.$setViewValue(ngModel.$viewValue);
                    }
                }, true); //TRUE NOTWENDIG!
            }
        };
    })

3. Verwendung

JavaScript

var app = angular.module("app.ddl", ["ui.bootstrap", "select.directives"]);

app.controller("ddlCtrl", function ($scope, $log) {
    $scope.multiSelectItems = {
        users: [
            {
                Text: "Ray Jones",
                Selected: true,
                joined: 2012
            },
            {
                Text: "Lana Lane",
                Selected: false,
                joined: 2001
            },
            {
                Text: "Titus Jonas",
                Selected: true,
                joined: 1995
            },
            {
                Text: "Serena Wuh",
                Selected: true,
                joined: 1990
            }
        ]
    };

    $scope.Fct = {};
    $scope.Fct.LogChange = function(entry) {
        $log.log(entry);
    }

});

HTML

<div ng-app="app.ddl" ng-controller="ddlCtrl">
    <form name="frm">
        <div class="row">
            <div class="col-md-12"><strong>Multiselect mit AngularUi</strong></div>
        </div>
        <div class="row">
            <div class="col-md-3">
                <div required name="multiPersonSelect" sq-multiselect ng-model="multiSelectItems.users" value-changed="Fct.LogChange(multiSelectItems.users)"></div>
            </div>
        </div>
    </form>
</div>

 

Das ganze kann natürlich beliebig erweitert werden. Auf meinem CodePlex Account findet Ihr die Quellcodes und eine funktionierende MVC Anwendung. Außerdem ist dort auch noch eine Implementierung einer einfachen Select Dropdown Liste im Bootstrap Style wie die Multiselect Liste vorhanden.

Ich weiß leider nicht ob meine Umsetzung dem Standard entspricht, denn ich habe leider kein ähnliches Control gefunden, welches ebenfalls die Form Validation von AngularJS unterstützt. Über Vorschläge und Hinweise freue ich mich sehr :-)

Font Awesome 4.0.0 veröffentlicht (Bootstrap 3 kompatibel)


Endlich wurde auch Font Awesome in einer aktuellen Version 4.0.0 veröffentlicht. Ab dieser Version ist Font Awesome auch Bootstrap 3.0.0 kompatibel. Was soviel heißt, das beide Frameworks inzwischen auf Fonts setzten und auch gleichzeitig verwendet werden können. Font Awesome wurde für die Version 4.0.0 komplett neu geschrieben, dabei wurde wert auf eine bessere Kompatibilität und höhere Schnelligkeit gesetzt, denn in Version 4.0.0 sind nur 10 neue Icons dazugekommen.

Ich habe in einem Beispiel MVC4 Projekt Font Awesome 4 und Bootstrap 3 per nuget installiert und das ganze sieht dann in der Ordnerstruktur folgendermaßen aus:

image

Hier werden die Schriften von Bootstrap und Font Awesome problemlos im gleichen Verzeichnis abgelegt und an der prinzipiellen Verwendung von Font Awesome hat sich nichts geändert und diese können parallel zu den Bootstrap Icons verwendet werden.

Einbinden des power off Symbols in einen Link:

<i class="fa fa-power-off fa-lg"></i>
         

Leider ist es aktuell nicht mehr so leicht die Bootstrap 3 less Dateien in einem MVC Projekt einzubinden und mittels Less.js zu kompilieren (css zu erzeugen). Hier kann es zu diversen Kompilierungsproblemen kommen, daher verwende ich aktuell die CSS Dateien direkt.

Bootstrap 3 RC1–Released


Nur zur Info – Twitter Bootstrap ist in der Version 3 RC1 Released worden. Sobald ich mehr Zeit habe und es nicht mehr so warm draußen ist, schaue ich mir das ganze mal im Detail an.

Was ich aber bisher bereits gesehen habe schaut sehr gut aus, vor allem die neuen “Panels” zum besseren Visualisieren von bestimmten Content Inhalten schauen sehr cool aus

image

image

Außerdem ist es jetzt auch möglich die Größe von Eingabefeldern bzw. Eingabekombinationen zu verändern.

image

image

Außerdem wirkt das ganze Layout etwas reduzierter und “flacher”, einfach gesagt etwas zeitgemäßer wenn man die aktuellen Trends im Layout verfolgt. Was man z.B. an den Buttonstyles ganz gut erkennen kann.

image

Quelle: http://twitter.github.io/bootstrap/

MVC Custom/Benutzerdefinierte Messages “verwalten” und zwischen Views und Controllern übergeben und anzeigen – v2.0


Es ist schon eine Weile her, da habe ich in “Grundzügen” in einem Blogpost gezeigt wie man Messages (Meldungen) in MVC so erweitert, das man auch eigene Meldungen anzeigen kann und nicht nur Fehlermeldungen anzeigt. Das ganze habe ich jetzt ein wenig aufgebohrt und mit Twitter Bootstrap Alert Styles umgesetzt. Mit der neuen Methode, ist es möglich gleichzeitig beliebig viele Fehlermeldungen und Messages anzuzeigen, die zum einen z.B. aus dem Modelstate Fehlern zusammengesetzt werden und aus den eigenen Meldungen bestehen. Die eigenen Meldungen müssen hier keine Fehler sein, es kann sich z.B. um Positive Bestätigungen oder Warnmeldungen handeln.

image

Das ganze setzt sich aus einem HTML Helper zusammen der einfach im View aufgerufen wird und die Meldungen dann nur noch darstellt. Die eigenen Meldungen, die wir ausgeben wollen, werden dabei im TempData abgelegt. Diese Variable wir automatisch gelöscht, nach dem Sie einmal abgerufen wurde und wir müssen uns um “nichts” kümmern. Wobei die Messages durch die Modelvalidation, werden immer als Fehler dargestellt und aus dem Modelstate abgerufen.

/// <summary>
/// Prüft ob im TempData Messages hinterlegt wurden und wenn ja dann werden diese angezeigt.
/// nach einem Request ist Tempdata auch direkt wieder leer, ist nur für einen Request aktiv!
/// Außerdem wird überprüft ob die Modeldaten ebenfalls einen Fehler enthalten und dieser wird hier ebenfalls ausgegeben
/// 
/// http://stackoverflow.com/questions/4642845/asp-net-mvc-how-to-preserve-modelstate-errors-across-redirecttoaction
/// </summary>
/// <returns>HTML Konstrukt was eine Custom Message anzeigt.</returns>
public static MvcHtmlString ShowCustomMessage(this HtmlHelper helper)
{
    //Eine neue Messageliste initialisieren
    List<Message> messages = new List<Message>();

    //Prüfen ob der passende Einträge im TempData vorhanden sind, die wird dort selbst abgelegt haben
    if (helper.ViewContext.Controller.TempData[CustomMessage.TempMessageString] != null)
    {
        //den Tempdata Liste der Nachrichten abrufen
        messages = (List<Message>)helper.ViewContext.Controller.TempData[CustomMessage.TempMessageString];
    }

    //Nur prüfen ob Fehler im Modelstate sind, wenn dieser auch nicht valide ist.
    if (!helper.ViewData.ModelState.IsValid)
    {
        //Nach Meldungen die Vom System im Modelstatus abgelegt wurden suchen
        //Ebenfalls den aktuellen Modelstatus nach meldungen durchsuchen, dabei handelt es sich immer um Fehlermeldungen!
        foreach (var key in helper.ViewData.ModelState.Keys)
        {
            foreach (var err in helper.ViewData.ModelState[key].Errors)
            {
                //Bei den Meldungen im Modelstate handelt es sich immer um einen Fehler. Die Fehler dem Modelstate hinzufügen
                messages.Add(new Message(helper.Encode(err.ErrorMessage), MessageTypes.error));
            }
        }    
    }

    string html = string.Empty;
    //Unsere Messages erstellen. Dabei wird jede Message in einer eigenen Fehlermeldung ausgegeben und sortiert ausgeben von Fehler nach OK
    foreach (Message message in messages.OrderByDescending(p => p.MessageType))
    {
        //Festlegen um welchen Fehlertyp es sich handelt
        string errorClass = GetMessageTypeCssString(message.MessageType);

        //Den HTML String zusammenbauen mit den Messages
        html += string.Format("<div class=\"{0}\"><button class=\"close\" data-dismiss=\"alert\" type=\"button\">×</button>{1}</div>", errorClass, message.Text);
    }

    //Damit der HTML String auch als HTML auf der Webseite ausgegeben werden kann, diesen noch umwandeln und den Rückgabewert des Helpers anpassen.    
    return new MvcHtmlString(html);
}

/// <summary>
/// Gibt den passenden CSS Code für den übergebenen Messagetype zurück
/// </summary>
/// <param name="message">Der MessageType für den der passende CSS Code zurück gegeben werden soll</param>
private static string GetMessageTypeCssString(MessageTypes message)
{
    string errorClass = string.Empty;
    switch (message)
    {
        case MessageTypes.error:
            errorClass = "alert alert-error";
            break;
        case MessageTypes.warning:
            errorClass = "alert";
            break;

        case MessageTypes.ok:
            errorClass = "alert alert-success";
            break;

        case MessageTypes.message:
            errorClass = "alert alert-info";
            break;
        default:
            errorClass = "alert";
            break;
    }

    return errorClass;
}

Die eigenen Messages setzten sich dabei aus einem Messagestring und einem Messagetyp zusammen. Der Messagetyp gibt an um welche Art von Meldung es sich handelt, z.B. Fehler, Warnung, Ok oder Meldung.

/// <summary>
/// Gibt an, welcher MessageType auf der Oberfläche angezeigt werden soll, wird im Viewbag abgelegt.
/// </summary>
public enum MessageTypes
{
    ok,
    warning,
    error,
    message
}

/// <summary>
/// Wird zum Darstellen von Nachrichten auf der Webseite benötigt, wenn diese über Views hinweg "versendet" werden.
/// </summary>
public class Message
{
    public string Text { get; set; }
    public MessageTypes MessageType { get; set; }

    /// <summary>
    /// Konstruktor für die Message
    /// </summary>
    /// <param name="text">Der Text der angezeigt werden soll</param>
    /// <param name="messageTypes">Um die Art der Nachricht die angezeigt werden soll</param>
    public Message(string text, MessageTypes messageTypes)
    {
        MessageType = messageTypes;
        Text = text;
    }
}

Das Verwalten der CustomMessages wird mit der folgenden Klasse umgesetzt. Wichtig ist hier, das wir immer den Aktuellen Controller mit übergeben müssen, damit wir auf TempData zugreifen können um die Messages hinzufügen zu können. Im Tempdata befindet sich nur eine Liste mit den jeweils von uns zuletzt hinzugefügten CustomMessages und wenn wir eine neue Message hinzufügen, wird die Liste um diese Meldung erweitert.

/// <summary>
/// Custom Message, damit Nachrichten auch über Views hinweg dargestellt werden können.
/// </summary>
public class CustomMessage
{
    #region static
    /// <summary>
    /// Gibt eine Instanz von CustomMessage zurück um Benutzerdefinierte Messages über Views hinweg anzeigen zu können.
    /// http://stackoverflow.com/questions/4642845/asp-net-mvc-how-to-preserve-modelstate-errors-across-redirecttoaction
    /// </summary>
    /// <param name="controller">Der CurrentController damit auf TempData zugegriffen werden kann.</param>
    /// <returns></returns>
    public static CustomMessage CurrentMessage(Controller controller)
    {
        return new CustomMessage(controller);
    }
    #endregion

    #region Member
    /// <summary>
    /// Constante die für den Tempdata[TempMessageString] benutzt wird.
    /// </summary>
    public const string TempMessageString = "CMTempMessageString";

    /// <summary>
    /// Der aktuelle CurrentController um auf 
    /// </summary>
    public Controller CurrentController { get; set; }
    #endregion

    #region Konstruktor
    /// <summary>
    /// Initialisieren der Methode, der CurrentController wird benötigt damit man auf das TempData zurückgreifen kann
    /// </summary>
    /// <param name="controller">Der aktuelle CurrentController damit TempData verwendet werden kann</param>
    private CustomMessage(Controller controller)
    {
        CurrentController = controller;
    }
    #endregion

    #region Public Functions
    /// <summary>
    /// Hinzufügen der Custommessage zu den TempData, damit dieser Mittels des passendne HTMLHelpers 
    /// auf der passenden Seite angezeigt werden kann.
    /// </summary>
    /// <param name="message">Die Message die angezeigt werden soll</param>
    /// <param name="messageType">Der Messagetyp der angezeigt werden soll</param>
    public void AddMessage(string message, MessageTypes messageType)
    {
        if (message != null)
        {
            //Eine Liste erstellen, damit wird jetzt jede Meldung hinzugefügt und alle können angezeigt werden.
            List<Message> messages = (List<Message>) CurrentController.TempData[TempMessageString];
            //Wenn noch keine Nachrichten hinterlegt wurden, muss die Liste initialisiert werden
            if (messages == null)
            {
                messages = new List<Message>();
            }

            messages.Add(new Message(message, messageType));

            //Die Message im TempData hinterlegen, damit diese dann von der Passenden Helper Methode dargestellt werden kann.
            CurrentController.TempData[TempMessageString] = messages;
        }
    }
    #endregion
}

Das Hinzufügen von CustomMessages ist überall da möglich wo Ihr Zugriff auf euren Controller habt, ich übergebe meist auch an mein Model den aktuellen Controller, um gleich in der Logik die passenden Messages hinzufügen zu können.

 CustomMessage.CurrentMessage(this).AddMessage("Warnung 1", MessageTypes.warning);
 CustomMessage.CurrentMessage(this).AddMessage("Warnung 2", MessageTypes.warning);
 CustomMessage.CurrentMessage(this).AddMessage("Bitte passen Sie auf!", MessageTypes.message);
 CustomMessage.CurrentMessage(this).AddMessage("Speichervorgang erfolgreich", MessageTypes.ok);

Im View selbst wird nur der HTML Helper “ShowCustomMessage” aufgerufen und dieser rendert jeden Fehlermeldung in einer Eigenen Box.

<div class="row">
    <div class="span12">
        @Html.ShowCustomMessage()
    </div>
</div>

Damit das ganze auch “gut” aussieht, ist noch die Einbindung von Twitter Bootstrap notwendig. Ihr könnt aber auch selbst die HTML Templates an eure Bedürfnisse anpassen.

jQueryUi und moment.js aus Rangeslider wird ein Timeslider


Da ich für ein privates Projekt einen Zeitslider benötigte habe, habe ich mich einfach bei dem bereits vorhandenen Slider von jqueryUi bedient und diesen dementsprechend angepasst, das dieser jetzt die Uhrzeit “anzeigt”. Das ganze sieht dann z.B. mit Twitter Bootstrap folgendermaßen aus:

image

Mit dem Slider kann die jeweils passende Uhrzeit ausgewählt werden und auch über die “Plus” und “Minus” Symbole an den jeweiligen enden lässt sich die Zeit einstellen. Die Uhrzeit wird in zwei getrennten Textboxen angezeigt und entsprechend aktualisiert.

Damit ich mich nicht groß um das Umrechnen und Anzeigen der Uhrzeit kümmern muss habe ich hier noch auf das NuGet Package von “moment.js” zurückgegriffen. Es wird außerdem die aktuellste Version von jQuery und jQueryUi inkl. der passenden jQueryUiStyles benötigt.

Das folgende HTML Konstrukt dient als Grundlage für den JavaScriptCode

&lt;label for=&quot;amount&quot;&gt;Timerange: &lt;/label&gt;
&lt;input id=&quot;StartTime&quot; readonly=&quot;readonly&quot; class=&quot;text-box single-line input-mini uneditable-input&quot;
    type=&quot;time&quot; value=&quot;07:00&quot; name=&quot;StartTime&quot; data-val=&quot;true&quot;&gt;
bis
&lt;input id=&quot;EndTime&quot; readonly=&quot;readonly&quot; class=&quot;text-box single-line input-mini uneditable-input&quot;
     type=&quot;time&quot; value=&quot;16:00&quot; name=&quot;EndTime&quot; data-val=&quot;true&quot;&gt;
Uhr

    &lt;div style=&quot;margin-top: 5px; height: 23px;&quot; class=&quot;well&quot;&gt;
        &lt;div class=&quot;row&quot;&gt;
            &lt;div class=&quot;span11&quot;&gt;
                &lt;div id=&quot;slider-range&quot;&gt;&lt;/div&gt;
            &lt;/div&gt;
            &lt;div class=&quot;row&quot;&gt;
                &lt;div style=&quot;padding-left: 20px;&quot; class=&quot;span1&quot;&gt;
                    &lt;i id=&quot;addOneStart&quot; class=&quot;icon-plus&quot;&gt;&lt;/i&gt;
                    &lt;i id=&quot;subOneStart&quot; class=&quot;icon-minus&quot;&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class=&quot;span9&quot;&gt;&lt;/div&gt;
                &lt;div style=&quot;margin-right: -10px&quot; class=&quot;span1 pull-right&quot;&gt;
                    &lt;i id=&quot;subOneEnde&quot; class=&quot;icon-minus&quot;&gt;&lt;/i&gt;
                    &lt;i id=&quot;addOneEnde&quot; class=&quot;icon-plus&quot;&gt;&lt;/i&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

Den Slider definierten und die Startwerte in den Textboxen auslesen. Unter “values” werden im Slider die Standardstartwerte angegeben in Minuten. Außerdem ist der “max” wert mit 1440 belegt, was 24 Stunden * 60 Minuten entspricht und die Minuten für einen Ganzen Tag ergibt. Die Stepsize kann extra definiert werden, da wir auf diese später noch zugreifen.

var stepSize = 5; //anzahl der Minuten die gesetzt werden sollen

//Slider definieren
var slider = $(&quot;#slider-range&quot;).slider({
    range: true,
    min: 0,
    max: 1440,
    step: stepSize,
    values: [420, 900],
    slide: function (event, ui) {
        //Die Start und Endezeit des Sliders ermitteln die gerade ausgeählt sind.
        var start = moment(&quot;HH:mm&quot;, &quot;00:00&quot;).minutes(ui.values[0]),
            ende = moment(&quot;HH:mm&quot;, &quot;00:00&quot;).minutes(ui.values[1]);
        $(&quot;#StartTime&quot;).val(start.format(&quot;HH:mm&quot;));
        $(&quot;#EndTime&quot;).val(ende.format(&quot;HH:mm&quot;));
    }
});

//Beim Start des Programm die passende Start und Endezeit in die Textboxen eintragen.
var start = moment(&quot;HH:mm&quot;, &quot;00:00&quot;).minutes($(&quot;#slider-range&quot;).slider(&quot;values&quot;, 0)),
    ende = moment(&quot;HH:mm&quot;, &quot;00:00&quot;).minutes($(&quot;#slider-range&quot;).slider(&quot;values&quot;, 1));
$(&quot;#StartTime&quot;).val(start.format(&quot;HH:mm&quot;));
$(&quot;#EndTime&quot;).val(ende.format(&quot;HH:mm&quot;));

Hinzufügen unserer Click Eventhandler für die “Plus” und “Minus” Zeichen am Slider. Diese rufen die jeweils passende Funktion zum setzten des Linken bzw. Rechten Sliders auf.

 //Eventhandler erstellen für die + und - Bilder zum setzten der passenden Zeiten
 $(&quot;#addOneStart&quot;).on(&quot;click&quot;,function () {
     setSliderStart(0, '#StartTime', stepSize);
 });
 
 $(&quot;#subOneStart&quot;).on(&quot;click&quot;, function () {
     setSliderStart(0, '#StartTime', -1 * stepSize);
 });
 
 $(&quot;#addOneEnde&quot;).on(&quot;click&quot;, function () {
     setSliderEnde(1, '#EndTime', stepSize);
 });

 $(&quot;#subOneEnde&quot;).on(&quot;click&quot;, function () {
     setSliderEnde(1, '#EndTime', -1 * stepSize);
 });

Dann fehlen noch unsere beiden Funktionen die die jeweiligen “Plus” und “Minus” Operationen für den Linken und Rechten Slider ausführen. Wir benötigen in beiden Funktionen immer die aktuelle Start- und Endezeit des Sliders, damit wir überprüfen können ob der Slider auch nicht zu weit nach rechts oder links bewegt wird und damit ein ungültiger Zustand entsteht.

Für das Setzten des linken Sliders wird die Funktion “slider.slider(‘values’, 0, startMinutes + diff);” verwendet und für das Setzten des rechten Sliders wird die Funktion “slider.slider(‘values’, 1, startMinutes + diff);” verwendet (ich übergebe hier den passenden ‘index’).

//Den Startzeitslider setzten
function setSliderStart(index, timeId, diff) {
    //Auslesen der aktuellen Oberflächenwerte
    var startTime = $(&quot;#StartTime&quot;).val(),
    endTime = $(&quot;#EndTime&quot;).val(),
    ende = moment(endTime, &quot;HH:mm&quot;),
    start = moment(startTime, &quot;HH:mm&quot;),
        //die passenden Start und Endeminuten berechnen die aktuell angezeigt werden.
    startMinutes = start.hours() * 60 + start.minutes(),
    endMinutes = ende.hours() * 60 + ende.minutes();
    //Prüfen das die Slider sich nicht &quot;vertauschen&quot; Min und Max Value prüfen
    if ((startMinutes + diff) &lt;= endMinutes &amp;&amp; (startMinutes + diff) &gt;= 0) {
        //http://stackoverflow.com/questions/2833396/jquery-ui-slider-setting-programatically
        //der Index gibt an welcher Slider gesetzt werden soll der für Min oder Max
        slider.slider(&quot;values&quot;, index, startMinutes + diff);
        //Prüfen ob die Minuten addiert oder Subtrahiert werden sollen
        if (diff &lt; 0) {
            $(timeId).val(start.subtract(&quot;m&quot;, stepSize).format(&quot;HH:mm&quot;));
        } else {
            $(timeId).val(start.add(&quot;m&quot;, stepSize).format(&quot;HH:mm&quot;));
        }
    }
}

//Den Endezeitslider setzten
function setSliderEnde(index, timeId, diff) {
    var startTime = $(&quot;#StartTime&quot;).val(),
    endTime = $(&quot;#EndTime&quot;).val(),
    ende = moment(endTime, &quot;HH:mm&quot;),
    start = moment(startTime, &quot;HH:mm&quot;),
    startMinutes = start.hours() * 60 + start.minutes(),
    endMinutes = ende.hours() * 60 + ende.minutes();
    if (startMinutes &lt;= (endMinutes + diff) &amp;&amp; (endMinutes + diff) &lt; 1440) {
        slider.slider(&quot;values&quot;, index, endMinutes + diff);
        if (diff &lt; 0) {
            $(timeId).val(ende.subtract(&quot;m&quot;, stepSize).format(&quot;HH:mm&quot;));
        } else {
            $(timeId).val(ende.add(&quot;m&quot;, stepSize).format(&quot;HH:mm&quot;));
        }
    }
}

Leider funktioniert der Slider noch nicht auf Touchgeräten, daher habe ich auch die “Plus” und “Minus” Zeichen eingefügt. Es sollte auch kein Problem darstellen das Projekt zu erweitern und die Textfelder bearbeitbar zu machen für Touchgeräte.

Quellen:

http://stackoverflow.com/questions/2833396/jquery-ui-slider-setting-programatically

http://jqueryui.com/slider/#range

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier dann die Wichtigste Datei im Webprojekt unter “Views/Home/TimeSlider.cshtml”. Das Projekt sollte auch so lauffähig sein.