Archiv der Kategorie: jQuery

MVC WebAPI und Serialisieren von Eingabedaten ohne Form Submit


Die Tage hatte ich das Problem, dass ich auf einer Seite noch ein paar Eingabefelder für einen Filter unterbringen musste. Es war mir leider nicht möglich ein zweites Form Tag dafür zu erstellen, daher habe ich die Elemente mit in das Hauptformular übernommen.

Ich benötigte für meinen Ajax Request an die WebAPI aber nur die Eingabefelder für meinen Filter und nicht die gesamten Formulardaten und ich wollte auch nicht jedes Eingabefeld einzeln heraussuchen und die Werte per Hand zusammensetzten.

Wenn man dann weiß das man mit jQuery problemlos Formulare Serialisieren kann und man kurz im Internet sucht, findet man auch schnell eine Möglichkeit wie man nur bestimmte Eingabefelder serialisieren kann ohne dabei das ganze Formular zu nutzen. Ich habe einfach um meine Suchfelder ein DIV mit einer ID gesetzt und dann per jQuery und einem passenden Selector die passenden Daten erhalten.

<input id="currentDate" class="span35" type="date" name="currentDate">

<div id="searchDiv" class="well well-small">
  <div class="btn-block">
    <select id="FilterWorktimeTypeList" class="span42" name="FilterWorktimeTypeList">
   </div>
   <div class="input-append btn-block">
     <input id="filterText" class="span35" type="text" name="SearchText">
     <button id="filterBtn" class="btn" type="button">Filtern</button>
  </div>
</div>

Da currentDate nicht innerhalb unseres SuchDIVs liegt, müssen wir den Inhalt, danach per Hand zu unseren Daten hinzufügen. Wichtig ist, das jedes Eingabefeld einen Namen hat, denn sonst wird es nicht serialisieren. Außerdem kann nicht die Hausmethode von jQuery zum serialisieren verwendet werden, sondern die Funktion “serializeObject” muss verwendet werden und erst in Verbindung mit “JSON.stringify” wird unser Datenobjekt auch von der WebAPI “erkannt”.

//wir suchen alle Inputfelder unterhalb unseres Divs heraus.
var searchDiv = $("#searchDiv :input")
//Wir serealisieren erst unser "Formular" aka unser SuchDiv
var jsonData = searchDiv.serializeObject();
//Da im Suchdiv nicht unser Datum mit enthalten ist, fügen wir es unserem Objekt mit hinzu.
jsonData["Datum"] = $("#currentDate").val();
//Wir müssen unsere serealisieren Daten noch so umwandeln, das diese auch von der WebAPI erkannt werden
jsonData = JSON.stringify(jsonData);

Die Funktion “serializeObject” habe ich im Netz gefunden und funktioniert wunderbar. Die Funktion selbst greift auf die jQuery Funktion “serializeArray” zurück. Diese Standardfunktion reicht leider nicht aus um ein gültiges Objekt zu erstellen, was die WebAPI als Objekt erkennt und umwandelt.

//jQuery Plugin um z.B. direkt Inputfelder und derren Inhalt zu serealisieren als Objekt!
//http://stackoverflow.com/questions/1184624/convert-form-data-to-js-object-with-jquery
$.fn.serializeObject = function()
{
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
        if (o[this.name] !== undefined) {
            if (!o[this.name].push) {
                o[this.name] = [o[this.name]];
            }
            o[this.name].push(this.value || '');
        } else {
            o[this.name] = this.value || '';
        }
    });
    return o;
};

Damit die Daten dann auch entsprechend an die WebAPI als Model übergeben werden können benötigen wir ein passendes C# Model für die Daten.

public class WorktimeEntryFilterModel
{
  /// <summary>
  /// Der Text nach dem gesucht werden soll.
  /// </summary>
  public string SearchText { get; set; }

  /// <summary>
  /// Die ausgewählte ID der WorktimeTypeList für die Filter
  /// </summary>
  public int? FilterWorktimeTypeList { get; set; }

  /// <summary>
  /// Der Datumsstring aus dem Model.
  /// </summary>
  public string Datum { get; set; }
}

Wichtig ist, das ich bei meinen WebAPI Aufrufen eine extra Route eingebunden habe in der WebApiConfig, wieso und weshalb das könnt Ihr hier in einem anderen Artikel nachlesen.

