2
* Copyright https://github.com/divio/django-cms
5
// #############################################################################
10
var CMS = window.CMS || {};
12
// #############################################################################
17
// shorthand for jQuery(document).ready();
20
* The sideframe is triggered via API calls from the backend either
21
* through the toolbar navigation or from plugins. The APIs only allow to
22
* open a url within the sideframe.
26
* @uses CMS.API.Helpers
28
CMS.Sideframe = new CMS.Class({
30
implement: [CMS.API.Helpers],
34
sideframeDuration: 300,
35
sideframeWidth: 0.8 // matches 80% of window width
38
initialize: function initialize(options) {
39
this.options = $.extend(true, {}, this.options, options);
45
this.click = 'click.cms.sideframe';
46
this.pointerDown = 'pointerdown.cms.sideframe contextmenu.cms.sideframe';
47
this.pointerUp = 'pointerup.cms.sideframe pointercancel.cms.sideframe';
48
this.pointerMove = 'pointermove.cms.sideframe';
49
this.enforceReload = false;
50
this.settingsRefreshTimer = 600;
54
* Stores all jQuery references within `this.ui`.
59
_setupUI: function _setupUI() {
60
var sideframe = $('.cms-sideframe');
65
dimmer: sideframe.find('.cms-sideframe-dimmer'),
66
close: sideframe.find('.cms-sideframe-close'),
67
resize: sideframe.find('.cms-sideframe-resize'),
68
frame: sideframe.find('.cms-sideframe-frame'),
69
shim: sideframe.find('.cms-sideframe-shim'),
70
historyBack: sideframe.find('.cms-sideframe-history .cms-icon-arrow-back'),
71
historyForward: sideframe.find('.cms-sideframe-history .cms-icon-arrow-forward')
76
* Sets up all the event handlers, such as closing and resizing.
81
_events: function _events() {
84
// we need to set the history state on event creation
85
// to ensure we start with clean states in new instances
91
this.ui.close.off(this.click).on(this.click, function () {
95
// the resize event attaches an off event to the body
96
// which is handled within _startResize()
97
this.ui.resize.off(this.pointerDown).on(this.pointerDown, function (e) {
102
// close sideframe when clicking on the dimmer
103
this.ui.dimmer.off(this.click).on(this.click, function () {
107
// attach events to the back button
108
this.ui.historyBack.off(this.click).on(this.click, function () {
109
if (that.ui.historyBack.hasClass('cms-icon-disabled')) {
112
that._goToHistory('back');
115
// attach events to the forward button
116
this.ui.historyForward.off(this.click).on(this.click, function () {
117
if (that.ui.historyForward.hasClass('cms-icon-disabled')) {
120
that._goToHistory('forward');
125
* Opens a given url within a sideframe.
129
* @param {Object} opts
130
* @param {String} opts.url url to render iframe
131
* @param {Boolean} [opts.animate] should modal be animated
133
open: function open(opts) {
134
if (!(opts && opts.url)) {
135
throw new Error('The arguments passed to "open" were invalid.');
139
var animate = opts.animate;
142
var language = 'language=' + CMS.config.request.language;
143
var page_id = 'page_id=' + CMS.config.request.page_id;
145
var width = window.innerWidth;
146
var currentWidth = this.ui.sideframe.outerWidth();
147
var isFrameVisible = this.ui.sideframe.is(':visible');
149
// set the ratio for bigger devices than mobile
150
if (this.ui.body.width() >= CMS.BREAKPOINTS.mobile) {
151
width = CMS.settings.sideframe.position ||
152
(this.options.sideframeWidth * 100 + '%');
155
// We have to rebind events every time we open a sideframe
156
// because the event handlers contain references to the instance
157
// and since we reuse the same markup we need to update
158
// that instance reference every time.
161
// show dimmer even before iframe is loaded
162
this.ui.dimmer.show();
163
this.ui.frame.addClass('cms-loader');
166
if (CMS.API && CMS.API.Toolbar) {
167
CMS.API.Toolbar.showLoader();
170
// we need to modify the url appropriately to pass
171
// language and page to the params
172
if (url.indexOf(CMS.config.request.tree) >= 0) {
173
if (CMS.config.request.language) {
174
params.push(language);
176
if (CMS.config.request.page_id) {
177
params.push(page_id);
181
url = this.makeURL(url, params);
186
// cancel animation if sideframe is already shown
187
if (isFrameVisible && currentWidth < width) {
188
// The user has performed an action that requires the
189
// sideframe to be shown, this intent outweighs any
190
// previous intent to minimize the frame.
191
CMS.settings.sideframe.hidden = false;
194
if (isFrameVisible && Math.round(currentWidth) === Math.round(width)) {
195
// Math.round because subpixel values
200
this._show(width, animate);
206
* Handles content replacement mechanisms.
210
* @param {String} url valid uri to pass on the iframe
212
_content: function _content(url) {
214
var iframe = $('<iframe src="' + url + '" class="" frameborder="0" />');
215
var holder = this.ui.frame;
218
var iOS = /iPhone|iPod|iPad/.test(navigator.userAgent);
221
* On iOS iframes do not respect the size set in css or attributes, and
222
* is always matching the content. However, if you first load the page
223
* with one amount of content (small) and then from there you'd go to a page
224
* with lots of content (long, scroll requred) it won't be scrollable, because
225
* iframe would retain the size of previous page. When this happens we
226
* need to rerender the iframe (that's why we are animating the width here, so far
227
* that was the only reliable way). But after that if you try to scroll the iframe
228
* which height was just adjusted it will hide completely from the screen
229
* (this is an iOS glitch, the content would still be there and in fact it would
230
* be usable, but just not visible). To get rid of that we bring up the shim element
231
* up and down again and this fixes the glitch. (same shim we use for resizing the sideframe)
233
* It is not recommended to expose it and use it on other devices rather than iOS ones.
235
* @function forceRerenderOnIOS
238
function forceRerenderOnIOS() {
239
var w = that.ui.sideframe.width();
240
that.ui.sideframe.animate({ 'width': w + 1 }, 0);
241
setTimeout(function () {
242
that.ui.sideframe.animate({ 'width': w }, 0);
243
that.ui.shim.css('z-index', 20);
244
setTimeout(function () {
245
that.ui.shim.css('z-index', 1);
250
// attach load event to iframe
251
iframe.hide().on('load', function () {
252
contents = iframe.contents();
253
body = contents.find('body');
256
body.addClass('cms-admin cms-admin-sideframe');
259
that.ui.frame.removeClass('cms-loader');
263
// force style recalculation on iOS
265
forceRerenderOnIOS();
269
if (CMS.config.debug) {
270
body.addClass('cms-debug');
273
// save url in settings
274
CMS.settings.sideframe.url = iframe[0].contentWindow.location.href;
275
CMS.settings = that.setSettings(CMS.settings);
277
// This essentially hides the toolbar dropdown when
278
// click happens inside of a sideframe iframe
279
body.on(that.click, function () {
280
$(document).trigger(that.click);
283
// attach close event
284
body.on('keydown.cms', function (e) {
285
if (e.keyCode === CMS.KEYS.ESC) {
290
// adding django hacks
291
contents.find('.viewsitelink').attr('target', '_top');
294
that._addToHistory(this.contentWindow.location.href);
297
// clear the frame (removes all the handlers)
304
* Animation helper for opening the sideframe.
308
* @param {Number} width width that the iframes opens to
309
* @param {Number} [animate] Animation duration
311
_show: function _show(width, animate) {
314
this.ui.sideframe.show();
316
// check if sideframe should be hidden
317
if (CMS.settings.sideframe.hidden) {
321
// otherwise do normal behaviour
323
this.ui.sideframe.animate({
326
}, this.options.sideframeDuration);
328
this.ui.sideframe.css('width', width);
329
// reset width if larger than available space
330
if (width >= $(window).width()) {
331
this.ui.sideframe.css({
332
width: $(window).width() - 30,
338
// trigger API handlers
339
if (CMS.API && CMS.API.Toolbar) {
340
// FIXME: initialization needs to be done after our libs are loaded
341
CMS.API.Toolbar.open();
342
CMS.API.Toolbar.hideLoader();
343
CMS.API.Toolbar._lock(true);
346
// add esc close event
347
this.ui.body.off('keydown.cms.close').on('keydown.cms.close', function (e) {
348
if (e.keyCode === CMS.KEYS.ESC) {
349
that.options.onClose = null;
354
// disable scrolling for touch
355
this.ui.body.addClass('cms-prevent-scrolling');
356
this.preventTouchScrolling($(document), 'sideframe');
360
* Closes the current instance.
364
close: function close() {
365
// hide dimmer immediately
366
this.ui.dimmer.hide();
369
CMS.settings.sideframe = {
372
width: this.options.sideframeWidth
374
CMS.settings = this.setSettings(CMS.settings);
376
// check for reloading
377
this.reloadBrowser(this.options.onClose, false, true);
379
// trigger hide animation
381
duration: this.options.sideframeDuration / 2
386
* Animation helper for closing the iframe.
390
* @param {Object} opts
391
* @param {Number} opts.duration animation duration
393
_hide: function _hide(opts) {
394
var duration = this.options.sideframeDuration;
395
if (opts && typeof opts.duration === 'number') {
396
duration = opts.duration;
399
this.ui.sideframe.animate({ width: 0 }, duration, function () {
402
this.ui.frame.removeClass('cms-loader');
404
if (CMS.API && CMS.API.Toolbar) {
405
CMS.API.Toolbar._lock(false);
408
this.ui.body.off('keydown.cms.close');
410
// enable scrolling again
411
this.ui.body.removeClass('cms-prevent-scrolling');
412
this.allowTouchScrolling($(document), 'sideframe');
416
* Initiates the start resize event from `_events`.
418
* @method _startResize
421
_startResize: function _startResize() {
423
var outerOffset = 30;
424
var timer = function () {};
426
// create event for stopping
427
this.ui.body.on(this.pointerUp, function (e) {
432
// this prevents the iframe from being focusable
433
this.ui.shim.css('z-index', 20);
435
this.ui.body.attr('data-touch-action', 'none').on(this.pointerMove, function (e) {
436
if (e.originalEvent.clientX <= 320) {
437
e.originalEvent.clientX = 320;
439
if (e.originalEvent.clientX >= $(window).width() - outerOffset) {
440
e.originalEvent.clientX = $(window).width() - outerOffset;
443
that.ui.sideframe.css('width', e.originalEvent.clientX);
446
CMS.settings.sideframe.position = e.originalEvent.clientX;
448
// save position into our settings
450
timer = setTimeout(function () {
451
CMS.settings = that.setSettings(CMS.settings);
452
}, that.settingsRefreshTimer);
457
* Initiates the stop resize event from `_startResize`.
459
* @method _stopResize
462
_stopResize: function _stopResize() {
463
this.ui.shim.css('z-index', 1);
466
.off(this.pointerMove)
467
.removeAttr('data-touch-action');
471
* Retrieves the history states from `this.history`.
473
* @method _goToHistory
475
* @param {String} type can be either `back` or `forward`
477
_goToHistory: function _goToHistory(type) {
478
var iframe = this.ui.frame.find('iframe');
481
if (type === 'back') {
482
// remove latest entry (which is the current site)
483
this.history.forward.push(this.history.back.pop());
484
iframe.attr('src', this.history.back[this.history.back.length - 1]);
487
if (type === 'forward') {
488
tmp = this.history.forward.pop();
489
this.history.back.push(tmp);
490
iframe.attr('src', tmp);
493
this._updateHistoryButtons();
497
* Stores the history states in `this.history`.
499
* @method _addToHistory
501
* @param {String} url url to be stored in `this.history.back`
503
_addToHistory: function _addToHistory(url) {
504
var iframe = this.ui.frame.find('iframe');
506
// we need to update history first
507
this.history.back.push(url);
508
// and than set local variables
509
var length = this.history.back.length;
511
// store current url if array is empty
512
if (this.history.back.length <= 0) {
513
this.history.back.push(iframe.attr('src'));
516
// check for duplicates
517
if (this.history.back[length - 1] === this.history.back[length - 2]) {
518
this.history.back.pop();
521
this._updateHistoryButtons();
525
* Sets the correct states for the history UI elements.
527
* @method _updateHistoryButtons
530
_updateHistoryButtons: function _updateHistoryButtons() {
531
if (this.history.back.length > 1) {
532
this.ui.historyBack.removeClass('cms-icon-disabled');
534
this.ui.historyBack.addClass('cms-icon-disabled');
537
if (this.history.forward.length >= 1) {
538
this.ui.historyForward.removeClass('cms-icon-disabled');
540
this.ui.historyForward.addClass('cms-icon-disabled');