evil-blocks-rails 0.5.1 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5be4b6d7fdc3df28a8ad6bc1cd73f4024f6396f6
4
- data.tar.gz: 3dfc6a88f8d0da7aa01cd5c93c1ec1363f14e6fa
3
+ metadata.gz: e3218d628224ba9091d719cc07bc71d08197753f
4
+ data.tar.gz: b7bf21a51deba749a22b9a11e6f13d1d705d682e
5
5
  SHA512:
6
- metadata.gz: 771a05c59ed27eaa63d918fcd0b68971c86b7e7afe072d1e07d20406a7b549b5a91438856c1ed257f21fadb3bfceb53f4c6438be9254afb3efb908fc29952c90
7
- data.tar.gz: 633c478e433f53c3e431bd0d12f076088b88e177660052ce34d4b08f3030e85e48396dc0566c5b4228bf25bbc39e1847c07d16051559726a73de702ab1db8444
6
+ metadata.gz: 09eb8985c804ca37642f418375554dd9f0cde07ba7d55d9ef2586becbe6de5af7a25948cd61d1bef365f5d7c3e6553419545774d1e924470543c4cb496b905f9
7
+ data.tar.gz: 13cc2cd3cce7438aee98f2e66078f7448a9cbed49318e37253b7fed9b3087753cbaa80b8a44351e420a0777abc648a6c89c628af52883e34ad73ff92a01eb1c7
@@ -1,3 +1,8 @@
1
+ ## 0.6 (Ranger Able, 27th January 1951)
2
+ * Add filters, which process block object before init was called.
3
+ * Most build-in features was moved to filter to be disableable.
4
+ * Listener `load on window` will call immediately, if page was already loaded.
5
+
1
6
  ## 0.5.1
2
7
  * Fix block vitalizing, when multiple blocks was binded to same DOM node.
3
8
 
data/README.md CHANGED
@@ -15,7 +15,7 @@ Evil Block is a tiny JS framework for web pages. It is based on 4 ideas:
15
15
 
16
16
  See also [Evil Front], a pack of helpers for Ruby on Rails and Evil Blocks.
17
17
 
18
- Sponsored by [Evil Martians]. Role aliases was taken from [Role.js].
18
+ Sponsored by [Evil Martians]. Role aliases were taken from [Role.js].
19
19
 
20
20
  [Role.js]: https://github.com/kossnocorp/role
21
21
  [big price]: http://staal.io/blog/2014/02/05/2-way-data-binding-under-the-microscope/
@@ -85,7 +85,8 @@ and `data-role` (to define elements inside block).
85
85
  ```
86
86
 
87
87
  Evil Blocks extends Slim and jQuery, so you can use shortcuts for this
88
- attributes: `@@block` and `@role`:
88
+ attributes: `@@block` and `@role`. For Haml you can use [Role Block Haml] gem
89
+ to use same shortcuts.
89
90
 
90
91
  ```haml
91
92
  @@todo
@@ -106,6 +107,8 @@ and be sure in scripts:
106
107
  Of course, Evil Block doesn’t force you to use only this selectors.
107
108
  You can any attributes, that you like.
108
109
 
110
+ [Role Block Haml]: https://github.com/vladson/role_block_haml
111
+
109
112
  ## Blocks
110
113
 
111
114
  You should split your interface to independent controls and mark them
@@ -233,6 +236,8 @@ evil.blocks '@@docs',
233
236
  @openPage(location.hash)
234
237
  ```
235
238
 
239
+ Listener `load on window` will execute immediately, if window is already loaded.
240
+
236
241
  [evil-front/links]: https://github.com/ai/evil-front/blob/master/evil-front/lib/assets/javascripts/evil-front/links.js
237
242
 
238
243
  ## Blocks Communications
@@ -241,7 +246,7 @@ Blocks should communicates by custom jQuery events. You can bind event listener
241
246
  to block node by `"on events"` method:
242
247
 
243
248
  ```coffee
244
- evil.block '@@slideshow', ->
249
+ evil.block '@@slideshow',
245
250
  nextSlide: -> …
246
251
 
