cardboard_cms 0.1.8 → 0.2.1

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