Requires

Provides

Expectations.js

A trait that allows to wait for related widgets until they are ready

License:
Public domain (http://unlicense.org).
Authors:
Yaroslaff Fedin
  1. 24
  2. 25
  3. 26
  4. 27
  5. 28
  6. 29
  7. 30
  8. 31
  9. 32
  10. 33
  11. 34
  12. 35
  13. 36
  14. 37
  15. 38
  16. 39
  17. 40
  18. 41
  19. 42
  20. 43
  21. 44
  22. 45
  23. 46
  24. 47
  25. 48
  26. 49
  27. 50
  28. 51
  29. 52
  30. 53
  31. 54
  32. 55
  33. 56
  34. 57
  35. 58
  36. 59
  37. 60
  38. 61
  39. 62
  40. 63
  41. 64
  42. 65
  43. 66
  44. 67
  45. 68
  46. 69
  47. 70
  48. 71
  49. 72
  50. 73
  51. 74
  52. 75
  53. 76
  54. 77
  55. 78
  56. 79
  57. 80
  58. 81
  59. 82
  60. 83
  61. 84
  62. 85
  63. 86
  64. 87
  65. 88
  66. 89
  67. 90
  68. 91
  69. 92
  70. 93
  71. 94
  72. 95
  73. 96
  74. 97
  75. 98
  76. 99
  77. 100
  78. 101
  79. 102
  80. 103
  81. 104
  82. 105
  83. 106
  84. 107
  85. 108
  86. 109
  87. 110
  88. 111
  89. 112
  90. 113
  91. 114
  92. 115
  93. 116
  94. 117
  95. 118
  96. 119
  97. 120
  98. 121
  99. 122
  100. 123
  101. 124
  102. 125
  103. 126
  104. 127
  105. 128
  106. 129
  107. 130
  108. 131
  109. 132
  110. 133
  111. 134
  112. 135
  113. 136
!function() { var Expectations = LSD.Module.Expectations = new Class({ options: { has: { one: null, many: null } }, initialize: function() { this.expectations = {}; this.addEvents({ nodeInserted: function(widget) { var expectations = this.expectations, type = expectations.tag, tag = widget.tagName; if (!type) type = expectations.tag = {}; var group = type[tag]; if (!group) group = type[tag] = []; group.push(widget); group = type['*']; if (!group) group = type['*'] = []; group.push(widget); update.call(this, widget, tag, true); }, nodeRemoved: function(widget) { var expectations = this.expectations, type = expectations.tag, tag = widget.tagName; type[tag].erase(this); type["*"].erase(this); update.call(this, widget, tag, false); }, setParent: function(parent) { notify(this, '!>', parent.tagName, true, parent); for (; parent; parent = parent.parentNode) notify(this, '!', parent.tagName, true, parent); }, unsetParent: function(parent) { notify(this, '!>', parent.tagName, false, parent); for (; parent; parent = parent.parentNode) notify(this, '!', parent.tagName, false, parent); } }, true); this.parent.apply(this, arguments); var has = this.options.has, one = has.one, many = has.many; if (one) for (var name in one) { var value = one[name]; if (value.indexOf) value = {selector: value} value.name = name; this.addRelation(value); } if (many) for (var name in many) { var value = many[name]; if (value.indexOf) value = {selector: value} value.name = name; value.multiple = true; this.addRelation(value); } }, getElementsByTagName: function(tag) { var cache = this.expectations.tag; return (cache && cache[tag.toLowerCase()]) || []; }, removeClass: function(name) { return this.parent.apply(this, arguments); }, addClass: function(name) { var result = this.parent.apply(this, arguments); check(this, 'classes', name, true); return result; }, removePseudo: function(pseudo) { check(this, 'pseudos', pseudo, false); return this.parent.apply(this, arguments); }, addPseudo: function(pseudo) { var result = this.parent.apply(this, arguments); check(this, 'pseudos', pseudo, true); return result; }, setAttribute: function(name) { check(this, 'attributes', name, false); var result = this.parent.apply(this, arguments); check(this, 'attributes', name, true); return result; }, removeAttribute: function(name) { check(this, 'attributes', name, false); return this.parent.apply(this, arguments); }, match: function(selector) { if (typeof selector == 'string') selector = Slick.parse(selector); if (selector.expressions) selector = selector.expressions[0][0]; if (selector.tag && (selector.tag != '*') && (this.options.tag != selector.tag)) return false; if (selector.id && (this.options.id != selector.id)) return false; if (selector.attributes) for (var i = 0, j; j = selector.attributes[i]; i++) if (j.operator ? !j.test(this.attributes[j.key] && this.attributes[j.key].toString()) : !(j.key in this.attributes)) return false; if (selector.classes) for (var i = 0, j; j = selector.classes[i]; i++) if (!this.classes[j.value]) return false; if (selector.pseudos) { for (var i = 0, j; j = selector.pseudos[i]; i++) { var name = j.key; if (this.pseudos[name]) continue; var pseudo = pseudos[name]; if (pseudo == null) pseudos[name] = pseudo = Slick.lookupPseudo(name) || false; if (pseudo === false || (pseudo && !pseudo.call(this, this, j.value))) return false; } } return true; },

Expect processes a single step in a complex selector.

Each of those bits (e.g. strong.important) consists pieces that can not be cnahged in runtime (tagname) and other dynamic parts (classes, pseudos, attributes).

The idea is to split the selector bit to static and dynamic parts. The widget that is expecting the selector, groups his expectations by tag name. Every node inserted into that element or its children will pick up expectations related to it, thus matching static part of a selector. Then it’s time to match the dynamic part.

  1. 152
  2. 153
  3. 154
  4. 155
  5. 156
  6. 157
  7. 158
expect: function(selector, callback) { var combinator = selector.combinator || 'self'; var id = selector.id; var index = (combinator == ' ' && id) ? 'id' : combinator; expectations = this.expectations[index]; if (!expectations) expectations = this.expectations[index] = {}; if (selector.combinator) {

Given selector has combinator. Finds related elements and passes expectations to them.

  1. 163
  2. 164
  3. 165
  4. 166
  5. 167
  6. 168
  7. 169
  8. 170
  9. 171
  10. 172
  11. 173
  12. 174
  13. 175
  14. 176
  15. 177
if (!selector.structure) { var separated = separate(selector); selector.structure = { Slick: true, expressions: [[separated.structure]] } if (separated.state) selector.state = separated.state; } var key = (index == 'id') ? id : selector.tag; var group = expectations[key]; if (!group) group = expectations[key] = []; group.push([selector, callback]); var state = selector.state; if (this.document && this.document.documentElement) this.getElements(selector.structure).each(function(widget) { if (state) widget.expect(state, callback); else callback(widget, true); }); } else {

Selector without combinator, depends on state of current widget.

  1. 182
  2. 183
  3. 184
  4. 185
  5. 186
  6. 187
  7. 188
  8. 189
  9. 190
  10. 191
  11. 192
  12. 193
  13. 194
  14. 195
  15. 196
  16. 197
  17. 198
  18. 199
  19. 200
  20. 201
  21. 202
  22. 203
  23. 204
  24. 205
  25. 206
  26. 207
  27. 208
  28. 209
  29. 210
  30. 211
  31. 212
  32. 213
  33. 214
  34. 215
  35. 216
  36. 217
  37. 218
  38. 219
  39. 220
  40. 221
  41. 222
  42. 223
  43. 224
  44. 225
  45. 226
  46. 227
  47. 228
  48. 229
  49. 230
  50. 231
  51. 232
  52. 233
  53. 234
  54. 235
  55. 236
  56. 237
  57. 238
  58. 239
  59. 240
  60. 241
  61. 242
  62. 243
  63. 244
  64. 245
  65. 246
  66. 247
  67. 248
  68. 249
  69. 250
  70. 251
  71. 252
  72. 253
  73. 254
  74. 255
  75. 256
  76. 257
  77. 258
  78. 259
  79. 260
  80. 261
  81. 262
  82. 263
  83. 264
  84. 265
  85. 266
  86. 267
  87. 268
  88. 269
  89. 270
  90. 271
  91. 272
  92. 273
  93. 274
  94. 275
  95. 276
  96. 277
  97. 278
  98. 279
  99. 280
  100. 281
  101. 282
  102. 283
  103. 284
  104. 285
  105. 286
  106. 287
  107. 288
  108. 289
  109. 290
  110. 291
  111. 292
  112. 293
  113. 294
  114. 295
  115. 296
  116. 297
  117. 298
  118. 299
  119. 300
  120. 301
  121. 302
  122. 303
  123. 304
  124. 305
  125. 306
  126. 307
  127. 308
  128. 309
  129. 310
  130. 311
  131. 312
  132. 313
  133. 314
  134. 315
  135. 316
  136. 317
  137. 318
  138. 319
  139. 320
  140. 321
  141. 322
  142. 323
  143. 324
  144. 325
  145. 326
  146. 327
  147. 328
  148. 329
  149. 330
  150. 331
  151. 332
  152. 333
  153. 334
  154. 335
  155. 336
  156. 337
  157. 338
  158. 339
  159. 340
  160. 341
  161. 342
  162. 343
  163. 344
  164. 345
  165. 346
  166. 347
  167. 348
  168. 349
  169. 350
  170. 351
  171. 352
  172. 353
  173. 354
  174. 355
  175. 356
  176. 357
  177. 358
  178. 359
  179. 360
  180. 361
  181. 362
  182. 363
  183. 364
  184. 365
  185. 366
  186. 367
  187. 368
  188. 369
  189. 370
  190. 371
  191. 372
  192. 373
  193. 374
  194. 375
  195. 376
  196. 377
  197. 378
  198. 379
  199. 380
  200. 381
  201. 382
  202. 383
  203. 384
  204. 385
  205. 386
  206. 387
  207. 388
  208. 389
  209. 390
  210. 391
  211. 392
  212. 393
  213. 394
  214. 395
  215. 396
  216. 397
  217. 398
  218. 399
  219. 400
  220. 401
  221. 402
  222. 403
  223. 404
  224. 405
  225. 406
  226. 407
  227. 408
  228. 409
  229. 410
  230. 411
  231. 412
  232. 413
  233. 414
  234. 415
  235. 416
  236. 417
  237. 418
  238. 419
  239. 420
  240. 421
  241. 422
  242. 423
  243. 424
  244. 425
  245. 426
  246. 427
  247. 428
  248. 429
  249. 430
  250. 431
  251. 432
  252. 433
  253. 434
  254. 435
  255. 436
  256. 437
  257. 438
  258. 439
  259. 440
  260. 441
  261. 442
  262. 443
  263. 444
  264. 445
  265. 446
  266. 447
  267. 448
  268. 449
  269. 450
  270. 451
  271. 452
  272. 453
  273. 454
  274. 455
  275. 456
  276. 457
  277. 458
  278. 459
  279. 460
  280. 461
  281. 462
  282. 463
  283. 464
  284. 465
for (var types = ['pseudos', 'classes', 'attributes'], type, i = 0; type = types[i++];) { var values = selector[type]; if (values) values: for (var j = 0, value; (value = values[j++]) && (value = value.key || value.value);) { var kind = expectations[type]; if (!kind) kind = expectations[type] = {}; var group = kind[value]; if (!group) group = kind[value] = []; for (var k = group.length, expectation; expectation = group[--k];) if (expectation[0] == selector) continue values; group.push([selector, callback]); } } if (this.match(selector)) callback(this, true); } }, unexpect: function(selector, callback, iterator) { if (selector.expressions) selector = selector.expressions[0][0]; if (selector.combinator) { remove(this.expectations[selector.combinator][selector.tag], callback); if (!selector.state) return; this.getElements(selector.structure).each(function(widget) { widget.unexpect(selector.state, callback); if (iterator) iterator(widget) }); } else { if (iterator) iterator(widget) for (var types = ['pseudos', 'classes', 'attributes'], type, i = 0; type = types[i++];) { var values = selector[type], self = this.expectations.self; if (values) for (var j = 0, value; (value = values[j++]) && (value = value.key || value.value);) { remove(self[type][value], callback); } } } }, watch: function(selector, callback, depth) { if (typeof selector == 'string') selector = Slick.parse(selector); if (!depth) depth = 0; selector.expressions.each(function(expressions) { var watcher = function(widget, state) { if (expressions[depth + 1]) widget[state ? 'watch' : 'unwatch'](selector, callback, depth + 1) else callback(widget, state) }; watcher.callback = callback; this.expect(expressions[depth], watcher); }, this); }, unwatch: function(selector, callback, depth) { if (typeof selector == 'string') selector = Slick.parse(selector); if (!depth) depth = 0; selector.expressions.each(function(expressions) { this.unexpect(expressions[depth], callback, function(widget) { if (expressions[depth + 1]) widget.unwatch(selector, callback, depth + 1) else callback(widget, false) }) }, this); }, use: function() { var selectors = Array.flatten(arguments); var widgets = [] var callback = selectors.pop(); var unresolved = selectors.length; selectors.each(function(selector, i) { var watcher = function(widget, state) { if (state) { if (!widgets[i]) { widgets[i] = widget; unresolved--; if (!unresolved) callback.apply(this, widgets.concat(state)) } } else { if (widgets[i]) { if (!unresolved) callback.apply(this, widgets.concat(state)) delete widgets[i]; unresolved++; } } } this.watch(selector, watcher) }, this) }, addRelation: function(relation, callback) { if (relation.indexOf) relation = {selector: relation}; var states = relation.states, name = relation.name, origin = relation.origin || this, proxy = relation.proxy, transform = relation.transform, events = relation.events, multiple = relation.multiple, chain = relation.chain, callbacks = relation.callbacks, alias = relation.alias, relay = relation.relay; if (name && multiple) origin[name] = []; if (callbacks) { callbacks = origin.bindEvents(callbacks); var onAdd = callbacks.add, onRemove = callbacks.remove; } var layout = relation.layout || relation.selector; this.options.layout[name] = layout; if (proxy) { var proxied = []; this.options.proxies[name] = { container: function(callback) { proxied.push(callback) }, condition: proxy } } if (relay) { var relayed = {}; Object.each(relay, function(callback, type) { relayed[type] = function(event) { for (var widget = Element.get(event.target, 'widget'); widget; widget = widget.parentNode) { if (origin[name].indexOf(widget) > -1) { callback.apply(widget, arguments); break; } } } }); } if (transform) { var transformation = {}; transformation[transform] = layout; this.addLayoutTransformations(transformation); } if (events) events = origin.bindEvents(events); this.watch(relation.selector, function(widget, state) { if (events) widget[state ? 'addEvents' : 'removeEvents'](events); if (callback) callback.call(origin, widget, state); if (!state && onRemove) onRemove.call(origin, widget); if (name) { if (multiple) { if (state) origin[name].push(widget) else origin[name].erase(widget); if (relay && (origin[name].length == (state ? 1 : 0))) origin.element[state ? 'addEvents' : 'removeEvents'](relayed); } else { if (state) origin[name] = widget; else delete origin[name]; } } if (alias) widget[alias] = origin; if (proxied) { for (var i = 0, proxy; proxy = proxied[i++];) proxy(widget); } if (state && onAdd) onAdd.call(origin, widget); if (states) { var get = states.get, set = states.set, add = states.add, method = state ? 'linkState' : 'unlinkState'; if (get) for (var from in get) widget[method](origin, from, (get[from] === true) ? from : get[from]); if (set) for (var to in set) origin[method](widget, to, (set[to] === true) ? to : set[to]); if (add) for (var index in add) widget.addState(index, add[index]); } if (chain) { for (var label in chain) { if (state) widget.options.chain[label] = chain[label] else delete widget.options.chain[label] } } }); } }); var pseudos = {}; var check = function(widget, type, value, state, target) { var expectations = widget.expectations if (!target) { expectations = expectations.self; target = widget; } expectations = expectations && expectations[type] && expectations[type][value]; if (expectations) for (var i = 0, expectation; expectation = expectations[i++];) { var selector = expectation[0]; if (selector.structure && selector.state) { if (target.match(selector.structure)) { if (!state) { if (target.match(selector.state)) { target.unexpect(selector.state, expectation[1]); expectation[1](target, !!state) } } else target.expect(selector.state, expectation[1]) } } else if (target.match(selector)) expectation[1](target, !!state) } } var notify = function(widget, type, tag, state, target) { check(widget, type, tag, state, target) check(widget, type, "*", state, target) } var update = function(widget, tag, state) { notify(this, ' ', tag, state, widget); var options = widget.options, id = options.id; if (id) check(this, 'id', id, state, widget); if (this.previousSibling) { notify(this.previousSibling, '!+', options.tag, state, widget); notify(this.previousSibling, '++', options.tag, state, widget); for (var sibling = this; sibling = sibling.previousSibling;) { notify(sibling, '!~', tag, state, widget); notify(sibling, '~~', tag, state, widget); } } if (this.nextSibling) { notify(this.nextSibling, '+', tag, state, widget); notify(this.nextSibling, '++', tag, state, widget); for (var sibling = this; sibling = sibling.nextSibling;) { notify(sibling, '~', tag, state, widget); notify(sibling, '~~', tag, state, widget); } } if (widget.parentNode == this) notify(this, '>', options.tag, state, widget); } var remove = function(array, callback) { if (array) for (var i = array.length; i--;) { var fn = array[i][1]; if (fn == callback || fn.callback == callback) array.splice(i, 1); } } var separate = function(selector) { if (selector.state || selector.structure) return selector var separated = {}; for (var criteria in selector) { switch (criteria) { case 'tag': case 'combinator': case 'id': var type = 'structure'; break; default: var type = 'state'; } var group = separated[type]; if (!group) group = separated[type] = {}; group[criteria] = selector[criteria] }; return separated; } Expectations.behaviours = {}; Expectations.behave = function(selector, events) { Expectations.behaviours[selector] = function(widget, state) { var behaviours = widget.expectations.behaviours; if (!behaviours) behaviours = widget.expectations.behaviours = {}; var behaviour = behaviours[selector]; if (!behaviour) behaviour = behaviours[selector] = widget.bindEvents(events); widget[state ? 'addEvents' : 'removeEvents'](behaviour); }; } Expectations.attach = function(document) { for (selector in Expectations.behaviours) document.watch(selector, Expectations.behaviours[selector]); } LSD.Module.Events.Targets.expected = function() { var self = this, Targets = LSD.Module.Events.Targets; return { addEvents: function(events) { Object.each(events, function(value, key) { if (!self.watchers) self.watchers = {}; self.watchers[key] = function(widget, state) { value = Object.append({}, value) for (var name in value) { if (typeof value[name] == 'object') continue; widget.addEvent(name, value[name]); delete value[name]; } for (var name in value) { target = (Targets[name] || Targets.expected).call(widget); target[state ? 'addEvents' : 'removeEvents'](value); break; } }; self.watch(key, self.watchers[key]); }); }, removeEvents: function(events) { Object.each(events, function(value, key) { self.unwatch(key, self.watchers[key]); }); } } } var States = LSD.States.Known; }();