cardboard_cms 0.1.8 → 0.2.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -5
  3. data/README.md +14 -14
  4. data/app/assets/javascripts/cardboard/admin.js +3 -24
  5. data/app/assets/javascripts/cardboard/datepicker.js +1 -5
  6. data/app/assets/javascripts/cardboard/rich_text.js +1 -1
  7. data/app/assets/javascripts/cardboard/search_filter.js +1 -1
  8. data/app/assets/stylesheets/cardboard/_bootstrap-select.css.scss +1 -1
  9. data/app/assets/stylesheets/cardboard/_framework.css.scss +1 -1
  10. data/app/assets/stylesheets/cardboard/_main_topbar.css.scss +1 -1
  11. data/app/controllers/cardboard/pages_controller.rb +25 -1
  12. data/app/controllers/pages_controller.rb +12 -11
  13. data/app/helpers/cardboard/resource_helper.rb +1 -1
  14. data/app/models/cardboard/field/boolean.rb +1 -1
  15. data/app/models/cardboard/field.rb +21 -9
  16. data/app/models/cardboard/page.rb +19 -30
  17. data/app/models/cardboard/page_part.rb +13 -46
  18. data/app/models/cardboard/setting.rb +3 -0
  19. data/app/models/cardboard/template.rb +15 -0
  20. data/app/views/cardboard/fields/_base_input.html.slim +1 -1
  21. data/app/views/cardboard/fields/_boolean.html.slim +2 -2
  22. data/app/views/cardboard/fields/_date.html.slim +1 -1
  23. data/app/views/cardboard/fields/_external_link.html.slim +2 -2
  24. data/app/views/cardboard/fields/_file.html.slim +4 -4
  25. data/app/views/cardboard/fields/_image.html.slim +2 -2
  26. data/app/views/cardboard/fields/_resource_link.html.slim +2 -2
  27. data/app/views/cardboard/fields/_rich_text.html.slim +1 -1
  28. data/app/views/cardboard/fields/_string.html.slim +1 -1
  29. data/app/views/cardboard/pages/_error.html.slim +1 -1
  30. data/app/views/cardboard/pages/_part_fields.html.slim +19 -0
  31. data/app/views/cardboard/pages/_sidebar.html.slim +8 -4
  32. data/app/views/cardboard/pages/_url_field.html.slim +6 -8
  33. data/app/views/cardboard/pages/edit.html.slim +24 -15
  34. data/app/views/cardboard/pages/new.html.slim +7 -0
  35. data/app/views/cardboard/pages/show.html.slim +3 -3
  36. data/app/views/cardboard/resources/_advanced_search.html.slim +1 -1
  37. data/app/views/cardboard/resources/_search_helper.html.slim +3 -3
  38. data/app/views/cardboard/resources/_simple_search.html.slim +1 -1
  39. data/app/views/cardboard/settings/index.html.slim +12 -3
  40. data/app/views/cardboard/super_user/index.html.slim +1 -1
  41. data/app/views/layouts/cardboard/_main_sidebar.html.slim +1 -1
  42. data/app/views/layouts/cardboard/_main_topbar.html.slim +1 -1
  43. data/app/views/layouts/cardboard/application.html.slim +1 -1
  44. data/cardboard.gemspec +1 -1
  45. data/config/routes.rb +2 -0
  46. data/db/migrate/1_create_cardboard.rb +63 -0
  47. data/lib/cardboard/engine.rb +1 -11
  48. data/lib/cardboard/helpers/seed.rb +31 -30
  49. data/lib/cardboard/version.rb +1 -1
  50. data/lib/cardboard_cms.rb +6 -2
  51. data/lib/generators/cardboard/install/install_generator.rb +5 -13
  52. data/lib/tasks/cardboard_tasks.rake +5 -3
  53. data/test/dummy/app/views/{pages → templates}/about-us.html.slim +0 -0
  54. data/test/dummy/app/views/templates/home.html.slim +5 -0
  55. data/test/dummy/config/cardboard.yml +1 -1
  56. data/test/dummy/db/schema.rb +7 -39
  57. data/test/dummy/db/seeds.rb +1 -1
  58. data/test/factories.rb +40 -1
  59. data/test/integration/page_editing_test.rb +2 -1
  60. data/test/integration/seeding_test.rb +16 -31
  61. data/test/models/field_test.rb +42 -28
  62. data/test/models/page_test.rb +1 -1
  63. data/test/models/template_test.rb +11 -0
  64. data/test/test_helper.rb +5 -1
  65. metadata +13 -29
  66. data/app/views/cardboard/pages/_subpart_fields.html.slim +0 -20
  67. data/lib/generators/cardboard/install/templates/migrations/1_create_cardboard_fields.rb +0 -21
  68. data/lib/generators/cardboard/install/templates/migrations/2_create_cardboard_page_parts.rb +0 -17
  69. data/lib/generators/cardboard/install/templates/migrations/3_create_cardboard_pages.rb +0 -18
  70. data/lib/generators/cardboard/install/templates/migrations/4_create_cardboard_settings.rb +0 -14
  71. data/test/dummy/app/views/pages/home.html.slim +0 -3
  72. data/test/dummy/db/migrate/20130426021522_create_news_posts.rb +0 -10
  73. data/test/dummy/db/migrate/20130501195423_create_icescreams.rb +0 -10
  74. data/test/dummy/db/migrate/20130502165540_create_beans.rb +0 -12
  75. data/test/dummy/db/migrate/20130522151358_create_cardboard_fields.rb +0 -21
  76. data/test/dummy/db/migrate/20130522151359_create_cardboard_page_parts.rb +0 -17
  77. data/test/dummy/db/migrate/20130522151400_create_cardboard_pages.rb +0 -18
  78. data/test/dummy/db/migrate/20130522151401_create_cardboard_settings.rb +0 -14
  79. data/test/dummy/db/migrate/20130607132558_create_admins.rb +0 -9
  80. data/vendor/assets/javascripts/cardboard/jquery.pjax.js +0 -840
