X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=roojs-bootstrap-debug.js;h=4d87cb67fc62e0b7ca2116c447d1803fbe191113;hb=248c5cdd0fe1c3012360d530f5fd34ef516a79fb;hp=eb8c55881d66dfc7651db62333cc91333ed8644d;hpb=102ba0fb3c5a9e3c1282974f7573064921d8357d;p=roojs1 diff --git a/roojs-bootstrap-debug.js b/roojs-bootstrap-debug.js index eb8c55881d..4d87cb67fc 100644 --- a/roojs-bootstrap-debug.js +++ b/roojs-bootstrap-debug.js @@ -1,4 +1,4 @@ -/** +Roo.bootstrap = {};/** * set the version of bootstrap based on the stylesheet... * */ @@ -17,7 +17,10 @@ Roo.bootstrap.version = ( function() { })(); Roo.bootstrap.menu = Roo.bootstrap.menu || {}; Roo.bootstrap.nav = {}; -Roo.bootstrap.form = {};Roo.bootstrap.panel = {};Roo.bootstrap.layout = {};/* +Roo.bootstrap.form = {};Roo.bootstrap.panel = {};Roo.bootstrap.layout = {}; +Roo.htmleditor = {}; +Roo.namespace('Roo.bootstrap.form.HtmlEditorToolbar'); +/* * Based on: * Ext JS Library 1.1.1 * Copyright(c) 2006-2007, Ext JS, LLC. @@ -2935,22 +2938,12 @@ Roo.extend(Roo.bootstrap.ButtonUploader, Roo.bootstrap.Button, { getAutoCreate : function() { - var im = { - tag: 'input', - type : 'file', - cls : 'd-none roo-card-upload-selector' - - }; - if (this.multiple) { - im.multiple = 'multiple'; - } + return { cls :'div' , cn : [ - Roo.bootstrap.Button.prototype.getAutoCreate.call(this), - im - + Roo.bootstrap.Button.prototype.getAutoCreate.call(this) ] }; @@ -2971,10 +2964,18 @@ Roo.extend(Roo.bootstrap.ButtonUploader, Roo.bootstrap.Button, { (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL); - - - - this.selectorEl = this.el.select('.roo-card-upload-selector', true).first(); + var im = { + tag: 'input', + type : 'file', + cls : 'd-none roo-card-upload-selector' + + }; + if (this.multiple) { + im.multiple = 'multiple'; + } + this.selectorEl = Roo.get(document.body).createChild(im); // so it does not capture click event for navitem. + + //this.selectorEl = this.el.select('.roo-card-upload-selector', true).first(); this.selectorEl.on('change', this.onFileSelected, this); @@ -4418,6 +4419,8 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component, { delete this.items; for(var i =0;i < items.length;i++) { + // we force children not to montor widnow resize - as we do that for them. + items[i].monitorWindowResize = false; nitems.push(this.addxtype(Roo.apply({}, items[i]))); } } @@ -4545,10 +4548,16 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component, { : this.el.select('.modal-footer div',true).first(); }, + + closeClick : function() + { + this.hide(); + }, + initEvents : function() { if (this.allow_close) { - this.closeEl.on('click', this.hide, this); + this.closeEl.on('click', this.closeClick, this); } Roo.EventManager.onWindowResize(this.resize, this, true); if (this.editableTitle) { @@ -4625,6 +4634,14 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component, { } this.resizeTo(w,h); + // any layout/border etc.. resize.. + (function () { + this.items.forEach( function(e) { + e.layout ? e.layout() : false; + + }); + }).defer(100,this); + }, show : function() { @@ -4669,7 +4686,9 @@ Roo.extend(Roo.bootstrap.Modal, Roo.bootstrap.Component, { // set zindex here - otherwise it appears to be ignored... this.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++); - + + + // this is for children that are... layout.Border (function () { this.items.forEach( function(e) { e.layout ? e.layout() : false; @@ -6546,7 +6565,7 @@ Roo.extend(Roo.bootstrap.nav.Item, Roo.bootstrap.Component, { this.href === false || this.href === '#' ){ - Roo.log("NavItem - prevent Default?"); + //Roo.log("NavItem - prevent Default?"); e.preventDefault(); } @@ -8453,7 +8472,7 @@ Roo.grid.ColumnModel = function(config){ }; Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, { /** - * @cfg {String} header The header text to display in the Grid view. + * @cfg {String} header [required] The header text to display in the Grid view. */ /** * @cfg {String} xsHeader Header at Bootsrap Extra Small width (default for all) @@ -8471,66 +8490,66 @@ Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, { * @cfg {String} xlHeader Header at Bootsrap extra Large width */ /** - * @cfg {String} dataIndex (Optional) The name of the field in the grid's {@link Roo.data.Store}'s + * @cfg {String} dataIndex The name of the field in the grid's {@link Roo.data.Store}'s * {@link Roo.data.Record} definition from which to draw the column's value. If not * specified, the column's index is used as an index into the Record's data Array. */ /** - * @cfg {Number} width (Optional) The initial width in pixels of the column. Using this + * @cfg {Number} width The initial width in pixels of the column. Using this * instead of {@link Roo.grid.Grid#autoSizeColumns} is more efficient. */ /** - * @cfg {Boolean} sortable (Optional) True if sorting is to be allowed on this column. + * @cfg {Boolean} sortable True if sorting is to be allowed on this column. * Defaults to the value of the {@link #defaultSortable} property. * Whether local/remote sorting is used is specified in {@link Roo.data.Store#remoteSort}. */ /** - * @cfg {Boolean} locked (Optional) True to lock the column in place while scrolling the Grid. Defaults to false. + * @cfg {Boolean} locked True to lock the column in place while scrolling the Grid. Defaults to false. */ /** - * @cfg {Boolean} fixed (Optional) True if the column width cannot be changed. Defaults to false. + * @cfg {Boolean} fixed True if the column width cannot be changed. Defaults to false. */ /** - * @cfg {Boolean} resizable (Optional) False to disable column resizing. Defaults to true. + * @cfg {Boolean} resizable False to disable column resizing. Defaults to true. */ /** - * @cfg {Boolean} hidden (Optional) True to hide the column. Defaults to false. + * @cfg {Boolean} hidden True to hide the column. Defaults to false. */ /** - * @cfg {Function} renderer (Optional) A function used to generate HTML markup for a cell + * @cfg {Function} renderer A function used to generate HTML markup for a cell * given the cell's data value. See {@link #setRenderer}. If not specified, the * default renderer returns the escaped data value. If an object is returned (bootstrap only) * then it is treated as a Roo Component object instance, and it is rendered after the initial row is rendered */ /** - * @cfg {Roo.grid.GridEditor} editor (Optional) For grid editors - returns the grid editor + * @cfg {Roo.grid.GridEditor} editor For grid editors - returns the grid editor */ /** - * @cfg {String} align (Optional) Set the CSS text-align property of the column. Defaults to undefined. + * @cfg {String} align (left|right) Set the CSS text-align property of the column. Defaults to undefined (left). */ /** - * @cfg {String} valign (Optional) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc). Defaults to undefined. + * @cfg {String} valign (top|bottom|middle) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc). Defaults to undefined (middle) */ /** - * @cfg {String} cursor (Optional) + * @cfg {String} cursor ( auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|e-resize|n-resize|ne-resize|nw-resize|s-resize|se-resize|sw-resize|w-resize|ew-resize|ns-resize|nesw-resize|nwse-resize|col-resize|row-resize|all-scroll|zoom-in|zoom-out|grab|grabbing) */ /** - * @cfg {String} tooltip (Optional) + * @cfg {String} tooltip mouse over tooltip text */ /** - * @cfg {Number} xs (Optional) can be '0' for hidden at this size (number less than 12) + * @cfg {Number} xs can be '0' for hidden at this size (number less than 12) */ /** - * @cfg {Number} sm (Optional) can be '0' for hidden at this size (number less than 12) + * @cfg {Number} sm can be '0' for hidden at this size (number less than 12) */ /** - * @cfg {Number} md (Optional) can be '0' for hidden at this size (number less than 12) + * @cfg {Number} md can be '0' for hidden at this size (number less than 12) */ /** - * @cfg {Number} lg (Optional) can be '0' for hidden at this size (number less than 12) + * @cfg {Number} lg can be '0' for hidden at this size (number less than 12) */ /** - * @cfg {Number} xl (Optional) can be '0' for hidden at this size (number less than 12) + * @cfg {Number} xl can be '0' for hidden at this size (number less than 12) */ /** * Returns the id of the column at the specified index. @@ -9147,6 +9166,7 @@ Currently the Table uses multiple headers to try and handle XL / Medium etc... * also adds table-responsive (see bootstrap docs for details) * @cfg {Boolean} loadMask (true|false) default false * @cfg {Boolean} footerShow (true|false) generate tfoot, default true + * @cfg {Boolean} footerRow (true|false) generate tfoot with columns of values, default false * @cfg {Boolean} headerShow (true|false) generate thead, default true * @cfg {Boolean} rowSelection (true|false) default false * @cfg {Boolean} cellSelection (true|false) default false @@ -9155,6 +9175,7 @@ Currently the Table uses multiple headers to try and handle XL / Medium etc... * @cfg {Boolean} lazyLoad auto load data while scrolling to the end (default false) * @cfg {Boolean} auto_hide_footer auto hide footer if only one page (default false) * @cfg {Boolean} enableColumnResize default true if columns can be resized = needs scrollBody to be set to work (drag/drop) + * @cfg {Boolean} disableAutoSize disable autoSize() and initCSS() * * * @cfg {Number} minColumnWidth default 50 pixels minimum column width @@ -9330,8 +9351,10 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { store : false, loadMask : false, footerShow : true, + footerRow : false, headerShow : true, enableColumnResize: true, + disableAutoSize: false, rowSelection : false, cellSelection : false, @@ -9404,9 +9427,10 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { cfg.cn.push(this.renderBody()); - if(this.footerShow){ + if(this.footerShow || this.footerRow){ cfg.cn.push(this.renderFooter()); } + // where does this come from? //cfg.cls+= ' TableGrid'; } @@ -9493,7 +9517,9 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { initCSS : function() { - + if(this.disableAutoSize) { + return; + } var cm = this.cm, styles = []; this.CSS.removeStyleSheet(this.id + '-cssrules'); @@ -9965,8 +9991,6 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { return footer; }, - - onLoad : function() { // Roo.log('ds onload'); @@ -10013,7 +10037,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { var tfoot = this.el.select('tfoot', true).first(); - if(this.footerShow && this.auto_hide_footer && this.mainFoot){ + if(this.footerShow && !this.footerRow && this.auto_hide_footer && this.mainFoot){ this.mainFoot.setVisibilityMode(Roo.Element.DISPLAY).hide(); @@ -10023,6 +10047,30 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { this.mainFoot.show(); } } + + if(!this.footerShow && this.footerRow) { + + var tr = { + tag : 'tr', + cn : [] + }; + + for(var i = 0, len = cm.getColumnCount(); i < len; i++){ + var footer = typeof(cm.config[i].footer) == "function" ? cm.config[i].footer(ds, cm.config[i]) : cm.config[i].footer; + var td = { + tag: 'td', + cls : ' x-fcol-' + i, + html: footer + }; + + tr.cn.push(td); + + } + + tfoot.dom.innerHTML = ''; + + tfoot.createChild(tr); + } Roo.each(this.el.select('tbody td', true).elements, function(e){ e.on('mouseover', _this.onMouseover, _this); @@ -10290,7 +10338,7 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { var id = false; if(typeof(renderer) !== 'undefined'){ - value = renderer(d.data[cm.getDataIndex(i)], false, d); + value = renderer.call(config, d.data[cm.getDataIndex(i)], false, d); } // if object are returned, then they are expected to be Roo.bootstrap.Component instances // and are rendered into the cells after the row is rendered - using the id for the element. @@ -10497,6 +10545,9 @@ Roo.extend(Roo.bootstrap.Table, Roo.bootstrap.Component, { */ autoSize : function() { + if(this.disableAutoSize) { + return; + } //var ctr = Roo.get(this.container.dom.parentElement); var ctr = Roo.get(this.el.dom); @@ -11267,7 +11318,13 @@ Roo.extend(Roo.form.Action.Submit, Roo.form.Action, { } var ret = false; try { - ret = Roo.decode(response.responseText); + var rt = response.responseText; + if (rt.match(/^\. + * + * @method cdata + * @param {String} text String to write out inside the comment. + */ + comment: function(text) { + this.html.push(''); + }, + /** + * Writes a PI node such as . + * + * @method pi + * @param {String} name Name of the pi. + * @param {String} text String to write out inside the pi. + */ + pi: function(name, text) { + text ? this.html.push('') : this.html.push(''); + this.indent != '' && this.html.push('\n'); + }, + /** + * Writes a doctype node such as . + * + * @method doctype + * @param {String} text String to write out inside the doctype. + */ + doctype: function(text) { + this.html.push('', this.indent != '' ? '\n' : ''); + }, + /** + * Resets the internal buffer if one wants to reuse the writer. + * + * @method reset + */ + reset: function() { + this.html.length = 0; + this.state = []; + this.pushState({ + indentstr : '', + in_pre : false, + in_inline : false + }) + }, + /** + * Returns the contents that got serialized. + * + * @method getContent + * @return {String} HTML contents that got written down. + */ + getContent: function() { + return this.html.join('').replace(/\n$/, ''); + }, + + pushState : function(cfg) + { + this.state.push(cfg); + Roo.apply(this, cfg); + }, + + popState : function() + { + if (this.state.length < 1) { + return; // nothing to push + } + var cfg = { + in_pre: false, + indentstr : '' + }; + this.state.pop(); + if (this.state.length > 0) { + cfg = this.state[this.state.length-1]; + } + Roo.apply(this, cfg); + }, + + addLine: function() + { + if (this.html.length < 1) { + return; + } + + + var value = this.html[this.html.length - 1]; + if (value.length > 0 && '\n' !== value) { + this.html.push('\n'); + } + } + + +//'pre script noscript style textarea video audio iframe object code' +// shortended... 'area base basefont br col frame hr img input isindex link meta param embed source wbr track'); +// inline +}; + +Roo.htmleditor.TidyWriter.inline_elements = [ + 'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR', + 'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A' +]; +Roo.htmleditor.TidyWriter.shortend_elements = [ + 'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT', + 'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK' +]; + +Roo.htmleditor.TidyWriter.whitespace_elements = [ + 'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE' +];/*** + * This is based loosely on tinymce + * @class Roo.htmleditor.TidyEntities + * @static + * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js + * + * Not 100% sure this is actually used or needed. + */ + +Roo.htmleditor.TidyEntities = { + + /** + * initialize data.. + */ + init : function (){ + + this.namedEntities = this.buildEntitiesLookup(this.namedEntitiesData, 32); + + }, + + + buildEntitiesLookup: function(items, radix) { + var i, chr, entity, lookup = {}; + if (!items) { + return {}; + } + items = typeof(items) == 'string' ? items.split(',') : items; + radix = radix || 10; + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); + // Only add non base entities + if (!this.baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; + } + } + return lookup; + + }, + + asciiMap : { + 128: '€', + 130: '‚', + 131: 'ƒ', + 132: '„', + 133: '…', + 134: '†', + 135: '‡', + 136: 'ˆ', + 137: '‰', + 138: 'Š', + 139: '‹', + 140: 'Œ', + 142: 'Ž', + 145: '‘', + 146: '’', + 147: '“', + 148: '”', + 149: '•', + 150: '–', + 151: '—', + 152: '˜', + 153: '™', + 154: 'š', + 155: '›', + 156: 'œ', + 158: 'ž', + 159: 'Ÿ' + }, + // Raw entities + baseEntities : { + '"': '"', + // Needs to be escaped since the YUI compressor would otherwise break the code + '\'': ''', + '<': '<', + '>': '>', + '&': '&', + '`': '`' + }, + // Reverse lookup table for raw entities + reverseEntities : { + '<': '<', + '>': '>', + '&': '&', + '"': '"', + ''': '\'' + }, + + attrsCharsRegExp : /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp : /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp : /[<>&\"\']/g, + entityRegExp : /&#([a-z0-9]+);?|&([a-z0-9]+);/gi, + namedEntities : false, + namedEntitiesData : [ + '50', + 'nbsp', + '51', + 'iexcl', + '52', + 'cent', + '53', + 'pound', + '54', + 'curren', + '55', + 'yen', + '56', + 'brvbar', + '57', + 'sect', + '58', + 'uml', + '59', + 'copy', + '5a', + 'ordf', + '5b', + 'laquo', + '5c', + 'not', + '5d', + 'shy', + '5e', + 'reg', + '5f', + 'macr', + '5g', + 'deg', + '5h', + 'plusmn', + '5i', + 'sup2', + '5j', + 'sup3', + '5k', + 'acute', + '5l', + 'micro', + '5m', + 'para', + '5n', + 'middot', + '5o', + 'cedil', + '5p', + 'sup1', + '5q', + 'ordm', + '5r', + 'raquo', + '5s', + 'frac14', + '5t', + 'frac12', + '5u', + 'frac34', + '5v', + 'iquest', + '60', + 'Agrave', + '61', + 'Aacute', + '62', + 'Acirc', + '63', + 'Atilde', + '64', + 'Auml', + '65', + 'Aring', + '66', + 'AElig', + '67', + 'Ccedil', + '68', + 'Egrave', + '69', + 'Eacute', + '6a', + 'Ecirc', + '6b', + 'Euml', + '6c', + 'Igrave', + '6d', + 'Iacute', + '6e', + 'Icirc', + '6f', + 'Iuml', + '6g', + 'ETH', + '6h', + 'Ntilde', + '6i', + 'Ograve', + '6j', + 'Oacute', + '6k', + 'Ocirc', + '6l', + 'Otilde', + '6m', + 'Ouml', + '6n', + 'times', + '6o', + 'Oslash', + '6p', + 'Ugrave', + '6q', + 'Uacute', + '6r', + 'Ucirc', + '6s', + 'Uuml', + '6t', + 'Yacute', + '6u', + 'THORN', + '6v', + 'szlig', + '70', + 'agrave', + '71', + 'aacute', + '72', + 'acirc', + '73', + 'atilde', + '74', + 'auml', + '75', + 'aring', + '76', + 'aelig', + '77', + 'ccedil', + '78', + 'egrave', + '79', + 'eacute', + '7a', + 'ecirc', + '7b', + 'euml', + '7c', + 'igrave', + '7d', + 'iacute', + '7e', + 'icirc', + '7f', + 'iuml', + '7g', + 'eth', + '7h', + 'ntilde', + '7i', + 'ograve', + '7j', + 'oacute', + '7k', + 'ocirc', + '7l', + 'otilde', + '7m', + 'ouml', + '7n', + 'divide', + '7o', + 'oslash', + '7p', + 'ugrave', + '7q', + 'uacute', + '7r', + 'ucirc', + '7s', + 'uuml', + '7t', + 'yacute', + '7u', + 'thorn', + '7v', + 'yuml', + 'ci', + 'fnof', + 'sh', + 'Alpha', + 'si', + 'Beta', + 'sj', + 'Gamma', + 'sk', + 'Delta', + 'sl', + 'Epsilon', + 'sm', + 'Zeta', + 'sn', + 'Eta', + 'so', + 'Theta', + 'sp', + 'Iota', + 'sq', + 'Kappa', + 'sr', + 'Lambda', + 'ss', + 'Mu', + 'st', + 'Nu', + 'su', + 'Xi', + 'sv', + 'Omicron', + 't0', + 'Pi', + 't1', + 'Rho', + 't3', + 'Sigma', + 't4', + 'Tau', + 't5', + 'Upsilon', + 't6', + 'Phi', + 't7', + 'Chi', + 't8', + 'Psi', + 't9', + 'Omega', + 'th', + 'alpha', + 'ti', + 'beta', + 'tj', + 'gamma', + 'tk', + 'delta', + 'tl', + 'epsilon', + 'tm', + 'zeta', + 'tn', + 'eta', + 'to', + 'theta', + 'tp', + 'iota', + 'tq', + 'kappa', + 'tr', + 'lambda', + 'ts', + 'mu', + 'tt', + 'nu', + 'tu', + 'xi', + 'tv', + 'omicron', + 'u0', + 'pi', + 'u1', + 'rho', + 'u2', + 'sigmaf', + 'u3', + 'sigma', + 'u4', + 'tau', + 'u5', + 'upsilon', + 'u6', + 'phi', + 'u7', + 'chi', + 'u8', + 'psi', + 'u9', + 'omega', + 'uh', + 'thetasym', + 'ui', + 'upsih', + 'um', + 'piv', + '812', + 'bull', + '816', + 'hellip', + '81i', + 'prime', + '81j', + 'Prime', + '81u', + 'oline', + '824', + 'frasl', + '88o', + 'weierp', + '88h', + 'image', + '88s', + 'real', + '892', + 'trade', + '89l', + 'alefsym', + '8cg', + 'larr', + '8ch', + 'uarr', + '8ci', + 'rarr', + '8cj', + 'darr', + '8ck', + 'harr', + '8dl', + 'crarr', + '8eg', + 'lArr', + '8eh', + 'uArr', + '8ei', + 'rArr', + '8ej', + 'dArr', + '8ek', + 'hArr', + '8g0', + 'forall', + '8g2', + 'part', + '8g3', + 'exist', + '8g5', + 'empty', + '8g7', + 'nabla', + '8g8', + 'isin', + '8g9', + 'notin', + '8gb', + 'ni', + '8gf', + 'prod', + '8gh', + 'sum', + '8gi', + 'minus', + '8gn', + 'lowast', + '8gq', + 'radic', + '8gt', + 'prop', + '8gu', + 'infin', + '8h0', + 'ang', + '8h7', + 'and', + '8h8', + 'or', + '8h9', + 'cap', + '8ha', + 'cup', + '8hb', + 'int', + '8hk', + 'there4', + '8hs', + 'sim', + '8i5', + 'cong', + '8i8', + 'asymp', + '8j0', + 'ne', + '8j1', + 'equiv', + '8j4', + 'le', + '8j5', + 'ge', + '8k2', + 'sub', + '8k3', + 'sup', + '8k4', + 'nsub', + '8k6', + 'sube', + '8k7', + 'supe', + '8kl', + 'oplus', + '8kn', + 'otimes', + '8l5', + 'perp', + '8m5', + 'sdot', + '8o8', + 'lceil', + '8o9', + 'rceil', + '8oa', + 'lfloor', + '8ob', + 'rfloor', + '8p9', + 'lang', + '8pa', + 'rang', + '9ea', + 'loz', + '9j0', + 'spades', + '9j3', + 'clubs', + '9j5', + 'hearts', + '9j6', + 'diams', + 'ai', + 'OElig', + 'aj', + 'oelig', + 'b0', + 'Scaron', + 'b1', + 'scaron', + 'bo', + 'Yuml', + 'm6', + 'circ', + 'ms', + 'tilde', + '802', + 'ensp', + '803', + 'emsp', + '809', + 'thinsp', + '80c', + 'zwnj', + '80d', + 'zwj', + '80e', + 'lrm', + '80f', + 'rlm', + '80j', + 'ndash', + '80k', + 'mdash', + '80o', + 'lsquo', + '80p', + 'rsquo', + '80q', + 'sbquo', + '80s', + 'ldquo', + '80t', + 'rdquo', + '80u', + 'bdquo', + '810', + 'dagger', + '811', + 'Dagger', + '81g', + 'permil', + '81p', + 'lsaquo', + '81q', + 'rsaquo', + '85c', + 'euro' + ], + + + /** + * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded. + * + * @method encodeRaw + * @param {String} text Text to encode. + * @param {Boolean} attr Optional flag to specify if the text is attribute contents. + * @return {String} Entity encoded text. + */ + encodeRaw: function(text, attr) + { + var t = this; + return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) { + return t.baseEntities[chr] || chr; + }); + }, + /** + * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents + * since it doesn't know if the context is within a attribute or text node. This was added for compatibility + * and is exposed as the DOMUtils.encode function. + * + * @method encodeAllRaw + * @param {String} text Text to encode. + * @return {String} Entity encoded text. + */ + encodeAllRaw: function(text) { + var t = this; + return ('' + text).replace(this.rawCharsRegExp, function(chr) { + return t.baseEntities[chr] || chr; + }); + }, + /** + * Encodes the specified string using numeric entities. The core entities will be + * encoded as named ones but all non lower ascii characters will be encoded into numeric entities. + * + * @method encodeNumeric + * @param {String} text Text to encode. + * @param {Boolean} attr Optional flag to specify if the text is attribute contents. + * @return {String} Entity encoded text. + */ + encodeNumeric: function(text, attr) { + var t = this; + return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) { + // Multi byte sequence convert it to a single entity + if (chr.length > 1) { + return '&#' + (1024 * (chr.charCodeAt(0) - 55296) + (chr.charCodeAt(1) - 56320) + 65536) + ';'; + } + return t.baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); + }, + /** + * Encodes the specified string using named entities. The core entities will be encoded + * as named ones but all non lower ascii characters will be encoded into named entities. + * + * @method encodeNamed + * @param {String} text Text to encode. + * @param {Boolean} attr Optional flag to specify if the text is attribute contents. + * @param {Object} entities Optional parameter with entities to use. + * @return {String} Entity encoded text. + */ + encodeNamed: function(text, attr, entities) { + var t = this; + entities = entities || this.namedEntities; + return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) { + return t.baseEntities[chr] || entities[chr] || chr; + }); + }, + /** + * Returns an encode function based on the name(s) and it's optional entities. + * + * @method getEncodeFunc + * @param {String} name Comma separated list of encoders for example named,numeric. + * @param {String} entities Optional parameter with entities to use instead of the built in set. + * @return {function} Encode function to be used. + */ + getEncodeFunc: function(name, entities) { + entities = this.buildEntitiesLookup(entities) || this.namedEntities; + var t = this; + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? t.attrsCharsRegExp : t.textCharsRegExp, function(chr) { + return t.baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); + } + + function encodeCustomNamed(text, attr) { + return t.encodeNamed(text, attr, entities); + } + // Replace + with , to be compatible with previous TinyMCE versions + name = this.makeMap(name.replace(/\+/g, ',')); + // Named and numeric encoder + if (name.named && name.numeric) { + return this.encodeNamedAndNumeric; + } + // Named encoder + if (name.named) { + // Custom names + if (entities) { + return encodeCustomNamed; + } + return this.encodeNamed; + } + // Numeric + if (name.numeric) { + return this.encodeNumeric; + } + // Raw encoder + return this.encodeRaw; + }, + /** + * Decodes the specified string, this will replace entities with raw UTF characters. + * + * @method decode + * @param {String} text Text to entity decode. + * @return {String} Entity decoded string. + */ + decode: function(text) + { + var t = this; + return text.replace(this.entityRegExp, function(all, numeric) { + if (numeric) { + numeric = 'x' === numeric.charAt(0).toLowerCase() ? parseInt(numeric.substr(1), 16) : parseInt(numeric, 10); + // Support upper UTF + if (numeric > 65535) { + numeric -= 65536; + return String.fromCharCode(55296 + (numeric >> 10), 56320 + (1023 & numeric)); + } + return t.asciiMap[numeric] || String.fromCharCode(numeric); + } + return t.reverseEntities[all] || t.namedEntities[all] || t.nativeDecode(all); + }); + }, + nativeDecode : function (text) { + return text; + }, + makeMap : function (items, delim, map) { + var i; + items = items || []; + delim = delim || ','; + if (typeof items == "string") { + items = items.split(delim); + } + map = map || {}; + i = items.length; + while (i--) { + map[items[i]] = {}; + } + return map; + } +}; + + + +Roo.htmleditor.TidyEntities.init(); +/** + * @class Roo.htmleditor.KeyEnter + * Handle Enter press.. + * @cfg {Roo.HtmlEditorCore} core the editor. + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + + + + + +Roo.htmleditor.KeyEnter = function(cfg) { + Roo.apply(this, cfg); + // this does not actually call walk as it's really just a abstract class + + Roo.get(this.core.doc.body).on('keypress', this.keypress, this); +} + +//Roo.htmleditor.KeyEnter.i = 0; + + +Roo.htmleditor.KeyEnter.prototype = { + + core : false, + + keypress : function(e) + { + if (e.charCode != 13 && e.charCode != 10) { + Roo.log([e.charCode,e]); + return true; + } + e.preventDefault(); + // https://stackoverflow.com/questions/18552336/prevent-contenteditable-adding-div-on-enter-chrome + var doc = this.core.doc; + //add a new line + + + var sel = this.core.getSelection(); + var range = sel.getRangeAt(0); + var n = range.commonAncestorContainer; + var pc = range.closest([ 'ol', 'ul']); + var pli = range.closest('li'); + if (!pc || e.ctrlKey) { + // on it list, or ctrl pressed. + if (!e.ctrlKey) { + sel.insertNode('br', 'after'); + } else { + // only do this if we have ctrl key.. + var br = doc.createElement('br'); + br.className = 'clear'; + br.setAttribute('style', 'clear: both'); + sel.insertNode(br, 'after'); + } + + + this.core.undoManager.addEvent(); + this.core.fireEditorEvent(e); + return false; + } + + // deal with
  • insetion + if (pli.innerText.trim() == '' && + pli.previousSibling && + pli.previousSibling.nodeName == 'LI' && + pli.previousSibling.innerText.trim() == '') { + pli.parentNode.removeChild(pli.previousSibling); + sel.cursorAfter(pc); + this.core.undoManager.addEvent(); + this.core.fireEditorEvent(e); + return false; + } + + var li = doc.createElement('LI'); + li.innerHTML = ' '; + if (!pli || !pli.firstSibling) { + pc.appendChild(li); + } else { + pli.parentNode.insertBefore(li, pli.firstSibling); + } + sel.cursorText (li.firstChild); + + this.core.undoManager.addEvent(); + this.core.fireEditorEvent(e); + + return false; + + + + + + } +}; + +/** + * @class Roo.htmleditor.Block + * Base class for html editor blocks - do not use it directly .. extend it.. + * @cfg {DomElement} node The node to apply stuff to. + * @cfg {String} friendly_name the name that appears in the context bar about this block + * @cfg {Object} Context menu - see Roo.form.HtmlEditor.ToolbarContext + + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + +Roo.htmleditor.Block = function(cfg) +{ + // do nothing .. should not be called really. +} +/** + * factory method to get the block from an element (using cache if necessary) + * @static + * @param {HtmlElement} the dom element + */ +Roo.htmleditor.Block.factory = function(node) +{ + var cc = Roo.htmleditor.Block.cache; + var id = Roo.get(node).id; + if (typeof(cc[id]) != 'undefined' && (!cc[id].node || cc[id].node.closest('body'))) { + Roo.htmleditor.Block.cache[id].readElement(node); + return Roo.htmleditor.Block.cache[id]; + } + var db = node.getAttribute('data-block'); + if (!db) { + db = node.nodeName.toLowerCase().toUpperCaseFirst(); + } + var cls = Roo.htmleditor['Block' + db]; + if (typeof(cls) == 'undefined') { + //Roo.log(node.getAttribute('data-block')); + Roo.log("OOps missing block : " + 'Block' + db); + return false; + } + Roo.htmleditor.Block.cache[id] = new cls({ node: node }); + return Roo.htmleditor.Block.cache[id]; /// should trigger update element +}; + +/** + * initalize all Elements from content that are 'blockable' + * @static + * @param the body element + */ +Roo.htmleditor.Block.initAll = function(body, type) +{ + if (typeof(type) == 'undefined') { + var ia = Roo.htmleditor.Block.initAll; + ia(body,'table'); + ia(body,'td'); + ia(body,'figure'); + return; + } + Roo.each(Roo.get(body).query(type), function(e) { + Roo.htmleditor.Block.factory(e); + },this); +}; +// question goes here... do we need to clear out this cache sometimes? +// or show we make it relivant to the htmleditor. +Roo.htmleditor.Block.cache = {}; + +Roo.htmleditor.Block.prototype = { + + node : false, + + // used by context menu + friendly_name : 'Based Block', + + // text for button to delete this element + deleteTitle : false, + + context : false, + /** + * Update a node with values from this object + * @param {DomElement} node + */ + updateElement : function(node) + { + Roo.DomHelper.update(node === undefined ? this.node : node, this.toObject()); + }, + /** + * convert to plain HTML for calling insertAtCursor.. + */ + toHTML : function() + { + return Roo.DomHelper.markup(this.toObject()); + }, + /** + * used by readEleemnt to extract data from a node + * may need improving as it's pretty basic + + * @param {DomElement} node + * @param {String} tag - tag to find, eg. IMG ?? might be better to use DomQuery ? + * @param {String} attribute (use html - for contents, style for using next param as style, or false to return the node) + * @param {String} style the style property - eg. text-align + */ + getVal : function(node, tag, attr, style) + { + var n = node; + if (tag !== true && n.tagName != tag.toUpperCase()) { + // in theory we could do figure[3] << 3rd figure? or some more complex search..? + // but kiss for now. + n = node.getElementsByTagName(tag).item(0); + } + if (!n) { + return ''; + } + if (attr === false) { + return n; + } + if (attr == 'html') { + return n.innerHTML; + } + if (attr == 'style') { + return n.style[style]; + } + + return n.hasAttribute(attr) ? n.getAttribute(attr) : ''; + + }, + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * (override this) + */ + toObject : function() + { + return {}; + }, + /** + * Read a node that has a 'data-block' property - and extract the values from it. + * @param {DomElement} node - the node + */ + readElement : function(node) + { + + } + + +}; + + + +/** + * @class Roo.htmleditor.BlockFigure + * Block that has an image and a figcaption + * @cfg {String} image_src the url for the image + * @cfg {String} align (left|right) alignment for the block default left + * @cfg {String} caption the text to appear below (and in the alt tag) + * @cfg {String} caption_display (block|none) display or not the caption + * @cfg {String|number} image_width the width of the image number or %? + * @cfg {String|number} image_height the height of the image number or %? + * + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + +Roo.htmleditor.BlockFigure = function(cfg) +{ + if (cfg.node) { + this.readElement(cfg.node); + this.updateElement(cfg.node); + } + Roo.apply(this, cfg); +} +Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, { + + + // setable values. + image_src: '', + align: 'center', + caption : '', + caption_display : 'block', + width : '100%', + cls : '', + href: '', + video_url : '', + + // margin: '2%', not used + + text_align: 'left', // (left|right) alignment for the text caption default left. - not used at present + + + // used by context menu + friendly_name : 'Image with caption', + deleteTitle : "Delete Image and Caption", + + contextMenu : function(toolbar) + { + + var block = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode); + }; + + + var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap; + + var syncValue = toolbar.editorcore.syncValue; + + var fields = {}; + + return [ + { + xtype : 'TextItem', + text : "Source: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'Button', + text: 'Change Image URL', + + listeners : { + click: function (btn, state) + { + var b = block(); + + Roo.MessageBox.show({ + title : "Image Source URL", + msg : "Enter the url for the image", + buttons: Roo.MessageBox.OKCANCEL, + fn: function(btn, val){ + if (btn != 'ok') { + return; + } + b.image_src = val; + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + }, + minWidth:250, + prompt:true, + //multiline: multiline, + modal : true, + value : b.image_src + }); + } + }, + xns : rooui.Toolbar + }, + + { + xtype : 'Button', + text: 'Change Link URL', + + listeners : { + click: function (btn, state) + { + var b = block(); + + Roo.MessageBox.show({ + title : "Link URL", + msg : "Enter the url for the link - leave blank to have no link", + buttons: Roo.MessageBox.OKCANCEL, + fn: function(btn, val){ + if (btn != 'ok') { + return; + } + b.href = val; + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + }, + minWidth:250, + prompt:true, + //multiline: multiline, + modal : true, + value : b.href + }); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: 'Show Video URL', + + listeners : { + click: function (btn, state) + { + Roo.MessageBox.alert("Video URL", + block().video_url == '' ? 'This image is not linked ot a video' : + 'The image is linked to: ' + block().video_url + ''); + } + }, + xns : rooui.Toolbar + }, + + + { + xtype : 'TextItem', + text : "Width: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'ComboBox', + allowBlank : false, + displayField : 'val', + editable : true, + listWidth : 100, + triggerAction : 'all', + typeAhead : true, + valueField : 'val', + width : 70, + name : 'width', + listeners : { + select : function (combo, r, index) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + var b = block(); + b.width = r.get('val'); + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.form, + store : { + xtype : 'SimpleStore', + data : [ + ['100%'], + ['80%'], + ['50%'], + ['20%'], + ['10%'] + ], + fields : [ 'val'], + xns : Roo.data + } + }, + { + xtype : 'TextItem', + text : "Align: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'ComboBox', + allowBlank : false, + displayField : 'val', + editable : true, + listWidth : 100, + triggerAction : 'all', + typeAhead : true, + valueField : 'val', + width : 70, + name : 'align', + listeners : { + select : function (combo, r, index) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + var b = block(); + b.align = r.get('val'); + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.form, + store : { + xtype : 'SimpleStore', + data : [ + ['left'], + ['right'], + ['center'] + ], + fields : [ 'val'], + xns : Roo.data + } + }, + + + { + xtype : 'Button', + text: 'Hide Caption', + name : 'caption_display', + pressed : false, + enableToggle : true, + setValue : function(v) { + // this trigger toggle. + + this.setText(v ? "Hide Caption" : "Show Caption"); + this.setPressed(v != 'block'); + }, + listeners : { + toggle: function (btn, state) + { + var b = block(); + b.caption_display = b.caption_display == 'block' ? 'none' : 'block'; + this.setText(b.caption_display == 'block' ? "Hide Caption" : "Show Caption"); + b.updateElement(); + syncValue(); + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + } + ]; + + }, + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + */ + toObject : function() + { + var d = document.createElement('div'); + d.innerHTML = this.caption; + + var m = this.width != '100%' && this.align == 'center' ? '0 auto' : 0; + + var iw = this.align == 'center' ? this.width : '100%'; + var img = { + tag : 'img', + contenteditable : 'false', + src : this.image_src, + alt : d.innerText.replace(/\n/g, " ").replace(/\s+/g, ' ').trim(), // removeHTML and reduce spaces.. + style: { + width : iw, + maxWidth : iw + ' !important', // this is not getting rendered? + margin : m + + } + }; + /* + '
    ' + + '' + + '' + + '' + + '
    ', + */ + + if (this.href.length > 0) { + img = { + tag : 'a', + href: this.href, + contenteditable : 'true', + cn : [ + img + ] + }; + } + + + if (this.video_url.length > 0) { + img = { + tag : 'div', + cls : this.cls, + frameborder : 0, + allowfullscreen : true, + width : 420, // these are for video tricks - that we replace the outer + height : 315, + src : this.video_url, + cn : [ + img + ] + }; + } + // we remove caption totally if its hidden... - will delete data.. but otherwise we end up with fake caption + var captionhtml = this.caption_display == 'none' ? '' : (this.caption.length ? this.caption : "Caption"); + + + var ret = { + tag: 'figure', + 'data-block' : 'Figure', + 'data-width' : this.width, + contenteditable : 'false', + + style : { + display: 'block', + float : this.align , + maxWidth : this.align == 'center' ? '100% !important' : (this.width + ' !important'), + width : this.align == 'center' ? '100%' : this.width, + margin: '0px', + padding: this.align == 'center' ? '0' : '0 10px' , + textAlign : this.align // seems to work for email.. + + }, + + + align : this.align, + cn : [ + img, + + { + tag: 'figcaption', + 'data-display' : this.caption_display, + style : { + textAlign : 'left', + fontSize : '16px', + lineHeight : '24px', + display : this.caption_display, + maxWidth : (this.align == 'center' ? this.width : '100%' ) + ' !important', + margin: m, + width: this.align == 'center' ? this.width : '100%' + + + }, + cls : this.cls.length > 0 ? (this.cls + '-thumbnail' ) : '', + cn : [ + { + tag: 'div', + style : { + marginTop : '16px', + textAlign : 'left' + }, + align: 'left', + cn : [ + { + // we can not rely on yahoo syndication to use CSS elements - so have to use '' to encase stuff. + tag : 'i', + contenteditable : true, + html : captionhtml + } + + ] + } + + ] + + } + ] + }; + return ret; + + }, + + readElement : function(node) + { + // this should not really come from the link... + this.video_url = this.getVal(node, 'div', 'src'); + this.cls = this.getVal(node, 'div', 'class'); + this.href = this.getVal(node, 'a', 'href'); + + + this.image_src = this.getVal(node, 'img', 'src'); + + this.align = this.getVal(node, 'figure', 'align'); + var figcaption = this.getVal(node, 'figcaption', false); + if (figcaption !== '') { + this.caption = this.getVal(figcaption, 'i', 'html'); + } + + + this.caption_display = this.getVal(node, 'figcaption', 'data-display'); + //this.text_align = this.getVal(node, 'figcaption', 'style','text-align'); + this.width = this.getVal(node, true, 'data-width'); + //this.margin = this.getVal(node, 'figure', 'style', 'margin'); + + }, + removeNode : function() + { + return this.node; + } + + + + + + + + +}) + + + +/** + * @class Roo.htmleditor.BlockTable + * Block that manages a table + * + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + +Roo.htmleditor.BlockTable = function(cfg) +{ + if (cfg.node) { + this.readElement(cfg.node); + this.updateElement(cfg.node); + } + Roo.apply(this, cfg); + if (!cfg.node) { + this.rows = []; + for(var r = 0; r < this.no_row; r++) { + this.rows[r] = []; + for(var c = 0; c < this.no_col; c++) { + this.rows[r][c] = this.emptyCell(); + } + } + } + + +} +Roo.extend(Roo.htmleditor.BlockTable, Roo.htmleditor.Block, { + + rows : false, + no_col : 1, + no_row : 1, + + + width: '100%', + + // used by context menu + friendly_name : 'Table', + deleteTitle : 'Delete Table', + // context menu is drawn once.. + + contextMenu : function(toolbar) + { + + var block = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode); + }; + + + var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap; + + var syncValue = toolbar.editorcore.syncValue; + + var fields = {}; + + return [ + { + xtype : 'TextItem', + text : "Width: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'ComboBox', + allowBlank : false, + displayField : 'val', + editable : true, + listWidth : 100, + triggerAction : 'all', + typeAhead : true, + valueField : 'val', + width : 100, + name : 'width', + listeners : { + select : function (combo, r, index) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + var b = block(); + b.width = r.get('val'); + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.form, + store : { + xtype : 'SimpleStore', + data : [ + ['100%'], + ['auto'] + ], + fields : [ 'val'], + xns : Roo.data + } + }, + // -------- Cols + + { + xtype : 'TextItem', + text : "Columns: ", + xns : rooui.Toolbar //Boostrap? + }, + + { + xtype : 'Button', + text: '-', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + block().removeColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: '+', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + block().addColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + // -------- ROWS + { + xtype : 'TextItem', + text : "Rows: ", + xns : rooui.Toolbar //Boostrap? + }, + + { + xtype : 'Button', + text: '-', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + block().removeRow(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: '+', + listeners : { + click : function (_self, e) + { + block().addRow(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + // -------- ROWS + { + xtype : 'Button', + text: 'Reset Column Widths', + listeners : { + + click : function (_self, e) + { + block().resetWidths(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + } + + + + ]; + + }, + + + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * ?? should it be called with option to hide all editing features? + */ + toObject : function() + { + + var ret = { + tag : 'table', + contenteditable : 'false', // this stops cell selection from picking the table. + 'data-block' : 'Table', + style : { + width: this.width, + border : 'solid 1px #000', // ??? hard coded? + 'border-collapse' : 'collapse' + }, + cn : [ + { tag : 'tbody' , cn : [] } + ] + }; + + // do we have a head = not really + var ncols = 0; + Roo.each(this.rows, function( row ) { + var tr = { + tag: 'tr', + style : { + margin: '6px', + border : 'solid 1px #000', + textAlign : 'left' + }, + cn : [ ] + }; + + ret.cn[0].cn.push(tr); + // does the row have any properties? ?? height? + var nc = 0; + Roo.each(row, function( cell ) { + + var td = { + tag : 'td', + contenteditable : 'true', + 'data-block' : 'Td', + html : cell.html, + style : cell.style + }; + if (cell.colspan > 1) { + td.colspan = cell.colspan ; + nc += cell.colspan; + } else { + nc++; + } + if (cell.rowspan > 1) { + td.rowspan = cell.rowspan ; + } + + + // widths ? + tr.cn.push(td); + + + }, this); + ncols = Math.max(nc, ncols); + + + }, this); + // add the header row.. + + ncols++; + + + return ret; + + }, + + readElement : function(node) + { + node = node ? node : this.node ; + this.width = this.getVal(node, true, 'style', 'width') || '100%'; + + this.rows = []; + this.no_row = 0; + var trs = Array.from(node.rows); + trs.forEach(function(tr) { + var row = []; + this.rows.push(row); + + this.no_row++; + var no_column = 0; + Array.from(tr.cells).forEach(function(td) { + + var add = { + colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan')*1 : 1, + rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan')*1 : 1, + style : td.hasAttribute('style') ? td.getAttribute('style') : '', + html : td.innerHTML + }; + no_column += add.colspan; + + + row.push(add); + + + },this); + this.no_col = Math.max(this.no_col, no_column); + + + },this); + + + }, + normalizeRows: function() + { + var ret= []; + var rid = -1; + this.rows.forEach(function(row) { + rid++; + ret[rid] = []; + row = this.normalizeRow(row); + var cid = 0; + row.forEach(function(c) { + while (typeof(ret[rid][cid]) != 'undefined') { + cid++; + } + if (typeof(ret[rid]) == 'undefined') { + ret[rid] = []; + } + ret[rid][cid] = c; + c.row = rid; + c.col = cid; + if (c.rowspan < 2) { + return; + } + + for(var i = 1 ;i < c.rowspan; i++) { + if (typeof(ret[rid+i]) == 'undefined') { + ret[rid+i] = []; + } + ret[rid+i][cid] = c; + } + }); + }, this); + return ret; + + }, + + normalizeRow: function(row) + { + var ret= []; + row.forEach(function(c) { + if (c.colspan < 2) { + ret.push(c); + return; + } + for(var i =0 ;i < c.colspan; i++) { + ret.push(c); + } + }); + return ret; + + }, + + deleteColumn : function(sel) + { + if (!sel || sel.type != 'col') { + return; + } + if (this.no_col < 2) { + return; + } + + this.rows.forEach(function(row) { + var cols = this.normalizeRow(row); + var col = cols[sel.col]; + if (col.colspan > 1) { + col.colspan --; + } else { + row.remove(col); + } + + }, this); + this.no_col--; + + }, + removeColumn : function() + { + this.deleteColumn({ + type: 'col', + col : this.no_col-1 + }); + this.updateElement(); + }, + + + addColumn : function() + { + + this.rows.forEach(function(row) { + row.push(this.emptyCell()); + + }, this); + this.updateElement(); + }, + + deleteRow : function(sel) + { + if (!sel || sel.type != 'row') { + return; + } + + if (this.no_row < 2) { + return; + } + + var rows = this.normalizeRows(); + + + rows[sel.row].forEach(function(col) { + if (col.rowspan > 1) { + col.rowspan--; + } else { + col.remove = 1; // flage it as removed. + } + + }, this); + var newrows = []; + this.rows.forEach(function(row) { + newrow = []; + row.forEach(function(c) { + if (typeof(c.remove) == 'undefined') { + newrow.push(c); + } + + }); + if (newrow.length > 0) { + newrows.push(row); + } + }); + this.rows = newrows; + + + + this.no_row--; + this.updateElement(); + + }, + removeRow : function() + { + this.deleteRow({ + type: 'row', + row : this.no_row-1 + }); + + }, + + + addRow : function() + { + + var row = []; + for (var i = 0; i < this.no_col; i++ ) { + + row.push(this.emptyCell()); + + } + this.rows.push(row); + this.updateElement(); + + }, + + // the default cell object... at present... + emptyCell : function() { + return (new Roo.htmleditor.BlockTd({})).toObject(); + + + }, + + removeNode : function() + { + return this.node; + }, + + + + resetWidths : function() + { + Array.from(this.node.getElementsByTagName('td')).forEach(function(n) { + var nn = Roo.htmleditor.Block.factory(n); + nn.width = ''; + nn.updateElement(n); + }); + } + + + + +}) + +/** + * + * editing a TD? + * + * since selections really work on the table cell, then editing really should work from there + * + * The original plan was to support merging etc... - but that may not be needed yet.. + * + * So this simple version will support: + * add/remove cols + * adjust the width +/- + * reset the width... + * + * + */ + + + + +/** + * @class Roo.htmleditor.BlockTable + * Block that manages a table + * + * @constructor + * Create a new Filter. + * @param {Object} config Configuration options + */ + +Roo.htmleditor.BlockTd = function(cfg) +{ + if (cfg.node) { + this.readElement(cfg.node); + this.updateElement(cfg.node); + } + Roo.apply(this, cfg); + + + +} +Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, { + + node : false, + + width: '', + textAlign : 'left', + valign : 'top', + + colspan : 1, + rowspan : 1, + + + // used by context menu + friendly_name : 'Table Cell', + deleteTitle : false, // use our customer delete + + // context menu is drawn once.. + + contextMenu : function(toolbar) + { + + var cell = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode); + }; + + var table = function() { + return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table')); + }; + + var lr = false; + var saveSel = function() + { + lr = toolbar.editorcore.getSelection().getRangeAt(0); + } + var restoreSel = function() + { + if (lr) { + (function() { + toolbar.editorcore.focus(); + var cr = toolbar.editorcore.getSelection(); + cr.removeAllRanges(); + cr.addRange(lr); + toolbar.editorcore.onEditorEvent(); + }).defer(10, this); + + + } + } + + var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap; + + var syncValue = toolbar.editorcore.syncValue; + + var fields = {}; + + return [ + { + xtype : 'Button', + text : 'Edit Table', + listeners : { + click : function() { + var t = toolbar.tb.selectedNode.closest('table'); + toolbar.editorcore.selectNode(t); + toolbar.editorcore.onEditorEvent(); + } + } + + }, + + + + { + xtype : 'TextItem', + text : "Column Width: ", + xns : rooui.Toolbar + + }, + { + xtype : 'Button', + text: '-', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().shrinkColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Button', + text: '+', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().growColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + + { + xtype : 'TextItem', + text : "Vertical Align: ", + xns : rooui.Toolbar //Boostrap? + }, + { + xtype : 'ComboBox', + allowBlank : false, + displayField : 'val', + editable : true, + listWidth : 100, + triggerAction : 'all', + typeAhead : true, + valueField : 'val', + width : 100, + name : 'valign', + listeners : { + select : function (combo, r, index) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + var b = cell(); + b.valign = r.get('val'); + b.updateElement(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.form, + store : { + xtype : 'SimpleStore', + data : [ + ['top'], + ['middle'], + ['bottom'] // there are afew more... + ], + fields : [ 'val'], + xns : Roo.data + } + }, + + { + xtype : 'TextItem', + text : "Merge Cells: ", + xns : rooui.Toolbar + + }, + + + { + xtype : 'Button', + text: 'Right', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().mergeRight(); + //block().growColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + + { + xtype : 'Button', + text: 'Below', + listeners : { + click : function (_self, e) + { + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().mergeBelow(); + //block().growColumn(); + syncValue(); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'TextItem', + text : "| ", + xns : rooui.Toolbar + + }, + + { + xtype : 'Button', + text: 'Split', + listeners : { + click : function (_self, e) + { + //toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + cell().split(); + syncValue(); + toolbar.editorcore.selectNode(toolbar.tb.selectedNode); + toolbar.editorcore.onEditorEvent(); + + } + }, + xns : rooui.Toolbar + }, + { + xtype : 'Fill', + xns : rooui.Toolbar + + }, + + + { + xtype : 'Button', + text: 'Delete', + + xns : rooui.Toolbar, + menu : { + xtype : 'Menu', + xns : rooui.menu, + items : [ + { + xtype : 'Item', + html: 'Column', + listeners : { + click : function (_self, e) + { + var t = table(); + + cell().deleteColumn(); + syncValue(); + toolbar.editorcore.selectNode(t.node); + toolbar.editorcore.onEditorEvent(); + } + }, + xns : rooui.menu + }, + { + xtype : 'Item', + html: 'Row', + listeners : { + click : function (_self, e) + { + var t = table(); + cell().deleteRow(); + syncValue(); + + toolbar.editorcore.selectNode(t.node); + toolbar.editorcore.onEditorEvent(); + + } + }, + xns : rooui.menu + }, + { + xtype : 'Separator', + xns : rooui.menu + }, + { + xtype : 'Item', + html: 'Table', + listeners : { + click : function (_self, e) + { + var t = table(); + var nn = t.node.nextSibling || t.node.previousSibling; + t.node.parentNode.removeChild(t.node); + if (nn) { + toolbar.editorcore.selectNode(nn, true); + } + toolbar.editorcore.onEditorEvent(); + + } + }, + xns : rooui.menu + } + ] + } + } + + // align... << fixme + + ]; + + }, + + + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * ?? should it be called with option to hide all editing features? + */ + /** + * create a DomHelper friendly object - for use with + * Roo.DomHelper.markup / overwrite / etc.. + * ?? should it be called with option to hide all editing features? + */ + toObject : function() + { + var ret = { + tag : 'td', + contenteditable : 'true', // this stops cell selection from picking the table. + 'data-block' : 'Td', + valign : this.valign, + style : { + 'text-align' : this.textAlign, + border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded? + 'border-collapse' : 'collapse', + padding : '6px', // 8 for desktop / 4 for mobile + 'vertical-align': this.valign + }, + html : this.html + }; + if (this.width != '') { + ret.width = this.width; + ret.style.width = this.width; + } + + + if (this.colspan > 1) { + ret.colspan = this.colspan ; + } + if (this.rowspan > 1) { + ret.rowspan = this.rowspan ; + } + + + + return ret; + + }, + + readElement : function(node) + { + node = node ? node : this.node ; + this.width = node.style.width; + this.colspan = Math.max(1,1*node.getAttribute('colspan')); + this.rowspan = Math.max(1,1*node.getAttribute('rowspan')); + this.html = node.innerHTML; + if (node.style.textAlign != '') { + this.textAlign = node.style.textAlign; + } + + + }, + + // the default cell object... at present... + emptyCell : function() { + return { + colspan : 1, + rowspan : 1, + textAlign : 'left', + html : " " // is this going to be editable now? + }; + + }, + + removeNode : function() + { + return this.node.closest('table'); + + }, + + cellData : false, + + colWidths : false, + + toTableArray : function() + { + var ret = []; + var tab = this.node.closest('tr').closest('table'); + Array.from(tab.rows).forEach(function(r, ri){ + ret[ri] = []; + }); + var rn = 0; + this.colWidths = []; + var all_auto = true; + Array.from(tab.rows).forEach(function(r, ri){ + + var cn = 0; + Array.from(r.cells).forEach(function(ce, ci){ + var c = { + cell : ce, + row : rn, + col: cn, + colspan : ce.colSpan, + rowspan : ce.rowSpan + }; + if (ce.isEqualNode(this.node)) { + this.cellData = c; + } + // if we have been filled up by a row? + if (typeof(ret[rn][cn]) != 'undefined') { + while(typeof(ret[rn][cn]) != 'undefined') { + cn++; + } + c.col = cn; + } + + if (typeof(this.colWidths[cn]) == 'undefined' && c.colspan < 2) { + this.colWidths[cn] = ce.style.width; + if (this.colWidths[cn] != '') { + all_auto = false; + } + } + + + if (c.colspan < 2 && c.rowspan < 2 ) { + ret[rn][cn] = c; + cn++; + return; + } + for(var j = 0; j < c.rowspan; j++) { + if (typeof(ret[rn+j]) == 'undefined') { + continue; // we have a problem.. + } + ret[rn+j][cn] = c; + for(var i = 0; i < c.colspan; i++) { + ret[rn+j][cn+i] = c; + } + } + + cn += c.colspan; + }, this); + rn++; + }, this); + + // initalize widths.? + // either all widths or no widths.. + if (all_auto) { + this.colWidths[0] = false; // no widths flag. + } + + + return ret; + + }, + + + + + mergeRight: function() + { + + // get the contents of the next cell along.. + var tr = this.node.closest('tr'); + var i = Array.prototype.indexOf.call(tr.childNodes, this.node); + if (i >= tr.childNodes.length - 1) { + return; // no cells on right to merge with. + } + var table = this.toTableArray(); + + if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') { + return; // nothing right? + } + var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan]; + // right cell - must be same rowspan and on the same row. + if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) { + return; // right hand side is not same rowspan. + } + + + + this.node.innerHTML += ' ' + rc.cell.innerHTML; + tr.removeChild(rc.cell); + this.colspan += rc.colspan; + this.node.setAttribute('colspan', this.colspan); + + var table = this.toTableArray(); + this.normalizeWidths(table); + this.updateWidths(table); + }, + + + mergeBelow : function() + { + var table = this.toTableArray(); + if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') { + return; // no row below + } + if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') { + return; // nothing right? + } + var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col]; + + if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) { + return; // right hand side is not same rowspan. + } + this.node.innerHTML = this.node.innerHTML + rc.cell.innerHTML ; + rc.cell.parentNode.removeChild(rc.cell); + this.rowspan += rc.rowspan; + this.node.setAttribute('rowspan', this.rowspan); + }, + + split: function() + { + if (this.node.rowSpan < 2 && this.node.colSpan < 2) { + return; + } + var table = this.toTableArray(); + var cd = this.cellData; + this.rowspan = 1; + this.colspan = 1; + + for(var r = cd.row; r < cd.row + cd.rowspan; r++) { + + + for(var c = cd.col; c < cd.col + cd.colspan; c++) { + if (r == cd.row && c == cd.col) { + this.node.removeAttribute('rowspan'); + this.node.removeAttribute('colspan'); + } + + var ntd = this.node.cloneNode(); // which col/row should be 0.. + ntd.removeAttribute('id'); + ntd.style.width = this.colWidths[c]; + ntd.innerHTML = ''; + table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1 }; + } + + } + this.redrawAllCells(table); + + }, + + + + redrawAllCells: function(table) + { + + + var tab = this.node.closest('tr').closest('table'); + var ctr = tab.rows[0].parentNode; + Array.from(tab.rows).forEach(function(r, ri){ + + Array.from(r.cells).forEach(function(ce, ci){ + ce.parentNode.removeChild(ce); + }); + r.parentNode.removeChild(r); + }); + for(var r = 0 ; r < table.length; r++) { + var re = tab.rows[r]; + + var re = tab.ownerDocument.createElement('tr'); + ctr.appendChild(re); + for(var c = 0 ; c < table[r].length; c++) { + if (table[r][c].cell === false) { + continue; + } + + re.appendChild(table[r][c].cell); + + table[r][c].cell = false; + } + } + + }, + updateWidths : function(table) + { + for(var r = 0 ; r < table.length; r++) { + + for(var c = 0 ; c < table[r].length; c++) { + if (table[r][c].cell === false) { + continue; + } + + if (this.colWidths[0] != false && table[r][c].colspan < 2) { + var el = Roo.htmleditor.Block.factory(table[r][c].cell); + el.width = Math.floor(this.colWidths[c]) +'%'; + el.updateElement(el.node); + } + if (this.colWidths[0] != false && table[r][c].colspan > 1) { + var el = Roo.htmleditor.Block.factory(table[r][c].cell); + var width = 0; + for(var i = 0; i < table[r][c].colspan; i ++) { + width += Math.floor(this.colWidths[c + i]); + } + el.width = width +'%'; + el.updateElement(el.node); + } + table[r][c].cell = false; // done + } + } + }, + normalizeWidths : function(table) + { + if (this.colWidths[0] === false) { + var nw = 100.0 / this.colWidths.length; + this.colWidths.forEach(function(w,i) { + this.colWidths[i] = nw; + },this); + return; + } + + var t = 0, missing = []; + + this.colWidths.forEach(function(w,i) { + //if you mix % and + this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1; + var add = this.colWidths[i]; + if (add > 0) { + t+=add; + return; + } + missing.push(i); + + + },this); + var nc = this.colWidths.length; + if (missing.length) { + var mult = (nc - missing.length) / (1.0 * nc); + var t = mult * t; + var ew = (100 -t) / (1.0 * missing.length); + this.colWidths.forEach(function(w,i) { + if (w > 0) { + this.colWidths[i] = w * mult; + return; + } + + this.colWidths[i] = ew; + }, this); + // have to make up numbers.. + + } + // now we should have all the widths.. + + + }, + + shrinkColumn : function() + { + var table = this.toTableArray(); + this.normalizeWidths(table); + var col = this.cellData.col; + var nw = this.colWidths[col] * 0.8; + if (nw < 5) { + return; + } + var otherAdd = (this.colWidths[col] * 0.2) / (this.colWidths.length -1); + this.colWidths.forEach(function(w,i) { + if (i == col) { + this.colWidths[i] = nw; + return; + } + this.colWidths[i] += otherAdd + }, this); + this.updateWidths(table); + + }, + growColumn : function() + { + var table = this.toTableArray(); + this.normalizeWidths(table); + var col = this.cellData.col; + var nw = this.colWidths[col] * 1.2; + if (nw > 90) { + return; + } + var otherSub = (this.colWidths[col] * 0.2) / (this.colWidths.length -1); + this.colWidths.forEach(function(w,i) { + if (i == col) { + this.colWidths[i] = nw; + return; + } + this.colWidths[i] -= otherSub + }, this); + this.updateWidths(table); + + }, + deleteRow : function() + { + // delete this rows 'tr' + // if any of the cells in this row have a rowspan > 1 && row!= this row.. + // then reduce the rowspan. + var table = this.toTableArray(); + // this.cellData.row; + for (var i =0;i< table[this.cellData.row].length ; i++) { + var c = table[this.cellData.row][i]; + if (c.row != this.cellData.row) { + + c.rowspan--; + c.cell.setAttribute('rowspan', c.rowspan); + continue; + } + if (c.rowspan > 1) { + c.rowspan--; + c.cell.setAttribute('rowspan', c.rowspan); + } + } + table.splice(this.cellData.row,1); + this.redrawAllCells(table); + + }, + deleteColumn : function() + { + var table = this.toTableArray(); + + for (var i =0;i< table.length ; i++) { + var c = table[i][this.cellData.col]; + if (c.col != this.cellData.col) { + table[i][this.cellData.col].colspan--; + } else if (c.colspan > 1) { + c.colspan--; + c.cell.setAttribute('colspan', c.colspan); + } + table[i].splice(this.cellData.col,1); + } + + this.redrawAllCells(table); + } + + + + +}) + +//