twine-rails 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|