~hanspayer/+junk/kobersdorf

« back to all changes in this revision

Viewing changes to static/cms/js/modules/cms.sideframe.js

  • Committer: Payer Hans-Christian
  • Date: 2016-03-29 20:18:05 UTC
  • Revision ID: hans@net-so.org-20160329201805-cs2re2zwb7svwje4
base template working

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright https://github.com/divio/django-cms
 
3
 */
 
4
 
 
5
// #############################################################################
 
6
// NAMESPACES
 
7
/**
 
8
 * @module CMS
 
9
 */
 
10
var CMS = window.CMS || {};
 
11
 
 
12
// #############################################################################
 
13
// SIDEFRAME
 
14
(function ($) {
 
15
    'use strict';
 
16
 
 
17
    // shorthand for jQuery(document).ready();
 
18
    $(function () {
 
19
        /**
 
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.
 
23
         *
 
24
         * @class Sideframe
 
25
         * @namespace CMS
 
26
         * @uses CMS.API.Helpers
 
27
         */
 
28
        CMS.Sideframe = new CMS.Class({
 
29
 
 
30
            implement: [CMS.API.Helpers],
 
31
 
 
32
            options: {
 
33
                onClose: false,
 
34
                sideframeDuration: 300,
 
35
                sideframeWidth: 0.8 // matches 80% of window width
 
36
            },
 
37
 
 
38
            initialize: function initialize(options) {
 
39
                this.options = $.extend(true, {}, this.options, options);
 
40
 
 
41
                // elements
 
42
                this._setupUI();
 
43
 
 
44
                // states and events
 
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;
 
51
            },
 
52
 
 
53
            /**
 
54
             * Stores all jQuery references within `this.ui`.
 
55
             *
 
56
             * @method _setupUI
 
57
             * @private
 
58
             */
 
59
            _setupUI: function _setupUI() {
 
60
                var sideframe = $('.cms-sideframe');
 
61
                this.ui = {
 
62
                    sideframe: sideframe,
 
63
                    body: $('html'),
 
64
                    window: $(window),
 
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')
 
72
                };
 
73
            },
 
74
 
 
75
            /**
 
76
             * Sets up all the event handlers, such as closing and resizing.
 
77
             *
 
78
             * @method _events
 
79
             * @private
 
80
             */
 
81
            _events: function _events() {
 
82
                var that = this;
 
83
 
 
84
                // we need to set the history state on event creation
 
85
                // to ensure we start with clean states in new instances
 
86
                this.history = {
 
87
                    back: [],
 
88
                    forward: []
 
89
                };
 
90
 
 
91
                this.ui.close.off(this.click).on(this.click, function () {
 
92
                    that.close();
 
93
                });
 
94
 
 
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) {
 
98
                    e.preventDefault();
 
99
                    that._startResize();
 
100
                });
 
101
 
 
102
                // close sideframe when clicking on the dimmer
 
103
                this.ui.dimmer.off(this.click).on(this.click, function () {
 
104
                    that.close();
 
105
                });
 
106
 
 
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')) {
 
110
                        return false;
 
111
                    }
 
112
                    that._goToHistory('back');
 
113
                });
 
114
 
 
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')) {
 
118
                        return false;
 
119
                    }
 
120
                    that._goToHistory('forward');
 
121
                });
 
122
            },
 
123
 
 
124
            /**
 
125
             * Opens a given url within a sideframe.
 
126
             *
 
127
             * @method open
 
128
             * @chainable
 
129
             * @param {Object} opts
 
130
             * @param {String} opts.url url to render iframe
 
131
             * @param {Boolean} [opts.animate] should modal be animated
 
132
             */
 
