Skip to content

Instantly share code, notes, and snippets.

@hakobe
Last active December 15, 2015 04:59
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hakobe/5205534 to your computer and use it in GitHub Desktop.
Save hakobe/5205534 to your computer and use it in GitHub Desktop.
3種類の方式のクライアントサイドMVCをつかった疑似TODOアプリ実装
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<style>
body {
margin : 0;
padding : 0;
background-color: #eee;
font-size: 25px;
}
#content {
background-color: white;
margin: 0 auto;
padding: 20px;
width : 480px;
}
h1 {
font-size: 28px;
}
input {
font-size: 28px;
}
ul {
list-style: none;
padding: 0;
}
</style>
</head>
<body>
<section id="content">
<h1>TODO sample</h1>
<form id="task-add" method="post" action=".">
<input id="new-task-name" type="text" placeholder="input your task">
</form>
<ul id="tasks">
</ul>
</section>
<script src="http://zeptojs.com/zepto.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="./backbone_sample.js"></script>
</body>
</html>
(function() {
'use strict';
var Model = {};
var View = {};
Model.Task = Backbone.Model.extend({
defaults : {
done : false
},
});
Model.Tasks = Backbone.Collection.extend({
model : Model.Task
});
View.TaskList = Backbone.View.extend({
initialize : function() {
// 状態の変化をモデルから受けとる
this.collection.on('add', function() {
this.render();
}, this);
this.collection.on('change', function() {
this.render();
}, this);
},
render : function() {
// {{{ html を再描画する ちょっときたない..
this.$el.empty();
this.collection.each( function(task) {
var $item = $('<li></li>');
if (task.get('done')) {
$item.css({ 'color' : '#eee' });
}
$item.append( $('<input></input>').
attr({ 'type':'checkbox' }).
prop('checked', task.get('done') ? 'checked' : '').
on('click', function(e) {
task.set({ 'done' : $(e.target).prop('checked') ? true : false });
})
);
$item.append( task.get('title') );
this.$el.append($item);
}, this);
// }}}
}
});
// モデルとビューの初期化
var tasks = new Model.Tasks();
var taskList = new View.TaskList({
collection : tasks,
el : $('#tasks'),
});
tasks.add(new Model.Task({ title : '牛乳を買う' }));
tasks.add(new Model.Task({ title : 'お金を1000円返す' }));
tasks.add(new Model.Task({ title : 'テレビを新調する' }));
tasks.add(new Model.Task({ title : '郵便を出す' }));
// コントローラを設定
var $taskAddForm = $('#task-add');
var $newTaskNameForm = $('#new-task-name');
$newTaskNameForm.focus();
$taskAddForm.on('submit', function(e) {
tasks.unshift( new Model.Task({ title : $newTaskNameForm.val() }) );
$newTaskNameForm.val('');
return false;
});
})();
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<style>
body {
margin : 0;
padding : 0;
background-color: #eee;
font-size: 25px;
}
#content {
background-color: white;
margin: 0 auto;
padding: 20px;
width : 480px;
}
h1 {
font-size: 28px;
}
input {
font-size: 28px;
}
ul {
list-style: none;
padding: 0;
}
</style>
</head>
<body>
<section id="content">
<h1>TODO sample</h1>
<form id="task-add" method="post" action=".">
<input id="new-task-name" type="text" placeholder="input your task">
</form>
<ul data-bind="foreach: tasks">
<li data-bind="style: { color: done() ? '#eee' : '#000' }"">
<input type="checkbox" data-bind="checked: done(), click: $parent.checkboxClicked"/>
<span data-bind="text: done()"></span>
</li>
</ul>
</section>
<script src="http://zeptojs.com/zepto.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.0/knockout-min.js"></script>
<script src="./knockout_sample.js"></script>
</body>
</html>
(function() {
'use strict';
var Model = {};
var ViewModel = {};
Model.Task = function(title) {
this.title = title;
this.done = ko.observable(false); // done の変化があれば通知
};
ViewModel.TaskList = function() {
this.tasks = ko.observableArray([ // Arrayに変化があれば通知
new Model.Task('牛乳を買う'),
new Model.Task('お金を1000円返す'),
new Model.Task('テレビを新調する'),
new Model.Task('郵便を出す'),
]);
this.checkboxClicked = function(task) {
task.done( !task.done() );
return true;
};
};
var taskList = new ViewModel.TaskList();
ko.applyBindings(taskList); // HTMLにbind (HTML中のdata-bindを参照)
// コントローラを設定
var $taskAddForm = $('#task-add');
var $newTaskNameForm = $('#new-task-name');
$newTaskNameForm.focus();
$taskAddForm.on('submit', function(e) {
taskList.tasks.unshift(
new Model.Task($newTaskNameForm.val())
);
$newTaskNameForm.val('');
return false;
});
})();
/*!
* LucidJS
*
* Lucid is an easy to use event emitter library. LucidJS allows you to create your own event system and even pipe in
* events from one emitter to another.
*
* Copyright 2012, Robert William Hurst
* Licenced under the BSD License.
* See https://raw.github.com/RobertWHurst/LucidJS/master/license.txt
*/
(function(factory) {
//AMD
if(typeof define === 'function' && define.amd) {
define(factory);
//NODE
} else if((typeof module == 'object' || typeof module == 'function') && module.exports) {
module.exports = factory();
//GLOBAL
} else {
window.LucidJS = factory();
}
})(function() {
var api;
//return the api
api = {
"emitter": EventEmitter
};
//indexOf pollyfill
[].indexOf||(Array.prototype.indexOf=function(a,b,c){for(c=this.length,b=(c+~~b)%c;b<c&&(!(b in this)||this[b]!==a);b++);return b^c?b:-1;});
return api;
/**
* Creates a event emitter.
*/
function EventEmitter(object) {
var emitter = object || {}, listeners = {}, setEvents = {}, pipes = [];
//augment an object if it isn't already an emitter
if(
!emitter.on &&
!emitter.once &&
!emitter.trigger &&
!emitter.set &&
!emitter.pipe &&
!emitter.listeners
) {
emitter.on = on;
emitter.off = off;
emitter.once = once;
emitter.trigger = trigger;
emitter.set = set;
emitter.set.clear = clearSet;
emitter.pipe = pipe;
emitter.pipe.clear = clearPipes;
emitter.listeners = getListeners;
emitter.listeners.clear = clearListeners;
} else {
return emitter;
}
if(emitter.addEventListener || emitter.attachEvent) {
handleNode(emitter);
}
return emitter;
/**
* Binds listeners to events.
* @param event
* @return {Object}
*/
function on(event ) {
var args = Array.prototype.slice.apply(arguments, [1]), binding = {}, aI, sI;
//recurse over a batch of events
if(typeof event === 'object' && typeof event.push === 'function') { return batchOn(event, args); }
//trigger the listener event
if(event.slice(0, 7) !== 'emitter') {
trigger('emitter.listener', event, args);
}
//create the event
if(!listeners[event]) { listeners[event] = []; }
//add each callback
for(aI = 0; aI < args.length; aI += 1) {
if(typeof args[aI] !== 'function') { throw new Error('Cannot bind event. All callbacks must be functions.'); }
listeners[event].push(args[aI]);
}
binding.clear = clear;
return binding;
function clear() {
if(!listeners[event]) { return; }
for(aI = 0; aI < args.length; aI += 1) {
listeners[event].splice(listeners[event].indexOf(args[aI]), 1);
}
if(listeners[event].length < 1) { delete listeners[event]; }
}
function batchOn(events, args) {
var eI, binding = {}, bindings = [];
for(eI = 0; eI < events.length; eI += 1) {
args.unshift(events[eI]);
bindings.push(on.apply(this, args));
args.shift();
}
binding.clear = clear;
return binding;
function clear() {
var bI;
for(bI = 0; bI < bindings.length; bI += 1) {
bindings[bI].clear();
}
}
}
}
/**
* Unbinds listeners to events.
* @param event
* @return {Object}
*/
function off(event ) {
var args = Array.prototype.slice.apply(arguments, [1]), aI, sI;
//recurse over a batch of events
if(typeof event === 'object' && typeof event.push === 'function') {
for(sI = 0; sI < event.length; sI += 1) {
off.apply(null, [event[sI]].concat(args));
}
return;
}
if(!listeners[event]) { throw new Error('Tried to remove an event from a non-existant event of type "'+event+'".'); }
//remove each callback
for(aI = 0; aI < args.length; aI += 1) {
if(typeof args[aI] !== 'function') { throw new Error('Tried to remove a non-function.'); }
var listenerIndex = listeners[event].indexOf(args[aI]);
listeners[event].splice(listenerIndex, 1);
}
}
/**
* Binds listeners to events. Once an event is fired the binding is cleared automatically.
* @param event
* @return {Object}
*/
function once(event ) {
var binding, args = Array.prototype.slice.apply(arguments, [1]), result = true;
binding = on(event, function( ) {
var aI, eventArgs = Array.prototype.slice.apply(arguments);
binding.clear();
for(aI = 0; aI < args.length; aI += 1) {
if(args[aI].apply(this, eventArgs) === false) {
result = false;
}
}
return result;
});
return binding;
}
/**
* Triggers events. Passes listeners any additional arguments.
* Optimized for 4 arguments.
* @param event
* @return {Boolean}
*/
function trigger(event, a1, a2, a3, a4, la) {
var longArgs, lI, eventListeners, result = true;
if(typeof la !== 'undefined') {
longArgs = Array.prototype.slice.apply(arguments, [1]);
}
if(typeof event === 'object' && typeof event.push === 'function') {
if(longArgs) {
return batchTrigger.apply(null, arguments);
} else {
return batchTrigger(event, a1, a2, a3, a4);
}
}
event = event.split('.');
while(event.length) {
eventListeners = listeners[event.join('.')];
if(event[0] !== 'emitter') {
if(longArgs) {
trigger.apply(this, [].concat('emitter.event', event.join('.'), longArgs));
} else {
trigger('emitter.event', a1, a2, a3, a4);
}
}
if(eventListeners) {
eventListeners = [].concat(eventListeners);
for(lI = 0; lI < eventListeners.length; lI += 1) {
if(longArgs) {
if(eventListeners[lI].apply(this, longArgs) === false) {
result = false;
}
} else {
if(eventListeners[lI](a1, a2, a3, a4) === false) {
result = false;
}
}
}
}
event.pop();
}
return result;
function batchTrigger(events, a1, a2, a3, a4, la) {
var eI, result = true;
if(typeof la !== 'undefined') {
longArgs = Array.prototype.slice.apply(arguments, [1]);
}
for(eI = 0; eI < events.length; eI += 1) {
if(longArgs) {
args.unshift(events[eI]);
if(trigger.apply(this, args) === false) { result = false; }
args.shift();
} else {
if(trigger(events[eI], a1, a2, a3, a4) === false) { result = false; }
}
}
return result;
}
}
/**
* Sets events. Passes listeners any additional arguments.
* @param event
* @return {*}
*/
function set(event, a1, a2, a3, a4, la) {
var args, binding, _clear;
if(la) { args = Array.prototype.slice.apply(arguments, [1]); }
if(la) { trigger.apply(arguments) }
else { trigger(event, a1, a2, a3, a4); }
binding = on('emitter.listener', function(_event, listeners) {
var lI;
if(event === _event) {
for(lI = 0; lI < listeners.length; lI += 1) {
if(args) { listeners[lI].apply(args); }
else { listeners[lI](a1, a2, a3, a4); }
}
}
});
if(!setEvents[event]) { setEvents[event] = []; };
setEvents[event].push(binding);
_clear = binding.clear;
binding.clear = clear;
return binding;
function clear() {
setEvents[event].splice(setEvents[event].indexOf(binding), 1);
_clear();
}
}
/**
* Clears a set event, or all set events.
* @param event
*/
function clearSet(event) {
var bI;
if(event) {
for(bI = 0; bI < setEvents[event].length; bI += 1) {
setEvents[event][bI].clear();
}
delete setEvents[event];
} else {
for(event in setEvents) {
if(!setEvents.hasOwnProperty(event)) { continue; }
clearSet(event);
}
}
}
/**
* Pipes events from another emitter.
* @param event [optional]
* @return {Object}
*/
function pipe(event ) {
var api = {}, args = Array.prototype.slice.apply(arguments), eI, aI, pI, connections = [], connection, bindings = [],
binding;
//a batch of events
if(typeof event === 'object' && typeof event.push === 'function' && typeof event[0] === 'string') {
for(eI = 0; eI < event.length; eI += 1) {
bindings.push(pipe.apply(null, [event[eI]].concat(args.slice(1))));
}
api.clear = clearBatch;
return api;
}
//a single emitter (all events)
if(typeof event === 'object') {
event = false;
}
//a specific event
else {
args.shift();
}
//validate event
if(event !== false && typeof event !== 'string') { throw new Error('Cannot create pipe. The first argument must be an event string or an emitter.'); }
for(aI = 0; aI < args.length; aI += 1) {
//if dom node
if(args[aI].addEventListener || args[aI].attachEvent) { args[aI] = EventEmitter(args[aI]); }
//find existing pipe to emitter (if any)
for(pI = 0; pI < pipes.length; pI += 1) {
if(pipes[pI].emitter === args[aI]) {
connection = pipes[pI];
break;
}
}
//if a pipe was found and its type 2 then skip this emitter (its already piped)
if(connection && connection.type === 2) { continue; }
//if no pipe exists then create it for the first time
if(!connection) {
connection = {};
connection.emitter = args[aI];
connection.bindings = [];
connection.events = [];
if(event) {
connection.type = 1;
} else {
connection.type = 2;
}
}
if(connection.events.indexOf(event) !== -1) { continue; }
connection.events.push(event);
if(connection.type === 1) {
binding = captureEvent(args[aI], event);
binding.event = event;
connection.bindings.push(binding);
} else if(connection.type === 2) {
for(event in listeners) {
if(!listeners.hasOwnProperty(event)) { continue; }
binding = args[aI].on(event, captureEvent);
binding.event = event;
connection.bindings.push(binding);
connection.events.push(event);
}
captureListener(connection, args[aI]);
}
connections.push(connection);
pipes.push(connection);
}
api.clear = clear;
return api;
function captureListener(connection, emitter) {
connection.listenerBinding = on('emitter.listener', function(event) {
if(connection.events.indexOf(event) === -1) {
connection.bindings.push(captureEvent(emitter, event));
connection.events.push(event);
}
});
}
function captureEvent(emitter, event) {
return emitter.on(event, function( ) {
var args = Array.prototype.slice.apply(arguments);
args.unshift(event);
return trigger.apply(this, args);
});
}
function clearBatch() {
if(bindings.length) {
while(bindings.length) {
bindings[0].clear();
bindings.splice(0, 1);
}
}
}
function clear() {
while(connections.length) {
if(connections[0].listenerBinding) {
connections[0].listenerBinding.clear();
}
while(connections[0].bindings.length) {
connections[0].bindings[0].clear();
connections[0].bindings.splice(0, 1);
}
pipes.splice(pipes.indexOf(connections[0]), 1);
connections.splice(0, 1);
}
}
}
/**
* Clears pipes based on the events they transport.
* @param event
*/
function clearPipes(event) {
var pI, bI, binding;
for(pI = 0; pI < pipes.length; pI += 1) {
if(event) {
if(pipes[pI].type === 2) { continue; }
if(pipes[pI].events.indexOf(event) === -1) { continue; }
pipes[pI].events.splice(pipes[pI].events.indexOf(event), 1);
}
if(pipes[pI].type === 2) { pipes[pI].listenerBinding.clear(); }
for(bI = 0; bI < pipes[pI].bindings.length; bI += 1) {
if(event && pipes[pI].bindings[bI].event !== event) { continue; }
pipes[pI].bindings[bI].clear();
pipes[pI].bindings.splice(bI, 1);
bI -= 1;
}
if(pipes[pI].bindings.length < 1) {
pipes.splice(pI, 1);
pI -= 1;
}
}
}
/**
* Gets listeners for events.
* @param event
* @return {*}
*/
function getListeners(event) {
if(event) {
return listeners[event];
} else {
return listeners;
}
}
/**
* Clears listeners by events.
* @param event
*/
function clearListeners(event) {
if(event) {
delete listeners[event];
} else {
listeners = {};
}
}
/**
* Clears the emitter
*/
function clear() {
trigger('emitter.clear');
listeners = {};
clearSet();
clearPipes();
delete emitter.on;
delete emitter.once;
delete emitter.trigger;
delete emitter.set;
delete emitter.pipe;
delete emitter.listeners;
delete emitter.clear;
}
/**
* Binds the emitter's event system to the DOM event system
* @param node
*/
function handleNode(node) {
var handledEvents = [], listenerBinding, DOMEventListeners = [];
listenerBinding = on('emitter.listener', function(event) {
if(handledEvents.indexOf(event) > -1) { return; }
handledEvents.push(event);
try {
//W3C
if(node.addEventListener) {
node.addEventListener(event, nodeListener, false);
DOMEventListeners.push({
"event": event,
"listener": nodeListener
});
}
//MICROSOFT
else if(node.attachEvent) {
node.attachEvent('on' + event, nodeListener);
DOMEventListeners.push({
"event": event,
"listener": nodeListener
});
}
} catch(e) {
console.error(e);
}
function nodeListener(eventObj ) {
var args = Array.prototype.slice.apply(arguments);
args.unshift([event, 'dom.' + event]);
if(trigger.apply(this, args) === false) {
eventObj.preventDefault();
eventObj.stopPropagation();
}
}
});
emitter.clearNodeEmitter = clearNodeEmitter;
function clearNodeEmitter() {
var DI;
for(DI = 0; DI < DOMEventListeners.length; DI += 1) {
try {
//W3C
if(node.removeEventListener) {
node.removeEventListener(DOMEventListeners[DI].event, DOMEventListeners[DI].listener, false);
}
//MICROSOFT
else if(node.detachEvent) {
node.detachEvent('on' + DOMEventListeners[DI].event, DOMEventListeners[DI].listener);
}
} catch(e) {
console.error(e);
}
}
handledEvents = [];
listenerBinding.clear();
}
}
}
});
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<head>
<style>
body {
margin : 0;
padding : 0;
background-color: #eee;
font-size: 25px;
}
#content {
background-color: white;
margin: 0 auto;
padding: 20px;
width : 480px;
}
h1 {
font-size: 28px;
}
input {
font-size: 28px;
}
ul {
list-style: none;
padding: 0;
}
</style>
</head>
<body>
<section id="content">
<h1>TODO sample</h1>
<form id="task-add" method="post" action=".">
<input id="new-task-name" type="text" placeholder="input your task">
</form>
<ul id="tasks">
</ul>
</section>
<script src="http://zeptojs.com/zepto.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="./lucid.js"></script>
<script src="./lucidjs_sample.js"></script>
</body>
</html>
(function() {
'use strict';
var Model = {};
var View = {};
Model.Task = function(title) {
LucidJS.emitter(this);
this.title = title;
};
// done プロパティに値が設定されたらイベントを発生させるようにする
Object.defineProperty(Model.Task.prototype, 'done', ( function() {
var done = false;
return {
set : function(value) {
this.trigger('change');
done = value;
},
get : function() {
return done;
}
};
})());
Model.Tasks = function() {
LucidJS.emitter(this);
this.models = [];
};
$.extend(Model.Tasks.prototype, {
add : function(model) {
this.models.unshift(model);
this.pipe(model); // 要素のmodelで発生したイベントはこのオブジェクトでも発生する
this.trigger('add', model);
return model;
},
});
View.TaskList = function(tasks, $el) {
LucidJS.emitter(this);
this.tasks = tasks;
this.$el = $el;
tasks.on('add', _.bind(function(task) {
this.render();
}, this));
tasks.on('change', _.bind(function() {
this.render();
}, this));
};
$.extend(View.TaskList.prototype, {
render : function() {
// {{{ html を再描画する ちょっときたない..
this.$el.empty();
_.each(this.tasks.models, function(task) {
var $item = $('<li></li>');
if (task.done) {
$li.css({ 'color' : '#eee' });
}
$item.append( $('<input></input>').
attr({ 'type':'checkbox' }).
prop('checked', task.done ? 'checked' : '').
on('click', function(e) {
task.done = $(e.target).prop('checked') ? true : false;
})
);
$item.append(task.title);
this.$el.append($item);
}, this);
// }}}
}
});
// モデルとビューの初期化
var tasks = new Model.Tasks();
var taskList = new View.TaskList(tasks, $('#tasks'));
tasks.add(new Model.Task('郵便を出す'));
tasks.add(new Model.Task('テレビを新調する'));
tasks.add(new Model.Task('お金を1000円返す'));
tasks.add(new Model.Task('牛乳を買う'));
// コントローラを設定
var $taskAddForm = $('#task-add');
var $newTaskNameForm = $('#new-task-name');
$newTaskNameForm.focus();
$taskAddForm.on('submit', function(e) {
tasks.add( new Model.Task($newTaskNameForm.val()) );
$newTaskNameForm.val('');
return false;
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment