bootstrapTabs.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Our dialog definition.
  2. CKEDITOR.dialog.add( 'bootstrapTabsDialog', function( editor ) {
  3. return {
  4. // Basic properties of the dialog window: title, minimum size.
  5. // dialog window title
  6. title: editor.lang.bootstrapTabs.dialogTitle,
  7. // dialog window size for .cke_dialog_contents_body
  8. minWidth: 310,
  9. minHeight: 280,
  10. // Dialog window content definition.
  11. // An array of objects that defines tabs.
  12. // Each object that defines a tab has an array of elements (e.g., form fields).
  13. contents: [
  14. {
  15. // Definition of the Basic Settings dialog tab.
  16. id: 'tab-basic',
  17. label: editor.lang.bootstrapTabs.tabBasicLabel,
  18. // The tab content.
  19. elements: [
  20. // Dialog window UI element: HTML code field.
  21. // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.ui.dialog.html.html
  22. {
  23. type: 'html',
  24. // HTML code to be shown inside the field.
  25. // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.ui.dialog.html.html#constructor
  26. html: editor.lang.bootstrapTabs.infoHtml
  27. },
  28. {
  29. // Text input field for the number of tabs.
  30. // Possible valid values:
  31. // hbox, vbox, labeled, button, checkbox, file, fileButton, html, radio, selectElement, textInput, textarea
  32. type: 'text',
  33. id: 'tab-set-title',
  34. label: editor.lang.bootstrapTabs.tabSetTitleLabel,
  35. // Validation for empty values.
  36. validate: CKEDITOR.dialog.validate.notEmpty( editor.lang.bootstrapTabs.invalidTabSetTitle ),
  37. setup: function( element ) {
  38. var tabsElement = element,
  39. tabSetTitle = element.data( 'tab-set-title' );
  40. this.setValue( tabSetTitle );
  41. }
  42. },
  43. {
  44. // Select input field for the number of tabs.
  45. // Possible valid values:
  46. // hbox, vbox, labeled, button, checkbox, file, fileButton, html, radio, selectElement, textInput, textarea
  47. type: 'select',
  48. id: 'number-of-tabs',
  49. label: editor.lang.bootstrapTabs.numberOfTabsLabel,
  50. "default": 4,
  51. items: [ ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9'] ],
  52. // Validation for empty values.
  53. validate: CKEDITOR.dialog.validate.notEmpty( editor.lang.bootstrapTabs.invalidNumberOfTabs ),
  54. setup: function( element ) {
  55. var tabsElement = element,
  56. oldNumberOfTabs = tabsElement.find( '.nav.nav-tabs li a.tab-link' ).count();
  57. this.setValue( oldNumberOfTabs );
  58. }
  59. },
  60. {
  61. type: 'html',
  62. // HTML code to be shown inside the field.
  63. // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.ui.dialog.html.html#constructor
  64. html: '<hr style="display: block;position: relative;padding: 0;margin: 8px auto;width: 100%;max-height: 0;font-size: 1px;line-height: 0;clear: both;border: none;border-top: 1px solid #aaaaaa;border-bottom: 1px solid #ffffff;"/>'
  65. },
  66. {
  67. type: "select",
  68. id: "tab-to-remove",
  69. label: editor.lang.bootstrapTabs.removeTabLabel,
  70. "default": editor.lang.bootstrapTabs.removeTabDefault,
  71. items: [ [ editor.lang.bootstrapTabs.removeTabDefault ] ],
  72. setup: function(element) {
  73. // clear the array of options (because repeated dialog calls will accumulate options).
  74. this.clear().add( editor.lang.bootstrapTabs.removeTabDefault );
  75. // iterate over the tabs, adding each tab's link text as an option for select field
  76. var tabElements = element.findOne( '.nav.nav-tabs').find('li');
  77. for (i = 0; i < tabElements.count(); i ++) {
  78. this.add( tabElements.getItem(i).findOne('a.tab-link').getText() );
  79. }
  80. }
  81. }
  82. ]
  83. }
  84. ], // end contents
  85. onShow: function() {
  86. // The code that will be executed when a dialog window is loaded.
  87. // To get to the element that is selected by the user (either highlighted or just having the caret inside),
  88. // we need to use the editor.getSelection method.
  89. var selection = editor.getSelection();
  90. // We will also use the selection.getStartElement method to get the element in which the selection starts,
  91. // and assign it to the element variable.
  92. var element = selection.getStartElement();
  93. if ( element ) {
  94. ascendant = element.getAscendant( function(element) {
  95. // Suddenly, getAscendant traverses up all CKEDITOR.dom.element instances to CKEDITOR.dom.document
  96. // Return false since this indicates we have not found a tab set as an ascendant.
  97. if (element instanceof CKEDITOR.dom.document) {
  98. return false;
  99. }
  100. return element.hasClass('bootstrap-tabs');
  101. });
  102. }
  103. // If there is asendant (the selection is part of a tab set)
  104. if ( ascendant ) {
  105. // We are not inserting a new tab set.
  106. this.insertMode = false;
  107. // Set the element to be passed to setup functions.
  108. element = ascendant;
  109. } else {
  110. // We are inserting a new tab set.
  111. this.insertMode = true;
  112. }
  113. // element is always a CKEDITOR.dom.element ?
  114. this.element = element;
  115. // The onShow function will finish with a call to the setupContent method that will
  116. // invoke the setup functions for the element. Each parameter that will be passed
  117. // on to the setupContent function will also be passed on to the setup functions.
  118. if ( !this.insertMode )
  119. this.setupContent( element );
  120. }, // end onShow
  121. // This method is invoked once a user clicks the OK button, confirming the dialog.
  122. onOk: function() {
  123. // The context of this function is the dialog object itself.
  124. // http://docs.ckeditor.com/#!/api/CKEDITOR.dialog
  125. var dialog = this;
  126. // Dialog input data.
  127. var numberOfTabs = dialog.getValueOf( 'tab-basic', 'number-of-tabs' ),
  128. tabSetTitle = dialog.getValueOf( 'tab-basic', 'tab-set-title' ),
  129. tabToRemove = dialog.getValueOf( 'tab-basic', 'tab-to-remove' );
  130. if (!this.insertMode) { // If we're editing an existing tabsElement ...
  131. // The tabsElement is the element that was found by the context menu via onShow.
  132. var tabsElement = this.element,
  133. oldNumberOfTabs = tabsElement.find( '.nav.nav-tabs li a.tab-link' ).count(), // actual number of tabs that exist
  134. // This is how to get the old title that is stored on containing div.
  135. // oldTabSetTitle is not currently used, but we may want to update all references to the
  136. // tabSetTitle in future revisions of this plugin via oldTabSetTitle.
  137. oldTabSetTitle = tabsElement.data( 'tab-set-title' );
  138. var navTabsElement = tabsElement.findOne( 'ul.nav.nav-tabs' ),
  139. tabContentElement = tabsElement.findOne( 'div.tab-content' );
  140. var tabElements = navTabsElement.find('li'),
  141. tabPanelElements = tabContentElement.find( 'div.tab-pane' );
  142. for (i = 0; i < tabElements.count(); i ++) {
  143. // tabToRemove always holds a value, even if only the default value.
  144. if ( tabToRemove === tabElements.getItem(i).findOne('a.tab-link').getText() ) {
  145. // Remove the tab that was specified.
  146. // Only remove the tab and its content if we konw that the number of tabs
  147. // is the same as the number of tab contents
  148. // This is a simple precaution to removing content from a tab element that has become "corrupt".
  149. if ( tabElements.count() === tabPanelElements.count() ) {
  150. tabElements
  151. .getItem(i).remove();
  152. tabPanelElements
  153. .getItem(i).remove();
  154. }
  155. }
  156. }
  157. if ( numberOfTabs > oldNumberOfTabs ) { // If the user has increased the number of tabs,
  158. // Find the active tab and tab panel, then remove the active class.
  159. navTabsElement.findOne('.active').removeClass('active');
  160. tabContentElement.findOne('.active').removeClass('active');
  161. for ( var i = oldNumberOfTabs + 1; i <= numberOfTabs; i++ ) { // create and append the difference in tabs.
  162. appendTabToElement(editor, dialog, tabsElement, numberOfTabs, i);
  163. }
  164. }
  165. } else { // this.insertMode (we're inserting a new set of tabs).
  166. // Define various html and elements of the bootstrap tabs skeleton markup (http://getbootstrap.com/javascript/#tabs-examples).
  167. var tabsHtml = '<div class="bootstrap-tabs"><ul class="nav nav-tabs" role="tablist"><!-- add tabs here --></ul><div class="tab-content"><!-- add tab panels here --></div></div>',
  168. tabsElement = CKEDITOR.dom.element.createFromHtml( tabsHtml );
  169. // Create and append numberOfTabs tabs to the tabsElement, complete with default content and tab names.
  170. for ( var i = 1; i <= numberOfTabs; i++ ) {
  171. appendTabToElement(editor, dialog, tabsElement, numberOfTabs, i);
  172. };
  173. // Finally, insert the tabsElement into the editor at the caret position.
  174. editor.insertElement( tabsElement );
  175. } // end for ( var i = 1; i <= numberOfTabs; i++ )
  176. // Set the (new) title on the tabsElement containing div.
  177. tabsElement.data( 'tab-set-title', tabSetTitle);
  178. } // end onOK
  179. }; // return dialog definition object
  180. }); // CKEDITOR.dialog.add( 'bootstrapTabsDialog', function( editor ) {
  181. function appendTabToElement(editor, dialog, tabsElement, numberOfTabs, i) {
  182. // Get the new tab-set-title from the dialog input.
  183. var tabSetTitle = dialog.getValueOf( 'tab-basic', 'tab-set-title' );
  184. // Text for the tab name and dynamically assigned attributes for the tab and tab-panels
  185. var tabName = 'Tab ' + i,
  186. // prepend the tab tabSetTitle for tab id uniqueness and replace non-alpha-numeric and whitespace with a dash
  187. // 'TeSt Title StRing !! .* !@# $$ %^& () __-- += with non-alpha-num'.replace(/(\W+|\s+|_|-+)/g, '-').replace(/-+/g, '-')
  188. tabIdentifier = (tabSetTitle + ' ' + tabName).replace(/(\W+|\s+|_|-+)/g, '-').replace(/-+/g, '-').toLowerCase();
  189. // This div template contains the content that a user will edit.
  190. // Without this extra div inide the tab-pane, editing the tab contents produces unexpected results.
  191. var tabPanelContentHtml = '<div class="tab-pane-content">' + tabName + ' Content</div>';
  192. // Template html for a tab and tabPanel (http://getbootstrap.com/javascript/#tabs-examples).
  193. var tabHtml = '<li role="presentation"><a class="tab-link" href="#' + tabIdentifier + '" aria-controls="' + tabIdentifier + '" role="tab" data-toggle="tab">' + tabName + ' Name</a></li>';
  194. var tabPanelHtml = '<div role="tabpanel" class="tab-pane" id="' + tabIdentifier + '">' + tabPanelContentHtml + '</div>';
  195. var tabElement = new CKEDITOR.dom.element.createFromHtml( tabHtml ),
  196. tabPanelElement = new CKEDITOR.dom.element.createFromHtml( tabPanelHtml );
  197. // add the active class to the last tab of the element
  198. if (i == numberOfTabs) {
  199. tabElement.addClass( 'active' );
  200. tabPanelElement.addClass( 'active' );
  201. }
  202. // From the tabsElement, find the appropriate place to insert a new tab and its tab content.
  203. var navTabsElement = tabsElement.findOne( 'ul.nav.nav-tabs' ),
  204. tabContentElement = tabsElement.findOne( 'div.tab-content' );
  205. // Append number-of-tabs nav-tabs (the clickable tabs in Bootstrap tabs)
  206. navTabsElement.append( tabElement );
  207. // Append number-of-tabs tab-panels (the area where content is displayed in Bootstrap tabs)
  208. tabContentElement.append( tabPanelElement );
  209. }