/**
* @class Roo.data.SortTypes
- * @singleton
+ * @static
* Defines the default sorting (casting?) comparison functions used when sorting data.
*/
Roo.data.SortTypes = {
*/
/**
- * @cfg {Roo.data.DataProxy} proxy The Proxy object which provides access to a data object.
+ * @cfg {Roo.data.DataProxy} proxy [required] The Proxy object which provides access to a data object.
*/
/**
* @cfg {Array} data Inline data to be loaded when the store is initialized.
*/
/**
- * @cfg {Roo.data.Reader} reader The Reader object which processes the data object and returns
+ * @cfg {Roo.data.DataReader} reader [required] The Reader object which processes the data object and returns
* an Array of Roo.data.record objects which are cached keyed by their <em>id</em> property.
*/
/**
* <p>
* @param {Object} options An object containing properties which control loading options:<ul>
* <li>params {Object} An object containing properties to pass as HTTP parameters to a remote data source.</li>
+ * <li>params.data {Object} if you are using a MemoryProxy / JsonReader, use this as the data to load stuff..
+ * <pre>
+ {
+ data : data, // array of key=>value data like JsonReader
+ total : data.length,
+ success : true
+
+ }
+ </pre>
+ }.</li>
* <li>callback {Function} A function to be called after the Records have been loaded. The callback is
* passed the following arguments:<ul>
* <li>r : Roo.data.Record[]</li>
// private
// Called as a callback by the Reader during a load operation.
loadRecords : function(o, options, success){
- if(!o || success === false){
+
+ if(!o){
if(success !== false){
this.fireEvent("load", this, [], options, o);
}
var r = this.reader.readRecords(o);
this.loadRecords(r, {add: append}, true);
},
+
+ /**
+ * using 'cn' the nested child reader read the child array into it's child stores.
+ * @param {Object} rec The record with a 'children array
+ */
+ loadDataFromChildren : function(rec)
+ {
+ this.loadData(this.reader.toLoadData(rec));
+ },
+
/**
* Gets the number of cached records.
* Small helper class to make creating Stores from Array data easier.
* @cfg {Number} id The array index of the record id. Leave blank to auto generate ids.
* @cfg {Array} fields An array of field definition objects, or field name strings.
+ * @cfg {Object} an existing reader (eg. copied from another store)
* @cfg {Array} data The multi-dimensional array of data
+ * @cfg {Roo.data.DataProxy} proxy [not-required]
+ * @cfg {Roo.data.Reader} reader [not-required]
* @constructor
* @param {Object} config
*/
-Roo.data.SimpleStore = function(config){
+Roo.data.SimpleStore = function(config)
+{
Roo.data.SimpleStore.superclass.constructor.call(this, {
isLocal : true,
- reader: new Roo.data.ArrayReader({
+ reader: typeof(config.reader) != 'undefined' ? config.reader : new Roo.data.ArrayReader({
id: config.id
},
Roo.data.Record.create(config.fields)
/**
* @class Roo.data.DataReader
+ * @abstract
* Base class for reading structured data from a data source. This class is intended to be
* extended (see {Roo.data.ArrayReader}, {Roo.data.JsonReader} and {Roo.data.XmlReader}) and should not be created directly.
*/
};
Roo.data.DataReader.prototype = {
+
+
+ readerType : 'Data',
/**
* Create an empty record
* @param {Object} data (optional) - overlay some values
return new this.recordType(Roo.apply(da, d));
}
+
};/*
* Based on:
* Ext JS Library 1.1.1
/**
* @class Roo.data.DataProxy
- * @extends Roo.data.Observable
+ * @extends Roo.util.Observable
+ * @abstract
* This class is an abstract base class for implementations which provide retrieval of
* unformatted data objects.<br>
* <p>
try {
result = o.reader.read(response);
}catch(e){
+ o.success = false;
+ o.raw = { errorMsg : response.responseText };
this.fireEvent("loadexception", this, o, response, e);
- o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ o.request.callback.call(o.request.scope, o, o.request.arg, false);
return;
}
};
Roo.extend(Roo.data.JsonReader, Roo.data.DataReader, {
+ readerType : 'Json',
+
/**
* @prop {Boolean} metaFromRemote - if the meta data was loaded from the remote source.
* Used by Store query builder to append _requestMeta to params.
}
var records = [];
for(var i = 0; i < c; i++){
- var n = root[i];
+ var n = root[i];
var values = {};
var id = this.getId(n);
for(var j = 0; j < fl; j++){
f = fi[j];
- var v = this.ef[j](n);
- if (!f.convert) {
- Roo.log('missing convert for ' + f.name);
- Roo.log(f);
- continue;
- }
- values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue);
- }
+ var v = this.ef[j](n);
+ if (!f.convert) {
+ Roo.log('missing convert for ' + f.name);
+ Roo.log(f);
+ continue;
+ }
+ values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue);
+ }
+ if (!Record) {
+ return {
+ raw : { errorMsg : "JSON Reader Error: fields or metadata not available to create Record" },
+ success : false,
+ records : [],
+ totalRecords : 0
+ };
+ }
var record = new Record(values, id);
record.json = n;
records[i] = record;
records : records,
totalRecords : totalRecords
};
+ },
+ // used when loading children.. @see loadDataFromChildren
+ toLoadData: function(rec)
+ {
+ // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
+ var data = typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
+ return { data : data, total : data.length };
+
}
});/*
* Based on:
Roo.data.XmlReader.superclass.constructor.call(this, meta, recordType||meta.fields);
};
Roo.extend(Roo.data.XmlReader, Roo.data.DataReader, {
+
+ readerType : 'Xml',
+
/**
* This method is only used by a DataProxy which has retrieved data from a remote server.
* @param {Object} response The XHR object which contains the parsed XML document. The response is expected
*
* created using {@link Roo.data.Record#create}.
*/
-Roo.data.ArrayReader = function(meta, recordType){
-
-
+Roo.data.ArrayReader = function(meta, recordType)
+{
Roo.data.ArrayReader.superclass.constructor.call(this, meta, recordType||meta.fields);
};
Roo.extend(Roo.data.ArrayReader, Roo.data.JsonReader, {
- /**
+
+ /**
* Create a data block containing Roo.data.Records from an XML document.
* @param {Object} o An Array of row objects which represents the dataset.
* @return {Object} A data block which is used by an {@link Roo.data.Store} object as
* a cache of Roo.data.Records.
*/
- readRecords : function(o){
+ readRecords : function(o)
+ {
var sid = this.meta ? this.meta.id : null;
- var recordType = this.recordType, fields = recordType.prototype.fields;
- var records = [];
- var root = o;
- for(var i = 0; i < root.length; i++){
- var n = root[i];
- var values = {};
- var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
- for(var j = 0, jlen = fields.length; j < jlen; j++){
+ var recordType = this.recordType, fields = recordType.prototype.fields;
+ var records = [];
+ var root = o;
+ for(var i = 0; i < root.length; i++){
+ var n = root[i];
+ var values = {};
+ var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
+ for(var j = 0, jlen = fields.length; j < jlen; j++){
var f = fields.items[j];
var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
var v = n[k] !== undefined ? n[k] : f.defaultValue;
v = f.convert(v);
values[f.name] = v;
}
- var record = new recordType(values, id);
- record.json = n;
- records[records.length] = record;
- }
- return {
- records : records,
- totalRecords : records.length
- };
+ var record = new recordType(values, id);
+ record.json = n;
+ records[records.length] = record;
+ }
+ return {
+ records : records,
+ totalRecords : records.length
+ };
+ },
+ // used when loading children.. @see loadDataFromChildren
+ toLoadData: function(rec)
+ {
+ // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
+ return typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
+
}
+
+
});/*
* Based on:
* Ext JS Library 1.1.1
* Fork - LGPL
* <script type="text/javascript">
*/
- (function(){
-/**
- * @class Roo.Layer
- * @extends Roo.Element
- * An extended {@link Roo.Element} object that supports a shadow and shim, constrain to viewport and
- * automatic maintaining of shadow/shim positions.
- * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
- * @cfg {String/Boolean} shadow True to create a shadow element with default class "x-layer-shadow", or
- * you can pass a string with a CSS class name. False turns off the shadow.
- * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: "div", cls: "x-layer"}).
- * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
- * @cfg {String} cls CSS class to add to the element
- * @cfg {Number} zindex Starting z-index (defaults to 11000)
- * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 3)
- * @constructor
- * @param {Object} config An object with config options.
- * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
- */
-
-Roo.Layer = function(config, existingEl){
- config = config || {};
- var dh = Roo.DomHelper;
- var cp = config.parentEl, pel = cp ? Roo.getDom(cp) : document.body;
- if(existingEl){
- this.dom = Roo.getDom(existingEl);
- }
- if(!this.dom){
- var o = config.dh || {tag: "div", cls: "x-layer"};
- this.dom = dh.append(pel, o);
- }
- if(config.cls){
- this.addClass(config.cls);
- }
- this.constrain = config.constrain !== false;
- this.visibilityMode = Roo.Element.VISIBILITY;
- if(config.id){
- this.id = this.dom.id = config.id;
- }else{
- this.id = Roo.id(this.dom);
- }
- this.zindex = config.zindex || this.getZIndex();
- this.position("absolute", this.zindex);
- if(config.shadow){
- this.shadowOffset = config.shadowOffset || 4;
- this.shadow = new Roo.Shadow({
- offset : this.shadowOffset,
- mode : config.shadow
- });
- }else{
- this.shadowOffset = 0;
- }
- this.useShim = config.shim !== false && Roo.useShims;
- this.useDisplay = config.useDisplay;
- this.hide();
-};
-
-var supr = Roo.Element.prototype;
-
-// shims are shared among layer to keep from having 100 iframes
-var shims = [];
-
-Roo.extend(Roo.Layer, Roo.Element, {
-
- getZIndex : function(){
- return this.zindex || parseInt(this.getStyle("z-index"), 10) || 11000;
- },
-
- getShim : function(){
- if(!this.useShim){
- return null;
- }
- if(this.shim){
- return this.shim;
- }
- var shim = shims.shift();
- if(!shim){
- shim = this.createShim();
- shim.enableDisplayMode('block');
- shim.dom.style.display = 'none';
- shim.dom.style.visibility = 'visible';
- }
- var pn = this.dom.parentNode;
- if(shim.dom.parentNode != pn){
- pn.insertBefore(shim.dom, this.dom);
- }
- shim.setStyle('z-index', this.getZIndex()-2);
- this.shim = shim;
- return shim;
- },
-
- hideShim : function(){
- if(this.shim){
- this.shim.setDisplayed(false);
- shims.push(this.shim);
- delete this.shim;
- }
- },
-
- disableShadow : function(){
- if(this.shadow){
- this.shadowDisabled = true;
- this.shadow.hide();
- this.lastShadowOffset = this.shadowOffset;
- this.shadowOffset = 0;
- }
- },
-
- enableShadow : function(show){
- if(this.shadow){
- this.shadowDisabled = false;
- this.shadowOffset = this.lastShadowOffset;
- delete this.lastShadowOffset;
- if(show){
- this.sync(true);
- }
- }
- },
-
- // private
- // this code can execute repeatedly in milliseconds (i.e. during a drag) so
- // code size was sacrificed for effeciency (e.g. no getBox/setBox, no XY calls)
- sync : function(doShow){
- var sw = this.shadow;
- if(!this.updating && this.isVisible() && (sw || this.useShim)){
- var sh = this.getShim();
-
- var w = this.getWidth(),
- h = this.getHeight();
-
- var l = this.getLeft(true),
- t = this.getTop(true);
-
- if(sw && !this.shadowDisabled){
- if(doShow && !sw.isVisible()){
- sw.show(this);
- }else{
- sw.realign(l, t, w, h);
- }
- if(sh){
- if(doShow){
- sh.show();
- }
- // fit the shim behind the shadow, so it is shimmed too
- var a = sw.adjusts, s = sh.dom.style;
- s.left = (Math.min(l, l+a.l))+"px";
- s.top = (Math.min(t, t+a.t))+"px";
- s.width = (w+a.w)+"px";
- s.height = (h+a.h)+"px";
- }
- }else if(sh){
- if(doShow){
- sh.show();
- }
- sh.setSize(w, h);
- sh.setLeftTop(l, t);
- }
-
- }
- },
-
- // private
- destroy : function(){
- this.hideShim();
- if(this.shadow){
- this.shadow.hide();
- }
- this.removeAllListeners();
- var pn = this.dom.parentNode;
- if(pn){
- pn.removeChild(this.dom);
- }
- Roo.Element.uncache(this.id);
- },
-
- remove : function(){
- this.destroy();
- },
-
- // private
- beginUpdate : function(){
- this.updating = true;
- },
-
- // private
- endUpdate : function(){
- this.updating = false;
- this.sync(true);
- },
-
- // private
- hideUnders : function(negOffset){
- if(this.shadow){
- this.shadow.hide();
- }
- this.hideShim();
- },
-
- // private
- constrainXY : function(){
- if(this.constrain){
- var vw = Roo.lib.Dom.getViewWidth(),
- vh = Roo.lib.Dom.getViewHeight();
- var s = Roo.get(document).getScroll();
-
- var xy = this.getXY();
- var x = xy[0], y = xy[1];
- var w = this.dom.offsetWidth+this.shadowOffset, h = this.dom.offsetHeight+this.shadowOffset;
- // only move it if it needs it
- var moved = false;
- // first validate right/bottom
- if((x + w) > vw+s.left){
- x = vw - w - this.shadowOffset;
- moved = true;
- }
- if((y + h) > vh+s.top){
- y = vh - h - this.shadowOffset;
- moved = true;
- }
- // then make sure top/left isn't negative
- if(x < s.left){
- x = s.left;
- moved = true;
- }
- if(y < s.top){
- y = s.top;
- moved = true;
- }
- if(moved){
- if(this.avoidY){
- var ay = this.avoidY;
- if(y <= ay && (y+h) >= ay){
- y = ay-h-5;
- }
- }
- xy = [x, y];
- this.storeXY(xy);
- supr.setXY.call(this, xy);
- this.sync();
- }
- }
- },
-
- isVisible : function(){
- return this.visible;
- },
-
- // private
- showAction : function(){
- this.visible = true; // track visibility to prevent getStyle calls
- if(this.useDisplay === true){
- this.setDisplayed("");
- }else if(this.lastXY){
- supr.setXY.call(this, this.lastXY);
- }else if(this.lastLT){
- supr.setLeftTop.call(this, this.lastLT[0], this.lastLT[1]);
- }
- },
-
- // private
- hideAction : function(){
- this.visible = false;
- if(this.useDisplay === true){
- this.setDisplayed(false);
- }else{
- this.setLeftTop(-10000,-10000);
- }
- },
-
- // overridden Element method
- setVisible : function(v, a, d, c, e){
- if(v){
- this.showAction();
- }
- if(a && v){
- var cb = function(){
- this.sync(true);
- if(c){
- c();
- }
- }.createDelegate(this);
- supr.setVisible.call(this, true, true, d, cb, e);
- }else{
- if(!v){
- this.hideUnders(true);
- }
- var cb = c;
- if(a){
- cb = function(){
- this.hideAction();
- if(c){
- c();
- }
- }.createDelegate(this);
- }
- supr.setVisible.call(this, v, a, d, cb, e);
- if(v){
- this.sync(true);
- }else if(!a){
- this.hideAction();
- }
- }
- },
-
- storeXY : function(xy){
- delete this.lastLT;
- this.lastXY = xy;
- },
-
- storeLeftTop : function(left, top){
- delete this.lastXY;
- this.lastLT = [left, top];
- },
-
- // private
- beforeFx : function(){
- this.beforeAction();
- return Roo.Layer.superclass.beforeFx.apply(this, arguments);
- },
-
- // private
- afterFx : function(){
- Roo.Layer.superclass.afterFx.apply(this, arguments);
- this.sync(this.isVisible());
- },
-
- // private
- beforeAction : function(){
- if(!this.updating && this.shadow){
- this.shadow.hide();
- }
- },
-
- // overridden Element method
- setLeft : function(left){
- this.storeLeftTop(left, this.getTop(true));
- supr.setLeft.apply(this, arguments);
- this.sync();
- },
-
- setTop : function(top){
- this.storeLeftTop(this.getLeft(true), top);
- supr.setTop.apply(this, arguments);
- this.sync();
- },
-
- setLeftTop : function(left, top){
- this.storeLeftTop(left, top);
- supr.setLeftTop.apply(this, arguments);
- this.sync();
- },
-
- setXY : function(xy, a, d, c, e){
- this.fixDisplay();
- this.beforeAction();
- this.storeXY(xy);
- var cb = this.createCB(c);
- supr.setXY.call(this, xy, a, d, cb, e);
- if(!a){
- cb();
- }
- },
-
- // private
- createCB : function(c){
- var el = this;
- return function(){
- el.constrainXY();
- el.sync(true);
- if(c){
- c();
- }
- };
- },
-
- // overridden Element method
- setX : function(x, a, d, c, e){
- this.setXY([x, this.getY()], a, d, c, e);
- },
-
- // overridden Element method
- setY : function(y, a, d, c, e){
- this.setXY([this.getX(), y], a, d, c, e);
- },
-
- // overridden Element method
- setSize : function(w, h, a, d, c, e){
- this.beforeAction();
- var cb = this.createCB(c);
- supr.setSize.call(this, w, h, a, d, cb, e);
- if(!a){
- cb();
- }
- },
-
- // overridden Element method
- setWidth : function(w, a, d, c, e){
- this.beforeAction();
- var cb = this.createCB(c);
- supr.setWidth.call(this, w, a, d, cb, e);
- if(!a){
- cb();
- }
- },
-
- // overridden Element method
- setHeight : function(h, a, d, c, e){
- this.beforeAction();
- var cb = this.createCB(c);
- supr.setHeight.call(this, h, a, d, cb, e);
- if(!a){
- cb();
- }
- },
-
- // overridden Element method
- setBounds : function(x, y, w, h, a, d, c, e){
- this.beforeAction();
- var cb = this.createCB(c);
- if(!a){
- this.storeXY([x, y]);
- supr.setXY.call(this, [x, y]);
- supr.setSize.call(this, w, h, a, d, cb, e);
- cb();
- }else{
- supr.setBounds.call(this, x, y, w, h, a, d, cb, e);
- }
- return this;
- },
-
- /**
- * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
- * incremented by two more than the value passed in so that it always shows above any shadow or shim (the shadow
- * element, if any, will be assigned z-index + 1, and the shim element, if any, will be assigned the unmodified z-index).
- * @param {Number} zindex The new z-index to set
- * @return {this} The Layer
- */
- setZIndex : function(zindex){
- this.zindex = zindex;
- this.setStyle("z-index", zindex + 2);
- if(this.shadow){
- this.shadow.setZIndex(zindex + 1);
- }
- if(this.shim){
- this.shim.setStyle("z-index", zindex);
- }
- }
-});
-})();/*
- * Based on:
- * Ext JS Library 1.1.1
- * Copyright(c) 2006-2007, Ext JS, LLC.
- *
- * Originally Released Under LGPL - original licence link has changed is not relivant.
- *
- * Fork - LGPL
- * <script type="text/javascript">
- */
/**
* frame: Shadow displays equally on all four sides<br />
* drop: Traditional bottom-right drop shadow (default)
*/
+ mode: false,
/**
* @cfg {String} offset
* The number of pixels to offset the shadow from the element (defaults to 4)
*/
enableToggle: false,
/**
- * @cfg {Mixed} menu
+ * @cfg {Roo.menu.Menu} menu
* Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
*/
menu : undefined,
/**
* @class Roo.Toolbar
+ * @children Roo.Toolbar.Item Roo.Toolbar.Button Roo.Toolbar.SplitButton Roo.form.Field
* Basic Toolbar class.
* @constructor
* Creates a new Toolbar
* @cfg {Array} items
* array of button configs or elements to add (will be converted to a MixedCollection)
*/
-
+ items: false,
/**
* @cfg {String/HTMLElement/Element} container
* The id or element that will contain the toolbar
* A simple class that renders text directly into a toolbar.
* @constructor
* Creates a new TextItem
- * @param {String} text
+ * @cfg {string} text
*/
Roo.Toolbar.TextItem = function(cfg){
var text = cfg || "";
enable:Roo.emptyFn,
disable:Roo.emptyFn,
- focus:Roo.emptyFn
+ focus:Roo.emptyFn,
+ /**
+ * Shows this button
+ */
+ show: function(){
+ this.hidden = false;
+ this.el.style.display = "";
+ },
+
+ /**
+ * Hides this button
+ */
+ hide: function(){
+ this.hidden = true;
+ this.el.style.display = "none";
+ }
+
});
/**
Roo.Toolbar.Button = function(config){
Roo.Toolbar.Button.superclass.constructor.call(this, null, config);
};
-Roo.extend(Roo.Toolbar.Button, Roo.Button, {
+Roo.extend(Roo.Toolbar.Button, Roo.Button,
+{
+
+
render : function(td){
this.td = td;
Roo.Toolbar.Button.superclass.render.call(this, td);
/**
* @class Roo.PagingToolbar
* @extends Roo.Toolbar
+ * @children Roo.Toolbar.Item Roo.Toolbar.Button Roo.Toolbar.SplitButton Roo.form.Field
* A specialized toolbar that is bound to a {@link Roo.data.Store} and provides automatic paging controls.
* @constructor
* Create a new PagingToolbar
};
Roo.extend(Roo.PagingToolbar, Roo.Toolbar, {
- /**
- * @cfg {Roo.data.Store} dataSource
- * The underlying data store providing the paged data
- */
+
/**
* @cfg {String/HTMLElement/Element} container
* container The id or element that will contain the toolbar
* @cfg {Boolean} displayInfo
* True to display the displayMsg (defaults to false)
*/
+
+
/**
* @cfg {Number} pageSize
* The number of records to display per page (defaults to 20)
this.loading.disable();
}
},
-
+ /**
+ * event that occurs when you click on the navigation buttons - can be used to trigger load of a grid.
+ * @param {String} which (first|prev|next|last|refresh) which button to press.
+ *
+ */
// private
onClick : function(which){
var ds = this.ds;
/**
* @class Roo.BasicDialog
* @extends Roo.util.Observable
+ * @parent none builder
* Lightweight Dialog Class. The code below shows the creation of a typical dialog using existing HTML markup:
* <pre><code>
var dlg = new Roo.BasicDialog("my-dlg", {
/**
* @class Roo.LayoutDialog
* @extends Roo.BasicDialog
+ * @children Roo.ContentPanel
+ * @parent builder none
* Dialog which provides adjustments for working with a layout in a Dialog.
* Add your necessary layout config options to the dialog's config.<br>
* Example usage (including a nested layout):
};
Roo.extend(Roo.LayoutDialog, Roo.BasicDialog, {
+
+
+ /**
+ * @cfg {Roo.LayoutRegion} east
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} west
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} south
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} north
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} center
+ */
+ /**
+ * @cfg {Roo.Button} buttons[] Bottom buttons..
+ */
+
+
/**
* Ends update of the layout <strike>and resets display to none</strike>. Use standard beginUpdate/endUpdate on the layout.
* @deprecated
/**
* @class Roo.MessageBox
+ * @static
* Utility class for generating different styles of message boxes. The alias Roo.Msg can also be used.
* Example usage:
*<pre><code>
animEl: 'elId'
});
</code></pre>
- * @singleton
+ * @static
*/
Roo.MessageBox = function(){
var dlg, opt, mask, waitTimer;
}
}
});
+
dlg.on("hide", handleHide);
mask = dlg.mask;
dlg.addKeyListener(27, handleEsc);
d.animateTarget = null;
d.show(options.animEl);
}
+ dlg.toFront();
return this;
},
/**
* @class Roo.QuickTips
* Provides attractive and customizable tooltips for any element.
- * @singleton
+ * @static
*/
Roo.QuickTips = function(){
var el, tipBody, tipBodyText, tipTitle, tm, cfg, close, tagEls = {}, esc, removeCls = null, bdLeft, bdRight;
/**
* @class Roo.tree.TreePanel
* @extends Roo.data.Tree
-
+ * @cfg {Roo.tree.TreeNode} root The root node
* @cfg {Boolean} rootVisible false to hide the root node (defaults to true)
* @cfg {Boolean} lines false to disable tree lines (defaults to true)
* @cfg {Boolean} enableDD true to enable drag and drop
* @cfg {Boolean} animate true to enable animated expand/collapse (defaults to the value of Roo.enableFx)
* @cfg {Boolean} singleExpand true if only 1 node per branch may be expanded
* @cfg {Boolean} selModel A tree selection model to use with this TreePanel (defaults to a {@link Roo.tree.DefaultSelectionModel})
- * @cfg {Boolean} loader A TreeLoader for use with this TreePanel
- * @cfg {Object|Roo.tree.TreeEditor} editor The TreeEditor or xtype data to display when clicked.
+ * @cfg {Roo.tree.TreeLoader} loader A TreeLoader for use with this TreePanel
+ * @cfg {Roo.tree.TreeEditor} editor The TreeEditor to display when clicked.
* @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to '/')
* @cfg {Function} renderer DEPRECATED - use TreeLoader:create event / Sets the rendering (formatting) function for the nodes. to return HTML markup for the tree view. The render function is called with the following parameters:<ul><li>The {Object} The data for the node.</li></ul>
* @cfg {Function} rendererTip DEPRECATED - use TreeLoader:create event / Sets the rendering (formatting) function for the nodes hovertip to return HTML markup for the tree view. The render function is called with the following parameters:<ul><li>The {Object} The data for the node.</li></ul>
* @param {Object} oldconfig DEPRECIATED Either a prebuilt {@link Roo.form.Field} instance or a Field config object
*
* @cfg {Roo.tree.TreePanel} tree The tree to bind to.
- * @cfg {Roo.form.TextField|Object} field The field configuration
+ * @cfg {Roo.form.TextField} field [required] The field configuration
*
*
*/
/**
* @class Roo.tree.ColumnTree
- * @extends Roo.data.TreePanel
+ * @extends Roo.tree.TreePanel
* @cfg {Object} columns Including width, header, renderer, cls, dataIndex
* @cfg {int} borderWidth compined right/left border allowance
* @constructor
/**
* @class Roo.menu.Menu
* @extends Roo.util.Observable
+ * @children Roo.menu.Item Roo.menu.Separator Roo.menu.TextItem
* A menu object. This is the container to which you add all other menu items. Menu can also serve a as a base class
* when you want a specialzed menu based off of another component (like {@link Roo.menu.DateMenu} for example).
* @constructor
/**
* @class Roo.menu.MenuMgr
* Provides a common registry of all menu items on a page so that they can be easily accessed by id.
- * @singleton
+ * @static
*/
Roo.menu.MenuMgr = function(){
var menus, active, groups = {}, attached = false, lastShow = new Date();
/**
* @class Roo.menu.BaseItem
* @extends Roo.Component
+ * @abstract
* The base class for all items that render into menus. BaseItem provides default rendering, activated state
* management and base configuration options shared by all menu components.
* @constructor
/**
* @class Roo.menu.Adapter
* @extends Roo.menu.BaseItem
+ * @abstract
* A base utility class that adapts a non-menu component so that it can be wrapped by a menu item and added to a menu.
* It provides basic rendering, activation management and enable/disable logic required to work in menus.
* @constructor
Roo.extend(Roo.menu.TextItem, Roo.menu.BaseItem, {
/**
- * @cfg {Boolean} text Text to show on item.
+ * @cfg {String} text Text to show on item.
*/
text : '',
}
};
Roo.extend(Roo.menu.Item, Roo.menu.BaseItem, {
-
+ /**
+ * @cfg {Roo.menu.Menu} menu
+ * A Sub menu
+ */
/**
* @cfg {String} text
* The text to show on the menu item.
*/
text: '',
/**
- * @cfg {String} HTML to render in menu
+ * @cfg {String} html to render in menu
* The text to show on the menu item (HTML version).
*/
html: '',
}
this.el = ct.createChild(cfg, position);
}
+ },
+ /*
+ * setHTML
+ * @param {String} html update the Contents of the element.
+ */
+ setHTML : function(html)
+ {
+ this.fieldEl.dom.innerHTML = html;
}
});/*
* @cfg {Boolean} selectOnFocus True to automatically select any existing field text when the field receives input focus (defaults to false)
*/
selectOnFocus : false,
+ /**
+ * @cfg {Boolean} allowLeadingSpace True to prevent the stripping of leading white space
+ */
+ allowLeadingSpace : false,
/**
* @cfg {String} blankText Error text to display if the allow blank validation fails (defaults to "This field is required")
*/
if(this.selectOnFocus){
this.on("focus", this.preFocus, this);
-
}
+ if (!this.allowLeadingSpace) {
+ this.on('blur', this.cleanLeadingSpace, this);
+ }
+
if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
this.el.on("keypress", this.filterKeys, this);
}
this.autoSize();
}
},
-
+ // private - clean the leading white space
+ cleanLeadingSpace : function(e)
+ {
+ if ( this.inputType == 'file') {
+ return;
+ }
+
+ this.setValue((this.getValue() + '').replace(/^\s+/,''));
+ },
/**
* Resets the current field value to the originally-loaded value and clears any validation messages.
*
reset : function(){
Roo.form.TextField.superclass.reset.call(this);
- },
-
-
+ },
// private
preFocus : function(){
* Create a new DateField
* @param {Object} config
*/
-Roo.form.DateField = function(config){
+Roo.form.DateField = function(config)
+{
Roo.form.DateField.superclass.constructor.call(this, config);
this.addEvents({
* The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled')
*/
disabledDatesText : "Disabled",
+
+
+ /**
+ * @cfg {Date/String} zeroValue
+ * if the date is less that this number, then the field is rendered as empty
+ * default is 1800
+ */
+ zeroValue : '1800-01-01',
+
+
/**
* @cfg {Date/String} minValue
* The minimum allowed date. Can be either a Javascript date object or a string date in a
// private
parseDate : function(value){
+
+ if (value instanceof Date) {
+ if (value < Date.parseDate(this.zeroValue, 'Y-m-d') ) {
+ return '';
+ }
+ return value;
+ }
+
+
if(!value || value instanceof Date){
return value;
}
v = Date.parseDate(value, this.altFormatsArray[i]);
}
}
+ if (v < Date.parseDate(this.zeroValue, 'Y-m-d') ) {
+ v = '';
+ }
return v;
},
return String(this.getValue()) !== String(this.startValue);
+ },
+ // @overide
+ cleanLeadingSpace : function(e)
+ {
+ return;
}
+
});/*
* Based on:
* Ext JS Library 1.1.1
// element that contains real text value.. (when hidden is used..)
// private
- onRender : function(ct, position){
+ onRender : function(ct, position)
+ {
Roo.form.ComboBox.superclass.onRender.call(this, ct, position);
- if(this.hiddenName){
+
+ if(this.hiddenName){
this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id: (this.hiddenId||this.hiddenName)},
'before', true);
this.hiddenField.value =
}
+
if(Roo.isGecko){
this.el.dom.setAttribute('autocomplete', 'off');
}
}
this.view = new Roo.View(this.innerList, this.tpl, {
- singleSelect:true, store: this.store, selectedClass: this.selectedClass
+ singleSelect:true,
+ store: this.store,
+ selectedClass: this.selectedClass
});
this.view.on('click', this.onViewClick, this);
this.view.select(match);
var sn = Roo.get(this.view.getSelectedNodes()[0]);
sn.scrollIntoView(sn.dom.parentNode, false);
- }
+ }
/**
* @cfg {Boolean} grow
Roo.extend(Roo.form.ComboBoxArray, Roo.form.TextField,
{
/**
- * @cfg {Roo.form.Combo} combo The combo box that is wrapped
+ * @cfg {Roo.form.ComboBox} combo [required] The combo box that is wrapped
*/
lastData : false,
* @cfg {String} hiddenName The hidden name of the field, often contains an comma seperated list of names
*/
hiddenName : false,
-
+ /**
+ * @cfg {String} seperator The value seperator normally ','
+ */
+ seperator : ',',
// private the array of items that are displayed..
items : false,
// give fake names to child combo;
this.combo.hiddenName = this.hiddenName ? (this.hiddenName+'-subcombo') : this.hiddenName;
- this.combo.name = this.name? (this.name+'-subcombo') : this.name;
+ this.combo.name = this.name ? (this.name+'-subcombo') : this.name;
this.combo = Roo.factory(this.combo, Roo.form);
this.combo.onRender(ct, position);
{
var valueField = this.combo.valueField;
var displayField = this.combo.displayField;
+
if (this.items.indexOfKey(rec[valueField]) > -1) {
//console.log("GOT " + rec.data.id);
return;
this.items.each(function(f) {
ar.push(f.data[idField]);
-
});
- this.hiddenEl.dom.value = ar.join(',');
+ this.hiddenEl.dom.value = ar.join(this.seperator);
this.validate();
},
},
setValue: function(v) // not a valid action - must use addItems..
{
-
- this.reset();
-
-
+ this.reset();
+
if (this.store.isLocal && (typeof(v) == 'string')) {
// then we can use the store to find the values..
// comma seperated at present.. this needs to allow JSON based encoding..
this.hiddenEl.value = v;
var v_ar = [];
- Roo.each(v.split(','), function(k) {
+ Roo.each(v.split(this.seperator), function(k) {
Roo.log("CHECK " + this.valueField + ',' + k);
var li = this.store.query(this.valueField, k);
if (!li.length) {
if (typeof(v) == 'object' ) {
// then let's assume it's an array of objects..
Roo.each(v, function(l) {
- this.addItem(l);
+ var add = l;
+ if (typeof(l) == 'string') {
+ add = {};
+ add[this.valueField] = l;
+ add[this.displayField] = l
+ }
+ this.addItem(add);
}, this);
}
dv = typeof(dv) != 'string' ? '' : dv;
- var keys = kv.split(',');
- var display = dv.split(',');
+ var keys = kv.split(this.seperator);
+ var display = dv.split(this.seperator);
for (var i = 0 ; i < keys.length; i++) {
-
add = {};
add[this.valueField] = keys[i];
add[this.displayField] = display[i];
originalValue.push(d[i][this.valueField]);
}
- return String(this.getValue()) !== String(originalValue.join(','));
+ return String(this.getValue()) !== String(originalValue.join(this.seperator));
}
}
}
+});/*
+ * RooJS Library 1.1.1
+ * Copyright(c) 2008-2011 Alan Knowles
+ *
+ * License - LGPL
+ */
+
+
+/**
+ * @class Roo.form.ComboNested
+ * @extends Roo.form.ComboBox
+ * A combobox for that allows selection of nested items in a list,
+ * eg.
+ *
+ * Book
+ * -> red
+ * -> green
+ * Table
+ * -> square
+ * ->red
+ * ->green
+ * -> rectangle
+ * ->green
+ *
+ *
+ * @constructor
+ * Create a new ComboNested
+ * @param {Object} config Configuration options
+ */
+Roo.form.ComboNested = function(config){
+ Roo.form.ComboCheck.superclass.constructor.call(this, config);
+ // should verify some data...
+ // like
+ // hiddenName = required..
+ // displayField = required
+ // valudField == required
+ var req= [ 'hiddenName', 'displayField', 'valueField' ];
+ var _t = this;
+ Roo.each(req, function(e) {
+ if ((typeof(_t[e]) == 'undefined' ) || !_t[e].length) {
+ throw "Roo.form.ComboNested : missing value for: " + e;
+ }
+ });
+
+
+};
+
+Roo.extend(Roo.form.ComboNested, Roo.form.ComboBox, {
+
+ /*
+ * @config {Number} max Number of columns to show
+ */
+
+ maxColumns : 3,
+
+ list : null, // the outermost div..
+ innerLists : null, // the
+ views : null,
+ stores : null,
+ // private
+ loadingChildren : false,
+
+ onRender : function(ct, position)
+ {
+ Roo.form.ComboBox.superclass.onRender.call(this, ct, position); // skip parent call - got to above..
+
+ if(this.hiddenName){
+ this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id: (this.hiddenId||this.hiddenName)},
+ 'before', true);
+ this.hiddenField.value =
+ this.hiddenValue !== undefined ? this.hiddenValue :
+ this.value !== undefined ? this.value : '';
+
+ // prevent input submission
+ this.el.dom.removeAttribute('name');
+
+
+ }
+
+ if(Roo.isGecko){
+ this.el.dom.setAttribute('autocomplete', 'off');
+ }
+
+ var cls = 'x-combo-list';
+
+ this.list = new Roo.Layer({
+ shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
+ });
+
+ var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
+ this.list.setWidth(lw);
+ this.list.swallowEvent('mousewheel');
+ this.assetHeight = 0;
+
+ if(this.title){
+ this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
+ this.assetHeight += this.header.getHeight();
+ }
+ this.innerLists = [];
+ this.views = [];
+ this.stores = [];
+ for (var i =0 ; i < this.maxColumns; i++) {
+ this.onRenderList( cls, i);
+ }
+
+ // always needs footer, as we are going to have an 'OK' button.
+ this.footer = this.list.createChild({cls:cls+'-ft'});
+ this.pageTb = new Roo.Toolbar(this.footer);
+ var _this = this;
+ this.pageTb.add( {
+
+ text: 'Done',
+ handler: function()
+ {
+ _this.collapse();
+ }
+ });
+
+ if ( this.allowBlank && !this.disableClear) {
+
+ this.pageTb.add(new Roo.Toolbar.Fill(), {
+ cls: 'x-btn-icon x-btn-clear',
+ text: ' ',
+ handler: function()
+ {
+ _this.collapse();
+ _this.clearValue();
+ _this.onSelect(false, -1);
+ }
+ });
+ }
+ if (this.footer) {
+ this.assetHeight += this.footer.getHeight();
+ }
+
+ },
+ onRenderList : function ( cls, i)
+ {
+
+ var lw = Math.floor(
+ ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
+ );
+
+ this.list.setWidth(lw); // default to '1'
+
+ var il = this.innerLists[i] = this.list.createChild({cls:cls+'-inner'});
+ //il.on('mouseover', this.onViewOver, this, { list: i });
+ //il.on('mousemove', this.onViewMove, this, { list: i });
+ il.setWidth(lw);
+ il.setStyle({ 'overflow-x' : 'hidden'});
+
+ if(!this.tpl){
+ this.tpl = new Roo.Template({
+ html : '<div class="'+cls+'-item '+cls+'-item-{cn:this.isEmpty}">{' + this.displayField + '}</div>',
+ isEmpty: function (value, allValues) {
+ //Roo.log(value);
+ var dl = typeof(value.data) != 'undefined' ? value.data.length : value.length; ///json is a nested response..
+ return dl ? 'has-children' : 'no-children'
+ }
+ });
+ }
+
+ var store = this.store;
+ if (i > 0) {
+ store = new Roo.data.SimpleStore({
+ //fields : this.store.reader.meta.fields,
+ reader : this.store.reader,
+ data : [ ]
+ });
+ }
+ this.stores[i] = store;
+
+ var view = this.views[i] = new Roo.View(
+ il,
+ this.tpl,
+ {
+ singleSelect:true,
+ store: store,
+ selectedClass: this.selectedClass
+ }
+ );
+ view.getEl().setWidth(lw);
+ view.getEl().setStyle({
+ position: i < 1 ? 'relative' : 'absolute',
+ top: 0,
+ left: (i * lw ) + 'px',
+ display : i > 0 ? 'none' : 'block'
+ });
+ view.on('selectionchange', this.onSelectChange.createDelegate(this, {list : i }, true));
+ view.on('dblclick', this.onDoubleClick.createDelegate(this, {list : i }, true));
+ //view.on('click', this.onViewClick, this, { list : i });
+
+ store.on('beforeload', this.onBeforeLoad, this);
+ store.on('load', this.onLoad, this, { list : i});
+ store.on('loadexception', this.onLoadException, this);
+
+ // hide the other vies..
+
+
+
+ },
+
+ restrictHeight : function()
+ {
+ var mh = 0;
+ Roo.each(this.innerLists, function(il,i) {
+ var el = this.views[i].getEl();
+ el.dom.style.height = '';
+ var inner = el.dom;
+ var h = Math.max(il.clientHeight, il.offsetHeight, il.scrollHeight);
+ // only adjust heights on other ones..
+ mh = Math.max(h, mh);
+ if (i < 1) {
+
+ el.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
+ il.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
+
+ }
+
+
+ }, this);
+
+ this.list.beginUpdate();
+ this.list.setHeight(mh+this.list.getFrameWidth('tb')+this.assetHeight);
+ this.list.alignTo(this.el, this.listAlign);
+ this.list.endUpdate();
+
+ },
+
+
+ // -- store handlers..
+ // private
+ onBeforeLoad : function()
+ {
+ if(!this.hasFocus){
+ return;
+ }
+ this.innerLists[0].update(this.loadingText ?
+ '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
+ this.restrictHeight();
+ this.selectedIndex = -1;
+ },
+ // private
+ onLoad : function(a,b,c,d)
+ {
+ if (!this.loadingChildren) {
+ // then we are loading the top level. - hide the children
+ for (var i = 1;i < this.views.length; i++) {
+ this.views[i].getEl().setStyle({ display : 'none' });
+ }
+ var lw = Math.floor(
+ ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
+ );
+
+ this.list.setWidth(lw); // default to '1'
+
+
+ }
+ if(!this.hasFocus){
+ return;
+ }
+
+ if(this.store.getCount() > 0) {
+ this.expand();
+ this.restrictHeight();
+ } else {
+ this.onEmptyResults();
+ }
+
+ if (!this.loadingChildren) {
+ this.selectActive();
+ }
+ /*
+ this.stores[1].loadData([]);
+ this.stores[2].loadData([]);
+ this.views
+ */
+
+ //this.el.focus();
+ },
+
+
+ // private
+ onLoadException : function()
+ {
+ this.collapse();
+ Roo.log(this.store.reader.jsonData);
+ if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
+ Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
+ }
+
+
+ },
+ // no cleaning of leading spaces on blur here.
+ cleanLeadingSpace : function(e) { },
+
+
+ onSelectChange : function (view, sels, opts )
+ {
+ var ix = view.getSelectedIndexes();
+
+ if (opts.list > this.maxColumns - 2) {
+ if (view.store.getCount()< 1) {
+ this.views[opts.list ].getEl().setStyle({ display : 'none' });
+
+ } else {
+ if (ix.length) {
+ // used to clear ?? but if we are loading unselected
+ this.setFromData(view.store.getAt(ix[0]).data);
+ }
+
+ }
+
+ return;
+ }
+
+ if (!ix.length) {
+ // this get's fired when trigger opens..
+ // this.setFromData({});
+ var str = this.stores[opts.list+1];
+ str.data.clear(); // removeall wihtout the fire events..
+ return;
+ }
+
+ var rec = view.store.getAt(ix[0]);
+
+ this.setFromData(rec.data);
+ this.fireEvent('select', this, rec, ix[0]);
+
+ var lw = Math.floor(
+ (
+ (this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')
+ ) / this.maxColumns
+ );
+ this.loadingChildren = true;
+ this.stores[opts.list+1].loadDataFromChildren( rec );
+ this.loadingChildren = false;
+ var dl = this.stores[opts.list+1]. getTotalCount();
+
+ this.views[opts.list+1].getEl().setHeight( this.innerLists[0].getHeight());
+
+ this.views[opts.list+1].getEl().setStyle({ display : dl ? 'block' : 'none' });
+ for (var i = opts.list+2; i < this.views.length;i++) {
+ this.views[i].getEl().setStyle({ display : 'none' });
+ }
+
+ this.innerLists[opts.list+1].setHeight( this.innerLists[0].getHeight());
+ this.list.setWidth(lw * (opts.list + (dl ? 2 : 1)));
+
+ if (this.isLoading) {
+ // this.selectActive(opts.list);
+ }
+
+ },
+
+
+
+
+ onDoubleClick : function()
+ {
+ this.collapse(); //??
+ },
+
+
+
+
+
+ // private
+ recordToStack : function(store, prop, value, stack)
+ {
+ var cstore = new Roo.data.SimpleStore({
+ //fields : this.store.reader.meta.fields, // we need array reader.. for
+ reader : this.store.reader,
+ data : [ ]
+ });
+ var _this = this;
+ var record = false;
+ var srec = false;
+ if(store.getCount() < 1){
+ return false;
+ }
+ store.each(function(r){
+ if(r.data[prop] == value){
+ record = r;
+ srec = r;
+ return false;
+ }
+ if (r.data.cn && r.data.cn.length) {
+ cstore.loadDataFromChildren( r);
+ var cret = _this.recordToStack(cstore, prop, value, stack);
+ if (cret !== false) {
+ record = cret;
+ srec = r;
+ return false;
+ }
+ }
+
+ return true;
+ });
+ if (record == false) {
+ return false
+ }
+ stack.unshift(srec);
+ return record;
+ },
+
+ /*
+ * find the stack of stores that match our value.
+ *
+ *
+ */
+
+ selectActive : function ()
+ {
+ // if store is not loaded, then we will need to wait for that to happen first.
+ var stack = [];
+ this.recordToStack(this.store, this.valueField, this.getValue(), stack);
+ for (var i = 0; i < stack.length; i++ ) {
+ this.views[i].select(stack[i].store.indexOf(stack[i]), false, false );
+ }
+
+ }
+
+
+
+
+
+
});/*
* Based on:
* Ext JS Library 1.1.1
* Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
* @param {Roo.HtmlEditorCore} this
*/
- editorevent: true
+ editorevent: true
+
});
* @cfg {Number} width (in pixels)
*/
width: 500,
+ /**
+ * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
+ * if you are doing an email editor, this probably needs disabling, it's designed
+ */
+ autoClean: true,
+ /**
+ * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
+ */
+ enableBlocks : true,
/**
* @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
*
*/
stylesheets: false,
+ /**
+ * @cfg {String} language default en - language of text (usefull for rtl languages)
+ *
+ */
+ language: 'en',
+ /**
+ * @cfg {boolean} allowComments - default false - allow comments in HTML source
+ * - by default they are stripped - if you are editing email you may need this.
+ */
+ allowComments: false,
// id of frame..
frameId: false,
bodyCls : '',
+
+ undoManager : false,
/**
* Protected method that will not generally be called directly. It
* is called when the editor initializes the iframe with HTML contents. Override this method if you
st = '<style type="text/css">' +
'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
'</style>';
- } else {
- st = '<style type="text/css">' +
- this.stylesheets +
- '</style>';
+ } else {
+ for (var i in this.stylesheets) {
+ if (typeof(this.stylesheets[i]) != 'string') {
+ continue;
+ }
+ st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
+ }
+
}
st += '<style type="text/css">' +
'IMG { cursor: pointer } ' +
'</style>';
-
- var cls = 'roo-htmleditor-body';
+
+ st += '<meta name="google" content="notranslate">';
+
+ var cls = 'notranslate roo-htmleditor-body';
if(this.bodyCls.length){
cls += ' ' + this.bodyCls;
}
- return '<html><head>' + st +
+ return '<html class="notranslate" translate="no"><head>' + st +
//<style type="text/css">' +
//'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
//'</style>' +
- ' </head><body class="' + cls + '"></body></html>';
+ ' </head><body contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
},
// private
this.iframe = iframe.dom;
- this.assignDocWin();
+ this.assignDocWin();
this.doc.designMode = 'on';
if(this.doc.body || this.doc.readyState == 'complete'){
try {
this.doc.designMode="on";
+
} catch (e) {
return;
}
if(this.sourceEditMode){
- Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
+ Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']); //FIXME - what's the BS styles for these
}else{
- Roo.get(this.iframe).removeClass(['x-hidden','hide']);
+ Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
//this.iframe.className = '';
this.deferFocus();
}
* @param {String} html The HTML to be cleaned
* return {String} The cleaned HTML
*/
- cleanHtml : function(html){
+ cleanHtml : function(html)
+ {
html = String(html);
if(html.length > 5){
if(Roo.isSafari){ // strip safari nonsense
* Protected method that will not generally be called directly. Syncs the contents
* of the editor iframe with the textarea.
*/
- syncValue : function(){
+ syncValue : function()
+ {
+ //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
if(this.initialized){
+
+ this.undoManager.addEvent();
+
+
var bd = (this.doc.body || this.doc.documentElement);
- //this.cleanUpPaste(); -- this is done else where and causes havoc..
- var html = bd.innerHTML;
+
+
+ var sel = this.win.getSelection();
+
+ var div = document.createElement('div');
+ div.innerHTML = bd.innerHTML;
+ var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of.
+ if (gtx.length > 0) {
+ var rm = gtx.item(0).parentNode;
+ rm.parentNode.removeChild(rm);
+ }
+
+
+ if (this.enableBlocks) {
+ new Roo.htmleditor.FilterBlock({ node : div });
+ }
+ //?? tidy?
+ var tidy = new Roo.htmleditor.TidySerializer({
+ inner: true
+ });
+ var html = tidy.serialize(div);
+
+
if(Roo.isSafari){
var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
var m = bs ? bs.match(/text-align:(.*?);/i) : false;
html = this.cleanHtml(html);
// fix up the special chars.. normaly like back quotes in word...
// however we do not want to do this with chinese..
- html = html.replace(/([\x80-\uffff])/g, function (a, b) {
- var cc = b.charCodeAt();
- if (
+ html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
+
+ var cc = match.charCodeAt();
+
+ // Get the character value, handling surrogate pairs
+ if (match.length == 2) {
+ // It's a surrogate pair, calculate the Unicode code point
+ var high = match.charCodeAt(0) - 0xD800;
+ var low = match.charCodeAt(1) - 0xDC00;
+ cc = (high * 0x400) + low + 0x10000;
+ } else if (
(cc >= 0x4E00 && cc < 0xA000 ) ||
(cc >= 0x3400 && cc < 0x4E00 ) ||
(cc >= 0xf900 && cc < 0xfb00 )
) {
- return b;
- }
- return "&#"+cc+";"
+ return match;
+ }
+
+ // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
+ return "&#" + cc + ";";
+
+
});
+
+
+
if(this.owner.fireEvent('beforesync', this, html) !== false){
this.el.dom.value = html;
this.owner.fireEvent('sync', this, html);
},
/**
+ * TEXTAREA -> EDITABLE
* Protected method that will not generally be called directly. Pushes the value of the textarea
* into the iframe editor.
*/
- pushValue : function(){
+ pushValue : function()
+ {
+ //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
if(this.initialized){
var v = this.el.dom.value.trim();
-// if(v.length < 1){
-// v = ' ';
-// }
if(this.owner.fireEvent('beforepush', this, v) !== false){
var d = (this.doc.body || this.doc.documentElement);
d.innerHTML = v;
- this.cleanUpPaste();
+
this.el.dom.value = d.innerHTML;
this.owner.fireEvent('push', this, v);
}
+ if (this.autoClean) {
+ new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
+ new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
+ }
+ if (this.enableBlocks) {
+ Roo.htmleditor.Block.initAll(this.doc.body);
+ }
+
+ this.updateLanguage();
+
+ var lc = this.doc.body.lastChild;
+ if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
+ // add an extra line at the end.
+ this.doc.body.appendChild(this.doc.createElement('br'));
+ }
+
+
}
},
//var ss = this.el.getStyles( 'background-image', 'background-repeat');
//ss['background-attachment'] = 'fixed'; // w3c
dbody.bgProperties = 'fixed'; // ie
+ dbody.setAttribute("translate", "no");
+
//Roo.DomHelper.applyStyles(dbody, ss);
Roo.EventManager.on(this.doc, {
- //'mousedown': this.onEditorEvent,
+
'mouseup': this.onEditorEvent,
'dblclick': this.onEditorEvent,
'click': this.onEditorEvent,
'keyup': this.onEditorEvent,
+
buffer:100,
scope: this
});
+ Roo.EventManager.on(this.doc, {
+ 'paste': this.onPasteEvent,
+ scope : this
+ });
if(Roo.isGecko){
Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
}
+ //??? needed???
if(Roo.isIE || Roo.isSafari || Roo.isOpera){
Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
}
this.initialized = true;
+
+ // initialize special key events - enter
+ new Roo.htmleditor.KeyEnter({core : this});
+
+
+
this.owner.fireEvent('initialize', this);
this.pushValue();
},
-
+ // this is to prevent a href clicks resulting in a redirect?
+
+ onPasteEvent : function(e,v)
+ {
+ // I think we better assume paste is going to be a dirty load of rubish from word..
+
+ // even pasting into a 'email version' of this widget will have to clean up that mess.
+ var cd = (e.browserEvent.clipboardData || window.clipboardData);
+
+ // check what type of paste - if it's an image, then handle it differently.
+ if (cd.files.length > 0) {
+ // pasting images?
+ var urlAPI = (window.createObjectURL && window) ||
+ (window.URL && URL.revokeObjectURL && URL) ||
+ (window.webkitURL && webkitURL);
+
+ var url = urlAPI.createObjectURL( cd.files[0]);
+ this.insertAtCursor('<img src=" + url + ">');
+ return false;
+ }
+
+ var html = cd.getData('text/html'); // clipboard event
+ var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
+ var images = parser.doc ? parser.doc.getElementsByType('pict') : [];
+ Roo.log(images);
+ //Roo.log(imgs);
+ // fixme..
+ images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable)/); }) // ignore headers
+ .map(function(g) { return g.toDataURL(); })
+ .filter(function(g) { return g != 'about:blank'; });
+
+
+ html = this.cleanWordChars(html);
+
+ var d = (new DOMParser().parseFromString(html, 'text/html')).body;
+
+
+ var sn = this.getParentElement();
+ // check if d contains a table, and prevent nesting??
+ //Roo.log(d.getElementsByTagName('table'));
+ //Roo.log(sn);
+ //Roo.log(sn.closest('table'));
+ if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
+ e.preventDefault();
+ this.insertAtCursor("You can not nest tables");
+ //Roo.log("prevent?"); // fixme -
+ return false;
+ }
+
+ if (images.length > 0) {
+ Roo.each(d.getElementsByTagName('img'), function(img, i) {
+ img.setAttribute('src', images[i]);
+ });
+ }
+ if (this.autoClean) {
+ new Roo.htmleditor.FilterStyleToTag({ node : d });
+ new Roo.htmleditor.FilterAttributes({
+ node : d,
+ attrib_white : ['href', 'src', 'name', 'align'],
+ attrib_clean : ['href', 'src' ]
+ });
+ new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
+ // should be fonts..
+ new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', 'O:P' ]} );
+ new Roo.htmleditor.FilterParagraph({ node : d });
+ new Roo.htmleditor.FilterSpan({ node : d });
+ new Roo.htmleditor.FilterLongBr({ node : d });
+ }
+ if (this.enableBlocks) {
+
+ Array.from(d.getElementsByTagName('img')).forEach(function(img) {
+ if (img.closest('figure')) { // assume!! that it's aready
+ return;
+ }
+ var fig = new Roo.htmleditor.BlockFigure({
+ image_src : img.src
+ });
+ fig.updateElement(img); // replace it..
+
+ });
+ }
+
+
+ this.insertAtCursor(d.innerHTML.replace(/ /g,' '));
+ if (this.enableBlocks) {
+ Roo.htmleditor.Block.initAll(this.doc.body);
+ }
+
+
+ e.preventDefault();
+ return false;
+ // default behaveiour should be our local cleanup paste? (optional?)
+ // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
+ //this.owner.fireEvent('paste', e, v);
+ },
// private
onDestroy : function(){
onFirstFocus : function(){
this.assignDocWin();
-
+ this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
this.activated = true;
onEditorEvent : function(e)
{
- this.owner.fireEvent('editorevent', this, e);
+
+
+ if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
+ return; // we do not handle this.. (undo manager does..)
+ }
+ // in theory this detects if the last element is not a br, then we try and do that.
+ // its so clicking in space at bottom triggers adding a br and moving the cursor.
+ if (e &&
+ e.target.nodeName == 'BODY' &&
+ e.type == "mouseup" &&
+ this.doc.body.lastChild
+ ) {
+ var lc = this.doc.body.lastChild;
+ // gtx-trans is google translate plugin adding crap.
+ while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
+ lc = lc.previousSibling;
+ }
+ if (lc.nodeType == 1 && lc.nodeName != 'BR') {
+ // if last element is <BR> - then dont do anything.
+
+ var ns = this.doc.createElement('br');
+ this.doc.body.appendChild(ns);
+ range = this.doc.createRange();
+ range.setStartAfter(ns);
+ range.collapse(true);
+ var sel = this.win.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+ }
+
+
+
+ this.fireEditorEvent(e);
// this.updateToolbar();
this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
},
+
+ fireEditorEvent: function(e)
+ {
+ this.owner.fireEvent('editorevent', this, e);
+ },
insertTag : function(tg)
{
// could be a bit smarter... -> wrap the current selected tRoo..
- if (tg.toLowerCase() == 'span' || tg.toLowerCase() == 'code') {
+ if (tg.toLowerCase() == 'span' ||
+ tg.toLowerCase() == 'code' ||
+ tg.toLowerCase() == 'sup' ||
+ tg.toLowerCase() == 'sub'
+ ) {
range = this.createRange(this.getSelection());
var wrappingNode = this.doc.createElement(tg.toLowerCase());
}
this.execCmd("formatblock", tg);
-
+ this.undoManager.addEvent();
},
insertText : function(txt)
//alert(Sender.getAttribute('label'));
range.insertNode(this.doc.createTextNode(txt));
+ this.undoManager.addEvent();
} ,
* @param {String} cmd The Midas command
* @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
*/
- relayCmd : function(cmd, value){
+ relayCmd : function(cmd, value)
+ {
+
+ switch (cmd) {
+ case 'justifyleft':
+ case 'justifyright':
+ case 'justifycenter':
+ // if we are in a cell, then we will adjust the
+ var n = this.getParentElement();
+ var td = n.closest('td');
+ if (td) {
+ var bl = Roo.htmleditor.Block.factory(td);
+ bl.textAlign = cmd.replace('justify','');
+ bl.updateElement();
+ this.owner.fireEvent('editorevent', this);
+ return;
+ }
+ this.execCmd('styleWithCSS', true); //
+ break;
+ case 'bold':
+ case 'italic':
+ // if there is no selection, then we insert, and set the curson inside it..
+ this.execCmd('styleWithCSS', false);
+ break;
+
+
+ default:
+ break;
+ }
+
+
this.win.focus();
this.execCmd(cmd, value);
this.owner.fireEvent('editorevent', this);
if(!this.activated){
return;
}
- /*
- if(Roo.isIE){
- this.win.focus();
- var r = this.doc.selection.createRange();
- if(r){
- r.collapse(true);
- r.pasteHTML(text);
- this.syncValue();
- this.deferFocus();
-
- }
- return;
- }
- */
+
if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
this.win.focus();
var win = this.win;
if (win.getSelection && win.getSelection().getRangeAt) {
+
+ // delete the existing?
+
+ this.createRange(this.getSelection()).deleteContents();
range = win.getSelection().getRangeAt(0);
node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
range.insertNode(node);
+ range = range.cloneRange();
+ range.collapse(false);
+
+ win.getSelection().removeAllRanges();
+ win.getSelection().addRange(range);
+
+
+
} else if (win.document.selection && win.document.selection.createRange) {
// no firefox support
var txt = typeof(text) == 'string' ? text : text.outerHTML;
win.document.selection.createRange().pasteHTML(txt);
+
} else {
// no firefox support
var txt = typeof(text) == 'string' ? text : text.outerHTML;
this.execCmd('InsertHTML', txt);
}
-
this.syncValue();
this.deferFocus();
cmd = 'underline';
break;
- case 'v':
- this.cleanUpPaste.defer(100, this);
- return;
+ //case 'v':
+ // this.cleanUpPaste.defer(100, this);
+ // return;
}
if(cmd){
- this.win.focus();
- this.execCmd(cmd);
- this.deferFocus();
+
+ this.relayCmd(cmd);
+ //this.win.focus();
+ //this.execCmd(cmd);
+ //this.deferFocus();
e.preventDefault();
}
// private
fixKeys : function(){ // load time branching for fastest keydown performance
+
+
if(Roo.isIE){
return function(e){
var k = e.getKey(), r;
}
return;
}
-
+ /// this is handled by Roo.htmleditor.KeyEnter
+ /*
if(k == e.ENTER){
r = this.doc.selection.createRange();
if(r){
var target = r.parentElement();
if(!target || target.tagName.toLowerCase() != 'li'){
e.stopEvent();
- r.pasteHTML('<br />');
+ r.pasteHTML('<br/>');
r.collapse(false);
r.select();
}
}
}
- if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
- this.cleanUpPaste.defer(100, this);
- return;
- }
+ */
+ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+ //}
};
this.execCmd('InsertHTML','    ');
this.deferFocus();
}
- if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
- this.cleanUpPaste.defer(100, this);
- return;
- }
+
+ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+ //}
};
}else if(Roo.isSafari){
this.deferFocus();
return;
}
- if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
- this.cleanUpPaste.defer(100, this);
- return;
- }
+ this.mozKeyPress(e);
+
+ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+ // }
};
}
getSelection : function()
{
this.assignDocWin();
- return Roo.isIE ? this.doc.selection : this.win.getSelection();
+ return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
+ },
+ /**
+ * Select a dom node
+ * @param {DomElement} node the node to select
+ */
+ selectNode : function(node, collapse)
+ {
+ var nodeRange = node.ownerDocument.createRange();
+ try {
+ nodeRange.selectNode(node);
+ } catch (e) {
+ nodeRange.selectNodeContents(node);
+ }
+ if (collapse === true) {
+ nodeRange.collapse(true);
+ }
+ //
+ var s = this.win.getSelection();
+ s.removeAllRanges();
+ s.addRange(nodeRange);
},
getSelectedNode: function()
// should we cache this!!!!
-
-
+
var range = this.createRange(this.getSelection()).cloneRange();
return nodes[0];
},
+
+
createRange: function(sel)
{
// this has strange effects when using with
// fully contined.
return 3;
},
-
- // private? - in a new class?
- cleanUpPaste : function()
- {
- // cleans up the whole document..
- Roo.log('cleanuppaste');
-
- this.cleanUpChildren(this.doc.body);
- var clean = this.cleanWordChars(this.doc.body.innerHTML);
- if (clean != this.doc.body.innerHTML) {
- this.doc.body.innerHTML = clean;
- }
-
- },
-
+
cleanWordChars : function(input) {// change the chars to hex code
- var he = Roo.HtmlEditorCore;
+ var swapCodes = [
+ [ 8211, "–" ],
+ [ 8212, "—" ],
+ [ 8216, "'" ],
+ [ 8217, "'" ],
+ [ 8220, '"' ],
+ [ 8221, '"' ],
+ [ 8226, "*" ],
+ [ 8230, "..." ]
+ ];
var output = input;
- Roo.each(he.swapCodes, function(sw) {
+ Roo.each(swapCodes, function(sw) {
var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
output = output.replace(swapper, sw[1]);
return output;
},
-
- cleanUpChildren : function (n)
- {
- if (!n.childNodes.length) {
- return;
- }
- for (var i = n.childNodes.length-1; i > -1 ; i--) {
- this.cleanUpChild(n.childNodes[i]);
- }
- },
-
+
cleanUpChild : function (node)
{
- var ed = this;
- //console.log(node);
- if (node.nodeName == "#text") {
- // clean up silly Windows -- stuff?
- return;
- }
- if (node.nodeName == "#comment") {
- node.parentNode.removeChild(node);
- // clean up silly Windows -- stuff?
- return;
- }
- var lcname = node.tagName.toLowerCase();
- // we ignore whitelists... ?? = not really the way to go, but we probably have not got a full
- // whitelist of tags..
-
- if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
- // remove node.
- node.parentNode.removeChild(node);
- return;
-
- }
-
- var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
-
- // remove <a name=....> as rendering on yahoo mailer is borked with this.
- // this will have to be flaged elsewhere - perhaps ablack=name... on the mailer..
-
- //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
- // remove_keep_children = true;
- //}
-
- if (remove_keep_children) {
- this.cleanUpChildren(node);
- // inserts everything just before this node...
- while (node.childNodes.length) {
- var cn = node.childNodes[0];
- node.removeChild(cn);
- node.parentNode.insertBefore(cn, node);
- }
- node.parentNode.removeChild(node);
- return;
- }
-
- if (!node.attributes || !node.attributes.length) {
- this.cleanUpChildren(node);
- return;
- }
-
- function cleanAttr(n,v)
- {
-
- if (v.match(/^\./) || v.match(/^\//)) {
- return;
- }
- if (v.match(/^(http|https):\/\//) || v.match(/^mailto:/) || v.match(/^ftp:/)) {
- return;
- }
- if (v.match(/^#/)) {
- return;
- }
-// Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
- node.removeAttribute(n);
-
- }
-
- var cwhite = this.cwhite;
- var cblack = this.cblack;
-
- function cleanStyle(n,v)
- {
- if (v.match(/expression/)) { //XSS?? should we even bother..
- node.removeAttribute(n);
- return;
- }
-
- var parts = v.split(/;/);
- var clean = [];
-
- Roo.each(parts, function(p) {
- p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
- if (!p.length) {
- return true;
- }
- var l = p.split(':').shift().replace(/\s+/g,'');
- l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
-
- if ( cwhite.length && cblack.indexOf(l) > -1) {
-// Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
- //node.removeAttribute(n);
- return true;
- }
- //Roo.log()
- // only allow 'c whitelisted system attributes'
- if ( cwhite.length && cwhite.indexOf(l) < 0) {
-// Roo.log('(REMOVE CSS)' + node.tagName +'.' + n + ':'+l + '=' + v);
- //node.removeAttribute(n);
- return true;
- }
-
-
-
-
- clean.push(p);
- return true;
- });
- if (clean.length) {
- node.setAttribute(n, clean.join(';'));
- } else {
- node.removeAttribute(n);
- }
-
- }
-
-
- for (var i = node.attributes.length-1; i > -1 ; i--) {
- var a = node.attributes[i];
- //console.log(a);
-
- if (a.name.toLowerCase().substr(0,2)=='on') {
- node.removeAttribute(a.name);
- continue;
- }
- if (Roo.HtmlEditorCore.ablack.indexOf(a.name.toLowerCase()) > -1) {
- node.removeAttribute(a.name);
- continue;
- }
- if (Roo.HtmlEditorCore.aclean.indexOf(a.name.toLowerCase()) > -1) {
- cleanAttr(a.name,a.value); // fixme..
- continue;
- }
- if (a.name == 'style') {
- cleanStyle(a.name,a.value);
- continue;
- }
- /// clean up MS crap..
- // tecnically this should be a list of valid class'es..
-
-
- if (a.name == 'class') {
- if (a.value.match(/^Mso/)) {
- node.className = '';
- }
-
- if (a.value.match(/^body$/)) {
- node.className = '';
- }
- continue;
- }
-
- // style cleanup!?
- // class cleanup?
-
- }
-
-
- this.cleanUpChildren(node);
+ new Roo.htmleditor.FilterComment({node : node});
+ new Roo.htmleditor.FilterAttributes({
+ node : node,
+ attrib_black : this.ablack,
+ attrib_clean : this.aclean,
+ style_white : this.cwhite,
+ style_black : this.cblack
+ });
+ new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
+ new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
+
},
/**
* Clean up MS wordisms...
+ * @deprecated - use filter directly
*/
cleanWord : function(node)
{
- if (!node) {
- this.cleanWord(this.doc.body);
- return;
- }
-
- if(
- node.nodeName == 'SPAN' &&
- !node.hasAttributes() &&
- node.childNodes.length == 1 &&
- node.firstChild.nodeName == "#text"
- ) {
- var textNode = node.firstChild;
- node.removeChild(textNode);
-
- node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
- node.parentNode.insertBefore(textNode, node);
- node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
- node.parentNode.removeChild(node);
- }
-
- if (node.nodeName == "#text") {
- // clean up silly Windows -- stuff?
- return;
- }
- if (node.nodeName == "#comment") {
- node.parentNode.removeChild(node);
- // clean up silly Windows -- stuff?
- return;
- }
-
- if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
- node.parentNode.removeChild(node);
- return;
- }
-
- // remove - but keep children..
- if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|font)/)) {
- while (node.childNodes.length) {
- var cn = node.childNodes[0];
- node.removeChild(cn);
- node.parentNode.insertBefore(cn, node);
- }
- node.parentNode.removeChild(node);
- this.iterateChildren(node, this.cleanWord);
- return;
- }
- // clean styles
- if (node.className.length) {
-
- var cn = node.className.split(/\W+/);
- var cna = [];
- Roo.each(cn, function(cls) {
- if (cls.match(/Mso[a-zA-Z]+/)) {
- return;
- }
- cna.push(cls);
- });
- node.className = cna.length ? cna.join(' ') : '';
- if (!cna.length) {
- node.removeAttribute("class");
- }
- }
+ new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
- if (node.hasAttribute("lang")) {
- node.removeAttribute("lang");
- }
-
- if (node.hasAttribute("style")) {
-
- var styles = node.getAttribute("style").split(";");
- var nstyle = [];
- Roo.each(styles, function(s) {
- if (!s.match(/:/)) {
- return;
- }
- var kv = s.split(":");
- if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
- return;
- }
- // what ever is left... we allow.
- nstyle.push(s);
- });
- node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
- if (!nstyle.length) {
- node.removeAttribute('style');
- }
- }
- this.iterateChildren(node, this.cleanWord);
-
-
-
- },
- /**
- * iterateChildren of a Node, calling fn each time, using this as the scole..
- * @param {DomNode} node node to iterate children of.
- * @param {Function} fn method of this class to call on each item.
- */
- iterateChildren : function(node, fn)
- {
- if (!node.childNodes.length) {
- return;
- }
- for (var i = node.childNodes.length-1; i > -1 ; i--) {
- fn.call(this, node.childNodes[i])
- }
},
-
+
/**
- * cleanTableWidths.
- *
- * Quite often pasting from word etc.. results in tables with column and widths.
- * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
- *
+
+ * @deprecated - use filters
*/
cleanTableWidths : function(node)
{
-
-
- if (!node) {
- this.cleanTableWidths(this.doc.body);
- return;
- }
-
- // ignore list...
- if (node.nodeName == "#text" || node.nodeName == "#comment") {
- return;
- }
- Roo.log(node.tagName);
- if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
- this.iterateChildren(node, this.cleanTableWidths);
- return;
- }
- if (node.hasAttribute('width')) {
- node.removeAttribute('width');
- }
-
-
- if (node.hasAttribute("style")) {
- // pretty basic...
-
- var styles = node.getAttribute("style").split(";");
- var nstyle = [];
- Roo.each(styles, function(s) {
- if (!s.match(/:/)) {
- return;
- }
- var kv = s.split(":");
- if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
- return;
- }
- // what ever is left... we allow.
- nstyle.push(s);
- });
- node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
- if (!nstyle.length) {
- node.removeAttribute('style');
- }
- }
-
- this.iterateChildren(node, this.cleanTableWidths);
-
+ new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
+
},
-
-
-
- domToHTML : function(currentElement, depth, nopadtext) {
-
- depth = depth || 0;
- nopadtext = nopadtext || false;
-
- if (!currentElement) {
- return this.domToHTML(this.doc.body);
- }
-
- //Roo.log(currentElement);
- var j;
- var allText = false;
- var nodeName = currentElement.nodeName;
- var tagName = Roo.util.Format.htmlEncode(currentElement.tagName);
-
- if (nodeName == '#text') {
-
- return nopadtext ? currentElement.nodeValue : currentElement.nodeValue.trim();
- }
-
-
- var ret = '';
- if (nodeName != 'BODY') {
-
- var i = 0;
- // Prints the node tagName, such as <A>, <IMG>, etc
- if (tagName) {
- var attr = [];
- for(i = 0; i < currentElement.attributes.length;i++) {
- // quoting?
- var aname = currentElement.attributes.item(i).name;
- if (!currentElement.attributes.item(i).value.length) {
- continue;
- }
- attr.push(aname + '="' + Roo.util.Format.htmlEncode(currentElement.attributes.item(i).value) + '"' );
- }
-
- ret = "<"+currentElement.tagName+ ( attr.length ? (' ' + attr.join(' ') ) : '') + ">";
- }
- else {
-
- // eack
- }
- } else {
- tagName = false;
- }
- if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
- return ret;
- }
- if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
- nopadtext = true;
- }
-
-
- // Traverse the tree
- i = 0;
- var currentElementChild = currentElement.childNodes.item(i);
- var allText = true;
- var innerHTML = '';
- lastnode = '';
- while (currentElementChild) {
- // Formatting code (indent the tree so it looks nice on the screen)
- var nopad = nopadtext;
- if (lastnode == 'SPAN') {
- nopad = true;
- }
- // text
- if (currentElementChild.nodeName == '#text') {
- var toadd = Roo.util.Format.htmlEncode(currentElementChild.nodeValue);
- toadd = nopadtext ? toadd : toadd.trim();
- if (!nopad && toadd.length > 80) {
- innerHTML += "\n" + (new Array( depth + 1 )).join( " " );
- }
- innerHTML += toadd;
-
- i++;
- currentElementChild = currentElement.childNodes.item(i);
- lastNode = '';
- continue;
- }
- allText = false;
-
- innerHTML += nopad ? '' : "\n" + (new Array( depth + 1 )).join( " " );
-
- // Recursively traverse the tree structure of the child node
- innerHTML += this.domToHTML(currentElementChild, depth+1, nopadtext);
- lastnode = currentElementChild.nodeName;
- i++;
- currentElementChild=currentElement.childNodes.item(i);
- }
-
- ret += innerHTML;
-
- if (!allText) {
- // The remaining code is mostly for formatting the tree
- ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
- }
-
-
- if (tagName) {
- ret+= "</"+tagName+">";
- }
- return ret;
-
- },
+
applyBlacklists : function()
{
var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white : [];
var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black : [];
+ this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean : Roo.HtmlEditorCore.aclean;
+ this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack : Roo.HtmlEditorCore.ablack;
+ this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove : Roo.HtmlEditorCore.tag_remove;
+
this.white = [];
this.black = [];
Roo.each(Roo.HtmlEditorCore.white, function(tag) {
},
+
+ updateLanguage : function()
+ {
+ if (!this.iframe || !this.iframe.contentDocument) {
+ return;
+ }
+ Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
+ },
+
+
removeStylesheets : function()
{
var _this = this;
});
Roo.HtmlEditorCore.white = [
- 'area', 'br', 'img', 'input', 'hr', 'wbr',
+ 'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
- 'address', 'blockquote', 'center', 'dd', 'dir', 'div',
- 'dl', 'dt', 'h1', 'h2', 'h3', 'h4',
- 'h5', 'h6', 'hr', 'isindex', 'listing', 'marquee',
- 'menu', 'multicol', 'ol', 'p', 'plaintext', 'pre',
- 'table', 'ul', 'xmp',
+ 'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD', 'DIR', 'DIV',
+ 'DL', 'DT', 'H1', 'H2', 'H3', 'H4',
+ 'H5', 'H6', 'HR', 'ISINDEX', 'LISTING', 'MARQUEE',
+ 'MENU', 'MULTICOL', 'OL', 'P', 'PLAINTEXT', 'PRE',
+ 'TABLE', 'UL', 'XMP',
- 'caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
- 'thead', 'tr',
+ 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
+ 'THEAD', 'TR',
- 'dir', 'menu', 'ol', 'ul', 'dl',
+ 'DIR', 'MENU', 'OL', 'UL', 'DL',
- 'embed', 'object'
+ 'EMBED', 'OBJECT'
];
Roo.HtmlEditorCore.black = [
// 'embed', 'object', // enable - backend responsiblity to clean thiese
- 'applet', //
- 'base', 'basefont', 'bgsound', 'blink', 'body',
- 'frame', 'frameset', 'head', 'html', 'ilayer',
- 'iframe', 'layer', 'link', 'meta', 'object',
- 'script', 'style' ,'title', 'xml' // clean later..
+ 'APPLET', //
+ 'BASE', 'BASEFONT', 'BGSOUND', 'BLINK', 'BODY',
+ 'FRAME', 'FRAMESET', 'HEAD', 'HTML', 'ILAYER',
+ 'IFRAME', 'LAYER', 'LINK', 'META', 'OBJECT',
+ 'SCRIPT', 'STYLE' ,'TITLE', 'XML',
+ //'FONT' // CLEAN LATER..
+ 'COLGROUP', 'COL' // messy tables.
+
+
];
-Roo.HtmlEditorCore.clean = [
- 'script', 'style', 'title', 'xml'
+Roo.HtmlEditorCore.clean = [ // ?? needed???
+ 'SCRIPT', 'STYLE', 'TITLE', 'XML'
];
-Roo.HtmlEditorCore.remove = [
- 'font'
+Roo.HtmlEditorCore.tag_remove = [
+ 'FONT', 'TBODY'
];
// attributes..
];
-Roo.HtmlEditorCore.swapCodes =[
- [ 8211, "--" ],
- [ 8212, "--" ],
- [ 8216, "'" ],
- [ 8217, "'" ],
- [ 8220, '"' ],
- [ 8221, '"' ],
- [ 8226, "*" ],
- [ 8230, "..." ]
-];
+
//<script type="text/javascript">
width: 500,
/**
- * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
+ * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets - this is usally a good idea rootURL + '/roojs1/css/undoreset.css', .
*
*/
stylesheets: false,
*
*/
white: false,
+ /**
+ * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
+ */
+ allowComments: false,
+ /**
+ * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
+ */
+ enableBlocks : true,
+ /**
+ * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
+ * if you are doing an email editor, this probably needs disabling, it's designed
+ */
+ autoClean: true,
+ /**
+ * @cfg {string} bodyCls default '' default classes to add to body of editable area - usually undoreset is a good start..
+ */
+ bodyCls : '',
+ /**
+ * @cfg {String} language default en - language of text (usefull for rtl languages)
+ *
+ */
+ language: 'en',
+
+
// id of frame..
frameId: false,
* Fires when press the Sytlesheets button
* @param {Roo.HtmlEditorCore} this
*/
- stylesheetsclick: true
+ stylesheetsclick: true,
+ /**
+ * @event paste
+ * Fires when press user pastes into the editor
+ * @param {Roo.HtmlEditorCore} this
+ */
+ paste: true
});
this.defaultAutoCreate = {
tag: "textarea",
},
-
-
+ /**
+ * get the Context selected node
+ * @returns {DomElement|boolean} selected node if active or false if none
+ *
+ */
+ getSelectedNode : function()
+ {
+ if (this.toolbars.length < 2 || !this.toolbars[1].tb) {
+ return false;
+ }
+ return this.toolbars[1].tb.selectedNode;
+
+ },
// private
onRender : function(ct, position)
{
this.el.removeClass('x-hidden');
this.el.dom.removeAttribute('tabIndex');
this.el.focus();
+ this.el.dom.scrollTop = 0;
+
for (var i = 0; i < this.toolbars.length; i++) {
if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
this.editorcore.pushValue();
},
+ /**
+ * update the language in the body - really done by core
+ * @param {String} language - eg. en / ar / zh-CN etc..
+ */
+ updateLanguage : function(lang)
+ {
+ this.language = lang;
+ this.editorcore.language = lang;
+ this.editorcore.updateLanguage();
+ },
// private
deferFocus : function(){
this.focus.defer(10, this);
*/
});
- // <script type="text/javascript">
-/*
+ /*
* Based on
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
*/
/**
- * @class Roo.form.HtmlEditorToolbar1
+ * @class Roo.form.HtmlEditor.ToolbarStandard
* Basic Toolbar
- *
+
* Usage:
*
new Roo.form.HtmlEditor({
*
* @cfg {Object} disable List of elements to disable..
- * @cfg {Array} btns List of additional buttons.
+ * @cfg {Roo.Toolbar.Item|Roo.Toolbar.Button|Roo.Toolbar.SplitButton|Roo.form.Field} btns[] List of additional buttons.
*
*
* NEEDS Extra CSS?
// dont call parent... till later.
}
-Roo.apply(Roo.form.HtmlEditor.ToolbarStandard.prototype, {
+Roo.form.HtmlEditor.ToolbarStandard.prototype = {
tb: false,
["h1"],["h2"],["h3"],["h4"],["h5"],["h6"],
["pre"],[ "code"],
["abbr"],[ "acronym"],[ "address"],[ "cite"],[ "samp"],[ "var"],
- ['div'],['span']
+ ['div'],['span'],
+ ['sup'],['sub']
],
cleanStyles : [
tabIndex:-1
});
}
- cmenu.menu.items.push({
+ cmenu.menu.items.push({
actiontype : 'tablewidths',
html: 'Remove Table Widths',
handler: function(a,b) {
var c = Roo.get(editorcore.doc.body);
c.select('[class]').each(function(s) {
- s.dom.className = '';
+ s.dom.removeAttribute('class');
});
+ editorcore.cleanWord();
editorcore.syncValue();
},
tabIndex:-1
actiontype : 'tidy',
html: 'Tidy HTML Source',
handler: function(a,b) {
- editorcore.doc.body.innerHTML = editorcore.domToHTML();
+ new Roo.htmleditor.Tidy(editorcore.doc.body);
editorcore.syncValue();
},
tabIndex:-1
if (this.btns) {
for(var i =0; i< this.btns.length;i++) {
- var b = Roo.factory(this.btns[i],Roo.form);
+ var b = Roo.factory(this.btns[i],this.btns[i].xns || Roo.form);
b.cls = 'x-edit-none';
if(typeof(this.btns[i].cls) != 'undefined' && this.btns[i].cls.indexOf('x-init-enable') !== -1){
},
// private used internally
createLink : function(){
- Roo.log("create link?");
- var url = prompt(this.createLinkText, this.defaultLinkValue);
- if(url && url != 'http:/'+'/'){
- this.editorcore.relayCmd('createlink', url);
+ //Roo.log("create link?");
+ var ec = this.editorcore;
+ var ar = ec.getAllAncestors();
+ var n = false;
+ for(var i = 0;i< ar.length;i++) {
+ if (ar[i] && ar[i].nodeName == 'A') {
+ n = ar[i];
+ break;
+ }
}
+
+ (function() {
+
+ Roo.MessageBox.show({
+ title : "Add / Edit Link URL",
+ msg : "Enter the url for the link",
+ buttons: Roo.MessageBox.OKCANCEL,
+ fn: function(btn, url){
+ if (btn != 'ok') {
+ return;
+ }
+ if(url && url != 'http:/'+'/'){
+ if (n) {
+ n.setAttribute('href', url);
+ } else {
+ ec.relayCmd('createlink', url);
+ }
+ }
+ },
+ minWidth:250,
+ prompt:true,
+ //multiline: multiline,
+ modal : true,
+ value : n ? n.getAttribute('href') : ''
+ });
+
+
+ }).defer(100, this); // we have to defer this , otherwise the mouse click gives focus to the main window.
+
},
this.tb.items.each(function(item){
item.enable();
});
+ // initialize 'blocks'
+ Roo.each(Roo.get(this.editorcore.doc.body).query('*[data-block]'), function(e) {
+ Roo.htmleditor.Block.factory(e).updateElement(e);
+ },this);
+
}
}
item.enable();
});
}
-});
+};
Roo.form.HtmlEditor.ToolbarContext.types = {
- 'IMG' : {
- width : {
+ 'IMG' : [
+ {
+ name : 'width',
title: "Width",
width: 40
},
- height: {
+ {
+ name : 'height',
title: "Height",
width: 40
},
- align: {
+ {
+ name : 'align',
title: "Align",
opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
width : 80
},
- border: {
+ {
+ name : 'border',
title: "Border",
width: 40
},
- alt: {
+ {
+ name : 'alt',
title: "Alt",
width: 120
},
- src : {
+ {
+ name : 'src',
title: "Src",
width: 220
}
- },
- 'A' : {
- name : {
+ ],
+
+ 'FIGURE' : [
+ {
+ name : 'align',
+ title: "Align",
+ opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
+ width : 80
+ }
+ ],
+ 'A' : [
+ {
+ name : 'name',
title: "Name",
width: 50
},
- target: {
+ {
+ name : 'target',
title: "Target",
width: 120
},
- href: {
+ {
+ name : 'href',
title: "Href",
width: 220
} // border?
- },
- 'TABLE' : {
- rows : {
- title: "Rows",
- width: 20
- },
- cols : {
- title: "Cols",
- width: 20
- },
- width : {
- title: "Width",
- width: 40
- },
- height : {
- title: "Height",
- width: 40
- },
- border : {
- title: "Border",
- width: 20
- }
- },
- 'TD' : {
- width : {
- title: "Width",
- width: 40
- },
- height : {
- title: "Height",
- width: 40
- },
- align: {
- title: "Align",
- opts : [[""],[ "left"],[ "center"],[ "right"],[ "justify"],[ "char"]],
- width: 80
- },
- valign: {
- title: "Valign",
- opts : [[""],[ "top"],[ "middle"],[ "bottom"],[ "baseline"]],
- width: 80
- },
- colspan: {
- title: "Colspan",
- width: 20
-
- },
- 'font-family' : {
- title : "Font",
- style : 'fontFamily',
- displayField: 'display',
- optname : 'font-family',
- width: 140
- }
- },
- 'INPUT' : {
- name : {
+ ],
+
+ 'INPUT' : [
+ {
+ name : 'name',
title: "name",
width: 120
},
- value : {
+ {
+ name : 'value',
title: "Value",
width: 120
},
- width : {
+ {
+ name : 'width',
title: "Width",
width: 40
}
- },
- 'LABEL' : {
- 'for' : {
+ ],
+ 'LABEL' : [
+ {
+ name : 'for',
title: "For",
width: 120
}
- },
- 'TEXTAREA' : {
- name : {
+ ],
+ 'TEXTAREA' : [
+ {
+ name : 'name',
title: "name",
width: 120
},
- rows : {
+ {
+ name : 'rows',
title: "Rows",
width: 20
},
- cols : {
+ {
+ name : 'cols',
title: "Cols",
width: 20
}
- },
- 'SELECT' : {
- name : {
+ ],
+ 'SELECT' : [
+ {
+ name : 'name',
title: "name",
width: 120
},
- selectoptions : {
+ {
+ name : 'selectoptions',
title: "Options",
width: 200
}
- },
+ ],
// should we really allow this??
// should this just be
- 'BODY' : {
- title : {
+ 'BODY' : [
+
+ {
+ name : 'title',
title: "Title",
width: 200,
disabled : true
}
- },
- 'SPAN' : {
- 'font-family' : {
- title : "Font",
- style : 'fontFamily',
- displayField: 'display',
- optname : 'font-family',
- width: 140
- }
- },
- 'DIV' : {
- 'font-family' : {
- title : "Font",
- style : 'fontFamily',
- displayField: 'display',
- optname : 'font-family',
- width: 140
- }
- },
- 'P' : {
- 'font-family' : {
- title : "Font",
- style : 'fontFamily',
- displayField: 'display',
- optname : 'font-family',
- width: 140
- }
- },
-
- '*' : {
- // empty..
- }
+ ],
+
+ '*' : [
+ // empty.
+ ]
};
// disable everything...
var ty= Roo.form.HtmlEditor.ToolbarContext.types;
this.toolbars = {};
-
+ // block toolbars are built in updateToolbar when needed.
for (var i in ty) {
-
+
this.toolbars[i] = this.buildToolbar(ty[i],i);
}
this.tb = this.toolbars.BODY;
*
* Note you can force an update by calling on('editorevent', scope, false)
*/
- updateToolbar: function(editor,ev,sel){
-
+ updateToolbar: function(editor ,ev, sel)
+ {
+
+ if (ev) {
+ ev.stopEvent(); // se if we can stop this looping with mutiple events.
+ }
+
//Roo.log(ev);
// capture mouse up - this is handy for selecting images..
// perhaps should go somewhere else...
this.editor.onFirstFocus();
return;
}
-
+ //Roo.log(ev ? ev.target : 'NOTARGET');
// http://developer.yahoo.com/yui/docs/simple-editor.js.html
// selectNode - might want to handle IE?
+
+
+
if (ev &&
(ev.type == 'mouseup' || ev.type == 'click' ) &&
- ev.target && ev.target.tagName == 'IMG') {
+ ev.target && ev.target.tagName != 'BODY' ) { // && ev.target.tagName == 'IMG') {
// they have click on an image...
// let's see if we can change the selection...
sel = ev.target;
-
- var nodeRange = sel.ownerDocument.createRange();
- try {
- nodeRange.selectNode(sel);
- } catch (e) {
- nodeRange.selectNodeContents(sel);
- }
- //nodeRange.collapse(true);
- var s = this.editorcore.win.getSelection();
- s.removeAllRanges();
- s.addRange(nodeRange);
- }
+
+ // this triggers looping?
+ //this.editorcore.selectNode(sel);
+
+ }
+ // this forces an id..
+ Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) {
+ e.classList.remove('roo-ed-selection');
+ });
+ //Roo.select('.roo-ed-selection', false, this.editorcore.doc).removeClass('roo-ed-selection');
+ //Roo.get(node).addClass('roo-ed-selection');
- var updateFooter = sel ? false : true;
+ //var updateFooter = sel ? false : true;
var ans = this.editorcore.getAllAncestors();
// pick
- var ty= Roo.form.HtmlEditor.ToolbarContext.types;
+ var ty = Roo.form.HtmlEditor.ToolbarContext.types;
if (!sel) {
sel = ans.length ? (ans[0] ? ans[0] : ans[1]) : this.editorcore.doc.body;
sel = sel.tagName.length ? sel : this.editorcore.doc.body;
}
- // pick a menu that exists..
- var tn = sel.tagName.toUpperCase();
- //sel = typeof(ty[tn]) != 'undefined' ? sel : this.editor.doc.body;
-
- tn = sel.tagName.toUpperCase();
+ var tn = sel.tagName.toUpperCase();
var lastSel = this.tb.selectedNode;
-
this.tb.selectedNode = sel;
+ var left_label = tn;
- // if current menu does not match..
+ // ok see if we are editing a block?
- if ((this.tb.name != tn) || (lastSel != this.tb.selectedNode) || ev === false) {
-
- this.tb.el.hide();
- ///console.log("show: " + tn);
- this.tb = typeof(ty[tn]) != 'undefined' ? this.toolbars[tn] : this.toolbars['*'];
- this.tb.el.show();
- // update name
- this.tb.items.first().el.innerHTML = tn + ': ';
-
-
- // update attributes
- if (this.tb.fields) {
- this.tb.fields.each(function(e) {
- if (e.stylename) {
- e.setValue(sel.style[e.stylename]);
- return;
- }
- e.setValue(sel.getAttribute(e.attrname));
- });
- }
+ var db = false;
+ // you are not actually selecting the block.
+ if (sel && sel.hasAttribute('data-block')) {
+ db = sel;
+ } else if (sel && sel.closest('[data-block]')) {
- var hasStyles = false;
- for(var i in this.styles) {
- hasStyles = true;
- break;
- }
+ db = sel.closest('[data-block]');
+ //var cepar = sel.closest('[contenteditable=true]');
+ //if (db && cepar && cepar.tagName != 'BODY') {
+ // db = false; // we are inside an editable block.. = not sure how we are going to handle nested blocks!?
+ //}
+ }
+
+
+ var block = false;
+ //if (db && !sel.hasAttribute('contenteditable') && sel.getAttribute('contenteditable') != 'true' ) {
+ if (db && this.editorcore.enableBlocks) {
+ block = Roo.htmleditor.Block.factory(db);
- // update styles
- if (hasStyles) {
- var st = this.tb.fields.item(0);
-
- st.store.removeAll();
-
-
- var cn = sel.className.split(/\s+/);
+
+ if (block) {
+ db.className = (
+ db.classList.length > 0 ? db.className + ' ' : ''
+ ) + 'roo-ed-selection';
+
+ // since we removed it earlier... its not there..
+ tn = 'BLOCK.' + db.getAttribute('data-block');
- var avs = [];
- if (this.styles['*']) {
-
- Roo.each(this.styles['*'], function(v) {
- avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );
- });
+ //this.editorcore.selectNode(db);
+ if (typeof(this.toolbars[tn]) == 'undefined') {
+ this.toolbars[tn] = this.buildToolbar( false ,tn ,block.friendly_name, block);
}
- if (this.styles[tn]) {
- Roo.each(this.styles[tn], function(v) {
- avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );
- });
- }
-
- st.store.loadData(avs);
- st.collapse();
- st.setValue(cn);
+ this.toolbars[tn].selectedNode = db;
+ left_label = block.friendly_name;
+ ans = this.editorcore.getAllAncestors();
}
- // flag our selected Node.
- this.tb.selectedNode = sel;
-
-
- Roo.menu.MenuMgr.hideAll();
-
+
+
+
}
- if (!updateFooter) {
- //this.footDisp.dom.innerHTML = '';
- return;
+
+ if (this.tb.name == tn && lastSel == this.tb.selectedNode && ev !== false) {
+ return; // no change?
}
+
+
+
+ this.tb.el.hide();
+ ///console.log("show: " + tn);
+ this.tb = typeof(this.toolbars[tn]) != 'undefined' ? this.toolbars[tn] : this.toolbars['*'];
+
+ this.tb.el.show();
+ // update name
+ this.tb.items.first().el.innerHTML = left_label + ': ';
+
+
+ // update attributes
+ if (block && this.tb.fields) {
+
+ this.tb.fields.each(function(e) {
+ e.setValue(block[e.name]);
+ });
+
+
+ } else if (this.tb.fields && this.tb.selectedNode) {
+ this.tb.fields.each( function(e) {
+ if (e.stylename) {
+ e.setValue(this.tb.selectedNode.style[e.stylename]);
+ return;
+ }
+ e.setValue(this.tb.selectedNode.getAttribute(e.attrname));
+ }, this);
+ this.updateToolbarStyles(this.tb.selectedNode);
+ }
+
+
+
+ Roo.menu.MenuMgr.hideAll();
+
+
+
+
// update the footer
//
+ this.updateFooter(ans);
+
+ },
+
+ updateToolbarStyles : function(sel)
+ {
+ var hasStyles = false;
+ for(var i in this.styles) {
+ hasStyles = true;
+ break;
+ }
+
+ // update styles
+ if (hasStyles && this.tb.hasStyles) {
+ var st = this.tb.fields.item(0);
+
+ st.store.removeAll();
+ var cn = sel.className.split(/\s+/);
+
+ var avs = [];
+ if (this.styles['*']) {
+
+ Roo.each(this.styles['*'], function(v) {
+ avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );
+ });
+ }
+ if (this.styles[tn]) {
+ Roo.each(this.styles[tn], function(v) {
+ avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );
+ });
+ }
+
+ st.store.loadData(avs);
+ st.collapse();
+ st.setValue(cn);
+ }
+ },
+
+
+ updateFooter : function(ans)
+ {
var html = '';
+ if (ans === false) {
+ this.footDisp.dom.innerHTML = '';
+ return;
+ }
this.footerEls = ans.reverse();
Roo.each(this.footerEls, function(a,i) {
this.footDisp.dom.innerHTML = html;
- //this.editorsyncValue();
+
},
-
-
// private
item.enable();
});
},
- buildToolbar: function(tlist, nm)
+ buildToolbar: function(tlist, nm, friendly_name, block)
{
var editor = this.editor;
var editorcore = this.editorcore;
var tb = new Roo.Toolbar(wdiv);
- // add the name..
+ ///this.tb = tb; // << this sets the active toolbar..
+ if (tlist === false && block) {
+ tlist = block.contextMenu(this);
+ }
- tb.add(nm+ ": ");
+ tb.hasStyles = false;
+ tb.name = nm;
+
+ tb.add((typeof(friendly_name) == 'undefined' ? nm : friendly_name) + ": ");
+
+ var styles = Array.from(this.styles);
- var styles = [];
- for(var i in this.styles) {
- styles.push(i);
- }
// styles...
if (styles && styles.length) {
-
+ tb.hasStyles = true;
// this needs a multi-select checkbox...
tb.addField( new Roo.form.ComboBox({
store: new Roo.data.SimpleStore({
}
var tbc = Roo.form.HtmlEditor.ToolbarContext;
- var tbops = tbc.options;
- for (var i in tlist) {
+
+ for (var i = 0; i < tlist.length; i++) {
+
+ // newer versions will use xtype cfg to create menus.
+ if (typeof(tlist[i].xtype) != 'undefined') {
+
+ tb[typeof(tlist[i].name)== 'undefined' ? 'add' : 'addField'](Roo.factory(tlist[i]));
+
+
+ continue;
+ }
var item = tlist[i];
tb.add(item.title + ": ");
//optname == used so you can configure the options available..
var opts = item.opts ? item.opts : false;
- if (item.optname) {
- opts = tbops[item.optname];
+ if (item.optname) { // use the b
+ opts = Roo.form.HtmlEditor.ToolbarContext.options[item.optname];
}
fields: ['val', 'display'],
data : opts
}),
- name : '-roo-edit-' + i,
- attrname : i,
+ name : '-roo-edit-' + tlist[i].name,
+
+ attrname : tlist[i].name,
stylename : item.style ? item.style : false,
+
displayField: item.displayField ? item.displayField : 'val',
valueField : 'val',
typeAhead: false,
- mode: typeof(tbc.stores[i]) != 'undefined' ? 'remote' : 'local',
+ mode: typeof(tbc.stores[tlist[i].name]) != 'undefined' ? 'remote' : 'local',
editable : false,
triggerAction: 'all',
emptyText:'Select',
width: item.width ? item.width : 130,
listeners : {
'select': function(c, r, i) {
+
+
if (c.stylename) {
tb.selectedNode.style[c.stylename] = r.get('val');
+ editorcore.syncValue();
+ return;
+ }
+ if (r === false) {
+ tb.selectedNode.removeAttribute(c.attrname);
+ editorcore.syncValue();
return;
}
tb.selectedNode.setAttribute(c.attrname, r.get('val'));
+ editorcore.syncValue();
}
}
continue;
-
+ /*
tb.addField( new Roo.form.TextField({
name: i,
width: 100,
value: ''
}));
continue;
+ */
}
tb.addField( new Roo.form.TextField({
- name: '-roo-edit-' + i,
- attrname : i,
+ name: '-roo-edit-' + tlist[i].name,
+ attrname : tlist[i].name,
width: item.width,
//allowBlank:true,
value: '',
listeners: {
'change' : function(f, nv, ov) {
+
+
tb.selectedNode.setAttribute(f.attrname, nv);
editorcore.syncValue();
}
}
var _this = this;
-
+ var show_delete = !block || block.deleteTitle !== false;
if(nm == 'BODY'){
+ show_delete = false;
tb.addSeparator();
tb.addButton( {
}
tb.addFill();
- tb.addButton( {
- text: 'Remove Tag',
-
- listeners : {
- click : function ()
- {
- // remove
- // undo does not work.
-
- var sn = tb.selectedNode;
-
- var pn = sn.parentNode;
-
- var stn = sn.childNodes[0];
- var en = sn.childNodes[sn.childNodes.length - 1 ];
- while (sn.childNodes.length) {
- var node = sn.childNodes[0];
- sn.removeChild(node);
- //Roo.log(node);
- pn.insertBefore(node, sn);
+ if (show_delete) {
+ tb.addButton({
+ text: block && block.deleteTitle ? block.deleteTitle : 'Remove Block or Formating', // remove the tag, and puts the children outside...
+
+ listeners : {
+ click : function ()
+ {
+ var sn = tb.selectedNode;
+ if (block) {
+ sn = Roo.htmleditor.Block.factory(tb.selectedNode).removeNode();
+
+ }
+ if (!sn) {
+ return;
+ }
+ var stn = sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode;
+ if (sn.hasAttribute('data-block')) {
+ stn = sn.nextSibling || sn.previousSibling || sn.parentNode;
+ sn.parentNode.removeChild(sn);
+
+ } else if (sn && sn.tagName != 'BODY') {
+ // remove and keep parents.
+ a = new Roo.htmleditor.FilterKeepChildren({tag : false});
+ a.replaceTag(sn);
+ }
+
+
+ var range = editorcore.createRange();
+
+ range.setStart(stn,0);
+ range.setEnd(stn,0);
+ var selection = editorcore.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+
+ //_this.updateToolbar(null, null, pn);
+ _this.updateToolbar(null, null, null);
+ _this.updateFooter(false);
}
- pn.removeChild(sn);
- var range = editorcore.createRange();
-
- range.setStart(stn,0);
- range.setEnd(en,0); //????
- //range.selectNode(sel);
-
-
- var selection = editorcore.getSelection();
- selection.removeAllRanges();
- selection.addRange(range);
-
-
-
- //_this.updateToolbar(null, null, pn);
- _this.updateToolbar(null, null, null);
- _this.footDisp.dom.innerHTML = '';
}
- }
-
+
+
-
- });
-
+ });
+ }
tb.el.on('click', function(e){
e.preventDefault(); // what does this do?
});
tb.el.setVisibilityMode( Roo.Element.DISPLAY);
tb.el.hide();
- tb.name = nm;
+
// dont need to disable them... as they will get hidden
return tb;
},
+ // when the footer contect changes
onContextClick : function (ev,dom)
{
ev.preventDefault();
var ans = this.footerEls;
var sel = ans[n];
- // pick
- var range = this.editorcore.createRange();
-
- range.selectNodeContents(sel);
- //range.selectNode(sel);
-
-
- var selection = this.editorcore.getSelection();
- selection.removeAllRanges();
- selection.addRange(range);
-
+ this.editorcore.selectNode(sel);
this.updateToolbar(null, null, sel);
return valid;
},
-
+ /**
+ * Returns array of invalid form fields.
+ * @return Array
+ */
+
+ invalidFields : function()
+ {
+ var ret = [];
+ this.items.each(function(f){
+ if(f.validate()){
+ return;
+ }
+ ret.push(f);
+
+ });
+
+ return ret;
+ },
+
+
/**
* DEPRICATED Returns true if any fields in this form have changed since their original load.
* @return Boolean
return this;
},
-
+
/**
* Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
* they are returned as an array.
* @param {Boolean} asString
* @return {Object}
*/
- getValues : function(asString){
+ getValues : function(asString)
+ {
if (this.childForms) {
// copy values from the child forms
Roo.each(this.childForms, function (f) {
- this.setValues(f.getValues());
+ this.setValues(f.getFieldValues()); // get the full set of data, as we might be copying comboboxes from external into this one.
}, this);
}
+ // use formdata
+ if (typeof(FormData) != 'undefined' && asString !== true) {
+ // this relies on a 'recent' version of chrome apparently...
+ try {
+ var fd = (new FormData(this.el.dom)).entries();
+ var ret = {};
+ var ent = fd.next();
+ while (!ent.done) {
+ ret[ent.value[0]] = ent.value[1]; // not sure how this will handle duplicates..
+ ent = fd.next();
+ };
+ return ret;
+ } catch(e) {
+
+ }
+
+ }
var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
/**
* Returns the fields in this form as an object with key/value pairs.
* This differs from getValues as it calls getValue on each child item, rather than using dom data.
+ * Normally this will not return readOnly data
+ * @param {Boolean} with_readonly return readonly field data.
* @return {Object}
*/
- getFieldValues : function(with_hidden)
+ getFieldValues : function(with_readonly)
{
if (this.childForms) {
// copy values from the child forms
// should this call getFieldValues - probably not as we do not currently copy
// hidden fields when we generate..
Roo.each(this.childForms, function (f) {
- this.setValues(f.getValues());
+ this.setValues(f.getFieldValues());
}, this);
}
var ret = {};
this.items.each(function(f){
+
+ if (f.readOnly && with_readonly !== true) {
+ return; // skip read only values. - this is in theory to stop 'old' values being copied over new ones
+ // if a subform contains a copy of them.
+ // if you have subforms with the same editable data, you will need to copy the data back
+ // and forth.
+ }
+
if (!f.getName()) {
return;
}
/**
* @class Roo.form.Form
* @extends Roo.form.BasicForm
+ * @children Roo.form.Column Roo.form.FieldSet Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem
* Adds the ability to dynamically render forms with JavaScript to {@link Roo.form.BasicForm}.
* @constructor
* @param {Object} config Configuration options
};
Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
+ /**
+ * @cfg {Roo.Button} buttons[] buttons at bottom of form
+ */
+
/**
* @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
*/
/**
* @class Roo.form.Layout
* @extends Roo.Component
+ * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet
* Creates a container for layout and rendering of fields in an {@link Roo.form.Form}.
* @constructor
* @param {Object} config Configuration options
/**
* @class Roo.form.Column
* @extends Roo.form.Layout
+ * @children Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet
* Creates a column container for layout and rendering of fields in an {@link Roo.form.Form}.
* @constructor
* @param {Object} config Configuration options
/**
* @class Roo.form.Row
* @extends Roo.form.Layout
+ * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet
* Creates a row container for layout and rendering of fields in an {@link Roo.form.Form}.
* @constructor
* @param {Object} config Configuration options
/**
* @class Roo.form.FieldSet
* @extends Roo.form.Layout
+ * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem
* Creates a fieldset container for layout and rendering of fields in an {@link Roo.form.Form}.
* @constructor
* @param {Object} config Configuration options
/**
* @class Roo.form.VTypes
* Overridable validation definitions. The validations provided are basic and intended to be easily customizable and extended.
- * @singleton
+ * @static
*/
Roo.form.VTypes = function(){
// closure these in so they are only created once.
this.getEl().setStyle("outline", "0px none");
this.getEl().unselectable();
if (this.dragGroup) {
- this.setDraggable(this.dragGroup.split(","));
+ this.setDraggable(this.dragGroup.split(","));
}
if (this.dropGroup) {
- this.setDroppable(this.dropGroup.split(","));
+ this.setDroppable(this.dropGroup.split(","));
}
if (this.deletable) {
this.setDeletable();
/**
* @class Roo.BorderLayout
* @extends Roo.LayoutManager
+ * @children Roo.ContentPanel
* This class represents a common layout manager used in desktop applications. For screenshots and more details,
* please see: <br><br>
* <a href="http://www.jackslocum.com/yui/2006/10/19/cross-browser-web-20-layouts-with-yahoo-ui/">Cross Browser Layouts - Part 1</a><br>
};
Roo.extend(Roo.BorderLayout, Roo.LayoutManager, {
+
+ /**
+ * @cfg {Roo.LayoutRegion} east
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} west
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} north
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} south
+ */
+ /**
+ * @cfg {Roo.LayoutRegion} center
+ */
/**
* Creates and adds a new region if it doesn't already exist.
* @param {String} target The target region key (north, south, east, west or center).
* Collapses this region.
* @param {Boolean} skipAnim (optional) true to collapse the element without animation (if animate is true)
*/
- collapse : function(skipAnim, skipCheck = false){
+ collapse : function(skipAnim, skipCheck){
if(this.collapsed) {
return;
}
/**
* @class Roo.ContentPanel
* @extends Roo.util.Observable
+ * @children Roo.form.Form Roo.JsonView Roo.View
+ * @parent Roo.BorderLayout Roo.LayoutDialog builder
* A basic ContentPanel element.
* @cfg {Boolean} fitToFrame True for this panel to adjust its size to fit when the region resizes (defaults to false)
* @cfg {Boolean} fitContainer When using {@link #fitToFrame} and {@link #resizeEl}, you can also fit the parent container (defaults to false)
- * @cfg {Boolean/Object} autoCreate True to auto generate the DOM element for this panel, or a {@link Roo.DomHelper} config of the element to create
+ * @cfg {Boolean|Object} autoCreate True to auto generate the DOM element for this panel, or a {@link Roo.DomHelper} config of the element to create
* @cfg {Boolean} closable True if the panel can be closed/removed
* @cfg {Boolean} background True if the panel should not be activated when it is added (defaults to false)
- * @cfg {String/HTMLElement/Element} resizeEl An element to resize if {@link #fitToFrame} is true (instead of this panel's element)
- * @cfg {Toolbar} toolbar A toolbar for this panel
+ * @cfg {String|HTMLElement|Element} resizeEl An element to resize if {@link #fitToFrame} is true (instead of this panel's element)
+ * @cfg {Roo.Toolbar} toolbar A toolbar for this panel
* @cfg {Boolean} autoScroll True to scroll overflow in this panel (use with {@link #fitToFrame})
* @cfg {String} title The title for this panel
* @cfg {Array} adjustments Values to <b>add</b> to the width/height when doing a {@link #fitToFrame} (default is [0, 0])
* @cfg {String} url Calls {@link #setUrl} with this value
- * @cfg {String} region (center|north|south|east|west) which region to put this panel on (when used with xtype constructors)
- * @cfg {String/Object} params When used with {@link #url}, calls {@link #setUrl} with this value
+ * @cfg {String} region (center|north|south|east|west) [required] which region to put this panel on (when used with xtype constructors)
+ * @cfg {String|Object} params When used with {@link #url}, calls {@link #setUrl} with this value
* @cfg {Boolean} loadOnce When used with {@link #url}, calls {@link #setUrl} with this value
* @cfg {String} content Raw content to fill content panel with (uses setContent on construction.)
+ * @cfg {String} style Extra style to add to the content panel
+ * @cfg {Roo.menu.Menu} menu popup menu
* @constructor
* Create a new ContentPanel.
{tag: "div", cls: "x-layout-inactive-content", id: config.id||el}, true);
}
}
+
+
this.closable = false;
this.loaded = false;
this.active = false;
}
});
+
+
+
+
+
+
+
+
+
+
+
/**
* @class Roo.GridPanel
* @extends Roo.ContentPanel
+ * @parent Roo.BorderLayout Roo.LayoutDialog builder
* @constructor
* Create a new GridPanel.
- * @param {Roo.grid.Grid} grid The grid for this panel
- * @param {String/Object} config A string to set only the panel's title, or a config object
+ * @cfg {Roo.grid.Grid} grid The grid for this panel
*/
Roo.GridPanel = function(grid, config){
-
+ // universal ctor...
+ if (typeof(grid.grid) != 'undefined') {
+ config = grid;
+ grid = config.grid;
+ }
this.wrapper = Roo.DomHelper.append(document.body, // wrapper for IE7 strict & safari scroll issue
{tag: "div", cls: "x-layout-grid-wrapper x-layout-inactive-content"}, true);
/**
* @class Roo.NestedLayoutPanel
* @extends Roo.ContentPanel
+ * @parent Roo.BorderLayout Roo.LayoutDialog builder
+ * @cfg {Roo.BorderLayout} layout [required] The layout for this panel
+ *
+ *
* @constructor
* Create a new NestedLayoutPanel.
*
*
- * @param {Roo.BorderLayout} layout The layout for this panel
+ * @param {Roo.BorderLayout} layout [required] The layout for this panel
* @param {String/Object} config A string to set only the title or a config object
*/
Roo.NestedLayoutPanel = function(layout, config)
Roo.extend(Roo.NestedLayoutPanel, Roo.ContentPanel, {
+ layout : false,
+
setSize : function(width, height){
if(!this.ignoreResize(width, height)){
var size = this.adjustForComponents(width, height);
/**
* Returns the nested BorderLayout for this panel
- * @return {Roo.BorderLayout}
+ * @return {Roo.BorderLayout}
*/
getLayout : function(){
return this.layout;
-
-
-
-
-
-
/**
* @class Roo.TreePanel
* @extends Roo.ContentPanel
+ * @parent Roo.BorderLayout Roo.LayoutDialog builder
+ * Treepanel component
+ *
* @constructor
* Create a new TreePanel. - defaults to fit/scoll contents.
* @param {String/Object} config A string to set only the panel's title, or a config object
- * @cfg {Roo.tree.TreePanel} tree The tree TreePanel, with config etc.
*/
Roo.TreePanel = function(config){
var el = config.el;
Roo.extend(Roo.TreePanel, Roo.ContentPanel, {
fitToFrame : true,
- autoScroll : true
-});
-
-
-
-
-
-
-
-
-
-
+ autoScroll : true,
+ /*
+ * @cfg {Roo.tree.TreePanel} tree [required] The tree TreePanel, with config etc.
+ */
+ tree : false
+});
/*
* Based on:
* Ext JS Library 1.1.1
Roo.extend(Roo.grid.Grid, Roo.util.Observable, {
/**
+ * @cfg {Roo.grid.AbstractSelectionModel} sm The selection Model (default = Roo.grid.RowSelectionModel)
+ */
+ /**
+ * @cfg {Roo.grid.GridView} view The view that renders the grid (default = Roo.grid.GridView)
+ */
+ /**
+ * @cfg {Roo.grid.ColumnModel} cm[] The columns of the grid
+ */
+ /**
+ * @cfg {Roo.grid.Store} ds The data store for the grid
+ */
+ /**
+ * @cfg {Roo.Toolbar} toolbar a toolbar for buttons etc.
+ */
+ /**
* @cfg {String} ddGroup - drag drop group.
*/
+ /**
+ * @cfg {String} dragGroup - drag group (?? not sure if needed.)
+ */
/**
* @cfg {Number} minColumnWidth The minimum width a column can be resized to. Default is 25.
/**
* @cfg {Boolean} enableDrag True to enable drag of rows. Default is false. (double check if this is needed?)
+ */
+ /**
+ * @cfg {Boolean} enableDrop True to enable drop of elements. Default is false. (double check if this is needed?)
*/
/**
* @cfg {Roo.dd.DropTarget} dropTarget An {@link Roo.dd.DropTarget} config
*/
dropTarget: false,
-
-
+ /**
+ * @cfg {boolean} sortColMenu Sort the column order menu when it shows (usefull for long lists..) default false
+ */
+ sortColMenu : false,
// private
rendered : false,
/**
* @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if autoHeight is not on.
*/
+
+
+ /**
+ * @cfg {String} ddText Configures the text is the drag proxy (defaults to "%0 selected row(s)").
+ * %0 is replaced with the number of selected rows.
+ */
+ ddText : "{0} selected row{1}",
+
+
/**
* Called once after all setup has been completed and the grid is ready to be rendered.
* @return {Roo.grid.Grid} this
return this;
},
- /**
- * Reconfigures the grid to use a different Store and Column Model.
- * The View will be bound to the new objects and refreshed.
- * @param {Roo.data.Store} dataSource The new {@link Roo.data.Store} object
- * @param {Roo.grid.ColumnModel} The new {@link Roo.grid.ColumnModel} object
- */
+ /**
+ * Reconfigures the grid to use a different Store and Column Model.
+ * The View will be bound to the new objects and refreshed.
+ * @param {Roo.data.Store} dataSource The new {@link Roo.data.Store} object
+ * @param {Roo.grid.ColumnModel} The new {@link Roo.grid.ColumnModel} object
+ */
reconfigure : function(dataSource, colModel){
if(this.loadMask){
this.loadMask.destroy();
this.colModel = colModel;
this.view.refresh(true);
},
-
+ /**
+ * addColumns
+ * Add's a column, default at the end..
+
+ * @param {int} position to add (default end)
+ * @param {Array} of objects of column configuration see {@link Roo.grid.ColumnModel}
+ */
+ addColumns : function(pos, ar)
+ {
+
+ for (var i =0;i< ar.length;i++) {
+ var cfg = ar[i];
+ cfg.id = typeof(cfg.id) == 'undefined' ? Roo.id() : cfg.id; // don't normally use this..
+ this.cm.lookup[cfg.id] = cfg;
+ }
+
+
+ if (typeof(pos) == 'undefined' || pos >= this.cm.config.length) {
+ pos = this.cm.config.length; //this.cm.config.push(cfg);
+ }
+ pos = Math.max(0,pos);
+ ar.unshift(0);
+ ar.unshift(pos);
+ this.cm.config.splice.apply(this.cm.config, ar);
+
+
+
+ this.view.generateRules(this.cm);
+ this.view.refresh(true);
+
+ },
+
+
+
+
// private
onKeyDown : function(e){
this.fireEvent("keydown", e);
getView : function(){
if(!this.view){
this.view = new Roo.grid.GridView(this.viewConfig);
+ this.relayEvents(this.view, [
+ "beforerowremoved", "beforerowsinserted",
+ "beforerefresh", "rowremoved",
+ "rowsinserted", "rowupdated" ,"refresh"
+ ]);
}
return this.view;
},
/**
* Called to get grid's drag proxy text, by default returns this.ddText.
+ * Override this to put something different in the dragged text.
* @return {String}
*/
getDragDropText : function(){
return String.format(this.ddText, count, count == 1 ? '' : 's');
}
});
-/**
- * Configures the text is the drag proxy (defaults to "%0 selected row(s)").
- * %0 is replaced with the number of selected rows.
- * @type String
- */
-Roo.grid.Grid.prototype.ddText = "{0} selected row{1}";/*
+/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
-
+ /**
+ * @class Roo.grid.AbstractGridView
+ * @extends Roo.util.Observable
+ * @abstract
+ * Abstract base class for grid Views
+ * @constructor
+ */
Roo.grid.AbstractGridView = function(){
this.grid = null;
);
*/
if(ctop < stop){
- c.scrollTop = ctop;
+ c.scrollTop = ctop;
//Roo.log("set scrolltop to ctop DISABLE?");
}else if(cbot > sbot){
//Roo.log("set scrolltop to cbot-ch");
beforeColMenuShow : function(){
var cm = this.cm, colCount = cm.getColumnCount();
this.colMenu.removeAll();
+
+ var items = [];
for(var i = 0; i < colCount; i++){
- this.colMenu.add(new Roo.menu.CheckItem({
+ items.push({
id: "col-"+cm.getColumnId(i),
text: cm.getColumnHeader(i),
checked: !cm.isHidden(i),
hideOnClick:false
- }));
+ });
+ }
+
+ if (this.grid.sortColMenu) {
+ items.sort(function(a,b) {
+ if (a.text == b.text) {
+ return 0;
+ }
+ return a.text.toUpperCase() > b.text.toUpperCase() ? 1 : -1;
+ });
+ }
+
+ for(var i = 0; i < colCount; i++){
+ this.colMenu.add(new Roo.menu.CheckItem(items[i]));
}
},
}
},
- layout : function(initialRender, is2ndPass){
+ layout : function(initialRender, is2ndPass)
+ {
var g = this.grid;
var auto = g.autoHeight;
var scrollOffset = 16;
* Fork - LGPL
* <script type="text/javascript">
*/
-
+ /**
+ * @extends Roo.dd.DDProxy
+ * @class Roo.grid.SplitDragZone
+ * Support for Column Header resizing
+ * @constructor
+ * @param {Object} config
+ */
// private
// This is a support class used internally by the Grid components
Roo.grid.SplitDragZone = function(grid, hd, hd2){
this.grid = grid;
this.view = grid.getView();
this.proxy = this.view.resizeProxy;
- Roo.grid.SplitDragZone.superclass.constructor.call(this, hd,
- "gridSplitters" + this.grid.getGridEl().id, {
- dragElId : Roo.id(this.proxy.dom), resizeFrame:false
- });
+ Roo.grid.SplitDragZone.superclass.constructor.call(
+ this,
+ hd, // ID
+ "gridSplitters" + this.grid.getGridEl().id, // SGROUP
+ { // CONFIG
+ dragElId : Roo.id(this.proxy.dom),
+ resizeFrame:false
+ }
+ );
+
this.setHandleElId(Roo.id(hd));
- this.setOuterHandleElId(Roo.id(hd2));
+ if (hd2 !== false) {
+ this.setOuterHandleElId(Roo.id(hd2));
+ }
+
this.scroll = false;
};
Roo.extend(Roo.grid.SplitDragZone, Roo.dd.DDProxy, {
b4StartDrag : function(x, y){
this.view.headersDisabled = true;
- this.proxy.setHeight(this.view.mainWrap.getHeight());
+ var h = this.view.mainWrap ? this.view.mainWrap.getHeight() : (
+ this.view.headEl.getHeight() + this.view.bodyEl.getHeight()
+ );
+ this.proxy.setHeight(h);
+
+ // for old system colWidth really stored the actual width?
+ // in bootstrap we tried using xs/ms/etc.. to do % sizing?
+ // which in reality did not work.. - it worked only for fixed sizes
+ // for resizable we need to use actual sizes.
var w = this.cm.getColumnWidth(this.cellIndex);
+ if (!this.view.mainWrap) {
+ // bootstrap.
+ w = this.view.getHeaderIndex(this.cellIndex).getWidth();
+ }
+
+
+
+ // this was w-this.grid.minColumnWidth;
+ // doesnt really make sense? - w = thie curren width or the rendered one?
var minw = Math.max(w-this.grid.minColumnWidth, 0);
this.resetConstraints();
this.setXConstraint(minw, 1000);
this.minX = x - minw;
this.maxX = x + 1000;
this.startPos = x;
+ if (!this.view.mainWrap) { // this is Bootstrap code..
+ this.getDragEl().style.display='block';
+ }
+
Roo.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
},
this.view.headersDisabled = false;
var endX = Math.max(this.minX, Roo.lib.Event.getPageX(e));
var diff = endX - this.startPos;
- this.view.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
+ //
+ var w = this.cm.getColumnWidth(this.cellIndex);
+ if (!this.view.mainWrap) {
+ w = 0;
+ }
+ this.view.onColumnSplitterMoved(this.cellIndex, w+diff);
},
autoOffset : function(){
}
}
+ if (sm.getSelections && sm.getSelections().length < 1) {
+ return false;
+ }
+
+ // before it used to all dragging of unseleted... - now we dont do that.
if(rowIndex !== false){
// if editorgrid..
grid: this.grid,
ddel: this.ddel,
rowIndex: rowIndex,
- selections:sm.getSelections ? sm.getSelections() : (
- sm.getSelectedCell() ? [ this.grid.ds.getAt(sm.getSelectedCell()[0]) ] : []
- )
+ selections: sm.getSelections ? sm.getSelections() : (
+ sm.getSelectedCell() ? [ this.grid.ds.getAt(sm.getSelectedCell()[0]) ] : [])
};
}
return false;
},
-
+
+
onInitDrag : function(e){
var data = this.dragData;
this.ddel.innerHTML = this.grid.getDragDropText();
/**
* The config passed into the constructor
*/
- this.config = config;
+ this.config = []; //config;
this.lookup = {};
// if no id, create one
// if the column does not have a dataIndex mapping,
// map it to the order it is in the config
for(var i = 0, len = config.length; i < len; i++){
- var c = config[i];
- if(typeof c.dataIndex == "undefined"){
- c.dataIndex = i;
- }
- if(typeof c.renderer == "string"){
- c.renderer = Roo.util.Format[c.renderer];
- }
- if(typeof c.id == "undefined"){
- c.id = Roo.id();
- }
- if(c.editor && c.editor.xtype){
- c.editor = Roo.factory(c.editor, Roo.grid);
- }
- if(c.editor && c.editor.isFormField){
- c.editor = new Roo.grid.GridEditor(c.editor);
- }
- this.lookup[c.id] = c;
+ this.addColumn(config[i]);
+
}
/**
Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, {
/**
* @cfg {String} header The header text to display in the Grid view.
+ */
+ /**
+ * @cfg {String} xsHeader Header at Bootsrap Extra Small width (default for all)
+ */
+ /**
+ * @cfg {String} smHeader Header at Bootsrap Small width
+ */
+ /**
+ * @cfg {String} mdHeader Header at Bootsrap Medium width
+ */
+ /**
+ * @cfg {String} lgHeader Header at Bootsrap Large width
+ */
+ /**
+ * @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} tooltip (Optional)
*/
/**
- * @cfg {Number} xs (Optional)
+ * @cfg {Number} xs (Optional) can be '0' for hidden at this size (number less than 12)
*/
/**
- * @cfg {Number} sm (Optional)
+ * @cfg {Number} sm (Optional) can be '0' for hidden at this size (number less than 12)
*/
/**
- * @cfg {Number} md (Optional)
+ * @cfg {Number} md (Optional) can be '0' for hidden at this size (number less than 12)
*/
/**
- * @cfg {Number} lg (Optional)
+ * @cfg {Number} lg (Optional) 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)
*/
/**
* Returns the id of the column at the specified index.
/**
- * Returns the column for a specified dataIndex.
+ * Returns the column Object for a specified dataIndex.
* @param {String} dataIndex The column dataIndex
* @return {Object|Boolean} the column or false if not found
*/
/**
* Returns the width for the specified column.
* @param {Number} col The column index
+ * @param (optional) {String} gridSize bootstrap width size.
* @return {Number}
*/
- getColumnWidth : function(col){
- return this.config[col].width * 1 || this.defaultWidth;
+ getColumnWidth : function(col, gridSize)
+ {
+ var cfg = this.config[col];
+
+ if (typeof(gridSize) == 'undefined') {
+ return cfg.width * 1 || this.defaultWidth;
+ }
+ if (gridSize === false) { // if we set it..
+ return cfg.width || false;
+ }
+ var sizes = ['xl', 'lg', 'md', 'sm', 'xs'];
+
+ for(var i = sizes.indexOf(gridSize); i < sizes.length; i++) {
+ if (typeof(cfg[ sizes[i] ] ) == 'undefined') {
+ continue;
+ }
+ return cfg[ sizes[i] ];
+ }
+ return 1;
+
},
/**
*/
setEditor : function(col, editor){
this.config[col].editor = editor;
+ },
+ /**
+ * Add a column (experimental...) - defaults to adding to the end..
+ * @param {Object} config
+ */
+ addColumn : function(c)
+ {
+
+ var i = this.config.length;
+ this.config[i] = c;
+
+ if(typeof c.dataIndex == "undefined"){
+ c.dataIndex = i;
+ }
+ if(typeof c.renderer == "string"){
+ c.renderer = Roo.util.Format[c.renderer];
+ }
+ if(typeof c.id == "undefined"){
+ c.id = Roo.id();
+ }
+ if(c.editor && c.editor.xtype){
+ c.editor = Roo.factory(c.editor, Roo.grid);
+ }
+ if(c.editor && c.editor.isFormField){
+ c.editor = new Roo.grid.GridEditor(c.editor);
+ }
+ this.lookup[c.id] = c;
}
+
});
Roo.grid.ColumnModel.defaultRenderer = function(value)
/**
* @class Roo.grid.AbstractSelectionModel
* @extends Roo.util.Observable
+ * @abstract
* Abstract base class for grid SelectionModels. It provides the interface that should be
* implemented by descendant classes. This class should not be directly instantiated.
* @constructor
this.addEvents({
/**
- * @event selectionchange
- * Fires when the selection changes
- * @param {SelectionModel} this
- */
- "selectionchange" : true,
- /**
- * @event afterselectionchange
- * Fires after the selection changes (eg. by key press or clicking)
- * @param {SelectionModel} this
- */
- "afterselectionchange" : true,
- /**
- * @event beforerowselect
- * Fires when a row is selected being selected, return false to cancel.
- * @param {SelectionModel} this
- * @param {Number} rowIndex The selected index
- * @param {Boolean} keepExisting False if other selections will be cleared
- */
- "beforerowselect" : true,
- /**
- * @event rowselect
- * Fires when a row is selected.
- * @param {SelectionModel} this
- * @param {Number} rowIndex The selected index
- * @param {Roo.data.Record} r The record
- */
- "rowselect" : true,
- /**
- * @event rowdeselect
- * Fires when a row is deselected.
- * @param {SelectionModel} this
- * @param {Number} rowIndex The selected index
- */
+ * @event selectionchange
+ * Fires when the selection changes
+ * @param {SelectionModel} this
+ */
+ "selectionchange" : true,
+ /**
+ * @event afterselectionchange
+ * Fires after the selection changes (eg. by key press or clicking)
+ * @param {SelectionModel} this
+ */
+ "afterselectionchange" : true,
+ /**
+ * @event beforerowselect
+ * Fires when a row is selected being selected, return false to cancel.
+ * @param {SelectionModel} this
+ * @param {Number} rowIndex The selected index
+ * @param {Boolean} keepExisting False if other selections will be cleared
+ */
+ "beforerowselect" : true,
+ /**
+ * @event rowselect
+ * Fires when a row is selected.
+ * @param {SelectionModel} this
+ * @param {Number} rowIndex The selected index
+ * @param {Roo.data.Record} r The record
+ */
+ "rowselect" : true,
+ /**
+ * @event rowdeselect
+ * Fires when a row is deselected.
+ * @param {SelectionModel} this
+ * @param {Number} rowIndex The selected index
+ */
"rowdeselect" : true
});
Roo.grid.RowSelectionModel.superclass.constructor.call(this);
}else{ // allow click to work like normal
this.grid.on("rowclick", this.handleDragableRowClick, this);
}
-
+ // bootstrap does not have a view..
+ var view = this.grid.view ? this.grid.view : this.grid;
this.rowNav = new Roo.KeyNav(this.grid.getGridEl(), {
"up" : function(e){
if(!e.shiftKey){
}else if(this.last !== false && this.lastActive !== false){
var last = this.last;
this.selectRange(this.last, this.lastActive-1);
- this.grid.getView().focusRow(this.lastActive);
+ view.focusRow(this.lastActive);
if(last !== false){
this.last = last;
}
}else if(this.last !== false && this.lastActive !== false){
var last = this.last;
this.selectRange(this.last, this.lastActive+1);
- this.grid.getView().focusRow(this.lastActive);
+ view.focusRow(this.lastActive);
if(last !== false){
this.last = last;
}
scope: this
});
- var view = this.grid.view;
+
view.on("refresh", this.onRefresh, this);
view.on("rowupdated", this.onRowUpdated, this);
view.on("rowremoved", this.onRemove, this);
// private
onRefresh : function(){
- var ds = this.grid.dataSource, i, v = this.grid.view;
+ var ds = this.grid.ds, i, v = this.grid.view;
var s = this.selections;
s.each(function(r){
if((i = ds.indexOfId(r.id)) != -1){
if(!keepExisting){
this.clearSelections();
}
- var ds = this.grid.dataSource;
+ var ds = this.grid.ds;
for(var i = 0, len = records.length; i < len; i++){
this.selectRow(ds.indexOf(records[i]), true);
}
* @param {Boolean} keepExisting (optional) True to keep existing selections
*/
selectLastRow : function(keepExisting){
- this.selectRow(this.grid.dataSource.getCount() - 1, keepExisting);
+ this.selectRow(this.grid.ds.getCount() - 1, keepExisting);
},
/**
* @param {Boolean} keepExisting (optional) True to keep existing selections
*/
selectNext : function(keepExisting){
- if(this.last !== false && (this.last+1) < this.grid.dataSource.getCount()){
+ if(this.last !== false && (this.last+1) < this.grid.ds.getCount()){
this.selectRow(this.last+1, keepExisting);
- this.grid.getView().focusRow(this.last);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.focusRow(this.last);
}
},
selectPrevious : function(keepExisting){
if(this.last){
this.selectRow(this.last-1, keepExisting);
- this.grid.getView().focusRow(this.last);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.focusRow(this.last);
}
},
return;
}
if(fast !== true){
- var ds = this.grid.dataSource;
+ var ds = this.grid.ds;
var s = this.selections;
s.each(function(r){
this.deselectRow(ds.indexOfId(r.id));
return;
}
this.selections.clear();
- for(var i = 0, len = this.grid.dataSource.getCount(); i < len; i++){
+ for(var i = 0, len = this.grid.ds.getCount(); i < len; i++){
this.selectRow(i, true);
}
},
* @return {Boolean}
*/
isSelected : function(index){
- var r = typeof index == "number" ? this.grid.dataSource.getAt(index) : index;
+ var r = typeof index == "number" ? this.grid.ds.getAt(index) : index;
return (r && this.selections.key(r.id) ? true : false);
},
},
// private
- handleMouseDown : function(e, t){
- var view = this.grid.getView(), rowIndex;
+ handleMouseDown : function(e, t)
+ {
+ var view = this.grid.view ? this.grid.view : this.grid;
+ var rowIndex;
if(this.isLocked() || (rowIndex = view.findRowIndex(t)) === false){
return;
};
{
if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
this.selectRow(rowIndex, false);
- grid.view.focusRow(rowIndex);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.focusRow(rowIndex);
this.fireEvent("afterselectionchange", this);
}
},
* @param {Boolean} keepExisting (optional) True to keep existing selections
*/
selectRow : function(index, keepExisting, preventViewNotify){
- if(this.locked || (index < 0 || index >= this.grid.dataSource.getCount())) {
+ if(this.locked || (index < 0 || index >= this.grid.ds.getCount())) {
return;
}
if(this.fireEvent("beforerowselect", this, index, keepExisting) !== false){
if(!keepExisting || this.singleSelect){
this.clearSelections();
}
- var r = this.grid.dataSource.getAt(index);
+ var r = this.grid.ds.getAt(index);
this.selections.add(r);
this.last = this.lastActive = index;
if(!preventViewNotify){
- this.grid.getView().onRowSelect(index);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.onRowSelect(index);
}
this.fireEvent("rowselect", this, index, r);
this.fireEvent("selectionchange", this);
if(this.lastActive == index){
this.lastActive = false;
}
- var r = this.grid.dataSource.getAt(index);
+ var r = this.grid.ds.getAt(index);
this.selections.remove(r);
if(!preventViewNotify){
- this.grid.getView().onRowDeselect(index);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.onRowDeselect(index);
}
this.fireEvent("rowdeselect", this, index);
this.fireEvent("selectionchange", this);
/**
* @class Roo.grid.Calendar
- * @extends Roo.util.Grid
+ * @extends Roo.grid.Grid
* This class extends the Grid to provide a calendar widget
* <br><br>Usage:<pre><code>
var grid = new Roo.grid.Calendar("my-container-id", {
* True to create a single-use mask that is automatically destroyed after loading (useful for page loads),
* False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false.
*/
+ removeMask : false,
/**
* @cfg {String} msg
* The text to display in a centered loading message box (defaults to 'Loading...')