The new dialog system in CKEditor is to be written from scratch. One of the key things in CKEditor is that it doesn't rely on HTML pages to run. Everything is created on the fly in JavaScript. In this way, we avoid limitations about cross domain serving of the editor code, like CDN solutions, and enhance the editor performance.
Contents
Framework
Dialogs will be controlled by the Dialog System Framework. This system offer features to define, modify and use dialogs within the editor. It brings uniform design among the dialogs, as well as advanced features with easy.
Dialog Manager
The Dialog Manager is the core of the dialog system. It introduces API features to open and manage dialogs in the editor. It also creates the basic structure of the dialog, the dialog framework, and loads the dialog contents. It manages dialog contents validation and provides the features to be used by the dialog definition object.
Main API
The dialog system runs under the CKEDITOR.dialog namespace. Its code is to be defined in the "dialog" plugin.
The dialog plugin extends the CKEDITOR.editor class, by adding the "CKEDITOR.editor.openDialog" function. This function is relative to the editor instance, because dialogs are sensitive to its configurations, as well as to its contents and selection state.
Structure
In a macro structure definition, a dialog is composed by the following pieces:
- Cover: a semi transparent element which is placed over the entire page to give the user a "modal dialog" effect. The user is required to close the dialog to access the page again.
- Main Dialog Structure: a floating element that holds the dialog structure. This structure defines several elements that are common to all dialogs:
- Title: a title space for the dialog.
- Close button: an element that closes the dialog on click.
- Buttons space: a space reserved to the main dialog buttons, like the "OK" and "Cancel" buttons.
- Resize handle: the element to be used to resize the dialog, if resizable.
- Contents: the space where the dialog contents are to be rendered.
- Tabs Space: the space where dialog tabs are to be rendered. Each tab fills the Contents space with different contents.
The following is a sample graphical representation of this structure:
+---------------------------------------+ | (Cover) | | +---------------------------+ | | | (Title) (x) | | | |---------------------------| | | | (Tabs) | | | |---------------------------| | | | (Contents) | | | | | | | | | | | | | | | | | | | | | | | | | | | |---------------------------| | | | (Buttons) (/)| | | +---------------------------+ | | | +---------------------------------------+ (x) = Close button (/) = Resize handle
The main dialog structure is defined by the editor theme, by its "buildDialog" function. This function returns the HTML for the main dialog structure, as well as the information needed by the dialog system to render it properly and fill its "placeholders".
Dialog Definition
When opening a dialog, the "Dialog Definition" is to be passed to the dialog system. This definition must be first "registered", to be then called later. The following function can be used to register it:
CKEDITOR.dialog.add( 'dialog name', function( editor ) { return { ... definition ... }; }); or CKEDITOR.dialog.add( 'dialog name', 'definition file path.js' );
Note that the definition can either be passed to the dialog system directly, or even be found in a separated JavaScript file. The dialog system will then use a callback system to download the file and load the definition. The definition file should have the add() function call to register the dialog again, just like the first of the above examples. The definitions are then cached and reused by all editor instances.
The definition is a function that receives an editor instance. This function is called every time a dialog is to be displayed. It then returns the definition object, which may be customized for that specific instance. The following is a compact representation of this object:
{ title: 'Dialog Title', resizable: CKEDITOR.DIALOG_RESIZE_NONE, minWidth: {number}, minHeight: {number}, buttons: [ ... ], contents: [ ... ], onOk: function() { ... }, onCancel: function() { ... }, onLoad: function( data ) { ... } }
Only the "title" and "contents" properties are required. Other properties have their default values.
Plugins or user code may at any time change the dialog definition, adding or removing things from it. It is enough to listen for the "dialogDefinition" event in the CKEDITOR object. The definition can also be retrieved at any time after it with the CKEDITOR.dialog.get function.
Dialogs are not resizable by default. If any of the resize constants are defined (CKEDITOR.DIALOG_RESIZE_HEIGHT | CKEDITOR.DIALOG_RESIZE_WIDHT | CKEDITOR.DIALOG_RESIZE_BOTH), the resize handle will be displayed. The size of the "contents" placeholder is changed when resizing, and the minWidth and minHeight properties are respected.
The "onOk" and "onCancel" are properties that can be set to functions to be called when one of the standard buttons is clicked. If the function explicitly returns "false", the dialog don't get closed.
The "onLoad" function is called when the dialog is loaded. An empty "data" object is passed to it. This object may get filled by the function with data to be reused by the dialog elements.
Buttons Definition
By default, the "Cancel" button is available on dialogs. The definition can instead contain a list of buttons to be displayed. The following is an example of the buttons property definition:
buttons: [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton, { id: 'unique name', type: 'button', label: 'Custom Button', title: 'Button description', accessKey: 'C', disabled: false, onClick: function() { // code on click }, } ]
Note that special objects are available for the "Ok" and "Cancel" buttons. Those objects are actually functions, which accept an object that overrides their default properties. For example, if you want the "Ok" button to be labeled "Go", just do this:
buttons: [ CKEDITOR.dialog.okButton( { label: 'Go' }), CKEDITOR.dialog.cancelButton ]
The "id" property can be defined on custom buttons. It makes it possible to retrieve a specific button at runtime to execute special functions on it, like enabling/disabling it, or even triggering its click.
We encourage defining an access key for each custom button. It should be a keyboard keystroke, which will be then combined with the CTRL key. The Ok and Cancel buttons can be fired with the ENTER and ESC key, respectively.
Contents Definition
The "contents" property is the most important in the definition. It defines the actual dialog contents. Note that this property is an array. Each entry in this array is an object. Each object will be represented by a "tab" in the dialog. No tabs are shown if just one object is defined.
The following is the compact representation of a single contents object:
{ id: 'unique name', label: 'Tab Label', title: 'Contents description', accessKey: 'Q', elements : [ ... ] }
Just like the buttons, here again we have id, label, title and access key to be defined for each content object. It is possible to activate one of the tabs by using the CTRL + access key. It is also possible to switching between the tabs by hitting CTRL+SPACE (forward) and CTRL+SHIFT+SPACE (backward).
The "elements" array contains the UI element definitions for this contents object. Details can be found in the next section.
UI Elements
The inner part of the dialog is finally filled with "UI elements". For example, the following is a possible definition for a simple text field:
{ id: 'width', type: 'text', label: editor.lang.width, title: editor.lang.widthTitle, default: '120', className: 'anyClass', style: 'someStyles', onLoad: function( data ) { ... } }
Most of the properties are optional, including "id" property, but it is recommended as it makes it easier to customize the dialog.
The "type" property is required. It indicates which "UI element builder" to use for this element. The dialog core code provides only two element types, "vbox" and "hbox", which are containers for other elements:
{ type: 'vbox|hbox', children: [ ... ] }
The "vbox" element will dispose all children elements vertically, one bellow the other. The "hbox" element will place then one next to the other horizontally instead.
The main contents placeholder itself is rendered by a vbox element object.
Defining Elements
Plugins or inline code can easily define new dialog UI elements to be used inside the dialogs. For that, "UI element builders" must be registered with the following call:
CKEDITOR.dialog.addUIElement( 'type name', { build : function( elementDefinition, output ) { // render the element to the output (Array) return { setValue : function( value, resetChanged ) { // set the element value, based on the elementBag // information. }, getValue : function() { // returns the current value. }, isChanged : function() { // returns a Boolean indicating that the value // has been changed. }, focus : function() { } }; } });
The elementDefinition parameter is the exact element object taken from the dialog definition. If the element handles children elements, it is also responsible for calling the build function for those elements.
The build function returns an object, which will be used to handle this element at runtime.
Elements Plugin
The editor comes with the "dialogui" plugin, which defines the most common dialog UI elements types:
{ type : 'text', label : 'Label Text', title : 'Field description', default : '...', className: '...', style: '...', required: true/false, validate: function() { ... } } { type : 'select', label : 'Label Text', title : 'Field description', default : '...', className: '...', style: '...', required: true/false, validate: function() { ... } items: [ [ 'Label 1', 'Value' ], [ 'Label 2' ] // Value is 'Label 2' ] } { type : 'radio', label : 'Label Text', title : 'Field description', default : '...', className: '...', style: '...', required: true/false, validate: function() { ... }, items: [ [ 'Label 1', 'Value', 'Title' ], [ 'Label 2' ] // Value and Title are 'Label 2' ] } { type : 'checkbox', label : 'Label Text', title : 'Field description', checked : true|false, className: '...', style: '...', required: true/false, validate: function() { ... } } { type : 'button', label : 'Label Text', title : 'Field description', className: '...', style: '...', onClick : function(){ ... } } { type : 'html', className: '...', style: '...', html: 'Some <b>HTML</b>' }
Workflow
When loading the dialog:
- editor.openDialog( 'dialogName' ) is called.
- The dialog system shows the cover (hourglass icon).
- The dialog definition for 'dialogName' is loaded.
- If needed, the dialog definition is downloaded.
- editor.theme.buildDialog() is called returning the main structure HTML
- The main structure HTML is injected into the DOM (hidden).
- Based on the dialog definition, the dialog system fills the dialog title, enables the resize handler and creates the dialog buttons.
- The "onLoad" function is called for the dialog definition.
- Tabs are created for the content objects.
- The "onLoad" is called for each UI elements defined (recursively on children elements also).
- All elements in the content object to be displayed are rendered.
- The resulting rendered HTML is injected into the DOM.
- The dialog is displayed (normal icon).
When hitting Ok:
- The "validate" function is called for each element. If any of them is invalid, the focus is moved to it, and a standard UI alert is displayed.
- The "onOk" function is called.
- If "onOk" doesn't return "false", the dialog gets destroyed.
When hitting Cancel:
- The "isChanged" function is called for each element. If any of then has been changed, a standard warning message is displayed ("Are you sure blah blah...?").
- The "onCancel" function is called.
- If "onCancel" doesn't return "false", the dialog gets destroyed.
Dialog Definition API Extensions
Once registered, a dialog definition gets "extended" by the dialog system. A few API functions are added to it, making it possible to manipulate it:
- getContents( 'id' ): gets a specific contents object, by id.
- getButton( 'id' ): gets a specific button object, by id.
- addContents( { ... contents object } [, nextSiblingId] ): adds a content object to the definitions. If the second parameter is specify, the added object is placed before the object with that id, otherwise it is appended to the end.
- addButton( { ... button object } [,nextSiblingId] ): the same exact behavior of addContents, but for the buttons.
- removeContents( 'id' ): removes one of the contents objects.
- removeButton( 'id' ): removes one of the buttons.
Contents objects are extended with the following functions:
- get( 'id' ): gets one of the UI elements by id.
- add( { ... element definition } [,nextSiblingId] ): just like addContents, it adds UI elements to the list.
- remove( 'id' ): removes one of the UI elements.
Button object are extended with the following functions:
- enable(): enables the button.
- disable(): disables the button.