mizugumo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.document +5 -0
  2. data/Gemfile +11 -0
  3. data/Gemfile.lock +30 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +87 -0
  6. data/Rakefile +70 -0
  7. data/VERSION +1 -0
  8. data/lib/generators/mizugumo/install/install_generator.rb +59 -0
  9. data/lib/generators/mizugumo/install/templates/images/ui/spinner.gif +0 -0
  10. data/lib/generators/mizugumo/install/templates/javascripts/jquery-1.4.2.js +6240 -0
  11. data/lib/generators/mizugumo/install/templates/javascripts/jquery.ninja_script.js +997 -0
  12. data/lib/generators/mizugumo/install/templates/stylesheets/mizugumo.css +46 -0
  13. data/lib/generators/mizugumo/install/templates/stylesheets/sass/mizugumo.sass +48 -0
  14. data/lib/generators/rails/mizugumo/erb_generator.rb +21 -0
  15. data/lib/generators/rails/mizugumo/haml_generator.rb +21 -0
  16. data/lib/generators/rails/mizugumo/scaffold_controller_generator.rb +26 -0
  17. data/lib/generators/rails/mizugumo/templates/_form.html.erb +23 -0
  18. data/lib/generators/rails/mizugumo/templates/_form.html.haml +21 -0
  19. data/lib/generators/rails/mizugumo/templates/_row.html.erb +9 -0
  20. data/lib/generators/rails/mizugumo/templates/_row.html.haml +7 -0
  21. data/lib/generators/rails/mizugumo/templates/controller.rb +93 -0
  22. data/lib/generators/rails/mizugumo/templates/create.js.erb +2 -0
  23. data/lib/generators/rails/mizugumo/templates/destroy.js.erb +1 -0
  24. data/lib/generators/rails/mizugumo/templates/edit.html.erb +6 -0
  25. data/lib/generators/rails/mizugumo/templates/edit.html.haml +7 -0
  26. data/lib/generators/rails/mizugumo/templates/edit.js.erb +4 -0
  27. data/lib/generators/rails/mizugumo/templates/index.html.erb +18 -0
  28. data/lib/generators/rails/mizugumo/templates/index.html.haml +13 -0
  29. data/lib/generators/rails/mizugumo/templates/new.html.erb +5 -0
  30. data/lib/generators/rails/mizugumo/templates/new.html.haml +5 -0
  31. data/lib/generators/rails/mizugumo/templates/new.js.erb +2 -0
  32. data/lib/generators/rails/mizugumo/templates/show.html.erb +10 -0
  33. data/lib/generators/rails/mizugumo/templates/show.html.haml +9 -0
  34. data/lib/generators/rails/mizugumo/templates/update.js.erb +4 -0
  35. data/lib/generators/rails/mizugumo/view_generator.rb +44 -0
  36. data/lib/mizugumo.rb +13 -0
  37. data/lib/mizugumo_link_helper.rb +58 -0
  38. data/spec/spec_helper.rb +0 -0
  39. 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);