133
            open: function open(opts) {
 
134
                if (!(opts && opts.url)) {
 
135
                    throw new Error('The arguments passed to "open" were invalid.');
 
136
                }
 
137
 
 
138
                var url = opts.url;
 
139
                var animate = opts.animate;
 
140
 
 
141
                // setup internals
 
142
                var language = 'language=' + CMS.config.request.language;
 
143
                var page_id = 'page_id=' + CMS.config.request.page_id;
 
144
                var params = [];
 
145
                var width = window.innerWidth;
 
146
                var currentWidth = this.ui.sideframe.outerWidth();
 
147
                var isFrameVisible = this.ui.sideframe.is(':visible');
 
148
 
 
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 + '%');
 
153
                }
 
154
 
 
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.
 
159
                this._events();
 
160
 
 
161
                // show dimmer even before iframe is loaded
 
162
                this.ui.dimmer.show();
 
163
                this.ui.frame.addClass('cms-loader');
 
164
 
 
165
                // show loader
 
166
                if (CMS.API && CMS.API.Toolbar) {
 
167
                    CMS.API.Toolbar.showLoader();
 
168
                }
 
169
 
 
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);
 
175
                    }
 
176
                    if (CMS.config.request.page_id) {
 
177
                        params.push(page_id);
 
178
                    }
 
179
                }
 
180
 
 
181
                url = this.makeURL(url, params);
 
182
 
 
183
                // load the iframe
 
184
                this._content(url);
 
185
 
 
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;
 
192
                }
 
193
 
 
194
                if (isFrameVisible && Math.round(currentWidth) === Math.round(width)) {
 
195
                    // Math.round because subpixel values
 
196
                    animate = false;
 
197
                }
 
198
 
 
199
                // show iframe
 
200
                this._show(width, animate);
 
201
 
 
202
                return this;
 
203
            },
 
204
 
 
205
            /**
 
206
             * Handles content replacement mechanisms.
 
207
             *
 
208
             * @method _content
 
209
             * @private
 
210
             * @param {String} url valid uri to pass on the iframe
 
211
             */
 
212
            _content: function _content(url) {
 
213
                var that = this;
 
214
                var iframe = $('<iframe src="' + url + '" class="" frameborder="0" />');
 
215
                var holder = this.ui.frame;
 
216
                var contents;
 
217
                var body;
 
218
                var iOS = /iPhone|iPod|iPad/.test(navigator.userAgent);
 
219
 
 
220
                /**
 
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)
 
232
                 *
 
233
                 * It is not recommended to expose it and use it on other devices rather than iOS ones.
 
234
                 *
 
235
                 * @function forceRerenderOnIOS
 
236
                 * @private
 
237
                 */
 
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);
 
246
                        }, 0);
 
247
                    }, 0);
 
248
                }
 
249
 
 
250
                // attach load event to iframe
 
251
                iframe.hide().on('load', function () {
 
252
                    contents = iframe.contents();
 
253
                    body = contents.find('body');
 
254
 
 
255
                    // inject css class
 
256
                    body.addClass('cms-admin cms-admin-sideframe');
 
257
 
 
258
                    // remove loader
 
259
                    that.ui.frame.removeClass('cms-loader');
 
260
                    // than show
 
261
                    iframe.show();
 
262
 
 
263
                    // force style recalculation on iOS
 
264
                    if (iOS) {
 
265
                        forceRerenderOnIOS();
 
266
                    }
 
267
 
 
268
                    // add debug infos
 
269
                    if (CMS.config.debug) {
 
270
                        body.addClass('cms-debug');
 
271
                    }
 
272
 
 
273
                    // save url in settings
 
274
                    CMS.settings.sideframe.url = iframe[0].contentWindow.location.href;
 
275
                    CMS.settings = that.setSettings(CMS.settings);
 
276
 
 
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);
 
281
                    });
 
282
 
 
283
                    // attach close event
 
284
                    body.on('keydown.cms', function (e) {
 
285
                        if (e.keyCode === CMS.KEYS.ESC) {
 
286
                            that.close();
 
287
                        }
 
288
                    });
 
289
 
 
290
                    // adding django hacks
 
291
                    contents.find('.viewsitelink').attr('target', '_top');
 
292
 
 
293
                    // update history
 
294
                    that._addToHistory(this.contentWindow.location.href);
 
