3
* Multiple helpers used accross all CMS features
6
//##############################################################################
9
// ensuring django namespace is set correctly
10
window.django = window.django || undefined;
12
// ensuring jQuery namespace is set correctly
13
window.jQuery = (window.django && window.django.jQuery) ? window.django.jQuery : window.jQuery || undefined;
15
// ensuring Class namespace is set correctly
16
window.Class = window.Class || undefined;
18
// ensuring CMS namespace is set correctly
23
$: (typeof window.jQuery === 'function') ? window.jQuery : undefined,
24
Class: (typeof window.Class === 'function') ? window.Class : undefined,
31
* Provides key codes for common keys.
36
* if (e.keyCode === CMS.KEYS.ENTER) { ... };
52
* Provides breakpoints for certain device widths
55
* @submodule CMS.BREAKPOINTS
57
* if (window.width >= CMS.BREAKPOINTS.mobile) { ... };
64
//##############################################################################
68
// shorthand for jQuery(document).ready();
70
var root = $('#cms-top');
74
* @param {String} events space separated event names to be namespaces
75
* @return {String} string containing space separated namespaced event names
77
var _ns = function nameSpaceEvent(events) {
78
return events.split(/\s+/g).map(function (className) {
79
return 'cms-' + className;
84
* Provides various helpers that are mixed in all CMS classes.
95
* Redirects to a specific url or reloads browser.
97
* @method reloadBrowser
98
* @param {String} url where to redirect. if equal to `REFRESH_PAGE` will reload page instead
99
* @param {Number} timeout=0 timeout in ms
100
* @param {Boolean} ajax if set to true first initiates **synchronous**
101
* ajax request to figure out if the browser should reload current page,
102
* move to another one, or do nothing.
103
* @param {Object} [data] optional data to be passed instead of one provided by request config
104
* @param {String} [data.model=CMS.config.request.model]
105
* @param {String|Number} [data.pk=CMS.config.request.pk]
107
reloadBrowser: function (url, timeout, ajax, data) {
109
// is there a parent window?
110
var parent = (window.parent) ? window.parent : window;
112
// if there is an ajax reload, prioritize
114
parent.CMS.API.locked = true;
115
// check if the url has changed, if true redirect to the new path
116
// this requires an ajax request
120
url: parent.CMS.config.request.url,
122
model: parent.CMS.config.request.model,
123
pk: parent.CMS.config.request.pk
125
success: function (response) {
126
parent.CMS.API.locked = false;
128
if (response === '' && !url) {
129
// cancel if response is empty
131
} else if (parent.location.pathname !== response && response !== '') {
132
// api call to the backend to check if the current path is still the same
133
that.reloadBrowser(response);
134
} else if (url === 'REFRESH_PAGE') {
135
// if on_close provides REFRESH_PAGE, only do a reload
136
that.reloadBrowser();
138
// on_close can also provide a url, reload to the new destination
139
that.reloadBrowser(url);
141
that.reloadBrowser();
146
// cancel further operations
150
// add timeout if provided
151
parent.setTimeout(function () {
152
if (url && url !== parent.location.href) {
153
// location.reload() takes precedence over this, so we
154
// don't want to reload the page if we need a redirect
155
parent.location.href = url;
157
// ensure page is always reloaded #3413
158
parent.location.reload();
164
* Assigns an event handler to forms located in the toolbar
165
* to prevent multiple submissions.
167
* @method preventSubmit
169
preventSubmit: function () {
170
var forms = $('.cms-toolbar').find('form');
171
forms.submit(function () {
173
CMS.API.Toolbar.showLoader();
174
// we cannot use disabled as the name action will be ignored
175
$('input[type="submit"]').on('click', function (e) {
177
}).css('opacity', 0.5);
182
* Sets csrf token header on ajax requests.
185
* @param {String} csrf_token
187
csrf: function (csrf_token) {
189
beforeSend: function (xhr) {
190
xhr.setRequestHeader('X-CSRFToken', csrf_token);
196
* Sends or retrieves a JSON from localStorage
197
* or the session (through synchronous ajax request)
198
* if localStorage is not available.
200
* @method setSettings
203
setSettings: function (settings) {
205
settings = JSON.stringify($.extend({}, CMS.config.settings, settings));
207
if (CMS.API.Toolbar) {
208
CMS.API.Toolbar.showLoader();
211
// use local storage or session
212
if (this._isStorageSupported) {
213
// save within local storage
214
localStorage.setItem('cms_cookie', settings);
215
if (CMS.API.Toolbar) {
216
CMS.API.Toolbar.hideLoader();
219
// save within session
220
CMS.API.locked = true;
225
url: CMS.config.urls.settings,
227
csrfmiddlewaretoken: CMS.config.csrf,
230
success: function (data) {
231
CMS.API.locked = false;
232
// determine if logged in or not
233
settings = (data) ? JSON.parse(data) : CMS.config.settings;
234
if (CMS.API.Toolbar) {
235
CMS.API.Toolbar.hideLoader();
238
error: function (jqXHR) {
239
CMS.API.Messages.open({
240
message: jqXHR.response + ' | ' + jqXHR.status + ' ' + jqXHR.statusText,
248
CMS.settings = typeof settings === 'object' ? settings : JSON.parse(settings);
250
// ensure new settings are returned
255
* Gets user settings (from JSON or the session)
256
* in the same way as setSettings sets them.
258
* @method getSettings
260
getSettings: function () {
263
if (CMS.API.Toolbar) {
264
CMS.API.Toolbar.showLoader();
267
// use local storage or session
268
if (this._isStorageSupported) {
269
// get from local storage
270
settings = JSON.parse(localStorage.getItem('cms_cookie'));
271
if (CMS.API.Toolbar) {
272
CMS.API.Toolbar.hideLoader();
275
CMS.API.locked = true;
280
url: CMS.config.urls.settings,
281
success: function (data) {
282
CMS.API.locked = false;
283
// determine if logged in or not
284
settings = (data) ? JSON.parse(data) : CMS.config.settings;
285
if (CMS.API.Toolbar) {
286
CMS.API.Toolbar.hideLoader();
289
error: function (jqXHR) {
290
CMS.API.Messages.open({
291
message: jqXHR.response + ' | ' + jqXHR.status + ' ' + jqXHR.statusText,
299
settings = this.setSettings(CMS.config.settings);
303
CMS.settings = settings;
305
// ensure new settings are returned
310
* Modifies the url with new params and sanitises
311
* the ampersand within the url for #3404.
314
* @param {String} url original url
315
* @param {String[]} [params] array of `param=value` strings to update the url
317
makeURL: function makeURL(url, params) {
326
// return url if there is no param
327
if (!(url.split('?').length <= 1 || window.JSON === undefined)) {
329
urlArray = url.split('?');
330
urlParams = urlArray[1].split('&');
331
origin = urlArray[0];
334
// loop through the available params
335
$.each(urlParams, function (index, param) {
337
param: param.split('=')[0],
338
value: param.split('=')[1]
341
// loop through the new params
342
if (params && params.length) {
343
$.each(params, function (index, param) {
345
param: param.split('=')[0],
346
value: param.split('=')[1]
351
// merge manually because jquery...
352
$.each(arr, function (index, item) {
353
var i = $.inArray(item.param, keys);
356
keys.push(item.param);
357
values.push(item.value);
359
values[i] = item.value;
364
$.each(keys, function (index, key) {
365
tmp += '&' + key + '=' + values[index];
367
tmp = tmp.replace('&', '?');
369
url = url.replace('&', '&');
375
* Creates a debounced function that delays invoking `func`
376
* until after `wait` milliseconds have elapsed since
377
* the last time the debounced function was invoked.
378
* Optionally can be invoked first time immediately.
381
* @param {Function} func function to debounce
382
* @param {Number} wait time in ms to wait
383
* @param {Object} [opts]
384
* @param {Boolean} [opts.immediate] trigger func immediately?
387
debounce: function debounce(func, wait, opts) {
390
var context = this, args = arguments;
391
var later = function () {
393
if (!opts || !opts.immediate) {
394
func.apply(context, args);
397
var callNow = opts && opts.immediate && !timeout;
398
clearTimeout(timeout);
399
timeout = setTimeout(later, wait);
401
func.apply(context, args);
407
* Returns a function that when invoked, will only be triggered
408
* at most once during a given window of time. Normally, the
409
* throttled function will run as much as it can, without ever
410
* going more than once per `wait` duration, but if you’d like to
411
* disable the execution on the leading edge, pass `{leading: false}`.
412
* To disable execution on the trailing edge, ditto.
415
* @param {Function} func function to throttle
416
* @param {Number} wait time window
417
* @param {Object} [opts]
418
* @param {Boolean} [opts.leading=true] execute on the leading edge
419
* @param {Boolean} [opts.trailing=true] execute on the trailing edge
422
throttle: function throttle(func, wait, opts) {
423
var context, args, result;
429
var later = function () {
430
previous = opts.leading === false ? 0 : $.now();
432
result = func.apply(context, args);
434
context = args = null;
439
if (!previous && opts.leading === false) {
442
var remaining = wait - (now - previous);
445
if (remaining <= 0 || remaining > wait) {
447
clearTimeout(timeout);
451
result = func.apply(context, args);
453
context = args = null;
455
} else if (!timeout && opts.trailing !== false) {
456
timeout = setTimeout(later, remaining);
463
* Browsers allow to "Prevent this page form creating additional
464
* dialogs." checkbox which prevents further input from confirm messages.
465
* This method falls back to "true" once the user chooses this option.
467
* @method secureConfirm
468
* @param {String} message to be displayed
471
secureConfirm: function secureConfirm(message) {
472
var start = Number(new Date());
473
var result = confirm(message);
474
var end = Number(new Date());
476
return (end < (start + 10) || result === true);
480
* Is localStorage truly supported?
481
* Check is taken from modernizr.
483
* @property _isStorageSupported
487
_isStorageSupported: (function localStorageCheck() {
488
var mod = 'modernizr';
490
localStorage.setItem(mod, mod);
491
localStorage.removeItem(mod);
499
* Adds an event listener to the "CMS".
501
* @method addEventListener
502
* @param {String} eventName string containing space separated event names
503
* @param {Function} fn callback to run when the event happens
505
addEventListener: function addEventListener(eventName, fn) {
506
return root.on(_ns(eventName), fn);
510
* Removes the event listener from the "CMS". If a callback is provided - removes only that callback.
512
* @method addEventListener
513
* @param {String} eventName string containing space separated event names
514
* @param {Function} [fn] specific callback to be removed
516
removeEventListener: function removeEventListener(eventName, fn) {
517
return root.off(_ns(eventName), fn);
521
* Dispatches an event
522
* @method dispatchEvent
523
* @param {String} eventName string containing space separated event names
524
* @param {Object} payload whatever payload required for the consumer
526
dispatchEvent: function dispatchEvent(eventName, payload) {
527
return root.trigger(_ns(eventName), [payload]);
531
* Returns a function that wraps the passed function so the wrapped function
532
* is executed only once, no matter how many times the wrapper function is executed.
535
* @param {Function} fn function to be executed only once
538
once: function once(fn) {
540
var didRunOnce = false;
547
result = fn.apply(this, arguments);
554
* Prevents scrolling with touch in an element.
556
* @method preventTouchScrolling
557
* @param {jQuery} element element where we are preventing the scroll
558
* @param {String} namespace so we don't mix events from two different places on the same element
560
preventTouchScrolling: function preventTouchScrolling(element, namespace) {
561
element.on('touchmove.cms.preventscroll.' + namespace, function (e) {
567
* Allows scrolling with touch in an element.
569
* @method allowTouchScrolling
570
* @param {jQuery} element element where we are allowing the scroll again
571
* @param {String} namespace so we don't accidentally remove events from a different handler
573
allowTouchScrolling: function allowTouchScrolling(element, namespace) {
574
element.off('touchmove.cms.preventscroll.' + namespace);
579
CMS.API.Helpers.preventSubmit();