247
252
  'on play': ->
@@ -250,7 +255,7 @@ evil.block '@@slideshow', ->
250
255
  'on stop': ->
251
256
  clearInterval(@timer)
252
257
 
253
- evil.block '@@video', ->
258
+ evil.block '@@video',
254
259
 
255
260
  'click on @fullscreenButton': ->
256
261
  $('@@slideshow').trigger('stop')
@@ -259,12 +264,12 @@ evil.block '@@video', ->
259
264
  If you want to use broadcast messages, you can use custom events on body:
260
265
 
261
266
  ```coffee
262
- evil.block '@@callUs', ->
267
+ evil.block '@@callUs',
263
268
 
264
269
  'change-city on body': (e, city) ->
265
270
  @phoneNumber.text(city.phone)
266
271
 
267
- evil.block '@@cityChanger', ->
272
+ evil.block '@@cityChanger',
268
273
  getCurrentCity: -> …
269
274
 
270
275
  'change on @citySelect': ->
@@ -318,6 +323,74 @@ evil.block '@@comment',
318
323
 
319
324
  [big price]: http://staal.io/blog/2014/02/05/2-way-data-binding-under-the-microscope/
320
325
 
326
+ ## Debug
327
+
328
+ Evil Blocks contains debug extension, which log every events inside blocks.
329
+ To enable it, just load `evil-blocks.debug.js`. For example, in Rails:
330
+
331
+ ```haml
332
+ - if Rails.env.development?
333
+ = javascript_include_tag 'evil-blocks.debug'
334
+ ```
335
+
336
+ ## Extensions
337
+
338
+ Evil Blocks has tiny core. It only finds blocks by selectors, sets `@block`
339
+ property and calls `init` method. Any others features (like event bindings
340
+ or `@$()` method) was created by filters and can be disabled or replaced.
341
+
342
+ Before calling `init`, Evil Blocks processes object throw the filters list in
343
+ `evil.block.filters`. Filter accepts object as first argument and unique
344
+ class ID as second. It can find some properties in object, work with block
345
+ DOM nodes and add/remove some object properties. If filter will return `false`,
346
+ Evil Blocks will stop block vitalizing and will not call `init` method.
347
+
348
+ Default filters:
349
+
350
+ 1. **Don’t vitalize same DOM node twice.** It return `false` if block
351
+ was already initialized with this class ID.
352
+ 2. **Add `@$()` method.** It add shortcut find method to object.
353
+ 3. **Add shortcuts to `@element`.** It add properties for all children with
354
+ `data-role` attribute.
355
+ 4. **Bind block events.** Find, bind listeners and remove all methods with
356
+ name like `on event`.
357
+ 5. **Smarter window load listener.** Run `load on window` listener immediately,
358
+ if window is already loaded.
359
+ 6. **Bind window and body events.** Find, bind listeners and remove all methods
360
+ with name like `event on window` or `event on body`.
361
+ 7. **Bind elements events.** Find, bind listeners and remove all methods
362
+ with name like `event on child`.
363
+
364
+ You can add you own filter to `evil.block.filters`. Most filters should be added
365
+ after first filter to not been called on already initialized blocks.
366
+
367
+ Let’s write filter, which will initialize blocks only when they become to be
368
+ visible.
369
+
370
+ ```coffee
371
+ filter = (obj) ->
372
+ if not obj.block.is(':visible')
373
+ # Check for visibility every 100 ms
374
+ # and recall vitalizing if block become visible
375
+ checking = ->
376
+ evil.block.vitalize(obj.block) if obj.block.is(':visible')
377
+ setTimeout(checking, 100);
378
+
379
+ # Disable block initializing
380
+ return false
381
+
382
+ # Add filter to list
383
+ evil.block.filters.splice(0, 0, filter)
384
+ ```
385
+
386
+ With filters you can change Evil Blocks logic, add some new shortcuts or
387
+ features like mixins.
388
+
389
+ Also you can remove any default filters from `evil.block.filters`. For example,
390
+ you can create properties for `data-role` children only from some white list.
391
+
392
+ But Filters API is still unstable and you should be careful on major updates.
393
+
321
394
  ## Modules
322
395
 
323
396
  If your blocks has same behavior, you can create module-block and set
@@ -329,12 +402,12 @@ multiple blocks on same tag:
329
402
  ```
