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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4386189c38efbea2da170dd03b7d30d11c69d0e7
4
- data.tar.gz: 90f46d3522191a2e65f21f9bc0accef50accc453
2
+ SHA256:
3
+ metadata.gz: 7245233240a25c03d7714bbeab59980acd3a0743118611db224c5eacaf52f7c8
4
+ data.tar.gz: e91b4658d70df7a8106b7ef6705429681819da11f411ea7fc4cd3e9fdfb18300
5
5
  SHA512:
6
- metadata.gz: a841a7bc1f7a30ddd0dd06dbb317f4aef580ae384865bf6a7f3df2c1789c36ef22df1bc4a4ece8501f4b6349100994bc0d6bb48f1cb5d79bf5172f58ed41c09c
7
- data.tar.gz: f66a43f275158666c7012acd90ca5a634c379ef8545c6255a6efb920f9cb82109c42ef7c7a98b492880949ea92af60eaab5f736acf5edffa490f247a44ea6194
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 `bundle install` to update `Gemfile.lock`
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
- for type, binding of Twine.bindingTypes when definition = Twine.getAttribute(node, type)
73
- element = findOrCreateElementForNode(node)
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
- fn = binding(node, context, definition, element)
78
- element.bindings.push(fn) if fn
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 = findOrCreateElementForNode(node)
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 and SVGElement nodes.
95
- # If the element supports children we continue to traverse the children, otherwise
96
- # we stop traversing that subtree.
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
- Twine.refresh = ->
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 explaination in bind()
151
- Twine.unbind(childNode) for childNode in (node.children || [])
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
- valueAttributeForNode = (node) ->
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
- nAttributes = node.attributes.length
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 nodeHasArrayIndexes(node)
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
- nodeHasArrayIndexes = (node) ->
262
- return unless node.bindingId?
263
- !!elements[node.bindingId]?.indexes?
282
+ nodeArrayIndexes = (node) ->
283
+ node.bindingId? && elements[node.bindingId]?.indexes
264
284
 
265
285
  arrayPointersForNode = (node, context) ->
266
- return {} unless node.bindingId?
267
- indexes = elements[node.bindingId]?.indexes
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'] and keypathRegex.test(value)
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
- valueAttribute = valueAttributeForNode(node)
286
- value = node[valueAttribute]
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[valueAttribute]
336
+ return if newValue == node[valueProp]
300
337
 
301
- node[valueAttribute] = if checkedValueType then newValue == node.value else newValue
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[valueAttribute])
348
+ setValue(context, keypath, node[valueProp])
312
349
 
313
350
  keypath = keypathForKey(node, definition)
314
- twoWayBinding = valueAttribute != 'textContent' && node.type != 'hidden'
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[valueAttribute]
363
+ return if getValue(context, keypath) == this[valueProp]
326
364
  refreshContext()
327
365
  Twine.refreshImmediately()
328
- $(node).on 'input keyup change', changeHandler
366
+ node.addEventListener(eventName, changeHandler) for eventName in events
329
367
  teardown = ->
330
- $(node).off 'input keyup change', changeHandler
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
- $(node).toggleClass('hide', lastValue = newValue)
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
- lastValue = {}
382
+ lastValues = {}
345
383
  return refresh: ->
346
- newValue = fn.call(node, context, rootContext, arrayPointersForNode(node, context))
347
- for key, value of newValue when !lastValue[key] != !value
348
- $(node).toggleClass(key, !!value)
349
- lastValue = newValue
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
- $(node).attr(key, value || null)
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
- setupAttributeBinding = (attributeName, bindingName) ->
387
- booleanAttribute = attributeName in ['checked', 'indeterminate', 'disabled', 'readOnly']
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 booleanAttribute
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
- setupAttributeBinding(attribute, attribute)
449
+ for attribute in ['placeholder', 'checked', 'indeterminate', 'disabled', 'href', 'title', 'readOnly', 'src', 'draggable']
450
+ setupPropertyBinding(attribute, attribute)
402
451
 
403
- setupAttributeBinding('innerHTML', 'unsafe-html')
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
- $(node).on eventName, onEventHandler
469
+ node.addEventListener(eventName, onEventHandler)
421
470
 
422
471
  return teardown: ->
423
- $(node).off eventName, onEventHandler
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
@@ -1,5 +1,4 @@
1
1
  require "rails"
2
- require "active_support"
3
2
 
4
3
  require "twine-rails/version"
5
4
 
@@ -1,3 +1,3 @@
1
1
  module TwineRails
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
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: 1.0.0
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: 2016-06-23 00:00:00.000000000 Z
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
- rubyforge_project:
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