joosy 0.1.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
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
+