twine-rails 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +3 -2
- data/lib/assets/javascripts/twine.coffee +108 -59
- data/lib/twine-rails.rb +0 -1
- data/lib/twine-rails/version.rb +1 -1
- metadata +22 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7245233240a25c03d7714bbeab59980acd3a0743118611db224c5eacaf52f7c8
|
4
|
+
data.tar.gz: e91b4658d70df7a8106b7ef6705429681819da11f411ea7fc4cd3e9fdfb18300
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cc8f28bdd59879b679ea684021b5b500540e1d3928593b550488ae0cb9bd75a761bdc631ff70223028797812e9095e69c43ab9c2d77b2671a059be61d20a0a1
|
7
|
+
data.tar.gz: c8f4f398ef794db3e6062208f4d3ef8496d84f64968032bef0150b1a4b6f3f8044119bc362f182f0a0d15f09e4ea7818ed99a1ba62cb330e104ece89ff9a5f49
|
data/README.md
CHANGED
@@ -171,6 +171,7 @@ Where context expects a node and `$0` is shorthand for the current node in the d
|
|
171
171
|
## Releasing
|
172
172
|
|
173
173
|
1. Update version number in `package.json`, `bower.json`, and `lib/twine-rails/version.rb`
|
174
|
-
2. Run `
|
175
|
-
3. Run make .all && make .uglify to update JS
|
174
|
+
2. Run `dev up` to update `Gemfile.lock`
|
175
|
+
3. Run `make .all && make .uglify` to update JS
|
176
176
|
4. Push the new tag to GitHub and the new version to rubygems with `bundle exec rake release`
|
177
|
+
5. Publish the new version to NPM with `npm publish`.
|
@@ -24,6 +24,7 @@
|
|
24
24
|
|
25
25
|
keypathRegex = /^[a-z]\w*(\.[a-z]\w*|\[\d+\])*$/i # Tests if a string is a pure keypath.
|
26
26
|
refreshQueued = false
|
27
|
+
refreshCallbacks = []
|
27
28
|
rootNode = null
|
28
29
|
|
29
30
|
currentBindingCallbacks = null
|
@@ -56,6 +57,7 @@
|
|
56
57
|
|
57
58
|
bind = (context, node, indexes, forceSaveContext) ->
|
58
59
|
currentBindingCallbacks = []
|
60
|
+
element = null
|
59
61
|
if node.bindingId
|
60
62
|
Twine.unbind(node)
|
61
63
|
|
@@ -69,13 +71,26 @@
|
|
69
71
|
element = findOrCreateElementForNode(node)
|
70
72
|
element.indexes = indexes
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
+
bindingConstructors = null
|
75
|
+
for attribute in node.attributes
|
76
|
+
type = attribute.name
|
77
|
+
type = type.slice(5) if isDataAttribute(type)
|
78
|
+
|
79
|
+
constructor = Twine.bindingTypes[type]
|
80
|
+
continue unless constructor
|
81
|
+
|
82
|
+
bindingConstructors ?= []
|
83
|
+
definition = attribute.value
|
84
|
+
bindingConstructors.push([type, constructor, definition])
|
85
|
+
|
86
|
+
if bindingConstructors
|
87
|
+
element ?= findOrCreateElementForNode(node)
|
74
88
|
element.bindings ?= []
|
75
89
|
element.indexes ?= indexes
|
76
90
|
|
77
|
-
|
78
|
-
|
91
|
+
for [_, constructor, definition] in bindingConstructors.sort(bindingOrder)
|
92
|
+
binding = constructor(node, context, definition, element)
|
93
|
+
element.bindings.push(binding) if binding
|
79
94
|
|
80
95
|
if newContextKey = Twine.getAttribute(node, 'context')
|
81
96
|
keypath = keypathForKey(node, newContextKey)
|
@@ -85,18 +100,15 @@
|
|
85
100
|
context = getValue(context, keypath) || setValue(context, keypath, {})
|
86
101
|
|
87
102
|
if element || newContextKey || forceSaveContext
|
88
|
-
element
|
103
|
+
element ?= findOrCreateElementForNode(node)
|
89
104
|
element.childContext = context
|
90
105
|
element.indexes ?= indexes if indexes?
|
91
106
|
|
92
107
|
callbacks = currentBindingCallbacks
|
93
108
|
|
94
|
-
# IE and Safari don't support node.children for DocumentFragment
|
95
|
-
#
|
96
|
-
|
97
|
-
# https://developer.mozilla.org/en-US/docs/Web/API/ParentNode.children
|
98
|
-
# As a result, Twine are unsupported within DocumentFragment and SVGElement nodes.
|
99
|
-
bind(context, childNode, if newContextKey? then null else indexes) for childNode in (node.children || [])
|
109
|
+
# IE and Safari don't support node.children for DocumentFragment or SVGElement,
|
110
|
+
# See explanation in childrenForNode()
|
111
|
+
bind(context, childNode, if newContextKey? then null else indexes) for childNode in childrenForNode(node)
|
100
112
|
Twine.count = nodeCount
|
101
113
|
|
102
114
|
for callback in callbacks || []
|
@@ -105,13 +117,26 @@
|
|
105
117
|
|
106
118
|
Twine
|
107
119
|
|
120
|
+
# IE and Safari don't support node.children for DocumentFragment and SVGElement nodes.
|
121
|
+
# If the element supports children we continue to traverse the children, otherwise
|
122
|
+
# we stop traversing that subtree.
|
123
|
+
# https://developer.mozilla.org/en-US/docs/Web/API/ParentNode.children
|
124
|
+
# As a result, Twine are unsupported within DocumentFragment and SVGElement nodes.
|
125
|
+
#
|
126
|
+
# We also prevent nodes from being iterated over more than once by cacheing the
|
127
|
+
# lookup for children nodes, which prevents nodes that are dynamically inserted
|
128
|
+
# or removed as siblings from causing double/ missed binds and unbinds.
|
129
|
+
childrenForNode = (node) ->
|
130
|
+
if node.children then Array::slice.call(node.children, 0) else []
|
131
|
+
|
108
132
|
findOrCreateElementForNode = (node) ->
|
109
133
|
node.bindingId ?= ++nodeCount
|
110
134
|
elements[node.bindingId] ?= {}
|
111
|
-
elements[node.bindingId]
|
112
135
|
|
113
136
|
# Queues a refresh of the DOM, batching up calls for the current synchronous block.
|
114
|
-
|
137
|
+
# The callback will be called once when the refresh has completed.
|
138
|
+
Twine.refresh = (callback) ->
|
139
|
+
refreshCallbacks.push(callback) if callback
|
115
140
|
return if refreshQueued
|
116
141
|
refreshQueued = true
|
117
142
|
setTimeout(Twine.refreshImmediately, 0)
|
@@ -123,6 +148,10 @@
|
|
123
148
|
Twine.refreshImmediately = ->
|
124
149
|
refreshQueued = false
|
125
150
|
refreshElement(element) for key, element of elements
|
151
|
+
|
152
|
+
callbacks = refreshCallbacks
|
153
|
+
refreshCallbacks = []
|
154
|
+
cb() for cb in callbacks
|
126
155
|
return
|
127
156
|
|
128
157
|
Twine.register = (name, component) ->
|
@@ -145,10 +174,9 @@
|
|
145
174
|
delete elements[id]
|
146
175
|
delete node.bindingId
|
147
176
|
|
148
|
-
|
149
177
|
# IE and Safari don't support node.children for DocumentFragment or SVGElement,
|
150
|
-
# See
|
151
|
-
Twine.unbind(childNode) for childNode in (node
|
178
|
+
# See explanation in childrenForNode()
|
179
|
+
Twine.unbind(childNode) for childNode in childrenForNode(node)
|
152
180
|
this
|
153
181
|
|
154
182
|
# Returns the binding context for a node by looking up the tree.
|
@@ -187,7 +215,7 @@
|
|
187
215
|
addKey(rootContext) if node == rootNode
|
188
216
|
keys.join('.')
|
189
217
|
|
190
|
-
|
218
|
+
valuePropertyForNode = (node) ->
|
191
219
|
name = node.nodeName.toLowerCase()
|
192
220
|
if name in ['input', 'textarea', 'select']
|
193
221
|
if node.getAttribute('type') in ['checkbox', 'radio'] then 'checked' else 'value'
|
@@ -232,14 +260,7 @@
|
|
232
260
|
object[lastKey] = value
|
233
261
|
|
234
262
|
stringifyNodeAttributes = (node) ->
|
235
|
-
|
236
|
-
i = 0
|
237
|
-
result = ""
|
238
|
-
while i < nAttributes
|
239
|
-
attr = node.attributes.item(i)
|
240
|
-
result += "#{attr.nodeName}='#{attr.textContent}'"
|
241
|
-
i+=1
|
242
|
-
result
|
263
|
+
[].map.call(node.attributes, (attr) -> "#{attr.name}=#{JSON.stringify(attr.value)}").join(' ')
|
243
264
|
|
244
265
|
wrapFunctionString = (code, args, node) ->
|
245
266
|
if isKeypath(code) && keypath = keypathForKey(node, code)
|
@@ -249,7 +270,7 @@
|
|
249
270
|
($context, $root) -> getValue($context, keypath)
|
250
271
|
else
|
251
272
|
code = "return #{code}"
|
252
|
-
code = "with($arrayPointers) { #{code} }" if
|
273
|
+
code = "with($arrayPointers) { #{code} }" if nodeArrayIndexes(node)
|
253
274
|
code = "with($registry) { #{code} }" if requiresRegistry(args)
|
254
275
|
try
|
255
276
|
new Function(args, "with($context) { #{code} }")
|
@@ -258,14 +279,12 @@
|
|
258
279
|
|
259
280
|
requiresRegistry = (args) -> /\$registry/.test(args)
|
260
281
|
|
261
|
-
|
262
|
-
|
263
|
-
!!elements[node.bindingId]?.indexes?
|
282
|
+
nodeArrayIndexes = (node) ->
|
283
|
+
node.bindingId? && elements[node.bindingId]?.indexes
|
264
284
|
|
265
285
|
arrayPointersForNode = (node, context) ->
|
266
|
-
|
267
|
-
|
268
|
-
return {} unless indexes?
|
286
|
+
indexes = nodeArrayIndexes(node)
|
287
|
+
return {} unless indexes
|
269
288
|
|
270
289
|
result = {}
|
271
290
|
for key, index of indexes
|
@@ -273,17 +292,35 @@
|
|
273
292
|
result
|
274
293
|
|
275
294
|
isKeypath = (value) ->
|
276
|
-
value not in ['true', 'false', 'null', 'undefined']
|
295
|
+
value not in ['true', 'false', 'null', 'undefined'] && keypathRegex.test(value)
|
296
|
+
|
297
|
+
isDataAttribute = (value) ->
|
298
|
+
value[0] == 'd' &&
|
299
|
+
value[1] == 'a' &&
|
300
|
+
value[2] == 't' &&
|
301
|
+
value[3] == 'a' &&
|
302
|
+
value[4] == '-'
|
277
303
|
|
278
304
|
fireCustomChangeEvent = (node) ->
|
279
305
|
event = document.createEvent('CustomEvent')
|
280
306
|
event.initCustomEvent('bindings:change', true, false, {})
|
281
307
|
node.dispatchEvent(event)
|
282
308
|
|
309
|
+
bindingOrder = ([firstType], [secondType]) ->
|
310
|
+
ORDERED_BINDINGS = {
|
311
|
+
define: 1,
|
312
|
+
bind: 2,
|
313
|
+
eval: 3
|
314
|
+
}
|
315
|
+
return 1 unless ORDERED_BINDINGS[firstType]
|
316
|
+
return -1 unless ORDERED_BINDINGS[secondType]
|
317
|
+
|
318
|
+
ORDERED_BINDINGS[firstType] - ORDERED_BINDINGS[secondType]
|
319
|
+
|
283
320
|
Twine.bindingTypes =
|
284
321
|
bind: (node, context, definition) ->
|
285
|
-
|
286
|
-
value = node[
|
322
|
+
valueProp = valuePropertyForNode(node)
|
323
|
+
value = node[valueProp]
|
287
324
|
lastValue = undefined
|
288
325
|
teardown = undefined
|
289
326
|
|
@@ -296,9 +333,9 @@
|
|
296
333
|
return if newValue == lastValue # return if we can and avoid a DOM operation
|
297
334
|
|
298
335
|
lastValue = newValue
|
299
|
-
return if newValue == node[
|
336
|
+
return if newValue == node[valueProp]
|
300
337
|
|
301
|
-
node[
|
338
|
+
node[valueProp] = if checkedValueType then newValue == node.value else newValue
|
302
339
|
fireCustomChangeEvent(node)
|
303
340
|
|
304
341
|
return {refresh} unless isKeypath(definition)
|
@@ -308,10 +345,10 @@
|
|
308
345
|
return unless node.checked
|
309
346
|
setValue(context, keypath, node.value)
|
310
347
|
else
|
311
|
-
setValue(context, keypath, node[
|
348
|
+
setValue(context, keypath, node[valueProp])
|
312
349
|
|
313
350
|
keypath = keypathForKey(node, definition)
|
314
|
-
twoWayBinding =
|
351
|
+
twoWayBinding = valueProp != 'textContent' && node.type != 'hidden'
|
315
352
|
|
316
353
|
if keypath[0] == '$root'
|
317
354
|
context = rootContext
|
@@ -321,13 +358,14 @@
|
|
321
358
|
refreshContext()
|
322
359
|
|
323
360
|
if twoWayBinding
|
361
|
+
events = ['input', 'keyup', 'change']
|
324
362
|
changeHandler = ->
|
325
|
-
return if getValue(context, keypath) == this[
|
363
|
+
return if getValue(context, keypath) == this[valueProp]
|
326
364
|
refreshContext()
|
327
365
|
Twine.refreshImmediately()
|
328
|
-
|
366
|
+
node.addEventListener(eventName, changeHandler) for eventName in events
|
329
367
|
teardown = ->
|
330
|
-
|
368
|
+
node.removeEventListener(eventName, changeHandler) for eventName in events
|
331
369
|
|
332
370
|
{refresh, teardown}
|
333
371
|
|
@@ -337,16 +375,24 @@
|
|
337
375
|
return refresh: ->
|
338
376
|
newValue = !fn.call(node, context, rootContext, arrayPointersForNode(node, context))
|
339
377
|
return if newValue == lastValue
|
340
|
-
|
378
|
+
node.classList.toggle('hide', lastValue = newValue)
|
341
379
|
|
342
380
|
'bind-class': (node, context, definition) ->
|
343
381
|
fn = wrapFunctionString(definition, '$context,$root,$arrayPointers', node)
|
344
|
-
|
382
|
+
lastValues = {}
|
345
383
|
return refresh: ->
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
384
|
+
newValues = fn.call(node, context, rootContext, arrayPointersForNode(node, context))
|
385
|
+
|
386
|
+
for key, value of newValues
|
387
|
+
newValue = newValues[key] = !!newValues[key]
|
388
|
+
currValue = lastValues[key] ? node.classList.contains(key)
|
389
|
+
if currValue != newValue
|
390
|
+
if newValue
|
391
|
+
node.classList.add(key)
|
392
|
+
else
|
393
|
+
node.classList.remove(key)
|
394
|
+
|
395
|
+
lastValues = newValues
|
350
396
|
|
351
397
|
'bind-attribute': (node, context, definition) ->
|
352
398
|
fn = wrapFunctionString(definition, '$context,$root,$arrayPointers', node)
|
@@ -354,7 +400,10 @@
|
|
354
400
|
return refresh: ->
|
355
401
|
newValue = fn.call(node, context, rootContext, arrayPointersForNode(node, context))
|
356
402
|
for key, value of newValue when lastValue[key] != value
|
357
|
-
|
403
|
+
if (!value)
|
404
|
+
node.removeAttribute(key)
|
405
|
+
else
|
406
|
+
node.setAttribute(key, value)
|
358
407
|
lastValue = newValue
|
359
408
|
|
360
409
|
define: (node, context, definition) ->
|
@@ -383,24 +432,24 @@
|
|
383
432
|
|
384
433
|
indexes
|
385
434
|
|
386
|
-
|
387
|
-
|
435
|
+
setupPropertyBinding = (attributeName, bindingName) ->
|
436
|
+
booleanProp = attributeName in ['checked', 'indeterminate', 'disabled', 'readOnly', 'draggable']
|
388
437
|
|
389
|
-
Twine.bindingTypes["bind-#{bindingName}"] = (node, context, definition) ->
|
438
|
+
Twine.bindingTypes["bind-#{bindingName.toLowerCase()}"] = (node, context, definition) ->
|
390
439
|
fn = wrapFunctionString(definition, '$context,$root,$arrayPointers', node)
|
391
440
|
lastValue = undefined
|
392
441
|
return refresh: ->
|
393
442
|
newValue = fn.call(node, context, rootContext, arrayPointersForNode(node, context))
|
394
|
-
newValue = !!newValue if
|
443
|
+
newValue = !!newValue if booleanProp
|
395
444
|
return if newValue == lastValue
|
396
445
|
node[attributeName] = lastValue = newValue
|
397
446
|
|
398
447
|
fireCustomChangeEvent(node) if attributeName == 'checked'
|
399
448
|
|
400
|
-
for attribute in ['placeholder', 'checked', 'indeterminate', 'disabled', 'href', 'title', 'readOnly', 'src']
|
401
|
-
|
449
|
+
for attribute in ['placeholder', 'checked', 'indeterminate', 'disabled', 'href', 'title', 'readOnly', 'src', 'draggable']
|
450
|
+
setupPropertyBinding(attribute, attribute)
|
402
451
|
|
403
|
-
|
452
|
+
setupPropertyBinding('innerHTML', 'unsafe-html')
|
404
453
|
|
405
454
|
preventDefaultForEvent = (event) ->
|
406
455
|
(event.type == 'submit' || event.currentTarget.nodeName.toLowerCase() == 'a') &&
|
@@ -408,7 +457,7 @@
|
|
408
457
|
|
409
458
|
setupEventBinding = (eventName) ->
|
410
459
|
Twine.bindingTypes["bind-event-#{eventName}"] = (node, context, definition) ->
|
411
|
-
onEventHandler = (event, data) ->
|
460
|
+
onEventHandler = (event, data = event.detail) ->
|
412
461
|
discardEvent = Twine.shouldDiscardEvent[eventName]?(event)
|
413
462
|
if discardEvent || preventDefaultForEvent(event)
|
414
463
|
event.preventDefault()
|
@@ -417,10 +466,10 @@
|
|
417
466
|
|
418
467
|
wrapFunctionString(definition, '$context,$root,$arrayPointers,event,data', node).call(node, context, rootContext, arrayPointersForNode(node, context), event, data)
|
419
468
|
Twine.refreshImmediately()
|
420
|
-
|
469
|
+
node.addEventListener(eventName, onEventHandler)
|
421
470
|
|
422
471
|
return teardown: ->
|
423
|
-
|
472
|
+
node.removeEventListener(eventName, onEventHandler)
|
424
473
|
|
425
474
|
for eventName in ['click', 'dblclick', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'mousedown', 'mouseup',
|
426
475
|
'submit', 'dragenter', 'dragleave', 'dragover', 'drop', 'drag', 'change', 'keypress', 'keydown', 'keyup', 'input',
|
data/lib/twine-rails.rb
CHANGED
data/lib/twine-rails/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twine-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Li
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-02-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: coffee-rails
|
@@ -26,6 +26,20 @@ dependencies:
|
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: railties
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
29
43
|
- !ruby/object:Gem::Dependency
|
30
44
|
name: bundler
|
31
45
|
requirement: !ruby/object:Gem::Requirement
|
@@ -69,7 +83,11 @@ files:
|
|
69
83
|
homepage: https://github.com/Shopify/twine
|
70
84
|
licenses:
|
71
85
|
- MIT
|
72
|
-
metadata:
|
86
|
+
metadata:
|
87
|
+
bug_tracker_uri: https://github.com/Shopify/twine/issues
|
88
|
+
changelog_uri: https://github.com/Shopify/twine/releases
|
89
|
+
source_code_uri: https://github.com/Shopify/twine
|
90
|
+
allowed_push_host: https://rubygems.org
|
73
91
|
post_install_message:
|
74
92
|
rdoc_options: []
|
75
93
|
require_paths:
|
@@ -85,8 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
103
|
- !ruby/object:Gem::Version
|
86
104
|
version: '0'
|
87
105
|
requirements: []
|
88
|
-
|
89
|
-
rubygems_version: 2.5.1
|
106
|
+
rubygems_version: 3.0.3
|
90
107
|
signing_key:
|
91
108
|
specification_version: 4
|
92
109
|
summary: Minimalistic two-way bindings
|