Skip to content

Commit a8e12f8

Browse files
committed
Firebase example + bug fixes
- move ensurePath() to Observer.js - add Observer.ensurePaths(), which deals with the situation when a scope object with nested values is given a new value with incomplete nested structure. e.g. this.val = {} - fix Directive argument regex - sd-model: no longer locks during set() so filters work on input fields - sd-repeat: handles undefined update, also fix transition - utils.attr now takes additional `noRemove` argument - ViewModel.$emit now also triggers event on itself
1 parent 88ebff6 commit a8e12f8

File tree

14 files changed

+223
-40
lines changed

14 files changed

+223
-40
lines changed

examples/firebase/app.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
var baseURL = 'https://seed-demo.firebaseIO.com/',
2+
Users = new Firebase(baseURL + 'users')
3+
4+
Users.on('child_added', function (snapshot) {
5+
var item = snapshot.val()
6+
item.id = snapshot.name()
7+
app.users.push(item)
8+
})
9+
10+
Users.on('child_removed', function (snapshot) {
11+
var id = snapshot.name()
12+
app.users.some(function (user) {
13+
if (user.id === id) {
14+
app.users.remove(user)
15+
return true
16+
}
17+
})
18+
})
19+
20+
var app = new Seed({
21+
el: '#app',
22+
filters: validators,
23+
scope: {
24+
users: [],
25+
newUser: {
26+
name: '',
27+
email: ''
28+
},
29+
validation: {
30+
name: false,
31+
email: false
32+
},
33+
isValid: {
34+
$get: function () {
35+
var valid = true
36+
for (var key in this.validation) {
37+
if (!this.validation[key]) {
38+
valid = false
39+
}
40+
}
41+
return valid
42+
}
43+
},
44+
addUser: function (e) {
45+
e.preventDefault()
46+
if (this.isValid) {
47+
Users.push(this.newUser)
48+
this.newUser = {}
49+
}
50+
},
51+
removeUser: function (e) {
52+
new Firebase(baseURL + 'users/' + e.item.id).remove()
53+
}
54+
}
55+
})

examples/firebase/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title></title>
5+
<meta charset="utf-8">
6+
<link rel="stylesheet" type="text/css" href="style.css">
7+
<script src='https://cdn.firebase.com/v0/firebase.js'></script>
8+
<script src="../../dist/seed.js"></script>
9+
</head>
10+
<body>
11+
<div id="app">
12+
<ul>
13+
<li class="user" sd-repeat="user:users" sd-transition>
14+
<span>{{user.name}} - {{user.email}}</span>
15+
<button sd-on="click:removeUser">X</button>
16+
</li>
17+
</ul>
18+
<form id="form" sd-on="submit:addUser">
19+
<input sd-model="newUser.name | nameValidator | capitalize">
20+
<input sd-model="newUser.email | emailValidator">
21+
<input type="submit" value="Add User">
22+
</form>
23+
<ul class="errors">
24+
<li sd-show="!validation.name">Name cannot be empty.</li>
25+
<li sd-show="!validation.email">Please provide a valid email address.</li>
26+
</ul>
27+
</div>
28+
<script src="validators.js"></script>
29+
<script src="app.js"></script>
30+
</body>
31+
</html>

examples/firebase/style.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
ul {
2+
padding: 0;
3+
}
4+
.user {
5+
height: 30px;
6+
line-height: 30px;
7+
padding: 10px;
8+
border-top: 1px solid #eee;
9+
overflow: hidden;
10+
transition: all .25s ease;
11+
}
12+
.user:last-child {
13+
border-bottom: 1px solid #eee;
14+
}
15+
.sd-enter, .sd-leave {
16+
height: 0;
17+
padding-top: 0;
18+
padding-bottom: 0;
19+
border-top-width: 0;
20+
border-bottom-width: 0;
21+
}
22+
.errors {
23+
color: #f00;
24+
}

examples/firebase/validators.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var validators = (function () {
2+
var emailRE = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
3+
return {
4+
nameValidator: function (val) {
5+
this.validation.name = !!val
6+
return val
7+
},
8+
emailValidator: function (val) {
9+
this.validation.email = emailRE.test(val)
10+
return val
11+
}
12+
}
13+
})()

