From 9adfde0d23bf0d5b98e5b2daf1fa69d972a88e7e Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 4 May 2016 16:58:00 +0200 Subject: [PATCH 01/13] Added a frist draft of StickyToolbarView. --- src/stickytoolbar/stickytoolbarview.js | 118 +++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/stickytoolbar/stickytoolbarview.js diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js new file mode 100644 index 0000000..c88be36 --- /dev/null +++ b/src/stickytoolbar/stickytoolbarview.js @@ -0,0 +1,118 @@ +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +import ToolbarView from '../toolbar/toolbarview.js'; + +/** + * The stocky toolbar view class. + * + * @memberOf ui.toolbar + * @extends ui.toolbar + */ + +export default class StickyToolbarView extends ToolbarView { + constructor( model ) { + super( model ); + + this.model.set( 'isSticky', false ); + + const bind = this.attributeBinder; + this.template.attributes.class.push( bind.if( 'isSticky', 'ck-toolbar-sticky' ) ); + } + + init() { + super.init(); + + /** + * Toolbar placeholder is a dummy element which replaces the actual toolbar as + * long as the actual toolbar is sticky. It prevents flickering of the UI. + * + * @private + * @type {HTMLElement} + * @property _elementPlaceholder + */ + this._elementPlaceholder = document.createElement( 'div' ); + this._elementPlaceholder.classList.add( 'ck-toolbar-sticky-placeholder' ); + this.element.parentNode.insertBefore( this._elementPlaceholder, this.element ); + + // Update sticky state of the toolbar as the window is being scrolled. + this.listenTo( window, 'scroll', () => { + this._checkIfShouldBeSticky(); + } ); + + // Synchronize with `model.isActive` because sticking an inactive toolbar is pointless. + this.model.on( 'change:isActive', ( evt, name, value ) => { + if ( value ) { + this._checkIfShouldBeSticky(); + } else { + this._detach(); + } + } ); + } + + /** + * TODO + * + * @protected + */ + _checkIfShouldBeSticky() { + const rectElement = this.model.isSticky ? + this._elementPlaceholder : this.element; + const rect = rectElement.getBoundingClientRect(); + + if ( rect.top < 0 && this.model.isActive ) { + this._stick( rect ); + } else { + this._detach(); + } + } + + /** + * TODO + * + * @protected + */ + _stick( regionRect ) { + const elementStyle = this.element.style; + const placeholderStyle = this._elementPlaceholder.style; + + // Setup placeholder. + placeholderStyle.display = 'block'; + placeholderStyle.height = regionRect.height + 'px'; + + // Stick the top region. + elementStyle.position = 'fixed'; + elementStyle.top = 0; + elementStyle.background = 'rgba(0,0,255,.2)'; + elementStyle.width = regionRect.width + 'px'; + elementStyle.marginLeft = -window.scrollX + 'px'; + + this.model.isSticky = true; + } + + /** + * TODO + * + * @protected + */ + _detach() { + const elementStyle = this.element.style; + const placeholderStyle = this._elementPlaceholder.style; + + // Release the placeholder. + placeholderStyle.display = 'none'; + + // "Peel off" the top region. + elementStyle.position = 'static'; + elementStyle.top = 'auto'; + elementStyle.background = 'none'; + elementStyle.width = 'auto'; + elementStyle.marginLeft = 'auto'; + + this.model.isSticky = false; + } +} From d10aedae671b988b891ea96202252770faf4c0ab Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 4 May 2016 18:23:13 +0200 Subject: [PATCH 02/13] Extended documentation for StickyToolbarModel. Used BEM for class names. Code refactoring. --- src/stickytoolbar/stickytoolbarview.js | 59 +++++++++++++++----------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js index c88be36..9dc573b 100644 --- a/src/stickytoolbar/stickytoolbarview.js +++ b/src/stickytoolbar/stickytoolbarview.js @@ -8,20 +8,29 @@ import ToolbarView from '../toolbar/toolbarview.js'; /** - * The stocky toolbar view class. + * The sticky toolbar view class. * * @memberOf ui.toolbar - * @extends ui.toolbar + * @extends ui.toolbarView */ export default class StickyToolbarView extends ToolbarView { constructor( model ) { super( model ); + const bind = this.attributeBinder; + + /** + * Indicates whether the toolbar is in the "sticky" state. + * + * @readonly + * @observable + * @member {Boolean} ui.button.StickyToolbarModel#isSticky + */ this.model.set( 'isSticky', false ); - const bind = this.attributeBinder; - this.template.attributes.class.push( bind.if( 'isSticky', 'ck-toolbar-sticky' ) ); + // Toggle class of the toolbar when "sticky" state changes in the model. + this.template.attributes.class.push( bind.if( 'isSticky', 'ck-toolbar_sticky' ) ); } init() { @@ -36,7 +45,7 @@ export default class StickyToolbarView extends ToolbarView { * @property _elementPlaceholder */ this._elementPlaceholder = document.createElement( 'div' ); - this._elementPlaceholder.classList.add( 'ck-toolbar-sticky-placeholder' ); + this._elementPlaceholder.classList.add( 'ck-toolbar__placeholder' ); this.element.parentNode.insertBefore( this._elementPlaceholder, this.element ); // Update sticky state of the toolbar as the window is being scrolled. @@ -77,19 +86,20 @@ export default class StickyToolbarView extends ToolbarView { * @protected */ _stick( regionRect ) { - const elementStyle = this.element.style; - const placeholderStyle = this._elementPlaceholder.style; - // Setup placeholder. - placeholderStyle.display = 'block'; - placeholderStyle.height = regionRect.height + 'px'; + Object.assign( this._elementPlaceholder.style, { + display: 'block', + height: regionRect.height + 'px' + } ); // Stick the top region. - elementStyle.position = 'fixed'; - elementStyle.top = 0; - elementStyle.background = 'rgba(0,0,255,.2)'; - elementStyle.width = regionRect.width + 'px'; - elementStyle.marginLeft = -window.scrollX + 'px'; + Object.assign( this.element.style, { + position: 'fixed', + top: 0, + background: 'rgba(0,0,255,.2)', + width: regionRect.width + 'px', + marginLeft: -window.scrollX + 'px' + } ); this.model.isSticky = true; } @@ -100,18 +110,19 @@ export default class StickyToolbarView extends ToolbarView { * @protected */ _detach() { - const elementStyle = this.element.style; - const placeholderStyle = this._elementPlaceholder.style; - // Release the placeholder. - placeholderStyle.display = 'none'; + Object.assign( this._elementPlaceholder.style, { + display: 'none' + } ); // "Peel off" the top region. - elementStyle.position = 'static'; - elementStyle.top = 'auto'; - elementStyle.background = 'none'; - elementStyle.width = 'auto'; - elementStyle.marginLeft = 'auto'; + Object.assign( this.element.style, { + position: 'static', + top: 'auto', + background: 'none', + width: 'auto', + marginLeft: 'auto' + } ); this.model.isSticky = false; } From 92c2ba2d766cde53fbefd59405825ac24ee0f270 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Wed, 4 May 2016 18:35:36 +0200 Subject: [PATCH 03/13] Added theme file for StickyToolbar. --- src/stickytoolbar/stickytoolbarview.js | 6 ------ theme/components/stickytoolbar.scss | 8 ++++++++ theme/theme.scss | 4 ++++ 3 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 theme/components/stickytoolbar.scss create mode 100644 theme/theme.scss diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js index 9dc573b..84b799c 100644 --- a/src/stickytoolbar/stickytoolbarview.js +++ b/src/stickytoolbar/stickytoolbarview.js @@ -94,9 +94,6 @@ export default class StickyToolbarView extends ToolbarView { // Stick the top region. Object.assign( this.element.style, { - position: 'fixed', - top: 0, - background: 'rgba(0,0,255,.2)', width: regionRect.width + 'px', marginLeft: -window.scrollX + 'px' } ); @@ -117,9 +114,6 @@ export default class StickyToolbarView extends ToolbarView { // "Peel off" the top region. Object.assign( this.element.style, { - position: 'static', - top: 'auto', - background: 'none', width: 'auto', marginLeft: 'auto' } ); diff --git a/theme/components/stickytoolbar.scss b/theme/components/stickytoolbar.scss new file mode 100644 index 0000000..d985aef --- /dev/null +++ b/theme/components/stickytoolbar.scss @@ -0,0 +1,8 @@ +// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. +// For licensing, see LICENSE.md or http://ckeditor.com/license + +.ck-toolbar_sticky { + position: fixed; + top: 0; + background: rgba(0,0,255,.2); +} diff --git a/theme/theme.scss b/theme/theme.scss new file mode 100644 index 0000000..dcddcb8 --- /dev/null +++ b/theme/theme.scss @@ -0,0 +1,4 @@ +// Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. +// For licensing, see LICENSE.md or http://ckeditor.com/license + +@import 'components/stickytoolbar'; From 0a6eef1ee5a51636b9f296b0875376c8211acf81 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 5 May 2016 13:55:50 +0200 Subject: [PATCH 04/13] Theme adjustments for StickyToolbarView. --- src/stickytoolbar/stickytoolbarview.js | 3 ++- src/toolbar/toolbarview.js | 2 +- theme/components/stickytoolbar.scss | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js index 84b799c..f67c332 100644 --- a/src/stickytoolbar/stickytoolbarview.js +++ b/src/stickytoolbar/stickytoolbarview.js @@ -95,7 +95,8 @@ export default class StickyToolbarView extends ToolbarView { // Stick the top region. Object.assign( this.element.style, { width: regionRect.width + 'px', - marginLeft: -window.scrollX + 'px' + // Compensate 1px border which is added when becoming "sticky". + marginLeft: -window.scrollX - 1 + 'px' } ); this.model.isSticky = true; diff --git a/src/toolbar/toolbarview.js b/src/toolbar/toolbarview.js index 8300515..f9ff3e9 100644 --- a/src/toolbar/toolbarview.js +++ b/src/toolbar/toolbarview.js @@ -21,7 +21,7 @@ export default class ToolbarView extends View { this.template = { tag: 'div', attributes: { - class: [ 'ck-toolbar' ] + class: [ 'ck-reset ck-toolbar' ] } }; diff --git a/theme/components/stickytoolbar.scss b/theme/components/stickytoolbar.scss index d985aef..725e9ca 100644 --- a/theme/components/stickytoolbar.scss +++ b/theme/components/stickytoolbar.scss @@ -1,8 +1,11 @@ // Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. // For licensing, see LICENSE.md or http://ckeditor.com/license -.ck-toolbar_sticky { - position: fixed; - top: 0; - background: rgba(0,0,255,.2); +@include ck-editor { + .ck-toolbar.ck-toolbar_sticky { + position: fixed; + top: 0; + border: 1px solid ck-border-color(); + background: ck-color( 'foreground' ); + } } From 136862cc2cabb0bddfb2a8516893d95bfb337054 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 5 May 2016 14:07:12 +0200 Subject: [PATCH 05/13] Minor fixes to StickyToolbarView positioning. --- src/stickytoolbar/stickytoolbarview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js index f67c332..2398a9e 100644 --- a/src/stickytoolbar/stickytoolbarview.js +++ b/src/stickytoolbar/stickytoolbarview.js @@ -94,8 +94,8 @@ export default class StickyToolbarView extends ToolbarView { // Stick the top region. Object.assign( this.element.style, { - width: regionRect.width + 'px', // Compensate 1px border which is added when becoming "sticky". + width: regionRect.width + 2 + 'px', marginLeft: -window.scrollX - 1 + 'px' } ); From 0022b3010f5eaae2d9f022468963fb1e7268f2f5 Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Mon, 9 May 2016 11:14:11 +0200 Subject: [PATCH 06/13] Updated documentation of StickyToolbarView. --- src/stickytoolbar/stickytoolbarview.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js index 2398a9e..fe5a614 100644 --- a/src/stickytoolbar/stickytoolbarview.js +++ b/src/stickytoolbar/stickytoolbarview.js @@ -37,8 +37,8 @@ export default class StickyToolbarView extends ToolbarView { super.init(); /** - * Toolbar placeholder is a dummy element which replaces the actual toolbar as - * long as the actual toolbar is sticky. It prevents flickering of the UI. + * A dummy element which visually fills the space as long as the + * actual toolbar is sticky. It prevents flickering of the UI. * * @private * @type {HTMLElement} @@ -64,7 +64,9 @@ export default class StickyToolbarView extends ToolbarView { } /** - * TODO + * Analyzes the environment to decide whether the toolbar should + * be sticky or not. Then, it uses {@link _stick} and {@link _detach} + * methods to manage the state of the toolbar. * * @protected */ @@ -81,8 +83,13 @@ export default class StickyToolbarView extends ToolbarView { } /** - * TODO + * Sticks the toolbar to the top edge of the viewport simulating + * CSS position:sticky. Also see {@link _detach}. * + * TODO: Possibly replaced by CSS in the future + * http://caniuse.com/#feat=css-sticky + * + * @param {Object} regionRect An output of getBoundingClientRect native DOM method. * @protected */ _stick( regionRect ) { @@ -103,7 +110,8 @@ export default class StickyToolbarView extends ToolbarView { } /** - * TODO + * Detaches the toolbar from the top edge of the viewport. + * See {@link _stick}. * * @protected */ @@ -113,7 +121,7 @@ export default class StickyToolbarView extends ToolbarView { display: 'none' } ); - // "Peel off" the top region. + // Detach the top region. Object.assign( this.element.style, { width: 'auto', marginLeft: 'auto' From 274b55d5f8df1665ed78109d57a6392a3b44f845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Tue, 10 May 2016 15:42:47 +0200 Subject: [PATCH 07/13] Minor API doc fix. --- src/stickytoolbar/stickytoolbarview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stickytoolbar/stickytoolbarview.js b/src/stickytoolbar/stickytoolbarview.js index fe5a614..29a89f4 100644 --- a/src/stickytoolbar/stickytoolbarview.js +++ b/src/stickytoolbar/stickytoolbarview.js @@ -89,8 +89,8 @@ export default class StickyToolbarView extends ToolbarView { * TODO: Possibly replaced by CSS in the future * http://caniuse.com/#feat=css-sticky * - * @param {Object} regionRect An output of getBoundingClientRect native DOM method. * @protected + * @param {Object} regionRect An output of getBoundingClientRect native DOM method. */ _stick( regionRect ) { // Setup placeholder. From d11c824f0ccbd3a9abd1a6fa20c442cb39588aff Mon Sep 17 00:00:00 2001 From: Aleksander Nowodzinski Date: Thu, 12 May 2016 15:34:29 +0200 Subject: [PATCH 08/13] Added IframeView component with tests. --- src/iframe/iframeview.js | 108 +++++++++++++++++++++++++++++++++++++ tests/iframe/iframeview.js | 99 ++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 src/iframe/iframeview.js create mode 100644 tests/iframe/iframeview.js diff --git a/src/iframe/iframeview.js b/src/iframe/iframeview.js new file mode 100644 index 0000000..65a2671 --- /dev/null +++ b/src/iframe/iframeview.js @@ -0,0 +1,108 @@ +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +import View from '../view.js'; + +/** + * The basic iframe view class. + * + * @memberOf ui.iframe + * @extends ui.View + */ +export default class IframeView extends View { + /** + * Creates a new instance of the IframeView. + * + * @param {ui.iframe.IframeModel} [model] (View)Model of this IframeView. + * @param {utils.Locale} [locale] The {@link ckeditor5.Editor#locale editor's locale} instance. + */ + constructor( model, locale ) { + super( model, locale ); + + const bind = this.attributeBinder; + + this.template = { + tag: 'iframe', + attributes: { + class: [ 'ck-reset-all' ], + // It seems that we need to allow scripts in order to be able to listen to events. + // TODO: Research that. Perhaps the src must be set? + sandbox: 'allow-same-origin allow-scripts', + width: bind.to( 'width' ), + height: bind.to( 'height' ) + }, + on: { + load: 'loaded' + } + }; + + /** + * A promise returned by {@link init} since iframe loading may be asynchronous. + * + * **Note**: Listening to `load` in {@link init} makes no sense because at this point + * the element is already in the DOM and the `load` event might already be fired. + * + * See {@link _iframeDeferred}. + * + * @private + * @member {Object} ui.iframe.IframeView + */ + this._iframePromise = new Promise( ( resolve, reject ) => { + /** + * A deferred object used to resolve the iframe promise associated with + * asynchronous loading of `contentDocument`. See {@link _iframePromise}. + * + * @private + * @member {Object} ui.iframe.IframeView + */ + this._iframeDeferred = { resolve, reject }; + } ); + + this.on( 'loaded', () => { + this._iframeDeferred.resolve(); + } ); + } + + /** + * Initializes iframe {@link element} and returns a `Promise` for asynchronous + * child `contentDocument` loading process. See {@link _iframePromise}. + * + * @returns {Promise} A promise which resolves once the iframe `contentDocument` has + * been {@link ui.iframe.IframeView#loaded loaded}. + */ + init() { + super.init(); + + return this._iframePromise; + } +} + +/** + * Fired when the iframe `contentDocument` finished loading. + * + * @event ui.iframe.IframeView#loaded + */ + +/** + * The basic iframe model interface. + * + * @memberOf ui.iframe + * @interface IframeModel + * @mixes utils.ObservableMixin + */ + +/** + * The width of the iframe. + * + * @member {Number} ui.iframe.IframeModel#width + */ + +/** + * The height of the iframe. + * + * @member {Number} ui.iframe.IframeModel#height + */ diff --git a/tests/iframe/iframeview.js b/tests/iframe/iframeview.js new file mode 100644 index 0000000..f9d4e95 --- /dev/null +++ b/tests/iframe/iframeview.js @@ -0,0 +1,99 @@ +/** + * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* bender-tags: ui, button */ + +'use strict'; + +import IframeView from '/ckeditor5/ui/iframe/iframeview.js'; +import Model from '/ckeditor5/ui/model.js'; + +describe( 'IframeView', () => { + let model, view; + + beforeEach( () => { + model = new Model( { + width: 100, + height: 200 + } ); + + view = new IframeView( model ); + + view.init(); + } ); + + describe( 'constructor', () => { + it( 'creates view element from the template', () => { + expect( view.element.classList.contains( 'ck-reset-all' ) ).to.be.true; + expect( view.element.attributes.getNamedItem( 'sandbox' ).value ).to.equal( 'allow-same-origin allow-scripts' ); + } ); + } ); + + describe( 'init', () => { + it( 'returns promise', () => { + view = new IframeView( model ); + + expect( view.init() ).to.be.an.instanceof( Promise ); + } ); + + it( 'returns promise which is resolved when iframe finished loading', () => { + view = new IframeView( model ); + + const promise = view.init().then( () => { + expect( view.element.contentDocument.readyState ).to.equal( 'complete' ); + } ); + + // Moving iframe into DOM trigger creation of a document inside iframe. + document.body.appendChild( view.element ); + + return promise; + } ); + } ); + + describe( 'loaded event', () => { + it( 'is fired when frame finished loading', ( done ) => { + view = new IframeView( model ); + + view.on( 'loaded', () => { + done(); + } ); + + view.init(); + + // Moving iframe into DOM trigger creation of a document inside iframe. + document.body.appendChild( view.element ); + } ); + } ); + + describe( '