[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/ -> moodle-atto_table-button.js (source)

   1  YUI.add('moodle-atto_table-button', function (Y, NAME) {
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * @package    atto_table
  20   * @copyright  2013 Damyon Wiese  <damyon@moodle.com>
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  /**
  25   * @module moodle-atto_table-button
  26   */
  27  
  28  /**
  29   * Atto text editor table plugin.
  30   *
  31   * @namespace M.atto_table
  32   * @class Button
  33   * @extends M.editor_atto.EditorPlugin
  34   */
  35  
  36  var COMPONENT = 'atto_table',
  37      DEFAULT = {
  38          BORDERSTYLE: 'none',
  39          BORDERWIDTH: '1'
  40      },
  41      DIALOGUE = {
  42          WIDTH: '480px'
  43      },
  44      TEMPLATE = '' +
  45          '<form class="{{CSS.FORM}}">' +
  46              '<label for="{{elementid}}_atto_table_caption">{{get_string "caption" component}}</label>' +
  47              '<input class="{{CSS.CAPTION}} fullwidth" id="{{elementid}}_atto_table_caption" required />' +
  48              '<br/>' +
  49              '<br/>' +
  50              '<label for="{{elementid}}_atto_table_captionposition" class="sameline">' +
  51              '{{get_string "captionposition" component}}</label>' +
  52              '<select class="{{CSS.CAPTIONPOSITION}}" id="{{elementid}}_atto_table_captionposition">' +
  53                  '<option value=""></option>' +
  54                  '<option value="top">{{get_string "top" "editor"}}</option>' +
  55                  '<option value="bottom">{{get_string "bottom" "editor"}}</option>' +
  56              '</select>' +
  57              '<br/>' +
  58              '<label for="{{elementid}}_atto_table_headers" class="sameline">{{get_string "headers" component}}</label>' +
  59              '<select class="{{CSS.HEADERS}}" id="{{elementid}}_atto_table_headers">' +
  60                  '<option value="columns">{{get_string "columns" component}}' + '</option>' +
  61                  '<option value="rows">{{get_string "rows" component}}' + '</option>' +
  62                  '<option value="both">{{get_string "both" component}}' + '</option>' +
  63              '</select>' +
  64              '<br/>' +
  65              '{{#if nonedit}}' +
  66                  '<label for="{{elementid}}_atto_table_rows" class="sameline">{{get_string "numberofrows" component}}</label>' +
  67                  '<input class="{{CSS.ROWS}}" type="number" value="3" ' +
  68                  'id="{{elementid}}_atto_table_rows" size="8" min="1" max="50"/>' +
  69                  '<br/>' +
  70                  '<label for="{{elementid}}_atto_table_columns" ' +
  71                  'class="sameline">{{get_string "numberofcolumns" component}}</label>' +
  72                  '<input class="{{CSS.COLUMNS}}" type="number" value="3" id="{{elementid}}_atto_table_columns"' +
  73                  'size="8" min="1" max="20"/>' +
  74                  '<br/>' +
  75              '{{/if}}' +
  76              '{{#if allowStyling}}' +
  77                  '<fieldset>' +
  78                  '<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
  79                  '{{#if allowBorders}}' +
  80                      '<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
  81                      '<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
  82                          '<option value="default">{{get_string "themedefault" component}}' + '</option>' +
  83                          '<option value="outer">{{get_string "outer" component}}' + '</option>' +
  84                          '<option value="all">{{get_string "all" component}}' + '</option>' +
  85                      '</select>' +
  86                      '<br>' +
  87                      '<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
  88                      '{{get_string "borderstyles" component}}</label>' +
  89                      '<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
  90                          '{{#each borderStyles}}' +
  91                              '<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
  92                          '{{/each}}' +
  93                      '</select>' +
  94                      '<br>' +
  95                      '<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
  96                      '{{get_string "bordersize" component}}</label>' +
  97                      '<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
  98                      'type="number" value="1" size="8" min="1" max="50"/>' +
  99                      '<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
 100                      '<br>' +
 101                      '<label for="{{elementid}}_atto_table_bordercolour" class="sameline">' +
 102                      '{{get_string "bordercolour" component}}</label>' +
 103                      '<div id="{{elementid}}_atto_table_bordercolour"' +
 104                      'class="{{CSS.BORDERCOLOUR}} {{CSS.AVAILABLECOLORS}}" size="1">' +
 105                          '<label class="hideborder" for="{{../elementid}}_atto_table_bordercolour_-1"' +
 106                          'style="background-color:transparent;color:transparent">' +
 107  
 108                              '<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
 109                              'type="radio" name="borderColour" value="none" checked="checked"' +
 110                              'title="{{get_string "themedefault" component}}"></input>' +
 111  
 112                              '{{get_string "themedefault" component}}' +
 113                          '</label>' +
 114                          '{{#each availableColours}}' +
 115                              '<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
 116                              'style="background-color:{{this}};color:{{this}}">' +
 117  
 118                                  '<input id="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
 119                                  'type="radio" name="borderColour" value="' + '{{this}}' + '" title="{{this}}">' +
 120  
 121                                  '{{this}}' +
 122                              '</label>' +
 123                          '{{/each}}' +
 124                      '</div>' +
 125                      '<br>' +
 126                  '{{/if}}' +
 127                  '{{#if allowBackgroundColour}}' +
 128                      '<label for="{{elementid}}_atto_table_backgroundcolour" class="sameline">' +
 129                      '{{get_string "backgroundcolour" component}}</label>' +
 130                      '<div id="{{elementid}}_atto_table_backgroundcolour"' +
 131                      'class="{{CSS.BACKGROUNDCOLOUR}} {{CSS.AVAILABLECOLORS}}" size="1">' +
 132                          '<label class="hideborder" for="{{../elementid}}_atto_table_backgroundcolour_-1"' +
 133                          'style="background-color:transparent;color:transparent">' +
 134  
 135                              '<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
 136                              'type="radio" name="backgroundColour" value="none" checked="checked"' +
 137                              'title="{{get_string "themedefault" component}}"></input>' +
 138  
 139                              '{{get_string "themedefault" component}}' +
 140                          '</label>' +
 141  
 142                          '{{#each availableColours}}' +
 143                              '<label for="{{../elementid}}_atto_table_backgroundcolour_{{@index}}"' +
 144                              'style="background-color:{{this}};color:{{this}}">' +
 145  
 146                                  '<input id="{{../elementid}}_atto_table_backgroundcolour_{{@index}}"' +
 147                                  'type="radio" name="backgroundColour" value="' + '{{this}}' + '" title="{{this}}">' +
 148  
 149                                  '{{this}}' +
 150                              '</label>' +
 151                          '{{/each}}' +
 152                      '</div>' +
 153                      '<br>' +
 154                  '{{/if}}' +
 155                  '{{#if allowWidth}}' +
 156                      '<label for="{{elementid}}_atto_table_width" class="sameline">' +
 157                      '{{get_string "width" component}}</label>' +
 158                      '<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" ' +
 159                          'type="number" min="0" max="100"/>' +
 160                      '<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
 161                      '<br>' +
 162                  '{{/if}}' +
 163                  '</fieldset>' +
 164              '{{/if}}' +
 165              '<div class="mdl-align">' +
 166              '<br/>' +
 167              '{{#if edit}}' +
 168                  '<button class="submit" type="submit">{{get_string "updatetable" component}}</button>' +
 169              '{{/if}}' +
 170              '{{#if nonedit}}' +
 171                  '<button class="submit" type="submit">{{get_string "createtable" component}}</button>' +
 172              '{{/if}}' +
 173              '</div>' +
 174          '</form>',
 175      CSS = {
 176          CAPTION: 'caption',
 177          CAPTIONPOSITION: 'captionposition',
 178          HEADERS: 'headers',
 179          ROWS: 'rows',
 180          COLUMNS: 'columns',
 181          SUBMIT: 'submit',
 182          FORM: 'atto_form',
 183          BORDERS: 'borders',
 184          BORDERSIZE: 'bordersize',
 185          BORDERSIZEUNIT: 'px',
 186          BORDERCOLOUR: 'bordercolour',
 187          BORDERSTYLE: 'borderstyle',
 188          BACKGROUNDCOLOUR: 'backgroundcolour',
 189          WIDTH: 'customwidth',
 190          WIDTHUNIT: '%',
 191          AVAILABLECOLORS: 'availablecolors',
 192          COLOURROW: 'colourrow'
 193      },
 194      SELECTORS = {
 195          CAPTION: '.' + CSS.CAPTION,
 196          CAPTIONPOSITION: '.' + CSS.CAPTIONPOSITION,
 197          HEADERS: '.' + CSS.HEADERS,
 198          ROWS: '.' + CSS.ROWS,
 199          COLUMNS: '.' + CSS.COLUMNS,
 200          SUBMIT: '.' + CSS.SUBMIT,
 201          BORDERS: '.' + CSS.BORDERS,
 202          BORDERSIZE: '.' + CSS.BORDERSIZE,
 203          BORDERCOLOURS: '.' + CSS.BORDERCOLOUR + ' input[name="borderColour"]',
 204          SELECTEDBORDERCOLOUR: '.' + CSS.BORDERCOLOUR + ' input[name="borderColour"]:checked',
 205          BORDERSTYLE: '.' + CSS.BORDERSTYLE,
 206          BACKGROUNDCOLOURS: '.' + CSS.BACKGROUNDCOLOUR + ' input[name="backgroundColour"]',
 207          SELECTEDBACKGROUNDCOLOUR: '.' + CSS.BACKGROUNDCOLOUR + ' input[name="backgroundColour"]:checked',
 208          FORM: '.atto_form',
 209          WIDTH: '.' + CSS.WIDTH,
 210          AVAILABLECOLORS: '.' + CSS.AVAILABLECOLORS
 211      };
 212  
 213  Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
 214  
 215      /**
 216       * A reference to the current selection at the time that the dialogue
 217       * was opened.
 218       *
 219       * @property _currentSelection
 220       * @type Range
 221       * @private
 222       */
 223      _currentSelection: null,
 224  
 225      /**
 226       * The contextual menu that we can open.
 227       *
 228       * @property _contextMenu
 229       * @type M.editor_atto.Menu
 230       * @private
 231       */
 232      _contextMenu: null,
 233  
 234      /**
 235       * The last modified target.
 236       *
 237       * @property _lastTarget
 238       * @type Node
 239       * @private
 240       */
 241      _lastTarget: null,
 242  
 243      /**
 244       * The list of menu items.
 245       *
 246       * @property _menuOptions
 247       * @type Object
 248       * @private
 249       */
 250      _menuOptions: null,
 251  
 252      initializer: function() {
 253          this.addButton({
 254              icon: 'e/table',
 255              callback: this._displayTableEditor,
 256              tags: 'table'
 257          });
 258          // Disable mozilla table controls.
 259          if (Y.UA.gecko) {
 260              document.execCommand("enableInlineTableEditing", false, false);
 261              document.execCommand("enableObjectResizing", false, false);
 262          }
 263      },
 264  
 265      /**
 266       * Display the table tool.
 267       *
 268       * @method _displayDialogue
 269       * @private
 270       */
 271      _displayDialogue: function() {
 272          // Store the current cursor position.
 273          this._currentSelection = this.get('host').getSelection();
 274  
 275          if (this._currentSelection !== false && (!this._currentSelection.collapsed)) {
 276              var dialogue = this.getDialogue({
 277                  headerContent: M.util.get_string('createtable', COMPONENT),
 278                  focusAfterHide: true,
 279                  focusOnShowSelector: SELECTORS.CAPTION,
 280                  width: DIALOGUE.WIDTH
 281              });
 282  
 283              // Set the dialogue content, and then show the dialogue.
 284              dialogue.set('bodyContent', this._getDialogueContent(false))
 285                      .show();
 286  
 287              this._updateAvailableSettings();
 288          }
 289      },
 290  
 291      /**
 292       * Display the appropriate table editor.
 293       *
 294       * If the current selection includes a table, then we show the
 295       * contextual menu, otherwise show the table creation dialogue.
 296       *
 297       * @method _displayTableEditor
 298       * @param {EventFacade} e
 299       * @private
 300       */
 301      _displayTableEditor: function(e) {
 302          var cell = this._getSuitableTableCell();
 303          if (cell) {
 304              // Add the cell to the EventFacade to save duplication in when showing the menu.
 305              e.tableCell = cell;
 306              return this._showTableMenu(e);
 307          }
 308          return this._displayDialogue(e);
 309      },
 310  
 311      /**
 312       * Returns whether or not the parameter node exists within the editor.
 313       *
 314       * @method _stopAtContentEditableFilter
 315       * @param  {Node} node
 316       * @private
 317       * @return {boolean} whether or not the parameter node exists within the editor.
 318       */
 319      _stopAtContentEditableFilter: function(node) {
 320          this.editor.contains(node);
 321      },
 322  
 323      /**
 324       * Return the dialogue content for the tool, attaching any required
 325       * events.
 326       *
 327       * @method _getDialogueContent
 328       * @private
 329       * @return {Node} The content to place in the dialogue.
 330       */
 331      _getDialogueContent: function(edit) {
 332          var template = Y.Handlebars.compile(TEMPLATE);
 333          var allowBorders = this.get('allowBorders');
 334  
 335          this._content = Y.Node.create(template({
 336                  CSS: CSS,
 337                  elementid: this.get('host').get('elementid'),
 338                  component: COMPONENT,
 339                  edit: edit,
 340                  nonedit: !edit,
 341                  allowStyling: this.get('allowStyling'),
 342                  allowBorders: allowBorders,
 343                  borderStyles: this.get('borderStyles'),
 344                  allowBackgroundColour: this.get('allowBackgroundColour'),
 345                  availableColours: this.get('availableColors'),
 346                  allowWidth: this.get('allowWidth')
 347              }));
 348  
 349          // Handle table setting.
 350          if (edit) {
 351              this._content.one('.submit').on('click', this._updateTable, this);
 352          } else {
 353              this._content.one('.submit').on('click', this._setTable, this);
 354          }
 355  
 356          if (allowBorders) {
 357              this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
 358          }
 359  
 360          return this._content;
 361      },
 362  
 363      /**
 364       * Disables options within the dialogue if they shouldn't be available.
 365       * E.g.
 366       * If borders are set to "Theme default" then the border size, style and
 367       * colour options are disabled.
 368       *
 369       * @method _updateAvailableSettings
 370       * @private
 371       */
 372      _updateAvailableSettings: function() {
 373          var tableForm = this._content,
 374              enableBorders = tableForm.one('[name="borders"]'),
 375              borderStyle = tableForm.one('[name="borderstyles"]'),
 376              borderSize = tableForm.one('[name="bordersize"]'),
 377              borderColour = tableForm.all('[name="borderColour"]'),
 378              disabledValue = 'removeAttribute';
 379  
 380          if (!enableBorders) {
 381              return;
 382          }
 383  
 384          if (enableBorders.get('value') === 'default') {
 385              disabledValue = 'setAttribute';
 386          }
 387  
 388          if (borderStyle) {
 389              borderStyle[disabledValue]('disabled');
 390          }
 391  
 392          if (borderSize) {
 393              borderSize[disabledValue]('disabled');
 394          }
 395  
 396          if (borderColour) {
 397              borderColour[disabledValue]('disabled');
 398          }
 399  
 400      },
 401  
 402      /**
 403       * Given the current selection, return a table cell suitable for table editing
 404       * purposes, i.e. the first table cell selected, or the first cell in the table
 405       * that the selection exists in, or null if not within a table.
 406       *
 407       * @method _getSuitableTableCell
 408       * @private
 409       * @return {Node} suitable target cell, or null if not within a table
 410       */
 411      _getSuitableTableCell: function() {
 412          var targetcell = null,
 413              host = this.get('host');
 414  
 415          host.getSelectedNodes().some(function(node) {
 416              if (node.ancestor('td, th, caption', true, this._stopAtContentEditableFilter)) {
 417                  targetcell = node;
 418  
 419                  var caption = node.ancestor('caption', true, this._stopAtContentEditableFilter);
 420                  if (caption) {
 421                      var table = caption.get('parentNode');
 422                      if (table) {
 423                          targetcell = table.one('td, th');
 424                      }
 425                  }
 426  
 427                  // Once we've found a cell to target, we shouldn't need to keep looking.
 428                  return true;
 429              }
 430          });
 431  
 432          if (targetcell) {
 433              var selection = host.getSelectionFromNode(targetcell);
 434              host.setSelection(selection);
 435          }
 436  
 437          return targetcell;
 438      },
 439  
 440      /**
 441       * Change a node from one type to another, copying all attributes and children.
 442       *
 443       * @method _changeNodeType
 444       * @param {Y.Node} node
 445       * @param {String} new node type
 446       * @private
 447       * @chainable
 448       */
 449      _changeNodeType: function(node, newType) {
 450          var newNode = Y.Node.create('<' + newType + '></' + newType + '>');
 451          newNode.setAttrs(node.getAttrs());
 452          node.get('childNodes').each(function(child) {
 453              newNode.append(child.remove());
 454          });
 455          node.replace(newNode);
 456          return newNode;
 457      },
 458  
 459      /**
 460       * Handle updating an existing table.
 461       *
 462       * @method _updateTable
 463       * @param {EventFacade} e
 464       * @private
 465       */
 466      _updateTable: function(e) {
 467          var caption,
 468              captionposition,
 469              headers,
 470              borders,
 471              bordersize,
 472              borderstyle,
 473              bordercolour,
 474              backgroundcolour,
 475              table,
 476              width,
 477              captionnode;
 478  
 479          e.preventDefault();
 480          // Hide the dialogue.
 481          this.getDialogue({
 482              focusAfterHide: null
 483          }).hide();
 484  
 485          // Add/update the caption.
 486          caption = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTION);
 487          captionposition = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTIONPOSITION);
 488          headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
 489          borders = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERS);
 490          bordersize = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSIZE);
 491          bordercolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBORDERCOLOUR);
 492          borderstyle = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSTYLE);
 493          backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
 494          width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
 495  
 496          table = this._lastTarget.ancestor('table');
 497          this._setAppearance(table, {
 498              width: width,
 499              borders: borders,
 500              borderColour: bordercolour,
 501              borderSize: bordersize,
 502              borderStyle: borderstyle,
 503              backgroundColour: backgroundcolour
 504          });
 505  
 506          captionnode = table.one('caption');
 507          if (!captionnode) {
 508              captionnode = Y.Node.create('<caption></caption>');
 509              table.insert(captionnode, 0);
 510          }
 511          captionnode.setHTML(caption.get('value'));
 512          captionnode.setStyle('caption-side', captionposition.get('value'));
 513          if (!captionnode.getAttribute('style')) {
 514              captionnode.removeAttribute('style');
 515          }
 516  
 517          // Add the row headers.
 518          if (headers.get('value') === 'rows' || headers.get('value') === 'both') {
 519              table.all('tr').each(function(row) {
 520                  var cells = row.all('th, td'),
 521                      firstCell = cells.shift(),
 522                      newCell;
 523  
 524                  if (firstCell.get('tagName') === 'TD') {
 525                      // Cell is a td but should be a th - change it.
 526                      newCell = this._changeNodeType(firstCell, 'th');
 527                      newCell.setAttribute('scope', 'row');
 528                  } else {
 529                      firstCell.setAttribute('scope', 'row');
 530                  }
 531  
 532                  // Now make sure all other cells in the row are td.
 533                  cells.each(function(cell) {
 534                      if (cell.get('tagName') === 'TH') {
 535                          newCell = this._changeNodeType(cell, 'td');
 536                          newCell.removeAttribute('scope');
 537                      }
 538                  }, this);
 539  
 540              }, this);
 541          }
 542          // Add the col headers. These may overrule the row headers in the first cell.
 543          if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
 544              var rows = table.all('tr'),
 545                  firstRow = rows.shift(),
 546                  newCell;
 547  
 548              firstRow.all('td, th').each(function(cell) {
 549                  if (cell.get('tagName') === 'TD') {
 550                      // Cell is a td but should be a th - change it.
 551                      newCell = this._changeNodeType(cell, 'th');
 552                      newCell.setAttribute('scope', 'col');
 553                  } else {
 554                      cell.setAttribute('scope', 'col');
 555                  }
 556              }, this);
 557              // Change all the cells in the rest of the table to tds (unless they are row headers).
 558              rows.each(function(row) {
 559                  var cells = row.all('th, td');
 560  
 561                  if (headers.get('value') === 'both') {
 562                      // Ignore the first cell because it's a row header.
 563                      cells.shift();
 564                  }
 565                  cells.each(function(cell) {
 566                      if (cell.get('tagName') === 'TH') {
 567                          newCell = this._changeNodeType(cell, 'td');
 568                          newCell.removeAttribute('scope');
 569                      }
 570                  }, this);
 571  
 572              }, this);
 573          }
 574          // Clean the HTML.
 575          this.markUpdated();
 576      },
 577  
 578      /**
 579       * Handle creation of a new table.
 580       *
 581       * @method _setTable
 582       * @param {EventFacade} e
 583       * @private
 584       */
 585      _setTable: function(e) {
 586          var caption,
 587              captionposition,
 588              borders,
 589              bordersize,
 590              borderstyle,
 591              bordercolour,
 592              rows,
 593              cols,
 594              headers,
 595              tablehtml,
 596              backgroundcolour,
 597              width,
 598              i, j;
 599  
 600          e.preventDefault();
 601  
 602          // Hide the dialogue.
 603          this.getDialogue({
 604              focusAfterHide: null
 605          }).hide();
 606  
 607          caption = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTION);
 608          captionposition = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.CAPTIONPOSITION);
 609          borders = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERS);
 610          bordersize = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSIZE);
 611          bordercolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBORDERCOLOUR);
 612          borderstyle = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.BORDERSTYLE);
 613          backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
 614          rows = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.ROWS);
 615          cols = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.COLUMNS);
 616          headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
 617          width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
 618  
 619          // Set the selection.
 620          this.get('host').setSelection(this._currentSelection);
 621  
 622          // Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
 623          var nl = "\n";
 624          var tableId = Y.guid();
 625          tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
 626  
 627          var captionstyle = '';
 628          if (captionposition.get('value')) {
 629              captionstyle = ' style="caption-side: ' + captionposition.get('value') + '"';
 630          }
 631          tablehtml += '<caption' + captionstyle + '>' + Y.Escape.html(caption.get('value')) + '</caption>' + nl;
 632          i = 0;
 633          if (headers.get('value') === 'columns' || headers.get('value') === 'both') {
 634              i = 1;
 635              tablehtml += '<thead>' + nl + '<tr>' + nl;
 636              for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
 637                  tablehtml += '<th scope="col"></th>' + nl;
 638              }
 639              tablehtml += '</tr>' + nl + '</thead>' + nl;
 640          }
 641          tablehtml += '<tbody>' + nl;
 642          for (; i < parseInt(rows.get('value'), 10); i++) {
 643              tablehtml += '<tr>' + nl;
 644              for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
 645                  if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
 646                      tablehtml += '<th scope="row"></th>' + nl;
 647                  } else {
 648                      tablehtml += '<td ></td>' + nl;
 649                  }
 650              }
 651              tablehtml += '</tr>' + nl;
 652          }
 653          tablehtml += '</tbody>' + nl;
 654          tablehtml += '</table>' + nl + '<br/>';
 655  
 656          this.get('host').insertContentAtFocusPoint(tablehtml);
 657  
 658          var tableNode = Y.one('#' + tableId);
 659          this._setAppearance(tableNode, {
 660              width: width,
 661              borders: borders,
 662              borderColour: bordercolour,
 663              borderSize: bordersize,
 664              borderStyle: borderstyle,
 665              backgroundColour: backgroundcolour
 666          });
 667          tableNode.removeAttribute('id');
 668  
 669          // Mark the content as updated.
 670          this.markUpdated();
 671      },
 672  
 673      /**
 674       * Search for all the cells in the current, next and previous columns.
 675       *
 676       * @method _findColumnCells
 677       * @private
 678       * @return {Object} containing current, prev and next {Y.NodeList}s
 679       */
 680      _findColumnCells: function() {
 681          var columnindex = this._getColumnIndex(this._lastTarget),
 682              rows = this._lastTarget.ancestor('table').all('tr'),
 683              currentcells = new Y.NodeList(),
 684              prevcells = new Y.NodeList(),
 685              nextcells = new Y.NodeList();
 686  
 687          rows.each(function(row) {
 688              var cells = row.all('td, th'),
 689                  cell = cells.item(columnindex),
 690                  cellprev = cells.item(columnindex - 1),
 691                  cellnext = cells.item(columnindex + 1);
 692              currentcells.push(cell);
 693              if (cellprev) {
 694                  prevcells.push(cellprev);
 695              }
 696              if (cellnext) {
 697                  nextcells.push(cellnext);
 698              }
 699          });
 700  
 701          return {
 702              current: currentcells,
 703              prev: prevcells,
 704              next: nextcells
 705          };
 706      },
 707  
 708      /**
 709       * Hide the entries in the context menu that don't make sense with the
 710       * current selection.
 711       *
 712       * @method _hideInvalidEntries
 713       * @param {Y.Node} node - The node containing the menu.
 714       * @private
 715       */
 716      _hideInvalidEntries: function(node) {
 717          // Moving rows.
 718          var table = this._lastTarget.ancestor('table'),
 719              row = this._lastTarget.ancestor('tr'),
 720              rows = table.all('tr'),
 721              rowindex = rows.indexOf(row),
 722              prevrow = rows.item(rowindex - 1),
 723              prevrowhascells = prevrow ? prevrow.one('td') : null;
 724  
 725          if (!row || !prevrowhascells) {
 726              node.one('[data-change="moverowup"]').hide();
 727          } else {
 728              node.one('[data-change="moverowup"]').show();
 729          }
 730  
 731          var nextrow = rows.item(rowindex + 1),
 732              rowhascell = row ? row.one('td') : false;
 733  
 734          if (!row || !nextrow || !rowhascell) {
 735              node.one('[data-change="moverowdown"]').hide();
 736          } else {
 737              node.one('[data-change="moverowdown"]').show();
 738          }
 739  
 740          // Moving columns.
 741          var cells = this._findColumnCells();
 742          if (cells.prev.filter('td').size() > 0) {
 743              node.one('[data-change="movecolumnleft"]').show();
 744          } else {
 745              node.one('[data-change="movecolumnleft"]').hide();
 746          }
 747  
 748          var colhascell = cells.current.filter('td').size() > 0;
 749          if ((cells.next.size() > 0) && colhascell) {
 750              node.one('[data-change="movecolumnright"]').show();
 751          } else {
 752              node.one('[data-change="movecolumnright"]').hide();
 753          }
 754  
 755          // Delete col
 756          if (cells.current.filter('td').size() > 0) {
 757              node.one('[data-change="deletecolumn"]').show();
 758          } else {
 759              node.one('[data-change="deletecolumn"]').hide();
 760          }
 761          // Delete row
 762          if (!row || !row.one('td')) {
 763              node.one('[data-change="deleterow"]').hide();
 764          } else {
 765              node.one('[data-change="deleterow"]').show();
 766          }
 767      },
 768  
 769      /**
 770       * Display the table menu.
 771       *
 772       * @method _showTableMenu
 773       * @param {EventFacade} e
 774       * @private
 775       */
 776      _showTableMenu: function(e) {
 777          e.preventDefault();
 778  
 779          var boundingBox;
 780  
 781          if (!this._contextMenu) {
 782              this._menuOptions = [
 783                  {
 784                      text: M.util.get_string("addcolumnafter", COMPONENT),
 785                      data: {
 786                          change: "addcolumnafter"
 787                      }
 788                  }, {
 789                      text: M.util.get_string("addrowafter", COMPONENT),
 790                      data: {
 791                          change: "addrowafter"
 792                      }
 793                  }, {
 794                      text: M.util.get_string("moverowup", COMPONENT),
 795                      data: {
 796                          change: "moverowup"
 797                      }
 798                  }, {
 799                      text: M.util.get_string("moverowdown", COMPONENT),
 800                      data: {
 801                          change: "moverowdown"
 802                      }
 803                  }, {
 804                      text: M.util.get_string("movecolumnleft", COMPONENT),
 805                      data: {
 806                          change: "movecolumnleft"
 807                      }
 808                  }, {
 809                      text: M.util.get_string("movecolumnright", COMPONENT),
 810                      data: {
 811                          change: "movecolumnright"
 812                      }
 813                  }, {
 814                      text: M.util.get_string("deleterow", COMPONENT),
 815                      data: {
 816                          change: "deleterow"
 817                      }
 818                  }, {
 819                      text: M.util.get_string("deletecolumn", COMPONENT),
 820                      data: {
 821                          change: "deletecolumn"
 822                      }
 823                  }, {
 824                      text: M.util.get_string("edittable", COMPONENT),
 825                      data: {
 826                          change: "edittable"
 827                      }
 828                  }
 829              ];
 830  
 831              this._contextMenu = new Y.M.editor_atto.Menu({
 832                  items: this._menuOptions
 833              });
 834  
 835              // Add event handlers for table control menus.
 836              boundingBox = this._contextMenu.get('boundingBox');
 837              boundingBox.delegate('click', this._handleTableChange, 'a', this);
 838          }
 839  
 840          boundingBox = this._contextMenu.get('boundingBox');
 841  
 842          // We store the cell of the last click (the control node is transient).
 843          this._lastTarget = e.tableCell.ancestor('.editor_atto_content td, .editor_atto_content th', true);
 844  
 845          this._hideInvalidEntries(boundingBox);
 846  
 847          // Clear the focusAfterHide for any other menus which may be open.
 848          Y.Array.each(this.get('host').openMenus, function(menu) {
 849              menu.set('focusAfterHide', null);
 850          });
 851  
 852          // Ensure that we focus on the button in the toolbar when we tab back to the menu.
 853          var creatorButton = this.buttons[this.name];
 854          this.get('host')._setTabFocus(creatorButton);
 855  
 856          // Show the context menu, and align to the current position.
 857          this._contextMenu.show();
 858          this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
 859          this._contextMenu.set('focusAfterHide', creatorButton);
 860  
 861          // If there are any anchors in the bounding box, focus on the first.
 862          if (boundingBox.one('a')) {
 863              boundingBox.one('a').focus();
 864          }
 865  
 866          // Add this menu to the list of open menus.
 867          this.get('host').openMenus = [this._contextMenu];
 868      },
 869  
 870      /**
 871       * Handle a selection from the table control menu.
 872       *
 873       * @method _handleTableChange
 874       * @param {EventFacade} e
 875       * @private
 876       */
 877      _handleTableChange: function(e) {
 878          e.preventDefault();
 879  
 880          this._contextMenu.set('focusAfterHide', this.get('host').editor);
 881          // Hide the context menu.
 882          this._contextMenu.hide(e);
 883  
 884          // Make our changes.
 885          switch (e.target.getData('change')) {
 886              case 'addcolumnafter':
 887                  this._addColumnAfter();
 888                  break;
 889              case 'addrowafter':
 890                  this._addRowAfter();
 891                  break;
 892              case 'deleterow':
 893                  this._deleteRow();
 894                  break;
 895              case 'deletecolumn':
 896                  this._deleteColumn();
 897                  break;
 898              case 'edittable':
 899                  this._editTable();
 900                  break;
 901              case 'moverowdown':
 902                  this._moveRowDown();
 903                  break;
 904              case 'moverowup':
 905                  this._moveRowUp();
 906                  break;
 907              case 'movecolumnleft':
 908                  this._moveColumnLeft();
 909                  break;
 910              case 'movecolumnright':
 911                  this._moveColumnRight();
 912                  break;
 913          }
 914      },
 915  
 916      /**
 917       * Determine the index of a row in a table column.
 918       *
 919       * @method _getRowIndex
 920       * @param {Node} cell
 921       * @private
 922       */
 923      _getRowIndex: function(cell) {
 924          var tablenode = cell.ancestor('table'),
 925              rownode = cell.ancestor('tr');
 926  
 927          if (!tablenode || !rownode) {
 928              return;
 929          }
 930  
 931          var rows = tablenode.all('tr');
 932  
 933          return rows.indexOf(rownode);
 934      },
 935  
 936      /**
 937       * Determine the index of a column in a table row.
 938       *
 939       * @method _getColumnIndex
 940       * @param {Node} cellnode
 941       * @private
 942       */
 943      _getColumnIndex: function(cellnode) {
 944          var rownode = cellnode.ancestor('tr');
 945  
 946          if (!rownode) {
 947              return;
 948          }
 949  
 950          var cells = rownode.all('td, th');
 951  
 952          return cells.indexOf(cellnode);
 953      },
 954  
 955      /**
 956       * Delete the current row.
 957       *
 958       * @method _deleteRow
 959       * @private
 960       */
 961      _deleteRow: function() {
 962          var row = this._lastTarget.ancestor('tr');
 963  
 964          if (row && row.one('td')) {
 965              // Only delete rows with at least one non-header cell.
 966              row.remove(true);
 967          }
 968  
 969          // Clean the HTML.
 970          this.markUpdated();
 971      },
 972  
 973      /**
 974       * Move row up
 975       *
 976       * @method _moveRowUp
 977       * @private
 978       */
 979      _moveRowUp: function() {
 980          var row = this._lastTarget.ancestor('tr'),
 981              prevrow = row.previous('tr');
 982          if (!row || !prevrow) {
 983              return;
 984          }
 985  
 986          row.swap(prevrow);
 987          // Clean the HTML.
 988          this.markUpdated();
 989      },
 990  
 991      /**
 992       * Move column left
 993       *
 994       * @method _moveColumnLeft
 995       * @private
 996       */
 997      _moveColumnLeft: function() {
 998          var cells = this._findColumnCells();
 999  
1000          if (cells.current.size() > 0 && cells.prev.size() > 0 && cells.current.size() === cells.prev.size()) {
1001              var i = 0;
1002              for (i = 0; i < cells.current.size(); i++) {
1003                  var cell = cells.current.item(i),
1004                      prevcell = cells.prev.item(i);
1005  
1006                  cell.swap(prevcell);
1007              }
1008          }
1009          // Cleanup.
1010          this.markUpdated();
1011      },
1012  
1013      /**
1014       * Add a caption to the table if it doesn't have one.
1015       *
1016       * @method _addCaption
1017       * @private
1018       */
1019      _addCaption: function() {
1020          var table = this._lastTarget.ancestor('table'),
1021              caption = table.one('caption');
1022  
1023          if (!caption) {
1024              table.insert(Y.Node.create('<caption>&nbsp;</caption>'), 1);
1025          }
1026      },
1027  
1028      /**
1029       * Remove a caption from the table if has one.
1030       *
1031       * @method _removeCaption
1032       * @private
1033       */
1034      _removeCaption: function() {
1035          var table = this._lastTarget.ancestor('table'),
1036              caption = table.one('caption');
1037  
1038          if (caption) {
1039              caption.remove(true);
1040          }
1041      },
1042  
1043      /**
1044       * Move column right.
1045       *
1046       * @method _moveColumnRight
1047       * @private
1048       */
1049      _moveColumnRight: function() {
1050          var cells = this._findColumnCells();
1051  
1052          // Check we have some tds in this column, and one exists to the right.
1053          if ((cells.next.size() > 0) &&
1054                  (cells.current.size() === cells.next.size()) &&
1055                  (cells.current.filter('td').size() > 0)) {
1056              var i = 0;
1057              for (i = 0; i < cells.current.size(); i++) {
1058                  var cell = cells.current.item(i),
1059                      nextcell = cells.next.item(i);
1060  
1061                  cell.swap(nextcell);
1062              }
1063          }
1064          // Cleanup.
1065          this.markUpdated();
1066      },
1067  
1068      /**
1069       * Move row down.
1070       *
1071       * @method _moveRowDown
1072       * @private
1073       */
1074      _moveRowDown: function() {
1075          var row = this._lastTarget.ancestor('tr'),
1076              nextrow = row.next('tr');
1077          if (!row || !nextrow || !row.one('td')) {
1078              return;
1079          }
1080  
1081          row.swap(nextrow);
1082          // Clean the HTML.
1083          this.markUpdated();
1084      },
1085  
1086      /**
1087       * Obtain values for the table borders
1088       *
1089       * @method _getBorderConfiguration
1090       * @param {Node} node
1091       * @private
1092       * @return {Array} or {Boolean} Returns the settings, if presents, or else returns false
1093       */
1094      _getBorderConfiguration: function(node) {
1095          // We need to make a clone of the node in order to avoid grabbing any
1096          // of the computed styles from the DOM. We only want inline styles set by us.
1097          var shadowNode = node.cloneNode(true);
1098          var borderStyle = shadowNode.getStyle('borderStyle'),
1099              borderColor = shadowNode.getStyle('borderColor'),
1100              borderWidth = shadowNode.getStyle('borderWidth');
1101  
1102          if (borderStyle || borderColor || borderWidth) {
1103              var hexColour = Y.Color.toHex(borderColor);
1104              var width = parseInt(borderWidth, 10);
1105              return {
1106                  borderStyle: borderStyle,
1107                  borderColor: hexColour === "#" ? null : hexColour,
1108                  borderWidth: isNaN(width) ? null : width
1109              };
1110          }
1111  
1112          return false;
1113      },
1114  
1115      /**
1116       * Set the appropriate styles on the given table node according to
1117       * the provided configuration.
1118       *
1119       * @method _setAppearance
1120       * @param {Node} The table node to be modified.
1121       * @param {Object} Configuration object (associative array) containing the form nodes for
1122       *                 border styling.
1123       * @private
1124       */
1125      _setAppearance: function(tableNode, configuration) {
1126          var borderhex,
1127              borderSizeValue,
1128              borderStyleValue,
1129              backgroundcolourvalue;
1130  
1131          if (configuration.borderColour) {
1132              borderhex = configuration.borderColour.get('value');
1133          }
1134  
1135          if (configuration.borderSize) {
1136              borderSizeValue = configuration.borderSize.get('value');
1137          }
1138  
1139          if (configuration.borderStyle) {
1140              borderStyleValue = configuration.borderStyle.get('value');
1141          }
1142  
1143          if (configuration.backgroundColour) {
1144              backgroundcolourvalue = configuration.backgroundColour.get('value');
1145          }
1146  
1147          // Clear the inline border styling
1148          tableNode.removeAttribute('style');
1149          tableNode.all('td, th').each(function(cell) {
1150              cell.removeAttribute('style');
1151          }, this);
1152  
1153          if (configuration.borders) {
1154              if (configuration.borders.get('value') === 'outer') {
1155                  tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
1156                  tableNode.setStyle('borderStyle', borderStyleValue);
1157  
1158                  if (borderhex !== 'none') {
1159                      tableNode.setStyle('borderColor', borderhex);
1160                  }
1161              } else if (configuration.borders.get('value') === 'all') {
1162                  tableNode.all('td, th').each(function(cell) {
1163                      cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
1164                      cell.setStyle('borderStyle', borderStyleValue);
1165  
1166                      if (borderhex !== 'none') {
1167                          cell.setStyle('borderColor', borderhex);
1168                      }
1169                  }, this);
1170              }
1171          }
1172  
1173          if (backgroundcolourvalue !== 'none') {
1174              tableNode.setStyle('backgroundColor', backgroundcolourvalue);
1175          }
1176  
1177          if (configuration.width && configuration.width.get('value')) {
1178              tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
1179          }
1180      },
1181  
1182      /**
1183       * Edit table (show the dialogue).
1184       *
1185       * @method _editTable
1186       * @private
1187       */
1188      _editTable: function() {
1189          var dialogue = this.getDialogue({
1190              headerContent: M.util.get_string('edittable', COMPONENT),
1191              focusAfterHide: false,
1192              focusOnShowSelector: SELECTORS.CAPTION,
1193              width: DIALOGUE.WIDTH
1194          });
1195  
1196          // Set the dialogue content, and then show the dialogue.
1197          var node = this._getDialogueContent(true),
1198              captioninput = node.one(SELECTORS.CAPTION),
1199              captionpositioninput = node.one(SELECTORS.CAPTIONPOSITION),
1200              headersinput = node.one(SELECTORS.HEADERS),
1201              borderinput = node.one(SELECTORS.BORDERS),
1202              borderstyle = node.one(SELECTORS.BORDERSTYLE),
1203              bordercolours = node.all(SELECTORS.BORDERCOLOURS),
1204              bordersize = node.one(SELECTORS.BORDERSIZE),
1205              backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
1206              width = node.one(SELECTORS.WIDTH),
1207              table = this._lastTarget.ancestor('table'),
1208              captionnode = table.one('caption'),
1209              hexColour,
1210              matchedInput;
1211  
1212          if (captionnode) {
1213              captioninput.set('value', captionnode.getHTML());
1214          } else {
1215              captioninput.set('value', '');
1216          }
1217  
1218          if (width && table.getStyle('width').indexOf('px') === -1) {
1219              width.set('value', parseInt(table.getStyle('width'), 10));
1220          }
1221  
1222          if (captionpositioninput && captionnode && captionnode.getAttribute('style')) {
1223              captionpositioninput.set('value', captionnode.getStyle('caption-side'));
1224          } else {
1225              // Default to none.
1226              captionpositioninput.set('value', '');
1227          }
1228  
1229          if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
1230              hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
1231              matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
1232  
1233              if (matchedInput) {
1234                  matchedInput.set("checked", true);
1235              }
1236          }
1237  
1238          if (this.get('allowBorders')) {
1239              var borderValue = 'default',
1240                  borderConfiguration = this._getBorderConfiguration(table);
1241  
1242              if (borderConfiguration) {
1243                  borderValue = 'outer';
1244              } else {
1245                  borderConfiguration = this._getBorderConfiguration(table.one('td'));
1246                  if (borderConfiguration) {
1247                       borderValue = 'all';
1248                  }
1249              }
1250  
1251              if (borderConfiguration) {
1252                  var borderStyle = borderConfiguration.borderStyle || DEFAULT.BORDERSTYLE;
1253                  var borderSize = borderConfiguration.borderWidth || DEFAULT.BORDERWIDTH;
1254                  borderstyle.set('value', borderStyle);
1255                  bordersize.set('value', borderSize);
1256                  borderinput.set('value', borderValue);
1257  
1258                  hexColour = borderConfiguration.borderColor;
1259                  matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
1260  
1261                  if (matchedInput) {
1262                      matchedInput.set("checked", true);
1263                  }
1264              }
1265          }
1266  
1267          var headersvalue = 'columns';
1268          if (table.one('th[scope="row"]')) {
1269              headersvalue = 'rows';
1270              if (table.one('th[scope="col"]')) {
1271                  headersvalue = 'both';
1272              }
1273          }
1274          headersinput.set('value', headersvalue);
1275          dialogue.set('bodyContent', node).show();
1276          this._updateAvailableSettings();
1277      },
1278  
1279  
1280      /**
1281       * Delete the current column.
1282       *
1283       * @method _deleteColumn
1284       * @private
1285       */
1286      _deleteColumn: function() {
1287          var columnindex = this._getColumnIndex(this._lastTarget),
1288              table = this._lastTarget.ancestor('table'),
1289              rows = table.all('tr'),
1290              columncells = new Y.NodeList(),
1291              hastd = false;
1292  
1293          rows.each(function(row) {
1294              var cells = row.all('td, th');
1295              var cell = cells.item(columnindex);
1296              if (cell.get('tagName') === 'TD') {
1297                  hastd = true;
1298              }
1299              columncells.push(cell);
1300          });
1301  
1302          // Do not delete all the headers.
1303          if (hastd) {
1304              columncells.remove(true);
1305          }
1306  
1307          // Clean the HTML.
1308          this.markUpdated();
1309      },
1310  
1311      /**
1312       * Add a row after the current row.
1313       *
1314       * @method _addRowAfter
1315       * @private
1316       */
1317      _addRowAfter: function() {
1318          var target = this._lastTarget.ancestor('tr'),
1319              tablebody = this._lastTarget.ancestor('table').one('tbody');
1320          if (!tablebody) {
1321              // Not all tables have tbody.
1322              tablebody = this._lastTarget.ancestor('table');
1323          }
1324  
1325          var firstrow = tablebody.one('tr');
1326          if (!firstrow) {
1327              firstrow = this._lastTarget.ancestor('table').one('tr');
1328          }
1329          if (!firstrow) {
1330              // Table has no rows. Boo.
1331              return;
1332          }
1333          var newrow = firstrow.cloneNode(true);
1334          newrow.all('th, td').each(function(tablecell) {
1335              if (tablecell.get('tagName') === 'TH') {
1336                  if (tablecell.getAttribute('scope') !== 'row') {
1337                      var newcell = Y.Node.create('<td></td>');
1338                      tablecell.replace(newcell);
1339                      tablecell = newcell;
1340                  }
1341              }
1342              tablecell.setHTML('&nbsp;');
1343          });
1344  
1345          if (target.ancestor('thead')) {
1346              target = firstrow;
1347              tablebody.insert(newrow, target);
1348          } else {
1349              target.insert(newrow, 'after');
1350          }
1351  
1352          // Clean the HTML.
1353          this.markUpdated();
1354      },
1355  
1356      /**
1357       * Add a column after the current column.
1358       *
1359       * @method _addColumnAfter
1360       * @private
1361       */
1362      _addColumnAfter: function() {
1363          var cells = this._findColumnCells(),
1364              before = true,
1365              clonecells = cells.next;
1366          if (cells.next.size() <= 0) {
1367              before = false;
1368              clonecells = cells.current;
1369          }
1370  
1371          Y.each(clonecells, function(cell) {
1372              var newcell = cell.cloneNode();
1373              // Clear the content of the cell.
1374              newcell.setHTML('&nbsp;');
1375  
1376              if (before) {
1377                  cell.get('parentNode').insert(newcell, cell);
1378              } else {
1379                  cell.get('parentNode').insert(newcell, cell);
1380                  cell.swap(newcell);
1381              }
1382          }, this);
1383  
1384          // Clean the HTML.
1385          this.markUpdated();
1386      }
1387  
1388  }, {
1389      ATTRS: {
1390          /**
1391           * Whether or not to allow borders
1392           *
1393           * @attribute allowBorder
1394           * @type Boolean
1395           */
1396          allowBorders: {
1397              value: true
1398          },
1399  
1400          /**
1401           * What border styles to allow
1402           *
1403           * @attribute borderStyles
1404           * @type Array
1405           */
1406          borderStyles: {
1407              value: [
1408                  'none',
1409                  'solid',
1410                  'dashed',
1411                  'dotted'
1412              ]
1413          },
1414  
1415          /**
1416           * Whether or not to allow colourizing the background
1417           *
1418           * @attribute allowBackgroundColour
1419           * @type Boolean
1420           */
1421          allowBackgroundColour: {
1422              value: true
1423          },
1424  
1425          /**
1426           * Whether or not to allow setting the table width
1427           *
1428           * @attribute allowWidth
1429           * @type Boolean
1430           */
1431          allowWidth: {
1432              value: true
1433          },
1434  
1435          /**
1436           * Whether we allow styling
1437           * @attribute allowStyling
1438           * @type Boolean
1439           */
1440          allowStyling: {
1441              readOnly: true,
1442              getter: function() {
1443                  return this.get('allowBorders') || this.get('allowBackgroundColour') || this.get('allowWidth');
1444              }
1445          },
1446  
1447          /**
1448           * Available colors
1449           * @attribute availableColors
1450           * @type Array
1451           */
1452          availableColors: {
1453              value: [
1454                  '#FFFFFF',
1455                  '#EF4540',
1456                  '#FFCF35',
1457                  '#98CA3E',
1458                  '#7D9FD3',
1459                  '#333333'
1460              ],
1461              readOnly: true
1462          }
1463      }
1464  });
1465  
1466  
1467  }, '@VERSION@', {"requires": ["moodle-editor_atto-plugin", "moodle-editor_atto-menu", "event", "event-valuechange"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1