Commit 86462783 authored by Olivier Scherrer's avatar Olivier Scherrer Committed by Sindre Sorhus

Close GH-114: Olives + Emily Frameworks proposal.

parent a3c6bafd
# Olives • [TodoMVC](http://todomvc.com)
[Olives](http://flams.github.com/olives/) is a JS framework for creating real-time web applications, in no time.
Check out the [Wiki](https://github.com/flams/olives/wiki) for more information!
#main, #footer, #clear-completed {
display: none;
}
.show {
display: block !important;
}
html,
body {
margin: 0;
padding: 0;
}
body {
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
width: 520px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
}
#main {
display: none;
}
.show {
display: block !important;
}
#todoapp {
background: #fff;
padding: 20px;
margin-bottom: 40px;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-webkit-border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-ms-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#todoapp h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 0 0 10px 0;
}
#todoapp input[type="text"] {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todo-list {
margin: 10px 0;
padding: 0;
list-style: none;
}
#todo-list li {
padding: 18px 20px 18px 0;
position: relative;
font-size: 24px;
border-bottom: 1px solid #cccccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.done label {
color: #777777;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 20px;
right: 10px;
cursor: pointer;
width: 20px;
height: 20px;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUBAMAAAB/pwA+AAAABGdBTUEAALGPC/xhBQAAACdQTFRFzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMAAAA////zMzMhnu0WAAAAAt0Uk5T5u3pqtV3jFQEKAC0bVelAAAAfUlEQVQI12NYtWpFsc8R865VqxhWrZpyBgg8QcylZ8AgCsjMgTCPrWJYfgYKqhjWwJgaDDVnzpw+c2bPmTPHGWzOnNm95/TuM2cOM/AARXfvBooeZAAp270bRCIz4QoOIGtDMqwJZoUEQzvCYrhzuhhWtUKYEahOX7UK6iEA3A6NUGwCTZIAAAAASUVORK5CYII=') no-repeat center center;
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li.editing {
border-bottom: none;
margin-top: -1px;
padding: 0;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#todo-list li.editing .edit {
display: block;
width: 444px;
padding: 13px 15px 14px 20px;
margin: 0;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .view label {
word-break: break-word;
}
#todo-list li .edit {
display: none;
}
#todoapp footer {
display: none;
margin: 0 -20px -20px -20px;
overflow: hidden;
color: #555555;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 37px;
-webkit-border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-ms-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#clear-completed {
display: none;
float: right;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
margin-bottom: 8px;
padding: 0 10px 1px;
cursor: pointer;
-webkit-border-radius: 12px;
-moz-border-radius: 12px;
-ms-border-radius: 12px;
-o-border-radius: 12px;
border-radius: 12px;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
}
#clear-completed:active {
position: relative;
top: 1px;
}
#todo-count span {
font-weight: bold;
}
#instructions {
margin: 10px auto;
color: #777777;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#instructions a {
color: #336699;
}
#credits {
margin: 30px auto;
color: #999;
text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
text-align: center;
}
#credits a {
color: #888;
}
\ No newline at end of file
......@@ -2,56 +2,50 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>Olives + Emily - TodoMVC</title>
<link rel="stylesheet" href="css/base.css" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Olives • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<div id="todoapp">
<header>
<section id="todoapp">
<header id="header">
<h1>Todos</h1>
<input type="text" placeholder="What needs to be done?" data-event="listen:keydown,add">
<input id="new-todo" placeholder="What needs to be done?" autofocus data-event="listen:keydown,addTask">
</header>
<!-- this section is hidden by default and you be shown when there are todos and hidden when not -->
<section id="main" data-stats="bind:toggleClass,nbItems,show">
<input id="toggle-all" type="checkbox" data-stats="bind:checked,allChecked">
<input id="toggle-all" type="checkbox" data-event="listen:click,toggleAll" data-stats="bind:toggleCheck,nbCompleted">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" data-model="foreach">
<li data-model="bind:toggleClass,completed,done; bind:toggleClass,edit,editing">
<div class="view">
<li data-model="bind:toggleClass,completed,completed;">
<div class="view" data-event="listen:dblclick,startEdit">
<input class="toggle" type="checkbox" data-model="bind:checked,completed">
<label data-model="bind:innerHTML,name" data-event="listen:dblclick,edit;" ></label>
<a class="destroy" data-event="listen:click,remove"></a>
<label data-model="bind:innerHTML,title"></label>
<button class="destroy" data-event="listen:click,remove"></button>
</div>
<input class="edit" type="text" data-model="bind:value,name" data-event="listen:keydown,edit; listen:blur,edit" />
<input class="edit" data-model="bind:value,title" data-event="listen:keydown,stopEdit; listen:blur,stopEdit">
</li>
</ul>
</section>
<!-- this footer needs to be shown with JS when there are todos and hidden when not -->
<footer data-stats="bind:toggleClass,nbItems,show">
<span class="todo-count">
<strong data-stats="bind:innerHTML,uncompleted"></strong>
<span data-stats="bind:innerHTML,uncompletedWord"></span> left.
</span>
<span class="todo-clear">
<a href="#" data-event="listen:click,clear;" id="clear-completed" data-stats="bind:toggleClass,completed,show">
Clear <strong data-stats="bind:innerHTML,completed"></strong>
completed <span data-stats="bind:innerHTML,completedWord">item</span></a>
</span>
<footer id="footer" data-stats="bind:toggleClass,nbItems,show">
<span id="todo-count"><strong data-stats="bind:innerHTML,nbLeft">0</strong> <span data-stats="bind:innerHTML,plural"></span> left</span>
<button id="clear-completed" data-event="listen:click,delAll" data-stats="bind:toggleClass,nbCompleted,show">Clear completed (<span data-stats="bind:innerHTML,nbCompleted"></span>)</button>
</footer>
</div>
<div id="instructions">
Double-Click to edit a todo.
</div>
<div id="credits">
<p>Created by <a href="http://github.com/podefr/">Olivier Scherrer</a>.</p>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<p>Created by <a href="http://github.com/podefr">Olivier Scherrer</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</div>
<!-- scripts here -->
<script src="js/libs/require.js"></script>
<script src="js/libs/Emily.js"></script>
<script src="js/libs/Olives.js"></script>
<script src="js/app.js"></script>
</footer>
<script src="../../../assets/base.js"></script>
<script src="js/lib/require.js"></script>
<script src="js/lib/Emily.js"></script>
<script src="js/lib/Olives.js"></script>
<script src="js/lib/Tools.js"></script>
<script src="js/uis/Input.js"></script>
<script src="js/uis/List.js"></script>
<script src="js/uis/Controls.js"></script>
<script src="js/app.js"></script>
</body>
</html>
\ No newline at end of file
// The whole application is described in this module
// But we can imagine defining parts of it separately and creating a bigger UI
// that would include them. We'd have reusable code.
// A bit overkill here
require(["Olives/LocalStore", "Olives/OObject", "Olives/Model-plugin", "Olives/Event-plugin"],
function (Store, OObject, ModelPlugin, EventPlugin) {
// The Todo App constructor
function TodosConstructor() {
// A store to save the statistics
var stats = new Store({
nbItems: 0,
uncompleted: 0,
completed: 0,
completedWord: "item",
uncompletedWord: "item",
allChecked: false
}),
// Used for toggling a class according to a value's truthiness
// It will be added to both ModelPlugins
toggleClass = function (value, className) {
value ? this.classList.add(className) : this.classList.remove(className);
};
// Synchronize the store with localStorage
stats.sync("olives-todos-stats");
stats.watchValue("allChecked", function (checked) {
// I wish I could simply do this.model.alter("map", func) but
// Emily's Store can't notice a change in a property of an item when using
// native functions. (map/filter/foreach...)
// Until I find a solution, I need to explicitly set the new value
this.model.loop(function (value, idx) {
value.completed = checked;
this.model.set(idx, value);
}, this);
}, this);
(function( window ) {
'use strict';
// Add a task
this.add = function (event, node) {
if (event.keyCode == 13) {
this.model.alter("push", {
name: node.value,
completed: false,
edit: false
});
node.value = "";
}
};
// Toggle Edit on click in view mode
this.edit = function (event, node) {
if (event.type == "dblclick" || event.type == "blur" || event.keyCode == 13) {
// Don't know atm if model_id should be public API.
var item = this.model.get(node.dataset["model_id"]);
item.edit = !item.edit;
item.edit && node.focus();
// Thanks to double way binding, this line shouldn't be necessary.
// Though, keydown "enter" is fired before change, so double way binding doesn't work.
// Don't know how to fix this yet.
item.name = node.value || item.name;
this.model.set(node.dataset["model_id"], item);
}
};
// These are the UIs that compose the Todo application
require([ 'Todos/Input', 'Todos/List', 'Todos/Controls', 'Olives/LocalStore', 'Store' ],
// The application
function Todos( Input, List, Controls, LocalStore, Store ) {
// The tasks Store is told to init on an array
// so tasks are indexed by a number
// This store is shared among several UIs of this application
// that's why it's created here
var tasks = new LocalStore([]),
this.remove = function (event, node) {
this.model.del(node.dataset["model_id"]);
};
// Also create a shared stats store
stats = new Store({
nbItems: 0,
nbLeft: 0,
nbCompleted: 0,
plural: 'items'
});
// Clear all completed tasks
this.clear = function () {
this.model.delAll(this.getCompleted());
};
// Updates the statistics store that will also update the view
this.updateStats = function () {
var completed = this.getCompleted().length,
nbItems = this.model.getNbItems(),
uncompleted = nbItems - completed;
stats.set("nbItems", nbItems);
stats.set("completed", completed)
stats.set("completedWord", completed > 1 ? "items" : "item");
stats.set("uncompleted", uncompleted);
stats.set("uncompletedWord", uncompleted > 1 ? "items" : "item");
};
// Synchronize the store on 'todos-olives' localStorage
tasks.sync('todos-olives');
// Returns the completed tasks
this.getCompleted = function () {
var completed = [];
this.model.loop(function (value, idx) {
if (value.completed) {
completed.push(idx);
}
});
return completed;
};
// Initialize Input UI by giving it a view and a model.
Input(document.querySelector('#header input'), tasks );
// Watch for add/del/update to update the statistics
this.model.watch("added", this.updateStats, this);
this.model.watch("deleted", this.updateStats, this);
this.model.watch("updated", this.updateStats, this);
// Init the List UI the same way, pass it the stats store too
List(document.querySelector('#main'), tasks, stats );
// Synchronize the store with localStorage
this.model.sync("olives-todos-tasks");
// Same goes for the control UI
Controls(document.querySelector('#footer'), tasks, stats );
// Add the plugins.
this.plugins.addAll({
// ModelPlugin binds dom nodes with the model. The plumbing.
"stats": new ModelPlugin(stats, {
toggleClass: toggleClass
}),
"model": new ModelPlugin(this.model, {
// It's need here too
toggleClass: toggleClass
}),
"event": new EventPlugin(this)
});
}
// Make the application inherit from OObject
// The OObject brings up a statemachine, a store, and some UI logic
TodosConstructor.prototype = new OObject(new Store([]));
// Apply the plugins to #todoapp
(new TodosConstructor).alive(document.querySelector("#todoapp"));
});
});
\ No newline at end of file
})( window );
\ No newline at end of file
This diff is collapsed.
/*
Olives http://flams.github.com/olives
The MIT License (MIT)
Copyright (c) 2012 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
define("Olives/DomUtils",function(){return{getNodes:function(f,g){return f instanceof HTMLElement?(f.parentNode||document.createDocumentFragment().appendChild(f),f.parentNode.querySelectorAll(g||"*")):false},getDataset:function(f){var g=0,i,k={},d,a;if(f instanceof HTMLElement)if(f.hasOwnProperty("dataset"))return f.dataset;else{for(i=f.attributes.length;g<i;g++)d=f.attributes[g].name.split("-"),d.shift()=="data"&&(k[a=d.join("-")]=f.getAttribute("data-"+a));return k}else return false}}});
define("Olives/Event-plugin",function(){return function(f){this.listen=function(g,i,k,d){g.addEventListener(i,function(a){f[k].call(f,a,g)},d=="true")}}});
define("Olives/LocalStore",["Store","Tools"],function(f,g){function i(){var f=null,d=localStorage,a=function(){d.setItem(f,this.toJSON())};this.setLocalStorage=function(c){return c&&c.setItem instanceof Function?(d=c,true):false};this.getLocalStorage=function(){return d};this.sync=function(c){return typeof c=="string"?(f=c,c=JSON.parse(d.getItem(c)),g.loop(c,function(c,a){this.has(a)||this.set(a,c)},this),a.call(this),this.watch("added",a,this),this.watch("updated",a,this),this.watch("deleted",a,
this),true):false}}return function(g){i.prototype=new f(g);return new i}});
define("Olives/Model-plugin",["Store","Observable","Tools","Olives/DomUtils"],function(f,g,i,k){return function(d,a){var c=null,e={},j={};this.observers={};this.setModel=function(l){return l instanceof f?(c=l,true):false};this.getModel=function(){return c};this.ItemRenderer=function(l,a){var b=null,e=null,d=null,j=null,g=null;this.setRenderer=function(a){b=a;return true};this.getRenderer=function(){return b};this.setRootNode=function(b){return b instanceof HTMLElement?(d=b,b=d.querySelector("*"),
this.setRenderer(b),b&&d.removeChild(b),true):false};this.getRootNode=function(){return d};this.setPlugins=function(b){e=b;return true};this.getPlugins=function(){return e};this.items=new f([]);this.setStart=function(b){return j=parseInt(b,10)};this.getStart=function(){return j};this.setNb=function(b){return g=b=="*"?b:parseInt(b,10)};this.getNb=function(){return g};this.addItem=function(b){var a;return typeof b=="number"&&!this.items.get(b)?(a=this.create(b))?((b=this.getNextItem(b))?d.insertBefore(a,
b):d.appendChild(a),true):false:false};this.getNextItem=function(b){return this.items.alter("slice",b+1).filter(function(b){if(b instanceof HTMLElement)return true})[0]};this.removeItem=function(b){var a=this.items.get(b);return a?(d.removeChild(a),this.items.set(b),true):false};this.create=function(a){if(c.has(a)){var l=b.cloneNode(true),h=k.getNodes(l);i.toArray(h).forEach(function(b){b.setAttribute("data-"+e.name+"_id",a)});this.items.set(a,l);e.apply(l);return l}};this.render=function(){var b=
g=="*"?c.getNbItems():g,a=[];if(g!==null&&j!==null){this.items.loop(function(l,h){(h<j||h>=j+b||!c.has(h))&&a.push(h)},this);a.sort(i.compareNumbers).reverse().forEach(this.removeItem,this);for(var l=j,h=b+j;l<h;l++)this.addItem(l);return true}else return false};this.setPlugins(l);this.setRootNode(a)};this.setItemRenderer=function(a,h){j[a||"default"]=h};this.getItemRenderer=function(a){return j[a]};this.foreach=function(a,h,b,d){var e=new this.ItemRenderer(this.plugins,a);e.setStart(b||0);e.setNb(d||
"*");e.render();c.watch("added",e.render,e);c.watch("deleted",function(b){e.render();this.observers[b]&&this.observers[b].forEach(function(b){c.unwatchValue(b)},this);delete this.observers[b]},this);this.setItemRenderer(h,e)};this.updateStart=function(a,h){var b=this.getItemRenderer(a);return b?(b.setStart(h),true):false};this.updateNb=function(a,h){var b=this.getItemRenderer(a);return b?(b.setNb(h),true):false};this.refresh=function(a){return(a=this.getItemRenderer(a))?(a.render(),true):false};this.bind=
function(a,h,b){var b=b||"",e=a.getAttribute("data-"+this.plugins.name+"_id"),d=b.split("."),j=e||d.shift(),f=e?b:d.join("."),e=i.getNestedProperty(c.get(j),f),g=i.toArray(arguments).slice(3);if(e||e===0||e===false)this.execBinding.apply(this,[a,h,e].concat(g))||(a[h]=e);this.hasBinding(h)||a.addEventListener("change",function(){c.has(j)&&(f?c.update(j,b,a[h]):c.set(j,a[h]))},true);this.observers[j]=this.observers[j]||[];this.observers[j].push(c.watchValue(j,function(b){this.execBinding.apply(this,
[a,h,i.getNestedProperty(b,f)].concat(g))||(a[h]=i.getNestedProperty(b,f))},this))};this.set=function(a){return a instanceof HTMLElement&&a.name?(c.set(a.name,a.value),true):false};this.form=function h(h){if(h&&h.nodeName=="FORM"){var b=this;h.addEventListener("submit",function(a){i.toArray(h.querySelectorAll("[name]")).forEach(b.set,b);a.preventDefault()},true);return true}else return false};this.addBinding=function(a,b){return a&&typeof a=="string"&&typeof b=="function"?(e[a]=b,true):false};this.execBinding=
function(a,b){return this.hasBinding(b)?(e[b].apply(a,Array.prototype.slice.call(arguments,2)),true):false};this.hasBinding=function(a){return e.hasOwnProperty(a)};this.getBinding=function(a){return e[a]};this.addBindings=function(a){return i.loop(a,function(a,e){this.addBinding(e,a)},this)};this.setModel(d);this.addBindings(a)}});
define("Olives/OObject",["StateMachine","Store","Olives/Plugins","Olives/DomUtils","Tools"],function(f,g,i,k,d){return function(a){var c=function(a){var b=j||document.createElement("div");if(a.template){typeof a.template=="string"?b.innerHTML=a.template.trim():a.template instanceof HTMLElement&&b.appendChild(a.template);if(b.childNodes.length>1)throw Error("UI.template should have only one parent node");else a.dom=b.childNodes[0];a.plugins.apply(a.dom)}else throw Error("UI.template must be set prior to render");
},e=function b(a,b,e){b&&(e?b.insertBefore(a.dom,e):b.appendChild(a.dom),j=b)},j=null,l=new f("Init",{Init:[["render",c,this,"Rendered"],["place",function(a,j){c(a);e.apply(null,d.toArray(arguments))},this,"Rendered"]],Rendered:[["place",e,this],["render",c,this]]});this.model=a instanceof g?a:new g;this.plugins=new i;this.dom=this.template=null;this.place=function(a,e){l.event("place",this,a,e)};this.render=function(){l.event("render",this)};this.setTemplateFromDom=function(a){return a instanceof
HTMLElement?(this.template=a,true):false};this.alive=function(a){return a instanceof HTMLElement?(this.setTemplateFromDom(a),this.place(a.parentNode,a.nextElementSibling),true):false}}});
define("Olives/Plugins",["Tools","Olives/DomUtils"],function(f,g){return function(){var i={},k=function(a){return a.trim()},d=function(a,c,e){c.split(";").forEach(function(c){var d=c.split(":"),c=d[0].trim(),d=d[1]?d[1].split(",").map(k):[];d.unshift(a);i[e]&&i[e][c]&&i[e][c].apply(i[e],d)})};this.add=function(a,c){var e=this;return typeof a=="string"&&typeof c=="object"&&c?(i[a]=c,c.plugins={name:a,apply:function(){return e.apply.apply(e,arguments)}},true):false};this.addAll=function(a){return f.loop(a,
function(a,e){this.add(e,a)},this)};this.get=function(a){return i[a]};this.del=function(a){return delete i[a]};this.apply=function(a){var c;return a instanceof HTMLElement?(c=g.getNodes(a),f.loop(f.toArray(c),function(a){f.loop(g.getDataset(a),function(c,f){d(a,c,f)})}),a):false}}});
define("Olives/Transport",["Observable","Tools"],function(f,g){return function(i,k){var d=null,a=null,c=new f;this.setIO=function(e){return e&&typeof e.connect=="function"?(a=e,true):false};this.getIO=function(){return a};this.connect=function(e){return typeof e=="string"?(d=a.connect(e),true):false};this.getSocket=function(){return d};this.on=function(a,c){d.on(a,c)};this.once=function(a,c){d.once(a,c)};this.emit=function(a,c,f){d.emit(a,c,f)};this.request=function(a,c,f,h){var b=Date.now()+Math.floor(Math.random()*
1E6),g=function(){f&&f.apply(h||null,arguments)};d[c.keptAlive?"on":"once"](b,g);c.__eventId__=b;d.emit(a,c);if(c.keptAlive)return function(){d.emit("disconnect-"+b);d.removeListener(b,g)}};this.listen=function(a,d,f,h){var b=a+"/"+d.path,i,k;c.hasTopic(b)||(g.mixin({method:"GET",keptAlive:true},d),k=this.request(a,d,function(a){c.notify(b,a)},this));i=c.watch(b,f,h);return function(){c.unwatch(i);c.hasTopic(b)||k()}};this.getListenObservable=function(){return c};this.setIO(i);this.connect(k)}});
define("Olives/UI-plugin",["Olives/OObject","Tools"],function(f,g){return function(i){var k={};this.place=function(d,a){if(k[a]instanceof f)k[a].place(d);else throw Error(a+" is not an OObject UI in place:"+a);};this.set=function(d,a){return typeof d=="string"&&a instanceof f?(k[d]=a,true):false};this.setAll=function(d){g.loop(d,function(a,c){this.set(c,a)},this)};this.get=function(d){return k[d]};this.setAll(i)}});
/*
* A set of commonly used functions.
* They're useful for several UIs in the app.
* They could also be reused in other projects
*/
define( 'Todos/Tools', {
// className is set to the 'this' dom node according to the value's truthiness
'toggleClass': function ( value, className ) {
value ? this.classList.add( className ) : this.classList.remove( className );
}
});
\ No newline at end of file
This diff is collapsed.
/*
Olives
The MIT License (MIT)
Copyright(c) 2012 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com>
*/
define("Olives/DomUtils",["Tools"],function(){return{getNodes:function(f,i){return f instanceof HTMLElement?(f.parentNode||document.createDocumentFragment().appendChild(f),f.parentNode.querySelectorAll(i||"*")):false}}});define("Olives/Event-plugin",function(){return function(f){this.listen=function(i,g,j,c){i.addEventListener(g,function(a){f[j].call(f,a,i)},c=="true")}}});
define("Olives/LocalStore",["Store","Tools"],function(f,i){function g(){var g=null,c=function(){localStorage.setItem(g,this.toJSON())};this.sync=function(a){return typeof a=="string"?(g=a,a=JSON.parse(localStorage.getItem(a)),i.loop(a,function(a,d){this.has(d)||this.set(d,a)},this),c.call(this),true):false};this.watch("added",c,this);this.watch("updated",c,this);this.watch("deleted",c,this)}return function(i){g.prototype=new f(i);return new g}});
define("Olives/Model-plugin",["Store","Observable","Tools","Olives/DomUtils"],function(f,i,g,j){return function(c,a){var e=null,d={},h={};this.observers={};this.setModel=function(k){return k instanceof f?(e=k,true):false};this.getModel=function(){return e};this.ItemRenderer=function(k,l){var b=null,a=null,d=null,c=null,h=null;this.setRenderer=function(a){b=a;return true};this.getRenderer=function(){return b};this.setRootNode=function(b){return b instanceof HTMLElement?(d=b,b=d.querySelector("*"),
this.setRenderer(b),b&&d.removeChild(b),true):false};this.getRootNode=function(){return d};this.setPlugins=function(b){a=b;return true};this.getPlugins=function(){return a};this.items=new f([]);this.setStart=function(b){return c=parseInt(b,10)};this.getStart=function(){return c};this.setNb=function(b){return h=b=="*"?b:parseInt(b,10)};this.getNb=function(){return h};this.addItem=function(b){var a;return typeof b=="number"&&!this.items.get(b)?(a=this.create(b))?(d.insertBefore(a,this.getNextItem(b)),
true):false:false};this.getNextItem=function(b){return this.items.alter("slice",b+1).filter(function(b){if(b instanceof HTMLElement)return true})[0]};this.removeItem=function(b){var a=this.items.get(b);return a?(d.removeChild(a),this.items.set(b),true):false};this.create=function(k){if(e.has(k)){var d=b.cloneNode(true),l=j.getNodes(d);g.toArray(l).forEach(function(b){b.dataset[a.name+"_id"]=k});this.items.set(k,d);a.apply(d);return d}};this.render=function(){var b=h=="*"?e.getNbItems():h,a=[];if(h!==
null&&c!==null){this.items.loop(function(k,d){(d<c||d>=c+b||!e.has(d))&&a.push(d)},this);a.sort(g.compareNumbers).reverse().forEach(this.removeItem,this);for(var k=c,d=b+c;k<d;k++)this.addItem(k);return true}else return false};this.setPlugins(k);this.setRootNode(l)};this.setItemRenderer=function(a,d){h[a||"default"]=d};this.getItemRenderer=function(a){return h[a]};this.foreach=function(a,d,b,c){var h=new this.ItemRenderer(this.plugins,a);h.setStart(b||0);h.setNb(c||"*");h.render();e.watch("added",
h.render,h);e.watch("deleted",function(b){h.render();this.observers[b]&&this.observers[b].forEach(function(b){e.unwatchValue(b)},this);delete this.observers[b]},this);this.setItemRenderer(d,h)};this.updateStart=function(a,d){var b=this.getItemRenderer(a);return b?(b.setStart(d),true):false};this.updateNb=function(a,d){var b=this.getItemRenderer(a);return b?(b.setNb(d),true):false};this.refresh=function(a){return(a=this.getItemRenderer(a))?(a.render(),true):false};this.bind=function(a,d,b){var b=b||
"",c=a.dataset[this.plugins.name+"_id"],h=b.split("."),f=c||h.shift(),i=c?b:h.join("."),c=g.getNestedProperty(e.get(f),i),j=g.toArray(arguments).slice(3);if(c||c===0||c===false)this.execBinding.apply(this,[a,d,c].concat(j))||(a[d]=c);this.hasBinding(d)||a.addEventListener("change",function(){if(i){var c=e.get(f);g.setNestedProperty(c,b,a[d]);e.set(f,c)}else e.set(f,a[d])},true);this.observers[f]=this.observers[f]||[];this.observers[f].push(e.watchValue(f,function(b){this.execBinding.apply(this,[a,
d,g.getNestedProperty(b,i)].concat(j))||(a[d]=g.getNestedProperty(b,i))},this))};this.set=function(a){return a instanceof HTMLElement&&a.name?(e.set(a.name,a.value),true):false};this.form=function l(l){if(l&&l.nodeName=="FORM"){var b=this;l.addEventListener("submit",function(a){g.toArray(l.querySelectorAll("[name]")).forEach(b.set,b);a.preventDefault()},true);return true}else return false};this.addBinding=function(a,b){return a&&typeof a=="string"&&typeof b=="function"?(d[a]=b,true):false};this.execBinding=
function(a,b){return this.hasBinding(b)?(d[b].apply(a,Array.prototype.slice.call(arguments,2)),true):false};this.hasBinding=function(a){return d.hasOwnProperty(a)};this.getBinding=function(a){return d[a]};this.addBindings=function(a){return g.loop(a,function(a,d){this.addBinding(d,a)},this)};this.setModel(c);this.addBindings(a)}});
define("Olives/OObject",["StateMachine","Store","Olives/Plugins","Olives/DomUtils","Tools"],function(f,i,g,j,c){return function(a){var e=function(a){var b=h||document.createElement("div");if(a.template){typeof a.template=="string"?b.innerHTML=a.template.trim():a.template instanceof HTMLElement&&b.appendChild(a.template);if(b.childNodes.length>1)throw Error("UI.template should have only one parent node");else a.dom=b.childNodes[0];a.plugins.apply(a.dom)}else throw Error("UI.template must be set prior to render");
},d=function b(a,b,d){b&&(b.insertBefore(a.dom,d),h=b)},h=null,k=new f("Init",{Init:[["render",e,this,"Rendered"],["place",function(a,h){e(a);d.apply(null,c.toArray(arguments))},this,"Rendered"]],Rendered:[["place",d,this],["render",e,this]]});this.model=a instanceof i?a:new i;this.plugins=new g;this.dom=this.template=null;this.place=function(a,d){k.event("place",this,a,d)};this.render=function(){k.event("render",this)};this.setTemplateFromDom=function(a){return a instanceof HTMLElement?(this.template=
a,true):false};this.alive=function(a){return a instanceof HTMLElement?(this.setTemplateFromDom(a),this.place(a.parentNode,a.nextElementSibling),true):false}}});
define("Olives/Plugins",["Tools","Olives/DomUtils"],function(f,i){return function(){var g={},j=function(a){return a.trim()},c=function(a,c,d){c.split(";").forEach(function(c){var e=c.split(":"),c=e[0].trim(),e=e[1]?e[1].split(",").map(j):[];e.unshift(a);g[d]&&g[d][c]&&g[d][c].apply(g[d],e)})};this.add=function(a,c){var d=this;return typeof a=="string"&&typeof c=="object"&&c?(g[a]=c,c.plugins={name:a,apply:function(){return d.apply.apply(d,arguments)}},true):false};this.addAll=function(a){return f.loop(a,
function(a,d){this.add(d,a)},this)};this.get=function(a){return g[a]};this.del=function(a){return delete g[a]};this.apply=function(a){var e;return a instanceof HTMLElement?(e=i.getNodes(a),f.loop(f.toArray(e),function(a){f.loop(a.dataset,function(h,e){c(a,h,e)})}),a):false}}});
define("Olives/Transport",["Observable","Tools"],function(f,i){return function(g,j){var c=null,a=null,e=new f;this.setIO=function(d){return d&&typeof d.connect=="function"?(a=d,true):false};this.getIO=function(){return a};this.connect=function(d){return typeof d=="string"?(c=a.connect(d),true):false};this.getSocket=function(){return c};this.on=function(a,h){c.on(a,h)};this.once=function(a,h){c.once(a,h)};this.emit=function(a,h,e){c.emit(a,h,e)};this.request=function(a,e,f,g){var b=Date.now()+Math.floor(Math.random()*
1E6),i=function(){f&&f.apply(g||null,arguments)};c[e.keptAlive?"on":"once"](b,i);e.__eventId__=b;c.emit(a,e);if(e.keptAlive)return function(){c.emit("disconnect-"+b);c.removeListener(b,i)}};this.listen=function(a,c,f,g){var b=a+"/"+c.path,j,m;e.hasTopic(b)||(i.mixin({method:"GET",keptAlive:true},c),m=this.request(a,c,function(a){e.notify(b,a)},this));j=e.watch(b,f,g);return function(){e.unwatch(j);e.hasTopic(b)||m()}};this.getListenObservable=function(){return e};this.setIO(g);this.connect(j)}});
define("Olives/UI-plugin",["Olives/OObject","Tools"],function(f,i){return function(g){var j={};this.place=function(c,a){if(j[a]instanceof f)j[a].place(c);else throw Error(a+" is not an OObject UI in place:"+a);};this.set=function(c,a){return typeof c=="string"&&a instanceof f?(j[c]=a,true):false};this.setAll=function(c){i.loop(c,function(a,c){this.set(c,a)},this)};this.get=function(c){return j[c]};this.setAll(g)}});
define( 'Todos/Controls',
[ 'Olives/OObject', 'Olives/Event-plugin', 'Olives/Model-plugin', 'Olives/LocalStore', 'Todos/Tools' ],
// The Controls UI
function Controls( OObject, EventPlugin, ModelPlugin, Store, Tools ) {
return function ControlsInit( view, model, stats ) {
// The OObject (the controller) inits with a default model which is a simple store
// But it can be init'ed with any other store, like the LocalStore
var controls = new OObject(model),
// A function to get the completed tasks
getCompleted = function () {
var completed = [];
model.loop(function ( value, id ) {
if ( value.completed ) {
completed.push(id);
}
});
return completed;
},
// Update all stats
updateStats = function () {
var nbCompleted = getCompleted().length;
stats.set( 'nbItems', model.getNbItems() );
stats.set( 'nbLeft', stats.get('nbItems') - nbCompleted );
stats.set( 'nbCompleted', nbCompleted );
stats.set( 'plural', stats.get('nbLeft') === 1 ? 'item' : 'items' );
};
// Add plugins to the UI.
controls.plugins.addAll({
'event': new EventPlugin( controls ),
'stats': new ModelPlugin( stats, {
'toggleClass': Tools.toggleClass
})
});
// Alive applies the plugins to the HTML view
controls.alive( view );
// Delete all tasks
controls.delAll = function () {
model.delAll( getCompleted() );
};
// Update stats when the tasks list is modified
model.watch( 'added', updateStats );
model.watch( 'deleted', updateStats );
model.watch( 'updated', updateStats );
// I could either update stats at init or save them in a localStore
updateStats();
};
});
\ No newline at end of file
// It's going to be called Input
define( 'Todos/Input',
// It uses the Olives' OObject and the Event Plugin to listen to dom events and connect them to methods
[ 'Olives/OObject', 'Olives/Event-plugin' ],
// The Input UI
function Input( OObject, EventPlugin ) {
// It returns an init function
return function InputInit( view, model ) {
// The OObject (the controller) inits with a default model which is a simple store
// But it can be init'ed with any other store, like the LocalStore
var input = new OObject( model ),
ENTER_KEY = 13;
// The event plugin that is added to the OObject
// We have to tell it where to find the methods
input.plugins.add( 'event', new EventPlugin(input) );
// The method to add a new taks
input.addTask = function addTask( event, node ) {
if ( event.keyCode === ENTER_KEY && node.value.trim() ) {
model.alter( 'push', {
title: node.value.trim(),
completed: false
});
node.value = '';
}
};
// Alive applies the plugins to the HTML view
input.alive( view );
};
});
\ No newline at end of file
define( 'Todos/List',
[ 'Olives/OObject', 'Olives/Event-plugin', 'Olives/Model-plugin', 'Todos/Tools' ],
// The List UI
function List( OObject, EventPlugin, ModelPlugin, Tools ) {
return function ListInit( view, model, stats ) {
// The OObject (the controller) inits with a default model which is a simple store
// But it can be init'ed with any other store, like the LocalStore
var list = new OObject( model ),
ENTER_KEY = 13;
// The plugins
list.plugins.addAll({
'event': new EventPlugin( list ),
'model': new ModelPlugin( model, {
'toggleClass': Tools.toggleClass
}),
'stats': new ModelPlugin( stats, {
'toggleClass': Tools.toggleClass,
'toggleCheck': function ( value ) {
this.checked = model.getNbItems() === value ? 'on' : '';
}
})
});
// Remove the completed task
list.remove = function remove( event, node ) {
model.del( node.getAttribute('data-model_id') );
};
// Un/check all tasks
list.toggleAll = function toggleAll( event, node ) {
var checked = !!node.checked;
model.loop( function ( value, idx ) {
this.update( idx, 'completed', checked );
}, model);
};
// Enter edit mode
list.startEdit = function ( event, node ) {
var taskId = node.getAttribute('data-model_id');
Tools.toggleClass.call( view.querySelector('li[data-model_id="' + taskId + '"]'), true, 'editing' );
view.querySelector('input.edit[data-model_id="' + taskId + '"]').select();
};
// Leave edit mode
list.stopEdit = function ( event, node ) {
var taskId = node.getAttribute('data-model_id'),
value;
if ( event.keyCode === ENTER_KEY ) {
value = node.value.trim();
if ( value ) {
model.update( taskId, 'title', value );
} else {
model.del( taskId );
}
// When task #n is removed, #n+1 becomes #n, the dom node is updated to the new value, so editing mode should exit anyway
if ( model.has( taskId ) ) {
Tools.toggleClass.call( view.querySelector('li[data-model_id="' + taskId + '"]'), false, 'editing' );
}
} else if ( event.type === 'blur' ) {
Tools.toggleClass.call( view.querySelector('li[data-model_id="' + taskId + '"]'), false, 'editing' );
}
};
// Alive applies the plugins to the HTML view
list.alive( view );
};
});
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment