HTML5 Custom Controls und Sencha Touch
Kommentare

Einbindung in Sencha Touch
Mit Sencha Touch steht dem Mobil-Entwickler eine Vielzahl von gelungenen Widgets für die grafische Gestaltung zur Verfügung. Doch um der Mobile-App noch das gewisse Etwas zu

Einbindung in Sencha Touch

Mit Sencha Touch steht dem Mobil-Entwickler eine Vielzahl von gelungenen Widgets für die grafische Gestaltung zur Verfügung. Doch um der Mobile-App noch das gewisse Etwas zu verleihen, lohnt es sich gelegentlich, eigene Komponenten zu gestalten. Für die Einbindung eines Custom Controls in Sencha Touch soll hier eine komplexere Control-Komponente verwendet werden. Vorweg ein Hinweis: Alle Codebeispiele im Folgenden beziehen sich auf Sencha Touch 2 mit überarbeiteter MVC-Architektur. Manch ein älterer Entwickler erinnert sich sicherlich an die mechanischen Splitflap-Anzeigen aus Flughäfen oder Bahnhöfen. Die Ziffern der Anzeige werden hier durch Umklappen einzelner Kärtchen geändert, was sich bei einer umfangreichen Aktualisierung einer größeren Anzeigetafel mit einem unverwechselbaren Sound vollzieht. Eine derartige Control-Komponente wie in Abbildung 5 ist wie dafür geschaffen, in einem mobilen Fußball-Tippspiel für die Wahl eines Tippergebnisses zu dienen.

Abb. 5: HTML5-Splitflap-Komponente im Browser

Mit den eingebauten Board-Mitteln von Sencha Touch würde man den Control für das Tippergebnis wohl mittels eines Spinners (Ext.field.Spinner) realisieren. Spinner werden typischerweise für die Manipulation von HTML5-Nummernfeldern verwendet. Mit etwas Geschick lassen sich aber auch zwei Picker-Instanzen (Ext.picker.Picker) so arrangieren, dass man eine
brauchbare Komponente für die Auswahl der Tore beider Teams erhält. Mit dem gleichen Vorgehen wie bei der LED-Komponente kann man auch einen Splitflap Control realisieren. Neben der grafischen Gestaltung der Zifferkärtchen ist hier natürlich auch noch die richtige Animation ein entscheidendes Gestaltungselement. Letztere lässt sich relativ einfach und performant mittels CSS3 Keyframes umsetzen, doch das wäre Thema eines weiteren Artikels. Für die folgende Einbindung setzen wir den Splitflap Control als gegeben voraus. Damit sich der Control gut in beliebige Applikationen einbinden lässt, sollte er über einen Konstruktor parametrisierbar sein. Mittels Event Subscription sollen Änderungen am Tippergebnis nach außen zugänglich gemacht werden. Eine Umsetzung von beidem ist in Listing 9 veranschaulicht. Zu beachten ist, dass sich die Größe des Scoreboards automatisch an die des Parent-Elements anpasst.

Listing 9
// scoreboard constructor
var scoreBoard = new ScoreBoard(
  {
    parentId:'board',
    teamA: inTeamA,
    teamB: inTeamB,
    scoreA: inScoreA,
    scoreB: inScoreB,
    x:0,
    y:0
  }
);
// score event listener registration
scoreBoard.scoreEvent.subscribe(
  function (sender, eventArgs) { /* do something */ }
);

Die Einbindung in eine bestehende Sencha Touch App geschieht nun am besten über eine eigene View-Komponente. Hierzu erzeugen wir im view-Unterordner eine Komponente namens SplitFlap.js. Unsere SplitFlap View erbt sinnvollerweise von Ext.Component und hat ihren eigenen xtype splitflap. Um unsere View-Komponente von außen parametrisierbar zu machen, benötigen wir eine eigene Konstruktorimplementierung. Mittels config-Objekt können wir so alle notwendigen Parameter übergeben. Typischerweise werden Sencha Touch Views über das config-Objekt und dort über das items-Attribut statisch aufgebaut. Doch wie bindet man eine Custom-Komponente ein, die einen existierenden DOM Tree voraussetzt, um sich einfügen zu können? Die Lösung besteht in der dynamischen Instanziierung des config-Objekts im Konstruktor und der Registrierung eines Listeners auf das painted-Event. In der Dokumentation ist nachzulesen, dass nach dem painted-Event von einem fertig instanziierten DOM ausgegangen werden kann. Somit ist das der richtige Zeitpunkt für die Einbindung des Controls. Wichtig: im Konstruktor nicht den Parent-Aufruf am Schluss vergessen (this.callParent([cfg]); )! Listing 10 zeigt die vollständige Implementierung.

