mizugumo 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,15 @@
1
- Copyright (c) 2011 Evan Dorn
1
+ Copyright (c) 2011 Evan Dorn and Logical Reality Design
2
+
3
+ Includes a copy of NinjaScript, itself copyright (c) 2010-2011 Judson Lester and Logical Reality Design
4
+
5
+ Includes a copy of jQuery 1.4.2, Copyright 2010 John Resig
6
+ Dual licensed under the MIT or GPL Version 2 licenses.
7
+ http://jquery.org/license
8
+
9
+ Includes a copy of a jQuery-1.4.2 - compatible implementation of
10
+ Rails 3's 'rails.js', available from: https://gist.github.com/704387
11
+
12
+ --------------------------------------------------------------------
2
13
 
3
14
  Permission is hereby granted, free of charge, to any person obtaining
4
15
  a copy of this software and associated documentation files (the
@@ -6,11 +6,21 @@ Mizugumo is a gem designed to provide Rails with JavaScript and AJAX behavior th
6
6
  * gracefully-degrading: defaults to fully functional and
7
7
  sensible page-reload behavior when javascript is not available.
8
8
 
9
+ See a demo running at:
10
+ http://mizugumo-demo.lrdesign.com
11
+ Or download the code for the demo at:
12
+ https://github.com/LRDesign/mizugumo_demo
13
+
9
14
  Mizugumo uses NinjaScript by Judson Lester to provide unobtrusive JS behaviors. For more info, see:
10
15
  git@github.com:LRDesign/NinjaScript.git
11
16
 
12
- Mizugumo is currently in development and is not ready for public use. We
13
- are expecting a release later this week (note written 1/26/2010).
17
+ == WARNINGS
18
+
19
+ In the current (early release) version, Mizugumo and NinjaScript have some limitations. Notably, in this version they will clobber the normal rails.js and prevent its behaviors from
20
+ working. The installer will copy a jQuery-compatible version of rails.js that you should
21
+ use instead if you depend on Rails' default (semi-obtrusive) approaches to AJAX, link_to with :confirm =>'Are you sure?' and similar things working.
22
+
23
+ In addition, NinjaScript currently requires jQuery 1.4.2. It is NOT compatible with the current version of jQuery, 1.4.4, or the development version 1.5. We are eagerly working on a fix.
14
24
 
15
25
  == Features
16
26
 
@@ -51,6 +61,53 @@ NOTE: If you still have javascript_include_tag :defaults, you probably want to
51
61
  Ninjascript is meant to work with jQuery, and we don't know what happens if you try to run it
52
62
  side-by-side with Prototype.
53
63
 
64
+ == Graceful degradation of :method => 'delete' (or PUT, or POST) links
65
+
66
+ Rails' concept of REST runs headlong into the desire to make a site degrade
67
+ gracefully. Specifically, the link_to() helper, when passed a method other
68
+ than 'get', outputs a link with a data-method attribute that won't work
69
+ when JS is absent. To wit: link_to('Delete Item', @item, :method => 'delete') in rails outputs this:
70
+
71
+ <a href='/items/1' data-method='delete'>Delete Item</a>
72
+
73
+ When JS isn't available, this delete link acts as a show link. This is bad,
74
+ bad, bad. What's worse, Rails' scaffold generator makes these exact links
75
+ ubiquitous.
76
+
77
+ When Mizugumo is installed, that same helper instead outputs this:
78
+
79
+ <form action='/items/1' class="mizugumo_graceful_form">
80
+ <input type='hidden' name='_method' value='delete'>
81
+ <input type='submit' value='Delete Item'>
82
+ </form>
83
+
84
+ This gives the user a button instead of a form ... but it works without JS
85
+ running. Then, to give JS users the behavior the developer intended,
86
+ Mizugumo appends this to your application.js:
87
+
88
+ Ninja.behavior({
89
+ '.mizugumo_graceful_form': Ninja.becomesLink
90
+ })
91
+
92
+ This NinjaScript behavior converts that form back into a link. The JS user
93
+ sees no difference whatsoever, but the link at least *works* for the non-JS
94
+ user.
95
+
96
+ This behavior will work for :method => 'put' and :method => 'post' as well,
97
+ and it supports links whose content is an image as well; creating an image
98
+ submit button in the form rather than a text submit button.
99
+
100
+ WARNING: This cycle won't work if your link has complex content with html
101
+ structure; for example if you write
102
+
103
+ link_to('some <b>content</b><img src="foo">and text', @item, :method => 'delete' )
104
+
105
+ Mizugomo will not be able to preserve all that content through the degraded
106
+ form. However, we think this is a rare case. If you aren't passing :method
107
+ to your link_to, mizugumo will ignore it entirely, so you can still create
108
+ complex GET links.
109
+
110
+
54
111
  == Scaffold generator
55
112
 
56
113
  Mizugumo ships with a scaffold generator that builds out-of-the-box AJAX/UJS scaffolds that degrade gracefully in the absence of JavaScript. To use it, add this to config/application.rb:
@@ -72,6 +129,8 @@ For HAML views:
72
129
 
73
130
  == Contributing to Mizugumo
74
131
 
132
+ * Install MizugumoDemo and make sure that its tests still pass with your version of Mizugumo!
133
+
75
134
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
76
135
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
77
136
  * Fork the project
@@ -80,8 +139,14 @@ For HAML views:
80
139
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
81
140
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
82
141
 
142
+ == Reporting Bugs
143
+
144
+ Use the GitHub issue tracker.
145
+
83
146
  == Copyright
84
147
 
85
148
  Copyright (c) 2011 Evan Dorn and Logical Reality design. See LICENSE.txt for
86
149
  further details.
87
150
 
151
+ Web Development by Logical Reality Design: http://LRDesign.com
152
+
@@ -0,0 +1,14 @@
1
+ %h1 Mizugumo &#8121; Name
2
+
3
+ %p
4
+ 'mizugumo' is Japanese for 'water-spider'. Mythologically, however, it is the
5
+ name for special wooden shoes that ninja used to walk on water and thereby approach
6
+ targets via unexpected routes.
7
+
8
+ %p
9
+ As much as the Mythbusters were not impressed (real mizugumo likely did not work
10
+ for actual water-walking, and probably only worked as snowshoes to cross marshy
11
+ terrain), we thought it an appropriate name for an unobtrusive JS behavior library.
12
+
13
+
14
+
@@ -0,0 +1,71 @@
1
+ %h1 Mizugumo &#8121; Purpose
2
+
3
+ %p
4
+ Mizugumo exists to help Rails developers quickly build apps that are both AJAXy and that degrade
5
+ gracefully when JavaScript is absent. It uses NinjaScript to provide unobtrusive javascript
6
+ behaviors.
7
+
8
+ %h2 Graceful Degradation
9
+
10
+ %h3 Why we care
11
+
12
+ %p
13
+ Rails developers are notorious for making apps that do not degrade well when JS is unavailable,
14
+ especially if the app uses AJAX for some actions. It's hard to blame anyone - generally, adding
15
+ AJAX is a bit of a pain, and adding degradation requires duplicating a lot of development.
16
+
17
+ %p
18
+ Ideally, we should put the effort into graceful degradation because we can't count on JavaScript
19
+ in real-world conditions. Some situations we care about in which JavaScript might not be
20
+ available:
21
+
22
+ %ul
23
+ %li NoScript users and other security-conscious folk
24
+ %li Screen readers for vision-impaired users
25
+ %li Search engines and spiders, which should see as accurate
26
+ %li Automation tools
27
+
28
+ %h3 How to solve the problem
29
+
30
+ %p
31
+ Because the client sees the normal, JS-enabled behavior, and because we are all busy people,
32
+ Rails developers usually won't make their sites degrade gracefully until it becomes no harder
33
+ to build in graceful degradation than not to. Mizugumo aspires to this goal.
34
+
35
+ %p
36
+ Mizugumo replaces some of rails' default behavior that depend on JS with ones that work
37
+ in the absence of JS. Specifically, link_to with a :method option other than GET actually
38
+ outputs a <form>, not an <a href='foo'>. It then provides a default javascript behavior,
39
+ written in NinjaScript, to implicitly convert those forms
40
+
41
+ %p
42
+ Mizugumo also provides a scaffold tool that gives you an out-of-the box AJAX controller
43
+ in which all actions degrade perfectly in the absence of JS. From that scaffold, it becomes
44
+ much easier to build rich behaviors into your sites while maintaining JS-less functionality.
45
+
46
+ %h2 The Philosophy of Ninja-like Unobtrusiveness
47
+
48
+ %p
49
+ The goal of Mizugumo's JS engine, NinjaScript, is to make javascript super easy to use, and
50
+ reliable for the developer &%8212; no surprises.
51
+
52
+ %3 CSS is Great, JS Sucks
53
+
54
+ %p
55
+ We take for granted CSS's super-easy behavior: specify a selector and add a style. That style
56
+ will always apply to anything that matches that selector, even if the DOM is restructured. JavaScript
57
+ is not like this, however: if we use a library (say jQuery) to bind events to a selector, event
58
+ handlers will get bound to the DOM nodes that match that selector *now*, but elements that get added
59
+ later are out of luck. We can fix event handlers with event delegation methods, but that still
60
+ doesn't help us if what we want to do is transform elements. If I write a javascript block to
61
+ add rounded corners to a bunch of elements, or to add tooltip elements to a bunch of <li> nodes,
62
+ it will only make those changes to the elements that match at the time it is run.
63
+
64
+ %p
65
+ NinjaScripts aims to make JS as much like CSS as possible: specify a behavior once - including
66
+ transformations of the elements themselves - and those behaviors will always apply to all elements
67
+ even if the elements are added to the DOM later. So you can say "all divs with class .foo
68
+ get rounded corners" or "all forms matching .mizugomo_graceful_form get replaced with
69
+ links with the text matching the submit button text" and likewise not worry about when
70
+ and how said forms get added to the DOM.
71
+
@@ -0,0 +1,45 @@
1
+ These are notes to myself to hopefully avoid this kind of suffering again.
2
+
3
+ I ran into some troubles (a few hours worth) trying to get the generators
4
+ working.
5
+
6
+ Rails wants to load generators from specific "namespaces", but parts of those
7
+ namespaces are automatically generated. In particular, when a generator is
8
+ invoked, it will generally be looked for with both a "base" and a "context".
9
+ Rails will use *one* of these two when looking, for example, it will look for:
10
+
11
+ <base>/<your generator> and <your generator>/<context>.
12
+
13
+ These are interdependent; particularly, setting up a scaffold_controller
14
+ generator will cause part of it's namespace to be assumed when looking for
15
+ the other items, like template_engine and test_framework.
16
+
17
+ For example, setting scaffold_generator to "ninja_helper:scaffold_controller"
18
+ will cause it to assume a base of "ninja_helper" and a context of "scaffold"
19
+ when you run "rails generate scaffold", which will make it look for your view
20
+ generator in "ninja_helper:erb" and "erb:scaffold", even if you don't explicitly
21
+ set template_engine to anything.
22
+
23
+ The fix for this was to specify the hooks for template_engine etc. with an
24
+ :in => :rails parameter which causes it to look in the rails namespace
25
+ (and thus retrieve the defaults) unless configs in application.rb specifically
26
+ override those generators.
27
+
28
+ Ultimately, Rails will generate eight acceptable namespaces for the
29
+ generator based on the requested name in config in application.rb
30
+ ***It must live in one of those paths on the filesystem.*** However, the
31
+ actual ruby module namespace does not need to match (seems to be irrelevant).
32
+ The Rails 'namespace' can be massaged just by setting self.namespace(blah)
33
+ in the class, for example:
34
+
35
+ self.namespace("rails:ninja_helper:erb")
36
+
37
+ This lives in generators/rails/ninja_helper/erb_generator.rb, which is
38
+ one of the acceptable namespaces/paths for a beast with this config:
39
+
40
+ config.generators do |g|
41
+ g.template_engine 'ninja_helper:erb'
42
+ end
43
+
44
+
45
+
@@ -21,8 +21,9 @@ DESC
21
21
  file = File.join("public", "javascripts", "application.js")
22
22
  append_to_file(file) do
23
23
  <<ADDITIONAL_JS
24
+
25
+ // Generated by mizugumo install script
24
26
  Ninja.behavior({
25
- // Generated by mizugumo install script
26
27
  // This line enables the NH behavior of graceful degradation of method links
27
28
  // to forms and back again
28
29
  '.mizugumo_graceful_form': Ninja.becomesLink
@@ -35,14 +36,15 @@ ADDITIONAL_JS
35
36
  say (<<NOTICE
36
37
 
37
38
  Mizugumo is installed!
38
- Remember to remove the default JS and link to the jQuery/NinjaScript
39
- script and CSS files by adding these to your application layout:
39
+ Remember to remove the default JS and link to the jQuery/NinjaScript script and CSS files by adding these to your application layout:
40
40
 
41
41
  <%= stylesheet_link_tag 'mizugumo.css' %>
42
42
  <%= javascript_include_tag 'jquery-1.4.2.js' %>
43
43
  <%= javascript_include_tag 'jquery.ninja_script.js' %>
44
+ <%= javascript_include_tag 'rails.js' %>
44
45
  <%= javascript_include_tag 'application.js' %>
45
46
 
47
+ The included rails.js is a jQuery-1.4.2 compatible implementation of rails.js, and should replace the default rails.js.
46
48
  If you want to use the Mizugumo AJAX scaffold generators, add this to your application.rb:
47
49
 
48
50
  config.generators do |g|
@@ -1,12 +1,17 @@
1
- // vim: sw=2 ft=javascript
2
-
1
+ /*
2
+ * NinjaScript - 0.8.0
3
+ * written and copyright 2010-2011 Judson Lester and Logical Reality Design
4
+ * Licensed under the MIT license
5
+ */
3
6
  Ninja = (function() {
4
- function log(message) {
5
- try {
6
- console.log(message)
7
+ function log(message) {
8
+ if(false) { //LOGGING TURNED OFF IS 100% faster!
9
+ try {
10
+ console.log(message)
11
+ }
12
+ catch(e) {} //we're in IE or FF w/o Firebug or something
13
+ }
7
14
  }
8
- catch(e) {} //we're in IE or FF w/o Firebug or something
9
- }
10
15
 
11
16
  function isArray(candidate) {
12
17
  return (candidate.constructor == Array)
@@ -69,7 +74,7 @@ Ninja = (function() {
69
74
  collection.addBehavior(selector, dispatching[selector])
70
75
  }
71
76
  }
72
- $(window).load( function(){ Ninja.go() } )
77
+ jQuery(window).load( function(){ Ninja.go() } )
73
78
  },
74
79
 
75
80
  badBehavior: function(nonsense) {
@@ -83,8 +88,7 @@ Ninja = (function() {
83
88
  //If we ever receive either of the W3C DOMMutation events, we don't need our IE based
84
89
  //hack, so nerf it
85
90
  rootOfDocument.one("DOMSubtreeModified DOMNodeInserted", function(){
86
- this.fireMutationEvent = function(){}
87
- this.addMutationTargets = function(t){}
91
+ Ninja.tools.detachSyntheticMutationEvents()
88
92
  })
89
93
  this.behavior = this.badBehavior
90
94
  this.tools.fireMutationEvent()
@@ -95,47 +99,20 @@ Ninja = (function() {
95
99
 
96
100
  function Tools(ninja) {
97
101
  this.ninja = ninja
98
- this.mutationTargets = []
99
102
  }
100
103
 
101
104
  Tools.prototype = {
105
+ //Handy JS things
102
106
  forEach: forEach,
103
107
  enrich: function(left, right) {
104
- return $.extend(left, right)
108
+ return jQuery.extend(left, right)
105
109
  },
106
110
  ensureDefaults: function(config, defaults) {
107
111
  return this.enrich(defaults, config)
108
112
  },
109
- copyAttributes: function(from, to, which) {
110
- var attributeList = []
111
- var attrs = []
112
- var match = new RegExp("^" + which.join("$|^") + "$")
113
- to = $(to)
114
- this.forEach(from.attributes, function(att) {
115
- if(match.test(att.nodeName)) {
116
- to.attr(att.nodeName, att.nodeValue)
117
- }
118
- })
119
- },
120
- addMutationTargets: function(targets) {
121
- this.mutationTargets = this.mutationTargets.concat(target)
122
- },
113
+ //DOM and Events
123
114
  getRootOfDocument: function() {
124
- return $("html") //document.firstChild)
125
- },
126
- fireMutationEvent: function() {
127
- var targets = this.mutationTargets
128
- if (targets.length > 0 ) {
129
- for(var target = targets.shift();
130
- targets.length > 0;
131
- target = targets.shift()) {
132
- $(target).trigger("thisChangedDOM")
133
- }
134
- }
135
- else {
136
- this.getRootOfDocument().trigger("thisChangedDOM")
137
- //$("html").trigger("thisChangedDOM")
138
- }
115
+ return jQuery("html") //document.firstChild)
139
116
  },
140
117
  clearRootCollection: function() {
141
118
  Ninja.behavior = Ninja.goodBehavior
@@ -151,13 +128,6 @@ Ninja = (function() {
151
128
  rootOfDocument.data("ninja-behavior", collection);
152
129
  return collection
153
130
  },
154
- deriveElementsFrom: function(element, means){
155
- switch(typeof means){
156
- case 'undefined': return element
157
- case 'string': return $(means)
158
- case 'function': return means(element)
159
- }
160
- },
161
131
  suppressChangeEvents: function() {
162
132
  return new Behavior({
163
133
  events: {
@@ -166,32 +136,34 @@ Ninja = (function() {
166
136
  }
167
137
  })
168
138
  },
169
- hiddenDiv: function() {
170
- var existing = $("div#ninja-hide")
171
- if(existing.length > 0) {
172
- return existing[0]
173
- }
174
-
175
- var hide = $("<div id='ninja-hide'></div>").css("display", "none")
176
- $("body").append(hide)
177
- Ninja.tools.getRootCollection().applyBehaviorsTo(hide, [Ninja.tools.suppressChangeEvents()])
178
- return hide
139
+ addMutationTargets: function(targets) {
140
+ this.getRootCollection().addMutationTargets(targets)
179
141
  },
180
- ajaxSubmitter: function(form) {
181
- return new AjaxSubmitter(form)
142
+ fireMutationEvent: function() {
143
+ this.getRootCollection().fireMutationEvent()
182
144
  },
183
- overlay: function() {
184
- // I really liked using
185
- //return new Overlay([].map.apply(arguments,[function(i) {return i}]))
186
- //but IE8 doesn't implement ECMA 2.6.2 5th ed.
187
-
188
- return new Overlay(jQuery.makeArray(arguments))
145
+ detachSyntheticMutationEvents: function() {
146
+ this.getRootCollection().fireMutationEvent = function(){}
147
+ this.getRootCollection().addMutationTargets = function(t){}
189
148
  },
190
- busyOverlay: function(elem) {
191
- var overlay = this.overlay(elem)
192
- overlay.set.addClass("ninja busy")
193
- overlay.laziness = this.ninja.config.busyLaziness
194
- return overlay
149
+ //HTML Utils
150
+ copyAttributes: function(from, to, which) {
151
+ var attributeList = []
152
+ var attrs = []
153
+ var match = new RegExp("^" + which.join("$|^") + "$")
154
+ to = jQuery(to)
155
+ this.forEach(from.attributes, function(att) {
156
+ if(match.test(att.nodeName)) {
157
+ to.attr(att.nodeName, att.nodeValue)
158
+ }
159
+ })
160
+ },
161
+ deriveElementsFrom: function(element, means){
162
+ switch(typeof means){
163
+ case 'undefined': return element
164
+ case 'string': return jQuery(means)
165
+ case 'function': return means(element)
166
+ }
195
167
  },
196
168
  extractMethod: function(element, formData) {
197
169
  if(element.dataset !== undefined &&
@@ -201,9 +173,9 @@ Ninja = (function() {
201
173
  return element.dataset["method"]
202
174
  }
203
175
  if(element.dataset === undefined &&
204
- $(element).attr("data-method") !== undefined) {
205
- log("Override via data-method: " + $(element).attr("data-method"))
206
- return $(element).attr("data-method")
176
+ jQuery(element).attr("data-method") !== undefined) {
177
+ log("Override via data-method: " + jQuery(element).attr("data-method"))
178
+ return jQuery(element).attr("data-method")
207
179
  }
208
180
  if(typeof formData !== "undefined") {
209
181
  for(var i=0, len = formData.length; i<len; i++) {
@@ -218,10 +190,46 @@ Ninja = (function() {
218
190
  }
219
191
  return "GET"
220
192
  },
193
+ //Ninjascript utils
194
+ applyBehaviors: function(element, behaviors) {
195
+ this.getRootCollection().apply(element, behaviors)
196
+ },
197
+ message: function(text, classes) {
198
+ var addingMessage = this.ninja.config.messageWrapping(text, classes)
199
+ jQuery(this.ninja.config.messageList).append(addingMessage)
200
+ },
201
+ hiddenDiv: function() {
202
+ var existing = jQuery("div#ninja-hide")
203
+ if(existing.length > 0) {
204
+ return existing[0]
205
+ }
206
+
207
+ var hide = jQuery("<div id='ninja-hide'></div>").css("display", "none")
208
+ jQuery("body").append(hide)
209
+ Ninja.tools.getRootCollection().applyBehaviorsTo(hide, [Ninja.tools.suppressChangeEvents()])
210
+ return hide
211
+ },
212
+ ajaxSubmitter: function(form) {
213
+ return new AjaxSubmitter(form)
214
+ },
215
+ overlay: function() {
216
+ // I really liked using
217
+ //return new Overlay([].map.apply(arguments,[function(i) {return i}]))
218
+ //but IE8 doesn't implement ECMA 2.6.2 5th ed.
219
+
220
+ return new Overlay(jQuery.makeArray(arguments))
221
+ },
222
+ busyOverlay: function(elem) {
223
+ var overlay = this.overlay(elem)
224
+ overlay.set.addClass("ninja busy")
225
+ overlay.laziness = this.ninja.config.busyLaziness
226
+ return overlay
227
+ },
221
228
  //Currently, this doesn't respect changes to the original block...
229
+ //There should be an "Overlay behavior" that gets applied
222
230
  buildOverlayFor: function(elem) {
223
- var overlay = $(document.createElement("div"))
224
- var hideMe = $(elem)
231
+ var overlay = jQuery(document.createElement("div"))
232
+ var hideMe = jQuery(elem)
225
233
  var offset = hideMe.offset()
226
234
  overlay.css("position", "absolute")
227
235
  overlay.css("top", offset.top)
@@ -230,10 +238,6 @@ Ninja = (function() {
230
238
  overlay.height(hideMe.outerHeight())
231
239
  overlay.css("zIndex", "2")
232
240
  return overlay
233
- },
234
- message: function(text, classes) {
235
- var addingMessage = this.ninja.config.messageWrapping(text, classes)
236
- $(this.ninja.config.messageList).append(addingMessage)
237
241
  }
238
242
  }
239
243
 
@@ -259,7 +263,7 @@ Ninja = (function() {
259
263
  AjaxSubmitter.prototype = {
260
264
  submit: function() {
261
265
  log("Computed method: " + this.method)
262
- $.ajax(this.ajaxData())
266
+ jQuery.ajax(this.ajaxData())
263
267
  },
264
268
 
265
269
  ajaxData: function() {
@@ -302,7 +306,7 @@ Ninja = (function() {
302
306
  var elements = this.convertToElementArray(list)
303
307
  this.laziness = 0
304
308
  var ov = this
305
- this.set = $(jQuery.map(elements, function(element, idx) {
309
+ this.set = jQuery(jQuery.map(elements, function(element, idx) {
306
310
  return ov.buildOverlayFor(element)
307
311
  }))
308
312
  }
@@ -313,7 +317,7 @@ Ninja = (function() {
313
317
  switch(typeof list) {
314
318
  case 'undefined': return []
315
319
  case 'boolean': return []
316
- case 'string': return h.convertToElementArray($(list))
320
+ case 'string': return h.convertToElementArray(jQuery(list))
317
321
  case 'function': return h.convertToElementArray(list())
318
322
  case 'object': {
319
323
  //IE8 barfs on 'list instanceof Element'
@@ -335,8 +339,8 @@ Ninja = (function() {
335
339
  },
336
340
 
337
341
  buildOverlayFor: function(elem) {
338
- var overlay = $(document.createElement("div"))
339
- var hideMe = $(elem)
342
+ var overlay = jQuery(document.createElement("div"))
343
+ var hideMe = jQuery(elem)
340
344
  var offset = hideMe.offset()
341
345
  overlay.css("position", "absolute")
342
346
  overlay.css("top", offset.top)
@@ -348,7 +352,7 @@ Ninja = (function() {
348
352
  return overlay[0]
349
353
  },
350
354
  affix: function() {
351
- this.set.appendTo($("body"))
355
+ this.set.appendTo(jQuery("body"))
352
356
  overlaySet = this.set
353
357
  window.setTimeout(function() {
354
358
  overlaySet.css("display", "block")
@@ -359,14 +363,6 @@ Ninja = (function() {
359
363
  }
360
364
  }
361
365
 
362
- function BehaviorCollection() {
363
- this.lexicalCount = 0
364
- this.eventQueue = []
365
- this.behaviors = {}
366
- this.selectors = []
367
- return this
368
- }
369
-
370
366
  function EventScribe() {
371
367
  this.handlers = {}
372
368
  this.currentElement = null
@@ -377,8 +373,8 @@ Ninja = (function() {
377
373
  for(var eventName in this.handlers) {
378
374
  var handler = this.handlers[eventName]
379
375
  this.handlers[eventName] = function(eventRecord) {
380
- handler(eventRecord)
381
- $(element).remove()
376
+ handler.call(this, eventRecord)
377
+ jQuery(element).remove()
382
378
  }
383
379
  }
384
380
  },
@@ -401,7 +397,7 @@ Ninja = (function() {
401
397
  },
402
398
  applyEventHandlers: function(element) {
403
399
  for(var eventName in this.handlers) {
404
- $(element).bind(eventName, this.handlers[eventName])
400
+ jQuery(element).bind(eventName, this.handlers[eventName])
405
401
  }
406
402
  }
407
403
  }
@@ -413,24 +409,35 @@ Ninja = (function() {
413
409
  this.stashedElements = []
414
410
  }
415
411
 
416
- RootContext.prototype = {
417
- stash: function(element) {
418
- this.stashedElements.unshift(element)
419
- },
420
- clearStash: function() {
421
- this.stashedElements = []
422
- },
423
- //XXX Of concern: how do cascading events work out?
424
- //Should there be a first catch? Or a "doesn't cascade" or something?
425
- cascadeEvent: function(event) {
426
- var formDiv = Ninja.tools.hiddenDiv()
427
- forEach(this.stashedElements, function(element) {
428
- var elem = $(element)
429
- elem.data("ninja-visited", true)
430
- $(formDiv).append(elem)
431
- elem.trigger(event)
432
- })
433
- }
412
+ RootContext.prototype = Ninja.tools.enrich(
413
+ new Tools(Ninja),
414
+ {
415
+ stash: function(element) {
416
+ this.stashedElements.unshift(element)
417
+ },
418
+ clearStash: function() {
419
+ this.stashedElements = []
420
+ },
421
+ //XXX Of concern: how do cascading events work out?
422
+ //Should there be a first catch? Or a "doesn't cascade" or something?
423
+ cascadeEvent: function(event) {
424
+ var formDiv = Ninja.tools.hiddenDiv()
425
+ forEach(this.stashedElements, function(element) {
426
+ var elem = jQuery(element)
427
+ elem.data("ninja-visited", true)
428
+ jQuery(formDiv).append(elem)
429
+ elem.trigger(event)
430
+ })
431
+ }
432
+ })
433
+
434
+ function BehaviorCollection() {
435
+ this.lexicalCount = 0
436
+ this.eventQueue = []
437
+ this.behaviors = {}
438
+ this.selectors = []
439
+ this.mutationTargets = []
440
+ return this
434
441
  }
435
442
 
436
443
  BehaviorCollection.prototype = {
@@ -469,7 +476,22 @@ Ninja = (function() {
469
476
  this.behaviors[selector].push(behavior)
470
477
  }
471
478
  },
472
-
479
+ addMutationTargets: function(targets) {
480
+ this.mutationTargets = this.mutationTargets.concat(target)
481
+ },
482
+ fireMutationEvent: function() {
483
+ var targets = this.mutationTargets
484
+ if (targets.length > 0 ) {
485
+ for(var target = targets.shift();
486
+ targets.length > 0;
487
+ target = targets.shift()) {
488
+ jQuery(target).trigger("thisChangedDOM")
489
+ }
490
+ }
491
+ else {
492
+ Ninja.tools.getRootOfDocument().trigger("thisChangedDOM")
493
+ }
494
+ },
473
495
  mutationEventTriggered: function(evnt){
474
496
  if(this.eventQueue.length == 0){
475
497
  log("mutation event - first")
@@ -485,8 +507,8 @@ Ninja = (function() {
485
507
  var eventCovered = false
486
508
  var uncovered = []
487
509
  forEach(this.eventQueue, function(val) {
488
- eventCovered = eventCovered || $.contains(val.target, evnt.target)
489
- if (!($.contains(evnt.target, val.target))) {
510
+ eventCovered = eventCovered || jQuery.contains(val.target, evnt.target)
511
+ if (!(jQuery.contains(evnt.target, val.target))) {
490
512
  uncovered.push(val)
491
513
  }
492
514
  })
@@ -548,10 +570,12 @@ Ninja = (function() {
548
570
  }
549
571
  }
550
572
  )
551
- $(element).data("ninja-visited", true)
573
+ jQuery(element).data("ninja-visited", true)
552
574
 
553
575
  scribe.applyEventHandlers(element)
554
576
 
577
+ this.fireMutationEvent()
578
+
555
579
  return element
556
580
  },
557
581
  collectBehaviors: function(element, collection, behaviors) {
@@ -574,12 +598,12 @@ Ninja = (function() {
574
598
  apply: function(element, startBehaviors, selectorIndex) {
575
599
  var applicableBehaviors = [], len = this.selectors.length
576
600
  this.collectBehaviors(element, applicableBehaviors, startBehaviors)
577
- if (!$(element).data("ninja-visited")) {
601
+ if (!jQuery(element).data("ninja-visited")) {
578
602
  if(typeof selectorIndex == "undefined") {
579
603
  selectorIndex = 0
580
604
  }
581
605
  for(var j = selectorIndex; j < len; j++) {
582
- if($(element).is(this.selectors[j])) {
606
+ if(jQuery(element).is(this.selectors[j])) {
583
607
  this.collectBehaviors(element, applicableBehaviors, this.behaviors[this.selectors[j]])
584
608
  }
585
609
  }
@@ -592,9 +616,9 @@ Ninja = (function() {
592
616
  var collection = this
593
617
 
594
618
  //Sizzle?
595
- $(root).find(this.selectors[i]).each(
619
+ jQuery(root).find(this.selectors[i]).each(
596
620
  function(index, elem){
597
- if (!$(elem).data("ninja-visited")) { //Pure optimization
621
+ if (!jQuery(elem).data("ninja-visited")) { //Pure optimization
598
622
  collection.apply(elem, [], i)
599
623
  }
600
624
  }
@@ -629,7 +653,7 @@ Ninja = (function() {
629
653
  Selectabehavior.prototype = {
630
654
  choose: function(element) {
631
655
  for(var selector in this.menu) {
632
- if($(element).is(selector)) {
656
+ if(jQuery(element).is(selector)) {
633
657
  return this.menu[selector].choose(element)
634
658
  }
635
659
  }
@@ -670,7 +694,7 @@ Ninja = (function() {
670
694
  var context = this.inContext({})
671
695
 
672
696
  elem = this.applyTransform(context, elem)
673
- $(elem).data("ninja-visited", true)
697
+ jQuery(elem).data("ninja-visited", true)
674
698
 
675
699
  this.applyEventHandlers(context, elem)
676
700
 
@@ -701,7 +725,7 @@ Ninja = (function() {
701
725
  applyEventHandlers: function(context, elem) {
702
726
  for(var eventName in this.eventHandlers) {
703
727
  var handler = this.eventHandlers[eventName]
704
- $(elem).bind(eventName, this.makeHandler.call(context, handler))
728
+ jQuery(elem).bind(eventName, this.makeHandler.call(context, handler))
705
729
  }
706
730
  return elem
707
731
  },
@@ -718,6 +742,7 @@ Ninja = (function() {
718
742
  var stopDefault = true
719
743
  var stopPropagate = true
720
744
  var stopImmediate = false
745
+ var fireMutation = false
721
746
  var config = this.eventHandlers[eventName]
722
747
 
723
748
  if (typeof config == "function") {
@@ -728,15 +753,19 @@ Ninja = (function() {
728
753
  config = config.slice(1,config.length)
729
754
  var len = config.length
730
755
  for(var i = 0; i < len; i++) {
731
- if (config[i] == "default") {
756
+ if (config[i] == "andDoDefault" || config[i] == "allowDefault") {
732
757
  stopDefault = false
733
758
  }
734
- if (config[i] == "propagate") {
759
+ if (config[i] == "allowPropagate" || config[i] == "dontStopPropagation") {
735
760
  stopPropagate = false
736
761
  }
737
- if (config[i] == "immediate" || config[i] == "other") {
762
+ //stopImmediatePropagation is a jQuery thing
763
+ if (config[i] == "andDoOthers") {
738
764
  stopImmediate = false
739
765
  }
766
+ if (config[i] == "changesDOM") {
767
+ fireMutation = true
768
+ }
740
769
  }
741
770
  }
742
771
  var handler = function(eventRecord) {
@@ -758,13 +787,24 @@ Ninja = (function() {
758
787
  eventRecord.stopImmediatePropagation()
759
788
  })
760
789
  }
790
+ if (fireMutation) {
791
+ handler = this.appendAction(handler, function(eventRecord) {
792
+ Ninja.tools.fireMutationEvent()
793
+ })
794
+ }
761
795
 
762
796
  return handler
763
797
  },
764
798
  prependAction: function(handler, doWhat) {
765
799
  return function(eventRecord) {
766
- doWhat(eventRecord)
767
- handler(eventRecord)
800
+ doWhat.call(this, eventRecord)
801
+ handler.call(this, eventRecord)
802
+ }
803
+ },
804
+ appendAction: function(handler, doWhat) {
805
+ return function(eventRecord) {
806
+ handler.call(this, eventRecord)
807
+ doWhat.call(this, eventRecord)
768
808
  }
769
809
  },
770
810
  transform: function(elem){
@@ -818,15 +858,15 @@ Ninja = (function() {
818
858
  priority: 10,
819
859
  helpers: {
820
860
  findOverlay: function(elem) {
821
- return Ninja.tools.deriveElementsFrom(elem, configs.busyElement)
861
+ return this.deriveElementsFrom(elem, configs.busyElement)
822
862
  }
823
863
  },
824
864
  events: {
825
865
  click: function(evnt) {
826
- var overlay = Ninja.tools.busyOverlay(this.findOverlay(evnt.target))
827
- var submitter = Ninja.tools.ajaxSubmitter()
866
+ var overlay = this.busyOverlay(this.findOverlay(evnt.target))
867
+ var submitter = this.ajaxSubmitter()
828
868
  submitter.action = evnt.target.href
829
- submitter.method = Ninja.tools.extractMethod(evnt.target)
869
+ submitter.method = this.extractMethod(evnt.target)
830
870
 
831
871
  submitter.onResponse = function(xhr, statusTxt) {
832
872
  overlay.remove()
@@ -857,16 +897,16 @@ Ninja = (function() {
857
897
  priority: 20,
858
898
  helpers: {
859
899
  findOverlay: function(elem) {
860
- return Ninja.tools.deriveElementsFrom(elem, configs.busyElement)
900
+ return this.deriveElementsFrom(elem, configs.busyElement)
861
901
  }
862
902
  },
863
903
  events: {
864
904
  submit: function(evnt) {
865
- var overlay = Ninja.tools.busyOverlay(this.findOverlay(evnt.target))
866
- var submitter = Ninja.tools.ajaxSubmitter()
867
- submitter.formData = $(evnt.target).serializeArray()
905
+ var overlay = this.busyOverlay(this.findOverlay(evnt.target))
906
+ var submitter = this.ajaxSubmitter()
907
+ submitter.formData = jQuery(evnt.target).serializeArray()
868
908
  submitter.action = evnt.target.action
869
- submitter.method = Ninja.tools.extractMethod(evnt.target, submitter.formData)
909
+ submitter.method = this.extractMethod(evnt.target, submitter.formData)
870
910
 
871
911
  submitter.onResponse = function(xhr, statusTxt) {
872
912
  overlay.remove()
@@ -886,7 +926,7 @@ Ninja = (function() {
886
926
  //busy overlay - by default we overlay the element itself
887
927
  //
888
928
  //this.becomesAjaxLink({
889
- // busyElement: function(elem) { $("#user-notification") }
929
+ // busyElement: function(elem) { jQuery("#user-notification") }
890
930
  //})
891
931
  becomesAjaxLink: function(configs) {
892
932
  if(!(configs instanceof Object)) {
@@ -917,11 +957,11 @@ Ninja = (function() {
917
957
  priority: 30,
918
958
  transform: function(form){
919
959
  var linkText
920
- if ((images = $('input[type=image]', form)).size() > 0){
960
+ if ((images = jQuery('input[type=image]', form)).size() > 0){
921
961
  image = images[0]
922
962
  linkText = "<img src='" + image.src + "' alt='" + image.alt +"'";
923
963
  }
924
- else if((submits = $('input[type=submit]', form)).size() > 0) {
964
+ else if((submits = jQuery('input[type=submit]', form)).size() > 0) {
925
965
  submit = submits[0]
926
966
  if(submits.size() > 1) {
927
967
  log("Multiple submits. Using: " + submit)
@@ -932,9 +972,9 @@ Ninja = (function() {
932
972
  log("Couldn't find a submit input in form");
933
973
  }
934
974
 
935
- var link = $("<a rel='nofollow' href='#'>" + linkText + "</a>")
936
- Ninja.tools.copyAttributes(form, link, configs.retainAttributes)
937
- this.stash($(form).replaceWith(link))
975
+ var link = jQuery("<a rel='nofollow' href='#'>" + linkText + "</a>")
976
+ this.copyAttributes(form, link, configs.retainAttributes)
977
+ this.stash(jQuery(form).replaceWith(link))
938
978
  return link
939
979
  },
940
980
  events: {
@@ -955,26 +995,23 @@ Ninja = (function() {
955
995
  //{ lifetime: 10000, diesFor: 600 }
956
996
 
957
997
  decays: function(configs) {
958
- if(typeof configs == "undefined") { configs = {} }
959
-
960
- if(typeof configs.lifetime == "undefined") {
961
- configs.lifetime = 10000
962
- }
963
-
964
- if(typeof configs.diesFor == "undefined") {
965
- configs.diesFor = 600
966
- }
998
+ configs = Ninja.tools.ensureDefaults(configs, {
999
+ lifetime: 10000,
1000
+ diesFor: 600
1001
+ })
967
1002
 
968
1003
  return new ninja.does({
969
1004
  priority: 100,
970
1005
  transform: function(elem) {
971
- $(elem).delay(configs.lifetime).slideUp(configs.diesFor, function(){
972
- $(elem).remove()})
1006
+ jQuery(elem).delay(configs.lifetime).slideUp(configs.diesFor, function(){
1007
+ jQuery(elem).remove()
1008
+ Ninja.tools.fireMutationEvent()
1009
+ })
973
1010
  },
974
1011
  events: {
975
- click: function(evnt, elem) {
976
- $(elem).remove();
977
- }
1012
+ click: [function(event) {
1013
+ jQuery(this.element).remove();
1014
+ }, "changesDOM"]
978
1015
  }
979
1016
  })
980
1017
  }
@@ -0,0 +1,132 @@
1
+ jQuery(function ($) {
2
+ var csrf_token = $('meta[name=csrf-token]').attr('content'),
3
+ csrf_param = $('meta[name=csrf-param]').attr('content');
4
+
5
+ $.fn.extend({
6
+ /**
7
+ * Triggers a custom event on an element and returns the event result
8
+ * this is used to get around not being able to ensure callbacks are placed
9
+ * at the end of the chain.
10
+ *
11
+ * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
12
+ * own events and placing ourselves at the end of the chain.
13
+ */
14
+ triggerAndReturn: function (name, data) {
15
+ var event = new $.Event(name);
16
+ this.trigger(event, data);
17
+
18
+ return event.result !== false;
19
+ },
20
+
21
+ /**
22
+ * Handles execution of remote calls firing overridable events along the way
23
+ */
24
+ callRemote: function () {
25
+ var el = this,
26
+ method = el.attr('method') || el.attr('data-method') || 'GET',
27
+ url = el.attr('action') || el.attr('href'),
28
+ dataType = el.attr('data-type') || 'script';
29
+
30
+ if (url === undefined) {
31
+ throw "No URL specified for remote call (action or href must be present).";
32
+ } else {
33
+ if (el.triggerAndReturn('ajax:before')) {
34
+ var data = el.is('form') ? el.serializeArray() : [];
35
+ $.ajax({
36
+ url: url,
37
+ data: data,
38
+ dataType: dataType,
39
+ type: method.toUpperCase(),
40
+ beforeSend: function (xhr) {
41
+ el.trigger('ajax:loading', xhr);
42
+ },
43
+ success: function (data, status, xhr) {
44
+ el.trigger('ajax:success', [data, status, xhr]);
45
+ },
46
+ complete: function (xhr) {
47
+ el.trigger('ajax:complete', xhr);
48
+ },
49
+ error: function (xhr, status, error) {
50
+ el.trigger('ajax:failure', [xhr, status, error]);
51
+ }
52
+ });
53
+ }
54
+
55
+ el.trigger('ajax:after');
56
+ }
57
+ }
58
+ });
59
+
60
+ /**
61
+ * confirmation handler
62
+ */
63
+ $('a[data-confirm],input[data-confirm]').live('click', function () {
64
+ var el = $(this);
65
+ if (el.triggerAndReturn('confirm')) {
66
+ if (!confirm(el.attr('data-confirm'))) {
67
+ return false;
68
+ }
69
+ }
70
+ });
71
+
72
+
73
+ /**
74
+ * remote handlers
75
+ */
76
+ $('form[data-remote]').live('submit', function (e) {
77
+ $(this).callRemote();
78
+ e.preventDefault();
79
+ });
80
+
81
+ $('a[data-remote],input[data-remote]').live('click', function (e) {
82
+ $(this).callRemote();
83
+ e.preventDefault();
84
+ });
85
+
86
+ $('a[data-method]:not([data-remote])').live('click', function (e){
87
+ var link = $(this),
88
+ href = link.attr('href'),
89
+ method = link.attr('data-method'),
90
+ form = $('<form method="post" action="'+href+'"></form>'),
91
+ metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';
92
+
93
+ if (csrf_param != null && csrf_token != null) {
94
+ metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
95
+ }
96
+
97
+ form.hide()
98
+ .append(metadata_input)
99
+ .appendTo('body');
100
+
101
+ e.preventDefault();
102
+ form.submit();
103
+ });
104
+
105
+ /**
106
+ * disable-with handlers
107
+ */
108
+ var disable_with_input_selector = 'input[data-disable-with]';
109
+ var disable_with_form_remote_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')';
110
+ var disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')';
111
+
112
+ var disable_with_input_function = function () {
113
+ $(this).find(disable_with_input_selector).each(function () {
114
+ var input = $(this);
115
+ input.data('enable-with', input.val())
116
+ .attr('value', input.attr('data-disable-with'))
117
+ .attr('disabled', 'disabled');
118
+ });
119
+ };
120
+
121
+ $(disable_with_form_remote_selector).live('ajax:before', disable_with_input_function);
122
+ $(disable_with_form_not_remote_selector).live('submit', disable_with_input_function);
123
+
124
+ $(disable_with_form_remote_selector).live('ajax:complete', function () {
125
+ $(this).find(disable_with_input_selector).each(function () {
126
+ var input = $(this);
127
+ input.removeAttr('disabled')
128
+ .val(input.data('enable-with'));
129
+ });
130
+ });
131
+
132
+ });
@@ -43,11 +43,14 @@ module MizugumoLinkHelper
43
43
  # debugger
44
44
  method = html_options[:method]
45
45
 
46
+ # debugger
47
+
46
48
  cssclass = [ 'mizugumo_graceful_form' ]
47
49
  cssclass << html_options[:class] unless html_options[:class].blank?
48
50
  html_options = convert_options_to_data_attributes(options, html_options)
49
51
  html_options.merge!({:action => action, :method => :post, :title => title, :class => cssclass})
50
52
  html_options.delete('rel')
53
+ html_options.delete('class')
51
54
 
52
55
  content_tag(:form, html_options) do
53
56
  hidden_field_tag("_method", method) +
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mizugumo
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 0
10
- version: 0.1.0
9
+ - 1
10
+ version: 0.1.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Evan Dorn
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-25 00:00:00 -08:00
18
+ date: 2011-02-03 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -79,7 +79,7 @@ dependencies:
79
79
  version: "0"
80
80
  type: :development
81
81
  version_requirements: *id004
82
- description: Seamless UJS for Rails using NinjaScript
82
+ description: "Seamless UJS for Rails using NinjaScript.\n Get RESTFul delete links that work without JavaScript and AJAXy\n behavior that degrades gracefully right out of your scaffold!\n "
83
83
  email: evan@lrdesign.com
84
84
  executables: []
85
85
 
@@ -100,6 +100,7 @@ files:
100
100
  - lib/generators/mizugumo/install/templates/images/ui/spinner.gif
101
101
  - lib/generators/mizugumo/install/templates/javascripts/jquery-1.4.2.js
102
102
  - lib/generators/mizugumo/install/templates/javascripts/jquery.ninja_script.js
103
+ - lib/generators/mizugumo/install/templates/javascripts/rails.js
103
104
  - lib/generators/mizugumo/install/templates/stylesheets/mizugumo.css
104
105
  - lib/generators/mizugumo/install/templates/stylesheets/sass/mizugumo.sass
105
106
  - lib/generators/rails/mizugumo/erb_generator.rb
@@ -126,9 +127,12 @@ files:
126
127
  - lib/generators/rails/mizugumo/view_generator.rb
127
128
  - lib/mizugumo.rb
128
129
  - lib/mizugumo_link_helper.rb
130
+ - doc/making_generators.txt
131
+ - doc/NAME.haml
132
+ - doc/PURPOSE.haml
129
133
  - spec/spec_helper.rb
130
134
  has_rdoc: true
131
- homepage: http://github.com/IdahoEv/mizugumo
135
+ homepage: http://github.com/LRDesign/mizugumo
132
136
  licenses:
133
137
  - MIT
134
138
  post_install_message: