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.
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);