295
                });
 
296
 
 
297
                // clear the frame (removes all the handlers)
 
298
                holder.empty();
 
299
                // inject iframe
 
300
                holder.html(iframe);
 
301
            },
 
302
 
 
303
            /**
 
304
             * Animation helper for opening the sideframe.
 
305
             *
 
306
             * @method _show
 
307
             * @private
 
308
             * @param {Number} width width that the iframes opens to
 
309
             * @param {Number} [animate] Animation duration
 
310
             */
 
311
            _show: function _show(width, animate) {
 
312
                var that = this;
 
313
 
 
314
                this.ui.sideframe.show();
 
315
 
 
316
                // check if sideframe should be hidden
 
317
                if (CMS.settings.sideframe.hidden) {
 
318
                    this._hide();
 
319
                }
 
320
 
 
321
                // otherwise do normal behaviour
 
322
                if (animate) {
 
323
                    this.ui.sideframe.animate({
 
324
                        width: width,
 
325
                        overflow: 'visible'
 
326
                    }, this.options.sideframeDuration);
 
327
                } else {
 
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,
 
333
                            overflow: 'visible'
 
334
                        });
 
335
                    }
 
336
                }
 
337
 
 
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);
 
344
                }
 
345
 
 
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;
 
350
                        that.close();
 
351
                    }
 
352
                });
 
353
 
 
354
                // disable scrolling for touch
 
355
                this.ui.body.addClass('cms-prevent-scrolling');
 
356
                this.preventTouchScrolling($(document), 'sideframe');
 
357
            },
 
358
 
 
359
            /**
 
360
             * Closes the current instance.
 
361
             *
 
362
             * @method close
 
363
             */
 
364
            close: function close() {
 
365
                // hide dimmer immediately
 
366
                this.ui.dimmer.hide();
 
367
 
 
368
                // update settings
 
369
                CMS.settings.sideframe = {
 
370
                    url: null,
 
371
                    hidden: false,
 
372
                    width: this.options.sideframeWidth
 
373
                };
 
374
                CMS.settings = this.setSettings(CMS.settings);
 
375
 
 
376
                // check for reloading
 
377
                this.reloadBrowser(this.options.onClose, false, true);
 
378
 
 
379
                // trigger hide animation
 
380
                this._hide({
 
381
                    duration: this.options.sideframeDuration / 2
 
382
                });
 
383
            },
 
384
 
 
385
            /**
 
386
             * Animation helper for closing the iframe.
 
387
             *
 
388
             * @method _hide
 
389
             * @private
 
390
             * @param {Object} opts
 
391
             * @param {Number} opts.duration animation duration
 
392
             */
 
393
            _hide: function _hide(opts) {
 
394
                var duration = this.options.sideframeDuration;
 
395
                if (opts && typeof opts.duration === 'number') {
 
396
                    duration = opts.duration;
 
397
                }
 
398
 
 
399
                this.ui.sideframe.animate({ width: 0 }, duration, function () {
 
400
                    $(this).hide();
 
401
                });
 
402
                this.ui.frame.removeClass('cms-loader');
 
403
 
 
404
                if (CMS.API && CMS.API.Toolbar) {
 
405
                    CMS.API.Toolbar._lock(false);
 
406
                }
 
407
 
 
408
                this.ui.body.off('keydown.cms.close');
 
409
 
 
410
                // enable scrolling again
 
411
                this.ui.body.removeClass('cms-prevent-scrolling');
 
412
                this.allowTouchScrolling($(document), 'sideframe');
 
413
            },
 
414
 
 
415
            /**
 
416
             * Initiates the start resize event from `_events`.
 
417
             *
 
418
             * @method _startResize
 
419
             * @private
 
420
             */
 
