joosy 0.1.0.RC1

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 (98) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +159 -0
  4. data/Guardfile +30 -0
  5. data/MIT-LICENSE +21 -0
  6. data/README.rdoc +3 -0
  7. data/Rakefile +14 -0
  8. data/app/assets/javascripts/joosy.js.coffee +7 -0
  9. data/app/assets/javascripts/joosy/core/application.js.coffee +28 -0
  10. data/app/assets/javascripts/joosy/core/form.js.coffee +87 -0
  11. data/app/assets/javascripts/joosy/core/helpers.js.coffee +6 -0
  12. data/app/assets/javascripts/joosy/core/joosy.js.coffee +65 -0
  13. data/app/assets/javascripts/joosy/core/layout.js.coffee +47 -0
  14. data/app/assets/javascripts/joosy/core/modules/container.js.coffee +59 -0
  15. data/app/assets/javascripts/joosy/core/modules/events.js.coffee +35 -0
  16. data/app/assets/javascripts/joosy/core/modules/filters.js.coffee +39 -0
  17. data/app/assets/javascripts/joosy/core/modules/log.js.coffee +15 -0
  18. data/app/assets/javascripts/joosy/core/modules/module.js.coffee +43 -0
  19. data/app/assets/javascripts/joosy/core/modules/renderer.js.coffee +116 -0
  20. data/app/assets/javascripts/joosy/core/modules/time_manager.js.coffee +25 -0
  21. data/app/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +58 -0
  22. data/app/assets/javascripts/joosy/core/page.js.coffee +156 -0
  23. data/app/assets/javascripts/joosy/core/preloader.js.coffee +5 -0
  24. data/app/assets/javascripts/joosy/core/resource/generic.js.coffee +61 -0
  25. data/app/assets/javascripts/joosy/core/resource/rest.js.coffee +76 -0
  26. data/app/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +48 -0
  27. data/app/assets/javascripts/joosy/core/router.js.coffee +89 -0
  28. data/app/assets/javascripts/joosy/core/templaters/rails_jst.js.coffee +20 -0
  29. data/app/assets/javascripts/joosy/core/widget.js.coffee +41 -0
  30. data/app/assets/javascripts/joosy/preloaders/caching.js.coffee +94 -0
  31. data/app/assets/javascripts/joosy/preloaders/inline.js.coffee +55 -0
  32. data/app/helpers/joosy/sprockets_helper.rb +23 -0
  33. data/app/views/layouts/json_wrapper.json.erb +1 -0
  34. data/joosy.gemspec +25 -0
  35. data/lib/joosy.rb +9 -0
  36. data/lib/joosy/forms.rb +47 -0
  37. data/lib/joosy/rails/engine.rb +17 -0
  38. data/lib/joosy/rails/version.rb +5 -0
  39. data/lib/rails/generators/joosy/application_generator.rb +41 -0
  40. data/lib/rails/generators/joosy/joosy_base.rb +30 -0
  41. data/lib/rails/generators/joosy/layout_generator.rb +32 -0
  42. data/lib/rails/generators/joosy/page_generator.rb +44 -0
  43. data/lib/rails/generators/joosy/preloader_generator.rb +32 -0
  44. data/lib/rails/generators/joosy/resource_generator.rb +29 -0
  45. data/lib/rails/generators/joosy/templates/app.js.coffee +10 -0
  46. data/lib/rails/generators/joosy/templates/app/helpers/application.js.coffee +4 -0
  47. data/lib/rails/generators/joosy/templates/app/layouts/application.js.coffee +2 -0
  48. data/lib/rails/generators/joosy/templates/app/layouts/template.js.coffee +2 -0
  49. data/lib/rails/generators/joosy/templates/app/pages/application.js.coffee +1 -0
  50. data/lib/rails/generators/joosy/templates/app/pages/template.js.coffee +5 -0
  51. data/lib/rails/generators/joosy/templates/app/pages/welcome/index.js.coffee +22 -0
  52. data/lib/rails/generators/joosy/templates/app/resources/template.js.coffee +2 -0
  53. data/lib/rails/generators/joosy/templates/app/routes.js.coffee +8 -0
  54. data/lib/rails/generators/joosy/templates/app/templates/layouts/application.jst.hamlc +2 -0
  55. data/lib/rails/generators/joosy/templates/app/templates/pages/welcome/index.jst.hamlc +7 -0
  56. data/lib/rails/generators/joosy/templates/app/widgets/template.js.coffee +2 -0
  57. data/lib/rails/generators/joosy/templates/app_controller.rb +9 -0
  58. data/lib/rails/generators/joosy/templates/app_preloader.js.coffee.erb +13 -0
  59. data/lib/rails/generators/joosy/templates/preload.html.erb +26 -0
  60. data/lib/rails/generators/joosy/templates/preload.html.haml +19 -0
  61. data/lib/rails/generators/joosy/widget_generator.rb +32 -0
  62. data/spec/javascripts/helpers/spec_helper.js.coffee +44 -0
  63. data/spec/javascripts/joosy/core/application_spec.js.coffee +40 -0
  64. data/spec/javascripts/joosy/core/form_spec.js.coffee +141 -0
  65. data/spec/javascripts/joosy/core/joosy_spec.js.coffee +72 -0
  66. data/spec/javascripts/joosy/core/layout_spec.js.coffee +50 -0
  67. data/spec/javascripts/joosy/core/modules/container_spec.js.coffee +92 -0
  68. data/spec/javascripts/joosy/core/modules/events_spec.js.coffee +53 -0
  69. data/spec/javascripts/joosy/core/modules/filters_spec.js.coffee +71 -0
  70. data/spec/javascripts/joosy/core/modules/log_spec.js.coffee +15 -0
  71. data/spec/javascripts/joosy/core/modules/module_spec.js.coffee +47 -0
  72. data/spec/javascripts/joosy/core/modules/renderer_spec.js.coffee +149 -0
  73. data/spec/javascripts/joosy/core/modules/time_manager_spec.js.coffee +25 -0
  74. data/spec/javascripts/joosy/core/modules/widget_manager_spec.js.coffee +75 -0
  75. data/spec/javascripts/joosy/core/page_spec.js.coffee +175 -0
  76. data/spec/javascripts/joosy/core/resource/generic_spec.js.coffee +35 -0
  77. data/spec/javascripts/joosy/core/resource/rest_collection_spec.js.coffee +65 -0
  78. data/spec/javascripts/joosy/core/resource/rest_spec.js.coffee +108 -0
  79. data/spec/javascripts/joosy/core/router_spec.js.coffee +123 -0
  80. data/spec/javascripts/joosy/core/templaters/rails_jst_spec.js.coffee +25 -0
  81. data/spec/javascripts/joosy/core/widget_spec.js.coffee +51 -0
  82. data/spec/javascripts/joosy/preloaders/caching_spec.js.coffee +36 -0
  83. data/spec/javascripts/joosy/preloaders/inline_spec.js.coffee +16 -0
  84. data/spec/javascripts/support/assets/coolface.jpg +0 -0
  85. data/spec/javascripts/support/assets/okay.jpg +0 -0
  86. data/spec/javascripts/support/assets/test.js +1 -0
  87. data/spec/javascripts/support/jasmine.yml +74 -0
  88. data/spec/javascripts/support/jasmine_config.rb +23 -0
  89. data/spec/javascripts/support/jasmine_runner.rb +32 -0
  90. data/spec/javascripts/support/sinon-1.3.1.js +3469 -0
  91. data/spec/javascripts/support/sinon-ie-1.3.1.js +82 -0
  92. data/vendor/assets/javascripts/base64.js +135 -0
  93. data/vendor/assets/javascripts/inflection.js +656 -0
  94. data/vendor/assets/javascripts/jquery.form.js +980 -0
  95. data/vendor/assets/javascripts/jquery.hashchange.js +390 -0
  96. data/vendor/assets/javascripts/metamorph.js +409 -0
  97. data/vendor/assets/javascripts/sugar.js +6040 -0
  98. metadata +232 -0
