// Our dialog definition.
CKEDITOR.dialog.add( 'bootstrapTabsDialog', function( editor ) {
return {
// Basic properties of the dialog window: title, minimum size.
// dialog window title
title: editor.lang.bootstrapTabs.dialogTitle,
// dialog window size for .cke_dialog_contents_body
minWidth: 310,
minHeight: 280,
// Dialog window content definition.
// An array of objects that defines tabs.
// Each object that defines a tab has an array of elements (e.g., form fields).
contents: [
{
// Definition of the Basic Settings dialog tab.
id: 'tab-basic',
label: editor.lang.bootstrapTabs.tabBasicLabel,
// The tab content.
elements: [
// Dialog window UI element: HTML code field.
// http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.ui.dialog.html.html
{
type: 'html',
// HTML code to be shown inside the field.
// http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.ui.dialog.html.html#constructor
html: editor.lang.bootstrapTabs.infoHtml
},
{
// Text input field for the number of tabs.
// Possible valid values:
// hbox, vbox, labeled, button, checkbox, file, fileButton, html, radio, selectElement, textInput, textarea
type: 'text',
id: 'tab-set-title',
label: editor.lang.bootstrapTabs.tabSetTitleLabel,
// Validation for empty values.
validate: CKEDITOR.dialog.validate.notEmpty( editor.lang.bootstrapTabs.invalidTabSetTitle ),
setup: function( element ) {
var tabsElement = element,
tabSetTitle = element.data( 'tab-set-title' );
this.setValue( tabSetTitle );
}
},
{
// Select input field for the number of tabs.
// Possible valid values:
// hbox, vbox, labeled, button, checkbox, file, fileButton, html, radio, selectElement, textInput, textarea
type: 'select',
id: 'number-of-tabs',
label: editor.lang.bootstrapTabs.numberOfTabsLabel,
"default": 4,
items: [ ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9'] ],
// Validation for empty values.
validate: CKEDITOR.dialog.validate.notEmpty( editor.lang.bootstrapTabs.invalidNumberOfTabs ),
setup: function( element ) {
var tabsElement = element,
oldNumberOfTabs = tabsElement.find( '.nav.nav-tabs li a.tab-link' ).count();
this.setValue( oldNumberOfTabs );
}
},
{
type: 'html',
// HTML code to be shown inside the field.
// http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.ui.dialog.html.html#constructor
html: '
'
},
{
type: "select",
id: "tab-to-remove",
label: editor.lang.bootstrapTabs.removeTabLabel,
"default": editor.lang.bootstrapTabs.removeTabDefault,
items: [ [ editor.lang.bootstrapTabs.removeTabDefault ] ],
setup: function(element) {
// clear the array of options (because repeated dialog calls will accumulate options).
this.clear().add( editor.lang.bootstrapTabs.removeTabDefault );
// iterate over the tabs, adding each tab's link text as an option for select field
var tabElements = element.findOne( '.nav.nav-tabs').find('li');
for (i = 0; i < tabElements.count(); i ++) {
this.add( tabElements.getItem(i).findOne('a.tab-link').getText() );
}
}
}
]
}
], // end contents
onShow: function() {
// The code that will be executed when a dialog window is loaded.
// To get to the element that is selected by the user (either highlighted or just having the caret inside),
// we need to use the editor.getSelection method.
var selection = editor.getSelection();
// We will also use the selection.getStartElement method to get the element in which the selection starts,
// and assign it to the element variable.
var element = selection.getStartElement();
if ( element ) {
ascendant = element.getAscendant( function(element) {
// Suddenly, getAscendant traverses up all CKEDITOR.dom.element instances to CKEDITOR.dom.document
// Return false since this indicates we have not found a tab set as an ascendant.
if (element instanceof CKEDITOR.dom.document) {
return false;
}
return element.hasClass('bootstrap-tabs');
});
}
// If there is asendant (the selection is part of a tab set)
if ( ascendant ) {
// We are not inserting a new tab set.
this.insertMode = false;
// Set the element to be passed to setup functions.
element = ascendant;
} else {
// We are inserting a new tab set.
this.insertMode = true;
}
// element is always a CKEDITOR.dom.element ?
this.element = element;
// The onShow function will finish with a call to the setupContent method that will
// invoke the setup functions for the element. Each parameter that will be passed
// on to the setupContent function will also be passed on to the setup functions.
if ( !this.insertMode )
this.setupContent( element );
}, // end onShow
// This method is invoked once a user clicks the OK button, confirming the dialog.
onOk: function() {
// The context of this function is the dialog object itself.
// http://docs.ckeditor.com/#!/api/CKEDITOR.dialog
var dialog = this;
// Dialog input data.
var numberOfTabs = dialog.getValueOf( 'tab-basic', 'number-of-tabs' ),
tabSetTitle = dialog.getValueOf( 'tab-basic', 'tab-set-title' ),
tabToRemove = dialog.getValueOf( 'tab-basic', 'tab-to-remove' );
if (!this.insertMode) { // If we're editing an existing tabsElement ...
// The tabsElement is the element that was found by the context menu via onShow.
var tabsElement = this.element,
oldNumberOfTabs = tabsElement.find( '.nav.nav-tabs li a.tab-link' ).count(), // actual number of tabs that exist
// This is how to get the old title that is stored on containing div.
// oldTabSetTitle is not currently used, but we may want to update all references to the
// tabSetTitle in future revisions of this plugin via oldTabSetTitle.
oldTabSetTitle = tabsElement.data( 'tab-set-title' );
var navTabsElement = tabsElement.findOne( 'ul.nav.nav-tabs' ),
tabContentElement = tabsElement.findOne( 'div.tab-content' );
var tabElements = navTabsElement.find('li'),
tabPanelElements = tabContentElement.find( 'div.tab-pane' );
for (i = 0; i < tabElements.count(); i ++) {
// tabToRemove always holds a value, even if only the default value.
if ( tabToRemove === tabElements.getItem(i).findOne('a.tab-link').getText() ) {
// Remove the tab that was specified.
// Only remove the tab and its content if we konw that the number of tabs
// is the same as the number of tab contents
// This is a simple precaution to removing content from a tab element that has become "corrupt".
if ( tabElements.count() === tabPanelElements.count() ) {
tabElements
.getItem(i).remove();
tabPanelElements
.getItem(i).remove();
}
}
}
if ( numberOfTabs > oldNumberOfTabs ) { // If the user has increased the number of tabs,
// Find the active tab and tab panel, then remove the active class.
navTabsElement.findOne('.active').removeClass('active');
tabContentElement.findOne('.active').removeClass('active');
for ( var i = oldNumberOfTabs + 1; i <= numberOfTabs; i++ ) { // create and append the difference in tabs.
appendTabToElement(editor, dialog, tabsElement, numberOfTabs, i);
}
}
} else { // this.insertMode (we're inserting a new set of tabs).
// Define various html and elements of the bootstrap tabs skeleton markup (http://getbootstrap.com/javascript/#tabs-examples).
var tabsHtml = '',
tabsElement = CKEDITOR.dom.element.createFromHtml( tabsHtml );
// Create and append numberOfTabs tabs to the tabsElement, complete with default content and tab names.
for ( var i = 1; i <= numberOfTabs; i++ ) {
appendTabToElement(editor, dialog, tabsElement, numberOfTabs, i);
};
// Finally, insert the tabsElement into the editor at the caret position.
editor.insertElement( tabsElement );
} // end for ( var i = 1; i <= numberOfTabs; i++ )
// Set the (new) title on the tabsElement containing div.
tabsElement.data( 'tab-set-title', tabSetTitle);
} // end onOK
}; // return dialog definition object
}); // CKEDITOR.dialog.add( 'bootstrapTabsDialog', function( editor ) {
function appendTabToElement(editor, dialog, tabsElement, numberOfTabs, i) {
// Get the new tab-set-title from the dialog input.
var tabSetTitle = dialog.getValueOf( 'tab-basic', 'tab-set-title' );
// Text for the tab name and dynamically assigned attributes for the tab and tab-panels
var tabName = 'Tab ' + i,
// prepend the tab tabSetTitle for tab id uniqueness and replace non-alpha-numeric and whitespace with a dash
// 'TeSt Title StRing !! .* !@# $$ %^& () __-- += with non-alpha-num'.replace(/(\W+|\s+|_|-+)/g, '-').replace(/-+/g, '-')
tabIdentifier = (tabSetTitle + ' ' + tabName).replace(/(\W+|\s+|_|-+)/g, '-').replace(/-+/g, '-').toLowerCase();
// This div template contains the content that a user will edit.
// Without this extra div inide the tab-pane, editing the tab contents produces unexpected results.
var tabPanelContentHtml = '' + tabName + ' Content
';
// Template html for a tab and tabPanel (http://getbootstrap.com/javascript/#tabs-examples).
var tabHtml = '' + tabName + ' Name';
var tabPanelHtml = '' + tabPanelContentHtml + '
';
var tabElement = new CKEDITOR.dom.element.createFromHtml( tabHtml ),
tabPanelElement = new CKEDITOR.dom.element.createFromHtml( tabPanelHtml );
// add the active class to the last tab of the element
if (i == numberOfTabs) {
tabElement.addClass( 'active' );
tabPanelElement.addClass( 'active' );
}
// From the tabsElement, find the appropriate place to insert a new tab and its tab content.
var navTabsElement = tabsElement.findOne( 'ul.nav.nav-tabs' ),
tabContentElement = tabsElement.findOne( 'div.tab-content' );
// Append number-of-tabs nav-tabs (the clickable tabs in Bootstrap tabs)
navTabsElement.append( tabElement );
// Append number-of-tabs tab-panels (the area where content is displayed in Bootstrap tabs)
tabContentElement.append( tabPanelElement );
}