421
            _startResize: function _startResize() {
 
422
                var that = this;
 
423
                var outerOffset = 30;
 
424
                var timer = function () {};
 
425
 
 
426
                // create event for stopping
 
427
                this.ui.body.on(this.pointerUp, function (e) {
 
428
                    e.preventDefault();
 
429
                    that._stopResize();
 
430
                });
 
431
 
 
432
                // this prevents the iframe from being focusable
 
433
                this.ui.shim.css('z-index', 20);
 
434
 
 
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;
 
438
                    }
 
439
                    if (e.originalEvent.clientX >= $(window).width() - outerOffset) {
 
440
                        e.originalEvent.clientX = $(window).width() - outerOffset;
 
441
                    }
 
442
 
 
443
                    that.ui.sideframe.css('width', e.originalEvent.clientX);
 
444
 
 
445
                    // update settings
 
446
                    CMS.settings.sideframe.position = e.originalEvent.clientX;
 
447
 
 
448
                    // save position into our settings
 
449
                    clearTimeout(timer);
 
450
                    timer = setTimeout(function () {
 
451
                        CMS.settings = that.setSettings(CMS.settings);
 
452
                    }, that.settingsRefreshTimer);
 
453
                });
 
454
            },
 
455
 
 
456
            /**
 
457
             * Initiates the stop resize event from `_startResize`.
 
458
             *
 
459
             * @method _stopResize
 
460
             * @private
 
461
             */
 
462
            _stopResize: function _stopResize() {
 
463
                this.ui.shim.css('z-index', 1);
 
464
                this.ui.body
 
465
                    .off(this.pointerUp)
 
466
                    .off(this.pointerMove)
 
467
                    .removeAttr('data-touch-action');
 
468
            },
 
469
 
 
470
            /**
 
471
             * Retrieves the history states from `this.history`.
 
472
             *
 
473
             * @method _goToHistory
 
474
             * @private
 
475
             * @param {String} type can be either `back` or `forward`
 
476
             */
 
477
            _goToHistory: function _goToHistory(type) {
 
478
                var iframe = this.ui.frame.find('iframe');
 
479
                var tmp;
 
480
 
 
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]);
 
485
                }
 
486
 
 
487
                if (type === 'forward') {
 
488
                    tmp = this.history.forward.pop();
 
489
                    this.history.back.push(tmp);
 
490
                    iframe.attr('src', tmp);
 
491
                }
 
492
 
 
493
                this._updateHistoryButtons();
 
494
            },
 
495
 
 
496
            /**
 
497
             * Stores the history states in `this.history`.
 
498
             *
 
499
             * @method _addToHistory
 
500
             * @private
 
501
             * @param {String} url url to be stored in `this.history.back`
 
502
             */
 
503
            _addToHistory: function _addToHistory(url) {
 
504
                var iframe = this.ui.frame.find('iframe');
 
505
 
 
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;
 
510
 
 
511
                // store current url if array is empty
 
512
                if (this.history.back.length <= 0) {
 
513
                    this.history.back.push(iframe.attr('src'));
 
514
                }
 
515
 
 
516
                // check for duplicates
 
517
                if (this.history.back[length - 1] === this.history.back[length - 2]) {
 
518
                    this.history.back.pop();
 
519
                }
 
520
 
 
521
                this._updateHistoryButtons();
 
522
            },
 
523
 
 
524
            /**
 
525
             * Sets the correct states for the history UI elements.
 
526
             *
 
527
             * @method _updateHistoryButtons
 
528
             * @private
 
529
             */
 
530
            _updateHistoryButtons: function _updateHistoryButtons() {
 
531
                if (this.history.back.length > 1) {
 
532
                    this.ui.historyBack.removeClass('cms-icon-disabled');
 
533
                } else {
 
534
                    this.ui.historyBack.addClass('cms-icon-disabled');
 
535
                }
 
536
 
 
537
                if (this.history.forward.length >= 1) {
 
538
                    this.ui.historyForward.removeClass('cms-icon-disabled');
 
539
                } else {
 
540
                    this.ui.historyForward.addClass('cms-icon-disabled');
 
541
                }
 
542
            }
 
543
        });
 
544
 
 
545
    });
 
546
})(CMS.$);