@@ -1,840 +0,0 @@
1
- // jquery.pjax.js
2
- // copyright chris wanstrath
3
- // https://github.com/defunkt/jquery-pjax
4
- // version: 1.8.0
5
-
6
- (function($){
7
-
8
- // When called on a container with a selector, fetches the href with
9
- // ajax into the container or with the data-pjax attribute on the link
10
- // itself.
11
- //
12
- // Tries to make sure the back button and ctrl+click work the way
13
- // you'd expect.
14
- //
15
- // Exported as $.fn.pjax
16
- //
17
- // Accepts a jQuery ajax options object that may include these
18
- // pjax specific options:
19
- //
20
- //
21
- // container - Where to stick the response body. Usually a String selector.
22
- // $(container).html(xhr.responseBody)
23
- // (default: current jquery context)
24
- // push - Whether to pushState the URL. Defaults to true (of course).
25
- // replace - Want to use replaceState instead? That's cool.
26
- //
27
- // For convenience the second parameter can be either the container or
28
- // the options object.
29
- //
30
- // Returns the jQuery object
31
- function fnPjax(selector, container, options) {
32
- var context = this
33
- return this.on('click.pjax', selector, function(event) {
34
- var opts = $.extend({}, optionsFor(container, options))
35
- if (!opts.container)
36
- opts.container = $(this).attr('data-pjax') || context
37
- handleClick(event, opts)
38
- })
39
- }
40
-
41
- // Public: pjax on click handler
42
- //
43
- // Exported as $.pjax.click.
44
- //
45
- // event - "click" jQuery.Event
46
- // options - pjax options
47
- //
48
- // Examples
49
- //
50
- // $(document).on('click', 'a', $.pjax.click)
51
- // // is the same as
52
- // $(document).pjax('a')
53
- //
54
- // $(document).on('click', 'a', function(event) {
55
- // var container = $(this).closest('[data-pjax-container]')
56
- // $.pjax.click(event, container)
57
- // })
58
- //
59
- // Returns nothing.
60
- function handleClick(event, container, options) {
61
- options = optionsFor(container, options)
62
-
63
- var link = event.currentTarget
64
-
65
- if (link.tagName.toUpperCase() !== 'A')
66
- throw "$.fn.pjax or $.pjax.click requires an anchor element"
67
-
68
- // Middle click, cmd click, and ctrl click should open
69
- // links in a new tab as normal.
70
- if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
71
- return
72
-
73
- // Ignore cross origin links
74
- if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
75
- return
76
-
77
- // Ignore anchors on the same page
78
- if (link.hash && link.href.replace(link.hash, '') ===
79
- location.href.replace(location.hash, ''))
80
- return
81
-
82
- // Ignore empty anchor "foo.html#"
83
- if (link.href === location.href + '#')
84
- return
85
-
86
- var defaults = {
87
- url: link.href,
88
- container: $(link).attr('data-pjax'),
89
- target: link
90
- }
91
-
92
- var opts = $.extend({}, defaults, options)
93
- var clickEvent = $.Event('pjax:click')
94
- $(link).trigger(clickEvent, [opts])
95
-
96
- if (!clickEvent.isDefaultPrevented()) {
97
- pjax(opts)
98
- event.preventDefault()
99
- $(link).trigger('pjax:clicked', [opts])
100
- }
101
- }
102
-
103
- // Public: pjax on form submit handler
104
- //
105
- // Exported as $.pjax.submit
106
- //
107
- // event - "click" jQuery.Event
108
- // options - pjax options
109
- //
110
- // Examples
111
- //
112
- // $(document).on('submit', 'form', function(event) {
113
- // var container = $(this).closest('[data-pjax-container]')
114
- // $.pjax.submit(event, container)
115
- // })
116
- //
117
- // Returns nothing.
118
- function handleSubmit(event, container, options) {
119
- options = optionsFor(container, options)
120
-
121
- var form = event.currentTarget
122
-
123
- if (form.tagName.toUpperCase() !== 'FORM')
124
- throw "$.pjax.submit requires a form element"
125
-
126
- var defaults = {
127
- type: form.method.toUpperCase(),
128
- url: form.action,
129
- data: $(form).serializeArray(),
130
- container: $(form).attr('data-pjax'),
131
- target: form
132
- }
133
-
134
- pjax($.extend({}, defaults, options))
135
-
136
- event.preventDefault()
137
- }
138
-
139
- // Loads a URL with ajax, puts the response body inside a container,
140
- // then pushState()'s the loaded URL.
141
- //
142
- // Works just like $.ajax in that it accepts a jQuery ajax
143
- // settings object (with keys like url, type, data, etc).
144
- //
145
- // Accepts these extra keys:
146
- //
147
- // container - Where to stick the response body.
148
- // $(container).html(xhr.responseBody)
149
- // push - Whether to pushState the URL. Defaults to true (of course).
150
- // replace - Want to use replaceState instead? That's cool.
151
- //
152
- // Use it just like $.ajax:
153
- //
154
- // var xhr = $.pjax({ url: this.href, container: '#main' })
155
- // console.log( xhr.readyState )
156
- //
157
- // Returns whatever $.ajax returns.
158
- function pjax(options) {
159
- options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
160
-
161
- if ($.isFunction(options.url)) {
162
- options.url = options.url()
163
- }
164
-
165
- var target = options.target
166
-
167
- var hash = parseURL(options.url).hash
168
-
169
- var context = options.context = findContainerFor(options.container)
170
-
171
- // We want the browser to maintain two separate internal caches: one
172
- // for pjax'd partial page loads and one for normal page loads.
173
- // Without adding this secret parameter, some browsers will often
174
- // confuse the two.
175
- if (!options.data) options.data = {}
176
- options.data._pjax = context.selector
177
-
178
- function fire(type, args) {
179
- var event = $.Event(type, { relatedTarget: target })
180
- context.trigger(event, args)
181
- return !event.isDefaultPrevented()
182
- }
183
-
184
- var timeoutTimer
185
-
186
- options.beforeSend = function(xhr, settings) {
187
- // No timeout for non-GET requests
188
- // Its not safe to request the resource again with a fallback method.
189
- if (settings.type !== 'GET') {
190
- settings.timeout = 0
191
- }
192
-
193
- xhr.setRequestHeader('X-PJAX', 'true')
194
- xhr.setRequestHeader('X-PJAX-Container', context.selector)
195
-
196
- if (!fire('pjax:beforeSend', [xhr, settings]))
197
- return false
198
-
199
- if (settings.timeout > 0) {
200
- timeoutTimer = setTimeout(function() {
201
- if (fire('pjax:timeout', [xhr, options]))
202
- xhr.abort('timeout')
203
- }, settings.timeout)
204
-
205
- // Clear timeout setting so jquerys internal timeout isn't invoked
206
- settings.timeout = 0
207
- }
208
-
209
- options.requestUrl = parseURL(settings.url).href
210
- }
211
-
212
- options.complete = function(xhr, textStatus) {
213
- if (timeoutTimer)
214
- clearTimeout(timeoutTimer)
215
-
216
- fire('pjax:complete', [xhr, textStatus, options])
217
-
218
- fire('pjax:end', [xhr, options])
219
- }
220
-
221
- options.error = function(xhr, textStatus, errorThrown) {
222
- var container = extractContainer("", xhr, options)
223
-
224
- var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
225
- if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
226
- locationReplace(container.url)
227
- }
228
- }
229
-
230
- options.success = function(data, status, xhr) {
231
- // If $.pjax.defaults.version is a function, invoke it first.
232
- // Otherwise it can be a static string.
233
- var currentVersion = (typeof $.pjax.defaults.version === 'function') ?
234
- $.pjax.defaults.version() :
235
- $.pjax.defaults.version
236
-
237
- var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
238
-
239
- var container = extractContainer(data, xhr, options)
240
-
241
- // If there is a layout version mismatch, hard load the new url
242
- if (currentVersion && latestVersion && currentVersion !== latestVersion) {
243
- locationReplace(container.url)
244
- return
245
- }
246
-
247
- // If the new response is missing a body, hard load the page
248
- if (!container.contents) {
249
- locationReplace(container.url)
250
- return
251
- }
252
-
253
- pjax.state = {
254
- id: options.id || uniqueId(),
255
- url: container.url,
256
- title: container.title,
257
- container: context.selector,
258
- fragment: options.fragment,
259
- timeout: options.timeout
260
- }
261
-
262
- if (options.push || options.replace) {
263
- window.history.replaceState(pjax.state, container.title, container.url)
264
- }
265
-
266
- // Clear out any focused controls before inserting new page contents.
267
- document.activeElement.blur()
268
-
269
- if (container.title) document.title = container.title
270
- context.html(container.contents)
271
-
272
- // FF bug: Won't autofocus fields that are inserted via JS.
273
- // This behavior is incorrect. So if theres no current focus, autofocus
274
- // the last field.
275
- //
276
- // http://www.w3.org/html/wg/drafts/html/master/forms.html
277
- var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
278
- if (autofocusEl && document.activeElement !== autofocusEl) {
279
- autofocusEl.focus();
280
- }
281
-
282
- executeScriptTags(container.scripts)
283
-
284
- // Scroll to top by default
285
- if (typeof options.scrollTo === 'number')
286
- $(window).scrollTop(options.scrollTo)
287
-
288
- // If the URL has a hash in it, make sure the browser
289
- // knows to navigate to the hash.
290
- if ( hash !== '' ) {
291
- // Avoid using simple hash set here. Will add another history
292
- // entry. Replace the url with replaceState and scroll to target
293
- // by hand.
294
- //
295
- // window.location.hash = hash
296
- var url = parseURL(container.url)
297
- url.hash = hash
298
-
299
- pjax.state.url = url.href
300
- window.history.replaceState(pjax.state, container.title, url.href)
301
-
302
- var target = $(url.hash)
303
- if (target.length) $(window).scrollTop(target.offset().top)
304
- }
305
-
306
- fire('pjax:success', [data, status, xhr, options])
307
- }
308
-
309
-
310
- // Initialize pjax.state for the initial page load. Assume we're
311
- // using the container and options of the link we're loading for the
312
- // back button to the initial page. This ensures good back button
313
- // behavior.
314
- if (!pjax.state) {
315
- pjax.state = {
316
- id: uniqueId(),
317
- url: window.location.href,
318
- title: document.title,
319
- container: context.selector,
320
- fragment: options.fragment,
321
- timeout: options.timeout
322
- }
323
- window.history.replaceState(pjax.state, document.title)
324
- }
325
-
326
- // Cancel the current request if we're already pjaxing
327
- var xhr = pjax.xhr
328
- if ( xhr && xhr.readyState < 4) {
329
- xhr.onreadystatechange = $.noop
330
- xhr.abort()
331
- }
332
-
333
- pjax.options = options
334
- var xhr = pjax.xhr = $.ajax(options)
335
-
336
- if (xhr.readyState > 0) {
337
- if (options.push && !options.replace) {
338
- // Cache current container element before replacing it
339
- cachePush(pjax.state.id, context.clone().contents())
340
-
341
- window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
342
- }
343
-
344
- fire('pjax:start', [xhr, options])
345
- fire('pjax:send', [xhr, options])
346
- }
347
-
348
- return pjax.xhr
349
- }
350
-
351
- // Public: Reload current page with pjax.
352
- //
353
- // Returns whatever $.pjax returns.
354
- function pjaxReload(container, options) {
355
- var defaults = {
356
- url: window.location.href,
357
- push: false,
358
- replace: true,
359
- scrollTo: false
360
- }
361
-
362
- return pjax($.extend(defaults, optionsFor(container, options)))
363
- }
364
-
365
- // Internal: Hard replace current state with url.
366
- //
367
- // Work for around WebKit
368
- // https://bugs.webkit.org/show_bug.cgi?id=93506
369
- //
370
- // Returns nothing.
371
- function locationReplace(url) {
372
- window.history.replaceState(null, "", "#")
373
- window.location.replace(url)
374
- }
375
-
376
-
377
- var initialPop = true
378
- var initialURL = window.location.href
379
- var initialState = window.history.state
380
-
381
- // Initialize $.pjax.state if possible
382
- // Happens when reloading a page and coming forward from a different
383
- // session history.
384
- if (initialState && initialState.container) {
385
- pjax.state = initialState
386
- }
387
-
388
- // Non-webkit browsers don't fire an initial popstate event
389
- if ('state' in window.history) {
390
- initialPop = false
391
- }
392
-
393
- // popstate handler takes care of the back and forward buttons
394
- //
395
- // You probably shouldn't use pjax on pages with other pushState
396
- // stuff yet.
397
- function onPjaxPopstate(event) {
398
- var state = event.state
399
-
400
- if (state && state.container) {
401
- // When coming forward from a separate history session, will get an
402
- // initial pop with a state we are already at. Skip reloading the current
403
- // page.
404
- if (initialPop && initialURL == state.url) return
405
-
406
- // If popping back to the same state, just skip.
407
- // Could be clicking back from hashchange rather than a pushState.
408
- if (pjax.state.id === state.id) return
409
-
410
- var container = $(state.container)
411
- if (container.length) {
412
- var direction, contents = cacheMapping[state.id]
413
-
414
- if (pjax.state) {
415
- // Since state ids always increase, we can deduce the history
416
- // direction from the previous state.
417
- direction = pjax.state.id < state.id ? 'forward' : 'back'
418
-
419
- // Cache current container before replacement and inform the
420
- // cache which direction the history shifted.
421
- cachePop(direction, pjax.state.id, container.clone().contents())
422
- }
423
-
424
- var popstateEvent = $.Event('pjax:popstate', {
425
- state: state,
426
- direction: direction
427
- })
428
- container.trigger(popstateEvent)
429
-
430
- var options = {
431
- id: state.id,
432
- url: state.url,
433
- container: container,
434
- push: false,
435
- fragment: state.fragment,
436
- timeout: state.timeout,
437
- scrollTo: false
438
- }
439
-
440
- if (contents) {
441
- container.trigger('pjax:start', [null, options])
442
-
443
- if (state.title) document.title = state.title
444
- container.html(contents)
445
- pjax.state = state
446
-
447
- container.trigger('pjax:end', [null, options])
448
- } else {
449
- pjax(options)
450
- }
451
-
452
- // Force reflow/relayout before the browser tries to restore the
453
- // scroll position.
454
- container[0].offsetHeight
455
- } else {
456
- locationReplace(location.href)
457
- }
458
- }
459
- initialPop = false
460
- }
461
-
462
- // Fallback version of main pjax function for browsers that don't
463
- // support pushState.
464
- //
465
- // Returns nothing since it retriggers a hard form submission.
466
- function fallbackPjax(options) {
467
- var url = $.isFunction(options.url) ? options.url() : options.url,
468
- method = options.type ? options.type.toUpperCase() : 'GET'
469
-
470
- var form = $('<form>', {
471
- method: method === 'GET' ? 'GET' : 'POST',
472
- action: url,
473
- style: 'display:none'
474
- })
475
-
476
- if (method !== 'GET' && method !== 'POST') {
477
- form.append($('<input>', {
478
- type: 'hidden',
479
- name: '_method',
480
- value: method.toLowerCase()
481
- }))
482
- }
483
-
484
- var data = options.data
485
- if (typeof data === 'string') {
486
- $.each(data.split('&'), function(index, value) {
487
- var pair = value.split('=')
488
- form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
489
- })
490
- } else if (typeof data === 'object') {
491
- for (key in data)
492
- form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
493
- }
494
-
495
- $(document.body).append(form)
496
- form.submit()
497
- }
498
-
499
- // Internal: Generate unique id for state object.
500
- //
501
- // Use a timestamp instead of a counter since ids should still be
502
- // unique across page loads.
503
- //
504
- // Returns Number.
505
- function uniqueId() {
506
- return (new Date).getTime()
507
- }
508
-
509
- // Internal: Strips _pjax param from url
510
- //
511
- // url - String
512
- //
513
- // Returns String.
514
- function stripPjaxParam(url) {
515
- return url
516
- .replace(/\?_pjax=[^&]+&?/, '?')
517
- .replace(/_pjax=[^&]+&?/, '')
518
- .replace(/[\?&]$/, '')
519
- }
520
-
521
- // Internal: Parse URL components and returns a Locationish object.
522
- //
523
- // url - String URL
524
- //
525
- // Returns HTMLAnchorElement that acts like Location.
526
- function parseURL(url) {
527
- var a = document.createElement('a')
528
- a.href = url
529
- return a
530
- }
531
-
532
- // Internal: Build options Object for arguments.
533
- //
534
- // For convenience the first parameter can be either the container or
535
- // the options object.
536
- //
537
- // Examples
538
- //
539
- // optionsFor('#container')
540
- // // => {container: '#container'}
541
- //
542
- // optionsFor('#container', {push: true})
543
- // // => {container: '#container', push: true}
544
- //
545
- // optionsFor({container: '#container', push: true})
546
- // // => {container: '#container', push: true}
547
- //
548
- // Returns options Object.
549
- function optionsFor(container, options) {
550
- // Both container and options
551
- if ( container && options )
552
- options.container = container
553
-
554
- // First argument is options Object
555
- else if ( $.isPlainObject(container) )
556
- options = container
557
-
558
- // Only container
559
- else
560
- options = {container: container}
561
-
562
- // Find and validate container
563
- if (options.container)
564
- options.container = findContainerFor(options.container)
565
-
566
- return options
567
- }
568
-
569
- // Internal: Find container element for a variety of inputs.
570
- //
571
- // Because we can't persist elements using the history API, we must be
572
- // able to find a String selector that will consistently find the Element.
573
- //
574
- // container - A selector String, jQuery object, or DOM Element.
575
- //
576
- // Returns a jQuery object whose context is `document` and has a selector.
577
- function findContainerFor(container) {
578
- container = $(container)
579
-
580
- if ( !container.length ) {
581
- throw "no pjax container for " + container.selector
582
- } else if ( container.selector !== '' && container.context === document ) {
583
- return container
584
- } else if ( container.attr('id') ) {
585
- return $('#' + container.attr('id'))
586
- } else {
587
- throw "cant get selector for pjax container!"
588
- }
589
- }
590
-
591
- // Internal: Filter and find all elements matching the selector.
592
- //
593
- // Where $.fn.find only matches descendants, findAll will test all the
594
- // top level elements in the jQuery object as well.
595
- //
596
- // elems - jQuery object of Elements
597
- // selector - String selector to match
598
- //
599
- // Returns a jQuery object.
600
- function findAll(elems, selector) {
601
- return elems.filter(selector).add(elems.find(selector));
602
- }
603
-
604
- function parseHTML(html) {
605
- return $.parseHTML(html, document, true)
606
- }
607
-
608
- // Internal: Extracts container and metadata from response.
609
- //
610
- // 1. Extracts X-PJAX-URL header if set
611
- // 2. Extracts inline <title> tags
612
- // 3. Builds response Element and extracts fragment if set
613
- //
614
- // data - String response data
615
- // xhr - XHR response
616
- // options - pjax options Object
617
- //
618
- // Returns an Object with url, title, and contents keys.
619
- function extractContainer(data, xhr, options) {
620
- var obj = {}
621
-
622
- // Prefer X-PJAX-URL header if it was set, otherwise fallback to
623
- // using the original requested url.
624
- obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl)
625
-
626
- // Attempt to parse response html into elements
627
- if (/<html/i.test(data)) {
628
- var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]))
629
- var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
630
- } else {
631
- var $head = $body = $(parseHTML(data))
632
- }
633
-
634
- // If response data is empty, return fast
635
- if ($body.length === 0)
636
- return obj
637
-
638
- // If there's a <title> tag in the header, use it as
639
- // the page's title.
640
- obj.title = findAll($head, 'title').last().text()
641
-
642
- if (options.fragment) {
643
- // If they specified a fragment, look for it in the response
644
- // and pull it out.
645
- if (options.fragment === 'body') {
646
- var $fragment = $body
647
- } else {
648
- var $fragment = findAll($body, options.fragment).first()
649
- }
650
-
651
- if ($fragment.length) {
652
- obj.contents = $fragment.contents()
653
-
654
- // If there's no title, look for data-title and title attributes
655
- // on the fragment
656
- if (!obj.title)
657
- obj.title = $fragment.attr('title') || $fragment.data('title')
658
- }
659
-
660
- } else if (!/<html/i.test(data)) {
661
- obj.contents = $body
662
- }
663
-
664
- // Clean up any <title> tags
665
- if (obj.contents) {
666
- // Remove any parent title elements
667
- obj.contents = obj.contents.not(function() { return $(this).is('title') })
668
-
669
- // Then scrub any titles from their descendants
670
- obj.contents.find('title').remove()
671
-
672
- // Gather all script[src] elements
673
- obj.scripts = findAll(obj.contents, 'script[src]').remove()
674
- obj.contents = obj.contents.not(obj.scripts)
675
- }
676
-
677
- // Trim any whitespace off the title
678
- if (obj.title) obj.title = $.trim(obj.title)
679
-
680
- return obj
681
- }
682
-
683
- // Load an execute scripts using standard script request.
684
- //
685
- // Avoids jQuery's traditional $.getScript which does a XHR request and
686
- // globalEval.
687
- //
688
- // scripts - jQuery object of script Elements
689
- //
690
- // Returns nothing.
691
- function executeScriptTags(scripts) {
692
- if (!scripts) return
693
-
694
- var existingScripts = $('script[src]')
695
-
696
- scripts.each(function() {
697
- var src = this.src
698
- var matchedScripts = existingScripts.filter(function() {
699
- return this.src === src
700
- })
701
- if (matchedScripts.length) return
702
-
703
- var script = document.createElement('script')
704
- script.type = $(this).attr('type')
705
- script.src = $(this).attr('src')
706
- document.head.appendChild(script)
707
- })
708
- }
709
-
710
- // Internal: History DOM caching class.
711
- var cacheMapping = {}
712
- var cacheForwardStack = []
713
- var cacheBackStack = []
714
-
715
- // Push previous state id and container contents into the history
716
- // cache. Should be called in conjunction with `pushState` to save the
717
- // previous container contents.
718
- //
719
- // id - State ID Number
720
- // value - DOM Element to cache
721
- //
722
- // Returns nothing.
723
- function cachePush(id, value) {
724
- cacheMapping[id] = value
725
- cacheBackStack.push(id)
726
-
727
- // Remove all entires in forward history stack after pushing
728
- // a new page.
729
- while (cacheForwardStack.length)
730
- delete cacheMapping[cacheForwardStack.shift()]
731
-
732
- // Trim back history stack to max cache length.
733
- while (cacheBackStack.length > pjax.defaults.maxCacheLength)
734
- delete cacheMapping[cacheBackStack.shift()]
735
- }
736
-
737
- // Shifts cache from directional history cache. Should be
738
- // called on `popstate` with the previous state id and container
739
- // contents.
740
- //
741
- // direction - "forward" or "back" String
742
- // id - State ID Number
743
- // value - DOM Element to cache
744
- //
745
- // Returns nothing.
746
- function cachePop(direction, id, value) {
747
- var pushStack, popStack
748
- cacheMapping[id] = value
749
-
750
- if (direction === 'forward') {
751
- pushStack = cacheBackStack
752
- popStack = cacheForwardStack
753
- } else {
754
- pushStack = cacheForwardStack
755
- popStack = cacheBackStack
756
- }
757
-
758
- pushStack.push(id)
759
- if (id = popStack.pop())
760
- delete cacheMapping[id]
761
- }
762
-
763
- // Public: Find version identifier for the initial page load.
764
- //
765
- // Returns String version or undefined.
766
- function findVersion() {
767
- return $('meta').filter(function() {
768
- var name = $(this).attr('http-equiv')
769
- return name && name.toUpperCase() === 'X-PJAX-VERSION'
770
- }).attr('content')
771
- }
772
-
773
- // Install pjax functions on $.pjax to enable pushState behavior.
774
- //
775
- // Does nothing if already enabled.
776
- //
777
- // Examples
778
- //
779
- // $.pjax.enable()
780
- //
781
- // Returns nothing.
782
- function enable() {
783
- $.fn.pjax = fnPjax
784
- $.pjax = pjax
785
- $.pjax.enable = $.noop
786
- $.pjax.disable = disable
787
- $.pjax.click = handleClick
788
- $.pjax.submit = handleSubmit
789
- $.pjax.reload = pjaxReload
790
- $.pjax.defaults = {
791
- timeout: 650,
792
- push: true,
793
- replace: false,
794
- type: 'GET',
795
- dataType: 'html',
796
- scrollTo: 0,
797
- maxCacheLength: 20,
798
- version: findVersion
799
- }
800
- $(window).on('popstate.pjax', onPjaxPopstate)
801
- }
802
-
803
- // Disable pushState behavior.
804
- //
805
- // This is the case when a browser doesn't support pushState. It is
806
- // sometimes useful to disable pushState for debugging on a modern
807
- // browser.
808
- //
809
- // Examples
810
- //
811
- // $.pjax.disable()
812
- //
813
- // Returns nothing.
814
- function disable() {
815
- $.fn.pjax = function() { return this }
816
- $.pjax = fallbackPjax
817
- $.pjax.enable = enable
818
- $.pjax.disable = $.noop
819
- $.pjax.click = $.noop
820
- $.pjax.submit = $.noop
821
- $.pjax.reload = function() { window.location.reload() }
822
-
823
- $(window).off('popstate.pjax', onPjaxPopstate)
824
- }
825
-
826
-
827
- // Add the state property to jQuery's event object so we can use it in
828
- // $(window).bind('popstate')
829
- if ( $.inArray('state', $.event.props) < 0 )
830
- $.event.props.push('state')
831
-
832
- // Is pjax supported by this browser?
833
- $.support.pjax =
834
- window.history && window.history.pushState && window.history.replaceState &&
835
- // pushState isn't reliable on iOS until 5.
836
- !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
837
-
838
- $.support.pjax ? enable() : disable()
839
-
840
- })(jQuery);