src/compiler.js

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,9 @@ CompilerProto.setupElement = function (options) {
161161
*/
162162
CompilerProto.setupObserver = function () {
163163

164-
var bindings = this.bindings,
165-
observer = this.observer = new Emitter(),
164+
var compiler = this,
165+
bindings = compiler.bindings,
166+
observer = compiler.observer = new Emitter(),
166167
depsOb = DepsParser.observer
167168

168169
// a hash to hold event proxies for each root level key
@@ -172,18 +173,27 @@ CompilerProto.setupObserver = function () {
172173
// add own listeners which trigger binding updates
173174
observer
174175
.on('get', function (key) {
175-
if (bindings[key] && depsOb.isObserving) {
176+
check(key)
177+
if (depsOb.isObserving) {
176178
depsOb.emit('get', bindings[key])
177179
}
178180
})
179181
.on('set', function (key, val) {
180182
observer.emit('change:' + key, val)
181-
if (bindings[key]) bindings[key].update(val)
183+
check(key)
184+
bindings[key].update(val)
182185
})
183186
.on('mutate', function (key, val, mutation) {
184187
observer.emit('change:' + key, val, mutation)
185-
if (bindings[key]) bindings[key].pub()
188+
check(key)
189+
bindings[key].pub()
186190
})
191+
192+
function check (key) {
193+
if (!bindings[key]) {
194+
compiler.createBinding(key)
195+
}
196+
}
187197
}
188198

189199
/**
@@ -438,7 +448,7 @@ CompilerProto.createBinding = function (key, isExp, isFn) {
438448
bindings[key] = binding
439449
// make sure the key exists in the object so it can be observed
440450
// by the Observer!
441-
compiler.ensurePath(key)
451+
Observer.ensurePath(compiler.vm, key)
442452
if (binding.root) {
443453
// this is a root level binding. we need to define getter/setters for it.
444454
compiler.define(key, binding)
@@ -454,25 +464,6 @@ CompilerProto.createBinding = function (key, isExp, isFn) {
454464
return binding
455465
}
456466

457-
/**
458-
* Sometimes when a binding is found in the template, the value might
459-
* have not been set on the VM yet. To ensure computed properties and
460-
* dependency extraction can work, we have to create a dummy value for
461-
* any given path.
462-
*/
463-
CompilerProto.ensurePath = function (key) {
464-
var path = key.split('.'), sec, obj = this.vm
465-
for (var i = 0, d = path.length - 1; i < d; i++) {
466-
sec = path[i]
467-
if (!obj[sec]) obj[sec] = {}
468-
obj = obj[sec]
469-
}
470-
if (utils.typeOf(obj) === 'Object') {
471-
sec = path[i]
472-
if (!(sec in obj)) obj[sec] = undefined
473-
}
474-
}
475-
476467
/**
477468
* Defines the getter/setter for a root-level binding on the VM
478469
* and observe the initial value
@@ -523,6 +514,7 @@ CompilerProto.define = function (key, binding) {
523514
// set new value
524515
binding.value = newVal
525516
ob.emit('set', key, newVal)
517+
Observer.ensurePaths(key, newVal, compiler.bindings)
526518
// now watch the new value, which in turn emits 'set'
527519
// for all its nested values
528520
Observer.observe(newVal, key, ob)

src/directive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var config = require('./config'),
77
// regex to split multiple directive expressions
88
SPLIT_RE = /(?:['"](?:\\.|[^'"])*['"]|\\.|[^,])+/g,
99
KEY_RE = /^[^\|]+/,
10-
ARG_RE = /([^:]+):(.+)$/,
10+
ARG_RE = /^([\w- ]+):(.+)$/,
1111
FILTERS_RE = /\|[^\|]+/g,
1212
FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
1313
NESTING_RE = /^\^+/,

src/directives/model.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ module.exports = {
2727

2828
// attach listener
2929
self.set = function () {
30-
self.lock = true
3130
self.vm.$set(self.key, el[attr])
32-
self.lock = false
3331
}
3432
el.addEventListener(self.event, self.set)
3533

@@ -56,7 +54,6 @@ module.exports = {
5654
/* jshint eqeqeq: false */
5755
var self = this,
5856
el = self.el
59-
if (self.lock) return
6057
if (el.tagName === 'SELECT') { // select dropdown
6158
// setting <select>'s value in IE9 doesn't work
6259
var o = el.options,

src/directives/repeat.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ module.exports = {
103103
ctn.insertBefore(self.ref, el)
104104
ctn.removeChild(el)
105105

106+
self.initiated = false
106107
self.collection = null
107108
self.vms = null
108109
self.mutationListener = function (path, arr, mutation) {
@@ -125,10 +126,11 @@ module.exports = {
125126
// if initiating with an empty collection, we need to
126127
// force a compile so that we get all the bindings for
127128
// dependency extraction.
128-
if (!this.collection && !collection.length) {
129+
if (!this.initiated && (!collection || !collection.length)) {
129130
this.buildItem()
131+
this.initiated = true
130132
}
131-
this.collection = collection
133+
this.collection = collection || []
132134
this.vms = []
133135

134136
// listen for collection mutation events
@@ -137,11 +139,13 @@ module.exports = {
137139
collection.__observer__.on('mutate', this.mutationListener)
138140

139141
// create child-seeds and append to DOM
140-
this.detach()
141-
for (var i = 0, l = collection.length; i < l; i++) {
142-
this.buildItem(collection[i], i)
142+
if (collection.length) {
143+
this.detach()
144+
for (var i = 0, l = collection.length; i < l; i++) {
145+
this.buildItem(collection[i], i)
146+
}
147+
this.retach()
143148
}
144-
this.retach()
145149
},
146150

147151
/**
@@ -164,6 +168,8 @@ module.exports = {
164168
: this.ref
165169
// make sure it works with sd-if
166170
if (!ref.parentNode) ref = ref.sd_ref
171+
// process transition info before appending
172+
node.sd_trans = utils.attr(node, 'transition', true)
167173
transition(node, 1, function () {
168174
ctn.insertBefore(node, ref)
169175
}, this.compiler)

src/observer.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ function bind (obj, key, path, observer) {
121121
},
122122
set: function (newVal) {
123123
values[fullKey] = newVal
124+
ensurePaths(key, newVal, values)
124125
observer.emit('set', fullKey, newVal)
125126
watch(newVal, fullKey, observer)
126127
}
@@ -154,10 +155,45 @@ function emitSet (obj, observer, set) {
154155
}
155156
}
156157

158+
/**
159+
* Sometimes when a binding is found in the template, the value might
160+
* have not been set on the VM yet. To ensure computed properties and
161+
* dependency extraction can work, we have to create a dummy value for
162+
* any given path.
163+
*/
164+
function ensurePaths (key, val, paths) {
165+
key += '.'
166+
for (var path in paths) {
167+
if (!path.indexOf(key)) {
168+
ensurePath(val, path.replace(key, ''))
169+
}
170+
}
171+
}
172+
173+
/**
174+
* walk along a path and make sure it can be accessed
175+
* and enumerated in that object
176+
*/
177+
function ensurePath (obj, key) {
178+
if (typeOf(obj) !== 'Object') return
179+
var path = key.split('.'), sec
180+
for (var i = 0, d = path.length - 1; i < d; i++) {
181+
sec = path[i]
182+
if (!obj[sec]) obj[sec] = {}
183+
obj = obj[sec]
184+
}
185+
if (typeOf(obj) === 'Object') {
186+
sec = path[i]
187+
if (!(sec in obj)) obj[sec] = undefined
188+
}
189+
}
190+
157191
module.exports = {
158192

159193
// used in sd-repeat
160194
watchArray: watchArray,
195+
ensurePath: ensurePath,
196+
ensurePaths: ensurePaths,
161197

162198
/**
163199
* Observe an object with a given path,

src/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ var utils = module.exports = {
2626
/**
2727
* get an attribute and remove it.
2828
*/
29-
attr: function (el, type) {
29+
attr: function (el, type, noRemove) {
3030
var attr = attrs[type],
3131
val = el.getAttribute(attr)
32-
if (val !== null) el.removeAttribute(attr)
32+
if (!noRemove && val !== null) el.removeAttribute(attr)
3333
return val
3434
},
3535

0 commit comments

Comments
 (0)