Knockout + ContainerJS ã§ãã¹ã¿ãã«ã«ToDoãªã¹ããä½ããã¥ã¼ããªã¢ã«
Knockout + ContainerJS + Require.js 㧠ãã¹ã¿ãã« ã«ToDoãªã¹ããä½ããã¥ã¼ããªã¢ã«ã§ãã
ãã¤ã³ã
- MVVMã¢ã¼ããã¯ãã£ã§ãã¹ã¿ãã«ã«
- MVVMã¢ã¼ããã¯ãã£ãæ¡ç¨ããView(HTML/CSS)ã¨ViewModel,Modelãåé¢ã
- ViewModelãModel㯠HTMLã«éä¾åã¨ãªããããåä½ãã¹ããå¯è½ã«ãªãã¾ãã
- ãªãã¸ã§ã¯ãã®çæã¨ä¾åé¢ä¿ããDIã³ã³ããã§ä¸å
管ç
- DIã³ã³ãããå©ç¨ãã¦ãViewModelãModelã®çæã¨é¢é£ä»ããèªååã
- ã³ã³ãã¼ãã³ãéã®çµåãçã«ã§ãããã¹ãæã®ã¢ãã¯ã¸ã®å·®ãæ¿ããç°¡åã«ã§ããããã«ãªãã¾ãã
- JavaScriptã½ã¼ã¹ã¯ã¯ã©ã¹ãã¨ã«åå²ç®¡ç
- 1ãã¡ã¤ã«200è¡è¶ ãããã¡ã³ããã³ã¹ã¨ãç¡çã§ããã! ã¨ãããã¨ã§ãã½ã¼ã¹ãã¡ã¤ã«ã¯ã¯ã©ã¹ãã¨ã«åå²ç®¡çãã¾ãã
- ã½ã¼ã¹éã®ä¾åé¢ä¿è§£æ±ºã¨èªã¿è¾¼ã¿ã¯require.jsã§ã
- ãªãªã¼ã¹æã«ã¯ãå¿ è¦ãªã½ã¼ã¹ãminify && 1ãã¡ã¤ã«ã«çµåãã¦èªã¿è¾¼ã¿ãæé©åãã¾ãã
ç®æ¬¡
- Modelãä½ã
- Modelããã¹ããã
- ViewModelãä½ã
- ViewModelããã¹ããã
- View(HTML/CSS)ãä½ã
- minify && çµåãã¦ãªãªã¼ã¹
ã½ã¼ã¹ã¯こちらã§å ¬éãã¦ãã¾ããç´°ããã¨ããã¯çç¥ãã¦ãããããã®ã§ãã½ã¼ã¹ãåããã¦åç §ãã ããã
Todoãªã¹ãã®ä»æ§
ä½æããTodoãªã¹ãã®æ©è½ã¯ä»¥ä¸ã
- ã¿ã¤ãã«ãå
¥åãã¦ãTodoãç»é²ã§ããã
- Todoã«ã¯ã¿ã¤ãã«ã¨å®äºãããã©ãããä½ææ¥ãæçµæ´æ°æ¥ã®å±æ§ãããã
- çµäºããTodoã¯ãå®äºãã«ã§ããã
- å®äºãããã®ãæªå®äºã«æ»ããã¨ãã§ããã
- Todoãåå¥ã«åé¤ã§ããã
- å®äºããTodoã¯ã¾ã¨ãã¦åé¤ã§ããã
TodoMVC ã§ä½ããã¦ãããã®ã¨å¤§ä½åãã¯ããUIã¯ä»¥ä¸ã®ãããªæãã§ã
ãªããæ¬æ¥ã¯ç»é²ããTodoããµã¼ãã¼ã«éã£ã¦ä¿åã¨ãå¿ è¦ã§ãããä»åã¯JavaScriptããã¸ã§ã¯ãã®ãµã³ãã«ã¨ãããã¨ã§ãµã¼ãã¼ã¨ã®é£æºé¨åã¯çç¥ãã¾ãããµã¼ãã¼ã§æ°¸ç¶åããå ´åã¯ãModelå´ã®APIå¼ã³åºãæã«REST APIãªããªããªããå¼ã³åºãããã«ããã°OKã
1. Modelãä½ã
ã¾ãã¯ãModelãä½ãã¾ãã
- ã¯ã©ã¹ã¯ãTodoã¨Todoãªã¹ã(ç»é²ãããTodoã®éã¾ãã表ããã®)ã®äºã¤ã
- ã¢ãã«ã¯PlainãªJavaScriptã¯ã©ã¹ã¨ãã¦å®ç¾©ãã¾ãã
- ãã¬ã¼ã ã¯ã¼ã¯(or ã©ã¤ãã©ãª)ä¾åã«ã¯ããªãæ´¾ã
- çç±ã¯ãこのあたり ãåç §ã
- ãã¹ã¿ãã«ã§å¤åã«å¼·ãã¢ãã«ã«ããããã«ã¯ãPlainãªJavaScriptã¯ã©ã¹ã«ãã¦ããã¹ãã¨æãã®ã§ãã
Todo
- ã¿ã¤ãã«(title),å®äºãããã©ãã(completed)ãä½ææ¥(createdAt)ãæçµæ´æ°æ¥(lastModified)ã®å±æ§ãæã¡ã¾ãã
- ã¿ã¤ãã«ã¯100æåã¾ã§ã空æåãä¸å¯ã
- å種å±æ§ã®å¤æ´ãéç¥ããä»çµã¿ãæã¡ã¾ãã(Observerãã¿ã¼ã³)
- å®äºã«ãã(complete)/æªå®äºã«æ»ã(activate)æä½ãã§ãã¾ãã
- ç¾å¨æå»ã¯TimeSourceããåå¾ããããã«ãã¦ãã¾ãã
- ç¾å¨æå»ã«ä¾åããªããã¹ãã«ããããã®ã¯ã³ãã¤ã³ãã
- ãã¹ãæã«ã¯TimeSourceãã¢ãã¯ã«å·®ãæ¿ãã¦ããã¹ãå ã§ã®æéãã³ã³ããã¼ã«ãã¾ãã
define([ "models/events", "utils/observable" ], function(Events, Observable){ "use strict"; var sequence = 1; /** * @class */ var Todo = function(timeSource) { Observable.apply(this, arguments); var now = timeSource.now(); this.id = sequence++; this.title = ""; this.completed = false; this.createdAt = now; this.lastModified = now; this.timeSource = timeSource; this.todoList = null; Object.seal(this); }; Todo.prototype = new Observable(); /** * @public * @return {string} */ Todo.prototype.setTitle = function( title ) { validateTitle(title); update(this, "title", title); update(this, "lastModified", this.timeSource.now()); }; /** * @public */ Todo.prototype.complete = function() { update(this, "completed", true); update(this, "lastModified", this.timeSource.now()); }; /** * @public */ Todo.prototype.activate = function() { update(this, "completed", false); update(this, "lastModified", this.timeSource.now()); }; /** * @public */ Todo.prototype.remove = function() { if (!this.todoList) throw new Error("illegal state. todoList is not set."); this.todoList.removeById( this.id ); }; /** @private */ Todo.prototype.attachTo = function( todoList ) { this.todoList = todoList; }; /** * @public * @param {TimeSource} timeSource * @param {string} title * @param {boolean?} completed * @param {Date?} createdAt * @param {Date?} lastModified */ Todo.create = function( timeSource, title, completed, createdAt, lastModified ) { validateTitle(title); var now = timeSource.now(); var todo = new Todo(timeSource); todo.title = title; todo.completed = completed || false; todo.createdAt = createdAt || now; todo.lastModified = lastModified || now; return todo; }; /** @private */ var update = function(that, propertyName, newValue) { var oldValue = that[propertyName]; that[propertyName] = newValue; that.fire(Events.UPDATED, { propertyName : propertyName, newValue : newValue, oldValue : oldValue }); }; /** @private */ var validateTitle = function(title) { if (!title) throw new Error("title is not set."); if (title.length > 100) throw new Error("title is too long."); }; return Object.freeze(Todo); });
TodoList
- Todoã®é å(items)ã¨ããããæä½ããã¡ã½ãã(Todoã®è¿½å ãåé¤)ãæã¡ã¾ãã
- Todoã¨åããããã¡ããObserverãã¿ã¼ã³ã§Todoã®è¿½å /åé¤ãéç¥ããä»çµã¿ãç¨æã
define([ "container", "models/events", "models/todo", "utils/observable" ], function(ContainerJS, Events, Todo, Observable){ "use strict"; /** * @class */ var TodoList = function() { Observable.apply(this, arguments); this.timeSource = ContainerJS.Inject; this.items = []; Object.seal(this); }; TodoList.prototype = new Observable(); /** * @public */ TodoList.prototype.load = function( ) { this.fire( Events.LOADED, { items: this.items }); }; /** * @public * @param {string} title * @return {Todo} */ TodoList.prototype.add = function( title ) { var todo = Todo.create(this.timeSource, title); todo.attachTo(this); this.items.push(todo); this.fire( Events.ADDED, { added : [todo], items : this.items }); return todo; }; /** * @public */ TodoList.prototype.removeCompleted = function() { removeItems( this, function(item){ return item.completed; }); }; /** * @public * @param {number} id */ TodoList.prototype.removeById = function(id) { removeItems( this, function(item){ return item.id === id; }); }; /** @private */ var removeItems = function( that, f ) { var removed = []; that.items = that.items.filter(function(item){ if (f(item)) { removed.push(item); return false; } else { return true; } }); if (removed.length > 0) { that.fire( Events.REMOVED, { items : that.items, removed: removed }); } }; return Object.freeze(TodoList); });
2. Modelããã¹ããã
Modelãã§ããã®ã§ããã¹ããæ¸ãã¾ãã
- ãã¹ãã£ã³ã°ãã¬ã¼ã ã¯ã¼ã¯ã¯Jasmineãå©ç¨ã
- Todo,TodoListããããã§ãå種ããããã£ãå¤æ´ã§ãããå¤æ´ãããã¤ãã³ããçºç«ãããããããã確èªã
Todo spec
define([ "models/todo", "models/events", "test/mock/models/time-source" ], function( Todo, Events, TimeSource ) { describe('Todo', function() { var timeSource = new TimeSource(); timeSource.set(2013, 1, 1); it( "'new' creates a new instance.", function() { var todo = new Todo(timeSource); expect( todo.title ).toEqual( "" ); expect( todo.completed ).toEqual( false ); expect( todo.createdAt ).toEqual( new Date(2013,0,1) ); expect( todo.lastModified ).toEqual( new Date(2013,0,1) ); }); it( "'Todo.create' can creates a new instance with specified properties.", function() { var todo = Todo.create(timeSource, "title", true, new Date(1000), new Date(2000) ); expect( todo.title ).toEqual( "title" ); expect( todo.completed ).toEqual( true ); expect( todo.createdAt ).toEqual( new Date(1000) ); expect( todo.lastModified ).toEqual( new Date(2000) ); todo = Todo.create(timeSource, "title2", false, new Date(2000), new Date(3000) ); expect( todo.title ).toEqual( "title2" ); expect( todo.completed ).toEqual( false ); expect( todo.createdAt ).toEqual( new Date(2000) ); expect( todo.lastModified ).toEqual( new Date(3000) ); }); it( "'setTitle' can updates a title and fire updated events.", function() { var todo = Todo.create(timeSource, "title", false, new Date(1000), new Date(2000) ); var events = []; todo.addObserver( Events.UPDATED, function(ev) { events.push( ev ); }); todo.setTitle("title2"); expect( todo.title ).toEqual( "title2" ); expect( todo.completed ).toEqual( false ); expect( todo.createdAt ).toEqual( new Date(1000) ); expect( todo.lastModified ).not.toEqual( new Date(2000) ); expect( events.length ).toEqual( 2 ); expect( events[0].eventId ).toEqual( Events.UPDATED ); expect( events[0].propertyName ).toEqual( "title" ); expect( events[0].newValue ).toEqual( "title2" ); expect( events[0].oldValue ).toEqual( "title" ); expect( events[1].eventId ).toEqual( Events.UPDATED ); expect( events[1].propertyName ).toEqual( "lastModified" ); expect( events[1].newValue ).toEqual( todo.lastModified ); expect( events[1].oldValue ).toEqual( new Date(2000) ); }); it( "'complete' can updates a completed state and fire updated events.", function() { var todo = Todo.create(timeSource, "title", false, new Date(1000), new Date(2000) ); var events = []; todo.addObserver( Events.UPDATED, function(ev) { events.push( ev ); }); todo.complete(); expect( todo.title ).toEqual( "title" ); expect( todo.completed ).toEqual( true ); expect( todo.createdAt ).toEqual( new Date(1000) ); expect( todo.lastModified ).not.toEqual( new Date(2000) ); expect( events.length ).toEqual( 2 ); expect( events[0].eventId ).toEqual( Events.UPDATED ); expect( events[0].propertyName ).toEqual( "completed" ); expect( events[0].newValue ).toEqual( true ); expect( events[0].oldValue ).toEqual( false ); expect( events[1].eventId ).toEqual( Events.UPDATED ); expect( events[1].propertyName ).toEqual( "lastModified" ); expect( events[1].newValue ).toEqual( todo.lastModified ); expect( events[1].oldValue ).toEqual( new Date(2000) ); }); it( "'activate' can updates a completed state and fire updated events.", function() { var todo = Todo.create(timeSource, "title", true, new Date(1000), new Date(2000) ); var events = []; todo.addObserver( Events.UPDATED, function(ev) { events.push( ev ); }); todo.activate(); expect( todo.title ).toEqual( "title" ); expect( todo.completed ).toEqual( false ); expect( todo.createdAt ).toEqual( new Date(1000) ); expect( todo.lastModified ).not.toEqual( new Date(2000) ); expect( events.length ).toEqual( 2 ); expect( events[0].eventId ).toEqual( Events.UPDATED ); expect( events[0].propertyName ).toEqual( "completed" ); expect( events[0].newValue ).toEqual( false ); expect( events[0].oldValue ).toEqual( true ); expect( events[1].eventId ).toEqual( Events.UPDATED ); expect( events[1].propertyName ).toEqual( "lastModified" ); expect( events[1].newValue ).toEqual( todo.lastModified ); expect( events[1].oldValue ).toEqual( new Date(2000) ); }); }); });
TodoList spec
define([ "models/todo-list", "models/events", "test/mock/models/time-source" ], function( TodoList, Events, TimeSource ) { describe('TodoList', function() { var todoList; var events = []; beforeEach(function() { todoList = new TodoList(); todoList.timeSource = new TimeSource(); [Events.LOADED,Events.ADDED,Events.REMOVED].forEach(function(i) { todoList.addObserver( i, function(ev) { events.push( ev ); }); }); }); afterEach(function() { events = []; }); it( "'add' creates and add a new todo.", function() { todoList.add("test1"); expect( events.length ).toEqual( 1 ); expect( events[0].eventId ).toEqual( Events.ADDED ); expect( events[0].added.length ).toEqual( 1 ); expect( events[0].added[0].title ).toEqual( "test1" ); expect( events[0].items.length ).toEqual( 1 ); expect( events[0].items[0].title ).toEqual( "test1" ); todoList.add("test2"); expect( events.length ).toEqual( 2 ); expect( events[1].eventId ).toEqual( Events.ADDED ); expect( events[1].added.length ).toEqual( 1 ); expect( events[1].added[0].title ).toEqual( "test2" ); expect( events[1].items.length ).toEqual( 2 ); expect( events[1].items[0].title ).toEqual( "test1" ); expect( events[1].items[1].title ).toEqual( "test2" ); }); it( "'removeById' removes todos by id.", function() { var items = []; items.push(todoList.add("test1")); items.push(todoList.add("test2")); items.push(todoList.add("test3")); expect( todoList.items.length ).toEqual( 3 ); events = []; todoList.removeById( items[1].id ); { expect( todoList.items.length ).toEqual( 2 ); expect( events.length ).toEqual( 1 ); expect( events[0].eventId ).toEqual( Events.REMOVED ); expect( events[0].removed.length ).toEqual( 1 ); expect( events[0].removed[0].title ).toEqual( "test2" ); expect( events[0].items.length ).toEqual( 2 ); expect( events[0].items[0].title ).toEqual( "test1" ); expect( events[0].items[1].title ).toEqual( "test3" ); } todoList.removeById( items[2].id ); { expect( todoList.items.length ).toEqual( 1 ); expect( events.length ).toEqual( 2 ); expect( events[1].eventId ).toEqual( Events.REMOVED ); expect( events[1].removed.length ).toEqual( 1 ); expect( events[1].removed[0].title ).toEqual( "test3" ); expect( events[1].items.length ).toEqual( 1 ); expect( events[1].items[0].title ).toEqual( "test1" ); } todoList.removeById( 9999 ); { expect( todoList.items.length ).toEqual( 1 ); expect( events.length ).toEqual( 2 ); } todoList.removeById( items[0].id ); { expect( todoList.items.length ).toEqual( 0 ); expect( events.length ).toEqual( 3 ); expect( events[2].eventId ).toEqual( Events.REMOVED ); expect( events[2].removed.length ).toEqual( 1 ); expect( events[2].removed[0].title ).toEqual( "test1" ); expect( events[2].items.length ).toEqual( 0 ); } }); it( "'removeCompleted' removes completed todos.", function() { var items = []; items.push(todoList.add("test1")); items.push(todoList.add("test2")); items.push(todoList.add("test3")); expect( todoList.items.length ).toEqual( 3 ); events = []; todoList.removeCompleted( ); { expect( todoList.items.length ).toEqual( 3 ); expect( events.length ).toEqual( 0 ); } items[1].complete(); items[2].complete(); todoList.removeCompleted( ); { expect( todoList.items.length ).toEqual( 1 ); expect( events.length ).toEqual( 1 ); expect( events[0].eventId ).toEqual( Events.REMOVED ); expect( events[0].removed.length ).toEqual( 2 ); expect( events[0].removed[0].title ).toEqual( "test2" ); expect( events[0].removed[1].title ).toEqual( "test3" ); expect( events[0].items.length ).toEqual( 1 ); expect( events[0].items[0].title ).toEqual( "test1" ); } items[0].complete(); todoList.removeCompleted( ); { expect( todoList.items.length ).toEqual( 0 ); expect( events.length ).toEqual( 2 ); expect( events[1].eventId ).toEqual( Events.REMOVED ); expect( events[1].removed.length ).toEqual( 1 ); expect( events[1].removed[0].title ).toEqual( "test1" ); expect( events[1].items.length ).toEqual( 0 ); } }); }); });
ãã¹ããæ¸ããããå®è¡ç°å¢ãæ´åãã¦ããã¾ãã
- HTMLããã©ã¦ã¶ã«èªã¾ãã¦å®è¡ (ãã©ã¦ã¶ã§ã®ç¢ºèªç¨)
- ã³ãã³ãã©ã¤ã³ããRhinoã§å®è¡ (å®æãã«ãã§ã®ç¢ºèªç¨)
ã®2ã¤ãç¨æã
HTMLç
Jasminã®ãã¹ãã©ã³ãã¼ããã¼ã¹ã«ä½æãã¾ãããã¡ã¤ã«æ§æã¯ä»¥ä¸ã
âsrc/ ââmodels/ âãâtodo.js âãâtodo-list.js â test/ ãâspecs/ .. ãã¹ããæ ¼ç´ãããã£ã¬ã¯ã㪠ãââmodels/ ãâãâtodo.js ãâãâtodo-list.js ãâall-specs.js ãâconfig.js .. ãã¹ãç¨ã®require.jsè¨å®ãã¡ã¤ã« ãâtest.html .. ãã¹ãããã©ã¦ã¶ä¸ã§å®è¡ããæã«èªã¿è¾¼ã¾ããHTMLãã¡ã¤ã«
test.htmlã¯ä»¥ä¸ã®å 容ã
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Jasmine Test Runner</title> <link rel="stylesheet" type="text/css" href="../../../lib-test/jasmine-1.0.2/jasmine.css"> <script type="text/javascript" src="../../../lib/require.js"></script> <script type="text/javascript" src="config.js"></script> </head> <body> <script type="text/javascript"> require(["jasmine/jasmine"], function() { require(["jasmine/jasmine-html","larrymyers-jasmine-reporters/jasmine.console_reporter"], function() { require(["test/all-specs"], function() { jasmine.getEnv().addReporter(new jasmine.TrivialReporter()); jasmine.getEnv().execute(); }); }); }); </script> </body> </html>
config.jsã«require.jsã®è¨å®ãæ¸ãã¾ããpathsã«ä¾åã¢ã¸ã¥ã¼ã«ã®ãã¹ãè¨å®ã
require.config({ baseUrl: "../src/js", paths: { "container" : "../../../../minified/container", "knockout" : "./knockout-2.3.0", "test" : "../../test", "jasmine" : "../../../../lib-test/jasmine-1.0.2", "larrymyers-jasmine-reporters" : "../../../../lib-test/larrymyers-jasmine-reporters-adf6227/src" }, waitSeconds: 1 });
all-specs.js ã§ãã¹ããèªã¿è¾¼ã¿ã¾ãã
define([ "test/specs/models/todo", "test/specs/models/todo-list" ]);
ãã¨ã¯ãtest.htmlããã©ã¦ã¶ã«ãã©ãã°&ããããããã°ããã¹ããå®è¡ãããã¯ãã
ã³ãã³ãã©ã¤ã³ç
Antã使ãã¾ãã javaã¿ã¹ã¯ã§Rhinoãããã¹ããå®è¡ãã¾ãã
<?xml version="1.0" encoding="UTF-8"?> <project name="todo lists" default="release" basedir="."> <property name="test.dir" value="test" /> <property name="tools.dir" value="../../tools" /> <property name="rhino.home" value="${tools.dir}/rhino1_7R4" /> <property name="jasmine.home" value="../../lib-test/jasmine-1.0.2" /> <property name="jasmine-reporters.home" value="../../lib-test/larrymyers-jasmine-reporters-adf6227" /> <property name="envjs.home" value="../../lib-test/thatcher-env-js-cb738b9" /> <property name="closure-compiler.home" value="${tools.dir}/compiler-20130603" /> <target name="test"> <java jar="${rhino.home}/js.jar" fork='true' dir="test"> <arg line="-encoding utf-8 -opt -1 cui.js"/> <sysproperty key="envjs.home" value="../${envjs.home}"/> </java> </target> </project>
cui.jsã®å 容ã¯ä»¥ä¸ã
- Rhinoã§ãã¹ããå®è¡ããããã®è¨å®ããã«ããã«ãæ¸ãã¦ããã¾ãã
- config.js,all-specs.jsã¯htmlçã¨å ±æã
load("../../../tools/r.js"); load("../../../lib/require.js"); load(environment["envjs.home"]+"/dist/env.rhino.js"); load(environment["config"] || "config.js"); console = {}; console.log = function(str) { print(str); }; var consoleReporter = null; require(["jasmine/jasmine"], function() { require(["jasmine/jasmine-html", "larrymyers-jasmine-reporters/jasmine.console_reporter"], function() { require(["test/all-specs"], function() { consoleReporter = new jasmine.ConsoleReporter(); jasmine.getEnv().addReporter( consoleReporter ); jasmine.getEnv().execute(); }); }); }); Envjs.wait(); var filedCount = consoleReporter.executed_specs - consoleReporter.passed_specs; quit(filedCount <= 0 ? 0 : 1);
ãã¨ã¯ã
$ ant test
ã§å®è¡ããã¹ããèµ°ãã¾ãã
Buildfile: D:\git\container-js\samples\todo-list-example\build.xml test: [java] See https://github.com/jrburke/r.js for usage. [java] [ Envjs/1.6 (Rhino; U; Windows 7 amd64 6.1; en-US; rv:1.7.0.rc2) Resig/20070309 PilotFish/1.2.13 ] [java] Runner Started. [java] Todo : 'new' creates a new instance. ... [java] Passed. [java] Todo : 'Todo.create' can creates a new instance with specified properties. ... [java] Passed. [java] Todo : 'setTitle' can updates a title and fire updated events. ... [java] Passed. [java] Todo : 'complete' can updates a completed state and fire updated events. ... [java] Passed. [java] Todo : 'activate' can updates a completed state and fire updated events. ... [java] Passed. [java] Todo: 51 of 51 passed. [java] TodoList : 'add' creates and add a new todo. ... [java] Passed. [java] TodoList : 'removeById' removes todos by id. ... [java] Passed. [java] TodoList : 'removeCompleted' removes completed todos. ... [java] Passed. [java] TodoList: 54 of 54 passed. [java] Runner Finished. [java] 8 specs, 0 failures in 0.289s. BUILD SUCCESSFUL Total time: 5 seconds
3.ViewModelãä½ã
Modelã®æ¬¡ã¯ViewModelãä½ã£ã¦ããã¾ãã
- ViewModelã¯ãUIç¬èªã®ç¶æ
ãå¦çã管çãããªãã¸ã§ã¯ããã§ãã
- Modelãã©ããããå½¢ã§ãModelã¨Viewã®ä»²ç«ã¡ããã¾ãã
- Todoãªã¹ãã§ããã°ã以ä¸ã®ãããªå¦çãViewModelã®æ
å½ã«ãªãã¾ãã
- æçµæ´æ°æ¥æãUIã§è¡¨ç¤ºããæååã«å¤æããã
- Todoã®è¿½å ãã¿ã³ããå®äºããTodoãæ¶ããã¿ã³ããªã©ã®å¦çãå®è¡ããã¡ã½ãããæä¾ããã
- å®äºããTodoãæ¶ããã¿ã³ã®å©ç¨å¯/ä¸å¯ç¶æ ãå¶å¾¡ããã(å®äºããTodoã1ã¤ããªããã°å©ç¨ä¸å¯)
- Todoã追å ããéã«ã¿ã¤ãã«ãè¨å®ããå£ãæä¾ããã
- UIä¾åã®ãã¸ãã¯ãViewModelã¨ãã¦åé¢ãããã¨ã§ãModelã¯ããç´ç²ãªãã¸ãã¹ãã¸ãã¯ã ããæä¾ãã層ã«ã§ãã¾ãã
- UIã®å¤æ´ããã£ã¦ãViewModelã ãã§ã®å¤æ´ã§æ¸ã¿ãModelã«ã¯æ³¢åãã«ãããªãã¾ãã
ãUIãæ½è±¡åãã¦ãªãã¸ã§ã¯ãã«ãããã®ãèããã¨ãããããããããã¤ã¡ã¼ã¸çã«ã¯ä»¥ä¸ã®ãããªæãã
UIãæ½è±¡åãããã®ãªã®ã§ã å®éã®UIæä½ã·ããªãªã«æ²¿ã£ãå½¢ã§ãã¹ãã±ã¼ã¹ãæ¸ããã®ããã¤ã³ãã§ãã
ä½æããViewModelã®ä¸è¦§
ä½æããViewModelã¯ä»¥ä¸ã®3ã¤ã§ãã
- TodoListView
- Todoã®ä¸è¦§é¨åã«å¯¾å¿ããViewModelã§ãã
- TodoView
- Todoä¸è¦§ã®ä¸ã®åTodoã«å¯¾å¿ããViewModelã§ãã
- TodoInputForm
- æ°ããTodoãä½æããå ¥åãã©ã¼ã é¨åã«å¯¾å¿ããViewModelã§ãã
ViewModelã§ã¯Knockoutã®æ©è½ãå©ç¨ãã¾ãã
TodoListView
- items ã§Todoã®ä¸è¦§ãæã¡ã¾ãã
- itemsã¯ã¢ãã«ã®å¤æ´éç¥ãåãã¦éææ´æ°ã
- UIä¸ã®ãã¿ã³ãæ¼ä¸ããéã®å¦çãã¡ã½ããã§æä¾ãã¾ãã
- Todoã®è¿½å (newTodo),å®äºããTodoã®åé¤(removeCompleted)
- enableToRemoveCompleted ã§å®äºããTodoã®åé¤ãã¿ã³ãå©ç¨å¯ãã©ããã管çãã¾ãã
define([ "container", "knockout", "models/events", "viewmodels/todo-view" ], function(ContainerJS, ko, Events, TodoView){ "use strict"; /** * @class */ var TodoListView = function() { this.todoList = ContainerJS.Inject; this.todoInputForm = ContainerJS.Inject; this.items = ko.observableArray(); this.numberOfCompleted = ko.observable(0); this.enableToRemoveCompleted = ko.computed(function(){ return this.numberOfCompleted() > 0; }.bind(this)); Object.seal(this); }; TodoListView.prototype.initialize = function(){ this.todoList.addObserver( Events.LOADED, this.onLoad.bind(this) ); this.todoList.addObserver( Events.ADDED, this.onAdded.bind(this) ); this.todoList.addObserver( Events.REMOVED, this.onRemoved.bind(this) ); }; TodoListView.prototype.newTodo = function() { this.todoInputForm.newTodo(); }; TodoListView.prototype.removeCompleted = function(event) { this.todoList.removeCompleted(); }; TodoListView.prototype.onLoad = function(event) { this.items(event.items.map(function(todo){ this.addObserverToTodo(todo); return new TodoView(todo); }.bind(this))); }; TodoListView.prototype.onAdded = function(event) { event.added.forEach( function( todo ) { this.addObserverToTodo(todo); this.updateNumberOfCompleted( todo.completed ? 1 : 0 ); this.items.push(new TodoView(todo)); }.bind(this)); }; TodoListView.prototype.onRemoved = function(event) { var removedIds = {}; event.removed.forEach( function( todo ) { this.updateNumberOfCompleted( todo.completed ? -1 : 0 ); removedIds[todo.id] = true; }.bind(this)); this.items.remove( function(item) { return removedIds[item.model.id]; }); }; /** @private */ TodoListView.prototype.addObserverToTodo = function(todo){ todo.addObserver( Events.UPDATED, this.onTodoUpdated.bind(this)); }; /** @private */ TodoListView.prototype.onTodoUpdated = function(event){ if (event.propertyName === "completed") { if (event.newValue !== event.oldValue) { this.updateNumberOfCompleted( event.newValue ? 1 : -1 ); } } }; /** @private */ TodoListView.prototype.updateNumberOfCompleted = function(count){ this.numberOfCompleted( this.numberOfCompleted()+count ); }; return Object.freeze(TodoListView); });
TodoView
ä¸è¦§ã®åTodoã«å¯¾å¿ããViewModelã§ãã
- ã¿ã¤ãã«(title)ãæçµæ´æ°æå»(lastModified)ãªã©ãæã¡ã¾ãã
- Todoã®å®äºæä½ãã(åå¥ã®)åé¤ã¯Todoãã¨ã®æä½ãªã®ã§ããã¡ãã«å®ç¾©ãã¾ãã
define([ "container", "knockout", "models/events", "viewmodels/todo-view" ], function(ContainerJS, ko, Events, TodoView){ "use strict"; /** * @class */ var TodoView = function(todo) { this.model = todo; this.title = ko.observable(todo.title); this.completed = ko.observable(todo.completed); this.createdAt = ko.observable(todo.createdAt); this.lastModified = ko.observable(todo.lastModified); this.createdAtForDisplay = ko.computed(function(){ return formatDate(this.createdAt()); }.bind(this)); this.lastModifiedForDisplay = ko.computed(function(){ return formatDate(this.lastModified()); }.bind(this)); this.addObservers(); Object.seal(this); }; TodoView.prototype.addObservers = function(){ this.model.addObserver( Events.UPDATED, this.onUpdated.bind(this) ); }; TodoView.prototype.remove = function() { this.model.remove(); }; TodoView.prototype.complete = function(event) { this.model.complete(); }; TodoView.prototype.activate = function(event) { this.model.activate(); } TodoView.prototype.onUpdated = function(event) { this[event.propertyName](event.newValue); }; var formatDate = function(d){ if (!d) return ""; return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); }; return Object.freeze(TodoView); });
TodoInputForm
æå¾ã¯TodoInputFormãTodoã®æ°è¦ä½æãã©ã¼ã é¨åã«å¯¾å¿ããViewModelã§ãã
- ã¿ã¤ãã«(title)ãåãåãå£ã¨ã¨ã©ã¼æ
å ±(error)ãæã¡ã¾ãã
- Todo追å æã«ã¿ã¤ãã«ãæªå ¥åã ã£ããããå ´åãã¨ã©ã¼ã¡ãã»ã¼ã¸ãerrorã«å ¥ããã¼ã¿ãã¤ã³ãã£ã³ã°ã§UIã«è¡¨ç¤ºãããä»çµã¿ã
- Todo追å ã®æ¬å¦çããã¡ãã«å®ç¾©ãã¦ãã¾ãã
define([ "container", "knockout" ], function(ContainerJS, ko){ "use strict"; /** * @class */ var TodoInputForm = function() { this.todoList = ContainerJS.Inject; this.title = ko.observable(""); this.titleLength = ko.computed(function(){ return this.title().length; }.bind(this)); this.error = ko.observable(""); Object.seal(this); }; TodoInputForm.prototype.newTodo = function() { try { this.todoList.add( this.title() ); this.title(""); this.error(""); } catch ( exception ) { this.error(exception.message); } }; return Object.freeze(TodoInputForm); });
ViewModelã¨Modelã®çæã¨é¢é£ä»ãã¯ContainerJSã§ç®¡çãã¾ããContainerJSç¨ã®ã¢ã¸ã¥ã¼ã«å®ç¾©(composing/modules.js)ãç¨æãã¦ããã¾ãã
define(["container"], function( ContainerJS ) { "use strict"; var models = function( binder ){ binder.bind("todoList").to("models.TodoList"); binder.bind("timeSource").to("models.TimeSource"); }; var viewmodels = function( binder ){ binder.bind("todoListView").to("viewmodels.TodoListView").onInitialize("initialize"); binder.bind("todoInputForm").to("viewmodels.TodoInputForm"); }; var modules = function( binder ){ models(binder); viewmodels(binder); }; return modules; });
4.ViewModelããã¹ããã
ViewModelããã¹ããã¦ããã¾ãã
- ä»åã¯Modelã®ã¢ãã¯ã¯ç¨æãããViewModel+Modelã®åããã¾ã¨ãã¦ãã¹ãããããã«ãã¾ããã
- TimeSourceã®ã¿mockã使ãã¾ãã
- ãã¹ãã§ãViewModel,Modelã®ä½æã¯ContainerJSã使ã£ã¦è¡ãã¾ãã
TodoListView spec
- Todoã®è¿½å ã»åé¤ãåä½ãããã¨ã確èªã
- 追å ã»åé¤ã«å¿ãã¦ä¸è¦§ã®ãã¼ã¿ãå¢æ¸ãããã¨ããã§ãã¯ã
define([ "container", "models/events", "test/mock/modules", "test/utils/wait" ], function( ContainerJS, Events, modules, Wait ) { describe('TodoListView', function() { var container; var deferred; beforeEach(function() { container = new ContainerJS.Container( modules ); deferred = container.get( "todoListView" ); }); it( "first, items is empty .", function() { Wait.forFix(deferred); runs( function(){ var view = ContainerJS.utils.Deferred.unpack( deferred ); view.todoList.load(); expect( view.items().length ).toBe( 0 ); }); }); it( "can adds a new todo.", function() { Wait.forFix(deferred); runs( function(){ var view = ContainerJS.utils.Deferred.unpack( deferred ); view.todoList.load(); view.todoInputForm.title("test"); view.newTodo(); view.todoInputForm.title("test2"); view.newTodo(); view.todoInputForm.title("test3"); view.newTodo(); expect( view.items().length ).toBe( 3 ); expect( view.items()[0].title() ).toBe( "test" ); expect( view.items()[1].title() ).toBe( "test2" ); expect( view.items()[2].title() ).toBe( "test3" ); }); }); it( "can removes a todo.", function() { Wait.forFix(deferred); runs( function(){ var view = ContainerJS.utils.Deferred.unpack( deferred ); view.todoList.load(); view.todoInputForm.title("test"); view.newTodo(); view.todoInputForm.title("test2"); view.newTodo(); view.todoInputForm.title("test3"); view.newTodo(); expect( view.items().length ).toBe( 3 ); expect( view.items()[0].title() ).toBe( "test" ); expect( view.items()[1].title() ).toBe( "test2" ); expect( view.items()[2].title() ).toBe( "test3" ); view.items()[1].remove(); expect( view.items().length ).toBe( 2 ); expect( view.items()[0].title() ).toBe( "test" ); expect( view.items()[1].title() ).toBe( "test3" ); }); }); it( "can removes completed todos.", function() { Wait.forFix(deferred); runs( function(){ var view = ContainerJS.utils.Deferred.unpack( deferred ); view.todoList.load(); expect( view.enableToRemoveCompleted() ).toBe( false ); view.todoInputForm.title("test"); view.newTodo(); view.todoInputForm.title("test2"); view.newTodo(); view.todoInputForm.title("test3"); view.newTodo(); expect( view.items().length ).toBe( 3 ); expect( view.items()[0].title() ).toBe( "test" ); expect( view.items()[1].title() ).toBe( "test2" ); expect( view.items()[2].title() ).toBe( "test3" ); expect( view.enableToRemoveCompleted() ).toBe( false ); view.items()[0].complete(); expect( view.enableToRemoveCompleted() ).toBe( true ); view.items()[2].complete(); expect( view.enableToRemoveCompleted() ).toBe( true ); view.removeCompleted(); expect( view.items().length ).toBe( 1 ); expect( view.items()[0].title() ).toBe( "test2" ); expect( view.enableToRemoveCompleted() ).toBe( false ); view.items()[0].complete(); expect( view.enableToRemoveCompleted() ).toBe( true ); view.items()[0].activate(); expect( view.enableToRemoveCompleted() ).toBe( false ); }); }); }); });
TodoView spec
- Todoã¢ãã«ã®ããããã£ãæ£ããåæ ããã¦ãããã¨ã確èªã
define([ "models/todo", "models/events", "viewmodels/todo-view", "test/mock/models/time-source" ], function( Todo, Events, TodoView, TimeSource ) { describe('TodoView', function() { var timeSource = new TimeSource(); beforeEach(function() { timeSource.set(2013, 1, 1); }); it( "reflects a model's value.", function() { var todo = new Todo(timeSource); todo.setTitle("test."); var view = new TodoView(todo); { expect( view.title() ).toBe( "test." ); expect( view.completed() ).toBe( false ); expect( view.createdAt() ).toEqual( new Date( 2013, 0, 1 ) ); expect( view.lastModified() ).toEqual( new Date( 2013, 0, 1 )); expect( view.createdAtForDisplay() ).toEqual( "2013-1-1 0:0:0" ); expect( view.lastModifiedForDisplay() ).toEqual( "2013-1-1 0:0:0" ); } timeSource.set(2013, 2, 10); todo.setTitle("test2."); { expect( view.title() ).toBe( "test2." ); expect( view.completed() ).toBe( false ); expect( view.createdAt() ).toEqual( new Date( 2013, 0, 1 ) ); expect( view.lastModified() ).toEqual( new Date( 2013, 1, 10 )); expect( view.createdAtForDisplay() ).toEqual( "2013-1-1 0:0:0" ); expect( view.lastModifiedForDisplay() ).toEqual( "2013-2-10 0:0:0" ); } timeSource.set(2013, 2, 12); todo.complete(); { expect( view.title() ).toBe( "test2." ); expect( view.completed() ).toBe( true ); expect( view.createdAt() ).toEqual( new Date( 2013, 0, 1 ) ); expect( view.lastModified() ).toEqual( new Date( 2013, 1, 12 )); expect( view.createdAtForDisplay() ).toEqual( "2013-1-1 0:0:0" ); expect( view.lastModifiedForDisplay() ).toEqual( "2013-2-12 0:0:0" ); } timeSource.set(2013, 3, 7); todo.activate(); { expect( view.title() ).toBe( "test2." ); expect( view.completed() ).toBe( false ); expect( view.createdAt() ).toEqual( new Date( 2013, 0, 1 ) ); expect( view.lastModified() ).toEqual( new Date( 2013, 2, 7 )); expect( view.createdAtForDisplay() ).toEqual( "2013-1-1 0:0:0" ); expect( view.lastModifiedForDisplay() ).toEqual( "2013-3-7 0:0:0" ); } }); }); });
TodoInputForm spec
- ã¿ã¤ãã«ãå ¥ãã¦Todoã追å ã§ãããã¿ã¤ãã«ãæªå ¥åã ã¨è¿½å ã§ããã¨ã©ã¼ã表示ããããã¨ãã£ããããã確èªã
define([ "container", "models/events", "test/mock/modules", "test/utils/wait" ], function( ContainerJS, Events, modules, Wait ) { describe('TodoInputForm', function() { var container; beforeEach(function() { container = new ContainerJS.Container( modules ); }); it( "'newTodo' can create a new Todo.", function() { var deferred = container.get( "todoInputForm" ); Wait.forFix(deferred); runs( function(){ var form = ContainerJS.utils.Deferred.unpack( deferred ); { expect( form.title() ).toBe( "" ); expect( form.titleLength() ).toBe( 0 ); expect( form.error() ).toBe( "" ); expect( form.todoList.items.length ).toBe( 0 ); } form.title("test."); { expect( form.title() ).toBe( "test." ); expect( form.titleLength() ).toBe( 5 ); expect( form.error() ).toBe( "" ); } form.newTodo(); { expect( form.title() ).toBe( "" ); expect( form.titleLength() ).toBe( 0 ); expect( form.error() ).toBe( "" ); expect( form.todoList.items.length ).toBe( 1 ); expect( form.todoList.items[0].title ).toBe( "test." ); } }); }); it( "'newTodo' fails when the title is invalid.", function() { var deferred = container.get( "todoInputForm" ); Wait.forFix(deferred); runs( function(){ var form = ContainerJS.utils.Deferred.unpack( deferred ); form.newTodo(); { expect( form.title() ).toBe( "" ); expect( form.titleLength() ).toBe( 0 ); expect( form.error() ).toBe( "title is not set." ); expect( form.todoList.items.length ).toBe( 0 ); } form.title( createStringOfLength(101) ); form.newTodo(); { expect( form.title() ).toBe( createStringOfLength(101) ); expect( form.titleLength() ).toBe( 101 ); expect( form.error() ).toBe( "title is too long." ); expect( form.todoList.items.length ).toBe( 0 ); } form.title( createStringOfLength(100) ); form.newTodo(); { expect( form.title() ).toBe( "" ); expect( form.titleLength() ).toBe( 0 ); expect( form.error() ).toBe( "" ); expect( form.todoList.items.length ).toBe( 1 ); expect( form.todoList.items[0].title ).toBe( createStringOfLength(100) ); } }); }); var createStringOfLength = function(length) { var str = ""; for ( var i=length;i>0;i-- ) str += "a"; return str; }; }); });
5.View(HTML/CSS)ãä½ã
Modelã¨ViewModelãã§ããããViewãä½æãã¦ãView/ViewModel/Modelãçæããé¨åã®ã³ã¼ããæ¸ãã¦ããã¾ãã
ã¾ãã¯HTMLã
- ãdata-bindãã§Viewã¨ViewModelãç¹ãã§ããã¾ãã
- ããã«ViewModelã®ââã§ä¿æããããã¼ã¿ãæµãè¾¼ã
- ãã®ãã¿ã³ãæ¼ãããããViewModelã®â²â²ã¡ã½ãããå®è¡ãã¨ãã£ãæãã
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo List</title> <script type="text/javascript" data-main="../js/main.js" src="../../../../lib/require.js"> </script> <link rel="stylesheet" type="text/css" href="../css/todo-list.css"> </head> <body> <div class="todo-input-form"> title: <input type="text" data-bind="value:todoInputForm.title" class="title-field"></input> <button data-bind="click:newTodo" class="add-button"> add </button> <div class="error" data-bind="text:todoInputForm.error"></div> </div > <div class="todo-list" data-bind="foreach:items, visible:true" style="display:none;"> <div class="todo"> <img src="../images/normal.png" alt="" data-bind="visible:!completed()" /> <img src="../images/check.png" alt="checked" data-bind="visible:completed" /> <span class="title" data-bind="text:title"></span> <span class="last-modified" data-bind="text:lastModifiedForDisplay"></span> <span class="commands"> <a href="javascript:void(0)" data-bind="click:activate, visible: completed()"> activate </a> <a href="javascript:void(0)" data-bind="click:complete, visible: !completed()"> complete </a> <a href="javascript:void(0)" data-bind="click:remove">remove</a> </span> </div> </div> <div class="buttons"> <button data-bind="click: removeCompleted, enable:enableToRemoveCompleted"> remove completed </button> </div> </body> </html>
CSSãæ¸ãã¾ã
@charset "UTF-8"; body { padding:10px; color: #333; } button { padding: 3px 15px; } .todo-input-form { padding: 10px; border: 1px solid #ddd; } .todo-input-form .title-field { width: 200px; } .todo-input-form .error { color: #fa1188; } .todo-list { margin-left: 10px; } .todo { margin-top: 10px; } .todo span{ padding-right: 5px; } .todo .title { font-size: 110%; } .todo .last-modified { font-size: 80%; color: #999; } .todo .commands a { padding-right: 5px; } .buttons { margin-top: 10px; padding: 10px; border: 1px solid #ddd; }
æå¾ã¯main.jsã
main.jsã¯htmlã§æåã«èªã¾ããã¹ã¯ãªããã§ã以ä¸ã®å¦çãè¡ãã¾ãã
- DIã³ã³ããã使ã£ã¦ViewModel/Modelãä½ã
- Knockoutã®APIãå¼ã³åºãã¦ãViewModelã¨Viewã®é¢é£ä»ã(ãã¼ã¿ãã¤ã³ã)ãå®è¡ãã¾ãã
require.config({ baseUrl: "../js", paths: { "container" : "../../../../minified/container", "knockout" : "knockout-2.3.0" } }); require([ "container", "knockout", "composing/modules", "models/time-source", "models/todo-list", "models/todo", "viewmodels/todo-input-form", "viewmodels/todo-list-view", "viewmodels/todo-view" ], function( ContainerJS, ko, modules ) { var container = new ContainerJS.Container( modules ); container.get("todoListView").then(function( viewModel ){ ko.applyBindings(viewModel); }, function( error ) { alert( error.toString() ); }); });
å¿ è¦ãªã½ã¼ã¹ã®ä½æã¯ããã§å®äºãåãã¦ããã¨ããã¯こちらã§ã¿ããã¾ãã
6.minify && çµåãã¦ãªãªã¼ã¹
æå¾ã«ãã½ã¼ã¹ãminify && çµåãã¦ãªãªã¼ã¹ããAntã¿ã¹ã¯ãå®ç¾©ãã¦ããã¾ãã
- minify && çµåã¯Require.jsãããªãªã¼ã¹ããã¦ããr.jsã使ç¨ã
- Require.jsã®ä¾åé¢ä¿ããã©ã£ã¦å¿ è¦ãªãã¡ã¤ã«ãéãã1ãã¡ã¤ã«ã«çµ±åãã¾ãã
- closureçã使ç¨ãã¦minifyãè¡ãæ©è½ãããã¾ãã
- ãµã³ãã«ã§ã¯ãmain.jsã«ãã¹ã¦ã®ãã¡ã¤ã«ãçµ±åããå½¢ã«ãã¦ãã¾ãã
- ãªãªã¼ã¹çã§ã¯main.jsãçµ±åçã«ç½®ãæãããrequire.jsã¨main.jsã®ã¿ã®èªã¿è¾¼ã¿ã¨ãªãã¾ãã
- æåã«èªããã¡ã¤ã«ã¯å¤ãããªãã®ã§ãHTMLã®å¤æ´ã¯ä¸è¦ã
Antã¿ã¹ã¯ã¯ä»¥ä¸ã
<?xml version="1.0" encoding="UTF-8"?> <project name="todo lists" default="release" basedir="."> <property name="test.dir" value="test" /> <property name="tools.dir" value="../../tools" /> <property name="rhino.home" value="${tools.dir}/rhino1_7R4" /> <property name="jasmine.home" value="../../lib-test/jasmine-1.0.2" /> <property name="jasmine-reporters.home" value="../../lib-test/larrymyers-jasmine-reporters-adf6227" /> <property name="envjs.home" value="../../lib-test/thatcher-env-js-cb738b9" /> <property name="closure-compiler.home" value="${tools.dir}/compiler-20130603" /> .. ç¥ <target name="minify"> <java fork='true' dir="${basedir}" classname="org.mozilla.javascript.tools.shell.Main"> <classpath> <path path="${rhino.home}/js.jar" /> <path path="${closure-compiler.home}/compiler.jar" /> </classpath> <jvmarg line="-Xss1m"/> <arg line="-encoding utf-8 ${tools.dir}/r.js -o dist/build-config.json"/> </java> </target> </project>
build-config.jsonã« minify && çµå ã®è¨å®ãæ¸ãã¾ãã
({ appDir: "../src", baseUrl: "./js", paths: { "container" : "../../../../minified/container", "knockout" : "knockout-2.3.0" }, dir: "../dst", optimize: "closure", closure: { CompilerOptions: {}, CompilationLevel: 'SIMPLE_OPTIMIZATIONS', loggingLevel: 'WARNING' }, optimizeCss: "standard.keepLines", modules: [{ name: "main" }] })
ã¿ã¹ã¯ãå®è¡ããã¨
$ ant minify
ãdstããã£ã¬ã¯ããªä»¥ä¸ã«æé©åãããã½ã¼ã¹ãåºåããã¾ãããã¨ã¯ããããªãªã¼ã¹ããã°OKã
ãªãªã¼ã¹çã¯こちらã最適化前ã¨æ¯ã¹ã¦jsã®èªã¿è¾¼ã¿ãæå°åããã¦ãã¾ãã
ã¾ã¨ã
ãã¥ã¼ããªã¢ã«ã¯ä»¥ä¸ãMVVMã§Viewã¨ãã以å¤ãåé¢ããã°ã¯ã©ã¤ã¢ã³ãã¢ããªãããªãã®é¨åããã¹ãã§ãããã¨ããã¨ãããä¼ããã°å¹¸ãã§ãã
ä»åã®ãã¥ã¼ããªã¢ã«ã«ã¯å
¥ããªãã£ãã¨ããã§ããã¤ã¢ãã°ãåºãã¨ãã¯MVVMã§ä½ãã¨ã¡ãã£ã¨é¢åãããä»æããå¿
è¦ã ã£ãããã¾ãããã¡ããã¨ãã¹ã¿ãã«ã«ä½ããã¨ã¯å¯è½ã§ãã(ãã®ãããã¯æ©ä¼ãããã°ã¾ãä»åº¦ã)
ã¡ãªã¿ã«ãæããã¸ã§ã¯ãã§æ¡ç¨ããã¢ã¼ããã¯ãã£ã§ãjsã¯æ°ä¸è¡ç¨åº¦ã®è¦æ¨¡ã§ãããä»ã®ã¨ããç ´ç¶»ãªãéç¨ã§ãã¦ãã¾ããã