evil-blocks-rails 0.4.2 → 0.5.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: 042b8793f4b21ddc3537ffeedb52bf385f5151cb
4
- data.tar.gz: 1cdcd05204062ec41c691b866b73bcd99b2b499d
3
+ metadata.gz: d18be5dfc85ca1bdff5b9748f2a033f09ab24a81
4
+ data.tar.gz: 58bc3e5399b808b2e8025c91df312e2472d34943
5
5
  SHA512:
6
- metadata.gz: f2af591d5da0d690ff751c001d82fe037b5ead54fc9fd1bb0139a39b37e185e66a51037d2e01a2edf388876aef0944a9e5c52bc17f6abc829a34b9c39e607492
7
- data.tar.gz: f68b5e4e6b15be19ddfe42b3c9d67f0043a71268c5107b873847d85cb783797a9d36e0b7285f018763058164daf4e4350c3d74adcacea90e0eadaccdde8534dc
6
+ metadata.gz: 7bc25d378340da42e2533f0b1eb7baf366a7f74b76f74a44093ffee067442cad9036e143d30d28779f9259b1468cd4a7f1868ab0f5cbcbbdbdc496857e983d4b
7
+ data.tar.gz: 4daffdd46ff66ec0f9a133c4c9186e5220858d140848a0153f37e7d7d9ed7a35eabd1f46085e1784229ce0e8239925da4d87e26ea9469a88868f61edfaa933c3
@@ -0,0 +1,37 @@
1
+ ## 0.5 (RDS-1, 29th August 1949)
2
+ * Current event target was moved from first argument to `event.el`.
3
+ * Inside finder was moved from `@(selector)` to `@$(selector)`.
4
+ * Remove old function style API.
5
+ * Add `@@block` alias.
6
+ * Add debugger extension.
7
+ * Vitalize blocks on next tick after page ready.
8
+ * Don’t vitalize blocks twice.
9
+ * Method `evil.block.vitalize()` calls on `document` by default.
10
+ * Allow to use GitHub master in Bundler.
11
+ * Add Bower support.
12
+
13
+ ## 0.4.2 (Zebra, 14th May 1948)
14
+ * Don’t listen bubbled events as block event.
15
+ * Change license to MIT.
16
+
17
+ ## 0.4.1 (Yoke, 30th April 1948)
18
+ * Allow to listen body and window events from object style.
19
+
20
+ ## 0.4 (X-Ray, 14th April 1948)
21
+ * Add new object style.
22
+
23
+ ## 0.3.2 (Helen of Bikini, 25th July 1946)
24
+ * Fix searching elements inside several blocks (by Andrei Miroshnik).
25
+
26
+ ## 0.3.1 (Gilda, 1st July 1946)
27
+ * Fix in multiple block copies on one page (by Andrei Miroshnik).
28
+
29
+ ## 0.3 (Fat Man, 9th August 1945)
30
+ * Add shortcut to Slim to set data-role and class.
31
+ * Run callback on every block copy in page.
32
+
33
+ ## 0.2 (Little Boy, 6th August 1945)
34
+ * Support non-Rails applications in gem.
35
+
36
+ ## 0.1 (The Gadget, 16th July 1945)
37
+ * Initial release.
data/README.md CHANGED
@@ -1,122 +1,388 @@
1
1
  # Evil Blocks
2
2
 
3
- Evil Block is a tiny framework for web pages. It splits your application
4
- to separated blocks with isolated styles and scripts.
3
+ Evil Block is a tiny JS framework for web pages. It is based on 4 ideas:
5
4
 