Der Beispielhafte Aufruf unserer Daten “jsonData” kann dann z.B. folgendermaßen aussehen.

$.ajax({
   url: "/api/worktimeApi/ByTextFilter/@Model.UserId",
   type: "POST",
   data: jsonData
   contentType: 'application/json; charset=utf-8',
   statusCode: {
        200 /* OK */: function(daten) {
            //Do some fun with the Data
            }
   }
});

und die passende WepAPI Funktion die unsere Daten entgegennimmt und verarbeitet.

/// <summary>
/// Filtert den Eintrag nach den übergebenen Modeldaten
/// </summary>
/// <param name="id">Die UserId nach der gefiltert werden soll</param>
/// <param name="value">Unsere Modeldaten nach denen gefiltert werden soll</param>
public HttpResponseMessage ByTextFilter(long id, WorktimeEntryFilterModel value)
{
    if (value != null)
    {
        WorktimeListModel worktime = new WorktimeListModel(id, value);
        worktime.FilterWorktimeList();

        //Wird automatisch in JSON Objekt umgewandelt und gibt den StatusCode 200 für OK zurück!
        HttpResponseMessage response = Request.CreateResponse<WorktimeListModel>(HttpStatusCode.OK, worktime);
        response.Headers.Location = new Uri(Request.RequestUri, string.Format("/api/WorktimeApi/ByTextFilter/{0}", id.ToString()));
        return response;
    }

    throw new HttpResponseException(HttpStatusCode.BadRequest);
}

Damit ist es problemlos möglich nur kleine Teile eines großen Formulars zu benutzten. Vielleicht existiert auch kein Formular und damit hat man die Möglichkeit die Daten trotzdem problemlos zu serialisieren und zu versendet.

Advertisements

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.

MVC Client Datenvalidierung für “dd.MM.yyyy” mit jQuery Globalize


Ich hatte bei meiner MVC Anwendung das Problem, das die Clientseitige Validierung eines Datums aus meinem Model als nicht “richtig” erkannt wurde und damit ist die Clientseitige Validierung fehlgeschlagen. Dabei wurde das Datumsformat in Browser als “dd.MM.yyyy” angezeigt und als ungültiges Datum erkannt, trotz eines gültigen Datums.

Model:

[Required]
[Display(Name = "Jobstartdate", ResourceType = typeof(WorktimeViews))]
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }

Fehlermeldung im Browser:

image

Als erstes habe ich das NuGet Package “jquery-globalize” zu meinem Projekt hinzugefügt, hier habe ich aber nur einige der “Sprachen” im Projekt behalten.

image

Dann habe ich das Bundle für die JavaScript Bibliotheken angepasst und die Globalize Funktionen nach meinen anderen jQuery Bibliotheken eingefügt.

  bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*", 
                        "~/Scripts/jquery.globalize/globalize.js",
                        "~/Scripts/jquery.globalize/cultures/globalize.culture.de-DE.js",
                        "~/Scripts/_myjQueryExtensions.js"));

und in meiner eigenen “_myjQueryExtensions.js” noch die folgenden Zeilen an Code hinzugefügt. In der Document Ready Funktion gibt man noch an welche Sprache aktuell für die Validierung genutzt werden soll.

jQuery.validator.methods.date = function(value, element) {
    return Globalize.parseDate(value);
};

$.validator.methods.number = function(value, element) {
    if (Globalize.parseFloat(value)) {
        return true;
    }
    return false;
};

jQuery(document).ready(function () {
    Globalize.culture("de-DE");
});

Dann wurde mein Datum “dd.MM.yyyy” als gültiges Datum erkannt und es gab KEINEN Validation Error mehr. Selbiges geht wohl auch für Zahlenwerte ob Komma oder Punkt als Tausendertrennzeichen verwendet werden soll. Das ganze lässt sich auch ausbauen, damit die richtige Sprache verwendet wird, je nach Client der sich die Webseite anzeigen lässt in meinem Beispiel habe ich hier hart die “de-DE.js” verwendet.

Probleme gab es bei mir im Chrome v23.xx mit dem Datum hier wurde vom Chrome ein “falsch” formatiertes Datum an die parseDate Methode übergeben. Außerdem gibt es Probleme mit der Range Validation Methode im MVC und Double werten.

Quellen:

http://blog.brainnovative.com/2010/12/globalizing-aspnet-mvc-unobtrusive.html

http://www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/adding-validation-to-the-model