330
403
 
331
404
  ```coffee
332
- evil.block '@@closable', ->
405
+ evil.block '@@closable',
333
406
 
334
407
  'click on @closeLink': ->
335
408
  @block.trigger('close')
336
409
 
337
- evil.block '@@popup', ->
410
+ evil.block '@@popup',
338
411
 
339
412
  'on close': ->
340
413
  @clock.removeClass('is-open')
@@ -361,16 +434,6 @@ evil.block '@@docs',
361
434
  @openInFancybox(@example)
362
435
  ```
363
436
 
364
- ## Debug
365
-
366
- Evil Blocks contains debug extension, which log every events inside blocks.
367
- To enable it, just load `evil-block.debug.js`. For example, in Rails:
368
-
369
- ```haml
370
- - if Rails.env.development?
371
- = javascript_include_tag 'evil-block.debug'
372
- ```
373
-
374
437
  ## Install
375
438
 
376
439
  ### Ruby on Rails
@@ -387,6 +450,9 @@ Load `evil-blocks.js` in your script:
387
450
  //= require evil-blocks
388
451
  ```
389
452
 
453
+ If you use Rails 3 on Heroku, you may need
454
+ [some hack](https://github.com/ai/evil-blocks/issues/17).
455
+
390
456
  ### Ruby
391
457
 
392
458
  If you use Sinatra or other non-Rails framework you can add Evil Blocks path
@@ -13,17 +13,29 @@
13
13
  return;
14
14
  }
15
15
 
16
- window.evil.block.eventFilter = function (callback, block, event) {
17
- return function () {
18
- var params = Array.prototype.slice.call(arguments, 1);
19
- var messages = ['Event "' + event + '" on', block.block[0]];
20
- if ( params.length > 0 ) {
21
- messages.push('with params');
22
- messages = messages.concat(params);
16
+ var logger = function (obj) {
17
+ for ( var name in obj ) {
18
+ if ( name.indexOf('on ') == -1 ) continue;
19
+
20
+ var parts = name.split('on ');
21
+ var event = parts[0] ? parts[0] : parts[1];
22
+
23
+ var callback = obj[name];
24
+ obj[name] = function (e) {
25
+ var source = e.el ? e.el[0] : this.block[0];
26
+ var messages = ['Event "' + event + '" on', source];
27
+
28
+ var params = Array.prototype.slice.call(arguments, 1);
29
+ if ( params.length > 0 ) {
30
+ messages.push('with params');
31
+ messages = messages.concat(params);
32
+ }
33
+
34
+ log.apply(this, messages);
35
+ callback.apply(this, arguments);
23
36
  }
24
- log.apply(this, messages);
37
+ }
38
+ };
25
39
 
26
- callback.apply(this, arguments);
27
- };
28
- }
40
+ evil.block.filters.splice(2, 0, logger);
29
41
  })();
@@ -1,6 +1,23 @@
1
- ;(function ($) {
1
+ ;(function ($, window) {
2
2
  "use strict";
3
3
 
4
+ // Helpers
5
+ var $window = $(window);
6
+
7
+ // Clone object
8
+ var clone = function (origin) {
9
+ var cloned = { };
10
+ for ( var name in origin ) {
11
+ cloned[name] = origin[name];
12
+ }
13
+ return cloned;
14
+ };
15
+
16
+ // Is string ends with substring.
17
+ var endsWith = function (string, substring) {
18
+ return string.substr(-substring.length) === substring;
19
+ };
20
+
4
21
  /*
5
22
  * Add `@data-role` alias to jQuery.
6
23
  *
@@ -9,9 +26,7 @@
9
26
 
10
27
  var rewriteSelector = function (context, name, pos) {
11
28
  var original = context[name];
12
- if ( !original ) {
13
- return;
14
- }
29
+ if ( !original ) return;
15
30
 
16
31
  context[name] = function () {
17
32
  arguments[pos] = arguments[pos].replace(
@@ -35,138 +50,47 @@
35
50
  if ( !window.evil ) {
36
51
  window.evil = { };
37
52
  }
53
+ var evil = window.evil;
38
54
 
39
- /**
40
- * If onready event is already happend.
41
- */
42
- var ready = false;
43
-
44
- var callbacks = {
45
- /**
46
- * Create callback wrapper for block listener.
47
- */
48
- block: function (self, func) {
49
- return function (e) {
50
- if ( e.currentTarget == e.target ) {
51
- func.apply(self, arguments);
52
- }
53
- }
54
- },
55
-
56
- /**
57
- * Create callback wrapper for body/document listener.
58
- */
59
- global: function (self, func) {
60
- return function () {
61
- func.apply(self, arguments);
62
- }
63
- },
64
-
65
- /**
66
- * Create callback wrapper for element listener.
67
- */
68
- elem: function (self, func) {
69
- return function () {
70
- var event = arguments[0];
71
- event.el = $(this);
72
- func.apply(self, arguments);
73
- }
74
- }
75
- };
76
-
77
- /**
78
- * Execute `callback` on every finded `selector` inside `base`.
79
- */
80
- var vitalize = function (base, id, selector, klass) {
55
+ // Find selector inside base DOM node and cretae class for it.
56
+ var find = function (base, id, selector, klass) {
81
57
  var blocks = $().add( base.filter(selector) ).
82
58
  add( base.find(selector) );
83
59
 
84
- if ( blocks.length == 0 ) {
85
- return;
86
- }
60
+ if ( blocks.length == 0 ) return;
87
61
 
88
- var inits = [];
89
-
90
- for ( var i = 0; i < blocks.length; i++ ) {
91
- var block = $(blocks[i]);
62
+ var objects = [];
92
63
 
93
- var vitalized = block.data('evil-vitalized');
94
- if ( !vitalized ) {
95
- vitalized = [];
96
- } else if ( vitalized.indexOf(id) != -1 ) {
97
- continue;
98
- }
99
- vitalized.push(id);
100
- block.data('evil-vitalized', vitalized);
101
-
102
- var obj = { };
103
-
104
- obj.$ = (function (block) {
105
- return function (subselector) {
106
- return $(subselector, block);
107
- };
108
- })(block);
109
-
110
- var actives = { };
111
- block.find('[data-role]').each(function (_, el) {
112
- var roles = el.attributes['data-role'].value.split(' ');
113
- for ( var i = 0; i < roles.length; i++ ) {
114
- var role = roles[i];
115
- if ( !obj[role] ) {
116
- obj[role] = $();
117
- }
118
- obj[role].push(el);
119
- }
120
- });
64
+ blocks.each(function (_, node) {
65
+ var block = $(node);
121
66
 
67
+ var obj = clone(klass);
122
68
  obj.block = block;
123
69
 
124
- var event = function (type, name, prop) {
125
- var result = callbacks[type](obj, prop);
126
- if ( window.evil.block.eventFilter ) {
127
- result = window.evil.block.eventFilter(result, obj, name);
128
- }
129
- return result;
130
- };
131
-
132
- for ( var name in klass ) {
133
- var prop = klass[name];
134
-
135
- if ( name.indexOf('on ') == -1 ) {
136
- obj[name] = prop;
137
- continue;
138
- }
139
-
140
- (function (name, prop) {
141
- var parts = name.split(' on ');
142
-
143
- if ( parts[1] == 'body' ) {
144
- $('body').on(parts[0], event('global', name, prop));
145
-
146
- } else if ( parts[1] == 'window' ) {
147
- $(window).on(parts[0], event('global', name, prop));
148
-
149
- } else if ( parts[1] ) {
150
- block.on(parts[0], parts[1], event('elem', name, prop));
151
-
152
- } else {
153
- block.on(parts[0], event('block', name, prop));
154
- }
155
- })(name, prop);
70
+ for ( var i = 0; i < evil.block.filters.length; i++ ) {
71
+ var stop = evil.block.filters[i](obj, id);
72
+ if ( stop === false ) return;
156
73
  }
157
74
 
158
- inits.push(obj);
159
- }
75
+ objects.push(obj)
76
+ });
160
77
 
161
- if ( klass.init ) {
162
- return function () {
163
- for ( var i = 0; i < inits.length; i++ ) {
164
- klass.init.apply(inits[i]);
165
- }
166
- };
167
- }
78
+ return function () {
79
+ objects.forEach(function (obj) {
80
+ if (obj.init) obj.init();
81
+ })
82
+ };
168
83
  };
169
84
 
85
+ // If onready event was already happend.
86
+ var ready = false;
87
+
88
+ // If onload event was already happend.
89
+ var loaded = false;
90
+ $window.load(function (event) {
91
+ loaded = event;
92
+ });
93
+
170
94
  // Latest block ID
171
95
  var lastBlock = 0;
172
96
 
@@ -205,29 +129,22 @@
205
129
  * evil.block '.block', ->
206
130
  * # init method
207
131
  */
208
- window.evil.block = function (selector, vitalizer) {
132
+ evil.block = function (selector, klass) {
209
133
  lastBlock += 1;
210
134
  var id = lastBlock;
211
135
 
212
- if ( typeof(vitalizer) == 'function' ) {
213
- vitalizer = { init: vitalizer };
136
+ if ( typeof(klass) == 'function' ) {
137
+ klass = { init: klass };
214
138
  }
215
139
 
216
- window.evil.block.vitalizers.push([id, selector, vitalizer]);
140
+ evil.block.defined.push([id, selector, klass]);
217
141
 
218
142
  if ( ready ) {
219
- var init = vitalize($(document), id, selector, vitalizer);
220
- if ( init ) {
221
- init();
222
- }
143
+ var init = find($(document), id, selector, klass);
144
+ if ( init ) init();
223
145
  }
224
146
  };
225
147
 
226
- /**
227
- * Evil blocks list.
228
- */
229
- window.evil.block.vitalizers = [];
230
-
231
148
  /**
232
149
  * Vitalize all current blocks inside base. You must call it on every
233
150
  * new content from AJAX.
@@ -236,7 +153,7 @@
236
153
  * $.get '/comments', (comments) =>
237
154
  * evil.block.vitalize $(comments).applyTo(@comments)
238
155
  */
239
- window.evil.block.vitalize = function (base) {
156
+ evil.block.vitalize = function (base) {
240
157
  if ( base ) {
241
158
  base = $(base);
242
159
  } else {
@@ -244,26 +161,165 @@
244
161
  }
245
162
 
246
163
  var inits = [];
247
- for ( var i = 0; i < window.evil.block.vitalizers.length; i++ ) {
248
- var block = window.evil.block.vitalizers[i];
249
- inits.push( vitalize(base, block[0], block[1], block[2]) );
250
- }
164
+ evil.block.defined.forEach(function (define) {
165
+ inits.push( find(base, define[0], define[1], define[2]) );
166
+ });
251
167
 
252
168
  for ( var i = 0; i < inits.length; i++ ) {
253
- if ( inits[i] ) {
254
- inits[i]();
255
- }
169
+ if ( inits[i] ) inits[i]();
256
170
  }
257
171
  };
258
172
 
173
+ /**
174
+ * Evil blocks list.
175
+ */
176
+ evil.block.defined = [];
177
+
178
+ /**
179
+ * Filters to process block object and add some extra functions
180
+ * to Evil Blocks. For example, allow to write listeners.
181
+ *
182
+ * Filter will receive block object and unique class ID.
183
+ * If filter return `false`, block will not be created.
184
+ */
185
+ evil.block.filters = [];
186
+
187
+ var filters = evil.block.filters;
188
+
189
+ /**
190
+ * Don’t vitalize already vitalized block.
191
+ *
192
+ * For better perfomance, it should be last filter.
193
+ */
194
+ filters.push(function (obj, id) {
195
+ var ids = obj.block.data('evil-blocks');
196
+ if ( !ids ) {
197
+ ids = [];
198
+ } else if ( ids.indexOf(id) != -1 ) {
199
+ return false;
200
+ }
201
+ ids.push(id);
202
+ obj.block.data('evil-blocks', ids);
203
+ });
204
+
205
+ /**
206
+ * Create `this.$()` as alias for `this.block.find()`
207
+ */
208
+ filters.push(function (obj) {
209
+ obj.$ = function (subselector) {
210
+ return obj.block.find(subselector);
211
+ };
212
+ });
213
+
214
+ /**
215
+ * Create properties for each element with `data-role`.
216
+ */
217
+ filters.push(function (obj) {
218
+ obj.block.find('[data-role]').each(function (_, el) {
219
+ var roles = el.attributes['data-role'].value.split(' ');
220
+ for ( var i = 0; i < roles.length; i++ ) {
221
+ var role = roles[i];
222
+ if ( !obj[role] ) obj[role] = $();
223
+ if ( obj[role].jquery ) obj[role].push(el);
224
+ }
225
+ });
226
+ });
227
+
228
+ /**
229
+ * Syntax sugar to listen block events.
230
+ */
231
+ filters.push(function (obj) {
232
+ for ( var name in obj ) {
233
+ if ( name.substr(0, 3) != 'on ' ) continue;
234
+
235
+ var events = name.substr(3);
236
+ var callback = obj[name];
237
+ delete obj[name];
238
+
239
+ (function (events, callback) {
240
+ obj.block.on(events, function (e) {
241
+ if ( e.currentTarget == e.target ) {
242
+ callback.apply(obj, arguments);
243
+ }
244
+ });
245
+ })(events, callback);
246
+ }
247
+ });
248
+
249
+ /**
250
+ * Smart `load on window` listener, which fire immediately
251
+ * if page was already loaded.
252
+ */
253
+ filters.push(function (obj) {
254
+ var name = 'load on window';
255
+ var callback = obj[name];
256
+
257
+ if ( !callback ) return;
258
+ delete obj[name];
259
+
260
+ if ( loaded ) {
261
+ setTimeout(function () {
262
+ callback.call(obj, loaded);
263
+ }, 1);
264
+ } else {
265
+ $window.load(function (event) {
266
+ callback.call(obj, event);
267
+ });
268
+ }
269
+ });
270
+
271
+ /**
272
+ * Syntax sugar to listen window and body events.
273
+ */
274
+ filters.push(function (obj) {
275
+ for ( var name in obj ) {
276
+ var elem = false;
277
+ if ( endsWith(name, 'on body') ) {
278
+ elem = $('body');
279
+ } else if ( endsWith(name, 'on window') ) {
280
+ elem = $window;
281
+ }
282
+
283
+ if ( !elem ) continue;
284
+
285
+ var event = name.split(' on ')[0];
286
+ var callback = obj[name];
287
+ delete obj[name];
288
+
289
+ (function (elem, event, callback) {
290
+ elem.on(event, function () {
291
+ callback.apply(obj, arguments);
292
+ });
293
+ })(elem, event, callback);
294
+ }
295
+ });
296
+
297
+ /**
298
+ * Syntax sugar to listen element events.
299
+ */
300
+ filters.push(function (obj) {
301
+ for ( var name in obj ) {
302
+ var parts = name.split(' on ');
303
+ if ( !parts[1] ) continue;
304
+
305
+ var callback = obj[name];
306
+ delete obj[name];
307
+
308
+ (function (parts, callback) {
309
+ obj.block.on(parts[0], parts[1], function (e) {
310
+ e.el = $(this);
311
+ callback.apply(obj, arguments);
312
+ });
313
+ })(parts, callback);
314
+ }
315
+ });
316
+
259
317
  /*
260
318
  * Run all blocks on load.
261
319
  */
262
320
  $(document).ready(function () {
263
- setTimeout(function () {
264
- ready = true;
265
- evil.block.vitalize();
266
- }, 1);
321
+ ready = true;
322
+ evil.block.vitalize();
267
323
  });
268
324
 
269
- })(jQuery);
325
+ })(jQuery, window);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evil-blocks-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Sitnik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-05 00:00:00.000000000 Z
11
+ date: 2014-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sprockets