evil-blocks-rails 0.5.1 → 0.6.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
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