6
- Sponsored by [Evil Martians](http://evilmartians.com/).
7
- Role aliases taken from [role](https://github.com/kossnocorp/role)
8
- and [role-rails](https://github.com/kossnocorp/role-rails) by Sasha Koss.
5
+ * **Split code to independent blocks.** “Divide and rule” is always good idea.
6
+ * **Blocks communicate by events.** Events is easy and safe method to clean
7
+ very complicated dependencies between controls.
8
+ * **Separate JS and CSS.** You use classes only for styles and bind JS
9
+ by selectors with special attributes. So you can update your styles without
10
+ fear to break your scripts.
11
+ * **Try not to render on client.** 2 way data-binding looks very cool,
12
+ but it has [big price]. Most of web pages (instead of web applications)
13
+ can render all HTML on server and use client rendering only in few small
14
+ places. Without rendering we can incredibly clean code and architecture.
15
+
16
+ See also [Evil Front], a pack of helpers for Ruby on Rails and Evil Blocks.
17
+
18
+ Sponsored by [Evil Martians]. Role aliases was taken from [Role.js].
19
+
20
+ [Role.js]: https://github.com/kossnocorp/role
21
+ [big price]: http://staal.io/blog/2014/02/05/2-way-data-binding-under-the-microscope/
22
+ [Evil Front]: https://github.com/ai/evil-front
23
+ [Evil Martians]: http://evilmartians.com/
9
24
 
10
25
  ## Quick Example
11
26
 
12
- ### Slim Template
27
+ Slim template:
28
+
29
+ ```haml
30
+ .todo-control@@todo
31
+ ul@tasks
32
+
33
+ - @tasks.each do |task|
34
+ .task@task
35
+ = task.name
36
+ form@finishForm action="/tasks/#{ task.id }/finish"
37
+ input type="submit" value="Finish"
38
+
39
+ form@addForm action="/tasks/"
40
+ input type="text" name="name"
41
+ input type="submit" value="Add"
42
+ ```
43
+
44
+ Block’s CoffeeScript:
45
+
46
+ ```coffee
47
+ evil.block '@@todo',
48
+
49
+ ajaxSubmit: (e) ->
50
+ e.preventDefault()
51
+
52
+ form = e.el
53
+ form.addClass('is-loading')
54
+
55
+ $.ajax
56
+ url: form.attr('action')
57
+ data: form.serialize()
58
+ complete: -> form.addClass('is-loading')
59
+
60
+ 'submit on @finishForm': (e) ->
61
+ @ajaxSubmit(e).done ->
62
+ e.el.closest("@task").addClass("is-finished")
63
+
64
+ 'submit on @addForm': (e) ->
65
+ e.preventDefault()
66
+ @ajaxSubmit(e).done (newTaskHTML) ->
67
+ @tasks.append(newTaskHTML)
68
+ ```
69
+
70
+ ## Attributes
71
+
72
+ If you use classes selectors in CSS and JS, your scripts will be depend
73
+ on styles. If you will change `.small-button` to `.big-button`, you must
74
+ change all button’s selectors in scripts.
75
+
76
+ Separated scripts and styles are better, so Evil Blocks prefer to work with
77
+ two HTML attributes to bind your JS: `data-block` (to define blocks)
78
+ and `data-role` (to define elements inside block).
79
+
80
+ ```html
81
+ <div data-block="todo">
82
+ <ul data-role="tasks">
83
+ </ul>
84
+ </div>
85
+ ```
86
+
87
+ Evil Blocks extends Slim and jQuery, so you can use shortcuts for this
88
+ attributes: `@@block` and `@role`:
89
+
90
+ ```haml
91
+ @@todo
92
+ ul@tasks
93
+ ```
94
+
95
+ ```js
96
+ $('@tasks')
97
+ ```
98
+
99
+ With this attributes you can easily change interface style
100
+ and be sure in scripts:
13
101
 
14
102
  ```haml
15
- .user-page
16
- .title User Name
17
- .gallery-control.is-big
18
- // Evil Blocks add @data-role alias to Slim
19
- // .class to bind styles, @data-role to bind JavaScript
20
- a.gallery-left@nextPhoto href="#"
21
- a.gallery-right@prevPhoto href="#"
22
- img src="photos/1.jpg"
23
- img src="photos/2.jpg"
24
- img src="photos/3.jpg"
25
- ```
26
-
27
- ### Sass Styles
28
-
29
- ```sass
30
- // Block
31
- .user-page
32
- // All elements must be inside blocks (like in namespaces)
33
- .title
34
- font-size: 20px
35
-
36
- // Block
37
- .gallery-control
38
- position: relative
39
- img, .gallery-left, .gallery-right
40
- position: absolute
41
- // Modificator
42
- &.is-big
43
- width: 600px
44
- ```
45
-
46
- ### CoffeeScript
47
-
48
- ```coffee
49
- # Will execute init only if .gallery-control is in current page
50
- evil.block '.gallery-control',
103
+ .big-button@addButton
104
+ ```
105
+
106
+ Of course, Evil Block doesn’t force you to use only this selectors.
107
+ You can any attributes, that you like.
108
+
109
+ ## Blocks
110
+
111
+ You should split your interface to independent controls and mark them
112
+ with `data-block`:
113
+
114
+ ```haml
115
+ header@@header
116
+ a.exit href="#"
117
+
118
+ .todo-control@@todo
119
+ ul.tasks
120
+
121
+ .docs-page@@docs
122
+ ```
123
+
124
+ Then you can vitalize your blocks in scripts by `evil.block` function:
125
+
126
+ ```coffee
127
+ evil.block '@@header',
128
+
129
+ init: ->
130
+ console.log('Vitalize', @block)
131
+ ```
132
+
133
+ When page will be loaded Evil Blocks finds blocks by `@@header` selector
134
+ (this is shortcut for `[data-block=header]`) and call `init` on every
135
+ founded block. So, if your page contains two headers, `init` will be called
136
+ twice with different `@block`.
137
+
138
+ Property `@block` will contain jQuery-node of current block. You can search
139
+ inside current block by `@$(selector)` method:
140
+
141
+ ```coffee
142
+ evil.block '@@docs',
143
+
144
+ init: ->
145
+ @$('a').attr(target: '_blank') # Open all links inside docs in new tab
146
+ # Same as @block.find('a')
147
+ ```
148
+
149
+ You can add any methods and properties to your block class:
150
+
151
+ ```coffee
152
+ evil.block '@@gallery',
51
153
  current: 0
52
154
 
53
155
  showPhoto: (num) ->
54
- @('img').hide().
156
+ @$('img').hide().
55
157
  filter("eql(#{ num })").show()
56
158
 
57
159
  init: ->
58
160
  @showPhoto(@current)
161
+ ```
162
+
163
+ Evil Blocks will automatically create properties with jQuery-nodes
164
+ for every element inside block with `data-role` attribute:
165
+
166
+ ```haml
167
+ .todo-control@@todo
168
+ ul.tasks@tasks
169
+ ```
170
+
171
+ ```coffee
172
+ evil.block '@@todo',
173
+
174
+ addTask: (task) ->
175
+ @tasks.append(task)
176
+ ```
177
+
178
+ If you add new HTML by AJAX, you can vitalize new blocks by
179
+ `evil.block.vitalize()`. This function will vitalize only new blocks in
180
+ document.
181
+
182
+ ```coffee
183
+ @sections.append(html)
184
+ evil.block.vitalize()
185
+ ```
186
+
187
+ ## Events
188
+
189
+ You can bind listeners to events inside block by `"events on selectors"` method:
190
+
191
+ ```coffee
192
+ evil.block '@@todo',
193
+
194
+ 'submit on @finishForm': ->
195
+ # Event listener
196
+ ```
197
+
198
+ More difficult example:
199
+
200
+ ```coffee
201
+ evil.block '@@form',
202
+ ajaxSearch: -> …
59
203
 
60
- 'click on @nextPhoto', (link, event) ->
61
- @showPhoto(current += 1)
204
+ 'change, keyup on input, select': (event) ->
205
+ field = event.el()
206
+ @ajaxSearch('Changed', field.val())
207
+ ```
208
+
209
+ Listener will receive jQuery Event object as first argument.
210
+ Current element (`this` in jQuery listeners) will be in `event.el` property.
211
+ All listeners are delegated on current block, so `click on @button` will be
212
+ equal to `@block.on 'click', '@button', ->`.
62
213
 
63
- 'on start-slideshow', ->
64
- # You can communicate between blocks by simple events
65
- setTimeout( => @nextPhoto.click() , 5000)
214
+ You should prevent default event behavior by `event.preventDefault()`,
215
+ `return false` will not do anything in block’s listeners. I recommend
216
+ [evil-front/links] to prevent default behavior in any links with `href="#"`
217
+ to clean your code.
66
218
 
67
- # Will execute init only on user page, where .user-page exists
68
- evil.block '.user-page',
219
+ You can also bind events on body and window:
220
+
221
+ ```coffee
222
+ evil.blocks '@@docs',
223
+ recalcMenu: -> …
224
+ openPage: -> …
69
225
 
70
226
  init: ->
71
- @('.gallery-control').trigger('start-slideshow')
72
- ```
73
-
74
- ## Styles
75
-
76
- * You split all you tags to “blocks” and “elements”. Elements must belong
77
- to block. Blocks can be nested.
78
- * Blocks classes must have special suffix, that can’t be accidentally used
79
- in elements. For example. `-page`, `-layout`, `-control` suffixes
80
- (for example, `.user-page`, `.header-layout`, `.gallery-control`).
81
- * All styles must have block class in condition. For example,
82
- `.user-page .title`, `.user-page .edit`. So all you styles is protected
83
- from accidentally same classes (it’s important, if you join all your CSS files
84
- in one file).
85
- * If block can be nested in another blocks (like common controls), adds prefix
86
- to all its elements (like `.gallery-left`). If block is a root block
87
- (like `.user-page` or `.header-layout`) be free to use short classes.
88
- * Classes to modificate elements or blocks, must be like sentence without a noun
89
- (for example, `.is-big`, `.with-header`).
90
-
91
- ## JavaScript
92
-
93
- * Unobtrusive JavaScript.
94
- * Write animation and states in CSS. JavaScript just changes CSS classes.
95
- * Avoid rendering. Send from server HTML, not JSON.
96
- * Split JS by widgets. Describe widget class in `evil.block(selector, klass)`.
97
- It will create `klass` instance and call `init` method for each selectors,
98
- which exist in current page. So you can be free to join all JS files in one.
99
- * Describe events by `EVENTS on SELECTORS` methods
100
- (like `keyup submit on @name, @family`). This methods save this and
101
- receive jQuery node in first argument and event in second.
102
- * Bind JavaScript to `data-role` attribute to be free to change styles
103
- and classes without dangeros of breaking scripts.
104
- * Every tag with `data-role` will by as property in object with jQuery node.
105
- * If you need to find elements inside block, use `@(selector)` function.
106
- * If you need to communicate between blocks, use custom events and create
107
- block events listeners by `on EVENTS` method. It will receive events object
108
- as first argument and event parameters as next arguments.
227
+ @recalcMenu()
228
+
229
+ 'resize on window': ->
230
+ @recalcMenu()
231
+
232
+ 'hashchange on window': ->
233
+ @openPage(location.hash)
234
+ ```
235
+
236
+ [evil-front/links]: https://github.com/ai/evil-front/blob/master/evil-front/lib/assets/javascripts/evil-front/links.js
237
+
238
+ ## Blocks Communications
239
+
240
+ Blocks should communicates by custom jQuery events. You can bind event listener
241
+ to block node by `"on events"` method:
242
+
243
+ ```coffee
244
+ evil.block '@@slideshow', ->
245
+ nextSlide: ->
246
+
247
+ 'on play': ->
248
+ @timer = setInterval(=> @nextSlide, 5000)
249
+
250
+ 'on stop': ->
251
+ clearInterval(@timer)
252
+
253
+ evil.block '@@video', ->
254
+
255
+ 'click on @fullscreenButton': ->
256
+ $('@@slideshow').trigger('stop')
257
+ ```
258
+
259
+ If you want to use broadcast messages, you can use custom events on body:
260
+
261
+ ```coffee
262
+ evil.block '@@callUs', ->
263
+
264
+ 'change-city on body': (e, city) ->
265
+ @phoneNumber.text(city.phone)
266
+
267
+ evil.block '@@cityChanger', ->
268
+ getCurrentCity: -> …
269
+
270
+ 'change on @citySelect': ->
271
+ $('body').trigger('change-city', @getCurrentCity())
272
+ ```
273
+
274
+ ## Rendering
275
+
276
+ If you will render on client and on server-side, you must repeat helpers, i18n,
277
+ templates. Client rendering requires a lot of libraries and architecture.
278
+ 2-way data binding looks cool, but has very [big price] in performance,
279
+ templates, animation and overengeniring.
280
+
281
+ If you develop web page (not web application with offline support, etc),
282
+ server-side rendering will be more useful. Users will see your interface
283
+ imminently, search engines will index your content and your code will be much
284
+ simple and clear.
285
+
286
+ In most of cases you can avoid client rendering. If you need to add some block
287
+ by JS, you can render it hidden to page HTML and show in right time:
288
+
289
+ ```coffee
290
+ evil.block '@@comment',
291
+
292
+ 'click on @addCommentButton': ->
293
+ @newCommentForm.slideDown()
294
+ ```
295
+
296
+ If user change some data and you need to update view, you anyway need to send
297
+ request to save new data on server. Just ask server to render new view.
298
+ For example, on new comment server can return new comment HTML:
299
+
300
+ ```coffee
301
+ evil.block '@@comment',
302
+
303
+ 'submit on @addCommentForm': ->
304
+ $.post '/comments', @addCommentForm.serialize(), (newComment) ->
305
+ @comments.append(newComment)
306
+ ```
307
+
308
+ But, of course, some cases require client rendering. Evil Blocks only recommend
309
+ to do it server-side, but not force you:
310
+
311
+ ```coffee
312
+ evil.block '@@comment',
313
+
314
+ 'change, keyup on @commentField', ->
315
+ html = JST['comment'](text: @commentField.text())
316
+ @preview.html(html)
317
+ ```
318
+
319
+ [big price]: http://staal.io/blog/2014/02/05/2-way-data-binding-under-the-microscope/
320
+
321
+ ## Modules
322
+
323
+ If your blocks has same behavior, you can create module-block and set
324
+ multiple blocks on same tag:
325
+
326
+ ```haml
327
+ @popup@@closable
328
+ a@closeLink href="#"
329
+ ```
330
+
331
+ ```coffee
332
+ evil.block '@@closable', ->
333
+
334
+ 'click on @closeLink': ->
335
+ @block.trigger('close')
336
+
337
+ evil.block '@@popup', ->
338
+
339
+ 'on close': ->
340
+ @clock.removeClass('is-open')
341
+ ```
342
+
343
+ If you want to use same methods inside multiple block, you can create
344
+ inject-function:
345
+
346
+ ```coffee
347
+ fancybox = (obj) ->
348
+ for name, value of fancybox.module
349
+ obj[name] = value
350
+ # Initializer code
351
+
352
+ fancybox.module =
353
+ openInFancybox: (node) ->
354
+
355
+ evil.block '@@docs',
356
+
357
+ init: ->
358
+ fancybox(@)
359
+
360
+ 'click on @showExampleButton': ->
361
+ @openInFancybox(@example)
362
+ ```
363
+
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
+ ```
109
373
 
110
374
  ## Install
111
375
 
112
376
  ### Ruby on Rails
113
377
 
114
378
  Add `evil-block-rails` gem to `Gemfile`:
379
+
115
380
  ```ruby
116
381
  gem "evil-blocks-rails"
117
382
  ```
118
383
 
119
384
  Load `evil-blocks.js` in your script:
385
+
120
386
  ```js
121
387
  //= require evil-blocks
122
388
  ```
@@ -125,16 +391,19 @@ Load `evil-blocks.js` in your script:
125
391
 
126
392
  If you use Sinatra or other non-Rails framework you can add Evil Blocks path
127
393
  to Sprockets environment:
394
+
128
395
  ```ruby
129
396
  EvilBlocks.install(sprockets)
130
397
  ```
131
398
 
132
- And change Slim options to support `@data-rule` shortcut:
399
+ And change Slim options to support `@@block` and `@rule` shortcuts:
400
+
133
401
  ```ruby
134
402
  EvilBlocks.install_to_slim!
135
403
  ```
136
404
 
137
405
  Then just load `evil-blocks.js` in your script:
406
+
138
407
  ```js
139
408
  //= require evil-blocks
140
409
  ```
@@ -142,8 +411,3 @@ Then just load `evil-blocks.js` in your script:
142
411
  ### Others
143
412
 
144
413
  Add file `lib/evil-blocks.js` to your project.
145
-
146
- ## See Also
147
-
148
- * [Evil Front](https://github.com/ai/evil-front) – pack of helpers and libraries
149
- for Ruby on Rails and Evil Blocks.
@@ -6,20 +6,21 @@ module EvilBlocks
6
6
  # Copy from role-rails by Sasha Koss.
7
7
  # https://github.com/kossnocorp/role-rails
8
8
  shortcut = Slim::Parser.default_options[:shortcut]
9
- shortcut['@'] = { :attr => 'data-role' }
10
- shortcut['@.'] = { :attr => ['data-role', 'class'] }
11
- Slim::Engine.default_options[:merge_attrs]['data-role'] = ' '
9
+ shortcut['@'] = { attr: 'data-role' }
10
+ shortcut['@@'] = { attr: 'data-block' }
11
+ Slim::Engine.default_options[:merge_attrs]['data-role'] = ' '
12
+ Slim::Engine.default_options[:merge_attrs]['data-block'] = ' '
12
13
  end
13
14
 
14
15
  # Add assets paths to standalone Sprockets environment.
15
16
  def self.install(sprockets)
16
- sprockets.append_path(Pathname(__FILE__).dirname.join('assets/javascripts'))
17
+ sprockets.append_path(Pathname(__FILE__).dirname)
17
18
  end
18
19
 
19
20
  if defined? ::Rails
20
- # Tell Ruby on Rails to add `evil-block.js` to Rails Admin load paths.
21
21
  class Engine < ::Rails::Engine
22
- initializer 'evil-front.slim' do
22
+ initializer 'evil-blocks' do
23
+ EvilBlocks.install(Rails.application.assets)
23
24
  EvilBlocks.install_to_slim! if defined?(Slim::Parser)
24
25
  end
25
26
  end
@@ -0,0 +1,29 @@
1
+ ;(function () {
2
+ "use strict";
3
+
4
+ var log = function () {
5
+ if ( !console || !console.log ) {
6
+ return;
7
+ }
8
+ console.log.apply(console, arguments);
9
+ };
10
+
11
+ if ( !window.evil || !window.evil.block ) {
12
+ log("You should include evil-blocks.debug.js after evil-blocks.js");
13
+ return;
14
+ }
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);
23
+ }
24
+ log.apply(this, messages);
25
+
26
+ callback.apply(this, arguments);
27
+ };
28
+ }
29
+ })();
@@ -15,7 +15,9 @@
15
15
 
