BigBastis Blog About Me & my Digital Lifestyle

25Jan/124

ASP.NET MVC: HTML5 Elemente mit jQuery Fallback nutzen

Soo, nachdem ich die Überschrift mit all den hippen Begriffen vollgepackt habe die es in Sachen Web momentan so gibt möchte ich euch kurz erklären worum es in diesem Artikel gehen soll.

Ihr werdet sicher mitbekommen haben, dass HTML5 auf dem Vormarsch ist und auch wenn es immer noch nicht offiziell fertig ist findet man immer häufiger den HTML5-Header <!DOCTYPE html> auf diversen Seiten.

Eins der meiner Meinung nach nützlichsten Features die wir mit HTML5 erhalten sind die neuen Input-Typen. Denn bisher hatten wir keine Wahl und mussten type="text" benutzen!

Die Eingabefelder vom Typ Text konnten natürlich alles enthalten, sind aber nicht gerade Benutzerfreundlich wenn man etwas komplexere Daten eingeben soll wie zum Beispiel ein Datum.

In HTML5 wurden deswegen spezifische Typen für solche immer wieder einkehrende Eingaben eingeführt. Eine von ihnen ist &quot;Date&quot;:


Birthday: <input type="date" name="bday" />

Der große Vorteil hier ist, dass die Browser nun eine eigene Implementierung für diesen Typ von Eingabefeldern machen können. Falls der Browser nun also diesen neuen Eingabe Typ unterstützt blendet er automatisch einem (mehr oder weniger) schönen Date-Picker ein in dem man bequem das Datum wählen kann:

Ansicht des neuen Typen in verschiedenen Browsern

Ein weiterer großer Vorteil ist, dass auch die mobilen Browser wie z.B. auf dem iPhone diese neuen Elemente ebenfalls unterstützen und einen passenden Picker einblenden:

Date-Picker auf dem iPhone

Wie ihr in dem ersten Screenshot sehen könnt unterstützt der FireFox (zumindest zum Zeitpunkt des Screenshots) den neuen Typ nicht. In diesem Fall blendet er ein gewöhnliches Textfeld ein wo wir nun gezwungen sind das Datum auf die gewohnte (unbequeme) Weise einzutragen.

Übrigens, ich glaube ich muss euch nicht sagen warum der Internet Explorer hier nicht mit aufgeführt ist oder? ;-)

Und genau das ist das Problem um das es in diesem Blogpost geht. Wenn wir die tollen neuen Elemente nutzen gehen wir das Risiko ein, dass Nutzer die einen älteren Browser nutzen nicht in den Genuss einer bequemen Eingabe kommen.

jQuery Date-Picker

Wir könnten auch komplett auf das HTML5 Element verzichten und direkt alles mit jQuery machen, welches auch einen Date-Picker bietet, aber das ist auch nicht das gelbe vom Ei, denn dann wären die Mobilen Nutzer in Nachteil, da dort die Browser diese Elemente für gewöhnlich unterstützen und eine gewohnte Oberfläche für die Eingabe bieten.

Die Optimale Lösung ist offensichtlich eine Mischung beider Welten. Wenn der Benutzer die Seite mit einem kompatiblen Browser aufruft soll der Date-Picker des Browsers genutzt werden, wenn der Browser aber veraltet ist soll stattdessen der jQuery Fallback greifen und der JavaScript Date-Picker (siehe links) genutzt werden.

Zu unserem Glück macht uns ASP.NET MVC die Umsetzung dieses Plans sehr leicht, da es an den nötigen Stellen sehr einfach erweitert werden kann.

Schauen wir uns mal ein kleines Beispiel an. Wir haben eine Klasse Person, und wollen auch dessen Geburtstag speichern, hier wollen wir die tolle neue Funktionalität nutzen. Implementieren wir das Ganze jedoch erstmal wie gewohnt:


    public class Person
    {
        public int ID { get; set; }
        public String Name { get; set; }
        public DateTime Geburtstag { get; set; }
    }

