JavaScript Data Binding mit jQuery Mobile
Tobias Bosch & Stefan Scheidt / OPITZ CONSULTING GmbH
Wer sind wir?
tobias.bosch@opitz-‐consulQng.com (@Qgbro)
stefan.scheidt@opitz-‐consulQng.com
(@beezlebug)
© OPITZ CONSULTING GmbH 2011 Seite 3
Das Bild kann nicht angezeigt werden. Dieser Computer verfügt möglicherweise über zu wenig Arbeitsspeicher, um das Bild zu öffnen, oder das
OPITZ CONSULTING Vorlage Powerpoint 2011; Version 1.3; 10.05.2011; TGA, KSH
1Pager • Layout ausschließlich für den
1Pager • Einsatz ist bei Konferenzen,
ext. Veranstaltungen etc. obligatorisch. Die Folie ist Folie 2 (nach der Titelfolie)
• Der Inhalt darf nicht verändert werden.
• Ausnahme: Der Block Märkte darf situativ um Partnerlogos (ORACLE, etc.) ergänzt werden
© OPITZ CONSULTING GmbH 2011
Märkte n Java n SOA n ORACLE n BI/DWH n Outtasking
Kunden n Branchen-
übergreifend n Über 600
Kunden
Leistungs- angebot n IT-Strategie n Beratung n Implementierung n Betrieb n Training
Fakten n Gründung 1990 n 400 Mitarbeiter n 8 Standorte in D/
PL
Industrie / Versorger / Telekommunikation
29%
Handel / Logistik / Dienstleistungen 29%
42% Öffentliche Auftraggeber /
Banken & Versicherungen / Vereine & Verbände
Wer sind Sie?
In diesem Vortrag geht‘s um...
...die Entwicklung testbarer und wartbarer mobiler Web-‐Apps
Unser Beispiel
Unser Beispiel...
Mobile Web-‐Apps
Architektur
"MulQ Page Web App"
Design: • Das Farbschema ist im Design als „OC 2009“ hinterlegt.
• Ebenso sind die Schri\arten als „OC 2009“ hinterlegt.
• Die Standardfarben sind:
Browser Server
Controller Backend
HTML-‐Page
UI Values Data
"AJAX Web App"
Design: • Das Farbschema ist im Design als „OC 2009“ hinterlegt.
• Ebenso sind die Schri\arten als „OC 2009“ hinterlegt.
• Die Standardfarben sind:
Browser
AJAX-‐ Engine
Server
Controller Change
Events Data Backend
"Single Page Web App"
Design: • Das Farbschema ist im Design als „OC 2009“ hinterlegt.
• Ebenso sind die Schri\arten als „OC 2009“ hinterlegt.
• Die Standardfarben sind:
Browser Server
Controller Backend Data
Bibliotheken
jQuery Mobile
h=p://jquerymobile.com/
Noch einmal unser Beispiel...
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> jQuery Mobile Markup
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> jQuery Mobile Markup
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> jQuery Mobile Markup
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> jQuery Mobile Markup
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> jQuery Mobile Markup
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> jQuery Mobile Markup
DOM-‐TransformaQon durch jQuery Mobile
<input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> <div class="ui-‐checkbox"> <input type="checkbox" name="todo.done" id="todo1"> <label class="ui-‐btn ui-‐btn-‐up-‐c ui-‐btn-‐icon-‐left ui-‐btn-‐corner-‐all ui-‐checkbox-‐off" for="todo1" data-‐theme="c"> <span class="ui-‐btn-‐inner ui-‐btn-‐corner-‐all"> <span class="ui-‐btn-‐text">create a mobile todo app</span> <span class="ui-‐icon ui-‐icon-‐checkbox-‐off ui-‐icon-‐shadow"></span> </span> </label> </div>
jQuery Mobile Markup TransformaIon
Vorher
<input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> <div class="ui-‐checkbox"> <input type="checkbox" name="todo.done" id="todo1"> <label class="ui-‐btn ui-‐btn-‐up-‐c ui-‐btn-‐icon-‐left ui-‐btn-‐corner-‐all ui-‐checkbox-‐off" for="todo1" data-‐theme="c"> <span class="ui-‐btn-‐inner ui-‐btn-‐corner-‐all"> <span class="ui-‐btn-‐text">create a mobile todo app</span> <span class="ui-‐icon ui-‐icon-‐checkbox-‐off ui-‐icon-‐shadow"></span> </span> </label> </div>
jQuery Mobile Markup TransformaIon
Nachher
Manuelles Binding
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
$('#addTodo').submit(function(event) { addTodo(); event.preventDefault(); }); function addTodo() { var inputText = $('#inputText').val(); var list = $('#todos'); var entryCount = list.find('input').length; list.append(entryTemplate(entryCount, inputText)); list.trigger('create'); $('#input').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; }
function TodoController() { this.todos = []; this.inputText = ''; } TodoController.prototype = { addTodo: function() { this.todos.push({
name: this.inputText, done: false });
this.inputText = ''; } }
Das Ziel ist aber:
MVC with Dependency InjecQon
Angular JS
h=p://angularjs.org/#/
Two-‐Way Data Binding
DeclaraQve UI Templates
Framework
Two-‐Way Databinding
Data-‐binding
Controller DOM
read write
watch
read write
watch
Scopes
Scope
Object
$watch(<expr>, <callback>)
$set(<expr>, <value>)
$get(<expr>) Expressions
'inputText'
'todos.length'
...
<div id="main" data-‐role="page"> <div data-‐role="header"> <h1>Todos</h1> <a href="">Save</a> <a href="#settings">Settings</a> </div> <div data-‐role="content"> <div data-‐role="fieldcontain"> <form data-‐ajax="false"> <input type="text"> </form> </div> <fieldset data-‐role="controlgroup"> <input type="checkbox" id="todo1"/> <label for="todo1">create a mobile todo app</label> </fieldset> </div> </div> Das DOM
function TodoController() { this.todos = []; this.inputText = ''; } TodoController.prototype = { addTodo: function() { this.todos.push({
name: this.inputText, done: false });
this.inputText = ''; } }
Der Controller
inputText: 'new todo' todos: [...]
TodoController-‐Scope
todo: { done: false name: 'makemoney' }
Repeater Scope todo: { done: false name: 'makemoney' }
Repeater Scope todo: { done: false name: 'makemoney' }
Repeater Scope
<div data-‐role="page" ng:controller="TodoController">
<input type="text" name="inputText"
<div ng:repeat="todo in todos">
<input type="checkbox" name="todo.done"/>
<label> {{todo.name}} </label>
erzeugt
bindet
bindet
erzeugt
bindet
bindet
Damit ist das Ziel fast erreicht...
Die DOM-‐ManipulaQonen von
jQuery Mobile und Angular JS
müssen "nur noch" koordiniert werden.
Dazu später mehr!
MVC with Dependency InjecQon
Angular JS
h=p://angularjs.org/#/
Two-‐Way Data Binding
DeclaraQve UI Templates
Framework
var readUrl = 'https://secure.openkeyval.org/'; var jsonp = ...; var waitdialog = ...; function read(key, success) { var url = readUrl + key; waitdialog.show(); jsonp(url, function(data) { success(data); waitdialog.hide(); }); }
Backend-‐Anbindung
var readUrl = 'https://secure.openkeyval.org/'; var jsonp = ...; var waitdialog = ...; function read(key, success) { var url = readUrl + key; waitdialog.show(); jsonp(url, function(data) { success(data); waitdialog.hide(); }); }
Backend-‐Anbindung
var readUrl = 'https://secure.openkeyval.org/'; var jsonp = ...; var waitdialog = ...; function read(key, success) { var url = readUrl + key; waitdialog.show(); jsonp(url, function(data) { success(data); waitdialog.hide(); }); }
Backend-‐Anbindung
angular.service('jsonp', jsonpFactory); angular.service('waitdialog', waitdialogFactory); function todoStoreFactory(jsonp, waitdialog) { function read(...) { ... } function write(...) { ... } return { read: read, write: write } } todoStoreFactory.$inject = ['jsonp', 'waitdialog']; angular.service('todostore', todoStoreFactory);
Services und DI mit Angular
angular.service('jsonp', jsonpFactory); angular.service('waitdialog', waitdialogFactory); function todoStoreFactory(jsonp, waitdialog) { function read(...) { ... } function write(...) { ... } return { read: read, write: write } } todoStoreFactory.$inject = ['jsonp', 'waitdialog']; angular.service('todostore', todoStoreFactory);
Services und DI mit Angular
angular.service('jsonp', jsonpFactory); angular.service('waitdialog', waitdialogFactory); function todoStoreFactory(jsonp, waitdialog) { function read(...) { ... } function write(...) { ... } return { read: read, write: write } } todoStoreFactory.$inject = ['jsonp', 'waitdialog']; angular.service('todostore', todoStoreFactory);
Services und DI mit Angular
angular.service('jsonp', jsonpFactory); angular.service('waitdialog', waitdialogFactory); function todoStoreFactory(jsonp, waitdialog) { function read(...) { ... } function write(...) { ... } return { read: read, write: write } } todoStoreFactory.$inject = ['jsonp', 'waitdialog']; angular.service('todostore', todoStoreFactory);
Services und DI mit Angular
function TodoController(todoStore) { ... } TodoController.$inject = ['todoStore'];
Di für Controller
function TodoController(todoStore) { ... } TodoController.$inject = ['todoStore'];
Di für Controller
function TodoController(todoStore) { ... } TodoController.$inject = ['todoStore'];
Di für Controller
One more thing...
IntegraQon von AngularJS und jQuery Mobile
jquery-‐mobile-‐angular-‐adapter
KoordinaQon von jQuery Mobile und Angular JS
Erweiterungen für mobile Web-‐Apps
Open Source unter heps://github.com/Qgbro/
jquery-‐mobile-‐angular-‐adapter
Paging in Listen
<div ng:repeat= "todo in todos.$paged()">
... </div> <div ng:if= "todos.$paged().hasMorePages()"> <a href="#" ngm:click= "todos.$paged().loadNextPage()"> Load more </a> </div>
Paging in Listen
<div ng:repeat= "todo in todos.$paged()">
... </div> <div ng:if= "todos.$paged().hasMorePages()"> <a href="#" ngm:click= "todos.$paged().loadNextPage()"> Load more </a> </div>
Paging in Listen
<div ng:repeat= "todo in todos.$paged()">
... </div> <div ng:if= "todos.$paged().hasMorePages()"> <a href="#" ngm:click= "todos.$paged().loadNextPage()"> Load more </a> </div>
Mobile Events
<div id="main" data-‐role="page" ng:event="swipeleft:showSettings()"> ... </div> <div id="settings" data-‐role="page" ng:event="swiperight:back()"> ... </div>
NavigaQon über Pages
function TodoController(todoStore, activePage) { ... } TodoController.prototype = { onActivate: function(prevscope) { if (prevscope && prevscope.storageKey) { this.storageKey = prevscope.storageKey; this.refreshTodos(); } }, showSettings: function() { this.activePage("#settings"); } };
NavigaQon über Pages
function TodoController(todoStore, activePage) { ... } TodoController.prototype = { onActivate: function(prevscope) { if (prevscope && prevscope.storageKey) { this.storageKey = prevscope.storageKey; this.refreshTodos(); } }, showSettings: function() { this.activePage("#settings"); } };
NavigaQon über Pages
function TodoController(todoStore, activePage) { ... } TodoController.prototype = { onActivate: function(prevscope) { if (prevscope && prevscope.storageKey) { this.storageKey = prevscope.storageKey; this.refreshTodos(); } }, showSettings: function() { this.activePage("#settings"); } };
Wait-‐Dialog Service
waitDialog.show('loading'); waitDialog.hide();
waitDialog.show('click to abort', onClickCallback);
Fazit
Auch bei der Entwicklung von JavaScript Clients
sollten geeignete Entwurfsmuster angewendet werden!
Fazit
Bibliotheken und Frameworks helfen bei der Umsetzung!
Fazit
Eine praxiserprobte KombinaQon:
jQuery Mobile + AngularJS + Adapter
In the hive 11: nectar and pollen by Max xx, hep://www.flickr.com/photos/max_westby/4567762490
Books
By Rodrigo Galindez, hep://www.flickr.com/photos/rodrigogalindez/4637637337/
Vielen Dank für Ihr Interesse!
@Qgbro
@beezlebug
Top Related