16
16
  context[name] = function () {
17
17
  arguments[pos] = arguments[pos].replace(
18
- /@([\w\u00c0-\uFFFF\-]+)/g, '[data-role~="$1"]');
18
+ /@@([\w\u00c0-\uFFFF\-]+)/g, '[data-block~="$1"]');
19
+ arguments[pos] = arguments[pos].replace(
20
+ /@([\w\u00c0-\uFFFF\-]+)/g, '[data-role~="$1"]');
19
21
  return original.apply(context, arguments);
20
22
  };
21
23
 
@@ -34,20 +36,48 @@
34
36
  window.evil = { };
35
37
  }
36
38
 
37
- /**
38
- * Evil blocks list.
39
- */
40
- var vitalizers = [];
41
-
42
39
  /**
43
40
  * If onready event is already happend.
44
41
  */
45
42
  var ready = false;
46
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
+
47
77
  /**
48
78
  * Execute `callback` on every finded `selector` inside `base`.
49
79
  */
50
- var vitalize = function (base, selector, callback) {
80
+ var vitalize = function (base, selector, klass) {
51
81
  var blocks = $().add( base.filter(selector) ).
52
82
  add( base.find(selector) );
53
83
 
@@ -55,10 +85,19 @@
55
85
  return;
56
86
  }
57
87
 
88
+ var inits = [];
89
+
58
90
  for ( var i = 0; i < blocks.length; i++ ) {
59
91
  var block = $(blocks[i]);
60
92
 
61
- var b = (function (block) {
93
+ if ( block.data('evil-vitalized') ) {
94
+ continue;
95
+ }
96
+ block.data('evil-vitalized', true);
97
+
98
+ var obj = { };
99
+
100
+ obj.$ = (function (block) {
62
101
  return function (subselector) {
63
102
  return $(subselector, block);
64
103
  };
@@ -68,99 +107,61 @@
68
107
  block.find('[data-role]').each(function (_, el) {
69
108
  var roles = el.attributes['data-role'].value.split(' ');
70
109
  for ( var i = 0; i < roles.length; i++ ) {
71
- var role = roles[i].replace(/-\w/g, function (s) {
72
- return s[1].toUpperCase();
73
- });
74
- if ( !actives[role] ) {
75
- actives[role] = [];
110
+ var role = roles[i];
111
+ if ( !obj[role] ) {
112
+ obj[role] = $();
76
113
  }
77
- actives[role].push(el);
114
+ obj[role].push(el);
78
115
  }
79
116
  });
80
117
 
81
- for ( var role in actives ) {
82
- b[role] = b(actives[role]);
83
- }
118
+ obj.block = block;
84
119
 
85
- var inits = callback($, b, block);
86
- if ( typeof(inits) == 'object' ) {
87
- for ( var init in inits ) {
88
- inits[init]($, b, block);
120
+ var event = function (type, name, prop) {
121
+ var result = callbacks[type](obj, prop);
122
+ if ( window.evil.block.eventFilter ) {
123
+ result = window.evil.block.eventFilter(result, obj, name);
89
124
  }
90
- }
91
- }
92
- };
93
-
94
- /**
95
- * Create callback wrapper for block listener.
96
- */
97
- var blockCallback = function (self, func) {
98
- return function (e) {
99
- if ( e.currentTarget == e.target ) {
100
- func.apply(self, arguments);
101
- }
102
- }
103
- };
104
-
105
- /**
106
- * Create callback wrapper for body/document listener.
107
- */
108
- var globalCallback = function (self, func) {
109
- return function () {
110
- func.apply(self, arguments);
111
- }
112
- };
113
-
114
- /**
115
- * Create callback wrapper for element listener.
116
- */
117
- var elemCallback = function (self, func) {
118
- return function () {
119
- var args = Array.prototype.slice.call(arguments);
120
- var el = $(this);
121
- args.unshift(el);
122
- func.apply(self, args);
123
- }
124
- };
125
-
126
- /**
127
- * Convert block class to callback.
128
- */
129
- var convert = function (klass) {
130
- return function ($, obj, block) {
131
- obj.block = block;
125
+ return result;
126
+ };
132
127
 
133
128
  for ( var name in klass ) {
134
129
  var prop = klass[name];
135
130
 
136
- (function (name, prop) {
137
- if ( name.indexOf('on ') == -1 ) {
138
- obj[name] = prop;
139
- return;
140
- }
131
+ if ( name.indexOf('on ') == -1 ) {
132
+ obj[name] = prop;
133
+ continue;
134
+ }
141
135
 
136
+ (function (name, prop) {
142
137
  var parts = name.split(' on ');
143
138
 
144
139
  if ( parts[1] == 'body' ) {
145
- $('body').on(parts[0], globalCallback(obj, prop));
140
+ $('body').on(parts[0], event('global', name, prop));
146
141
 
147
142
  } else if ( parts[1] == 'window' ) {
148
- $(window).on(parts[0], globalCallback(obj, prop));
143
+ $(window).on(parts[0], event('global', name, prop));
149
144
 
150
145
  } else if ( parts[1] ) {
151
- block.on(parts[0], parts[1], elemCallback(obj, prop));
146
+ block.on(parts[0], parts[1], event('elem', name, prop));
152
147
 
153
148
  } else {
154
- block.on(parts[0], blockCallback(obj, prop));
149
+ block.on(parts[0], event('block', name, prop));
155
150
  }
156
151
  })(name, prop);
157
152
  }
158
153
 
159
- if ( typeof(obj.init) == 'function' ) {
160
- obj.init();
161
- }
154
+ inits.push(obj);
162
155
  }
163
- }
156
+
157
+ if ( klass.init ) {
158
+ return function () {
159
+ for ( var i = 0; i < inits.length; i++ ) {
160
+ klass.init.apply(inits[i]);
161
+ }
162
+ };
163
+ }
164
+ };
164
165
 
165
166
  /**
166
167
  * Create object for every `selector` finded in page and call their
@@ -172,8 +173,8 @@
172
173
  * delete: ->
173
174
  * @deleteForm.submit ->
174
175
  * $('user-status').trigger('deleted')
175
- * 'click on @deleleLink': (link) ->
176
- * link.addClass('is-loading')
176
+ * 'click on @deleleLink': (e) ->
177
+ * e.el.addClass('is-loading')
177
178
  * delete()
178
179
  * 'on update': ->
179
180
  * location.reload()
@@ -181,7 +182,7 @@
181
182
  * Every `data-role="aName"` in HTML will create in object `aName` property
182
183
  * with jQuery node.
183
184
  *
184
- * To bind delegate listener just create `on EVENT on SELECTOR` method.
185
+ * To bind delegate listener just create `EVENT on SELECTOR` method.
185
186
  * In first argument it will receive jQuery node of `e.currentTarget`,
186
187
  * second will be event object and others will be parameters.
187
188
  *
@@ -198,16 +199,25 @@
198
199
  * # init method
199
200
  */
200
201
  window.evil.block = function (selector, vitalizer) {
201
- if ( typeof(vitalizer) != 'function' ) {
202
- vitalizer = convert(vitalizer)
202
+ if ( typeof(vitalizer) == 'function' ) {
203
+ vitalizer = { init: vitalizer };
203
204
  }
204
- vitalizers.push([selector, vitalizer]);
205
+
206
+ window.evil.block.vitalizers.push([selector, vitalizer]);
205
207
 
206
208
  if ( ready ) {
207
- vitalize($(document), selector, vitalizer);
209
+ var init = vitalize($(document), selector, vitalizer);
210
+ if ( init ) {
211
+ init();
212
+ }
208
213
  }
209
214
  };
210
215
 
216
+ /**
217
+ * Evil blocks list.
218
+ */
219
+ window.evil.block.vitalizers = [];
220
+
211
221
  /**
212
222
  * Vitalize all current blocks inside base. You must call it on every
213
223
  * new content from AJAX.
@@ -217,11 +227,22 @@
217
227
  * evil.block.vitalize $(comments).applyTo(@comments)
218
228
  */
219
229
  window.evil.block.vitalize = function (base) {
220
- base = $(base);
230
+ if ( base ) {
231
+ base = $(base);
232
+ } else {
233
+ base = $(document);
234
+ }
235
+
236
+ var inits = [];
237
+ for ( var i = 0; i < window.evil.block.vitalizers.length; i++ ) {
238
+ var vitalizer = window.evil.block.vitalizers[i];
239
+ inits.push( vitalize(base, vitalizer[0], vitalizer[1]) );
240
+ }
221
241
 
222
- for ( var i = 0; i < vitalizers.length; i++ ) {
223
- var vitalizer = vitalizers[i];
224
- vitalize(base, vitalizer[0], vitalizer[1]);
242
+ for ( var i = 0; i < inits.length; i++ ) {
243
+ if ( inits[i] ) {
244
+ inits[i]();
245
+ }
225
246
  }
226
247
  };
227
248
 
@@ -229,8 +250,10 @@
229
250
  * Run all blocks on load.
230
251
  */
231
252
  $(document).ready(function () {
232
- ready = true;
233
- evil.block.vitalize(document);
253
+ setTimeout(function () {
254
+ ready = true;
255
+ evil.block.vitalize();
256
+ }, 1);
234
257
  });
235
258
 
236
259
  })(jQuery);
metadata CHANGED
@@ -1,45 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evil-blocks-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
- - Andrey "A.I." Sitnik
7
+ - Andrey Sitnik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-01 00:00:00.000000000 Z
11
+ date: 2014-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sprockets
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
- description: Evil Block is a tiny framework for web pages. It split your application
28
- to separated blocks and isolate their styles and scripts.
29
- email:
30
- - andrey@sitnik.ru
27
+ description:
28
+ email: andrey@sitnik.ru
31
29
  executables: []
32
30
  extensions: []
33
31
  extra_rdoc_files:
34
32
  - LICENSE
35
33
  - README.md
36
- - ChangeLog
34
+ - ChangeLog.md
37
35
  files:
38
- - lib/assets/javascripts/evil-blocks.js
39
- - lib/evil-blocks-rails.rb
36
+ - ChangeLog.md
40
37
  - LICENSE
41
38
  - README.md
42
- - ChangeLog
39
+ - lib/evil-blocks-rails.rb
40
+ - lib/evil-blocks.debug.js
41
+ - lib/evil-blocks.js
43
42
  homepage: https://github.com/ai/evil-blocks
44
43
  licenses:
45
44
  - MIT
@@ -50,18 +49,18 @@ require_paths:
50
49
  - lib
51
50
  required_ruby_version: !ruby/object:Gem::Requirement
52
51
  requirements:
53
- - - '>='
52
+ - - ">="
54
53
  - !ruby/object:Gem::Version
55
54
  version: '0'
56
55
  required_rubygems_version: !ruby/object:Gem::Requirement
57
56
  requirements:
58
- - - '>='
57
+ - - ">="
59
58
  - !ruby/object:Gem::Version
60
59
  version: '0'
61
60
  requirements: []
62
61
  rubyforge_project:
63
- rubygems_version: 2.0.3
62
+ rubygems_version: 2.2.0
64
63
  signing_key:
65
64
  specification_version: 4
66
- summary: Tiny framework for web pages to split your app to separated blocks
65
+ summary: Tiny JS framework for web pages to split your app to independent blocks
67
66
  test_files: []
data/ChangeLog DELETED
@@ -1,24 +0,0 @@
1
- == 0.4.2 (Zebra, 14th May 1948)
2
- * Don’t listen bubbled events as block event.
3
-
4
- == 0.4.1 (Yoke, 30th April 1948)
5
- * Allow to listen body and window events from object style.
6
-
7
- == 0.4 (X-Ray, 14th April 1948)
8
- * Add new object style.
9
-
10
- == 0.3.2 (Helen of Bikini, 25th July 1946)
11
- * Fix searching elements inside several blocks (by Andrei Miroshnik).
12
-
13
- == 0.3.1 (Gilda, 1st July 1946)
14
- * Fix in multiple block copies on one page (by Andrei Miroshnik).
15
-
16
- == 0.3 (Fat Man, 9th August 1945)
17
- * Add shortcut to Slim to set data-role and class.
18
- * Run callback on every block copy in page.
19
-
20
- == 0.2 (Little Boy, 6th August 1945)
21
- * Support non-Rails applications in gem.
22
-
23
- == 0.1 (The Gadget, 16th July 1945)
24
- * Initial release.