Wenn wir die Model-Klasse haben können wir das MVC Scaffolding nutzen um für uns eine View zum Erstellen neuer Personen anzulegen:

MVC Scaffolding kommt uns zu Hilfe

Schauen wir uns nun doch mal an was da für uns tolles generiert wurde:


    <div class="editor-field">
        @Html.EditorFor(model => model.Geburtstag)
        @Html.ValidationMessageFor(model => model.Geburtstag)
    </div>

Auffällig hierbei ist, dass hier @Html.EditorFor genutzt wird und nicht etwa @Html.TextBoxFor! Der Hintergrund ist der, dass MVC bei der Generierung versuchen wird ein passendes Element für den angegebenen Datentyp (DateTime) zu bestimmen, aber da MVC standardmäßig nur den Typ Text kennt wird hier eigentlich immer ein Input-Element vom Typ text generiert.

Hier kommt uns die Erweiterbarkeit von MVC zu gute, denn wir können ganz einfach NuGet nutzen um uns die nötige Funktionalität zu verschaffen. Wir installieren das Packet MvcHtml5Templates.

Installation über NuGet

Neue Templates

Nachdem NuGet alles erledigt hat werdet ihr feststellen, dass ihr ein paar neue Dateien in eurem Views/Shared Verzeichnis habt (siehe Bild links). Und wie ihr schon an dem Namen erkennen könnt sind das die neuen Input-Typen aus HTML5.

Diese Templates machen im Grunde nichts anderes als die MVC-Eigenen zu überschreiben und versehen die mit dem passendem Type-Attribut.

Hier sind euch keine Grenzen gesetzt ihr könnt natürlich auch eire eigenen Templates definieren mit euren eigenen Typen. (Auch wenn man das eher selten benötigt)

Das Tolle: mehr müssen wir nicht machen. MVC wird nun zur Laufzeit statt einem Text ein Date Input-Element für uns anlegen. Beachtet, dass das nur geht wenn wir in der View die allgemeine Funktion @Html.EditorFor nutzen und keinen Spezifischen Typ angeben.

Schauen wir mal in den Sourcecode der zur Laufzeit für das Geburtstagsfeld generiert wird:


<input class="text-box single-line"
data-val="true"
data-val-required="Das Feld 'Geburtstag' ist erforderlich"
id="Geburtstag"
name="Geburtstag"
type="datetime"
value=""; />

Wie ihr seht wird nun der Korrekte Typ, nämlich datetime verwendet. Wenn wir die Seite nun also mit einem kompatiblen Browser aufrufen können wir ganz bequem das Datum wählen.

Leider ist der Opera Browser momentan wohl der einzige Desktop Browser der uns hier einen Benutzerfreundlichen Dialog einblendet (siehe erstes Bild oben), somit müssen wir dafür sorgen, dass die Benutzer mit anderen oder alten Browsern nicht benachteiligt werden.

JavaScript Magie

Auch für diese Problematik bringt MVC bereits alles mit was nötig ist sie zu lösen. Werfen wir doch mal einen Blick in unseren Scripts Ordner finden wir alle nötigen jQuery und jQuery UI Skripte, dazu kommt noch die modernizr Bibliothek die uns auch zu Gute kommen wird.

Die jQuery Bibliotheken liefern und die nötige Funktionalität die wir benötigen um diesen hübschen Date-Picker einzublenden.

Die modernizr Bibliothek dagegen hilft uns herauszufinden ob der Browser, den der Benutzer momentan verwendet die gewünschten HTML5 Features unterstützt.

Nun müssen wir also bei unseren Formularen prüfen, ob der Browser die nötigen Funktionen kennt und bei Bedarf die jQuery Klassen einbinden. Das Ganze ist dank der Einfachheit von Modernizr und jQuery ziemlich simpel.

Wechseln wir zu unserer View und fügen die Referenzen auf die nötigen Skripte und CSS Dateien ein:


<script src="@Url.Content("~/Scripts/jquery-1.5.1.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.js")" type="text/javascript"></script>
<link href="@Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="stylesheet" type="text/css" />

