evil-blocks-rails 0.4.2 → 0.5.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: 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.