mizugumo 0.1.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.
- data/.document +5 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +87 -0
- data/Rakefile +70 -0
- data/VERSION +1 -0
- data/lib/generators/mizugumo/install/install_generator.rb +59 -0
- data/lib/generators/mizugumo/install/templates/images/ui/spinner.gif +0 -0
- data/lib/generators/mizugumo/install/templates/javascripts/jquery-1.4.2.js +6240 -0
- data/lib/generators/mizugumo/install/templates/javascripts/jquery.ninja_script.js +997 -0
- data/lib/generators/mizugumo/install/templates/stylesheets/mizugumo.css +46 -0
- data/lib/generators/mizugumo/install/templates/stylesheets/sass/mizugumo.sass +48 -0
- data/lib/generators/rails/mizugumo/erb_generator.rb +21 -0
- data/lib/generators/rails/mizugumo/haml_generator.rb +21 -0
- data/lib/generators/rails/mizugumo/scaffold_controller_generator.rb +26 -0
- data/lib/generators/rails/mizugumo/templates/_form.html.erb +23 -0
- data/lib/generators/rails/mizugumo/templates/_form.html.haml +21 -0
- data/lib/generators/rails/mizugumo/templates/_row.html.erb +9 -0
- data/lib/generators/rails/mizugumo/templates/_row.html.haml +7 -0
- data/lib/generators/rails/mizugumo/templates/controller.rb +93 -0
- data/lib/generators/rails/mizugumo/templates/create.js.erb +2 -0
- data/lib/generators/rails/mizugumo/templates/destroy.js.erb +1 -0
- data/lib/generators/rails/mizugumo/templates/edit.html.erb +6 -0
- data/lib/generators/rails/mizugumo/templates/edit.html.haml +7 -0
- data/lib/generators/rails/mizugumo/templates/edit.js.erb +4 -0
- data/lib/generators/rails/mizugumo/templates/index.html.erb +18 -0
- data/lib/generators/rails/mizugumo/templates/index.html.haml +13 -0
- data/lib/generators/rails/mizugumo/templates/new.html.erb +5 -0
- data/lib/generators/rails/mizugumo/templates/new.html.haml +5 -0
- data/lib/generators/rails/mizugumo/templates/new.js.erb +2 -0
- data/lib/generators/rails/mizugumo/templates/show.html.erb +10 -0
- data/lib/generators/rails/mizugumo/templates/show.html.haml +9 -0
- data/lib/generators/rails/mizugumo/templates/update.js.erb +4 -0
- data/lib/generators/rails/mizugumo/view_generator.rb +44 -0
- data/lib/mizugumo.rb +13 -0
- data/lib/mizugumo_link_helper.rb +58 -0
- data/spec/spec_helper.rb +0 -0
- metadata +165 -0
@@ -0,0 +1,997 @@
|
|
1
|
+
// vim: sw=2 ft=javascript
|
2
|
+
|
3
|
+
Ninja = (function() {
|
4
|
+
function log(message) {
|
5
|
+
try {
|
6
|
+
console.log(message)
|
7
|
+
}
|
8
|
+
catch(e) {} //we're in IE or FF w/o Firebug or something
|
9
|
+
}
|
10
|
+
|
11
|
+
function isArray(candidate) {
|
12
|
+
return (candidate.constructor == Array)
|
13
|
+
}
|
14
|
+
|
15
|
+
function forEach(list, callback, thisArg) {
|
16
|
+
if(typeof list.forEach == "function") {
|
17
|
+
return list.forEach(callback, thisArg)
|
18
|
+
}
|
19
|
+
else if(typeof Array.prototype.forEach == "function") {
|
20
|
+
return Array.prototype.forEach.call(list, callback, thisArg)
|
21
|
+
}
|
22
|
+
else {
|
23
|
+
var len = Number(list.length)
|
24
|
+
for(var k = 0; k < len; k+=1) {
|
25
|
+
if(typeof list[k] != "undefined") {
|
26
|
+
callback.call(thisArg, list[k], k, list)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
return
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
function NinjaScript() {
|
34
|
+
//NinjaScript-wide configurations. Currently, not very many
|
35
|
+
this.config = {
|
36
|
+
//This is the half-assed: it should be template of some sort
|
37
|
+
messageWrapping: function(text, classes) {
|
38
|
+
return "<div class='flash " + classes +"'><p>" + text + "</p></div>"
|
39
|
+
},
|
40
|
+
messageList: "#messages",
|
41
|
+
busyLaziness: 200
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
this.behavior = this.goodBehavior
|
46
|
+
this.tools = new Tools(this)
|
47
|
+
}
|
48
|
+
|
49
|
+
NinjaScript.prototype = {
|
50
|
+
|
51
|
+
packageBehaviors: function(callback) {
|
52
|
+
var types = {
|
53
|
+
does: Behavior,
|
54
|
+
chooses: Metabehavior,
|
55
|
+
selects: Selectabehavior
|
56
|
+
}
|
57
|
+
result = callback(types)
|
58
|
+
this.tools.enrich(this, result)
|
59
|
+
},
|
60
|
+
|
61
|
+
goodBehavior: function(dispatching) {
|
62
|
+
var collection = this.tools.getRootCollection()
|
63
|
+
for(var selector in dispatching)
|
64
|
+
{
|
65
|
+
if(typeof dispatching[selector] == "undefined") {
|
66
|
+
log("Selector " + selector + " not properly defined - ignoring")
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
collection.addBehavior(selector, dispatching[selector])
|
70
|
+
}
|
71
|
+
}
|
72
|
+
$(window).load( function(){ Ninja.go() } )
|
73
|
+
},
|
74
|
+
|
75
|
+
badBehavior: function(nonsense) {
|
76
|
+
throw new Error("Called Ninja.behavior() after Ninja.go() - don't do that. 'Go' means 'I'm done, please proceed'")
|
77
|
+
},
|
78
|
+
|
79
|
+
go: function() {
|
80
|
+
if(this.behavior != this.misbehavior) {
|
81
|
+
var rootOfDocument = this.tools.getRootOfDocument()
|
82
|
+
rootOfDocument.bind("DOMSubtreeModified DOMNodeInserted thisChangedDOM", handleMutation);
|
83
|
+
//If we ever receive either of the W3C DOMMutation events, we don't need our IE based
|
84
|
+
//hack, so nerf it
|
85
|
+
rootOfDocument.one("DOMSubtreeModified DOMNodeInserted", function(){
|
86
|
+
this.fireMutationEvent = function(){}
|
87
|
+
this.addMutationTargets = function(t){}
|
88
|
+
})
|
89
|
+
this.behavior = this.badBehavior
|
90
|
+
this.tools.fireMutationEvent()
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
|
96
|
+
function Tools(ninja) {
|
97
|
+
this.ninja = ninja
|
98
|
+
this.mutationTargets = []
|
99
|
+
}
|
100
|
+
|
101
|
+
Tools.prototype = {
|
102
|
+
forEach: forEach,
|
103
|
+
enrich: function(left, right) {
|
104
|
+
return $.extend(left, right)
|
105
|
+
},
|
106
|
+
ensureDefaults: function(config, defaults) {
|
107
|
+
return this.enrich(defaults, config)
|
108
|
+
},
|
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
|
+
},
|
123
|
+
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
|
+
}
|
139
|
+
},
|
140
|
+
clearRootCollection: function() {
|
141
|
+
Ninja.behavior = Ninja.goodBehavior
|
142
|
+
this.getRootOfDocument().data("ninja-behavior", null)
|
143
|
+
},
|
144
|
+
getRootCollection: function() {
|
145
|
+
var rootOfDocument = this.getRootOfDocument()
|
146
|
+
if(rootOfDocument.data("ninja-behavior") instanceof BehaviorCollection) {
|
147
|
+
return rootOfDocument.data("ninja-behavior")
|
148
|
+
}
|
149
|
+
|
150
|
+
var collection = new BehaviorCollection()
|
151
|
+
rootOfDocument.data("ninja-behavior", collection);
|
152
|
+
return collection
|
153
|
+
},
|
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
|
+
suppressChangeEvents: function() {
|
162
|
+
return new Behavior({
|
163
|
+
events: {
|
164
|
+
DOMSubtreeModified: function(e){},
|
165
|
+
DOMNodeInserted: function(e){}
|
166
|
+
}
|
167
|
+
})
|
168
|
+
},
|
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
|
179
|
+
},
|
180
|
+
ajaxSubmitter: function(form) {
|
181
|
+
return new AjaxSubmitter(form)
|
182
|
+
},
|
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))
|
189
|
+
},
|
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
|
195
|
+
},
|
196
|
+
extractMethod: function(element, formData) {
|
197
|
+
if(element.dataset !== undefined &&
|
198
|
+
element.dataset["method"] !== undefined &&
|
199
|
+
element.dataset["method"].length > 0) {
|
200
|
+
log("Override via dataset: " + element.dataset["method"])
|
201
|
+
return element.dataset["method"]
|
202
|
+
}
|
203
|
+
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")
|
207
|
+
}
|
208
|
+
if(typeof formData !== "undefined") {
|
209
|
+
for(var i=0, len = formData.length; i<len; i++) {
|
210
|
+
if(formData[i].name == "Method") {
|
211
|
+
log("Override via Method: " + formData[i].value)
|
212
|
+
return formData[i].value
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
if(typeof element.method !== "undefined") {
|
217
|
+
return element.method
|
218
|
+
}
|
219
|
+
return "GET"
|
220
|
+
},
|
221
|
+
//Currently, this doesn't respect changes to the original block...
|
222
|
+
buildOverlayFor: function(elem) {
|
223
|
+
var overlay = $(document.createElement("div"))
|
224
|
+
var hideMe = $(elem)
|
225
|
+
var offset = hideMe.offset()
|
226
|
+
overlay.css("position", "absolute")
|
227
|
+
overlay.css("top", offset.top)
|
228
|
+
overlay.css("left", offset.left)
|
229
|
+
overlay.width(hideMe.outerWidth())
|
230
|
+
overlay.height(hideMe.outerHeight())
|
231
|
+
overlay.css("zIndex", "2")
|
232
|
+
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
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
var Ninja = new NinjaScript();
|
241
|
+
//Below here is the dojo - the engines that make NinjaScript work.
|
242
|
+
//With any luck, only the helpful and curious should have call to keep
|
243
|
+
//reading
|
244
|
+
//
|
245
|
+
|
246
|
+
function handleMutation(evnt) {
|
247
|
+
Ninja.tools.getRootCollection().mutationEventTriggered(evnt);
|
248
|
+
}
|
249
|
+
|
250
|
+
function AjaxSubmitter() {
|
251
|
+
this.formData = []
|
252
|
+
this.action = "/"
|
253
|
+
this.method = "GET"
|
254
|
+
this.dataType = 'script'
|
255
|
+
|
256
|
+
return this
|
257
|
+
}
|
258
|
+
|
259
|
+
AjaxSubmitter.prototype = {
|
260
|
+
submit: function() {
|
261
|
+
log("Computed method: " + this.method)
|
262
|
+
$.ajax(this.ajaxData())
|
263
|
+
},
|
264
|
+
|
265
|
+
ajaxData: function() {
|
266
|
+
return {
|
267
|
+
data: this.formData,
|
268
|
+
dataType: this.dataType,
|
269
|
+
url: this.action,
|
270
|
+
type: this.method,
|
271
|
+
complete: this.responseHandler(),
|
272
|
+
success: this.successHandler(),
|
273
|
+
error: this.onError
|
274
|
+
}
|
275
|
+
},
|
276
|
+
|
277
|
+
successHandler: function() {
|
278
|
+
var submitter = this
|
279
|
+
return function(data, statusTxt, xhr) {
|
280
|
+
submitter.onSuccess(xhr, statusTxt, data)
|
281
|
+
}
|
282
|
+
},
|
283
|
+
responseHandler: function() {
|
284
|
+
var submitter = this
|
285
|
+
return function(xhr, statusTxt) {
|
286
|
+
submitter.onResponse(xhr, statusTxt)
|
287
|
+
Ninja.tools.fireMutationEvent()
|
288
|
+
}
|
289
|
+
},
|
290
|
+
|
291
|
+
onResponse: function(xhr, statusTxt) {
|
292
|
+
},
|
293
|
+
onSuccess: function(xhr, statusTxt, data) {
|
294
|
+
},
|
295
|
+
onError: function(xhr, statusTxt, errorThrown) {
|
296
|
+
log(xhr.responseText)
|
297
|
+
Ninja.tools.message("Server error: " + xhr.statusText, "error")
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
function Overlay(list) {
|
302
|
+
var elements = this.convertToElementArray(list)
|
303
|
+
this.laziness = 0
|
304
|
+
var ov = this
|
305
|
+
this.set = $(jQuery.map(elements, function(element, idx) {
|
306
|
+
return ov.buildOverlayFor(element)
|
307
|
+
}))
|
308
|
+
}
|
309
|
+
|
310
|
+
Overlay.prototype = {
|
311
|
+
convertToElementArray: function(list) {
|
312
|
+
var h = this
|
313
|
+
switch(typeof list) {
|
314
|
+
case 'undefined': return []
|
315
|
+
case 'boolean': return []
|
316
|
+
case 'string': return h.convertToElementArray($(list))
|
317
|
+
case 'function': return h.convertToElementArray(list())
|
318
|
+
case 'object': {
|
319
|
+
//IE8 barfs on 'list instanceof Element'
|
320
|
+
if("focus" in list && "blur" in list && !("jquery" in list)) {
|
321
|
+
return [list]
|
322
|
+
}
|
323
|
+
else if("length" in list && "0" in list) {
|
324
|
+
var result = []
|
325
|
+
forEach(list, function(element) {
|
326
|
+
result = result.concat(h.convertToElementArray(element))
|
327
|
+
})
|
328
|
+
return result
|
329
|
+
}
|
330
|
+
else {
|
331
|
+
return []
|
332
|
+
}
|
333
|
+
}
|
334
|
+
}
|
335
|
+
},
|
336
|
+
|
337
|
+
buildOverlayFor: function(elem) {
|
338
|
+
var overlay = $(document.createElement("div"))
|
339
|
+
var hideMe = $(elem)
|
340
|
+
var offset = hideMe.offset()
|
341
|
+
overlay.css("position", "absolute")
|
342
|
+
overlay.css("top", offset.top)
|
343
|
+
overlay.css("left", offset.left)
|
344
|
+
overlay.width(hideMe.outerWidth())
|
345
|
+
overlay.height(hideMe.outerHeight())
|
346
|
+
overlay.css("zIndex", "2")
|
347
|
+
overlay.css("display", "none")
|
348
|
+
return overlay[0]
|
349
|
+
},
|
350
|
+
affix: function() {
|
351
|
+
this.set.appendTo($("body"))
|
352
|
+
overlaySet = this.set
|
353
|
+
window.setTimeout(function() {
|
354
|
+
overlaySet.css("display", "block")
|
355
|
+
}, this.laziness)
|
356
|
+
},
|
357
|
+
remove: function() {
|
358
|
+
this.set.remove()
|
359
|
+
}
|
360
|
+
}
|
361
|
+
|
362
|
+
function BehaviorCollection() {
|
363
|
+
this.lexicalCount = 0
|
364
|
+
this.eventQueue = []
|
365
|
+
this.behaviors = {}
|
366
|
+
this.selectors = []
|
367
|
+
return this
|
368
|
+
}
|
369
|
+
|
370
|
+
function EventScribe() {
|
371
|
+
this.handlers = {}
|
372
|
+
this.currentElement = null
|
373
|
+
}
|
374
|
+
|
375
|
+
EventScribe.prototype = {
|
376
|
+
makeHandlersRemove: function(element) {
|
377
|
+
for(var eventName in this.handlers) {
|
378
|
+
var handler = this.handlers[eventName]
|
379
|
+
this.handlers[eventName] = function(eventRecord) {
|
380
|
+
handler(eventRecord)
|
381
|
+
$(element).remove()
|
382
|
+
}
|
383
|
+
}
|
384
|
+
},
|
385
|
+
recordEventHandlers: function (context, behavior) {
|
386
|
+
if(this.currentElement !== context.element) {
|
387
|
+
if(this.currentElement !== null) {
|
388
|
+
this.makeHandlersRemove(this.currentElement)
|
389
|
+
this.applyEventHandlers(this.currentElement)
|
390
|
+
this.handlers = {}
|
391
|
+
}
|
392
|
+
this.currentElement = context.element
|
393
|
+
}
|
394
|
+
for(var eventName in behavior.eventHandlers) {
|
395
|
+
var oldHandler = this.handlers[eventName]
|
396
|
+
if(typeof oldHandler == "undefined") {
|
397
|
+
oldHandler = function(){}
|
398
|
+
}
|
399
|
+
this.handlers[eventName] = behavior.buildHandler(context, eventName, oldHandler)
|
400
|
+
}
|
401
|
+
},
|
402
|
+
applyEventHandlers: function(element) {
|
403
|
+
for(var eventName in this.handlers) {
|
404
|
+
$(element).bind(eventName, this.handlers[eventName])
|
405
|
+
}
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
409
|
+
function TransformFailedException(){}
|
410
|
+
function CouldntChooseException() { }
|
411
|
+
|
412
|
+
function RootContext() {
|
413
|
+
this.stashedElements = []
|
414
|
+
}
|
415
|
+
|
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
|
+
}
|
434
|
+
}
|
435
|
+
|
436
|
+
BehaviorCollection.prototype = {
|
437
|
+
//XXX: check if this is source of new slowdown
|
438
|
+
addBehavior: function(selector, behavior) {
|
439
|
+
if(isArray(behavior)) {
|
440
|
+
forEach(behavior, function(behaves){
|
441
|
+
this.addBehavior(selector, behaves)
|
442
|
+
}, this)
|
443
|
+
}
|
444
|
+
else if(behavior instanceof Behavior) {
|
445
|
+
this.insertBehavior(selector, behavior)
|
446
|
+
}
|
447
|
+
else if(behavior instanceof Selectabehavior) {
|
448
|
+
this.insertBehavior(selector, behavior)
|
449
|
+
}
|
450
|
+
else if(behavior instanceof Metabehavior) {
|
451
|
+
this.insertBehavior(selector, behavior)
|
452
|
+
}
|
453
|
+
else if(typeof behavior == "function"){
|
454
|
+
this.addBehavior(selector, behavior())
|
455
|
+
}
|
456
|
+
else {
|
457
|
+
var behavior = new Behavior(behavior)
|
458
|
+
this.addBehavior(selector, behavior)
|
459
|
+
}
|
460
|
+
},
|
461
|
+
insertBehavior: function(selector, behavior) {
|
462
|
+
behavior.lexicalOrder = this.lexicalCount
|
463
|
+
this.lexicalCount += 1
|
464
|
+
if(this.behaviors[selector] === undefined) {
|
465
|
+
this.selectors.push(selector)
|
466
|
+
this.behaviors[selector] = [behavior]
|
467
|
+
}
|
468
|
+
else {
|
469
|
+
this.behaviors[selector].push(behavior)
|
470
|
+
}
|
471
|
+
},
|
472
|
+
|
473
|
+
mutationEventTriggered: function(evnt){
|
474
|
+
if(this.eventQueue.length == 0){
|
475
|
+
log("mutation event - first")
|
476
|
+
this.enqueueEvent(evnt)
|
477
|
+
this.handleQueue()
|
478
|
+
}
|
479
|
+
else {
|
480
|
+
log("mutation event - queueing")
|
481
|
+
this.enqueueEvent(evnt)
|
482
|
+
}
|
483
|
+
},
|
484
|
+
enqueueEvent: function(evnt) {
|
485
|
+
var eventCovered = false
|
486
|
+
var uncovered = []
|
487
|
+
forEach(this.eventQueue, function(val) {
|
488
|
+
eventCovered = eventCovered || $.contains(val.target, evnt.target)
|
489
|
+
if (!($.contains(evnt.target, val.target))) {
|
490
|
+
uncovered.push(val)
|
491
|
+
}
|
492
|
+
})
|
493
|
+
if(!eventCovered) {
|
494
|
+
uncovered.unshift(evnt)
|
495
|
+
this.eventQueue = uncovered
|
496
|
+
}
|
497
|
+
},
|
498
|
+
handleQueue: function(){
|
499
|
+
while (this.eventQueue.length != 0){
|
500
|
+
this.applyAll(this.eventQueue[0].target)
|
501
|
+
this.eventQueue.shift()
|
502
|
+
}
|
503
|
+
},
|
504
|
+
applyBehaviorsTo: function(element, behaviors) {
|
505
|
+
var curContext,
|
506
|
+
context = new RootContext,
|
507
|
+
applyList = [],
|
508
|
+
scribe = new EventScribe
|
509
|
+
|
510
|
+
behaviors = behaviors.sort(function(left, right) {
|
511
|
+
if(left.priority != right.priority) {
|
512
|
+
if(left.priority === undefined) {
|
513
|
+
return -1
|
514
|
+
}
|
515
|
+
else if(right.priority === undefined) {
|
516
|
+
return 1
|
517
|
+
}
|
518
|
+
else {
|
519
|
+
return left.priority - right.priority
|
520
|
+
}
|
521
|
+
}
|
522
|
+
else {
|
523
|
+
return left.lexicalOrder - right.lexicalOrder
|
524
|
+
}
|
525
|
+
}
|
526
|
+
)
|
527
|
+
|
528
|
+
forEach(behaviors,
|
529
|
+
function(behavior){
|
530
|
+
//XXX This needs to have exception handling back
|
531
|
+
try {
|
532
|
+
curContext = behavior.inContext(context)
|
533
|
+
element = behavior.applyTransform(curContext, element)
|
534
|
+
|
535
|
+
context = curContext
|
536
|
+
context.element = element
|
537
|
+
|
538
|
+
scribe.recordEventHandlers(context, behavior)
|
539
|
+
}
|
540
|
+
catch(ex) {
|
541
|
+
if(ex instanceof TransformFailedException) {
|
542
|
+
log("!!! Transform failed")
|
543
|
+
}
|
544
|
+
else {
|
545
|
+
log(ex)
|
546
|
+
throw ex
|
547
|
+
}
|
548
|
+
}
|
549
|
+
}
|
550
|
+
)
|
551
|
+
$(element).data("ninja-visited", true)
|
552
|
+
|
553
|
+
scribe.applyEventHandlers(element)
|
554
|
+
|
555
|
+
return element
|
556
|
+
},
|
557
|
+
collectBehaviors: function(element, collection, behaviors) {
|
558
|
+
forEach(behaviors, function(val) {
|
559
|
+
try {
|
560
|
+
collection.push(val.choose(element))
|
561
|
+
}
|
562
|
+
catch(ex) {
|
563
|
+
if(ex instanceof CouldntChooseException) {
|
564
|
+
log("!!! couldn't choose")
|
565
|
+
}
|
566
|
+
else {
|
567
|
+
log(ex)
|
568
|
+
throw(ex)
|
569
|
+
}
|
570
|
+
}
|
571
|
+
})
|
572
|
+
},
|
573
|
+
//XXX Still doesn't quite handle the sub-behavior case - order of application
|
574
|
+
apply: function(element, startBehaviors, selectorIndex) {
|
575
|
+
var applicableBehaviors = [], len = this.selectors.length
|
576
|
+
this.collectBehaviors(element, applicableBehaviors, startBehaviors)
|
577
|
+
if (!$(element).data("ninja-visited")) {
|
578
|
+
if(typeof selectorIndex == "undefined") {
|
579
|
+
selectorIndex = 0
|
580
|
+
}
|
581
|
+
for(var j = selectorIndex; j < len; j++) {
|
582
|
+
if($(element).is(this.selectors[j])) {
|
583
|
+
this.collectBehaviors(element, applicableBehaviors, this.behaviors[this.selectors[j]])
|
584
|
+
}
|
585
|
+
}
|
586
|
+
}
|
587
|
+
this.applyBehaviorsTo(element, applicableBehaviors)
|
588
|
+
},
|
589
|
+
applyAll: function(root){
|
590
|
+
var len = this.selectors.length
|
591
|
+
for(var i = 0; i < len; i++) {
|
592
|
+
var collection = this
|
593
|
+
|
594
|
+
//Sizzle?
|
595
|
+
$(root).find(this.selectors[i]).each(
|
596
|
+
function(index, elem){
|
597
|
+
if (!$(elem).data("ninja-visited")) { //Pure optimization
|
598
|
+
collection.apply(elem, [], i)
|
599
|
+
}
|
600
|
+
}
|
601
|
+
)
|
602
|
+
}
|
603
|
+
}
|
604
|
+
}
|
605
|
+
|
606
|
+
function Metabehavior(setup, callback) {
|
607
|
+
setup(this)
|
608
|
+
this.chooser = callback
|
609
|
+
}
|
610
|
+
|
611
|
+
Metabehavior.prototype = {
|
612
|
+
choose: function(element) {
|
613
|
+
var chosen = this.chooser(element)
|
614
|
+
if(chosen !== undefined) {
|
615
|
+
return chosen.choose(element)
|
616
|
+
}
|
617
|
+
else {
|
618
|
+
throw new CouldntChooseException
|
619
|
+
}
|
620
|
+
}
|
621
|
+
}
|
622
|
+
|
623
|
+
//For these to be acceptable, I need to fit them into the pattern that
|
624
|
+
//Ninja.behavior accepts...
|
625
|
+
function Selectabehavior(menu) {
|
626
|
+
this.menu = menu
|
627
|
+
}
|
628
|
+
|
629
|
+
Selectabehavior.prototype = {
|
630
|
+
choose: function(element) {
|
631
|
+
for(var selector in this.menu) {
|
632
|
+
if($(element).is(selector)) {
|
633
|
+
return this.menu[selector].choose(element)
|
634
|
+
}
|
635
|
+
}
|
636
|
+
return null //XXX Should raise exception
|
637
|
+
}
|
638
|
+
}
|
639
|
+
|
640
|
+
function Behavior(handlers) {
|
641
|
+
this.helpers = {}
|
642
|
+
this.eventHandlers = []
|
643
|
+
this.lexicalOrder = 0
|
644
|
+
this.priority = 0
|
645
|
+
|
646
|
+
if (typeof handlers.transform == "function") {
|
647
|
+
this.transform = handlers.transform
|
648
|
+
delete handlers.transform
|
649
|
+
}
|
650
|
+
if (typeof handlers.helpers != "undefined"){
|
651
|
+
this.helpers = handlers.helpers
|
652
|
+
delete handlers.helpers
|
653
|
+
}
|
654
|
+
if (typeof handlers.priority != "undefined"){
|
655
|
+
this.priority = handlers.priority
|
656
|
+
}
|
657
|
+
delete handlers.priority
|
658
|
+
if (typeof handlers.events != "undefined") {
|
659
|
+
this.eventHandlers = handlers.events
|
660
|
+
}
|
661
|
+
else {
|
662
|
+
this.eventHandlers = handlers
|
663
|
+
}
|
664
|
+
|
665
|
+
return this
|
666
|
+
}
|
667
|
+
Behavior.prototype = {
|
668
|
+
//XXX applyTo?
|
669
|
+
apply: function(elem) {
|
670
|
+
var context = this.inContext({})
|
671
|
+
|
672
|
+
elem = this.applyTransform(context, elem)
|
673
|
+
$(elem).data("ninja-visited", true)
|
674
|
+
|
675
|
+
this.applyEventHandlers(context, elem)
|
676
|
+
|
677
|
+
return elem
|
678
|
+
},
|
679
|
+
priority: function(value) {
|
680
|
+
this.priority = value
|
681
|
+
return this
|
682
|
+
},
|
683
|
+
choose: function(element) {
|
684
|
+
return this
|
685
|
+
},
|
686
|
+
inContext: function(basedOn) {
|
687
|
+
function Context() {}
|
688
|
+
Context.prototype = basedOn
|
689
|
+
return Ninja.tools.enrich(new Context, this.helpers)
|
690
|
+
},
|
691
|
+
applyTransform: function(context, elem) {
|
692
|
+
var previousElem = elem
|
693
|
+
var newElem = this.transform.call(context, elem)
|
694
|
+
if(newElem === undefined) {
|
695
|
+
return previousElem
|
696
|
+
}
|
697
|
+
else {
|
698
|
+
return newElem
|
699
|
+
}
|
700
|
+
},
|
701
|
+
applyEventHandlers: function(context, elem) {
|
702
|
+
for(var eventName in this.eventHandlers) {
|
703
|
+
var handler = this.eventHandlers[eventName]
|
704
|
+
$(elem).bind(eventName, this.makeHandler.call(context, handler))
|
705
|
+
}
|
706
|
+
return elem
|
707
|
+
},
|
708
|
+
recordEventHandlers: function(scribe, context) {
|
709
|
+
for(var eventName in this.eventHandlers) {
|
710
|
+
scribe.recordHandler(this, eventName, function(oldHandler){
|
711
|
+
return this.makeHandler.call(context, this.eventHandlers[eventName], oldHandler)
|
712
|
+
}
|
713
|
+
)
|
714
|
+
}
|
715
|
+
},
|
716
|
+
buildHandler: function(context, eventName, previousHandler) {
|
717
|
+
var handle
|
718
|
+
var stopDefault = true
|
719
|
+
var stopPropagate = true
|
720
|
+
var stopImmediate = false
|
721
|
+
var config = this.eventHandlers[eventName]
|
722
|
+
|
723
|
+
if (typeof config == "function") {
|
724
|
+
handle = config
|
725
|
+
}
|
726
|
+
else {
|
727
|
+
handle = config[0]
|
728
|
+
config = config.slice(1,config.length)
|
729
|
+
var len = config.length
|
730
|
+
for(var i = 0; i < len; i++) {
|
731
|
+
if (config[i] == "default") {
|
732
|
+
stopDefault = false
|
733
|
+
}
|
734
|
+
if (config[i] == "propagate") {
|
735
|
+
stopPropagate = false
|
736
|
+
}
|
737
|
+
if (config[i] == "immediate" || config[i] == "other") {
|
738
|
+
stopImmediate = false
|
739
|
+
}
|
740
|
+
}
|
741
|
+
}
|
742
|
+
var handler = function(eventRecord) {
|
743
|
+
handle.call(context, eventRecord, this, previousHandler)
|
744
|
+
return !stopDefault
|
745
|
+
}
|
746
|
+
if(stopDefault) {
|
747
|
+
handler = this.prependAction(handler, function(eventRecord) {
|
748
|
+
eventRecord.preventDefault()
|
749
|
+
})
|
750
|
+
}
|
751
|
+
if(stopPropagate) {
|
752
|
+
handler = this.prependAction(handler, function(eventRecord) {
|
753
|
+
eventRecord.stopPropagation()
|
754
|
+
})
|
755
|
+
}
|
756
|
+
if (stopImmediate) {
|
757
|
+
handler = this.prependAction(handler, function(eventRecord) {
|
758
|
+
eventRecord.stopImmediatePropagation()
|
759
|
+
})
|
760
|
+
}
|
761
|
+
|
762
|
+
return handler
|
763
|
+
},
|
764
|
+
prependAction: function(handler, doWhat) {
|
765
|
+
return function(eventRecord) {
|
766
|
+
doWhat(eventRecord)
|
767
|
+
handler(eventRecord)
|
768
|
+
}
|
769
|
+
},
|
770
|
+
transform: function(elem){
|
771
|
+
return elem
|
772
|
+
}
|
773
|
+
}
|
774
|
+
|
775
|
+
return Ninja;
|
776
|
+
})();
|
777
|
+
|
778
|
+
(function() {
|
779
|
+
function standardBehaviors(ninja){
|
780
|
+
return {
|
781
|
+
// START READING HERE
|
782
|
+
//Stock behaviors
|
783
|
+
|
784
|
+
//Converts either a link or a form to send its requests via AJAX - we eval
|
785
|
+
//the Javascript we get back. We get an busy overlay if configured to do
|
786
|
+
//so.
|
787
|
+
//
|
788
|
+
//This farms out the actual behavior to submitsAsAjaxLink and
|
789
|
+
//submitsAsAjaxForm, c.f.
|
790
|
+
submitsAsAjax: function(configs) {
|
791
|
+
return new ninja.chooses(function(meta) {
|
792
|
+
meta.asLink = Ninja.submitsAsAjaxLink(configs),
|
793
|
+
meta.asForm = Ninja.submitsAsAjaxForm(configs)
|
794
|
+
},
|
795
|
+
function(elem) {
|
796
|
+
switch(elem.tagName.toLowerCase()) {
|
797
|
+
case "a": return this.asLink
|
798
|
+
case "form": return this.asForm
|
799
|
+
}
|
800
|
+
})
|
801
|
+
},
|
802
|
+
|
803
|
+
|
804
|
+
//Converts a link to send its GET request via Ajax - we assume that we get
|
805
|
+
//Javascript back, which is eval'd. While we're waiting, we'll throw up a
|
806
|
+
//busy overlay if configured to do so. By default, we don't use a busy
|
807
|
+
//overlay.
|
808
|
+
//
|
809
|
+
//Ninja.submitAsAjaxLink({
|
810
|
+
// busyElement: function(elem) { elem.parent }
|
811
|
+
//})
|
812
|
+
//
|
813
|
+
submitsAsAjaxLink: function(configs) {
|
814
|
+
if(!(configs instanceof Object)) {
|
815
|
+
configs = { busyElement: undefined }
|
816
|
+
}
|
817
|
+
return new ninja.does({
|
818
|
+
priority: 10,
|
819
|
+
helpers: {
|
820
|
+
findOverlay: function(elem) {
|
821
|
+
return Ninja.tools.deriveElementsFrom(elem, configs.busyElement)
|
822
|
+
}
|
823
|
+
},
|
824
|
+
events: {
|
825
|
+
click: function(evnt) {
|
826
|
+
var overlay = Ninja.tools.busyOverlay(this.findOverlay(evnt.target))
|
827
|
+
var submitter = Ninja.tools.ajaxSubmitter()
|
828
|
+
submitter.action = evnt.target.href
|
829
|
+
submitter.method = Ninja.tools.extractMethod(evnt.target)
|
830
|
+
|
831
|
+
submitter.onResponse = function(xhr, statusTxt) {
|
832
|
+
overlay.remove()
|
833
|
+
}
|
834
|
+
overlay.affix()
|
835
|
+
submitter.submit()
|
836
|
+
}
|
837
|
+
}
|
838
|
+
})
|
839
|
+
},
|
840
|
+
|
841
|
+
//Converts a form to send its request via Ajax - we assume that we get
|
842
|
+
//Javascript back, which is eval'd. We pull the method from the form:
|
843
|
+
//either from the method attribute itself, a data-method attribute or a
|
844
|
+
//Method input. While we're waiting, we'll throw up a busy overlay if
|
845
|
+
//configured to do so. By default, we use the form itself as the busy
|
846
|
+
//element.
|
847
|
+
//
|
848
|
+
//Ninja.submitAsAjaxForm({
|
849
|
+
// busyElement: function(elem) { elem.parent }
|
850
|
+
//})
|
851
|
+
//
|
852
|
+
submitsAsAjaxForm: function(configs) {
|
853
|
+
if(!(configs instanceof Object)) {
|
854
|
+
configs = { busyElement: undefined }
|
855
|
+
}
|
856
|
+
return new ninja.does({
|
857
|
+
priority: 20,
|
858
|
+
helpers: {
|
859
|
+
findOverlay: function(elem) {
|
860
|
+
return Ninja.tools.deriveElementsFrom(elem, configs.busyElement)
|
861
|
+
}
|
862
|
+
},
|
863
|
+
events: {
|
864
|
+
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()
|
868
|
+
submitter.action = evnt.target.action
|
869
|
+
submitter.method = Ninja.tools.extractMethod(evnt.target, submitter.formData)
|
870
|
+
|
871
|
+
submitter.onResponse = function(xhr, statusTxt) {
|
872
|
+
overlay.remove()
|
873
|
+
}
|
874
|
+
overlay.affix()
|
875
|
+
submitter.submit()
|
876
|
+
}
|
877
|
+
}
|
878
|
+
})
|
879
|
+
},
|
880
|
+
|
881
|
+
|
882
|
+
//Converts a whole form into a link that submits via AJAX. The intention
|
883
|
+
//is that you create a <form> elements with hidden inputs and a single
|
884
|
+
//submit button - then when we transform it, you don't lose anything in
|
885
|
+
//terms of user interface. Like submitsAsAjaxForm, it will put up a
|
886
|
+
//busy overlay - by default we overlay the element itself
|
887
|
+
//
|
888
|
+
//this.becomesAjaxLink({
|
889
|
+
// busyElement: function(elem) { $("#user-notification") }
|
890
|
+
//})
|
891
|
+
becomesAjaxLink: function(configs) {
|
892
|
+
if(!(configs instanceof Object)) {
|
893
|
+
configs = { busyElement: undefined }
|
894
|
+
}
|
895
|
+
|
896
|
+
configs = Ninja.tools.ensureDefaults(configs, {
|
897
|
+
busyElement: undefined,
|
898
|
+
retainAttributes: ["id", "class", "lang", "dir", "title", "data-.*"]
|
899
|
+
})
|
900
|
+
|
901
|
+
return [ Ninja.submitsAsAjax(configs), Ninja.becomesLink(configs) ]
|
902
|
+
},
|
903
|
+
|
904
|
+
//Replaces a form with a link - the text of the link is based on the Submit
|
905
|
+
//input of the form. The form itself is pulled out of the document until
|
906
|
+
//the link is clicked, at which point, it gets stuffed back into the
|
907
|
+
//document and submitted, so the link behaves exactly link submitting the
|
908
|
+
//form with its default inputs. The motivation is to use hidden-input-only
|
909
|
+
//forms for POST interactions, which Javascript can convert into links if
|
910
|
+
//you want.
|
911
|
+
becomesLink: function(configs) {
|
912
|
+
configs = Ninja.tools.ensureDefaults(configs, {
|
913
|
+
retainAttributes: ["id", "class", "lang", "dir", "title", "rel", "data-.*"]
|
914
|
+
})
|
915
|
+
|
916
|
+
return new ninja.does({
|
917
|
+
priority: 30,
|
918
|
+
transform: function(form){
|
919
|
+
var linkText
|
920
|
+
if ((images = $('input[type=image]', form)).size() > 0){
|
921
|
+
image = images[0]
|
922
|
+
linkText = "<img src='" + image.src + "' alt='" + image.alt +"'";
|
923
|
+
}
|
924
|
+
else if((submits = $('input[type=submit]', form)).size() > 0) {
|
925
|
+
submit = submits[0]
|
926
|
+
if(submits.size() > 1) {
|
927
|
+
log("Multiple submits. Using: " + submit)
|
928
|
+
}
|
929
|
+
linkText = submit.value
|
930
|
+
}
|
931
|
+
else {
|
932
|
+
log("Couldn't find a submit input in form");
|
933
|
+
}
|
934
|
+
|
935
|
+
var link = $("<a rel='nofollow' href='#'>" + linkText + "</a>")
|
936
|
+
Ninja.tools.copyAttributes(form, link, configs.retainAttributes)
|
937
|
+
this.stash($(form).replaceWith(link))
|
938
|
+
return link
|
939
|
+
},
|
940
|
+
events: {
|
941
|
+
click: function(evnt, elem){
|
942
|
+
this.cascadeEvent("submit")
|
943
|
+
}
|
944
|
+
}
|
945
|
+
})
|
946
|
+
|
947
|
+
},
|
948
|
+
|
949
|
+
//Use for elements that should be transient. For instance, the default
|
950
|
+
//behavior of failed AJAX calls is to insert a message into a
|
951
|
+
//div#messages with a "flash" class. You can use this behavior to have3A2
|
952
|
+
//those disappear after a few seconds.
|
953
|
+
//
|
954
|
+
//Configs:
|
955
|
+
//{ lifetime: 10000, diesFor: 600 }
|
956
|
+
|
957
|
+
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
|
+
}
|
967
|
+
|
968
|
+
return new ninja.does({
|
969
|
+
priority: 100,
|
970
|
+
transform: function(elem) {
|
971
|
+
$(elem).delay(configs.lifetime).slideUp(configs.diesFor, function(){
|
972
|
+
$(elem).remove()})
|
973
|
+
},
|
974
|
+
events: {
|
975
|
+
click: function(evnt, elem) {
|
976
|
+
$(elem).remove();
|
977
|
+
}
|
978
|
+
}
|
979
|
+
})
|
980
|
+
}
|
981
|
+
};
|
982
|
+
}
|
983
|
+
|
984
|
+
Ninja.packageBehaviors(standardBehaviors)
|
985
|
+
})();
|
986
|
+
|
987
|
+
|
988
|
+
//This exists to carry over interfaces from earlier versions of Ninjascript. Likely, it will be removed from future versions of NinjaScript
|
989
|
+
( function($) {
|
990
|
+
$.extend(
|
991
|
+
{
|
992
|
+
ninja: Ninja,
|
993
|
+
behavior: Ninja.behavior
|
994
|
+
}
|
995
|
+
);
|
996
|
+
}
|
997
|
+
)(jQuery);
|