Unten in der View fügen wir nun eine Modernizr Abfrage ein und prüfen, ob der Browser das DateTime-Feld unterstützt, und wenn nicht lassen wir jQuery das Ganze für uns regeln:


<script type="text/javascript">
    $(function () {
        if (!Modernizr.inputtypes.date) {
            $("input[type='datetime']").datepicker();
        }
    });
</script>

Mit dem Befehl $("input[type='datetime']").datepicker(); werden alle Input-Felder vom Typ DateTime durch den jQuery Picker ersetzt. Coole Sache!

Unser Browser kennt den Typ nicht :(

Unser Browser kennt den Typ nicht :(

Kleiner Hinweis am Rande: Das ist nicht wirklich best practice da die Skripte auch geladen werden wenn sie gar nicht benötigt werden (der Browser kennt das HTML5 Feld) ich habe das hier der Einfachheit geopfert.

Und das wars auch schon! Alle Browser die das Feld unterstützen blenden nun ihren Picker ein, ansonsten wird der jQuery Fallback genutzt.

Wir müssen uns auch nicht mit irgendwelchen UserAgent-Prüfungen herumschlagen und lassen das alles Modernizr erledigen, der das intern über JavaScript prüft und somit immer aktuell ist. Das heißt dass wenn irgendwann der IE plötzlich die neuen Typen kennt werden diese auch funktionieren!

Genauso könnt ihr auch bei all den anderen neuen HTML5 Typen vorgehen und euren Usern ein bestmögliches Bedienerlebnis bereiten ;-)

kick it on dotnet-kicks.de

11Sep/114

Lokale Wetterdaten anzeigen mit der HTML5 Geolocation API und ASP.NET MVC

Wenn man heutzutage hipp sein will mit seiner Webseite sollte man irgendwelche coolen Features von HTML5 nutzen. Deswegen möchte ich euch in diesem kleinem Tutorial zeigen, wie ihr auf eurer Seite dem aktuellen Besucher sein lokales Wetter anzeigen könnt.

Um dieses Ziel zu verwirklichen benötigen wir folgende Daten bzw. Dienste:

  1. Die physische Position des Besuchers
  2. Einen Dienst der uns zu der Position des Besuchers die Wetterdaten liefert

Der erste Punkt ist schnell abgehackt, denn hier werden wir eins der tollen neuen HTML5 Features namens Geolocation API benutzen. Diese API liefert uns, vorausgesetzt der Browser unterstützt es, Koordinaten des aktuellen Besuchers der Webseite. Alle aktuellen Browser, auch der IE ab Version 9 unterstützt diese API.

Das Ganze läuft so ab, dass wir als Webseite den Browser nach den Koordinaten fragen, aber dieser uns diese nicht sofort herausrückt, sondern erst den Benutzer fragt ob er einverstanden ist, das sieht je nach Browser unterschiedlich aus, hier ein Beispiel aus dem FireFox 6:

Wenn der Benutzer hier auf die “Zugriff erlauben” Schaltfläche klickt bekommen wir ein Objekt mit einem Haufen an Informationen rund um die Position des Benutzers. Für uns ist an dieser Stelle aber nur die Adresse des Nutzers von Bedeutung.

Moment mal, woher weiß der Browser so ich bin?

Tja, das ist eine gute Frage, auf magische Weise weiß der Browser wo wir uns aufhalten, auch wenn wir grad mit einem Desktopcomputer auf der Seite sind, der kein GPS Chip verbaut hat (im Vergleich zum Handy – wo das ja klar sein sollte) und das auch noch _ziemlich_ genau!

Im ersten Augenblick könnte man denken der Browser macht es über die IP Adresse, aber nein die IP ist _Viel_ zu ungenau im Vergleich mit den echten Koordinaten.

So sendet der Browser (Firefox) nach eurer Erlaubnis folgende Daten an Google unter dieser Adresse: https://www.google.com/loc/json


"mac_address": "01-23-45-67-89-ab",
"signal_strength": 8,
"age": 0,
"SSID": "MyAccessPoint"

Weitere Informationen

Als Google mit ihren StreetView Autos durch die Gegend gefahren ist haben die nämlich auch die Namen und die genauen Positionen von den WLAN Netzwerken gespeichert, an denen sie vorbeigefahren sind. An Hand dieser Informationen versucht Google euch nun zu orten.

Wie komme ich nun an diese Informationen?

Glücklicherweise gestaltet sich dies für uns sehr einfach:


if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(success, error);
} else {
  alert("Not Supported!");
}

Wenn der Browser diese API kennt werden die statischen Klassen Navigator und geolocation aufgerufen und hier die Methode getCurrentPosition aufgerufen, diese bekommt als Parameter einen Verweis auf die Methode die ausgeführt werden soll. Wenn das Ganze fehlschlägt (Kein Browsersupport) soll die display_error-Methode aufgerufen werden.

Die Funktion success bekommt einen Parameter mit, der alle relevanten Informationen enthält:
<pre>


function success(position) {
alert(position.coords.latitude);
alert(position.coords.longitude);
alert(position.address.city);
}

Für uns ist für das weitere Vorgehen aber nur der Name der Stadt wichtig.

Auslesen der Wetterinformationen

Es gibt diverse offene APIs die uns die Wetterdaten zu einer angegebenen Stadt liefern können, ich habe mich an dieser Stelle für die von Google entschieden, da diese offen ist und keine Registrierung voraussetzt.

Die API ist unter folgender URL zu erreichen:

http://www.google.com/ig/api?weather=[LOCATION]&hl=[LANGUAGE]

Hier müsst ihr lediglich die gewünschte Stadt und die gewünschte Sprache einsetzen. Als Antwort bekommt ihr eine XML, die die Wetterdaten enthält, und das nicht nur für jetzt, sondern für die gesamte nächste Woche, inklusive ausgeschriebener Formulierungen wie “Teils sonnig” oder “Meistens bewölkt”.

Und wenn euch das noch nicht genug ist, bietet Google auch gleich das passende Icon zu dem Wetter, dass ebenfalls aus dem XML.Status ausgelesen werden kann. Insgesamt gibt es 22 verschiedene Wetterzustände zu denen es jeweils auch ein passendes Icon gibt.

Moment, wie komme ich nun an diese Daten?

Es besteht nun nur noch ein kleines Problem. Die ganze Abfrage erfolgt beim Client, deswegen kennt nur der Client die Position des Benutzers. Wir müssen nun aber (im Hintergrund => Ajax) einen Request an den Google Dienst absenden um an die Wetterdaten zu kommen.

Leider können wir das nicht direkt aus dem Client machen, da die Browser es verbieten, Requests an andere Domains abzusenden als an die von der das Script geladen wurde. Das ganze schimpft sich “Same Origin Policy” und soll den Benutzer unter Anderem vor Cross-Site-Scripting-Attaken schützen.

Das bedeutet, dass wir den Umweg über den Server gehen müssen und uns auf dem Server eine kleine API anlegen müssen die unserem Script auf Anfrage die passenden Daten liefert.

Dies kann man z.B so implementieren:


private const string GOOGLE_WEATHER_API_URL =
	"http://www.google.com/ig/api?weather={0}&amp;amp;amp;amp;amp;amp;amp;hl=de";
public WeatherResult GetWeahterData(string city) {

    string api_url = string.Format(GOOGLE_WEATHER_API_URL, city);

    try {
        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(api_url);
        HttpWebResponse res = (HttpWebResponse)req.GetResponse();
        StreamReader reader = new StreamReader(res.GetResponseStream());

        string response = reader.ReadToEnd();

        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(response);

        string temp = xmlDoc.SelectSingleNode("xml_api_reply/weather/current_conditions/temp_c").Attributes["data"].Value;
        string icon = xmlDoc.SelectSingleNode("xml_api_reply/weather/current_conditions/icon").Attributes["data"].Value;

        WeatherResult wRes = new WeatherResult(temp, city);
        wRes.Icon = icon;

        return wRes;
    } catch (Exception ex) {
        return null;
    }
}

Für den Response habe ich mir eine kleine Klasse gebastelt, die ich zu Json serialisiere und als Antwort zurückgebe:


    public class WeatherResult {
        public string Temperature { get; set; }
        public string City { get; set; }
        public string Icon { get; set; }

        public WeatherResult(string temp, string city) {
            Temperature = temp;
            City = city;
        }
    }

Das Ganze wird von dem ApiController aufgerufen:


    public class ApiController : Controller
    {
        //
        // GET: /Api/GetWeather

        public JsonResult GetWeather(string id)
        {

            WeatherResult res = new Tools.Weather.WeatherHelper().GetWeahterData(id);

            return Json(res, JsonRequestBehavior.AllowGet);
        }

    }

Was wir nun noch machen müssen ist unsere neu geschaffene API über einen Ajax Request aufzurufen. Hier habe ich jQuery benutzt um das Ganze zu vereinfachen:


function get_address(position) {
    var full_url = "/Api/GetWeather/" + position.address.city;

    $.ajax({
        url: full_url,
        success: get_weather,
        error: display_error
    });
}

Die get_weather-Funktion auf die ich hier im Erfolgsfall verweise ist nun dafür verantwortlich die vom Server erhaltenen Daten anzuzeigen, und dank dem zu Json serialisiertem WeatherResult-Objekt kann dies auch bequem erfolgen. Hier habe ich ebenfalls jQuery genutzt um mir die benötigten Referenzen auf die DOM-Objekte zu besorgen. Ein mögliches Ergebnis des Ganzen könnte ihr hier rechts sehen.


function get_weather(address) {

    $(".weather_info").html(address.Temperature == "" ? "N/A" : address.Temperature + "°C");
    $(".location_info").html(address.City);

}

Sonst noch was?

Im Grunde wars das auch schon. Aber eine Kleinigkeit ist mir noch aufgefallen, und zwar kann es vorkommen, dass ihr keine Adressdaten von der Geolocation API bekommt, sondern nur Koordinaten. (Falls Google diese Daten nicht gelistet hat oder ähnliches).

In diesem Fall müsst ihr ein Reverselookup machen und die Koordinaten in eine Adresse umwandeln, da die Google Wetter API nur Ortsnamen, aber keine Koordinaten annimmt.

Glücklicherweise stellt und Google auch dafür eine API zur Verfügung, diese könnt ihr unter folgender URL erreichen:

http://maps.google.com/maps/api/geocode/json?latlng=[KOORDINATEN]sensor=true 

Hier könnt ihr sogar als Parameter angeben, ob ihr XML oder Json als Antwort haben wollt. Diese könnt ihr z.B. so nutzen:


private const string GOOGLE_MAPS_REVERSE_LOOKUP_URL =
	"http://maps.google.com/maps/api/geocode/json?latlng={0}&amp;amp;amp;amp;amp;amp;amp;sensor=true";
public static string GetCityFromCoords(string coords) {
    string api_url = String.Format(GOOGLE_MAPS_REVERSE_LOOKUP_URL, coords);

    try {
        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(api_url);
        HttpWebResponse res = (HttpWebResponse)req.GetResponse();
        StreamReader reader = new StreamReader(res.GetResponseStream());

        string response = reader.ReadToEnd();

        var serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new[] { new DynamicJsonConverter() });

        dynamic obj = serializer.Deserialize(response, typeof(object));

        string city = obj.results[0].address_components[3].long_name;

        return city;
    } catch (Exception ex) {
        return "";
    }
}

Hier habe ich mir ein dynamic-Object generieren lassen aus dem übergebenen Json. Dazu habe ich diese Klasse hier genutzt.

Falls dieser Fall eintreffen sollte, braucht ihr übrigens nicht mit genauen Koordinaten bzw. Adressdaten zu rechnen, aber für unseren Zweck reicht es allemal.

kick it on dotnet-kicks.de

   
Get Adobe Flash player