2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 * @fileOverview [Widget](http://ckeditor.com/addon/widget) plugin.
13 var DRAG_HANDLER_SIZE
= 15;
15 CKEDITOR
.plugins
.add( 'widget', {
16 // jscs:disable maximumLineLength
17 lang: 'af,ar,az,bg,ca,cs,cy,da,de,de-ch,el,en,en-gb,eo,es,eu,fa,fi,fr,gl,he,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,no,oc,pl,pt,pt-br,ru,sk,sl,sq,sv,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
18 // jscs:enable maximumLineLength
19 requires: 'lineutils,clipboard,widgetselection',
22 '.cke_widget_wrapper{' +
23 'position:relative;' +
26 '.cke_widget_inline{' +
27 'display:inline-block' +
29 '.cke_widget_wrapper:hover>.cke_widget_element{' +
30 'outline:2px solid yellow;' +
33 '.cke_widget_wrapper:hover .cke_widget_editable{' +
34 'outline:2px solid yellow' +
36 '.cke_widget_wrapper.cke_widget_focused>.cke_widget_element,' +
37 // We need higher specificity than hover style.
38 '.cke_widget_wrapper .cke_widget_editable.cke_widget_editable_focused{' +
39 'outline:2px solid #ace' +
41 '.cke_widget_editable{' +
44 '.cke_widget_drag_handler_container{' +
45 'position:absolute;' +
46 'width:' + DRAG_HANDLER_SIZE
+ 'px;' +
48 // Initially drag handler should not be visible, until its position will be
49 // calculated (#11177).
50 // We need to hide unpositined handlers, so they don't extend
51 // widget's outline far to the left (#12024).
54 'transition:height 0s 0.2s;' + // Delay hiding drag handler.
55 // Prevent drag handler from being misplaced (#11198).
58 '.cke_widget_wrapper:hover>.cke_widget_drag_handler_container{' +
59 'height:' + DRAG_HANDLER_SIZE
+ 'px;' +
62 '.cke_widget_drag_handler_container:hover{' +
65 'img.cke_widget_drag_handler{' +
67 'width:' + DRAG_HANDLER_SIZE
+ 'px;' +
68 'height:' + DRAG_HANDLER_SIZE
+ 'px;' +
69 'display:inline-block' +
72 'position:absolute;' +
79 '.cke_editable.cke_widget_dragging, .cke_editable.cke_widget_dragging *{' +
80 'cursor:move !important' +
85 beforeInit: function( editor
) {
87 * An instance of widget repository. It contains all
88 * {@link CKEDITOR.plugins.widget.repository#registered registered widget definitions} and
89 * {@link CKEDITOR.plugins.widget.repository#instances initialized instances}.
91 * editor.widgets.add( 'someName', {
92 * // Widget definition...
95 * editor.widgets.registered.someName; // -> Widget definition
99 * @property {CKEDITOR.plugins.widget.repository} widgets
100 * @member CKEDITOR.editor
102 editor
.widgets
= new Repository( editor
);
105 afterInit: function( editor
) {
106 addWidgetButtons( editor
);
107 setupContextMenu( editor
);
112 * Widget repository. It keeps track of all {@link #registered registered widget definitions} and
113 * {@link #instances initialized instances}. An instance of the repository is available under
114 * the {@link CKEDITOR.editor#widgets} property.
116 * @class CKEDITOR.plugins.widget.repository
117 * @mixins CKEDITOR.event
118 * @constructor Creates a widget repository instance. Note that the widget plugin automatically
119 * creates a repository instance which is available under the {@link CKEDITOR.editor#widgets} property.
120 * @param {CKEDITOR.editor} editor The editor instance for which the repository will be created.
122 function Repository( editor
) {
124 * The editor instance for which this repository was created.
127 * @property {CKEDITOR.editor} editor
129 this.editor
= editor
;
132 * A hash of registered widget definitions (definition name => {@link CKEDITOR.plugins.widget.definition}).
134 * To register a definition use the {@link #add} method.
138 this.registered
= {};
141 * An object containing initialized widget instances (widget id => {@link CKEDITOR.plugins.widget}).
148 * An array of selected widget instances.
151 * @property {CKEDITOR.plugins.widget[]} selected
156 * The focused widget instance. See also {@link CKEDITOR.plugins.widget#event-focus}
157 * and {@link CKEDITOR.plugins.widget#event-blur} events.
159 * editor.on( 'selectionChange', function() {
160 * if ( editor.widgets.focused ) {
161 * // Do something when a widget is focused...
166 * @property {CKEDITOR.plugins.widget} focused
171 * The widget instance that contains the nested editable which is currently focused.
174 * @property {CKEDITOR.plugins.widget} widgetHoldingFocusedEditable
176 this.widgetHoldingFocusedEditable
= null;
185 setupWidgetsLifecycle( this );
186 setupSelectionObserver( this );
187 setupMouseObserver( this );
188 setupKeyboardObserver( this );
189 setupDragAndDrop( this );
190 setupNativeCutAndCopy( this );
193 Repository
.prototype = {
195 * Minimum interval between selection checks.
199 MIN_SELECTION_CHECK_INTERVAL: 500,
202 * Adds a widget definition to the repository. Fires the {@link CKEDITOR.editor#widgetDefinition} event
203 * which allows to modify the widget definition which is going to be registered.
205 * @param {String} name The name of the widget definition.
206 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition.
207 * @returns {CKEDITOR.plugins.widget.definition}
209 add: function( name
, widgetDef
) {
210 // Create prototyped copy of original widget definition, so we won't modify it.
211 widgetDef
= CKEDITOR
.tools
.prototypedCopy( widgetDef
);
212 widgetDef
.name
= name
;
214 widgetDef
._
= widgetDef
._
|| {};
216 this.editor
.fire( 'widgetDefinition', widgetDef
);
218 if ( widgetDef
.template
)
219 widgetDef
.template
= new CKEDITOR
.template( widgetDef
.template
);
221 addWidgetCommand( this.editor
, widgetDef
);
222 addWidgetProcessors( this, widgetDef
);
224 this.registered
[ name
] = widgetDef
;
230 * Adds a callback for element upcasting. Each callback will be executed
231 * for every element which is later tested by upcast methods. If a callback
232 * returns `false`, the element will not be upcasted.
234 * // Images with the "banner" class will not be upcasted (e.g. to the image widget).
235 * editor.widgets.addUpcastCallback( function( element ) {
236 * if ( element.name == 'img' && element.hasClass( 'banner' ) )
240 * @param {Function} callback
241 * @param {CKEDITOR.htmlParser.element} callback.element
243 addUpcastCallback: function( callback
) {
244 this._
.upcastCallbacks
.push( callback
);
248 * Checks the selection to update widget states (selection and focus).
250 * This method is triggered by the {@link #event-checkSelection} event.
252 checkSelection: function() {
253 var sel
= this.editor
.getSelection(),
254 selectedElement
= sel
.getSelectedElement(),
255 updater
= stateUpdater( this ),
258 // Widget is focused so commit and finish checking.
259 if ( selectedElement
&& ( widget
= this.getByElement( selectedElement
, true ) ) )
260 return updater
.focus( widget
).select( widget
).commit();
262 var range
= sel
.getRanges()[ 0 ];
264 // No ranges or collapsed range mean that nothing is selected, so commit and finish checking.
265 if ( !range
|| range
.collapsed
)
266 return updater
.commit();
268 // Range is not empty, so create walker checking for wrappers.
269 var walker
= new CKEDITOR
.dom
.walker( range
),
272 walker
.evaluator
= Widget
.isDomWidgetWrapper
;
274 while ( ( wrapper
= walker
.next() ) )
275 updater
.select( this.getByElement( wrapper
) );
281 * Checks if all widget instances are still present in the DOM.
282 * Destroys those instances that are not present.
283 * Reinitializes widgets on widget wrappers for which widget instances
284 * cannot be found. Takes nested widgets into account, too.
286 * This method triggers the {@link #event-checkWidgets} event whose listeners
287 * can cancel the method's execution or modify its options.
289 * @param [options] The options object.
290 * @param {Boolean} [options.initOnlyNew] Initializes widgets only on newly wrapped
291 * widget elements (those which still have the `cke_widget_new` class). When this option is
292 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
293 * will not be reinitialized. This makes the check faster.
294 * @param {Boolean} [options.focusInited] If only one widget is initialized by
295 * the method, it will be focused.
297 checkWidgets: function( options
) {
298 this.fire( 'checkWidgets', CKEDITOR
.tools
.copy( options
|| {} ) );
302 * Removes the widget from the editor and moves the selection to the closest
303 * editable position if the widget was focused before.
305 * @param {CKEDITOR.plugins.widget} widget The widget instance to be deleted.
307 del: function( widget
) {
308 if ( this.focused
=== widget
) {
309 var editor
= widget
.editor
,
310 range
= editor
.createRange(),
313 // If haven't found place for caret on the default side,
314 // try to find it on the other side.
315 if ( !( found
= range
.moveToClosestEditablePosition( widget
.wrapper
, true ) ) )
316 found
= range
.moveToClosestEditablePosition( widget
.wrapper
, false );
319 editor
.getSelection().selectRanges( [ range
] );
322 widget
.wrapper
.remove();
323 this.destroy( widget
, true );
327 * Destroys the widget instance and all its nested widgets (widgets inside its nested editables).
329 * @param {CKEDITOR.plugins.widget} widget The widget instance to be destroyed.
330 * @param {Boolean} [offline] Whether the widget is offline (detached from the DOM tree) —
331 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
333 destroy: function( widget
, offline
) {
334 if ( this.widgetHoldingFocusedEditable
=== widget
)
335 setFocusedEditable( this, widget
, null, offline
);
337 widget
.destroy( offline
);
338 delete this.instances
[ widget
.id
];
339 this.fire( 'instanceDestroyed', widget
);
343 * Destroys all widget instances.
345 * @param {Boolean} [offline] Whether the widgets are offline (detached from the DOM tree) —
346 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
347 * @param {CKEDITOR.dom.element} [container] The container within widgets will be destroyed.
348 * This option will be ignored if the `offline` flag was set to `true`, because in such case
349 * it is not possible to find widgets within the passed block.
351 destroyAll: function( offline
, container
) {
354 instances
= this.instances
;
356 if ( container
&& !offline
) {
357 var wrappers
= container
.find( '.cke_widget_wrapper' ),
358 l
= wrappers
.count(),
361 // Length is constant, because this is not a live node list.
362 // Note: since querySelectorAll returns nodes in document order,
363 // outer widgets are always placed before their nested widgets and therefore
364 // are destroyed before them.
365 for ( ; i
< l
; ++i
) {
366 widget
= this.getByElement( wrappers
.getItem( i
), true );
367 // Widget might not be found, because it could be a nested widget,
368 // which would be destroyed when destroying its parent.
370 this.destroy( widget
);
376 for ( id
in instances
) {
377 widget
= instances
[ id
];
378 this.destroy( widget
, offline
);
383 * Finalizes a process of widget creation. This includes:
385 * * inserting widget element into editor,
386 * * marking widget instance as ready (see {@link CKEDITOR.plugins.widget#event-ready}),
387 * * focusing widget instance.
389 * This method is used by the default widget's command and is called
390 * after widget's dialog (if set) is closed. It may also be used in a
391 * customized process of widget creation and insertion.
393 * widget.once( 'edit', function() {
394 * // Finalize creation only of not ready widgets.
395 * if ( widget.isReady() )
398 * // Cancel edit event to prevent automatic widget insertion.
401 * CustomDialog.open( widget.data, function saveCallback( savedData ) {
402 * // Cache the container, because widget may be destroyed while saving data,
403 * // if this process will require some deep transformations.
404 * var container = widget.wrapper.getParent();
406 * widget.setData( savedData );
408 * // Widget will be retrieved from container and inserted into editor.
409 * editor.widgets.finalizeCreation( container );
413 * @param {CKEDITOR.dom.element/CKEDITOR.dom.documentFragment} container The element
414 * or document fragment which contains widget wrapper. The container is used, so before
415 * finalizing creation the widget can be freely transformed (even destroyed and reinitialized).
417 finalizeCreation: function( container
) {
418 var wrapper
= container
.getFirst();
419 if ( wrapper
&& Widget
.isDomWidgetWrapper( wrapper
) ) {
420 this.editor
.insertElement( wrapper
);
422 var widget
= this.getByElement( wrapper
);
423 // Fire postponed #ready event.
425 widget
.fire( 'ready' );
431 * Finds a widget instance which contains a given element. The element will be the {@link CKEDITOR.plugins.widget#wrapper wrapper}
432 * of the returned widget or a descendant of this {@link CKEDITOR.plugins.widget#wrapper wrapper}.
434 * editor.widgets.getByElement( someWidget.wrapper ); // -> someWidget
435 * editor.widgets.getByElement( someWidget.parts.caption ); // -> someWidget
437 * // Check wrapper only:
438 * editor.widgets.getByElement( someWidget.wrapper, true ); // -> someWidget
439 * editor.widgets.getByElement( someWidget.parts.caption, true ); // -> null
441 * @param {CKEDITOR.dom.element} element The element to be checked.
442 * @param {Boolean} [checkWrapperOnly] If set to `true`, the method will not check wrappers' descendants.
443 * @returns {CKEDITOR.plugins.widget} The widget instance or `null`.
445 getByElement: ( function() {
446 var validWrapperElements
= { div: 1, span: 1 };
447 function getWidgetId( element
) {
448 return element
.is( validWrapperElements
) && element
.data( 'cke-widget-id' );
451 return function( element
, checkWrapperOnly
) {
455 var id
= getWidgetId( element
);
457 // There's no need to check element parents if element is a wrapper.
458 if ( !checkWrapperOnly
&& !id
) {
459 var limit
= this.editor
.editable();
461 // Try to find a closest ascendant which is a widget wrapper.
463 element
= element
.getParent();
464 } while ( element
&& !element
.equals( limit
) && !( id
= getWidgetId( element
) ) );
467 return this.instances
[ id
] || null;
472 * Initializes a widget on a given element if the widget has not been initialized on it yet.
474 * @param {CKEDITOR.dom.element} element The future widget element.
475 * @param {String/CKEDITOR.plugins.widget.definition} [widgetDef] Name of a widget or a widget definition.
476 * The widget definition should be previously registered by using the
477 * {@link CKEDITOR.plugins.widget.repository#add} method.
478 * @param [startupData] Widget startup data (has precedence over default one).
479 * @returns {CKEDITOR.plugins.widget} The widget instance or `null` if a widget could not be initialized on
482 initOn: function( element
, widgetDef
, startupData
) {
484 widgetDef
= this.registered
[ element
.data( 'widget' ) ];
485 else if ( typeof widgetDef
== 'string' )
486 widgetDef
= this.registered
[ widgetDef
];
491 // Wrap element if still wasn't wrapped (was added during runtime by method that skips dataProcessor).
492 var wrapper
= this.wrapElement( element
, widgetDef
.name
);
495 // Check if widget wrapper is new (widget hasn't been initialized on it yet).
496 // This class will be removed by widget constructor to avoid locking snapshot twice.
497 if ( wrapper
.hasClass( 'cke_widget_new' ) ) {
498 var widget
= new Widget( this, this._
.nextId
++, element
, widgetDef
, startupData
);
500 // Widget could be destroyed when initializing it.
501 if ( widget
.isInited() ) {
502 this.instances
[ widget
.id
] = widget
;
510 // Widget already has been initialized, so try to get widget by element.
511 // Note - it may happen that other instance will returned than the one created above,
512 // if for example widget was destroyed and reinitialized.
513 return this.getByElement( element
);
516 // No wrapper means that there's no widget for this element.
521 * Initializes widgets on all elements which were wrapped by {@link #wrapElement} and
522 * have not been initialized yet.
524 * @param {CKEDITOR.dom.element} [container=editor.editable()] The container which will be checked for not
525 * initialized widgets. Defaults to editor's {@link CKEDITOR.editor#editable editable} element.
526 * @returns {CKEDITOR.plugins.widget[]} Array of widget instances which have been initialized.
527 * Note: Only first-level widgets are returned — without nested widgets.
529 initOnAll: function( container
) {
530 var newWidgets
= ( container
|| this.editor
.editable() ).find( '.cke_widget_new' ),
534 for ( var i
= newWidgets
.count(); i
--; ) {
535 instance
= this.initOn( newWidgets
.getItem( i
).getFirst( Widget
.isDomWidgetElement
) );
537 newInstances
.push( instance
);
544 * Allows to listen to events on specific types of widgets, even if they are not created yet.
546 * Please note that this method inherits parameters from the {@link CKEDITOR.event#method-on} method with one
547 * extra parameter at the beginning which is the widget name.
549 * editor.widgets.onWidget( 'image', 'action', function( evt ) {
550 * // Event `action` occurs on `image` widget.
554 * @param {String} widgetName
555 * @param {String} eventName
556 * @param {Function} listenerFunction
557 * @param {Object} [scopeObj]
558 * @param {Object} [listenerData]
559 * @param {Number} [priority=10]
561 onWidget: function( widgetName
) {
562 var args
= Array
.prototype.slice
.call( arguments
);
566 for ( var i
in this.instances
) {
567 var instance
= this.instances
[ i
];
569 if ( instance
.name
== widgetName
) {
570 instance
.on
.apply( instance
, args
);
574 this.on( 'instanceCreated', function( evt
) {
575 var widget
= evt
.data
;
577 if ( widget
.name
== widgetName
) {
578 widget
.on
.apply( widget
, args
);
584 * Parses element classes string and returns an object
585 * whose keys contain class names. Skips all `cke_*` classes.
587 * This method is used by the {@link CKEDITOR.plugins.widget#getClasses} method and
588 * may be used when overriding that method.
591 * @param {String} classes String (value of `class` attribute).
592 * @returns {Object} Object containing classes or `null` if no classes found.
594 parseElementClasses: function( classes
) {
598 classes
= CKEDITOR
.tools
.trim( classes
).split( /\s+/ );
604 while ( ( cl
= classes
.pop() ) ) {
605 if ( cl
.indexOf( 'cke_' ) == -1 )
606 obj
[ cl
] = hasClasses
= 1;
609 return hasClasses
? obj : null;
613 * Wraps an element with a widget's non-editable container.
615 * If this method is called on an {@link CKEDITOR.htmlParser.element}, then it will
616 * also take care of fixing the DOM after wrapping (the wrapper may not be allowed in element's parent).
618 * @param {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} element The widget element to be wrapped.
619 * @param {String} [widgetName] The name of the widget definition. Defaults to element's `data-widget`
621 * @returns {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The wrapper element or `null` if
622 * the widget definition of this name is not registered.
624 wrapElement: function( element
, widgetName
) {
629 if ( element
instanceof CKEDITOR
.dom
.element
) {
630 widgetName
= widgetName
|| element
.data( 'widget' );
631 widgetDef
= this.registered
[ widgetName
];
636 // Do not wrap already wrapped element.
637 wrapper
= element
.getParent();
638 if ( wrapper
&& wrapper
.type
== CKEDITOR
.NODE_ELEMENT
&& wrapper
.data( 'cke-widget-wrapper' ) )
641 // If attribute isn't already set (e.g. for pasted widget), set it.
642 if ( !element
.hasAttribute( 'data-cke-widget-keep-attr' ) )
643 element
.data( 'cke-widget-keep-attr', element
.data( 'widget' ) ? 1 : 0 );
645 element
.data( 'widget', widgetName
);
647 isInline
= isWidgetInline( widgetDef
, element
.getName() );
649 wrapper
= new CKEDITOR
.dom
.element( isInline
? 'span' : 'div' );
650 wrapper
.setAttributes( getWrapperAttributes( isInline
, widgetName
) );
652 wrapper
.data( 'cke-display-name', widgetDef
.pathName
? widgetDef
.pathName : element
.getName() );
654 // Replace element unless it is a detached one.
655 if ( element
.getParent( true ) )
656 wrapper
.replace( element
);
657 element
.appendTo( wrapper
);
659 else if ( element
instanceof CKEDITOR
.htmlParser
.element
) {
660 widgetName
= widgetName
|| element
.attributes
[ 'data-widget' ];
661 widgetDef
= this.registered
[ widgetName
];
666 wrapper
= element
.parent
;
667 if ( wrapper
&& wrapper
.type
== CKEDITOR
.NODE_ELEMENT
&& wrapper
.attributes
[ 'data-cke-widget-wrapper' ] )
670 // If attribute isn't already set (e.g. for pasted widget), set it.
671 if ( !( 'data-cke-widget-keep-attr' in element
.attributes
) )
672 element
.attributes
[ 'data-cke-widget-keep-attr' ] = element
.attributes
[ 'data-widget' ] ? 1 : 0;
674 element
.attributes
[ 'data-widget' ] = widgetName
;
676 isInline
= isWidgetInline( widgetDef
, element
.name
);
678 wrapper
= new CKEDITOR
.htmlParser
.element( isInline
? 'span' : 'div', getWrapperAttributes( isInline
, widgetName
) );
679 wrapper
.attributes
[ 'data-cke-display-name' ] = widgetDef
.pathName
? widgetDef
.pathName : element
.name
;
681 var parent
= element
.parent
,
684 // Don't detach already detached element.
686 index
= element
.getIndex();
690 wrapper
.add( element
);
692 // Insert wrapper fixing DOM (splitting parents if wrapper is not allowed inside them).
693 parent
&& insertElement( parent
, index
, wrapper
);
700 _tests_createEditableFilter: createEditableFilter
703 CKEDITOR
.event
.implementOn( Repository
.prototype );
706 * An event fired when a widget instance is created, but before it is fully initialized.
708 * @event instanceCreated
709 * @param {CKEDITOR.plugins.widget} data The widget instance.
713 * An event fired when a widget instance was destroyed.
715 * See also {@link CKEDITOR.plugins.widget#event-destroy}.
717 * @event instanceDestroyed
718 * @param {CKEDITOR.plugins.widget} data The widget instance.
722 * An event fired to trigger the selection check.
724 * See the {@link #method-checkSelection} method.
726 * @event checkSelection
730 * An event fired by the the {@link #method-checkWidgets} method.
732 * It can be canceled in order to stop the {@link #method-checkWidgets}
733 * method execution or the event listener can modify the method's options.
735 * @event checkWidgets
737 * @param {Boolean} [data.initOnlyNew] Initialize widgets only on newly wrapped
738 * widget elements (those which still have the `cke_widget_new` class). When this option is
739 * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
740 * will not be reinitialized. This makes the check faster.
741 * @param {Boolean} [data.focusInited] If only one widget is initialized by
742 * the method, it will be focused.
747 * An instance of a widget. Together with {@link CKEDITOR.plugins.widget.repository} these
748 * two classes constitute the core of the Widget System.
750 * Note that neither the repository nor the widget instances can be created by using their constructors.
751 * A repository instance is automatically set up by the Widget plugin and is accessible under
752 * {@link CKEDITOR.editor#widgets}, while widget instances are created and destroyed by the repository.
754 * To create a widget, first you need to {@link CKEDITOR.plugins.widget.repository#add register} its
755 * {@link CKEDITOR.plugins.widget.definition definition}:
757 * editor.widgets.add( 'simplebox', {
758 * upcast: function( element ) {
759 * // Defines which elements will become widgets.
760 * if ( element.hasClass( 'simplebox' ) )
768 * Once the widget definition is registered, widgets will be automatically
769 * created when loading data:
771 * editor.setData( '<div class="simplebox">foo</div>', function() {
772 * console.log( editor.widgets.instances ); // -> An object containing one instance.
775 * It is also possible to create instances during runtime by using a command
776 * (if a {@link CKEDITOR.plugins.widget.definition#template} property was defined):
778 * // You can execute an automatically defined command to
779 * // insert a new simplebox widget or edit the one currently focused.
780 * editor.execCommand( 'simplebox' );
782 * Note: Since CKEditor 4.5 widget's `startupData` can be passed as the command argument:
784 * editor.execCommand( 'simplebox', {
790 * A widget can also be created in a completely custom way:
792 * var element = editor.document.createElement( 'div' );
793 * editor.insertElement( element );
794 * var widget = editor.widgets.initOn( element, 'simplebox' );
797 * @class CKEDITOR.plugins.widget
798 * @mixins CKEDITOR.event
799 * @extends CKEDITOR.plugins.widget.definition
800 * @constructor Creates an instance of the widget class. Do not use it directly, but instead initialize widgets
801 * by using the {@link CKEDITOR.plugins.widget.repository#initOn} method or by the upcasting system.
802 * @param {CKEDITOR.plugins.widget.repository} widgetsRepo
803 * @param {Number} id Unique ID of this widget instance.
804 * @param {CKEDITOR.dom.element} element The widget element.
805 * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget's registered definition.
806 * @param [startupData] Initial widget data. This data object will overwrite the default data and
807 * the data loaded from the DOM.
809 function Widget( widgetsRepo
, id
, element
, widgetDef
, startupData
) {
810 var editor
= widgetsRepo
.editor
;
812 // Extend this widget with widgetDef-specific methods and properties.
813 CKEDITOR
.tools
.extend( this, widgetDef
, {
815 * The editor instance.
818 * @property {CKEDITOR.editor}
823 * This widget's unique (per editor instance) ID.
831 * Whether this widget is an inline widget (based on an inline element unless
832 * forced otherwise by {@link CKEDITOR.plugins.widget.definition#inline}).
834 * **Note:** This option does not allow to turn a block element into an inline widget.
835 * However, it makes it possible to turn an inline element into a block widget or to
836 * force a correct type in case when automatic recognition fails.
839 * @property {Boolean}
841 inline: element
.getParent().getName() == 'span',
844 * The widget element — the element on which the widget was initialized.
847 * @property {CKEDITOR.dom.element} element
852 * Widget's data object.
854 * The data can only be set by using the {@link #setData} method.
855 * Changes made to the data fire the {@link #event-data} event.
859 data: CKEDITOR
.tools
.extend( {}, typeof widgetDef
.defaults
== 'function' ? widgetDef
.defaults() : widgetDef
.defaults
),
862 * Indicates if a widget is data-ready. Set to `true` when data from all sources
863 * ({@link CKEDITOR.plugins.widget.definition#defaults}, set in the
864 * {@link #init} method, loaded from the widget's element and startup data coming from the constructor)
865 * are finally loaded. This is immediately followed by the first {@link #event-data}.
872 * Whether a widget instance was initialized. This means that:
874 * * An instance was created,
875 * * Its properties were set,
876 * * The `init` method was executed.
878 * **Note**: The first {@link #event-data} event could not be fired yet which
879 * means that the widget's DOM has not been set up yet. Wait for the {@link #event-ready}
880 * event to be notified when a widget is fully initialized and ready.
882 * **Note**: Use the {@link #isInited} method to check whether a widget is initialized and
883 * has not been destroyed.
890 * Whether a widget instance is ready. This means that the widget is {@link #inited} and
891 * that its DOM was finally set up.
893 * **Note:** Use the {@link #isReady} method to check whether a widget is ready and
894 * has not been destroyed.
900 // Revert what widgetDef could override (automatic #edit listener).
901 edit: Widget
.prototype.edit
,
904 * The nested editable element which is currently focused.
907 * @property {CKEDITOR.plugins.widget.nestedEditable}
909 focusedEditable: null,
912 * The widget definition from which this instance was created.
915 * @property {CKEDITOR.plugins.widget.definition} definition
917 definition: widgetDef
,
920 * Link to the widget repository which created this instance.
923 * @property {CKEDITOR.plugins.widget.repository} repository
925 repository: widgetsRepo
,
927 draggable: widgetDef
.draggable
!== false,
929 // WAAARNING: Overwrite widgetDef's priv object, because otherwise violent unicorn's gonna visit you.
931 downcastFn: ( widgetDef
.downcast
&& typeof widgetDef
.downcast
== 'string' ) ?
932 widgetDef
.downcasts
[ widgetDef
.downcast
] : widgetDef
.downcast
937 * An object of widget component elements.
939 * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts},
940 * one `partName => element` pair is added to this object during the widget initialization.
943 * @property {Object} parts
947 * The template which will be used to create a new widget element (when the widget's command is executed).
948 * It will be populated with {@link #defaults default values}.
951 * @property {CKEDITOR.template} template
955 * The widget wrapper — a non-editable `div` or `span` element (depending on {@link #inline})
956 * which is a parent of the {@link #element} and widget compontents like the drag handler and the {@link #mask}.
957 * It is the outermost widget element.
960 * @property {CKEDITOR.dom.element} wrapper
963 widgetsRepo
.fire( 'instanceCreated', this );
965 setupWidget( this, widgetDef
);
967 this.init
&& this.init();
969 // Finally mark widget as inited.
972 setupWidgetData( this, startupData
);
974 // If at some point (e.g. in #data listener) widget hasn't been destroyed
975 // and widget is already attached to document then fire #ready.
976 if ( this.isInited() && editor
.editable().contains( this.wrapper
) ) {
978 this.fire( 'ready' );
984 * Adds a class to the widget element. This method is used by
985 * the {@link #applyStyle} method and should be overridden by widgets
986 * which should handle classes differently (e.g. add them to other elements).
988 * Since 4.6.0 this method also adds a corresponding class prefixed with {@link #WRAPPER_CLASS_PREFIX}
989 * to the widget wrapper element.
991 * **Note**: This method should not be used directly. Use the {@link #setData} method to
992 * set the `classes` property. Read more in the {@link #setData} documentation.
994 * See also: {@link #removeClass}, {@link #hasClass}, {@link #getClasses}.
997 * @param {String} className The class name to be added.
999 addClass: function( className
) {
1000 this.element
.addClass( className
);
1001 this.wrapper
.addClass( Widget
.WRAPPER_CLASS_PREFIX
+ className
);
1005 * Applies the specified style to the widget. It is highly recommended to use the
1006 * {@link CKEDITOR.editor#applyStyle} or {@link CKEDITOR.style#apply} methods instead of
1007 * using this method directly, because unlike editor's and style's methods, this one
1008 * does not perform any checks.
1010 * By default this method handles only classes defined in the style. It clones existing
1011 * classes which are stored in the {@link #property-data widget data}'s `classes` property,
1012 * adds new classes, and calls the {@link #setData} method if at least one new class was added.
1013 * Then, using the {@link #event-data} event listener widget applies modifications passing
1014 * new classes to the {@link #addClass} method.
1016 * If you need to handle classes differently than in the default way, you can override the
1017 * {@link #addClass} and related methods. You can also handle other style properties than `classes`
1018 * by overriding this method.
1020 * See also: {@link #checkStyleActive}, {@link #removeStyle}.
1023 * @param {CKEDITOR.style} style The custom widget style to be applied.
1025 applyStyle: function( style
) {
1026 applyRemoveStyle( this, style
, 1 );
1030 * Checks if the specified style is applied to this widget. It is highly recommended to use the
1031 * {@link CKEDITOR.style#checkActive} method instead of using this method directly,
1032 * because unlike style's method, this one does not perform any checks.
1034 * By default this method handles only classes defined in the style and passes
1035 * them to the {@link #hasClass} method. You can override these methods to handle classes
1036 * differently or to handle more of the style properties.
1038 * See also: {@link #applyStyle}, {@link #removeStyle}.
1041 * @param {CKEDITOR.style} style The custom widget style to be checked.
1042 * @returns {Boolean} Whether the style is applied to this widget.
1044 checkStyleActive: function( style
) {
1045 var classes
= getStyleClasses( style
),
1051 while ( ( cl
= classes
.pop() ) ) {
1052 if ( !this.hasClass( cl
) )
1059 * Destroys this widget instance.
1061 * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method.
1063 * This method fires the {#event-destroy} event.
1065 * @param {Boolean} [offline] Whether a widget is offline (detached from the DOM tree) —
1066 * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
1068 destroy: function( offline
) {
1069 this.fire( 'destroy' );
1071 if ( this.editables
) {
1072 for ( var name
in this.editables
)
1073 this.destroyEditable( name
, offline
);
1077 if ( this.element
.data( 'cke-widget-keep-attr' ) == '0' )
1078 this.element
.removeAttribute( 'data-widget' );
1079 this.element
.removeAttributes( [ 'data-cke-widget-data', 'data-cke-widget-keep-attr' ] );
1080 this.element
.removeClass( 'cke_widget_element' );
1081 this.element
.replace( this.wrapper
);
1084 this.wrapper
= null;
1088 * Destroys a nested editable and all nested widgets.
1090 * @param {String} editableName Nested editable name.
1091 * @param {Boolean} [offline] See {@link #method-destroy} method.
1093 destroyEditable: function( editableName
, offline
) {
1094 var editable
= this.editables
[ editableName
];
1096 editable
.removeListener( 'focus', onEditableFocus
);
1097 editable
.removeListener( 'blur', onEditableBlur
);
1098 this.editor
.focusManager
.remove( editable
);
1101 this.repository
.destroyAll( false, editable
);
1102 editable
.removeClass( 'cke_widget_editable' );
1103 editable
.removeClass( 'cke_widget_editable_focused' );
1104 editable
.removeAttributes( [ 'contenteditable', 'data-cke-widget-editable', 'data-cke-enter-mode' ] );
1107 delete this.editables
[ editableName
];
1111 * Starts widget editing.
1113 * This method fires the {@link CKEDITOR.plugins.widget#event-edit} event
1114 * which may be canceled in order to prevent it from opening a dialog window.
1116 * The dialog window name is obtained from the event's data `dialog` property or
1117 * from {@link CKEDITOR.plugins.widget.definition#dialog}.
1119 * @returns {Boolean} Returns `true` if a dialog window was opened.
1122 var evtData
= { dialog: this.dialog
},
1125 // Edit event was blocked or there's no dialog to be automatically opened.
1126 if ( this.fire( 'edit', evtData
) === false || !evtData
.dialog
)
1129 this.editor
.openDialog( evtData
.dialog
, function( dialog
) {
1133 // Allow to add a custom dialog handler.
1134 if ( that
.fire( 'dialog', dialog
) === false )
1137 showListener
= dialog
.on( 'show', function() {
1138 dialog
.setupContent( that
);
1141 okListener
= dialog
.on( 'ok', function() {
1142 // Commit dialog's fields, but prevent from
1143 // firing data event for every field. Fire only one,
1144 // bulk event at the end.
1146 dataListener
= that
.on( 'data', function( evt
) {
1151 // Create snapshot preceeding snapshot with changed widget...
1152 // TODO it should not be required, but it is and I found similar
1153 // code in dialog#ok listener in dialog/plugin.js.
1154 that
.editor
.fire( 'saveSnapshot' );
1155 dialog
.commitContent( that
);
1157 dataListener
.removeListener();
1158 if ( dataChanged
) {
1159 that
.fire( 'data', that
.data
);
1160 that
.editor
.fire( 'saveSnapshot' );
1164 dialog
.once( 'hide', function() {
1165 showListener
.removeListener();
1166 okListener
.removeListener();
1174 * Returns widget element classes parsed to an object. This method
1175 * is used to populate the `classes` property of widget's {@link #property-data}.
1177 * This method reuses {@link CKEDITOR.plugins.widget.repository#parseElementClasses}.
1178 * It should be overriden if a widget should handle classes differently (e.g. on other elements).
1180 * See also: {@link #removeClass}, {@link #addClass}, {@link #hasClass}.
1185 getClasses: function() {
1186 return this.repository
.parseElementClasses( this.element
.getAttribute( 'class' ) );
1190 * Checks if the widget element has specified class. This method is used by
1191 * the {@link #checkStyleActive} method and should be overriden by widgets
1192 * which should handle classes differently (e.g. on other elements).
1194 * See also: {@link #removeClass}, {@link #addClass}, {@link #getClasses}.
1197 * @param {String} className The class to be checked.
1198 * @param {Boolean} Whether a widget has specified class.
1200 hasClass: function( className
) {
1201 return this.element
.hasClass( className
);
1205 * Initializes a nested editable.
1207 * **Note**: Only elements from {@link CKEDITOR.dtd#$editable} may become editables.
1209 * @param {String} editableName The nested editable name.
1210 * @param {CKEDITOR.plugins.widget.nestedEditable.definition} definition The definition of the nested editable.
1211 * @returns {Boolean} Whether an editable was successfully initialized.
1213 initEditable: function( editableName
, definition
) {
1214 // Don't fetch just first element which matched selector but look for a correct one. (#13334)
1215 var editable
= this._findOneNotNested( definition
.selector
);
1217 if ( editable
&& editable
.is( CKEDITOR
.dtd
.$editable
) ) {
1218 editable
= new NestedEditable( this.editor
, editable
, {
1219 filter: createEditableFilter
.call( this.repository
, this.name
, editableName
, definition
)
1221 this.editables
[ editableName
] = editable
;
1223 editable
.setAttributes( {
1224 contenteditable: 'true',
1225 'data-cke-widget-editable': editableName
,
1226 'data-cke-enter-mode': editable
.enterMode
1229 if ( editable
.filter
)
1230 editable
.data( 'cke-filter', editable
.filter
.id
);
1232 editable
.addClass( 'cke_widget_editable' );
1233 // This class may be left when d&ding widget which
1234 // had focused editable. Clean this class here, not in
1235 // cleanUpWidgetElement for performance and code size reasons.
1236 editable
.removeClass( 'cke_widget_editable_focused' );
1238 if ( definition
.pathName
)
1239 editable
.data( 'cke-display-name', definition
.pathName
);
1241 this.editor
.focusManager
.add( editable
);
1242 editable
.on( 'focus', onEditableFocus
, this );
1243 CKEDITOR
.env
.ie
&& editable
.on( 'blur', onEditableBlur
, this );
1245 // Finally, process editable's data. This data wasn't processed when loading
1246 // editor's data, becuase they need to be processed separately, with its own filters and settings.
1247 editable
._
.initialSetData
= true;
1248 editable
.setData( editable
.getHtml() );
1257 * Looks inside wrapper element to find a node that
1258 * matches given selector and is not nested in other widget. (#13334)
1262 * @param {String} selector Selector to match.
1263 * @returns {CKEDITOR.dom.element} Matched element or `null` if a node has not been found.
1265 _findOneNotNested: function( selector
) {
1266 var matchedElements
= this.wrapper
.find( selector
),
1270 for ( var i
= 0; i
< matchedElements
.count(); i
++ ) {
1271 match
= matchedElements
.getItem( i
);
1272 closestWrapper
= match
.getAscendant( Widget
.isDomWidgetWrapper
);
1274 // The closest ascendant-wrapper of this match defines to which widget
1275 // this match belongs. If the ascendant is this widget's wrapper
1276 // it means that the match is not nested in other widget.
1277 if ( this.wrapper
.equals( closestWrapper
) ) {
1286 * Checks if a widget has already been initialized and has not been destroyed yet.
1288 * See {@link #inited} for more details.
1290 * @returns {Boolean}
1292 isInited: function() {
1293 return !!( this.wrapper
&& this.inited
);
1297 * Checks if a widget is ready and has not been destroyed yet.
1299 * See {@link #property-ready} for more details.
1301 * @returns {Boolean}
1303 isReady: function() {
1304 return this.isInited() && this.ready
;
1308 * Focuses a widget by selecting it.
1311 var sel
= this.editor
.getSelection();
1313 // Fake the selection before focusing editor, to avoid unpreventable viewports scrolling
1314 // on Webkit/Blink/IE which is done because there's no selection or selection was somewhere else than widget.
1316 var isDirty
= this.editor
.checkDirty();
1318 sel
.fake( this.wrapper
);
1320 !isDirty
&& this.editor
.resetDirty();
1323 // Always focus editor (not only when focusManger.hasFocus is false) (because of #10483).
1324 this.editor
.focus();
1328 * Removes a class from the widget element. This method is used by
1329 * the {@link #removeStyle} method and should be overriden by widgets
1330 * which should handle classes differently (e.g. on other elements).
1332 * **Note**: This method should not be used directly. Use the {@link #setData} method to
1333 * set the `classes` property. Read more in the {@link #setData} documentation.
1335 * See also: {@link #hasClass}, {@link #addClass}.
1338 * @param {String} className The class to be removed.
1340 removeClass: function( className
) {
1341 this.element
.removeClass( className
);
1342 this.wrapper
.removeClass( Widget
.WRAPPER_CLASS_PREFIX
+ className
);
1346 * Removes the specified style from the widget. It is highly recommended to use the
1347 * {@link CKEDITOR.editor#removeStyle} or {@link CKEDITOR.style#remove} methods instead of
1348 * using this method directly, because unlike editor's and style's methods, this one
1349 * does not perform any checks.
1351 * Read more about how applying/removing styles works in the {@link #applyStyle} method documentation.
1353 * See also {@link #checkStyleActive}, {@link #applyStyle}, {@link #getClasses}.
1356 * @param {CKEDITOR.style} style The custom widget style to be removed.
1358 removeStyle: function( style
) {
1359 applyRemoveStyle( this, style
, 0 );
1363 * Sets widget value(s) in the {@link #property-data} object.
1364 * If the given value(s) modifies current ones, the {@link #event-data} event is fired.
1366 * this.setData( 'align', 'left' );
1367 * this.data.align; // -> 'left'
1369 * this.setData( { align: 'right', opened: false } );
1370 * this.data.align; // -> 'right'
1371 * this.data.opened; // -> false
1373 * Set values are stored in {@link #element}'s attribute (`data-cke-widget-data`),
1374 * in a JSON string, therefore {@link #property-data} should contain
1375 * only serializable data.
1377 * **Note:** A special data property, `classes`, exists. It contains an object with
1378 * classes which were returned by the {@link #getClasses} method during the widget initialization.
1379 * This property is then used by the {@link #applyStyle} and {@link #removeStyle} methods.
1380 * When it is changed (the reference to object must be changed!), the widget updates its classes by
1381 * using the {@link #addClass} and {@link #removeClass} methods.
1383 * // Adding a new class.
1384 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1385 * classes.newClass = 1;
1386 * widget.setData( 'classes', classes );
1388 * // Removing a class.
1389 * var classes = CKEDITOR.tools.clone( widget.data.classes );
1390 * delete classes.newClass;
1391 * widget.setData( 'classes', classes );
1393 * @param {String/Object} keyOrData
1394 * @param {Object} value
1397 setData: function( key
, value
) {
1398 var data
= this.data
,
1401 if ( typeof key
== 'string' ) {
1402 if ( data
[ key
] !== value
) {
1403 data
[ key
] = value
;
1410 for ( key
in newData
) {
1411 if ( data
[ key
] !== newData
[ key
] ) {
1413 data
[ key
] = newData
[ key
];
1418 // Block firing data event and overwriting data element before setupWidgetData is executed.
1419 if ( modified
&& this.dataReady
) {
1420 writeDataToElement( this );
1421 this.fire( 'data', data
);
1428 * Changes the widget's focus state. This method is executed automatically after
1429 * a widget was focused by the {@link #method-focus} method or the selection was moved
1430 * out of the widget.
1432 * This is a low-level method which is not integrated with e.g. the undo manager.
1433 * Use the {@link #method-focus} method instead.
1435 * @param {Boolean} selected Whether to select or deselect this widget.
1438 setFocused: function( focused
) {
1439 this.wrapper
[ focused
? 'addClass' : 'removeClass' ]( 'cke_widget_focused' );
1440 this.fire( focused
? 'focus' : 'blur' );
1445 * Changes the widget's select state. This method is executed automatically after
1446 * a widget was selected by the {@link #method-focus} method or the selection
1447 * was moved out of the widget.
1449 * This is a low-level method which is not integrated with e.g. the undo manager.
1450 * Use the {@link #method-focus} method instead or simply change the selection.
1452 * @param {Boolean} selected Whether to select or deselect this widget.
1455 setSelected: function( selected
) {
1456 this.wrapper
[ selected
? 'addClass' : 'removeClass' ]( 'cke_widget_selected' );
1457 this.fire( selected
? 'select' : 'deselect' );
1462 * Repositions drag handler according to the widget's element position. Should be called from events, like mouseover.
1464 updateDragHandlerPosition: function() {
1465 var editor
= this.editor
,
1466 domElement
= this.element
.$,
1467 oldPos
= this._
.dragHandlerOffset
,
1469 x: domElement
.offsetLeft
,
1470 y: domElement
.offsetTop
- DRAG_HANDLER_SIZE
1473 if ( oldPos
&& newPos
.x
== oldPos
.x
&& newPos
.y
== oldPos
.y
)
1476 // We need to make sure that dirty state is not changed (#11487).
1477 var initialDirty
= editor
.checkDirty();
1479 editor
.fire( 'lockSnapshot' );
1480 this.dragHandlerContainer
.setStyles( {
1481 top: newPos
.y
+ 'px',
1482 left: newPos
.x
+ 'px',
1485 editor
.fire( 'unlockSnapshot' );
1486 !initialDirty
&& editor
.resetDirty();
1488 this._
.dragHandlerOffset
= newPos
;
1492 CKEDITOR
.event
.implementOn( Widget
.prototype );
1495 * Gets the {@link #isDomNestedEditable nested editable}
1496 * (returned as a {@link CKEDITOR.dom.element}, not as a {@link CKEDITOR.plugins.widget.nestedEditable})
1497 * closest to the `node` or the `node` if it is a nested editable itself.
1501 * @param {CKEDITOR.dom.element} guard Stop ancestor search on this node (usually editor's editable).
1502 * @param {CKEDITOR.dom.node} node Start the search from this node.
1503 * @returns {CKEDITOR.dom.element/null} Element or `null` if not found.
1505 Widget
.getNestedEditable = function( guard
, node
) {
1506 if ( !node
|| node
.equals( guard
) )
1509 if ( Widget
.isDomNestedEditable( node
) )
1512 return Widget
.getNestedEditable( guard
, node
.getParent() );
1516 * Checks whether the `node` is a widget's drag handle element.
1520 * @param {CKEDITOR.dom.node} node
1521 * @returns {Boolean}
1523 Widget
.isDomDragHandler = function( node
) {
1524 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasAttribute( 'data-cke-widget-drag-handler' );
1528 * Checks whether the `node` is a container of the widget's drag handle element.
1532 * @param {CKEDITOR.dom.node} node
1533 * @returns {Boolean}
1535 Widget
.isDomDragHandlerContainer = function( node
) {
1536 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasClass( 'cke_widget_drag_handler_container' );
1540 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#editables nested editable}.
1541 * Note that this function only checks whether it is the right element, not whether
1542 * the passed `node` is an instance of {@link CKEDITOR.plugins.widget.nestedEditable}.
1546 * @param {CKEDITOR.dom.node} node
1547 * @returns {Boolean}
1549 Widget
.isDomNestedEditable = function( node
) {
1550 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasAttribute( 'data-cke-widget-editable' );
1554 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1558 * @param {CKEDITOR.dom.node} node
1559 * @returns {Boolean}
1561 Widget
.isDomWidgetElement = function( node
) {
1562 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasAttribute( 'data-widget' );
1566 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1570 * @param {CKEDITOR.dom.element} node
1571 * @returns {Boolean}
1573 Widget
.isDomWidgetWrapper = function( node
) {
1574 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasAttribute( 'data-cke-widget-wrapper' );
1578 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1582 * @param {CKEDITOR.htmlParser.node} node
1583 * @returns {Boolean}
1585 Widget
.isParserWidgetElement = function( node
) {
1586 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& !!node
.attributes
[ 'data-widget' ];
1590 * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1594 * @param {CKEDITOR.htmlParser.element} node
1595 * @returns {Boolean}
1597 Widget
.isParserWidgetWrapper = function( node
) {
1598 return node
.type
== CKEDITOR
.NODE_ELEMENT
&& !!node
.attributes
[ 'data-cke-widget-wrapper' ];
1602 * Prefix added to wrapper classes. Each class added to the widget element by the {@link #addClass}
1603 * method will also be added to the wrapper prefixed with it.
1608 * @property {String} [='cke_widget_wrapper_']
1610 Widget
.WRAPPER_CLASS_PREFIX
= 'cke_widget_wrapper_';
1613 * An event fired when a widget is ready (fully initialized). This event is fired after:
1615 * * {@link #init} is called,
1616 * * The first {@link #event-data} event is fired,
1617 * * A widget is attached to the document.
1619 * Therefore, in case of widget creation with a command which opens a dialog window, this event
1620 * will be delayed after the dialog window is closed and the widget is finally inserted into the document.
1622 * **Note**: If your widget does not use automatic dialog window binding (i.e. you open the dialog window manually)
1623 * or another situation in which the widget wrapper is not attached to document at the time when it is
1624 * initialized occurs, you need to take care of firing {@link #event-ready} yourself.
1626 * See also {@link #property-ready} and {@link #property-inited} properties, and
1627 * {@link #isReady} and {@link #isInited} methods.
1633 * An event fired when a widget is about to be destroyed, but before it is
1640 * An event fired when a widget is focused.
1642 * Widget can be focused by executing {@link #method-focus}.
1648 * An event fired when a widget is blurred.
1654 * An event fired when a widget is selected.
1660 * An event fired when a widget is deselected.
1666 * An event fired by the {@link #method-edit} method. It can be canceled
1667 * in order to stop the default action (opening a dialog window and/or
1668 * {@link CKEDITOR.plugins.widget.repository#finalizeCreation finalizing widget creation}).
1672 * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog}
1673 * and can be changed or set by the listener.
1677 * An event fired when a dialog window for widget editing is opened.
1678 * This event can be canceled in order to handle the editing dialog in a custom manner.
1681 * @param {CKEDITOR.dialog} data The opened dialog window instance.
1685 * An event fired when a key is pressed on a focused widget.
1686 * This event is forwarded from the {@link CKEDITOR.editor#key} event and
1687 * has the ability to block editor keystrokes if it is canceled.
1691 * @param {Number} data.keyCode A number representing the key code (or combination).
1695 * An event fired when a widget is double clicked.
1697 * **Note:** If a default editing action is executed on double click (i.e. a widget has a
1698 * {@link CKEDITOR.plugins.widget.definition#dialog dialog} defined and the {@link #event-doubleclick} event was not
1699 * canceled), this event will be automatically canceled, so a listener added with the default priority (10)
1700 * will not be executed. Use a listener with low priority (e.g. 5) to be sure that it will be executed.
1702 * widget.on( 'doubleclick', function( evt ) {
1703 * console.log( 'widget#doubleclick' );
1704 * }, null, null, 5 );
1706 * If your widget handles double click in a special way (so the default editing action is not executed),
1707 * make sure you cancel this event, because otherwise it will be propagated to {@link CKEDITOR.editor#doubleclick}
1708 * and another feature may step in (e.g. a Link dialog window may be opened if your widget was inside a link).
1710 * @event doubleclick
1712 * @param {CKEDITOR.dom.element} data.element The double-clicked element.
1716 * An event fired when the context menu is opened for a widget.
1718 * @event contextMenu
1719 * @param data The object containing context menu options to be added
1720 * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}.
1724 * An event fired when the widget data changed. See the {@link #setData} method and the {@link #property-data} property.
1732 * The wrapper class for editable elements inside widgets.
1734 * Do not use directly. Use {@link CKEDITOR.plugins.widget.definition#editables} or
1735 * {@link CKEDITOR.plugins.widget#initEditable}.
1737 * @class CKEDITOR.plugins.widget.nestedEditable
1738 * @extends CKEDITOR.dom.element
1740 * @param {CKEDITOR.editor} editor
1741 * @param {CKEDITOR.dom.element} element
1743 * @param {CKEDITOR.filter} [config.filter]
1745 function NestedEditable( editor
, element
, config
) {
1746 // Call the base constructor.
1747 CKEDITOR
.dom
.element
.call( this, element
.$ );
1748 this.editor
= editor
;
1750 var filter
= this.filter
= config
.filter
;
1752 // If blockless editable - always use BR mode.
1753 if ( !CKEDITOR
.dtd
[ this.getName() ].p
)
1754 this.enterMode
= this.shiftEnterMode
= CKEDITOR
.ENTER_BR
;
1756 this.enterMode
= filter
? filter
.getAllowedEnterMode( editor
.enterMode
) : editor
.enterMode
;
1757 this.shiftEnterMode
= filter
? filter
.getAllowedEnterMode( editor
.shiftEnterMode
, true ) : editor
.shiftEnterMode
;
1761 NestedEditable
.prototype = CKEDITOR
.tools
.extend( CKEDITOR
.tools
.prototypedCopy( CKEDITOR
.dom
.element
.prototype ), {
1763 * Sets the editable data. The data will be passed through the {@link CKEDITOR.editor#dataProcessor}
1764 * and the {@link CKEDITOR.editor#filter}. This ensures that the data was filtered and prepared to be
1765 * edited like the {@link CKEDITOR.editor#method-setData editor data}.
1767 * Before content is changed, all nested widgets are destroyed. Afterwards, after new content is loaded,
1768 * all nested widgets are initialized.
1770 * @param {String} data
1772 setData: function( data
) {
1773 // For performance reasons don't call destroyAll when initializing a nested editable,
1774 // because there are no widgets inside.
1775 if ( !this._
.initialSetData
) {
1776 // Destroy all nested widgets before setting data.
1777 this.editor
.widgets
.destroyAll( false, this );
1779 this._
.initialSetData
= false;
1781 data
= this.editor
.dataProcessor
.toHtml( data
, {
1782 context: this.getName(),
1783 filter: this.filter
,
1784 enterMode: this.enterMode
1786 this.setHtml( data
);
1788 this.editor
.widgets
.initOnAll( this );
1792 * Gets the editable data. Like {@link #setData}, this method will process and filter the data.
1796 getData: function() {
1797 return this.editor
.dataProcessor
.toDataFormat( this.getHtml(), {
1798 context: this.getName(),
1799 filter: this.filter
,
1800 enterMode: this.enterMode
1806 * The editor instance.
1809 * @property {CKEDITOR.editor} editor
1813 * The filter instance if allowed content rules were defined.
1816 * @property {CKEDITOR.filter} filter
1820 * The enter mode active in this editable.
1821 * It is determined from editable's name (whether it is a blockless editable),
1822 * its allowed content rules (if defined) and the default editor's mode.
1825 * @property {Number} enterMode
1829 * The shift enter move active in this editable.
1832 * @property {Number} shiftEnterMode
1837 // REPOSITORY helpers -----------------------------------------------------
1840 function addWidgetButtons( editor
) {
1841 var widgets
= editor
.widgets
.registered
,
1846 for ( widgetName
in widgets
) {
1847 widget
= widgets
[ widgetName
];
1849 // Create button if defined.
1850 widgetButton
= widget
.button
;
1851 if ( widgetButton
&& editor
.ui
.addButton
) {
1852 editor
.ui
.addButton( CKEDITOR
.tools
.capitalize( widget
.name
, true ), {
1853 label: widgetButton
,
1854 command: widget
.name
,
1855 toolbar: 'insert,10'
1861 // Create a command creating and editing widget.
1864 // @param {CKEDITOR.plugins.widget.definition} widgetDef
1865 function addWidgetCommand( editor
, widgetDef
) {
1866 editor
.addCommand( widgetDef
.name
, {
1867 exec: function( editor
, commandData
) {
1868 var focused
= editor
.widgets
.focused
;
1869 // If a widget of the same type is focused, start editing.
1870 if ( focused
&& focused
.name
== widgetDef
.name
)
1873 // ... use insert method is was defined.
1874 else if ( widgetDef
.insert
)
1876 // ... or create a brand-new widget from template.
1877 else if ( widgetDef
.template
) {
1878 var defaults
= typeof widgetDef
.defaults
== 'function' ? widgetDef
.defaults() : widgetDef
.defaults
,
1879 element
= CKEDITOR
.dom
.element
.createFromHtml( widgetDef
.template
.output( defaults
) ),
1881 wrapper
= editor
.widgets
.wrapElement( element
, widgetDef
.name
),
1882 temp
= new CKEDITOR
.dom
.documentFragment( wrapper
.getDocument() );
1884 // Append wrapper to a temporary document. This will unify the environment
1885 // in which #data listeners work when creating and editing widget.
1886 temp
.append( wrapper
);
1887 instance
= editor
.widgets
.initOn( element
, widgetDef
, commandData
&& commandData
.startupData
);
1889 // Instance could be destroyed during initialization.
1890 // In this case finalize creation if some new widget
1891 // was left in temporary document fragment.
1897 // Listen on edit to finalize widget insertion.
1899 // * If dialog was set, then insert widget after dialog was successfully saved or destroy this
1900 // temporary instance.
1901 // * If dialog wasn't set and edit wasn't canceled, insert widget.
1902 var editListener
= instance
.once( 'edit', function( evt
) {
1903 if ( evt
.data
.dialog
) {
1904 instance
.once( 'dialog', function( evt
) {
1905 var dialog
= evt
.data
,
1909 // Finalize creation AFTER (20) new data was set.
1910 okListener
= dialog
.once( 'ok', finalizeCreation
, null, null, 20 );
1912 cancelListener
= dialog
.once( 'cancel', function( evt
) {
1913 if ( !( evt
.data
&& evt
.data
.hide
=== false ) ) {
1914 editor
.widgets
.destroy( instance
, true );
1918 dialog
.once( 'hide', function() {
1919 okListener
.removeListener();
1920 cancelListener
.removeListener();
1924 // Dialog hasn't been set, so insert widget now.
1927 }, null, null, 999 );
1931 // Remove listener in case someone canceled it before this
1932 // listener was executed.
1933 editListener
.removeListener();
1936 function finalizeCreation() {
1937 editor
.widgets
.finalizeCreation( temp
);
1941 allowedContent: widgetDef
.allowedContent
,
1942 requiredContent: widgetDef
.requiredContent
,
1943 contentForms: widgetDef
.contentForms
,
1944 contentTransformations: widgetDef
.contentTransformations
1948 function addWidgetProcessors( widgetsRepo
, widgetDef
) {
1949 var upcast
= widgetDef
.upcast
,
1951 priority
= widgetDef
.upcastPriority
|| 10;
1956 // Multiple upcasts defined in string.
1957 if ( typeof upcast
== 'string' ) {
1958 upcasts
= upcast
.split( ',' );
1959 while ( upcasts
.length
) {
1960 addUpcast( widgetDef
.upcasts
[ upcasts
.pop() ], widgetDef
.name
, priority
);
1963 // Single rule which is automatically activated.
1965 addUpcast( upcast
, widgetDef
.name
, priority
);
1968 function addUpcast( upcast
, name
, priority
) {
1969 // Find index of the first higher (in terms of value) priority upcast.
1970 var index
= CKEDITOR
.tools
.getIndex( widgetsRepo
._
.upcasts
, function( element
) {
1971 return element
[ 2 ] > priority
;
1973 // Add at the end if it is the highest priority so far.
1975 index
= widgetsRepo
._
.upcasts
.length
;
1978 widgetsRepo
._
.upcasts
.splice( index
, 0, [ upcast
, name
, priority
] );
1982 function blurWidget( widgetsRepo
, widget
) {
1983 widgetsRepo
.focused
= null;
1985 if ( widget
.isInited() ) {
1986 var isDirty
= widget
.editor
.checkDirty();
1988 // Widget could be destroyed in the meantime - e.g. data could be set.
1989 widgetsRepo
.fire( 'widgetBlurred', { widget: widget
} );
1990 widget
.setFocused( false );
1992 !isDirty
&& widget
.editor
.resetDirty();
1996 function checkWidgets( evt
) {
1997 var options
= evt
.data
;
1999 if ( this.editor
.mode
!= 'wysiwyg' )
2002 var editable
= this.editor
.editable(),
2003 instances
= this.instances
,
2004 newInstances
, i
, count
, wrapper
, notYetInitialized
;
2009 // Remove widgets which have no corresponding elements in DOM.
2010 for ( i
in instances
) {
2011 // #13410 Remove widgets that are ready. This prevents from destroying widgets that are during loading process.
2012 if ( instances
[ i
].isReady() && !editable
.contains( instances
[ i
].wrapper
) )
2013 this.destroy( instances
[ i
], true );
2016 // Init on all (new) if initOnlyNew option was passed.
2017 if ( options
&& options
.initOnlyNew
)
2018 newInstances
= this.initOnAll();
2020 var wrappers
= editable
.find( '.cke_widget_wrapper' );
2023 // Create widgets on existing wrappers if they do not exists.
2024 for ( i
= 0, count
= wrappers
.count(); i
< count
; i
++ ) {
2025 wrapper
= wrappers
.getItem( i
);
2026 notYetInitialized
= !this.getByElement( wrapper
, true );
2029 // * there's no instance for this widget
2030 // * wrapper is not inside some temporary element like copybin (#11088)
2031 // * it was a nested widget's wrapper which has been detached from DOM,
2032 // when nested editable has been initialized (it overwrites its innerHTML
2033 // and initializes nested widgets).
2034 if ( notYetInitialized
&& !findParent( wrapper
, isDomTemp
) && editable
.contains( wrapper
) ) {
2035 // Add cke_widget_new class because otherwise
2036 // widget will not be created on such wrapper.
2037 wrapper
.addClass( 'cke_widget_new' );
2038 newInstances
.push( this.initOn( wrapper
.getFirst( Widget
.isDomWidgetElement
) ) );
2043 // If only single widget was initialized and focusInited was passed, focus it.
2044 if ( options
&& options
.focusInited
&& newInstances
.length
== 1 )
2045 newInstances
[ 0 ].focus();
2048 // Unwraps widget element and clean up element.
2050 // This function is used to clean up pasted widgets.
2051 // It should have similar result to widget#destroy plus
2052 // some additional adjustments, specific for pasting.
2054 // @param {CKEDITOR.htmlParser.element} el
2055 function cleanUpWidgetElement( el
) {
2056 var parent
= el
.parent
;
2057 if ( parent
.type
== CKEDITOR
.NODE_ELEMENT
&& parent
.attributes
[ 'data-cke-widget-wrapper' ] )
2058 parent
.replaceWith( el
);
2061 // Similar to cleanUpWidgetElement, but works on DOM and finds
2062 // widget elements by its own.
2064 // Unlike cleanUpWidgetElement it will wrap element back.
2066 // @param {CKEDITOR.dom.element} container
2067 function cleanUpAllWidgetElements( widgetsRepo
, container
) {
2068 var wrappers
= container
.find( '.cke_widget_wrapper' ),
2071 l
= wrappers
.count();
2073 for ( ; i
< l
; ++i
) {
2074 wrapper
= wrappers
.getItem( i
);
2075 element
= wrapper
.getFirst( Widget
.isDomWidgetElement
);
2076 // If wrapper contains widget element - unwrap it and wrap again.
2077 if ( element
.type
== CKEDITOR
.NODE_ELEMENT
&& element
.data( 'widget' ) ) {
2078 element
.replace( wrapper
);
2079 widgetsRepo
.wrapElement( element
);
2081 // Otherwise - something is wrong... clean this up.
2087 // Creates {@link CKEDITOR.filter} instance for given widget, editable and rules.
2089 // Once filter for widget-editable pair is created it is cached, so the same instance
2090 // will be returned when method is executed again.
2092 // @param {String} widgetName
2093 // @param {String} editableName
2094 // @param {CKEDITOR.plugins.widget.nestedEditableDefinition} editableDefinition The nested editable definition.
2095 // @returns {CKEDITOR.filter} Filter instance or `null` if rules are not defined.
2096 // @context CKEDITOR.plugins.widget.repository
2097 function createEditableFilter( widgetName
, editableName
, editableDefinition
) {
2098 if ( !editableDefinition
.allowedContent
)
2101 var editables
= this._
.filters
[ widgetName
];
2104 this._
.filters
[ widgetName
] = editables
= {};
2106 var filter
= editables
[ editableName
];
2109 editables
[ editableName
] = filter
= new CKEDITOR
.filter( editableDefinition
.allowedContent
);
2114 // Creates an iterator function which when executed on all
2115 // elements in DOM tree will gather elements that should be wrapped
2116 // and initialized as widgets.
2117 function createUpcastIterator( widgetsRepo
) {
2118 var toBeWrapped
= [],
2119 upcasts
= widgetsRepo
._
.upcasts
,
2120 upcastCallbacks
= widgetsRepo
._
.upcastCallbacks
;
2123 toBeWrapped: toBeWrapped
,
2125 iterator: function( element
) {
2126 var upcast
, upcasted
,
2130 upcastCallbacksLength
;
2132 // Wrapper found - find widget element, add it to be
2133 // cleaned up (unwrapped) and wrapped and stop iterating in this branch.
2134 if ( 'data-cke-widget-wrapper' in element
.attributes
) {
2135 element
= element
.getFirst( Widget
.isParserWidgetElement
);
2138 toBeWrapped
.push( [ element
] );
2140 // Do not iterate over descendants.
2143 // Widget element found - add it to be cleaned up (just in case)
2144 // and wrapped and stop iterating in this branch.
2145 else if ( 'data-widget' in element
.attributes
) {
2146 toBeWrapped
.push( [ element
] );
2148 // Do not iterate over descendants.
2151 else if ( ( upcastsLength
= upcasts
.length
) ) {
2152 // Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (#11533).
2153 // Do not iterate over descendants.
2154 if ( element
.attributes
[ 'data-cke-widget-upcasted' ] )
2157 // Check element with upcast callbacks first.
2158 // If any of them return false abort upcasting.
2159 for ( i
= 0, upcastCallbacksLength
= upcastCallbacks
.length
; i
< upcastCallbacksLength
; ++i
) {
2160 if ( upcastCallbacks
[ i
]( element
) === false )
2162 // Return nothing in order to continue iterating over ascendants.
2163 // See http://dev.ckeditor.com/ticket/11186#comment:6
2166 for ( i
= 0; i
< upcastsLength
; ++i
) {
2167 upcast
= upcasts
[ i
];
2170 if ( ( upcasted
= upcast
[ 0 ]( element
, data
) ) ) {
2171 // If upcast function returned element, upcast this one.
2172 // It can be e.g. a new element wrapping the original one.
2173 if ( upcasted
instanceof CKEDITOR
.htmlParser
.element
)
2176 // Set initial data attr with data from upcast method.
2177 element
.attributes
[ 'data-cke-widget-data' ] = encodeURIComponent( JSON
.stringify( data
) );
2178 element
.attributes
[ 'data-cke-widget-upcasted' ] = 1;
2180 toBeWrapped
.push( [ element
, upcast
[ 1 ] ] );
2182 // Do not iterate over descendants.
2191 // Finds a first parent that matches query.
2193 // @param {CKEDITOR.dom.element} element
2194 // @param {Function} query
2195 function findParent( element
, query
) {
2196 var parent
= element
;
2198 while ( ( parent
= parent
.getParent() ) ) {
2199 if ( query( parent
) )
2205 function getWrapperAttributes( inlineWidget
, name
) {
2207 // tabindex="-1" means that it can receive focus by code.
2209 contenteditable: 'false',
2210 'data-cke-widget-wrapper': 1,
2211 'data-cke-filter': 'off',
2212 // Class cke_widget_new marks widgets which haven't been initialized yet.
2213 'class': 'cke_widget_wrapper cke_widget_new cke_widget_' +
2214 ( inlineWidget
? 'inline' : 'block' ) +
2215 ( name
? ' cke_widget_' + name : '' )
2219 // Inserts element at given index.
2220 // It will check DTD and split ancestor elements up to the first
2221 // that can contain this element.
2223 // @param {CKEDITOR.htmlParser.element} parent
2224 // @param {Number} index
2225 // @param {CKEDITOR.htmlParser.element} element
2226 function insertElement( parent
, index
, element
) {
2227 // Do not split doc fragment...
2228 if ( parent
.type
== CKEDITOR
.NODE_ELEMENT
) {
2229 var parentAllows
= CKEDITOR
.dtd
[ parent
.name
];
2230 // Parent element is known (included in DTD) and cannot contain
2232 if ( parentAllows
&& !parentAllows
[ element
.name
] ) {
2233 var parent2
= parent
.split( index
),
2234 parentParent
= parent
.parent
;
2236 // Element will now be inserted at right parent's index.
2237 index
= parent2
.getIndex();
2239 // If left part of split is empty - remove it.
2240 if ( !parent
.children
.length
) {
2245 // If right part of split is empty - remove it.
2246 if ( !parent2
.children
.length
)
2249 // Try inserting as grandpas' children.
2250 return insertElement( parentParent
, index
, element
);
2254 // Finally we can add this element.
2255 parent
.add( element
, index
);
2258 // Checks whether for the given widget definition and element widget should be created in inline or block mode.
2260 // See also: {@link CKEDITOR.plugins.widget.definition#inline} and {@link CKEDITOR.plugins.widget#element}.
2262 // @param {CKEDITOR.plugins.widget.definition} widgetDef The widget definition.
2263 // @param {String} elementName The name of the widget element.
2264 // @returns {Boolean}
2265 function isWidgetInline( widgetDef
, elementName
) {
2266 return typeof widgetDef
.inline
== 'boolean' ? widgetDef
.inline : !!CKEDITOR
.dtd
.$inline
[ elementName
];
2269 // @param {CKEDITOR.dom.element}
2270 // @returns {Boolean}
2271 function isDomTemp( element
) {
2272 return element
.hasAttribute( 'data-cke-temp' );
2275 function onEditableKey( widget
, keyCode
) {
2276 var focusedEditable
= widget
.focusedEditable
,
2280 if ( keyCode
== CKEDITOR
.CTRL
+ 65 ) {
2281 var bogus
= focusedEditable
.getBogus();
2283 range
= widget
.editor
.createRange();
2284 range
.selectNodeContents( focusedEditable
);
2285 // Exclude bogus if exists.
2287 range
.setEndAt( bogus
, CKEDITOR
.POSITION_BEFORE_START
);
2290 // Cancel event - block default.
2293 // DEL or BACKSPACE.
2294 else if ( keyCode
== 8 || keyCode
== 46 ) {
2295 var ranges
= widget
.editor
.getSelection().getRanges();
2297 range
= ranges
[ 0 ];
2299 // Block del or backspace if at editable's boundary.
2300 return !( ranges
.length
== 1 && range
.collapsed
&&
2301 range
.checkBoundaryOfElement( focusedEditable
, CKEDITOR
[ keyCode
== 8 ? 'START' : 'END' ] ) );
2305 function setFocusedEditable( widgetsRepo
, widget
, editableElement
, offline
) {
2306 var editor
= widgetsRepo
.editor
;
2308 editor
.fire( 'lockSnapshot' );
2310 if ( editableElement
) {
2311 var editableName
= editableElement
.data( 'cke-widget-editable' ),
2312 editableInstance
= widget
.editables
[ editableName
];
2314 widgetsRepo
.widgetHoldingFocusedEditable
= widget
;
2315 widget
.focusedEditable
= editableInstance
;
2316 editableElement
.addClass( 'cke_widget_editable_focused' );
2318 if ( editableInstance
.filter
)
2319 editor
.setActiveFilter( editableInstance
.filter
);
2320 editor
.setActiveEnterMode( editableInstance
.enterMode
, editableInstance
.shiftEnterMode
);
2323 widget
.focusedEditable
.removeClass( 'cke_widget_editable_focused' );
2325 widget
.focusedEditable
= null;
2326 widgetsRepo
.widgetHoldingFocusedEditable
= null;
2327 editor
.setActiveFilter( null );
2328 editor
.setActiveEnterMode( null, null );
2331 editor
.fire( 'unlockSnapshot' );
2334 function setupContextMenu( editor
) {
2335 if ( !editor
.contextMenu
)
2338 editor
.contextMenu
.addListener( function( element
) {
2339 var widget
= editor
.widgets
.getByElement( element
, true );
2342 return widget
.fire( 'contextMenu', {} );
2346 // And now we've got two problems - original problem and RegExp.
2348 // * FF tends to copy all blocks up to the copybin container.
2349 // * IE tends to copy only the copybin, without its container.
2350 // * We use spans on IE and blockless editors, but divs in other cases.
2351 var pasteReplaceRegex
= new RegExp(
2353 '(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' +
2354 '(?:<(?:div|span)(?: style="[^"]+")?>)?' +
2355 '<span [^>]*data-cke-copybin-start="1"[^>]*>.?</span>([\\s\\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?</span>' +
2356 '(?:</(?:div|span)>)?' +
2357 '(?:</(?:div|span)>)?' +
2359 // IE8 prefers uppercase when browsers stick to lowercase HTML (#13460).
2363 function pasteReplaceFn( match
, wrapperHtml
) {
2364 // Avoid polluting pasted data with any whitspaces,
2365 // what's going to break check whether only one widget was pasted.
2366 return CKEDITOR
.tools
.trim( wrapperHtml
);
2369 function setupDragAndDrop( widgetsRepo
) {
2370 var editor
= widgetsRepo
.editor
,
2371 lineutils
= CKEDITOR
.plugins
.lineutils
;
2373 // These listeners handle inline and block widgets drag and drop.
2374 // The only thing we need to do to make block widgets custom drag and drop functionality
2375 // is to fire those events with the right properties (like the target which must be the drag handle).
2376 editor
.on( 'dragstart', function( evt
) {
2377 var target
= evt
.data
.target
;
2379 if ( Widget
.isDomDragHandler( target
) ) {
2380 var widget
= widgetsRepo
.getByElement( target
);
2382 evt
.data
.dataTransfer
.setData( 'cke/widget-id', widget
.id
);
2387 // and widget need to be focused on drag start (#12172#comment:10).
2392 editor
.on( 'drop', function( evt
) {
2393 var dataTransfer
= evt
.data
.dataTransfer
,
2394 id
= dataTransfer
.getData( 'cke/widget-id' ),
2395 transferType
= dataTransfer
.getTransferType( editor
),
2396 dragRange
= editor
.createRange(),
2399 // Disable cross-editor drag & drop for widgets - #13599.
2400 if ( id
!== '' && transferType
=== CKEDITOR
.DATA_TRANSFER_CROSS_EDITORS
) {
2405 if ( id
=== '' || transferType
!= CKEDITOR
.DATA_TRANSFER_INTERNAL
) {
2409 sourceWidget
= widgetsRepo
.instances
[ id
];
2410 if ( !sourceWidget
) {
2414 dragRange
.setStartBefore( sourceWidget
.wrapper
);
2415 dragRange
.setEndAfter( sourceWidget
.wrapper
);
2416 evt
.data
.dragRange
= dragRange
;
2418 // [IE8-9] Reset state of the clipboard#fixSplitNodesAfterDrop fix because by setting evt.data.dragRange
2419 // (see above) after drop happened we do not need it. That fix is needed only if dragRange was created
2420 // before drop (before text node was split).
2421 delete CKEDITOR
.plugins
.clipboard
.dragStartContainerChildCount
;
2422 delete CKEDITOR
.plugins
.clipboard
.dragEndContainerChildCount
;
2424 evt
.data
.dataTransfer
.setData( 'text/html', editor
.editable().getHtmlFromRange( dragRange
).getHtml() );
2425 editor
.widgets
.destroy( sourceWidget
, true );
2428 editor
.on( 'contentDom', function() {
2429 var editable
= editor
.editable();
2431 // Register Lineutils's utilities as properties of repo.
2432 CKEDITOR
.tools
.extend( widgetsRepo
, {
2433 finder: new lineutils
.finder( editor
, {
2435 // Element is block but not list item and not in nested editable.
2436 'default': function( el
) {
2437 if ( el
.is( CKEDITOR
.dtd
.$listItem
) )
2440 if ( !el
.is( CKEDITOR
.dtd
.$block
) )
2443 // Allow drop line inside, but never before or after nested editable (#12006).
2444 if ( Widget
.isDomNestedEditable( el
) )
2447 // Do not allow droping inside the widget being dragged (#13397).
2448 if ( widgetsRepo
._
.draggedWidget
.wrapper
.contains( el
) ) {
2452 // If element is nested editable, make sure widget can be dropped there (#12006).
2453 var nestedEditable
= Widget
.getNestedEditable( editable
, el
);
2454 if ( nestedEditable
) {
2455 var draggedWidget
= widgetsRepo
._
.draggedWidget
;
2457 // Don't let the widget to be dropped into its own nested editable.
2458 if ( widgetsRepo
.getByElement( nestedEditable
) == draggedWidget
)
2461 var filter
= CKEDITOR
.filter
.instances
[ nestedEditable
.data( 'cke-filter' ) ],
2462 draggedRequiredContent
= draggedWidget
.requiredContent
;
2464 // There will be no relation if the filter of nested editable does not allow
2465 // requiredContent of dragged widget.
2466 if ( filter
&& draggedRequiredContent
&& !filter
.check( draggedRequiredContent
) )
2470 return CKEDITOR
.LINEUTILS_BEFORE
| CKEDITOR
.LINEUTILS_AFTER
;
2474 locator: new lineutils
.locator( editor
),
2475 liner: new lineutils
.liner( editor
, {
2477 cursor: 'move !important',
2478 'border-top-color': '#666'
2481 'border-left-color': '#666'
2484 'border-right-color': '#666'
2491 // Setup mouse observer which will trigger:
2492 // * widget focus on widget click,
2493 // * widget#doubleclick forwarded from editor#doubleclick.
2494 function setupMouseObserver( widgetsRepo
) {
2495 var editor
= widgetsRepo
.editor
;
2497 editor
.on( 'contentDom', function() {
2498 var editable
= editor
.editable(),
2499 evtRoot
= editable
.isInline() ? editable : editor
.document
,
2501 mouseDownOnDragHandler
;
2503 editable
.attachListener( evtRoot
, 'mousedown', function( evt
) {
2504 var target
= evt
.data
.getTarget();
2506 // #10887 Clicking scrollbar in IE8 will invoke event with empty target object.
2510 widget
= widgetsRepo
.getByElement( target
);
2511 mouseDownOnDragHandler
= 0; // Reset.
2513 // Widget was clicked, but not editable nested in it.
2515 // Ignore mousedown on drag and drop handler if the widget is inline.
2516 // Block widgets are handled by Lineutils.
2517 if ( widget
.inline
&& target
.type
== CKEDITOR
.NODE_ELEMENT
&& target
.hasAttribute( 'data-cke-widget-drag-handler' ) ) {
2518 mouseDownOnDragHandler
= 1;
2520 // When drag handler is pressed we have to clear current selection if it wasn't already on this widget.
2521 // Otherwise, the selection may be in a fillingChar, which prevents dragging a widget. (#13284, see comment 8 and 9.)
2522 if ( widgetsRepo
.focused
!= widget
)
2523 editor
.getSelection().removeAllRanges();
2528 if ( !Widget
.getNestedEditable( widget
.wrapper
, target
) ) {
2529 evt
.data
.preventDefault();
2530 if ( !CKEDITOR
.env
.ie
)
2533 // Reset widget so mouseup listener is not confused.
2539 // Focus widget on mouseup if mousedown was fired on drag handler.
2540 // Note: mouseup won't be fired at all if widget was dragged and dropped, so
2541 // this code will be executed only when drag handler was clicked.
2542 editable
.attachListener( evtRoot
, 'mouseup', function() {
2543 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null).
2544 if ( mouseDownOnDragHandler
&& widget
&& widget
.wrapper
) {
2545 mouseDownOnDragHandler
= 0;
2550 // On IE it is not enough to block mousedown. If widget wrapper (element with
2551 // contenteditable=false attribute) is clicked directly (it is a target),
2552 // then after mouseup/click IE will select that element.
2553 // It is not possible to prevent that default action,
2554 // so we force fake selection after everything happened.
2555 if ( CKEDITOR
.env
.ie
) {
2556 editable
.attachListener( evtRoot
, 'mouseup', function() {
2557 setTimeout( function() {
2558 // Check if widget is not destroyed (if widget is destroyed the wrapper will be null) and
2559 // in editable contains widget (it could be dragged and removed).
2560 if ( widget
&& widget
.wrapper
&& editable
.contains( widget
.wrapper
) ) {
2569 editor
.on( 'doubleclick', function( evt
) {
2570 var widget
= widgetsRepo
.getByElement( evt
.data
.element
);
2572 // Not in widget or in nested editable.
2573 if ( !widget
|| Widget
.getNestedEditable( widget
.wrapper
, evt
.data
.element
) )
2576 return widget
.fire( 'doubleclick', { element: evt
.data
.element
} );
2580 // Setup editor#key observer which will forward it
2581 // to focused widget.
2582 function setupKeyboardObserver( widgetsRepo
) {
2583 var editor
= widgetsRepo
.editor
;
2585 editor
.on( 'key', function( evt
) {
2586 var focused
= widgetsRepo
.focused
,
2587 widgetHoldingFocusedEditable
= widgetsRepo
.widgetHoldingFocusedEditable
,
2591 ret
= focused
.fire( 'key', { keyCode: evt
.data
.keyCode
} );
2592 else if ( widgetHoldingFocusedEditable
)
2593 ret
= onEditableKey( widgetHoldingFocusedEditable
, evt
.data
.keyCode
);
2599 // Setup copybin on native copy and cut events in order to handle copy and cut commands
2600 // if user accepted security alert on IEs.
2601 // Note: when copying or cutting using keystroke, copySingleWidget will be first executed
2602 // by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check.
2603 function setupNativeCutAndCopy( widgetsRepo
) {
2604 var editor
= widgetsRepo
.editor
;
2606 editor
.on( 'contentDom', function() {
2607 var editable
= editor
.editable();
2609 editable
.attachListener( editable
, 'copy', eventListener
);
2610 editable
.attachListener( editable
, 'cut', eventListener
);
2613 function eventListener( evt
) {
2614 if ( widgetsRepo
.focused
)
2615 copySingleWidget( widgetsRepo
.focused
, evt
.name
== 'cut' );
2619 // Setup selection observer which will trigger:
2620 // * widget select & focus on selection change,
2621 // * nested editable focus (related properites and classes) on selection change,
2622 // * deselecting and blurring all widgets on data,
2623 // * blurring widget on editor blur.
2624 function setupSelectionObserver( widgetsRepo
) {
2625 var editor
= widgetsRepo
.editor
;
2627 editor
.on( 'selectionCheck', function() {
2628 widgetsRepo
.fire( 'checkSelection' );
2631 widgetsRepo
.on( 'checkSelection', widgetsRepo
.checkSelection
, widgetsRepo
);
2633 editor
.on( 'selectionChange', function( evt
) {
2634 var nestedEditable
= Widget
.getNestedEditable( editor
.editable(), evt
.data
.selection
.getStartElement() ),
2635 newWidget
= nestedEditable
&& widgetsRepo
.getByElement( nestedEditable
),
2636 oldWidget
= widgetsRepo
.widgetHoldingFocusedEditable
;
2639 if ( oldWidget
!== newWidget
|| !oldWidget
.focusedEditable
.equals( nestedEditable
) ) {
2640 setFocusedEditable( widgetsRepo
, oldWidget
, null );
2642 if ( newWidget
&& nestedEditable
)
2643 setFocusedEditable( widgetsRepo
, newWidget
, nestedEditable
);
2646 // It may happen that there's no widget even if editable was found -
2647 // e.g. if selection was automatically set in editable although widget wasn't initialized yet.
2648 else if ( newWidget
&& nestedEditable
) {
2649 setFocusedEditable( widgetsRepo
, newWidget
, nestedEditable
);
2653 // Invalidate old widgets early - immediately on dataReady.
2654 editor
.on( 'dataReady', function() {
2655 // Deselect and blur all widgets.
2656 stateUpdater( widgetsRepo
).commit();
2659 editor
.on( 'blur', function() {
2662 if ( ( widget
= widgetsRepo
.focused
) )
2663 blurWidget( widgetsRepo
, widget
);
2665 if ( ( widget
= widgetsRepo
.widgetHoldingFocusedEditable
) )
2666 setFocusedEditable( widgetsRepo
, widget
, null );
2670 // Set up actions like:
2671 // * processing in toHtml/toDataFormat,
2672 // * pasting handling,
2673 // * insertion handling,
2674 // * editable reload handling (setData, mode switch, undo/redo),
2675 // * DOM invalidation handling,
2676 // * widgets checks.
2677 function setupWidgetsLifecycle( widgetsRepo
) {
2678 setupWidgetsLifecycleStart( widgetsRepo
);
2679 setupWidgetsLifecycleEnd( widgetsRepo
);
2681 widgetsRepo
.on( 'checkWidgets', checkWidgets
);
2682 widgetsRepo
.editor
.on( 'contentDomInvalidated', widgetsRepo
.checkWidgets
, widgetsRepo
);
2685 function setupWidgetsLifecycleEnd( widgetsRepo
) {
2686 var editor
= widgetsRepo
.editor
,
2687 downcastingSessions
= {};
2689 // Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll
2690 // loose data-cke-* attributes.
2691 editor
.on( 'toDataFormat', function( evt
) {
2692 // To avoid conflicts between htmlDP#toDF calls done at the same time
2693 // (e.g. nestedEditable#getData called during downcasting some widget)
2694 // mark every toDataFormat event chain with the downcasting session id.
2695 var id
= CKEDITOR
.tools
.getNextNumber(),
2696 toBeDowncasted
= [];
2697 evt
.data
.downcastingSessionId
= id
;
2698 downcastingSessions
[ id
] = toBeDowncasted
;
2700 evt
.data
.dataValue
.forEach( function( element
) {
2701 var attrs
= element
.attributes
,
2702 widget
, widgetElement
;
2705 // Perform first part of downcasting (cleanup) and cache widgets,
2706 // because after applying DP's filter all data-cke-* attributes will be gone.
2707 if ( 'data-cke-widget-id' in attrs
) {
2708 widget
= widgetsRepo
.instances
[ attrs
[ 'data-cke-widget-id' ] ];
2710 widgetElement
= element
.getFirst( Widget
.isParserWidgetElement
);
2711 toBeDowncasted
.push( {
2713 element: widgetElement
,
2718 // If widget did not have data-cke-widget attribute before upcasting remove it.
2719 if ( widgetElement
.attributes
[ 'data-cke-widget-keep-attr' ] != '1' )
2720 delete widgetElement
.attributes
[ 'data-widget' ];
2724 else if ( 'data-cke-widget-editable' in attrs
) {
2725 // Save the reference to this nested editable in the closest widget to be downcasted.
2726 // Nested editables are downcasted in the successive toDataFormat to create an opportunity
2727 // for dataFilter's "excludeNestedEditable" option to do its job (that option relies on
2728 // contenteditable="true" attribute) (#11372).
2729 toBeDowncasted
[ toBeDowncasted
.length
- 1 ].editables
[ attrs
[ 'data-cke-widget-editable' ] ] = element
;
2731 // Don't check children - there won't be next wrapper or nested editable which we
2732 // should process in this session.
2735 }, CKEDITOR
.NODE_ELEMENT
, true );
2738 // Listen after dataProcessor.htmlFilter and ACF were applied
2739 // so wrappers securing widgets' contents are removed after all filtering was done.
2740 editor
.on( 'toDataFormat', function( evt
) {
2741 // Ignore some unmarked sessions.
2742 if ( !evt
.data
.downcastingSessionId
)
2745 var toBeDowncasted
= downcastingSessions
[ evt
.data
.downcastingSessionId
],
2746 toBe
, widget
, widgetElement
, retElement
, editableElement
, e
;
2748 while ( ( toBe
= toBeDowncasted
.shift() ) ) {
2749 widget
= toBe
.widget
;
2750 widgetElement
= toBe
.element
;
2751 retElement
= widget
._
.downcastFn
&& widget
._
.downcastFn
.call( widget
, widgetElement
);
2753 // Replace nested editables' content with their output data.
2754 for ( e
in toBe
.editables
) {
2755 editableElement
= toBe
.editables
[ e
];
2757 delete editableElement
.attributes
.contenteditable
;
2758 editableElement
.setHtml( widget
.editables
[ e
].getData() );
2761 // Returned element always defaults to widgetElement.
2763 retElement
= widgetElement
;
2765 toBe
.wrapper
.replaceWith( retElement
);
2767 }, null, null, 13 );
2770 editor
.on( 'contentDomUnload', function() {
2771 widgetsRepo
.destroyAll( true );
2775 function setupWidgetsLifecycleStart( widgetsRepo
) {
2776 var editor
= widgetsRepo
.editor
,
2777 processedWidgetOnly
,
2780 // Listen after ACF (so data are filtered),
2781 // but before dataProcessor.dataFilter was applied (so we can secure widgets' internals).
2782 editor
.on( 'toHtml', function( evt
) {
2783 var upcastIterator
= createUpcastIterator( widgetsRepo
),
2786 evt
.data
.dataValue
.forEach( upcastIterator
.iterator
, CKEDITOR
.NODE_ELEMENT
, true );
2788 // Clean up and wrap all queued elements.
2789 while ( ( toBeWrapped
= upcastIterator
.toBeWrapped
.pop() ) ) {
2790 cleanUpWidgetElement( toBeWrapped
[ 0 ] );
2791 widgetsRepo
.wrapElement( toBeWrapped
[ 0 ], toBeWrapped
[ 1 ] );
2794 // Used to determine whether only widget was pasted.
2795 if ( evt
.data
.protectedWhitespaces
) {
2796 // Whitespaces are protected by wrapping content with spans. Take the middle node only.
2797 processedWidgetOnly
= evt
.data
.dataValue
.children
.length
== 3 &&
2798 Widget
.isParserWidgetWrapper( evt
.data
.dataValue
.children
[ 1 ] );
2800 processedWidgetOnly
= evt
.data
.dataValue
.children
.length
== 1 &&
2801 Widget
.isParserWidgetWrapper( evt
.data
.dataValue
.children
[ 0 ] );
2805 editor
.on( 'dataReady', function() {
2806 // Clean up all widgets loaded from snapshot.
2807 if ( snapshotLoaded
)
2808 cleanUpAllWidgetElements( widgetsRepo
, editor
.editable() );
2811 // Some widgets were destroyed on contentDomUnload,
2812 // some on loadSnapshot, but that does not include
2813 // e.g. setHtml on inline editor or widgets removed just
2814 // before setting data.
2815 widgetsRepo
.destroyAll( true );
2816 widgetsRepo
.initOnAll();
2819 // Set flag so dataReady will know that additional
2820 // cleanup is needed, because snapshot containing widgets was loaded.
2821 editor
.on( 'loadSnapshot', function( evt
) {
2822 // Primitive but sufficient check which will prevent from executing
2823 // heavier cleanUpAllWidgetElements if not needed.
2824 if ( ( /data-cke-widget/ ).test( evt
.data
) )
2827 widgetsRepo
.destroyAll( true );
2830 // Handle pasted single widget.
2831 editor
.on( 'paste', function( evt
) {
2832 var data
= evt
.data
;
2834 data
.dataValue
= data
.dataValue
.replace( pasteReplaceRegex
, pasteReplaceFn
);
2836 // If drag'n'drop kind of paste into nested editable (data.range), selection is set AFTER
2837 // data is pasted, which means editor has no chance to change activeFilter's context.
2838 // As a result, pasted data is filtered with default editor's filter instead of NE's and
2839 // funny things get inserted. Changing the filter by analysis of the paste range below (#13186).
2841 // Check if pasting into nested editable.
2842 var nestedEditable
= Widget
.getNestedEditable( editor
.editable(), data
.range
.startContainer
);
2844 if ( nestedEditable
) {
2845 // Retrieve the filter from NE's data and set it active before editor.insertHtml is done
2846 // in clipboard plugin.
2847 var filter
= CKEDITOR
.filter
.instances
[ nestedEditable
.data( 'cke-filter' ) ];
2850 editor
.setActiveFilter( filter
);
2856 // Listen with high priority to check widgets after data was inserted.
2857 editor
.on( 'afterInsertHtml', function( evt
) {
2858 if ( evt
.data
.intoRange
) {
2859 widgetsRepo
.checkWidgets( { initOnlyNew: true } );
2861 editor
.fire( 'lockSnapshot' );
2862 // Init only new for performance reason.
2863 // Focus inited if only widget was processed.
2864 widgetsRepo
.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly
} );
2866 editor
.fire( 'unlockSnapshot' );
2871 // Helper for coordinating which widgets should be
2872 // selected/deselected and which one should be focused/blurred.
2873 function stateUpdater( widgetsRepo
) {
2874 var currentlySelected
= widgetsRepo
.selected
,
2876 toBeDeselected
= currentlySelected
.slice( 0 ),
2880 select: function( widget
) {
2881 if ( CKEDITOR
.tools
.indexOf( currentlySelected
, widget
) < 0 )
2882 toBeSelected
.push( widget
);
2884 var index
= CKEDITOR
.tools
.indexOf( toBeDeselected
, widget
);
2886 toBeDeselected
.splice( index
, 1 );
2891 focus: function( widget
) {
2896 commit: function() {
2897 var focusedChanged
= widgetsRepo
.focused
!== focused
,
2900 widgetsRepo
.editor
.fire( 'lockSnapshot' );
2902 if ( focusedChanged
&& ( widget
= widgetsRepo
.focused
) )
2903 blurWidget( widgetsRepo
, widget
);
2905 while ( ( widget
= toBeDeselected
.pop() ) ) {
2906 currentlySelected
.splice( CKEDITOR
.tools
.indexOf( currentlySelected
, widget
), 1 );
2907 // Widget could be destroyed in the meantime - e.g. data could be set.
2908 if ( widget
.isInited() ) {
2909 isDirty
= widget
.editor
.checkDirty();
2911 widget
.setSelected( false );
2913 !isDirty
&& widget
.editor
.resetDirty();
2917 if ( focusedChanged
&& focused
) {
2918 isDirty
= widgetsRepo
.editor
.checkDirty();
2920 widgetsRepo
.focused
= focused
;
2921 widgetsRepo
.fire( 'widgetFocused', { widget: focused
} );
2922 focused
.setFocused( true );
2924 !isDirty
&& widgetsRepo
.editor
.resetDirty();
2927 while ( ( widget
= toBeSelected
.pop() ) ) {
2928 currentlySelected
.push( widget
);
2929 widget
.setSelected( true );
2932 widgetsRepo
.editor
.fire( 'unlockSnapshot' );
2939 // WIDGET helpers ---------------------------------------------------------
2942 // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers.
2943 var keystrokesNotBlockedByWidget
= { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 };
2945 // Applies or removes style's classes from widget.
2946 // @param {CKEDITOR.style} style Custom widget style.
2947 // @param {Boolean} apply Whether to apply or remove style.
2948 function applyRemoveStyle( widget
, style
, apply
) {
2950 classes
= getStyleClasses( style
),
2951 updatedClasses
= widget
.data
.classes
|| {},
2954 // Ee... Something is wrong with this style.
2958 // Clone, because we need to break reference.
2959 updatedClasses
= CKEDITOR
.tools
.clone( updatedClasses
);
2961 while ( ( cl
= classes
.pop() ) ) {
2963 if ( !updatedClasses
[ cl
] )
2964 changed
= updatedClasses
[ cl
] = 1;
2966 if ( updatedClasses
[ cl
] ) {
2967 delete updatedClasses
[ cl
];
2973 widget
.setData( 'classes', updatedClasses
);
2976 function cancel( evt
) {
2980 function copySingleWidget( widget
, isCut
) {
2981 var editor
= widget
.editor
,
2982 doc
= editor
.document
;
2984 // We're still handling previous copy/cut.
2985 // When keystroke is used to copy/cut this will also prevent
2986 // conflict with copySingleWidget called again for native copy/cut event.
2987 if ( doc
.getById( 'cke_copybin' ) )
2990 // [IE] Use span for copybin and its container to avoid bug with expanding editable height by
2991 // absolutely positioned element.
2992 var copybinName
= ( editor
.blockless
|| CKEDITOR
.env
.ie
) ? 'span' : 'div',
2993 copybin
= doc
.createElement( copybinName
),
2994 copybinContainer
= doc
.createElement( copybinName
),
2995 // IE8 always jumps to the end of document.
2996 needsScrollHack
= CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 9;
2998 copybinContainer
.setAttributes( {
3000 'data-cke-temp': '1'
3003 // Position copybin element outside current viewport.
3004 copybin
.setStyles( {
3005 position: 'absolute',
3011 copybin
.setStyle( editor
.config
.contentsLangDirection
== 'ltr' ? 'left' : 'right', '-5000px' );
3013 var range
= editor
.createRange();
3014 range
.setStartBefore( widget
.wrapper
);
3015 range
.setEndAfter( widget
.wrapper
);
3018 '<span data-cke-copybin-start="1">\u200b</span>' +
3019 editor
.editable().getHtmlFromRange( range
).getHtml() +
3020 '<span data-cke-copybin-end="1">\u200b</span>' );
3022 // Save snapshot with the current state.
3023 editor
.fire( 'saveSnapshot' );
3026 editor
.fire( 'lockSnapshot' );
3028 copybinContainer
.append( copybin
);
3029 editor
.editable().append( copybinContainer
);
3031 var listener1
= editor
.on( 'selectionChange', cancel
, null, null, 0 ),
3032 listener2
= widget
.repository
.on( 'checkSelection', cancel
, null, null, 0 );
3034 if ( needsScrollHack
) {
3035 var docElement
= doc
.getDocumentElement().$,
3036 scrollTop
= docElement
.scrollTop
;
3039 // Once the clone of the widget is inside of copybin, select
3040 // the entire contents. This selection will be copied by the
3041 // native browser's clipboard system.
3042 range
= editor
.createRange();
3043 range
.selectNodeContents( copybin
);
3046 if ( needsScrollHack
)
3047 docElement
.scrollTop
= scrollTop
;
3049 setTimeout( function() {
3050 // [IE] Focus widget before removing copybin to avoid scroll jump.
3054 copybinContainer
.remove();
3056 listener1
.removeListener();
3057 listener2
.removeListener();
3059 editor
.fire( 'unlockSnapshot' );
3062 widget
.repository
.del( widget
);
3063 editor
.fire( 'saveSnapshot' );
3065 }, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content.
3068 // Extracts classes array from style instance.
3069 function getStyleClasses( style
) {
3070 var attrs
= style
.getDefinition().attributes
,
3071 classes
= attrs
&& attrs
[ 'class' ];
3073 return classes
? classes
.split( /\s+/ ) : null;
3076 // [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable
3077 // when blurring nested editable.
3079 function onEditableBlur() {
3080 var active
= CKEDITOR
.document
.getActive(),
3081 editor
= this.editor
,
3082 editable
= editor
.editable();
3084 // If focus stays within editor override blur and set currentActive because it should be
3085 // automatically changed to editable on editable#focus but it is not fired.
3086 if ( ( editable
.isInline() ? editable : editor
.document
.getWindow().getFrame() ).equals( active
) )
3087 editor
.focusManager
.focus( editable
);
3090 // Force selectionChange when editable was focused.
3091 // Similar to hack in selection.js#~620.
3093 function onEditableFocus() {
3094 // Gecko does not support 'DOMFocusIn' event on which we unlock selection
3095 // in selection.js to prevent selection locking when entering nested editables.
3096 if ( CKEDITOR
.env
.gecko
)
3097 this.editor
.unlockSelection();
3099 // We don't need to force selectionCheck on Webkit, because on Webkit
3100 // we do that on DOMFocusIn in selection.js.
3101 if ( !CKEDITOR
.env
.webkit
) {
3102 this.editor
.forceNextSelectionCheck();
3103 this.editor
.selectionChange( 1 );
3107 // Setup listener on widget#data which will update (remove/add) classes
3108 // by comparing newly set classes with the old ones.
3109 function setupDataClassesListener( widget
) {
3110 // Note: previousClasses and newClasses may be null!
3111 // Tip: for ( cl in null ) is correct.
3112 var previousClasses
= null;
3114 widget
.on( 'data', function() {
3115 var newClasses
= this.data
.classes
,
3118 // When setting new classes one need to remember
3119 // that he must break reference.
3120 if ( previousClasses
== newClasses
)
3123 for ( cl
in previousClasses
) {
3124 // Avoid removing and adding classes again.
3125 if ( !( newClasses
&& newClasses
[ cl
] ) )
3126 this.removeClass( cl
);
3128 for ( cl
in newClasses
)
3129 this.addClass( cl
);
3131 previousClasses
= newClasses
;
3135 // Add a listener to data event that will set/change widget's label (#14539).
3136 function setupA11yListener( widget
) {
3137 // Note, the function gets executed in a context of widget instance.
3138 function getLabelDefault() {
3139 return this.editor
.lang
.widget
.label
.replace( /%1/, this.pathName
|| this.element
.getName() );
3142 // Setting a listener on data is enough, there's no need to perform it on widget initialization, as
3143 // setupWidgetData fires this event anyway.
3144 widget
.on( 'data', function() {
3145 // In some cases widget might get destroyed in an earlier data listener. For instance, image2 plugin, does
3146 // so when changing its internal state.
3147 if ( !widget
.wrapper
) {
3151 var label
= this.getLabel
? this.getLabel() : getLabelDefault
.call( this );
3153 widget
.wrapper
.setAttribute( 'role', 'region' );
3154 widget
.wrapper
.setAttribute( 'aria-label', label
);
3155 }, null, null, 9999 );
3158 function setupDragHandler( widget
) {
3159 if ( !widget
.draggable
)
3162 var editor
= widget
.editor
,
3163 // Use getLast to find wrapper's direct descendant (#12022).
3164 container
= widget
.wrapper
.getLast( Widget
.isDomDragHandlerContainer
),
3167 // Reuse drag handler if already exists (#11281).
3169 img
= container
.findOne( 'img' );
3171 container
= new CKEDITOR
.dom
.element( 'span', editor
.document
);
3172 container
.setAttributes( {
3173 'class': 'cke_reset cke_widget_drag_handler_container',
3174 // Split background and background-image for IE8 which will break on rgba().
3175 style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor
.plugins
.widget
.path
+ 'images/handle.png)'
3178 img
= new CKEDITOR
.dom
.element( 'img', editor
.document
);
3179 img
.setAttributes( {
3180 'class': 'cke_reset cke_widget_drag_handler',
3181 'data-cke-widget-drag-handler': '1',
3182 src: CKEDITOR
.tools
.transparentImageData
,
3183 width: DRAG_HANDLER_SIZE
,
3184 title: editor
.lang
.widget
.move,
3185 height: DRAG_HANDLER_SIZE
,
3186 role: 'presentation'
3188 widget
.inline
&& img
.setAttribute( 'draggable', 'true' );
3190 container
.append( img
);
3191 widget
.wrapper
.append( container
);
3194 // Preventing page reload when dropped content on widget wrapper (#13015).
3195 // Widget is not editable so by default drop on it isn't allowed what means that
3196 // browser handles it (there's no editable#drop event). If there's no drop event we cannot block
3197 // the drop, so page is reloaded. This listener enables drop on widget wrappers.
3198 widget
.wrapper
.on( 'dragover', function( evt
) {
3199 evt
.data
.preventDefault();
3202 widget
.wrapper
.on( 'mouseenter', widget
.updateDragHandlerPosition
, widget
);
3203 setTimeout( function() {
3204 widget
.on( 'data', widget
.updateDragHandlerPosition
, widget
);
3207 if ( !widget
.inline
) {
3208 img
.on( 'mousedown', onBlockWidgetDrag
, widget
);
3210 // On IE8 'dragstart' is propagated to editable, so editor#dragstart is fired twice on block widgets.
3211 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 9 ) {
3212 img
.on( 'dragstart', function( evt
) {
3213 evt
.data
.preventDefault( true );
3218 widget
.dragHandlerContainer
= container
;
3221 function onBlockWidgetDrag( evt
) {
3222 var finder
= this.repository
.finder
,
3223 locator
= this.repository
.locator
,
3224 liner
= this.repository
.liner
,
3225 editor
= this.editor
,
3226 editable
= editor
.editable(),
3232 // Mark dragged widget for repository#finder.
3233 this.repository
._
.draggedWidget
= this;
3235 // Harvest all possible relations and display some closest.
3236 var relations
= finder
.greedySearch(),
3238 buffer
= CKEDITOR
.tools
.eventsBuffer( 50, function() {
3239 locations
= locator
.locate( relations
);
3241 // There's only a single line displayed for D&D.
3242 sorted
= locator
.sort( y
, 1 );
3244 if ( sorted
.length
) {
3245 liner
.prepare( relations
, locations
);
3246 liner
.placeLine( sorted
[ 0 ] );
3251 // Let's have the "dragging cursor" over entire editable.
3252 editable
.addClass( 'cke_widget_dragging' );
3254 // Cache mouse position so it is re-used in events buffer.
3255 listeners
.push( editable
.on( 'mousemove', function( evt
) {
3256 y
= evt
.data
.$.clientY
;
3260 // Fire drag start as it happens during the native D&D.
3261 editor
.fire( 'dragstart', { target: evt
.sender
} );
3263 function onMouseUp() {
3268 // Stop observing events.
3269 while ( ( l
= listeners
.pop() ) )
3272 onBlockWidgetDrop
.call( this, sorted
, evt
.sender
);
3275 // Mouseup means "drop". This is when the widget is being detached
3276 // from DOM and placed at range determined by the line (location).
3277 listeners
.push( editor
.document
.once( 'mouseup', onMouseUp
, this ) );
3279 // Prevent calling 'onBlockWidgetDrop' twice in the inline editor.
3280 // `removeListener` does not work if it is called at the same time event is fired.
3281 if ( !editable
.isInline() ) {
3282 // Mouseup may occur when user hovers the line, which belongs to
3283 // the outer document. This is, of course, a valid listener too.
3284 listeners
.push( CKEDITOR
.document
.once( 'mouseup', onMouseUp
, this ) );
3288 function onBlockWidgetDrop( sorted
, dragTarget
) {
3289 var finder
= this.repository
.finder
,
3290 liner
= this.repository
.liner
,
3291 editor
= this.editor
,
3292 editable
= this.editor
.editable();
3294 if ( !CKEDITOR
.tools
.isEmpty( liner
.visible
) ) {
3295 // Retrieve range for the closest location.
3296 var dropRange
= finder
.getRange( sorted
[ 0 ] );
3298 // Focus widget (it could lost focus after mousedown+mouseup)
3299 // and save this state as the one where we want to be taken back when undoing.
3302 // Drag range will be set in the drop listener.
3303 editor
.fire( 'drop', {
3304 dropRange: dropRange
,
3305 target: dropRange
.startContainer
3309 // Clean-up custom cursor for editable.
3310 editable
.removeClass( 'cke_widget_dragging' );
3312 // Clean-up all remaining lines.
3313 liner
.hideVisible();
3315 // Clean-up drag & drop.
3316 editor
.fire( 'dragend', { target: dragTarget
} );
3319 function setupEditables( widget
) {
3322 definedEditables
= widget
.editables
;
3324 widget
.editables
= {};
3326 if ( !widget
.editables
)
3329 for ( editableName
in definedEditables
) {
3330 editableDef
= definedEditables
[ editableName
];
3331 widget
.initEditable( editableName
, typeof editableDef
== 'string' ? { selector: editableDef
} : editableDef
);
3335 function setupMask( widget
) {
3339 // Reuse mask if already exists (#11281).
3340 var img
= widget
.wrapper
.findOne( '.cke_widget_mask' );
3343 img
= new CKEDITOR
.dom
.element( 'img', widget
.editor
.document
);
3344 img
.setAttributes( {
3345 src: CKEDITOR
.tools
.transparentImageData
,
3346 'class': 'cke_reset cke_widget_mask'
3348 widget
.wrapper
.append( img
);
3354 // Replace parts object containing:
3355 // partName => selector pairs
3357 // partName => element pairs
3358 function setupParts( widget
) {
3359 if ( widget
.parts
) {
3363 for ( partName
in widget
.parts
) {
3364 el
= widget
.wrapper
.findOne( widget
.parts
[ partName
] );
3365 parts
[ partName
] = el
;
3367 widget
.parts
= parts
;
3371 function setupWidget( widget
, widgetDef
) {
3372 setupWrapper( widget
);
3373 setupParts( widget
);
3374 setupEditables( widget
);
3375 setupMask( widget
);
3376 setupDragHandler( widget
);
3377 setupDataClassesListener( widget
);
3378 setupA11yListener( widget
);
3380 // #11145: [IE8] Non-editable content of widget is draggable.
3381 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 9 ) {
3382 widget
.wrapper
.on( 'dragstart', function( evt
) {
3383 var target
= evt
.data
.getTarget();
3385 // Allow text dragging inside nested editables or dragging inline widget's drag handler.
3386 if ( !Widget
.getNestedEditable( widget
, target
) && !( widget
.inline
&& Widget
.isDomDragHandler( target
) ) )
3387 evt
.data
.preventDefault();
3391 widget
.wrapper
.removeClass( 'cke_widget_new' );
3392 widget
.element
.addClass( 'cke_widget_element' );
3394 widget
.on( 'key', function( evt
) {
3395 var keyCode
= evt
.data
.keyCode
;
3398 if ( keyCode
== 13 ) {
3400 // CTRL+C or CTRL+X.
3401 } else if ( keyCode
== CKEDITOR
.CTRL
+ 67 || keyCode
== CKEDITOR
.CTRL
+ 88 ) {
3402 copySingleWidget( widget
, keyCode
== CKEDITOR
.CTRL
+ 88 );
3403 return; // Do not preventDefault.
3404 } else if ( keyCode
in keystrokesNotBlockedByWidget
|| ( CKEDITOR
.CTRL
& keyCode
) || ( CKEDITOR
.ALT
& keyCode
) ) {
3405 // Pass chosen keystrokes to other plugins or default fake sel handlers.
3406 // Pass all CTRL/ALT keystrokes.
3411 }, null, null, 999 );
3412 // Listen with high priority so it's possible
3413 // to overwrite this callback.
3415 widget
.on( 'doubleclick', function( evt
) {
3416 if ( widget
.edit() ) {
3417 // We have to cancel event if edit method opens a dialog, otherwise
3418 // link plugin may open extra dialog (#12140).
3423 if ( widgetDef
.data
)
3424 widget
.on( 'data', widgetDef
.data
);
3426 if ( widgetDef
.edit
)
3427 widget
.on( 'edit', widgetDef
.edit
);
3430 function setupWidgetData( widget
, startupData
) {
3431 var widgetDataAttr
= widget
.element
.data( 'cke-widget-data' );
3433 if ( widgetDataAttr
)
3434 widget
.setData( JSON
.parse( decodeURIComponent( widgetDataAttr
) ) );
3436 widget
.setData( startupData
);
3438 // Populate classes if they are not preset.
3439 if ( !widget
.data
.classes
)
3440 widget
.setData( 'classes', widget
.getClasses() );
3442 // Unblock data and...
3443 widget
.dataReady
= true;
3445 // Write data to element because this was blocked when data wasn't ready.
3446 writeDataToElement( widget
);
3448 // Fire data event first time, because this was blocked when data wasn't ready.
3449 widget
.fire( 'data', widget
.data
);
3452 function setupWrapper( widget
) {
3453 // Retrieve widget wrapper. Assign an id to it.
3454 var wrapper
= widget
.wrapper
= widget
.element
.getParent();
3455 wrapper
.setAttribute( 'data-cke-widget-id', widget
.id
);
3458 function writeDataToElement( widget
) {
3459 widget
.element
.data( 'cke-widget-data', encodeURIComponent( JSON
.stringify( widget
.data
) ) );
3463 // WIDGET STYLE HANDLER ---------------------------------------------------
3467 // Styles categorized by group. It is used to prevent applying styles for the same group being used together.
3468 var styleGroups
= {};
3471 * The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like
3472 * the styles handler for widgets.
3474 * **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class.
3475 * Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}.
3478 * @class CKEDITOR.style.customHandlers.widget
3479 * @extends CKEDITOR.style
3481 CKEDITOR
.style
.addCustomHandler( {
3484 setup: function( styleDefinition
) {
3486 * The name of widget to which this style can be applied.
3487 * It is extracted from style definition's `widget` property.
3489 * @property {String} widget
3491 this.widget
= styleDefinition
.widget
;
3494 * An array of groups that this style belongs to.
3495 * Styles assigned to the same group cannot be combined.
3498 * @property {Array} group
3500 this.group
= typeof styleDefinition
.group
== 'string' ? [ styleDefinition
.group
] : styleDefinition
.group
;
3502 // Store style categorized by its group.
3503 // It is used to prevent enabling two styles from same group.
3505 saveStyleGroup( this );
3509 apply: function( editor
) {
3512 // Before CKEditor 4.4 wasn't a required argument, so we need to
3513 // handle a case when it wasn't provided.
3514 if ( !( editor
instanceof CKEDITOR
.editor
) )
3517 // Theoretically we could bypass checkApplicable, get widget from
3518 // widgets.focused and check its name, what would be faster, but then
3519 // this custom style would work differently than the default style
3520 // which checks if it's applicable before applying or removing itself.
3521 if ( this.checkApplicable( editor
.elementPath(), editor
) ) {
3522 widget
= editor
.widgets
.focused
;
3524 // Remove other styles from the same group.
3526 this.removeStylesFromSameGroup( editor
);
3529 widget
.applyStyle( this );
3533 remove: function( editor
) {
3534 // Before CKEditor 4.4 wasn't a required argument, so we need to
3535 // handle a case when it wasn't provided.
3536 if ( !( editor
instanceof CKEDITOR
.editor
) )
3539 if ( this.checkApplicable( editor
.elementPath(), editor
) )
3540 editor
.widgets
.focused
.removeStyle( this );
3544 * Removes all styles that belong to the same group as this style. This method will neither add nor remove
3545 * the current style.
3546 * Returns `true` if any style was removed, otherwise returns `false`.
3549 * @param {CKEDITOR.editor} editor
3550 * @returns {Boolean}
3552 removeStylesFromSameGroup: function( editor
) {
3553 var stylesFromSameGroup
,
3557 // Before CKEditor 4.4 wasn't a required argument, so we need to
3558 // handle a case when it wasn't provided.
3559 if ( !( editor
instanceof CKEDITOR
.editor
) )
3562 path
= editor
.elementPath();
3563 if ( this.checkApplicable( path
, editor
) ) {
3564 // Iterate over each group.
3565 for ( var i
= 0, l
= this.group
.length
; i
< l
; i
++ ) {
3566 stylesFromSameGroup
= styleGroups
[ this.widget
][ this.group
[ i
] ];
3567 // Iterate over each style from group.
3568 for ( var j
= 0; j
< stylesFromSameGroup
.length
; j
++ ) {
3569 if ( stylesFromSameGroup
[ j
] !== this && stylesFromSameGroup
[ j
].checkActive( path
, editor
) ) {
3570 editor
.widgets
.focused
.removeStyle( stylesFromSameGroup
[ j
] );
3580 checkActive: function( elementPath
, editor
) {
3581 return this.checkElementMatch( elementPath
.lastElement
, 0, editor
);
3584 checkApplicable: function( elementPath
, editor
) {
3585 // Before CKEditor 4.4 wasn't a required argument, so we need to
3586 // handle a case when it wasn't provided.
3587 if ( !( editor
instanceof CKEDITOR
.editor
) )
3590 return this.checkElement( elementPath
.lastElement
);
3593 checkElementMatch: checkElementMatch
,
3595 checkElementRemovable: checkElementMatch
,
3598 * Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a
3599 * widget whose name matches the {@link #widget widget name} specified in the style definition.
3601 * @param {CKEDITOR.dom.element} element
3602 * @returns {Boolean}
3604 checkElement: function( element
) {
3605 if ( !Widget
.isDomWidgetWrapper( element
) )
3608 var widgetElement
= element
.getFirst( Widget
.isDomWidgetElement
);
3609 return widgetElement
&& widgetElement
.data( 'widget' ) == this.widget
;
3612 buildPreview: function( label
) {
3613 return label
|| this._
.definition
.name
;
3617 * Returns allowed content rules which should be registered for this style.
3618 * Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule
3619 * allowing classes on specified elements or use widget's
3620 * {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style
3621 * into allowed content rules.
3623 * @param {CKEDITOR.editor} The editor instance.
3624 * @returns {CKEDITOR.filter.allowedContentRules}
3626 toAllowedContentRules: function( editor
) {
3630 var widgetDef
= editor
.widgets
.registered
[ this.widget
],
3637 if ( widgetDef
.styleableElements
) {
3638 classes
= this.getClassesArray();
3642 rule
[ widgetDef
.styleableElements
] = {
3644 propertiesOnly: true
3648 if ( widgetDef
.styleToAllowedContentRules
)
3649 return widgetDef
.styleToAllowedContentRules( this );
3654 * Returns classes defined in the style in form of an array.
3656 * @returns {String[]}
3658 getClassesArray: function() {
3659 var classes
= this._
.definition
.attributes
&& this._
.definition
.attributes
[ 'class' ];
3661 return classes
? CKEDITOR
.tools
.trim( classes
).split( /\s+/ ) : null;
3667 * @method applyToRange
3669 applyToRange: notImplemented
,
3674 * @method removeFromRange
3676 removeFromRange: notImplemented
,
3681 * @method applyToObject
3683 applyToObject: notImplemented
3686 function notImplemented() {}
3689 function checkElementMatch( element
, fullMatch
, editor
) {
3690 // Before CKEditor 4.4 wasn't a required argument, so we need to
3691 // handle a case when it wasn't provided.
3695 if ( !this.checkElement( element
) )
3698 var widget
= editor
.widgets
.getByElement( element
, true );
3699 return widget
&& widget
.checkStyleActive( this );
3702 // Save and categorize style by its group.
3703 function saveStyleGroup( style
) {
3704 var widgetName
= style
.widget
,
3707 if ( !styleGroups
[ widgetName
] ) {
3708 styleGroups
[ widgetName
] = {};
3711 for ( var i
= 0, l
= style
.group
.length
; i
< l
; i
++ ) {
3712 group
= style
.group
[ i
];
3713 if ( !styleGroups
[ widgetName
][ group
] ) {
3714 styleGroups
[ widgetName
][ group
] = [];
3717 styleGroups
[ widgetName
][ group
].push( style
);
3724 // EXPOSE PUBLIC API ------------------------------------------------------
3727 CKEDITOR
.plugins
.widget
= Widget
;
3728 Widget
.repository
= Repository
;
3729 Widget
.nestedEditable
= NestedEditable
;
3733 * An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method.
3734 * It is possible to modify the definition being registered.
3736 * @event widgetDefinition
3737 * @member CKEDITOR.editor
3738 * @param {CKEDITOR.plugins.widget.definition} data Widget definition.
3742 * This is an abstract class that describes the definition of a widget.
3743 * It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument.
3745 * Widget instances inherit from registered widget definitions, although not in a prototypal way.
3746 * They are simply extended with corresponding widget definitions. Note that not all properties of
3747 * the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become
3748 * widget's events listeners.
3750 * @class CKEDITOR.plugins.widget.definition
3752 * @mixins CKEDITOR.feature
3756 * Widget definition name. It is automatically set when the definition is
3757 * {@link CKEDITOR.plugins.widget.repository#add registered}.
3759 * @property {String} name
3763 * The method executed while initializing a widget, after a widget instance
3764 * is created, but before it is ready. It is executed before the first
3765 * {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to
3766 * use the `init` method to populate widget data with information loaded from
3767 * the DOM, like for exmaple:
3769 * init: function() {
3770 * this.setData( 'width', this.element.getStyle( 'width' ) );
3772 * if ( this.parts.caption.getStyle( 'display' ) != 'none' )
3773 * this.setData( 'showCaption', true );
3776 * @property {Function} init
3780 * The function to be used to upcast an element to this widget or a
3781 * comma-separated list of upcast methods from the {@link #upcasts} object.
3783 * The upcast function **is not** executed in the widget context (because the widget
3784 * does not exist yet) and two arguments are passed:
3786 * * `element` ({@link CKEDITOR.htmlParser.element}) – The element to be checked.
3787 * * `data` (`Object`) – The object which can be extended with data which will then be passed to the widget.
3789 * An element will be upcasted if a function returned `true` or an instance of
3790 * a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes
3791 * (in this case the widget will be initialized on the returned element).
3793 * @property {String/Function} upcast
3797 * The object containing functions which can be used to upcast this widget.
3798 * Only those pointed by the {@link #upcast} property will be used.
3800 * In most cases it is appropriate to use {@link #upcast} directly,
3801 * because majority of widgets need just one method.
3802 * However, in some cases the widget author may want to expose more than one variant
3803 * and then this property may be used.
3806 * // This function may upcast only figure elements.
3807 * figure: function() {
3810 * // This function may upcast only image elements.
3811 * image: function() {
3814 * // More variants...
3817 * // Then, widget user may choose which upcast methods will be enabled.
3818 * editor.on( 'widgetDefinition', function( evt ) {
3819 * if ( evt.data.name == 'image' )
3820 * evt.data.upcast = 'figure,image'; // Use both methods.
3823 * @property {Object} upcasts
3827 * The {@link #upcast} method(s) priority. The upcast with a lower priority number will be called before
3828 * the one with a higher number. The default priority is `10`.
3831 * @property {Number} [upcastPriority=10]
3835 * The function to be used to downcast this widget or
3836 * a name of the downcast option from the {@link #downcasts} object.
3838 * The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context
3839 * and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is
3840 * the widget's main element.
3842 * The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget
3843 * needs to be downcasted to a different node than the widget's main element.
3845 * @property {String/Function} downcast
3849 * The object containing functions which can be used to downcast this widget.
3850 * Only the one pointed by the {@link #downcast} property will be used.
3852 * In most cases it is appropriate to use {@link #downcast} directly,
3853 * because majority of widgets have just one variant of downcasting (or none at all).
3854 * However, in some cases the widget author may want to expose more than one variant
3855 * and then this property may be used.
3858 * // This downcast may transform the widget into the figure element.
3859 * figure: function() {
3862 * // This downcast may transform the widget into the image element with data-* attributes.
3863 * image: function() {
3868 * // Then, the widget user may choose one of the downcast options when setting up his editor.
3869 * editor.on( 'widgetDefinition', function( evt ) {
3870 * if ( evt.data.name == 'image' )
3871 * evt.data.downcast = 'figure';
3874 * @property downcasts
3878 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener.
3879 * This means that it will be executed when a widget is being edited.
3880 * See the {@link CKEDITOR.plugins.widget#method-edit} method.
3882 * @property {Function} edit
3886 * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener.
3887 * This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes.
3889 * @property {Function} data
3893 * The method to be executed when the widget's command is executed in order to insert a new widget
3894 * (widget of this type is not focused). If not defined, then the default action will be
3895 * performed which means that:
3897 * * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment},
3898 * * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing,
3899 * * The widget element will be inserted into DOM.
3901 * @property {Function} insert
3905 * The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}.
3906 * If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and
3907 * widget's command will insert a new widget without opening a dialog window first.
3909 * @property {String} dialog
3913 * The template which will be used to create a new widget element (when the widget's command is executed).
3914 * This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format.
3915 * Therefore it has to be a valid {@link CKEDITOR.template} argument.
3917 * @property {String} template
3921 * The data object which will be used to populate the data of a newly created widget.
3922 * See {@link CKEDITOR.plugins.widget#property-data}.
3925 * showCaption: true,
3929 * @property defaults
3933 * An object containing definitions of widget components (part name => CSS selector).
3937 * caption: 'div.caption'
3944 * An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}).
3945 * Note that editables *have to* be defined in the same order as they are in DOM / {@link CKEDITOR.plugins.widget.definition#template template}.
3946 * Otherwise errors will occur when nesting widgets inside each other.
3951 * selector: 'div.content',
3952 * allowedContent: 'p strong em; a[!href]'
3956 * @property editables
3960 * The function used to obtain an accessibility label for the widget. It might be used to make
3961 * the widget labels as precise as possible, since it has access to the widget instance.
3963 * If not specified, the default implementation will use the {@link #pathName} or the main
3964 * {@link CKEDITOR.plugins.widget#element element} tag name.
3966 * @property {Function} getLabel
3970 * The widget name displayed in the elements path.
3972 * @property {String} pathName
3976 * If set to `true`, the widget's element will be covered with a transparent mask.
3977 * This will prevent its content from being clickable, which matters in case
3978 * of special elements like embedded Flash or iframes that generate a separate "context".
3980 * @property {Boolean} mask
3984 * If set to `true/false`, it will force the widget to be either an inline or a block widget.
3985 * If not set, the widget type will be determined from the widget element.
3987 * Widget type influences whether a block (`div`) or an inline (`span`) element is used
3990 * @property {Boolean} inline
3994 * The label for the widget toolbar button.
3996 * editor.widgets.add( 'simplebox', {
3997 * button: 'Create a simple box'
4000 * editor.widgets.add( 'simplebox', {
4001 * button: editor.lang.simplebox.title
4004 * @property {String} button
4008 * Whether widget should be draggable. Defaults to `true`.
4009 * If set to `false` drag handler will not be displayed when hovering widget.
4011 * @property {Boolean} draggable
4015 * Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes
4016 * defined in the widget styles. For example if your widget is upcasted from a simple `<div>`
4017 * element, then in order to make it styleable you can set:
4019 * editor.widgets.add( 'customWidget', {
4020 * upcast: function( element ) {
4021 * return element.name == 'div';
4026 * styleableElements: 'div'
4029 * Then, when the following style is defined:
4032 * name: 'Thick border', type: 'widget', widget: 'customWidget',
4033 * attributes: { 'class': 'thickBorder' }
4036 * a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}.
4038 * If you need to have more freedom when transforming widget style to allowed content rules,
4039 * you can use the {@link #styleToAllowedContentRules} callback.
4042 * @property {String} styleableElements
4046 * Function transforming custom widget's {@link CKEDITOR.style} instance into
4047 * {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static
4048 * {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter}
4049 * what HTML features should be enabled when allowing the given style.
4051 * In most cases, when style's classes just have to be added to element name(s) used by
4052 * the widget element, it is recommended to use simpler {@link #styleableElements} property.
4054 * In order to get parsed classes from the style definition you can use
4055 * {@link CKEDITOR.style.customHandlers.widget#getClassesArray}.
4057 * For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format),
4058 * to specify `match` validator, your implementation could look like this:
4060 * editor.widgets.add( 'customWidget', {
4063 * styleToAllowedContentRules: funciton( style ) {
4064 * // Retrieve classes defined in the style.
4065 * var classes = style.getClassesArray();
4067 * // Do something crazy - for example return allowed content rules in object format,
4068 * // with custom match property and propertiesOnly flag.
4071 * match: isWidgetElement,
4072 * propertiesOnly: true,
4080 * @property {Function} styleToAllowedContentRules
4081 * @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed.
4082 * @returns {CKEDITOR.filter.allowedContentRules}
4086 * This is an abstract class that describes the definition of a widget's nested editable.
4087 * It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object.
4089 * In the simplest case the definition is a string which is a CSS selector used to
4090 * find an element that will become a nested editable inside the widget. Note that
4091 * the widget element can be a nested editable, too.
4093 * In the more advanced case a definition is an object with a required `selector` property.
4098 * selector: 'div.content',
4099 * allowedContent: 'p strong em; a[!href]'
4103 * @class CKEDITOR.plugins.widget.nestedEditable.definition
4108 * The CSS selector used to find an element which will become a nested editable.
4110 * @property {String} selector
4114 * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
4115 * which will be used to limit the content allowed in this nested editable.
4116 * This option is similar to {@link CKEDITOR.config#allowedContent} and one can
4117 * use it to limit the editor features available in the nested editable.
4119 * @property {CKEDITOR.filter.allowedContentRules} allowedContent
4123 * Nested editable name displayed in elements path.
4125 * @property {String} pathName