var inputText
Transcripción
var inputText
Bastian Schramm, Stefan Scheidt, Tobias Bosch | Opitz Consulting GmbH Mobile JavaScript-Web-App professionell entwickeln Entwurfsmuster für JavaScript Web-Apps: Data Binding Teil 2 Mobile JavaScript-‐Web-‐Apps Entwurfsmuster für JavaScript Web-‐Apps: Data Binding In diesem Vortrag geht‘s um... ...die Entwicklung testbarer und wartbarer JavaScript Web-‐Apps ...durch Data Binding Unser Beispiel Architektur "Single Page Web App" n a m t r e i r u t ? k ? u r ? t e d Wie s lient-‐CoBrowser den C Model View Controller Server Data Backend Two Way Data Binding Markup mit jQuery Mobile h=p://jquerymobile.com/ <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 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'); $('#inputText').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'); $('#inputText').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'); $('#inputText').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'); $('#inputText').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'); $('#inputText').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'); $('#inputText').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'); $('#inputText').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'); $('#inputText').val(''); } function entryTemplate(index, name) { var id = 'todo' + index; return '<input type="checkbox" id="' + id + '"/>' + '<label for="' + id + '">' + name + '</label>'; } Das Ziel ist aber: function TodoController() { this.todos = []; this.inputText = ''; this.addTodo = function() { this.todos.push({ name: this.inputText, done: false }); this.inputText = ''; }; } Angular JS DeclaraRve UI Templates MVC with Dependency InjecRon Two-‐Way Data Binding Framework h=p://angularjs.org/#/ Two-‐Way Databinding DOM read write watch Data-‐ binding read write watch Controller Scopes Expressions 'inputText' 'todos.length' Scope ... read / write Watch Watch last value Watch last value expression last value expression callback expression callback callback $watch(<expr>, <callback>) $digest() Demo <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 = ''; this.addTodo = function() { this.todos.push({ name: this.inputText, done: false }); this.inputText = ''; }; } Der Controller function TodoController($scope) { $scope.todos = []; $scope.inputText = ''; $scope.addTodo = function() { $scope.todos.push({ name: $scope.inputText, done: false }); $scope.inputText = ''; }; } TodoController.$inject = ['$scope']; Der Controller <div data-‐role="page" ng-‐controller="TodoController"> erzeugt <input type="text" ng-‐model="inputText" bindet TodoController-‐Scope inputText: 'new todo' todos: [...] bindet <div ng-‐repeat="todo in todos"> erzeugt <input type="checkbox" ng-‐model="todo.done"/> bindet <label> {{todo.name}} </label> bindet Repeater cope Repeater SScope Scope Repeater todo: { todo: { todo: { done: false done: false done: false name: 'makemoney' name: 'makemoney' name: 'makemoney' } } } Dependency InjecRon Beispiel: Backend-‐Anbindung waitDialog todoController key todoStore value key value refreshTodos ... read ... todoStore waitDialog jsonp key value ... ... jsonp key value ... ... Erinnerung: Angular Services var module = angular.module("todo", []); module.factory('jsonp', jsonpFactory); module.factory('waitdialog', waitdialogFactory); function todoStoreFactory(jsonp, waitdialog) { // ... } todoStoreFactory.$inject = ['jsonp', 'waitdialog']; module.factory('todoStore', todoStoreFactory); DI für Controller function TodoController($scope, todoStore) { ... } TodoController.$inject = ['$scope', 'todoStore']; module.controller("rylc.TodoController", TodoController); DI für Controller function TodoController($scope, todoStore) { ... } TodoController.$inject = ['$scope', 'todoStore']; module.controller("rylc.TodoController", TodoController); DI für Controller function TodoController($scope, todoStore) { ... } TodoController.$inject = ['$scope', 'todoStore']; module.controller("rylc.TodoController", TodoController); Erläuterungen zu RYLC Module-‐PaZern + DI mit Angular Module-‐PaZern + DI mit Angular (function(angular) { function LoginController($scope, backendService, $navigate) { // ... } LoginController.$inject = ['$scope','backendService', '$navigate']; var module = angular.module("rylc-‐controllers", ["rylc-‐services"]); module.controller("rylc.LoginController", LoginController); })(window.angular); Module-‐PaZern + DI mit Angular (function(angular) { function LoginController($scope, backendService, $navigate) { // ... } LoginController.$inject = ['$scope','backendService', '$navigate']; var module = angular.module("rylc-‐controllers", ["rylc-‐services"]); module.controller("rylc.LoginController", LoginController); })(window.angular); Module-‐PaZern + DI mit Angular (function(angular) { function LoginController($scope, backendService, $navigate) { // ... } LoginController.$inject = ['$scope','backendService', '$navigate']; var module = angular.module("rylc-‐controllers", ["rylc-‐services"]); module.controller("rylc.LoginController", LoginController); })(window.angular); Oberflächentests mit uitest.js Oberflächentests describe('login', function() { uit.url('/rylc-‐html5/index.jsp'); uit.append(function() { mockBackend(); }); ... }); Oberflächentests describe('login', function() { uit.url('/rylc-‐html5/index.jsp'); uit.append(function() { mockBackend(); }); ... }); Oberflächentests Glob describe('login', function() { al Fu ncRo uit.url('/rylc-‐html5/index.jsp'); from n testu Rls.js uit.append(function() { mockBackend(); }); ... }); Oberflächentests var someUsername = "someUsername"; var somePassword = "somePassword"; it('should show login page when visiting application', function() { uit.runs(function() { expect(activePageId()).toBe('loginPage'); }); }); it('should allow login when username and password not empty', function() { uit.runs(function() { value("#username", someUsername); value("#password", somePassword); expect(enabled(".login")).toBeTruthy(); }); }); Oberflächentests var someUsername = "someUsername"; var somePassword = "somePassword"; it('should show login page when visiting application', function() { Glob al Fu uit.runs(function() { ncRo from ns testu expect(activePageId()).toBe('loginPage'); Rls.js }); }); it('should allow login when username and password not empty', function() { uit.runs(function() { value("#username", someUsername); value("#password", somePassword); expect(enabled(".login")).toBeTruthy(); }); }); Oberflächentests var someCustomer = { id: 42, name: "someName" }; it('should show the welcome page after successful login', function() { uit.runs(function() { backendServiceResult('login').resolve(someCustomer); value("#username", someUsername); value("#password", somePassword); click(".login"); }); uit.runs(function() { expect(backendService().login) .toHaveBeenCalledWith(someUsername, somePassword); expect(activePageId()).toBe('welcomePage'); }); }); Oberflächentests Glob var someCustomer = { id: 42, name: "someName" }; al Fun c from testu Rons Rls.js it('should show the welcome page after successful login', function() { uit.runs(function() { backendServiceResult('login').resolve(someCustomer); value("#username", someUsername); value("#password", somePassword); click(".login"); }); uit.runs(function() { expect(backendService().login) .toHaveBeenCalledWith(someUsername, somePassword); expect(activePageId()).toBe('welcomePage'); }); }); Fazit Auch bei der Entwicklung von JavaScript Clients sollten geeignete Entwurfsmuster angewendet werden! Frameworks helfen bei der Umsetzung! In the hive 11: nectar and pollen by Max xx, hZp://www.flickr.com/photos/max_westby/4567762490 Double Via by amslerPIX, hZp://www.flickr.com/photos/amslerpix/6242266697/ MacBook Pro Keyboard by superstrikertwo, hZp://www.flickr.com/photos/superstrikertwo/4989727256/ Stubborn Last Drop by RogueSun Media, hZp://www.flickr.com/photos/shuZercat7/627798443/ Und jetzt viel Spaß beim Üben!