Listing 10
Ext.define('Cats.view.SplitFlap', {
    extend:'Ext.Component',
    xtype:'splitflap',

    scoreBoard: null,
    inTeamA: null,
    inTeamB: null,

    constructor: function (config) {
        var inTeamA = config.inTeamA;
        var inTeamB = config.inTeamB;
        var inScoreA = config.inScoreA || 0;
        var inScoreB = config.inScoreB || 0;
        var inRecordId = config.inRecordId;

        var me = this;
        var fireScoreChange = function(event) {
            var score = event.getScore();

            var scoreTeamA = score.getScoreTeamA();
            var scoreTeamB = score.getScoreTeamB();

            me.fireEvent('scorechange', me, { 
      recordId: inRecordId, 
      scoreTeamA: scoreTeamA, 
      scoreTeamB: scoreTeamB });
        };

        var cfg = {
            height:150,
            html:"
", listeners: { painted: function() { scoreBoard = new ScoreBoard( { parentId:'board', teamA: inTeamA, teamB: inTeamB, scoreA: inScoreA, scoreB: inScoreB, x:0, y:0 } ); scoreBoard.scoreEvent.subscribe( function (sender, eventArgs) { fireScoreChange(eventArgs); }); } } }; this.callParent([cfg]); } }); });

Bei der Implementierung fallen einem noch ein paar Besonderheiten auf. Das für die Einbindung der Scoreboard-Komponenten nötige Parent-Div wird dynamisch mittels html-Schnipsel (id=board) im config-Objekt erledigt. Der Callback für das painted-Event geschieht innerhalb der „listeners“-Konfiguration. In der Implementierung des Callbacks wird hier nicht nur das Scoreboard erzeugt, sondern auch die View-Komponente als Listener auf ScoreEvents registriert. Letztendlich soll es nämlich möglich sein, sich auf der View-Komponente selbst als Listener für ScoreEvents zu registrieren. Um den Code übersichtlicher zu gestalten, wurde hier die fireScoreChange()-Methode extrahiert. Über den Aufruf von fireEvent() können wir in dieser nach außen einen beliebigen, eigens definierten EventTyp weiterleiten.

Die so entwickelte Sencha View kann nun nach Belieben im Controller verwendet werden. In unserer Tippspiel-App erscheint das Scoreboard beim Tippen auf einen Match-Eintrag, der unsere onMatchItemTap()-Funktion aufruft. Die Daten für das Scoreboard kommen übrigens aus einem „MatchStore“ Record, der auch über das Control aktualisiert wird. Sinnvollerweise lässt man das Scoreboard mittels ActionSheet (Ext.ActionSheet) von unten nach oben sliden (Listing 11).

Listing 11
...
onMatchItemTap:function (dataview, record, target, index, event, eOpts) {
  var picker = Ext.create('Ext.ActionSheet', {
    items:[{
      xtype:'splitflap',
      centered: true,

      inTeamA:record.get("homeTeam"),
      inTeamB:record.get("awayTeam"),
      inScoreA:parseInt(record.get("tipGoalsHomeTeam")),
      inScoreB:parseInt(record.get("tipGoalsAwayTeam")),
      inRecordId:record.get("id")
      },
      {
      xtype:'button',
      text:'Tippen',
      ui:'confirm',
      listeners:{
        tap:function () {
          picker.hide();
          Ext.Viewport.remove(picker);
          picker.destroy();

          // store result
          var store = Ext.getStore("MatchStore");
          store.sync();
        }
      }
      }]
            });

            Ext.Viewport.add(picker);
            picker.show();
    },

    onScoreChanged:function(splitflap, scores) {
        var store = Ext.getStore("MatchStore");
        var record = store.getById(scores.recordId);
        record.set('tipGoalsHomeTeam', scores.scoreTeamA);
        record.set('tipGoalsAwayTeam', scores.scoreTeamB);
    }

Unterhalb des Scoreboards wird hierbei noch ein Bestätigungsbutton für das Schließen eingefügt, der auch das Ausblenden des ActionSheets steuert und die Tippergebnisse im MatchStore persistiert. Damit die onScoreChanged()-Funktion – zuständig für das Aktualisieren der Tippergebnisse – aufgerufen wird, muss in der Konfiguration des Controllers noch ein spezieller Selector (refs-Objekt) und ein Delegation (control-Objekt) definiert werden (Listing 12).

Listing 12
// Controller implementation
...
config:{
  views:[
    ...
    'SplitFlap',
  ],
  refs:{
     ...
    splitflap:'splitflap'
  },
  control:{
    ...
    splitflap:{
      scorechange:"onScoreChanged"
    }
  }
}
...

Das fertige Ergebnis – mit angepasstem Farbschema – präsentiert sich dann wie in Abbildung 6. Durch Tippen auf die oberen/unteren Ziffernhälften wird der Punktestand jeweils inkrementiert/dekrementiert. Eine Livedemo ist unter [2] verfügbar.

Abb. 6: HTML5-Splitflap-Komponente in Sencha Touch
Fazit

Mit relativ geringem Aufwand ist es möglich, unabhängige HTML5-Komponenten in Sencha Touch einzubauen, wenn man die oben genannten Hinweise berücksichtigt. Handelt es sich um grafisch aufwändige Controls, ist es sinnvoll, den Prototypen in einem Zeichenprogramm zu entwerfen und diesen Prototypen dann mit geeigneten Tools in Code zu überführen. Für die im Artikel genannten Beispiele wurde hierzu Adobe Fireworks verwendet – und der FxgConverter [3], mit dem sich Files im Adobe-FXG-Format unter anderem auch in HTML5 Canvas konvertieren lassen. Möchte man sich als Unternehmen mit einer Anwendung von der breiten Masse absetzen, stellen Custom Controls auf der Basis von HTML5 eine hervorragende Möglichkeit dar, eine Anwendung plattformübergreifend zu gestalten.

Gerrit Grunwald ist passionierter Java-Entwickler und beschäftigt sich bevorzugt mit der Entwicklung von Desktopanwendungen und Komponenten in JavaFX, Java Swing und HTML5. Des Weiteren ist er Mitgründer und Leader der Java Usergroup Münster.
Andreas Hölzl ist leidenschaftlicher Mobilentwickler und ist davon überzeugt, dass HTML5 der Weg für mobile Businessapplikationen ist. Außerdem ist er begeisterter Android-Entwickler und Mitgründer von Wikihood.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -