separate storage in todos example, expose some utils methods

This commit is contained in:
Evan You 2013-08-12 15:04:02 -04:00
parent d0c96c5c25
commit cfc27d89f1
8 changed files with 87 additions and 52 deletions

View File

@ -6,7 +6,7 @@
<link rel="stylesheet" type="text/css" href="common/base.css">
</head>
<body>
<section id="todoapp" sd-controller="Todos">
<section id="todoapp" sd-controller="todos">
<header id="header">
<h1>todos</h1>
<input
@ -38,7 +38,7 @@
sd-checked="todo.completed"
sd-on="change:updateCount"
>
<label sd-text="todo.title" sd-on="dblclick:edit"></label>
<label sd-text="todo.title" sd-on="dblclick:editTodo"></label>
<button class="destroy" sd-on="click:removeTodo"></button>
</div>
<input
@ -71,6 +71,7 @@
<p>Created by <a href="http://evanyou.me">Evan You</a></p>
</footer>
<script src="../../dist/seed.js"></script>
<script src="js/todoStorage.js"></script>
<script src="js/app.js"></script>
</body>
</html>

View File

@ -1,25 +1,7 @@
Seed.controller('Todos', function (scope) {
// data persistence -------------------------------------------------------
var STORAGE_KEY = 'todos-seedjs'
function sync () {
localStorage.setItem(STORAGE_KEY, scope.$serialize('todos'))
}
// filters ----------------------------------------------------------------
var filters = {
all: function () { return true },
active: function (v) { return !v },
completed: function (v) { return v }
}
updateFilter()
window.addEventListener('hashchange', updateFilter)
function updateFilter () {
scope.filter = location.hash ? location.hash.slice(2) : 'all'
}
Seed.controller('todos', function (scope) {
// regular properties -----------------------------------------------------
scope.todos = JSON.parse(localStorage.getItem(STORAGE_KEY)) || []
scope.todos = todoStorage.fetch()
scope.remaining = scope.todos.reduce(function (n, todo) {
return n + (todo.completed ? 0 : 1)
}, 0)
@ -63,23 +45,23 @@ Seed.controller('Todos', function (scope) {
scope.todos.unshift({ title: value, completed: false })
scope.newTodo = ''
scope.remaining++
sync()
todoStorage.save(scope.todos)
}
}
scope.removeTodo = function (e) {
scope.todos.remove(e.scope)
scope.remaining -= e.scope.completed ? 0 : 1
sync()
todoStorage.save(scope.todos)
}
scope.updateCount = function (e) {
scope.remaining += e.scope.completed ? -1 : 1
sync()
todoStorage.save(scope.todos)
}
var beforeEditCache
scope.edit = function (e) {
scope.editTodo = function (e) {
beforeEditCache = e.scope.title
e.scope.editing = true
}
@ -89,7 +71,7 @@ Seed.controller('Todos', function (scope) {
e.scope.editing = false
e.scope.title = e.scope.title.trim()
if (!e.scope.title) scope.removeTodo(e)
sync()
todoStorage.save(scope.todos)
}
scope.cancelEdit = function (e) {
@ -103,11 +85,23 @@ Seed.controller('Todos', function (scope) {
scope.todos = scope.todos.filter(function (todo) {
return !todo.completed
})
sync()
todoStorage.save(scope.todos)
}
// filters ----------------------------------------------------------------
var filters = {
all: function () { return true },
active: function (v) { return !v },
completed: function (v) { return v }
}
function updateFilter () {
scope.filter = location.hash ? location.hash.slice(2) : 'all'
}
updateFilter()
window.addEventListener('hashchange', updateFilter)
})
var s = Date.now()
Seed.bootstrap({ debug: false })
console.log(Date.now() - s)
Seed.bootstrap()

View File

@ -0,0 +1,11 @@
var todoStorage = (function () {
var STORAGE_KEY = 'todos-seedjs'
return {
fetch: function () {
return JSON.parse(localStorage.getItem(this.STORAGE_KEY) || '[]')
},
save: function (todos) {
localStorage.setItem(this.STORAGE_KEY, Seed.utils.serialize(todos))
}
}
}())

View File

@ -2,7 +2,8 @@ var config = require('./config'),
Seed = require('./seed'),
directives = require('./directives'),
filters = require('./filters'),
textParser = require('./text-parser')
textParser = require('./text-parser'),
utils = require('./utils')
var controllers = config.controllers,
datum = config.datum,
@ -10,6 +11,11 @@ var controllers = config.controllers,
reserved = ['datum', 'controllers'],
booted = false
/*
* expose utils
*/
api.utils = utils
/*
* Store a piece of plain data in config.datum
* so it can be consumed by sd-data
@ -45,12 +51,9 @@ api.filter = function (name, fn) {
}
/*
* Bootstrap the whole thing
* by creating a Seed instance for top level nodes
* that has either sd-controller or sd-data
* Set config options
*/
api.bootstrap = function (opts) {
if (booted) return
api.config = function (opts) {
if (opts) {
for (var key in opts) {
if (reserved.indexOf(key) === -1) {
@ -59,16 +62,31 @@ api.bootstrap = function (opts) {
}
}
textParser.buildRegex()
}
/*
* Compile a single element
*/
api.compile = function (el) {
new Seed(el)
}
/*
* Bootstrap the whole thing
* by creating a Seed instance for top level nodes
* that has either sd-controller or sd-data
*/
api.bootstrap = function (opts) {
if (booted) return
api.config(opts)
var el,
ctrlSlt = '[' + config.prefix + '-controller]',
dataSlt = '[' + config.prefix + '-data]',
seeds = []
dataSlt = '[' + config.prefix + '-data]'
/* jshint boss: true */
while (el = document.querySelector(ctrlSlt) || document.querySelector(dataSlt)) {
seeds.push((new Seed(el)).scope)
new Seed(el)
}
booted = true
return seeds.length > 1 ? seeds : seeds[0]
}
module.exports = api

View File

@ -56,7 +56,7 @@ ScopeProto.$unwatch = function (key) {
*/
ScopeProto.$dump = function (key) {
var bindings = this.$seed._bindings
return utils.dumpValue(key ? bindings[key].value : this)
return utils.dump(key ? bindings[key].value : this)
}
/*

View File

@ -64,9 +64,10 @@ function Seed (el, options) {
var ctrlID = el.getAttribute(ctrlAttr)
if (ctrlID) {
el.removeAttribute(ctrlAttr)
var factory = config.controllers[ctrlID]
if (factory) {
factory(this.scope)
var controller = config.controllers[ctrlID]
if (controller) {
this._controller = controller
controller(this.scope)
} else {
config.warn('controller "' + ctrlID + '" is not defined.')
}
@ -86,6 +87,7 @@ function Seed (el, options) {
if (this._computed.length) depsParser.parse(this._computed)
delete this._computed
// extract dependencies for computed properties with dynamic context
if (this._contextBindings.length) this._bindContexts(this._contextBindings)
delete this._contextBindings
}

View File

@ -15,6 +15,7 @@ module.exports = {
* Parse a piece of text, return an array of tokens
*/
parse: function (node) {
if (!BINDING_RE) module.exports.buildRegex()
var text = node.nodeValue
if (!BINDING_RE.test(text)) return null
var m, i, tokens = []

View File

@ -24,21 +24,22 @@ function typeOf (obj) {
/*
* Recursively dump stuff...
*/
function dumpValue (val) {
function dump (val) {
var type = typeOf(val)
if (type === 'Array') {
return val.map(dumpValue)
return val.map(dump)
} else if (type === 'Object') {
if (val.get) { // computed property
return val.get()
} else { // object / child scope
var ret = {}
var ret = {}, prop
for (var key in val) {
if (val.hasOwnProperty(key) &&
typeof val[key] !== 'function' &&
prop = val[key]
if (typeof prop !== 'function' &&
val.hasOwnProperty(key) &&
key.charAt(0) !== '$')
{
ret[key] = dumpValue(val[key])
ret[key] = dump(prop)
}
}
return ret
@ -51,7 +52,14 @@ function dumpValue (val) {
module.exports = {
typeOf: typeOf,
dumpValue: dumpValue,
dump: dump,
/*
* shortcut for JSON.stringify-ing a dumped value
*/
serialize: function (val) {
return JSON.stringify(dump(val))
},
/*
* Get a value from an object based on a path array