@@ -0,0 +1,390 @@
1
+ /*!
2
+ * jQuery hashchange event - v1.3 - 7/21/2010
3
+ * http://benalman.com/projects/jquery-hashchange-plugin/
4
+ *
5
+ * Copyright (c) 2010 "Cowboy" Ben Alman
6
+ * Dual licensed under the MIT and GPL licenses.
7
+ * http://benalman.com/about/license/
8
+ */
9
+
10
+ // Script: jQuery hashchange event
11
+ //
12
+ // *Version: 1.3, Last updated: 7/21/2010*
13
+ //
14
+ // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
15
+ // GitHub - http://github.com/cowboy/jquery-hashchange/
16
+ // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
17
+ // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
18
+ //
19
+ // About: License
20
+ //
21
+ // Copyright (c) 2010 "Cowboy" Ben Alman,
22
+ // Dual licensed under the MIT and GPL licenses.
23
+ // http://benalman.com/about/license/
24
+ //
25
+ // About: Examples
26
+ //
27
+ // These working examples, complete with fully commented code, illustrate a few
28
+ // ways in which this plugin can be used.
29
+ //
30
+ // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
31
+ // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
32
+ //
33
+ // About: Support and Testing
34
+ //
35
+ // Information about what version or versions of jQuery this plugin has been
36
+ // tested with, what browsers it has been tested in, and where the unit tests
37
+ // reside (so you can test it yourself).
38
+ //
39
+ // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
40
+ // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
41
+ // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
42
+ // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
43
+ //
44
+ // About: Known issues
45
+ //
46
+ // While this jQuery hashchange event implementation is quite stable and
47
+ // robust, there are a few unfortunate browser bugs surrounding expected
48
+ // hashchange event-based behaviors, independent of any JavaScript
49
+ // window.onhashchange abstraction. See the following examples for more
50
+ // information:
51
+ //
52
+ // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
53
+ // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
54
+ // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
55
+ // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
56
+ //
57
+ // Also note that should a browser natively support the window.onhashchange
58
+ // event, but not report that it does, the fallback polling loop will be used.
59
+ //
60
+ // About: Release History
61
+ //
62
+ // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
63
+ // "removable" for mobile-only development. Added IE6/7 document.title
64
+ // support. Attempted to make Iframe as hidden as possible by using
65
+ // techniques from http://www.paciellogroup.com/blog/?p=604. Added
66
+ // support for the "shortcut" format $(window).hashchange( fn ) and
67
+ // $(window).hashchange() like jQuery provides for built-in events.
68
+ // Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
69
+ // lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
70
+ // and <jQuery.fn.hashchange.src> properties plus document-domain.html
71
+ // file to address access denied issues when setting document.domain in
72
+ // IE6/7.
73
+ // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
74
+ // from a page on another domain would cause an error in Safari 4. Also,
75
+ // IE6/7 Iframe is now inserted after the body (this actually works),
76
+ // which prevents the page from scrolling when the event is first bound.
77
+ // Event can also now be bound before DOM ready, but it won't be usable
78
+ // before then in IE6/7.
79
+ // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
80
+ // where browser version is incorrectly reported as 8.0, despite
81
+ // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
82
+ // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
83
+ // window.onhashchange functionality into a separate plugin for users
84
+ // who want just the basic event & back button support, without all the
85
+ // extra awesomeness that BBQ provides. This plugin will be included as
86
+ // part of jQuery BBQ, but also be available separately.
87
+
88
+ (function($,window,undefined){
89
+ '$:nomunge'; // Used by YUI compressor.
90
+
91
+ // Reused string.
92
+ var str_hashchange = 'hashchange',
93
+
94
+ // Method / object references.
95
+ doc = document,
96
+ fake_onhashchange,
97
+ special = $.event.special,
98
+
99
+ // Does the browser support window.onhashchange? Note that IE8 running in
100
+ // IE7 compatibility mode reports true for 'onhashchange' in window, even
101
+ // though the event isn't supported, so also test document.documentMode.
102
+ doc_mode = doc.documentMode,
103
+ supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
104
+
105
+ // Get location.hash (or what you'd expect location.hash to be) sans any
106
+ // leading #. Thanks for making this necessary, Firefox!
107
+ function get_fragment( url ) {
108
+ url = url || location.href;
109
+ return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
110
+ };
111
+
112
+ // Method: jQuery.fn.hashchange
113
+ //
114
+ // Bind a handler to the window.onhashchange event or trigger all bound
115
+ // window.onhashchange event handlers. This behavior is consistent with
116
+ // jQuery's built-in event handlers.
117
+ //
118
+ // Usage:
119
+ //
120
+ // > jQuery(window).hashchange( [ handler ] );
121
+ //
122
+ // Arguments:
123
+ //
124
+ // handler - (Function) Optional handler to be bound to the hashchange
125
+ // event. This is a "shortcut" for the more verbose form:
126
+ // jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
127
+ // all bound window.onhashchange event handlers will be triggered. This
128
+ // is a shortcut for the more verbose
129
+ // jQuery(window).trigger( 'hashchange' ). These forms are described in
130
+ // the <hashchange event> section.
131
+ //
132
+ // Returns:
133
+ //
134
+ // (jQuery) The initial jQuery collection of elements.
135
+
136
+ // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
137
+ // $(elem).hashchange() for triggering, like jQuery does for built-in events.
138
+ $.fn[ str_hashchange ] = function( fn ) {
139
+ return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
140
+ };
141
+
142
+ // Property: jQuery.fn.hashchange.delay
143
+ //
144
+ // The numeric interval (in milliseconds) at which the <hashchange event>
145
+ // polling loop executes. Defaults to 50.
146
+
147
+ // Property: jQuery.fn.hashchange.domain
148
+ //
149
+ // If you're setting document.domain in your JavaScript, and you want hash
150
+ // history to work in IE6/7, not only must this property be set, but you must
151
+ // also set document.domain BEFORE jQuery is loaded into the page. This
152
+ // property is only applicable if you are supporting IE6/7 (or IE8 operating
153
+ // in "IE7 compatibility" mode).
154
+ //
155
+ // In addition, the <jQuery.fn.hashchange.src> property must be set to the
156
+ // path of the included "document-domain.html" file, which can be renamed or
157
+ // modified if necessary (note that the document.domain specified must be the
158
+ // same in both your main JavaScript as well as in this file).
159
+ //
160
+ // Usage:
161
+ //
162
+ // jQuery.fn.hashchange.domain = document.domain;
163
+
164
+ // Property: jQuery.fn.hashchange.src
165
+ //
166
+ // If, for some reason, you need to specify an Iframe src file (for example,
167
+ // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
168
+ // do so using this property. Note that when using this property, history
169
+ // won't be recorded in IE6/7 until the Iframe src file loads. This property
170
+ // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
171
+ // compatibility" mode).
172
+ //
173
+ // Usage:
174
+ //
175
+ // jQuery.fn.hashchange.src = 'path/to/file.html';
176
+
177
+ $.fn[ str_hashchange ].delay = 50;
178
+ /*
179
+ $.fn[ str_hashchange ].domain = null;
180
+ $.fn[ str_hashchange ].src = null;
181
+ */
182
+
183
+ // Event: hashchange event
184
+ //
185
+ // Fired when location.hash changes. In browsers that support it, the native
186
+ // HTML5 window.onhashchange event is used, otherwise a polling loop is
187
+ // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
188
+ // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
189
+ // compatibility" mode), a hidden Iframe is created to allow the back button
190
+ // and hash-based history to work.
191
+ //
192
+ // Usage as described in <jQuery.fn.hashchange>:
193
+ //
194
+ // > // Bind an event handler.
195
+ // > jQuery(window).hashchange( function(e) {
196
+ // > var hash = location.hash;
197
+ // > ...
198
+ // > });
199
+ // >
200
+ // > // Manually trigger the event handler.
201
+ // > jQuery(window).hashchange();
202
+ //
203
+ // A more verbose usage that allows for event namespacing:
204
+ //
205
+ // > // Bind an event handler.
206
+ // > jQuery(window).bind( 'hashchange', function(e) {
207
+ // > var hash = location.hash;
208
+ // > ...
209
+ // > });
210
+ // >
211
+ // > // Manually trigger the event handler.
212
+ // > jQuery(window).trigger( 'hashchange' );
213
+ //
214
+ // Additional Notes:
215
+ //
216
+ // * The polling loop and Iframe are not created until at least one handler
217
+ // is actually bound to the 'hashchange' event.
218
+ // * If you need the bound handler(s) to execute immediately, in cases where
219
+ // a location.hash exists on page load, via bookmark or page refresh for
220
+ // example, use jQuery(window).hashchange() or the more verbose
221
+ // jQuery(window).trigger( 'hashchange' ).
222
+ // * The event can be bound before DOM ready, but since it won't be usable
223
+ // before then in IE6/7 (due to the necessary Iframe), recommended usage is
224
+ // to bind it inside a DOM ready handler.
225
+
226
+ // Override existing $.event.special.hashchange methods (allowing this plugin
227
+ // to be defined after jQuery BBQ in BBQ's source code).
228
+ special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
229
+
230
+ // Called only when the first 'hashchange' event is bound to window.
231
+ setup: function() {
232
+ // If window.onhashchange is supported natively, there's nothing to do..
233
+ if ( supports_onhashchange ) { return false; }
234
+
235
+ // Otherwise, we need to create our own. And we don't want to call this
236
+ // until the user binds to the event, just in case they never do, since it
237
+ // will create a polling loop and possibly even a hidden Iframe.
238
+ $( fake_onhashchange.start );
239
+ },
240
+
241
+ // Called only when the last 'hashchange' event is unbound from window.
242
+ teardown: function() {
243
+ // If window.onhashchange is supported natively, there's nothing to do..
244
+ if ( supports_onhashchange ) { return false; }
245
+
246
+ // Otherwise, we need to stop ours (if possible).
247
+ $( fake_onhashchange.stop );
248
+ }
249
+
250
+ });
251
+
252
+ // fake_onhashchange does all the work of triggering the window.onhashchange
253
+ // event for browsers that don't natively support it, including creating a
254
+ // polling loop to watch for hash changes and in IE 6/7 creating a hidden
255
+ // Iframe to enable back and forward.
256
+ fake_onhashchange = (function(){
257
+ var self = {},
258
+ timeout_id,
259
+
260
+ // Remember the initial hash so it doesn't get triggered immediately.
261
+ last_hash = get_fragment(),
262
+
263
+ fn_retval = function(val){ return val; },
264
+ history_set = fn_retval,
265
+ history_get = fn_retval;
266
+
267
+ // Start the polling loop.
268
+ self.start = function() {
269
+ timeout_id || poll();
270
+ };
271
+
272
+ // Stop the polling loop.
273
+ self.stop = function() {
274
+ timeout_id && clearTimeout( timeout_id );
275
+ timeout_id = undefined;
276
+ };
277
+
278
+ // This polling loop checks every $.fn.hashchange.delay milliseconds to see
279
+ // if location.hash has changed, and triggers the 'hashchange' event on
280
+ // window when necessary.
281
+ function poll() {
282
+ var hash = get_fragment(),
283
+ history_hash = history_get( last_hash );
284
+
285
+ if ( hash !== last_hash ) {
286
+ history_set( last_hash = hash, history_hash );
287
+
288
+ $(window).trigger( str_hashchange );
289
+
290
+ } else if ( history_hash !== last_hash ) {
291
+ location.href = location.href.replace( /#.*/, '' ) + history_hash;
292
+ }
293
+
294
+ timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
295
+ };
296
+
297
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
298
+ // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
299
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
300
+ $.browser.msie && !supports_onhashchange && (function(){
301
+ // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
302
+ // when running in "IE7 compatibility" mode.
303
+
304
+ var iframe,
305
+ iframe_src;
306
+
307
+ // When the event is bound and polling starts in IE 6/7, create a hidden
308
+ // Iframe for history handling.
309
+ self.start = function(){
310
+ if ( !iframe ) {
311
+ iframe_src = $.fn[ str_hashchange ].src;
312
+ iframe_src = iframe_src && iframe_src + get_fragment();
313
+
314
+ // Create hidden Iframe. Attempt to make Iframe as hidden as possible
315
+ // by using techniques from http://www.paciellogroup.com/blog/?p=604.
316
+ iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
317
+
318
+ // When Iframe has completely loaded, initialize the history and
319
+ // start polling.
320
+ .one( 'load', function(){
321
+ iframe_src || history_set( get_fragment() );
322
+ poll();
323
+ })
324
+
325
+ // Load Iframe src if specified, otherwise nothing.
326
+ .attr( 'src', iframe_src || 'javascript:0' )
327
+
328
+ // Append Iframe after the end of the body to prevent unnecessary
329
+ // initial page scrolling (yes, this works).
330
+ .insertAfter( 'body' )[0].contentWindow;
331
+
332
+ // Whenever `document.title` changes, update the Iframe's title to
333
+ // prettify the back/next history menu entries. Since IE sometimes
334
+ // errors with "Unspecified error" the very first time this is set
335
+ // (yes, very useful) wrap this with a try/catch block.
336
+ doc.onpropertychange = function(){
337
+ try {
338
+ if ( event.propertyName === 'title' ) {
339
+ iframe.document.title = doc.title;
340
+ }
341
+ } catch(e) {}
342
+ };
343
+
344
+ }
345
+ };
346
+
347
+ // Override the "stop" method since an IE6/7 Iframe was created. Even
348
+ // if there are no longer any bound event handlers, the polling loop
349
+ // is still necessary for back/next to work at all!
350
+ self.stop = fn_retval;
351
+
352
+ // Get history by looking at the hidden Iframe's location.hash.
353
+ history_get = function() {
354
+ return get_fragment( iframe.location.href );
355
+ };
356
+
357
+ // Set a new history item by opening and then closing the Iframe
358
+ // document, *then* setting its location.hash. If document.domain has
359
+ // been set, update that as well.
360
+ history_set = function( hash, history_hash ) {
361
+ var iframe_doc = iframe.document,
362
+ domain = $.fn[ str_hashchange ].domain;
363
+
364
+ if ( hash !== history_hash ) {
365
+ // Update Iframe with any initial `document.title` that might be set.
366
+ iframe_doc.title = doc.title;
367
+
368
+ // Opening the Iframe's document after it has been closed is what
369
+ // actually adds a history entry.
370
+ iframe_doc.open();
371
+
372
+ // Set document.domain for the Iframe document as well, if necessary.
373
+ domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
374
+
375
+ iframe_doc.close();
376
+
377
+ // Update the Iframe's hash, for great justice.
378
+ iframe.location.hash = hash;
379
+ }
380
+ };
381
+
382
+ })();
383
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
384
+ // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
385
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
386
+
387
+ return self;
388
+ })();
389
+
390
+ })(jQuery,this);
@@ -0,0 +1,409 @@
1
+ // ==========================================================================
2
+ // Project: metamorph
3
+ // Copyright: ©2011 My Company Inc. All rights reserved.
4
+ // ==========================================================================
5
+
6
+ (function(window) {
7
+
8
+ var K = function(){},
9
+ guid = 0,
10
+ document = window.document,
11
+
12
+ // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
13
+ supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
14
+
15
+ // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
16
+ // is a "zero-scope" element. This problem can be worked around by making
17
+ // the first node an invisible text node. We, like Modernizr, use &shy;
18
+ needsShy = (function(){
19
+ var testEl = document.createElement('div');
20
+ testEl.innerHTML = "<div></div>";
21
+ testEl.firstChild.innerHTML = "<script></script>";
22
+ return testEl.firstChild.innerHTML === '';
23
+ })();
24
+
25
+ // Constructor that supports either Metamorph('foo') or new
26
+ // Metamorph('foo');
27
+ //
28
+ // Takes a string of HTML as the argument.
29
+
30
+ var Metamorph = function(html) {
31
+ var self;
32
+
33
+ if (this instanceof Metamorph) {
34
+ self = this;
35
+ } else {
36
+ self = new K();
37
+ }
38
+
39
+ self.innerHTML = html;
40
+ var myGuid = 'metamorph-'+(guid++);
41
+ self.start = myGuid + '-start';
42
+ self.end = myGuid + '-end';
43
+
44
+ return self;
45
+ };
46
+
47
+ K.prototype = Metamorph.prototype;
48
+
49
+ var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
50
+
51
+ outerHTMLFunc = function() {
52
+ return this.startTag() + this.innerHTML + this.endTag();
53
+ };
54
+
55
+ startTagFunc = function() {
56
+ return "<script id='" + this.start + "' type='text/x-placeholder'></script>";
57
+ };
58
+
59
+ endTagFunc = function() {
60
+ return "<script id='" + this.end + "' type='text/x-placeholder'></script>";
61
+ };
62
+
63
+ // If we have the W3C range API, this process is relatively straight forward.
64
+ if (supportsRange) {
65
+
66
+ // IE 9 supports ranges but doesn't define createContextualFragment
67
+ if (!Range.prototype.createContextualFragment) {
68
+ Range.prototype.createContextualFragment = function(html) {
69
+ var frag = document.createDocumentFragment(),
70
+ div = document.createElement("div");
71
+ frag.appendChild(div);
72
+ div.outerHTML = html;
73
+ return frag;
74
+ };
75
+ }
76
+
77
+ // Get a range for the current morph. Optionally include the starting and
78
+ // ending placeholders.
79
+ rangeFor = function(morph, outerToo) {
80
+ var range = document.createRange();
81
+ var before = document.getElementById(morph.start);
82
+ var after = document.getElementById(morph.end);
83
+
84
+ if (outerToo) {
85
+ range.setStartBefore(before);
86
+ range.setEndAfter(after);
87
+ } else {
88
+ range.setStartAfter(before);
89
+ range.setEndBefore(after);
90
+ }
91
+
92
+ return range;
93
+ };
94
+
95
+ htmlFunc = function(html, outerToo) {
96
+ // get a range for the current metamorph object
97
+ var range = rangeFor(this, outerToo);
98
+
99
+ // delete the contents of the range, which will be the
100
+ // nodes between the starting and ending placeholder.
101
+ range.deleteContents();
102
+
103
+ // create a new document fragment for the HTML
104
+ var fragment = range.createContextualFragment(html);
105
+
106
+ // insert the fragment into the range
107
+ range.insertNode(fragment);
108
+ };
109
+
110
+ removeFunc = function() {
111
+ // get a range for the current metamorph object including
112
+ // the starting and ending placeholders.
113
+ var range = rangeFor(this, true);
114
+
115
+ // delete the entire range.
116
+ range.deleteContents();
117
+ };
118
+
119
+ appendToFunc = function(node) {
120
+ var range = document.createRange();
121
+ range.setStart(node);
122
+ range.collapse(false);
123
+ var frag = range.createContextualFragment(this.outerHTML());
124
+ node.appendChild(frag);
125
+ };
126
+
127
+ afterFunc = function(html) {
128
+ var range = document.createRange();
129
+ var after = document.getElementById(this.end);
130
+
131
+ range.setStartAfter(after);
132
+ range.setEndAfter(after);
133
+
134
+ var fragment = range.createContextualFragment(html);
135
+ range.insertNode(fragment);
136
+ };
137
+
138
+ prependFunc = function(html) {
139
+ var range = document.createRange();
140
+ var start = document.getElementById(this.start);
141
+
142
+ range.setStartAfter(start);
143
+ range.setEndAfter(start);
144
+
145
+ var fragment = range.createContextualFragment(html);
146
+ range.insertNode(fragment);
147
+ };
148
+
149
+ } else {
150
+ /**
151
+ * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
152
+ * have some HTML and we need to figure out how to convert it into some nodes.
153
+ *
154
+ * In this case, jQuery needs to scan the HTML looking for an opening tag and use
155
+ * that as the key for the wrap map. In our case, we know the parent node, and
156
+ * can use its type as the key for the wrap map.
157
+ **/
158
+ var wrapMap = {
159
+ select: [ 1, "<select multiple='multiple'>", "</select>" ],
160
+ fieldset: [ 1, "<fieldset>", "</fieldset>" ],
161
+ table: [ 1, "<table>", "</table>" ],
162
+ tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
163
+ tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
164
+ colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
165
+ map: [ 1, "<map>", "</map>" ],
166
+ _default: [ 0, "", "" ]
167
+ };
168
+
169
+ /**
170
+ * Given a parent node and some HTML, generate a set of nodes. Return the first
171
+ * node, which will allow us to traverse the rest using nextSibling.
172
+ *
173
+ * We need to do this because innerHTML in IE does not really parse the nodes.
174
+ **/
175
+ var firstNodeFor = function(parentNode, html) {
176
+ var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
177
+ var depth = arr[0], start = arr[1], end = arr[2];
178
+
179
+ if (needsShy) { html = '&shy;'+html; }
180
+
181
+ var element = document.createElement('div');
182
+ element.innerHTML = start + html + end;
183
+
184
+ for (var i=0; i<=depth; i++) {
185
+ element = element.firstChild;
186
+ }
187
+
188
+ // Look for &shy; to remove it.
189
+ if (needsShy) {
190
+ var shyElement = element;
191
+
192
+ // Sometimes we get nameless elements with the shy inside
193
+ while (shyElement.nodeType === 1 && !shyElement.nodeName && shyElement.childNodes.length === 1) {
194
+ shyElement = shyElement.firstChild;
195
+ }
196
+
197
+ // At this point it's the actual unicode character.
198
+ if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
199
+ shyElement.nodeValue = shyElement.nodeValue.slice(1);
200
+ }
201
+ }
202
+
203
+ return element;
204
+ };
205
+
206
+ /**
207
+ * In some cases, Internet Explorer can create an anonymous node in
208
+ * the hierarchy with no tagName. You can create this scenario via:
209
+ *
210
+ * div = document.createElement("div");
211
+ * div.innerHTML = "<table>&shy<script></script><tr><td>hi</td></tr></table>";
212
+ * div.firstChild.firstChild.tagName //=> ""
213
+ *
214
+ * If our script markers are inside such a node, we need to find that
215
+ * node and use *it* as the marker.
216
+ **/
217
+ var realNode = function(start) {
218
+ while (start.parentNode.tagName === "") {
219
+ start = start.parentNode;
220
+ }
221
+
222
+ return start;
223
+ };
224
+
225
+ /**
226
+ * When automatically adding a tbody, Internet Explorer inserts the
227
+ * tbody immediately before the first <tr>. Other browsers create it
228
+ * before the first node, no matter what.
229
+ *
230
+ * This means the the following code:
231
+ *
232
+ * div = document.createElement("div");
233
+ * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
234
+ *
235
+ * Generates the following DOM in IE:
236
+ *
237
+ * + div
238
+ * + table
239
+ * - script id='first'
240
+ * + tbody
241
+ * + tr
242
+ * + td
243
+ * - "hi"
244
+ * - script id='last'
245
+ *
246
+ * Which means that the two script tags, even though they were
247
+ * inserted at the same point in the hierarchy in the original
248
+ * HTML, now have different parents.
249
+ *
250
+ * This code reparents the first script tag by making it the tbody's
251
+ * first child.
252
+ **/
253
+ var fixParentage = function(start, end) {
254
+ if (start.parentNode !== end.parentNode) {
255
+ end.parentNode.insertBefore(start, end.parentNode.firstChild);
256
+ }
257
+ };
258
+
259
+ htmlFunc = function(html, outerToo) {
260
+ // get the real starting node. see realNode for details.
261
+ var start = realNode(document.getElementById(this.start));
262
+ var end = document.getElementById(this.end);
263
+ var parentNode = end.parentNode;
264
+ var node, nextSibling, last;
265
+
266
+ // make sure that the start and end nodes share the same
267
+ // parent. If not, fix it.
268
+ fixParentage(start, end);
269
+
270
+ // remove all of the nodes after the starting placeholder and
271
+ // before the ending placeholder.
272
+ node = start.nextSibling;
273
+ while (node) {
274
+ nextSibling = node.nextSibling;
275
+ last = node === end;
276
+
277
+ // if this is the last node, and we want to remove it as well,
278
+ // set the `end` node to the next sibling. This is because
279
+ // for the rest of the function, we insert the new nodes
280
+ // before the end (note that insertBefore(node, null) is
281
+ // the same as appendChild(node)).
282
+ //
283
+ // if we do not want to remove it, just break.
284
+ if (last) {
285
+ if (outerToo) { end = node.nextSibling; } else { break; }
286
+ }
287
+
288
+ node.parentNode.removeChild(node);
289
+
290
+ // if this is the last node and we didn't break before
291
+ // (because we wanted to remove the outer nodes), break
292
+ // now.
293
+ if (last) { break; }
294
+
295
+ node = nextSibling;
296
+ }
297
+
298
+ // get the first node for the HTML string, even in cases like
299
+ // tables and lists where a simple innerHTML on a div would
300
+ // swallow some of the content.
301
+ node = firstNodeFor(start.parentNode, html);
302
+
303
+ // copy the nodes for the HTML between the starting and ending
304
+ // placeholder.
305
+ while (node) {
306
+ nextSibling = node.nextSibling;
307
+ parentNode.insertBefore(node, end);
308
+ node = nextSibling;
309
+ }
310
+ };
311
+
312
+ // remove the nodes in the DOM representing this metamorph.
313
+ //
314
+ // this includes the starting and ending placeholders.
315
+ removeFunc = function() {
316
+ var start = realNode(document.getElementById(this.start));
317
+ var end = document.getElementById(this.end);
318
+
319
+ this.html('');
320
+ start.parentNode.removeChild(start);
321
+ end.parentNode.removeChild(end);
322
+ };
323
+
324
+ appendToFunc = function(parentNode) {
325
+ var node = firstNodeFor(parentNode, this.outerHTML());
326
+
327
+ while (node) {
328
+ nextSibling = node.nextSibling;
329
+ parentNode.appendChild(node);
330
+ node = nextSibling;
331
+ }
332
+ };
333
+
334
+ afterFunc = function(html) {
335
+ // get the real starting node. see realNode for details.
336
+ var end = document.getElementById(this.end);
337
+ var parentNode = end.parentNode;
338
+ var nextSibling;
339
+ var node;
340
+
341
+ // get the first node for the HTML string, even in cases like
342
+ // tables and lists where a simple innerHTML on a div would
343
+ // swallow some of the content.
344
+ node = firstNodeFor(parentNode, html);
345
+
346
+ // copy the nodes for the HTML between the starting and ending
347
+ // placeholder.
348
+ while (node) {
349
+ nextSibling = node.nextSibling;
350
+ parentNode.insertBefore(node, end.nextSibling);
351
+ node = nextSibling;
352
+ }
353
+ };
354
+
355
+ prependFunc = function(html) {
356
+ var start = document.getElementById(this.start);
357
+ var parentNode = start.parentNode;
358
+ var nextSibling;
359
+ var node;
360
+
361
+ node = firstNodeFor(parentNode, html);
362
+ var insertBefore = start.nextSibling;
363
+
364
+ while (node) {
365
+ nextSibling = node.nextSibling;
366
+ parentNode.insertBefore(node, insertBefore);
367
+ node = nextSibling;
368
+ }
369
+ }
370
+ }
371
+
372
+ Metamorph.prototype.html = function(html) {
373
+ this.checkRemoved();
374
+ if (html === undefined) { return this.innerHTML; }
375
+
376
+ htmlFunc.call(this, html);
377
+
378
+ this.innerHTML = html;
379
+ };
380
+
381
+ Metamorph.prototype.replaceWith = function(html) {
382
+ this.checkRemoved();
383
+ htmlFunc.call(this, html, true);
384
+ };
385
+
386
+ Metamorph.prototype.remove = removeFunc;
387
+ Metamorph.prototype.outerHTML = outerHTMLFunc;
388
+ Metamorph.prototype.appendTo = appendToFunc;
389
+ Metamorph.prototype.after = afterFunc;
390
+ Metamorph.prototype.prepend = prependFunc;
391
+ Metamorph.prototype.startTag = startTagFunc;
392
+ Metamorph.prototype.endTag = endTagFunc;
393
+
394
+ Metamorph.prototype.isRemoved = function() {
395
+ var before = document.getElementById(this.start);
396
+ var after = document.getElementById(this.end);
397
+
398
+ return !before || !after;
399
+ };
400
+
401
+ Metamorph.prototype.checkRemoved = function() {
402
+ if (this.isRemoved()) {
403
+ throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
404
+ }
405
+ };
406
+
407
+ window.Metamorph = Metamorph;
408
+ })(this);
409
+