/**
* @class Roo
* Roo core utilities and functions.
- * @singleton
+ * @static
*/
var Roo = {};
/**
{
var node = Roo.DomQuery.selectNode(selector,root);
return node ? Roo.get(node) : new Roo.Element(false);
- }
+ },
+ /**
+ * Find the current bootstrap width Grid size
+ * Note xs is the default for smaller.. - this is currently used by grids to render correct columns
+ * @returns {String} (xs|sm|md|lg|xl)
+ */
+
+ getGridSize : function()
+ {
+ var w = Roo.lib.Dom.getViewWidth();
+ switch(true) {
+ case w > 1200:
+ return 'xl';
+ case w > 992:
+ return 'lg';
+ case w > 768:
+ return 'md';
+ case w > 576:
+ return 'sm';
+ default:
+ return 'xs'
+ }
+
+ }
});
Roo.namespace("Roo", "Roo.util", "Roo.grid", "Roo.dd", "Roo.tree", "Roo.data",
"Roo.form", "Roo.menu", "Roo.state", "Roo.lib", "Roo.layout",
- "Roo.app", "Roo.ux",
- "Roo.bootstrap",
- "Roo.bootstrap.dash");
+ "Roo.app", "Roo.ux"
+ );
/*
* Based on:
* Ext JS Library 1.1.1
);
};
+
+/**
+ * Make the first letter of a string uppercase
+ *
+ * @return {String} The new string.
+ */
+String.prototype.toUpperCaseFirst = function () {
+ return this.charAt(0).toUpperCase() + this.slice(1);
+};
+
/*
* Based on:
* Ext JS Library 1.1.1
*/
equals : function(b)
{
- // https://stackoverflow.com/questions/3115982/how-to-check-if-two-arrays-are-equal-with-javascript
- if (this === b) {
- return true;
- }
- if (b == null) {
- return false;
- }
- if (this.length !== b.length) {
- return false;
- }
-
- // sort?? a.sort().equals(b.sort());
+ // https://stackoverflow.com/questions/3115982/how-to-check-if-two-arrays-are-equal-with-javascript
+ if (this === b) {
+ return true;
+ }
+ if (b == null) {
+ return false;
+ }
+ if (this.length !== b.length) {
+ return false;
+ }
+
+ // sort?? a.sort().equals(b.sort());
+
+ for (var i = 0; i < this.length; ++i) {
+ if (this[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+
+
+});
+
+Roo.applyIf(Array, {
+ /**
+ * from
+ * @static
+ * @param {Array} o Or Array like object (eg. nodelist)
+ * @returns {Array}
+ */
+ from : function(o)
+ {
+ var ret= [];
+
+ for (var i =0; i < o.length; i++) {
+ ret[i] = o[i];
+ }
+ return ret;
- for (var i = 0; i < this.length; ++i) {
- if (this[i] !== b[i]) {
- return false;
- }
- }
- return true;
}
});
-
-
-
/*
* Based on:
* Ext JS Library 1.1.1
/**
Returns the number of milliseconds between this date and date
@param {Date} date (optional) Defaults to now
- @return {Number} The diff in milliseconds
+ @param {String} interval (optional) Default Date.MILLI, A valid date interval enum value (eg. Date.DAY)
+ @return {Number} The diff in milliseconds or units of interval
@member Date getElapsed
*/
-Date.prototype.getElapsed = function(date) {
- return Math.abs((date || new Date()).getTime()-this.getTime());
+Date.prototype.getElapsed = function(date, interval)
+{
+ date = date || new Date();
+ var ret = Math.abs(date.getTime()-this.getTime());
+ switch (interval) {
+
+ case Date.SECOND:
+ return Math.floor(ret / (1000));
+ case Date.MINUTE:
+ return Math.floor(ret / (1000*60));
+ case Date.HOUR:
+ return Math.floor(ret / (1000*60*60));
+ case Date.DAY:
+ return Math.floor(ret / (1000*60*60*24));
+ case Date.MONTH: // this does not give exact number...??
+ return ((date.format("Y") - this.format("Y")) * 12) + (date.format("m") - this.format("m"));
+ case Date.YEAR: // this does not give exact number...??
+ return (date.format("Y") - this.format("Y"));
+
+ case Date.MILLI:
+ default:
+ return ret;
+ }
};
+
// was in date file..
}
code += "if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
- + "{v = new Date(y, m, d, h, i, s);}\n"
+ + "{v = new Date(y, m, d, h, i, s); v.setFullYear(y);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
- + "{v = new Date(y, m, d, h, i);}\n"
+ + "{v = new Date(y, m, d, h, i); v.setFullYear(y);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0 && h >= 0)\n"
- + "{v = new Date(y, m, d, h);}\n"
+ + "{v = new Date(y, m, d, h); v.setFullYear(y);}\n"
+ "else if (y >= 0 && m >= 0 && d > 0)\n"
- + "{v = new Date(y, m, d);}\n"
+ + "{v = new Date(y, m, d); v.setFullYear(y);}\n"
+ "else if (y >= 0 && m >= 0)\n"
- + "{v = new Date(y, m);}\n"
+ + "{v = new Date(y, m); v.setFullYear(y);}\n"
+ "else if (y >= 0)\n"
- + "{v = new Date(y);}\n"
+ + "{v = new Date(y); v.setFullYear(y);}\n"
+ "}return (v && (z || o))?\n" // favour UTC offset over GMT offset
+ " ((z)? v.add(Date.SECOND, (v.getTimezoneOffset() * 60) + (z*1)) :\n" // reset to UTC, then add offset
+ " v.add(Date.HOUR, (v.getGMTOffset() / 100) + (o / -100))) : v\n" // reset to GMT, then add offset
s:"(\\d{1,2})"}; // Numeric representation of a month, without leading zeros
case "m":
return {g:1,
- c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
+ c:"m = Math.max(0,parseInt(results[" + currentGroup + "], 10) - 1);\n",
s:"(\\d{2})"}; // Numeric representation of a month, with leading zeros
case "t":
return {g:0,
}
return d;
};
-/*
- * 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">
- */
-
/**
* @class Roo.lib.Dom
+ * @licence LGPL
* @static
*
* Dom utils (from YIU afaik)
+ *
*
**/
Roo.lib.Dom = {
getViewHeight : function(full) {
return full ? this.getDocumentHeight() : this.getViewportHeight();
},
-
+ /**
+ * Get the Full Document height
+ * @return {Number} The height
+ */
getDocumentHeight: function() {
var scrollHeight = (document.compatMode != "CSS1Compat") ? document.body.scrollHeight : document.documentElement.scrollHeight;
return Math.max(scrollHeight, this.getViewportHeight());
},
-
+ /**
+ * Get the Full Document width
+ * @return {Number} The width
+ */
getDocumentWidth: function() {
var scrollWidth = (document.compatMode != "CSS1Compat") ? document.body.scrollWidth : document.documentElement.scrollWidth;
return Math.max(scrollWidth, this.getViewportWidth());
},
-
+ /**
+ * Get the Window Viewport height
+ * @return {Number} The height
+ */
getViewportHeight: function() {
var height = self.innerHeight;
var mode = document.compatMode;
return height;
},
-
+ /**
+ * Get the Window Viewport width
+ * @return {Number} The width
+ */
getViewportWidth: function() {
var width = self.innerWidth;
var mode = document.compatMode;
E._tryPreloadAttach();
})();
-/*
- * Portions of this file are based on pieces of Yahoo User Interface Library
- * Copyright (c) 2007, Yahoo! Inc. All rights reserved.
- * YUI licensed under the BSD License:
- * http://developer.yahoo.net/yui/license.txt
- * <script type="text/javascript">
- *
- */
+
(function() {
/**
* @class Roo.lib.Ajax
+ *
+ * provide a simple Ajax request utility functions
+ *
+ * Portions of this file are based on pieces of Yahoo User Interface Library
+ * Copyright (c) 2007, Yahoo! Inc. All rights reserved.
+ * YUI licensed under the BSD License:
+ * http://developer.yahoo.net/yui/license.txt
+ * <script type="text/javascript">
+ *
*
*/
Roo.lib.Ajax = {
return this.asyncRequest(method, uri, cb, data);
},
-
+ /**
+ * serialize a form
+ *
+ * @static
+ * @param {DomForm} form element
+ * @return {String} urlencode form output.
+ */
serializeForm : function(form) {
if(typeof form == 'string') {
form = (document.getElementById(form) || document.forms[form]);
return [ tmp[0][0], tmp[0][1] ];
};
- };/*
+ };
+
+/**
+ * @class Roo.lib.Color
+ * @constructor
+ * An abstract Color implementation. Concrete Color implementations should use
+ * an instance of this function as their prototype, and implement the getRGB and
+ * getHSL functions. getRGB should return an object representing the RGB
+ * components of this Color, with the red, green, and blue components in the
+ * range [0,255] and the alpha component in the range [0,100]. getHSL should
+ * return an object representing the HSL components of this Color, with the hue
+ * component in the range [0,360), the saturation and lightness components in
+ * the range [0,100], and the alpha component in the range [0,1].
+ *
+ *
+ * Color.js
+ *
+ * Functions for Color handling and processing.
+ *
+ * http://www.safalra.com/web-design/javascript/Color-handling-and-processing/
+ *
+ * The author of this program, Safalra (Stephen Morley), irrevocably releases all
+ * rights to this program, with the intention of it becoming part of the public
+ * domain. Because this program is released into the public domain, it comes with
+ * no warranty either expressed or implied, to the extent permitted by law.
+ *
+ * For more free and public domain JavaScript code by the same author, visit:
+ * http://www.safalra.com/web-design/javascript/
+ *
+ */
+Roo.lib.Color = function() { }
+
+
+Roo.apply(Roo.lib.Color.prototype, {
+
+ rgb : null,
+ hsv : null,
+ hsl : null,
+
+ /**
+ * getIntegerRGB
+ * @return {Object} an object representing the RGBA components of this Color. The red,
+ * green, and blue components are converted to integers in the range [0,255].
+ * The alpha is a value in the range [0,1].
+ */
+ getIntegerRGB : function(){
+
+ // get the RGB components of this Color
+ var rgb = this.getRGB();
+
+ // return the integer components
+ return {
+ 'r' : Math.round(rgb.r),
+ 'g' : Math.round(rgb.g),
+ 'b' : Math.round(rgb.b),
+ 'a' : rgb.a
+ };
+
+ },
+
+ /**
+ * getPercentageRGB
+ * @return {Object} an object representing the RGBA components of this Color. The red,
+ * green, and blue components are converted to numbers in the range [0,100].
+ * The alpha is a value in the range [0,1].
+ */
+ getPercentageRGB : function(){
+
+ // get the RGB components of this Color
+ var rgb = this.getRGB();
+
+ // return the percentage components
+ return {
+ 'r' : 100 * rgb.r / 255,
+ 'g' : 100 * rgb.g / 255,
+ 'b' : 100 * rgb.b / 255,
+ 'a' : rgb.a
+ };
+
+ },
+
+ /**
+ * getCSSHexadecimalRGB
+ * @return {String} a string representing this Color as a CSS hexadecimal RGB Color
+ * value - that is, a string of the form #RRGGBB where each of RR, GG, and BB
+ * are two-digit hexadecimal numbers.
+ */
+ getCSSHexadecimalRGB : function()
+ {
+
+ // get the integer RGB components
+ var rgb = this.getIntegerRGB();
+
+ // determine the hexadecimal equivalents
+ var r16 = rgb.r.toString(16);
+ var g16 = rgb.g.toString(16);
+ var b16 = rgb.b.toString(16);
+
+ // return the CSS RGB Color value
+ return '#'
+ + (r16.length == 2 ? r16 : '0' + r16)
+ + (g16.length == 2 ? g16 : '0' + g16)
+ + (b16.length == 2 ? b16 : '0' + b16);
+
+ },
+
+ /**
+ * getCSSIntegerRGB
+ * @return {String} a string representing this Color as a CSS integer RGB Color
+ * value - that is, a string of the form rgb(r,g,b) where each of r, g, and b
+ * are integers in the range [0,255].
+ */
+ getCSSIntegerRGB : function(){
+
+ // get the integer RGB components
+ var rgb = this.getIntegerRGB();
+
+ // return the CSS RGB Color value
+ return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
+
+ },
+
+ /**
+ * getCSSIntegerRGBA
+ * @return {String} Returns a string representing this Color as a CSS integer RGBA Color
+ * value - that is, a string of the form rgba(r,g,b,a) where each of r, g, and
+ * b are integers in the range [0,255] and a is in the range [0,1].
+ */
+ getCSSIntegerRGBA : function(){
+
+ // get the integer RGB components
+ var rgb = this.getIntegerRGB();
+
+ // return the CSS integer RGBA Color value
+ return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')';
+
+ },
+
+ /**
+ * getCSSPercentageRGB
+ * @return {String} a string representing this Color as a CSS percentage RGB Color
+ * value - that is, a string of the form rgb(r%,g%,b%) where each of r, g, and
+ * b are in the range [0,100].
+ */
+ getCSSPercentageRGB : function(){
+
+ // get the percentage RGB components
+ var rgb = this.getPercentageRGB();
+
+ // return the CSS RGB Color value
+ return 'rgb(' + rgb.r + '%,' + rgb.g + '%,' + rgb.b + '%)';
+
+ },
+
+ /**
+ * getCSSPercentageRGBA
+ * @return {String} a string representing this Color as a CSS percentage RGBA Color
+ * value - that is, a string of the form rgba(r%,g%,b%,a) where each of r, g,
+ * and b are in the range [0,100] and a is in the range [0,1].
+ */
+ getCSSPercentageRGBA : function(){
+
+ // get the percentage RGB components
+ var rgb = this.getPercentageRGB();
+
+ // return the CSS percentage RGBA Color value
+ return 'rgb(' + rgb.r + '%,' + rgb.g + '%,' + rgb.b + '%,' + rgb.a + ')';
+
+ },
+
+ /**
+ * getCSSHSL
+ * @return {String} a string representing this Color as a CSS HSL Color value - that
+ * is, a string of the form hsl(h,s%,l%) where h is in the range [0,100] and
+ * s and l are in the range [0,100].
+ */
+ getCSSHSL : function(){
+
+ // get the HSL components
+ var hsl = this.getHSL();
+
+ // return the CSS HSL Color value
+ return 'hsl(' + hsl.h + ',' + hsl.s + '%,' + hsl.l + '%)';
+
+ },
+
+ /**
+ * getCSSHSLA
+ * @return {String} a string representing this Color as a CSS HSLA Color value - that
+ * is, a string of the form hsla(h,s%,l%,a) where h is in the range [0,100],
+ * s and l are in the range [0,100], and a is in the range [0,1].
+ */
+ getCSSHSLA : function(){
+
+ // get the HSL components
+ var hsl = this.getHSL();
+
+ // return the CSS HSL Color value
+ return 'hsl(' + hsl.h + ',' + hsl.s + '%,' + hsl.l + '%,' + hsl.a + ')';
+
+ },
+
+ /**
+ * Sets the Color of the specified node to this Color. This functions sets
+ * the CSS 'color' property for the node. The parameter is:
+ *
+ * @param {DomElement} node - the node whose Color should be set
+ */
+ setNodeColor : function(node){
+
+ // set the Color of the node
+ node.style.color = this.getCSSHexadecimalRGB();
+
+ },
+
+ /**
+ * Sets the background Color of the specified node to this Color. This
+ * functions sets the CSS 'background-color' property for the node. The
+ * parameter is:
+ *
+ * @param {DomElement} node - the node whose background Color should be set
+ */
+ setNodeBackgroundColor : function(node){
+
+ // set the background Color of the node
+ node.style.backgroundColor = this.getCSSHexadecimalRGB();
+
+ },
+ // convert between formats..
+ toRGB: function()
+ {
+ var r = this.getIntegerRGB();
+ return new Roo.lib.RGBColor(r.r,r.g,r.b,r.a);
+
+ },
+ toHSL : function()
+ {
+ var hsl = this.getHSL();
+ // return the CSS HSL Color value
+ return new Roo.lib.HSLColor(hsl.h, hsl.s, hsl.l , hsl.a );
+
+ },
+
+ toHSV : function()
+ {
+ var rgb = this.toRGB();
+ var hsv = rgb.getHSV();
+ // return the CSS HSL Color value
+ return new Roo.lib.HSVColor(hsv.h, hsv.s, hsv.v , hsv.a );
+
+ },
+
+ // modify v = 0 ... 1 (eg. 0.5)
+ saturate : function(v)
+ {
+ var rgb = this.toRGB();
+ var hsv = rgb.getHSV();
+ return new Roo.lib.HSVColor(hsv.h, hsv.s * v, hsv.v , hsv.a );
+
+
+ },
+
+
+ /**
+ * getRGB
+ * @return {Object} the RGB and alpha components of this Color as an object with r,
+ * g, b, and a properties. r, g, and b are in the range [0,255] and a is in
+ * the range [0,1].
+ */
+ getRGB: function(){
+
+ // return the RGB components
+ return {
+ 'r' : this.rgb.r,
+ 'g' : this.rgb.g,
+ 'b' : this.rgb.b,
+ 'a' : this.alpha
+ };
+
+ },
+
+ /**
+ * getHSV
+ * @return {Object} the HSV and alpha components of this Color as an object with h,
+ * s, v, and a properties. h is in the range [0,360), s and v are in the range
+ * [0,100], and a is in the range [0,1].
+ */
+ getHSV : function()
+ {
+
+ // calculate the HSV components if necessary
+ if (this.hsv == null) {
+ this.calculateHSV();
+ }
+
+ // return the HSV components
+ return {
+ 'h' : this.hsv.h,
+ 's' : this.hsv.s,
+ 'v' : this.hsv.v,
+ 'a' : this.alpha
+ };
+
+ },
+
+ /**
+ * getHSL
+ * @return {Object} the HSL and alpha components of this Color as an object with h,
+ * s, l, and a properties. h is in the range [0,360), s and l are in the range
+ * [0,100], and a is in the range [0,1].
+ */
+ getHSL : function(){
+
+
+ // calculate the HSV components if necessary
+ if (this.hsl == null) { this.calculateHSL(); }
+
+ // return the HSL components
+ return {
+ 'h' : this.hsl.h,
+ 's' : this.hsl.s,
+ 'l' : this.hsl.l,
+ 'a' : this.alpha
+ };
+
+ }
+
+
+});
+
+
+/**
+ * @class Roo.lib.RGBColor
+ * @extends Roo.lib.Color
+ * Creates a Color specified in the RGB Color space, with an optional alpha
+ * component. The parameters are:
+ * @constructor
+ *
+
+ * @param {Number} r - the red component, clipped to the range [0,255]
+ * @param {Number} g - the green component, clipped to the range [0,255]
+ * @param {Number} b - the blue component, clipped to the range [0,255]
+ * @param {Number} a - the alpha component, clipped to the range [0,1] - this parameter is
+ * optional and defaults to 1
+ */
+Roo.lib.RGBColor = function (r, g, b, a){
+
+ // store the alpha component after clipping it if necessary
+ this.alpha = (a === undefined ? 1 : Math.max(0, Math.min(1, a)));
+
+ // store the RGB components after clipping them if necessary
+ this.rgb =
+ {
+ 'r' : Math.max(0, Math.min(255, r)),
+ 'g' : Math.max(0, Math.min(255, g)),
+ 'b' : Math.max(0, Math.min(255, b))
+ };
+
+ // initialise the HSV and HSL components to null
+
+
+ /*
+ * //private returns the HSV or HSL hue component of this RGBColor. The hue is in the
+ * range [0,360). The parameters are:
+ *
+ * maximum - the maximum of the RGB component values
+ * range - the range of the RGB component values
+ */
+
+
+}
+// this does an 'exteds'
+Roo.extend(Roo.lib.RGBColor, Roo.lib.Color, {
+
+
+ getHue : function(maximum, range)
+ {
+ var rgb = this.rgb;
+
+ // check whether the range is zero
+ if (range == 0){
+
+ // set the hue to zero (any hue is acceptable as the Color is grey)
+ var hue = 0;
+
+ }else{
+
+ // determine which of the components has the highest value and set the hue
+ switch (maximum){
+
+ // red has the highest value
+ case rgb.r:
+ var hue = (rgb.g - rgb.b) / range * 60;
+ if (hue < 0) { hue += 360; }
+ break;
+
+ // green has the highest value
+ case rgb.g:
+ var hue = (rgb.b - rgb.r) / range * 60 + 120;
+ break;
+
+ // blue has the highest value
+ case rgb.b:
+ var hue = (rgb.r - rgb.g) / range * 60 + 240;
+ break;
+
+ }
+
+ }
+
+ // return the hue
+ return hue;
+
+ },
+
+ /* //private Calculates and stores the HSV components of this RGBColor so that they can
+ * be returned be the getHSV function.
+ */
+ calculateHSV : function(){
+ var rgb = this.rgb;
+ // get the maximum and range of the RGB component values
+ var maximum = Math.max(rgb.r, rgb.g, rgb.b);
+ var range = maximum - Math.min(rgb.r, rgb.g, rgb.b);
+
+ // store the HSV components
+ this.hsv =
+ {
+ 'h' : this.getHue(maximum, range),
+ 's' : (maximum == 0 ? 0 : 100 * range / maximum),
+ 'v' : maximum / 2.55
+ };
+
+ },
+
+ /* //private Calculates and stores the HSL components of this RGBColor so that they can
+ * be returned be the getHSL function.
+ */
+ calculateHSL : function(){
+ var rgb = this.rgb;
+ // get the maximum and range of the RGB component values
+ var maximum = Math.max(rgb.r, rgb.g, rgb.b);
+ var range = maximum - Math.min(rgb.r, rgb.g, rgb.b);
+
+ // determine the lightness in the range [0,1]
+ var l = maximum / 255 - range / 510;
+
+ // store the HSL components
+ this.hsl =
+ {
+ 'h' : this.getHue(maximum, range),
+ 's' : (range == 0 ? 0 : range / 2.55 / (l < 0.5 ? l * 2 : 2 - l * 2)),
+ 'l' : 100 * l
+ };
+
+ }
+
+});
+
+/**
+ * @class Roo.lib.HSVColor
+ * @extends Roo.lib.Color
+ * Creates a Color specified in the HSV Color space, with an optional alpha
+ * component. The parameters are:
+ * @constructor
+ *
+ * @param {Number} h - the hue component, wrapped to the range [0,360)
+ * @param {Number} s - the saturation component, clipped to the range [0,100]
+ * @param {Number} v - the value component, clipped to the range [0,100]
+ * @param {Number} a - the alpha component, clipped to the range [0,1] - this parameter is
+ * optional and defaults to 1
+ */
+Roo.lib.HSVColor = function (h, s, v, a){
+
+ // store the alpha component after clipping it if necessary
+ this.alpha = (a === undefined ? 1 : Math.max(0, Math.min(1, a)));
+
+ // store the HSV components after clipping or wrapping them if necessary
+ this.hsv =
+ {
+ 'h' : (h % 360 + 360) % 360,
+ 's' : Math.max(0, Math.min(100, s)),
+ 'v' : Math.max(0, Math.min(100, v))
+ };
+
+ // initialise the RGB and HSL components to null
+ this.rgb = null;
+ this.hsl = null;
+}
+
+Roo.extend(Roo.lib.HSVColor, Roo.lib.Color, {
+ /* Calculates and stores the RGB components of this HSVColor so that they can
+ * be returned be the getRGB function.
+ */
+ calculateRGB: function ()
+ {
+ var hsv = this.hsv;
+ // check whether the saturation is zero
+ if (hsv.s == 0){
+
+ // set the Color to the appropriate shade of grey
+ var r = hsv.v;
+ var g = hsv.v;
+ var b = hsv.v;
+
+ }else{
+
+ // set some temporary values
+ var f = hsv.h / 60 - Math.floor(hsv.h / 60);
+ var p = hsv.v * (1 - hsv.s / 100);
+ var q = hsv.v * (1 - hsv.s / 100 * f);
+ var t = hsv.v * (1 - hsv.s / 100 * (1 - f));
+
+ // set the RGB Color components to their temporary values
+ switch (Math.floor(hsv.h / 60)){
+ case 0: var r = hsv.v; var g = t; var b = p; break;
+ case 1: var r = q; var g = hsv.v; var b = p; break;
+ case 2: var r = p; var g = hsv.v; var b = t; break;
+ case 3: var r = p; var g = q; var b = hsv.v; break;
+ case 4: var r = t; var g = p; var b = hsv.v; break;
+ case 5: var r = hsv.v; var g = p; var b = q; break;
+ }
+
+ }
+
+ // store the RGB components
+ this.rgb =
+ {
+ 'r' : r * 2.55,
+ 'g' : g * 2.55,
+ 'b' : b * 2.55
+ };
+
+ },
+
+ /* Calculates and stores the HSL components of this HSVColor so that they can
+ * be returned be the getHSL function.
+ */
+ calculateHSL : function (){
+
+ var hsv = this.hsv;
+ // determine the lightness in the range [0,100]
+ var l = (2 - hsv.s / 100) * hsv.v / 2;
+
+ // store the HSL components
+ this.hsl =
+ {
+ 'h' : hsv.h,
+ 's' : hsv.s * hsv.v / (l < 50 ? l * 2 : 200 - l * 2),
+ 'l' : l
+ };
+
+ // correct a division-by-zero error
+ if (isNaN(hsl.s)) { hsl.s = 0; }
+
+ }
+
+
+});
+
+
+/**
+ * @class Roo.lib.HSLColor
+ * @extends Roo.lib.Color
+ *
+ * @constructor
+ * Creates a Color specified in the HSL Color space, with an optional alpha
+ * component. The parameters are:
+ *
+ * @param {Number} h - the hue component, wrapped to the range [0,360)
+ * @param {Number} s - the saturation component, clipped to the range [0,100]
+ * @param {Number} l - the lightness component, clipped to the range [0,100]
+ * @param {Number} a - the alpha component, clipped to the range [0,1] - this parameter is
+ * optional and defaults to 1
+ */
+
+Roo.lib.HSLColor = function(h, s, l, a){
+
+ // store the alpha component after clipping it if necessary
+ this.alpha = (a === undefined ? 1 : Math.max(0, Math.min(1, a)));
+
+ // store the HSL components after clipping or wrapping them if necessary
+ this.hsl =
+ {
+ 'h' : (h % 360 + 360) % 360,
+ 's' : Math.max(0, Math.min(100, s)),
+ 'l' : Math.max(0, Math.min(100, l))
+ };
+
+ // initialise the RGB and HSV components to null
+}
+
+Roo.extend(Roo.lib.HSLColor, Roo.lib.Color, {
+
+ /* Calculates and stores the RGB components of this HSLColor so that they can
+ * be returned be the getRGB function.
+ */
+ calculateRGB: function (){
+
+ // check whether the saturation is zero
+ if (this.hsl.s == 0){
+
+ // store the RGB components representing the appropriate shade of grey
+ this.rgb =
+ {
+ 'r' : this.hsl.l * 2.55,
+ 'g' : this.hsl.l * 2.55,
+ 'b' : this.hsl.l * 2.55
+ };
+
+ }else{
+
+ // set some temporary values
+ var p = this.hsl.l < 50
+ ? this.hsl.l * (1 + hsl.s / 100)
+ : this.hsl.l + hsl.s - hsl.l * hsl.s / 100;
+ var q = 2 * hsl.l - p;
+
+ // initialise the RGB components
+ this.rgb =
+ {
+ 'r' : (h + 120) / 60 % 6,
+ 'g' : h / 60,
+ 'b' : (h + 240) / 60 % 6
+ };
+
+ // loop over the RGB components
+ for (var key in this.rgb){
+
+ // ensure that the property is not inherited from the root object
+ if (this.rgb.hasOwnProperty(key)){
+
+ // set the component to its value in the range [0,100]
+ if (this.rgb[key] < 1){
+ this.rgb[key] = q + (p - q) * this.rgb[key];
+ }else if (this.rgb[key] < 3){
+ this.rgb[key] = p;
+ }else if (this.rgb[key] < 4){
+ this.rgb[key] = q + (p - q) * (4 - this.rgb[key]);
+ }else{
+ this.rgb[key] = q;
+ }
+
+ // set the component to its value in the range [0,255]
+ this.rgb[key] *= 2.55;
+
+ }
+
+ }
+
+ }
+
+ },
+
+ /* Calculates and stores the HSV components of this HSLColor so that they can
+ * be returned be the getHSL function.
+ */
+ calculateHSV : function(){
+
+ // set a temporary value
+ var t = this.hsl.s * (this.hsl.l < 50 ? this.hsl.l : 100 - this.hsl.l) / 100;
+
+ // store the HSV components
+ this.hsv =
+ {
+ 'h' : this.hsl.h,
+ 's' : 200 * t / (this.hsl.l + t),
+ 'v' : t + this.hsl.l
+ };
+
+ // correct a division-by-zero error
+ if (isNaN(this.hsv.s)) { this.hsv.s = 0; }
+
+ }
+
+
+});
+/*
* Portions of this file are based on pieces of Yahoo User Interface Library
* Copyright (c) 2007, Yahoo! Inc. All rights reserved.
* YUI licensed under the BSD License:
}
};
})();
-/*
+/**
+ * Originally based of this code... - refactored for Roo...
+ * https://github.com/aaalsaleh/undo-manager
+
+ * undo-manager.js
+ * @author Abdulrahman Alsaleh
+ * @copyright 2015 Abdulrahman Alsaleh
+ * @license MIT License (c)
+ *
+ * Hackily modifyed by alan@roojs.com
+ *
+ *
+ *
+ *
+ * TOTALLY UNTESTED...
+ *
+ * Documentation to be done....
+ */
+
+
+/**
+* @class Roo.lib.UndoManager
+* An undo manager implementation in JavaScript. It follows the W3C UndoManager and DOM Transaction
+* Draft and the undocumented and disabled Mozilla Firefox's UndoManager implementation.
+
+ * Usage:
+ * <pre><code>
+
+
+editor.undoManager = new Roo.lib.UndoManager(1000, editor);
+
+</code></pre>
+
+* For more information see this blog post with examples:
+* <a href="http://www.cnitblog.com/seeyeah/archive/2011/12/30/38728.html/">DomHelper
+ - Create Elements using DOM, HTML fragments and Templates</a>.
+* @constructor
+* @param {Number} limit how far back to go ... use 1000?
+* @param {Object} scope usually use document..
+*/
+
+Roo.lib.UndoManager = function (limit, undoScopeHost)
+{
+ this.stack = [];
+ this.limit = limit;
+ this.scope = undoScopeHost;
+ this.fireEvent = typeof CustomEvent != 'undefined' && undoScopeHost && undoScopeHost.dispatchEvent;
+ if (this.fireEvent) {
+ this.bindEvents();
+ }
+ this.reset();
+
+};
+
+Roo.lib.UndoManager.prototype = {
+
+ limit : false,
+ stack : false,
+ scope : false,
+ fireEvent : false,
+ position : 0,
+ length : 0,
+
+
+ /**
+ * To push and execute a transaction, the method undoManager.transact
+ * must be called by passing a transaction object as the first argument, and a merge
+ * flag as the second argument. A transaction object has the following properties:
+ *
+ * Usage:
+<pre><code>
+undoManager.transact({
+ label: 'Typing',
+ execute: function() { ... },
+ undo: function() { ... },
+ // redo same as execute
+ redo: function() { this.execute(); }
+}, false);
+
+// merge transaction
+undoManager.transact({
+ label: 'Typing',
+ execute: function() { ... }, // this will be run...
+ undo: function() { ... }, // what to do when undo is run.
+ // redo same as execute
+ redo: function() { this.execute(); }
+}, true);
+</code></pre>
+ *
+ *
+ * @param {Object} transaction The transaction to add to the stack.
+ * @return {String} The HTML fragment
+ */
+
+
+ transact : function (transaction, merge)
+ {
+ if (arguments.length < 2) {
+ throw new TypeError('Not enough arguments to UndoManager.transact.');
+ }
+
+ transaction.execute();
+
+ this.stack.splice(0, this.position);
+ if (merge && this.length) {
+ this.stack[0].push(transaction);
+ } else {
+ this.stack.unshift([transaction]);
+ }
+
+ this.position = 0;
+
+ if (this.limit && this.stack.length > this.limit) {
+ this.length = this.stack.length = this.limit;
+ } else {
+ this.length = this.stack.length;
+ }
+
+ if (this.fireEvent) {
+ this.scope.dispatchEvent(
+ new CustomEvent('DOMTransaction', {
+ detail: {
+ transactions: this.stack[0].slice()
+ },
+ bubbles: true,
+ cancelable: false
+ })
+ );
+ }
+
+ //Roo.log("transaction: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
+
+
+ },
+
+ undo : function ()
+ {
+ //Roo.log("undo: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
+
+ if (this.position < this.length) {
+ for (var i = this.stack[this.position].length - 1; i >= 0; i--) {
+ this.stack[this.position][i].undo();
+ }
+ this.position++;
+
+ if (this.fireEvent) {
+ this.scope.dispatchEvent(
+ new CustomEvent('undo', {
+ detail: {
+ transactions: this.stack[this.position - 1].slice()
+ },
+ bubbles: true,
+ cancelable: false
+ })
+ );
+ }
+ }
+ },
+
+ redo : function ()
+ {
+ if (this.position > 0) {
+ for (var i = 0, n = this.stack[this.position - 1].length; i < n; i++) {
+ this.stack[this.position - 1][i].redo();
+ }
+ this.position--;
+
+ if (this.fireEvent) {
+ this.scope.dispatchEvent(
+ new CustomEvent('redo', {
+ detail: {
+ transactions: this.stack[this.position].slice()
+ },
+ bubbles: true,
+ cancelable: false
+ })
+ );
+ }
+ }
+ },
+
+ item : function (index)
+ {
+ if (index >= 0 && index < this.length) {
+ return this.stack[index].slice();
+ }
+ return null;
+ },
+
+ clearUndo : function () {
+ this.stack.length = this.length = this.position;
+ },
+
+ clearRedo : function () {
+ this.stack.splice(0, this.position);
+ this.position = 0;
+ this.length = this.stack.length;
+ },
+ /**
+ * Reset the undo - probaly done on load to clear all history.
+ */
+ reset : function()
+ {
+ this.stack = [];
+ this.position = 0;
+ this.length = 0;
+ this.current_html = this.scope.innerHTML;
+ if (this.timer !== false) {
+ clearTimeout(this.timer);
+ }
+ this.timer = false;
+ this.merge = false;
+ this.addEvent();
+
+ },
+ current_html : '',
+ timer : false,
+ merge : false,
+
+
+ // this will handle the undo/redo on the element.?
+ bindEvents : function()
+ {
+ var el = this.scope;
+ el.undoManager = this;
+
+
+ this.scope.addEventListener('keydown', function(e) {
+ if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
+ if (e.shiftKey) {
+ el.undoManager.redo(); // Ctrl/Command + Shift + Z
+ } else {
+ el.undoManager.undo(); // Ctrl/Command + Z
+ }
+
+ e.preventDefault();
+ }
+ });
+ /// ignore keyup..
+ this.scope.addEventListener('keyup', function(e) {
+ if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
+ e.preventDefault();
+ }
+ });
+
+
+
+ var t = this;
+
+ el.addEventListener('input', function(e) {
+ if(el.innerHTML == t.current_html) {
+ return;
+ }
+ // only record events every second.
+ if (t.timer !== false) {
+ clearTimeout(t.timer);
+ t.timer = false;
+ }
+ t.timer = setTimeout(function() { t.merge = false; }, 1000);
+
+ t.addEvent(t.merge);
+ t.merge = true; // ignore changes happening every second..
+ });
+ },
+ /**
+ * Manually add an event.
+ * Normall called without arguements - and it will just get added to the stack.
+ *
+ */
+
+ addEvent : function(merge)
+ {
+ //Roo.log("undomanager +" + (merge ? 'Y':'n'));
+ // not sure if this should clear the timer
+ merge = typeof(merge) == 'undefined' ? false : merge;
+
+ this.scope.undoManager.transact({
+ scope : this.scope,
+ oldHTML: this.current_html,
+ newHTML: this.scope.innerHTML,
+ // nothing to execute (content already changed when input is fired)
+ execute: function() { },
+ undo: function() {
+ this.scope.innerHTML = this.current_html = this.oldHTML;
+ },
+ redo: function() {
+ this.scope.innerHTML = this.current_html = this.newHTML;
+ }
+ }, false); //merge);
+
+ this.merge = merge;
+
+ this.current_html = this.scope.innerHTML;
+ }
+
+
+
+
+
+
+};
+/**
+ * @class Roo.lib.Range
+ * @constructor
+ * This is a toolkit, normally used to copy features into a Dom Range element
+ * Roo.lib.Range.wrap(x);
+ *
+ *
+ *
+ */
+Roo.lib.Range = function() { };
+
+/**
+ * Wrap a Dom Range object, to give it new features...
+ * @static
+ * @param {Range} the range to wrap
+ */
+Roo.lib.Range.wrap = function(r) {
+ return Roo.apply(r, Roo.lib.Range.prototype);
+};
+/**
+ * find a parent node eg. LI / OL
+ * @param {string|Array} node name or array of nodenames
+ * @return {DomElement|false}
+ */
+Roo.apply(Roo.lib.Range.prototype,
+{
+
+ closest : function(str)
+ {
+ if (typeof(str) != 'string') {
+ // assume it's a array.
+ for(var i = 0;i < str.length;i++) {
+ var r = this.closest(str[i]);
+ if (r !== false) {
+ return r;
+ }
+
+ }
+ return false;
+ }
+ str = str.toLowerCase();
+ var n = this.commonAncestorContainer; // might not be a node
+ while (n.nodeType != 1) {
+ n = n.parentNode;
+ }
+
+ if (n.nodeName.toLowerCase() == str ) {
+ return n;
+ }
+ if (n.nodeName.toLowerCase() == 'body') {
+ return false;
+ }
+
+ return n.closest(str) || false;
+
+ },
+ cloneRange : function()
+ {
+ return Roo.lib.Range.wrap(Range.prototype.cloneRange.call(this));
+ }
+});/**
+ * @class Roo.lib.Selection
+ * @constructor
+ * This is a toolkit, normally used to copy features into a Dom Selection element
+ * Roo.lib.Selection.wrap(x);
+ *
+ *
+ *
+ */
+Roo.lib.Selection = function() { };
+
+/**
+ * Wrap a Dom Range object, to give it new features...
+ * @static
+ * @param {Range} the range to wrap
+ */
+Roo.lib.Selection.wrap = function(r, doc) {
+ Roo.apply(r, Roo.lib.Selection.prototype);
+ r.ownerDocument = doc; // usefull so we dont have to keep referening to it.
+ return r;
+};
+/**
+ * find a parent node eg. LI / OL
+ * @param {string|Array} node name or array of nodenames
+ * @return {DomElement|false}
+ */
+Roo.apply(Roo.lib.Selection.prototype,
+{
+ /**
+ * the owner document
+ */
+ ownerDocument : false,
+
+ getRangeAt : function(n)
+ {
+ return Roo.lib.Range.wrap(Selection.prototype.getRangeAt.call(this,n));
+ },
+
+ /**
+ * insert node at selection
+ * @param {DomElement|string} node
+ * @param {string} cursor (after|in|none) where to place the cursor after inserting.
+ */
+ insertNode: function(node, cursor)
+ {
+ if (typeof(node) == 'string') {
+ node = this.ownerDocument.createElement(node);
+ if (cursor == 'in') {
+ node.innerHTML = ' ';
+ }
+ }
+
+ var range = this.getRangeAt(0);
+
+ if (this.type != 'Caret') {
+ range.deleteContents();
+ }
+ var sn = node.childNodes[0]; // select the contents.
+
+
+
+ range.insertNode(node);
+ if (cursor == 'after') {
+ node.insertAdjacentHTML('afterend', ' ');
+ sn = node.nextSibling;
+ }
+
+ if (cursor == 'none') {
+ return;
+ }
+
+ this.cursorText(sn);
+ },
+
+ cursorText : function(n)
+ {
+
+ //var range = this.getRangeAt(0);
+ range = Roo.lib.Range.wrap(new Range());
+ //range.selectNode(n);
+
+ var ix = Array.from(n.parentNode.childNodes).indexOf(n);
+ range.setStart(n.parentNode,ix);
+ range.setEnd(n.parentNode,ix+1);
+ //range.collapse(false);
+
+ this.removeAllRanges();
+ this.addRange(range);
+
+ Roo.log([n, range, this,this.baseOffset,this.extentOffset, this.type]);
+ },
+ cursorAfter : function(n)
+ {
+ if (!n.nextSibling || n.nextSibling.nodeValue != ' ') {
+ n.insertAdjacentHTML('afterend', ' ');
+ }
+ this.cursorText (n.nextSibling);
+ }
+
+
+});/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* @class Roo.DomHelper
* Utility class for working with DOM and/or Templates. It transparently supports using HTML fragments or DOM.
* For more information see <a href="http://web.archive.org/web/20071221063734/http://www.jackslocum.com/blog/2006/10/06/domhelper-create-elements-using-dom-html-fragments-or-templates/">this blog post with examples</a>.
- * @singleton
+ * @static
*/
Roo.DomHelper = function(){
var tempTableEl = null;
if (typeof(o) == 'string') {
return parentNode.appendChild(document.createTextNode(o));
}
- o.tag = o.tag || div;
+ o.tag = o.tag || 'div';
if (o.ns && Roo.isIE) {
ns = false;
o.tag = o.ns + ':' + o.tag;
var ieTable = function(depth, s, h, e){
tempTableEl.innerHTML = [s, h, e].join('');
var i = -1, el = tempTableEl;
- while(++i < depth){
+ while(++i < depth && el.firstChild){
el = el.firstChild;
}
return el;
el.insertBefore(node, before);
return node;
};
+
+ // this is a bit like the react update code...
+ //
+
+ var updateNode = function(from, to)
+ {
+ // should we handle non-standard elements?
+ Roo.log(["UpdateNode" , from, to]);
+ if (from.nodeType != to.nodeType) {
+ Roo.log(["ReplaceChild - mismatch notType" , to, from ]);
+ from.parentNode.replaceChild(to, from);
+ }
+
+ if (from.nodeType == 3) {
+ // assume it's text?!
+ if (from.data == to.data) {
+ return;
+ }
+ from.data = to.data;
+ return;
+ }
+ if (!from.parentNode) {
+ // not sure why this is happening?
+ return;
+ }
+ // assume 'to' doesnt have '1/3 nodetypes!
+ // not sure why, by from, parent node might not exist?
+ if (from.nodeType !=1 || from.tagName != to.tagName) {
+ Roo.log(["ReplaceChild" , from, to ]);
+
+ from.parentNode.replaceChild(to, from);
+ return;
+ }
+ // compare attributes
+ var ar = Array.from(from.attributes);
+ for(var i = 0; i< ar.length;i++) {
+ if (to.hasAttribute(ar[i].name)) {
+ continue;
+ }
+ if (ar[i].name == 'id') { // always keep ids?
+ continue;
+ }
+ //if (ar[i].name == 'style') {
+ // throw "style removed?";
+ //}
+ Roo.log("removeAttribute" + ar[i].name);
+ from.removeAttribute(ar[i].name);
+ }
+ ar = to.attributes;
+ for(var i = 0; i< ar.length;i++) {
+ if (from.getAttribute(ar[i].name) == to.getAttribute(ar[i].name)) {
+ Roo.log("skipAttribute " + ar[i].name + '=' + to.getAttribute(ar[i].name));
+ continue;
+ }
+ Roo.log("updateAttribute " + ar[i].name + '=>' + to.getAttribute(ar[i].name));
+ from.setAttribute(ar[i].name, to.getAttribute(ar[i].name));
+ }
+ // children
+ var far = Array.from(from.childNodes);
+ var tar = Array.from(to.childNodes);
+ // if the lengths are different.. then it's probably a editable content change, rather than
+ // a change of the block definition..
+
+ // this did notwork , as our rebuilt nodes did not include ID's so did not match at all.
+ /*if (from.innerHTML == to.innerHTML) {
+ return;
+ }
+ if (far.length != tar.length) {
+ from.innerHTML = to.innerHTML;
+ return;
+ }
+ */
+
+ for(var i = 0; i < Math.max(tar.length, far.length); i++) {
+ if (i >= far.length) {
+ from.appendChild(tar[i]);
+ Roo.log(["add", tar[i]]);
+
+ } else if ( i >= tar.length) {
+ from.removeChild(far[i]);
+ Roo.log(["remove", far[i]]);
+ } else {
+
+ updateNode(far[i], tar[i]);
+ }
+ }
+
+
+
+
+ };
+
+
return {
- /** True to force the use of DOM instead of html fragments @type Boolean */
- useDom : false,
-
- /**
- * Returns the markup for the passed Element(s) config
- * @param {Object} o The Dom object spec (and children)
- * @return {String}
- */
- markup : function(o){
- return createHtml(o);
- },
-
- /**
- * Applies a style specification to an element
- * @param {String/HTMLElement} el The element to apply styles to
- * @param {String/Object/Function} styles A style specification string eg "width:100px", or object in the form {width:"100px"}, or
- * a function which returns such a specification.
- */
- applyStyles : function(el, styles){
- if(styles){
- el = Roo.fly(el);
- if(typeof styles == "string"){
- var re = /\s?([a-z\-]*)\:\s?([^;]*);?/gi;
- var matches;
- while ((matches = re.exec(styles)) != null){
- el.setStyle(matches[1], matches[2]);
- }
- }else if (typeof styles == "object"){
- for (var style in styles){
- el.setStyle(style, styles[style]);
+ /** True to force the use of DOM instead of html fragments @type Boolean */
+ useDom : false,
+
+ /**
+ * Returns the markup for the passed Element(s) config
+ * @param {Object} o The Dom object spec (and children)
+ * @return {String}
+ */
+ markup : function(o){
+ return createHtml(o);
+ },
+
+ /**
+ * Applies a style specification to an element
+ * @param {String/HTMLElement} el The element to apply styles to
+ * @param {String/Object/Function} styles A style specification string eg "width:100px", or object in the form {width:"100px"}, or
+ * a function which returns such a specification.
+ */
+ applyStyles : function(el, styles){
+ if(styles){
+ el = Roo.fly(el);
+ if(typeof styles == "string"){
+ var re = /\s?([a-z\-]*)\:\s?([^;]*);?/gi;
+ var matches;
+ while ((matches = re.exec(styles)) != null){
+ el.setStyle(matches[1], matches[2]);
+ }
+ }else if (typeof styles == "object"){
+ for (var style in styles){
+ el.setStyle(style, styles[style]);
+ }
+ }else if (typeof styles == "function"){
+ Roo.DomHelper.applyStyles(el, styles.call());
}
- }else if (typeof styles == "function"){
- Roo.DomHelper.applyStyles(el, styles.call());
- }
- }
- },
-
- /**
- * Inserts an HTML fragment into the Dom
- * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
- * @param {HTMLElement} el The context element
- * @param {String} html The HTML fragmenet
- * @return {HTMLElement} The new node
- */
- insertHtml : function(where, el, html){
- where = where.toLowerCase();
- if(el.insertAdjacentHTML){
- if(tableRe.test(el.tagName)){
- var rs;
- if(rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html)){
- return rs;
+ }
+ },
+
+ /**
+ * Inserts an HTML fragment into the Dom
+ * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
+ * @param {HTMLElement} el The context element
+ * @param {String} html The HTML fragmenet
+ * @return {HTMLElement} The new node
+ */
+ insertHtml : function(where, el, html){
+ where = where.toLowerCase();
+ if(el.insertAdjacentHTML){
+ if(tableRe.test(el.tagName)){
+ var rs;
+ if(rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html)){
+ return rs;
+ }
+ }
+ switch(where){
+ case "beforebegin":
+ el.insertAdjacentHTML('BeforeBegin', html);
+ return el.previousSibling;
+ case "afterbegin":
+ el.insertAdjacentHTML('AfterBegin', html);
+ return el.firstChild;
+ case "beforeend":
+ el.insertAdjacentHTML('BeforeEnd', html);
+ return el.lastChild;
+ case "afterend":
+ el.insertAdjacentHTML('AfterEnd', html);
+ return el.nextSibling;
}
+ throw 'Illegal insertion point -> "' + where + '"';
}
+ var range = el.ownerDocument.createRange();
+ var frag;
switch(where){
- case "beforebegin":
- el.insertAdjacentHTML('BeforeBegin', html);
+ case "beforebegin":
+ range.setStartBefore(el);
+ frag = range.createContextualFragment(html);
+ el.parentNode.insertBefore(frag, el);
return el.previousSibling;
- case "afterbegin":
- el.insertAdjacentHTML('AfterBegin', html);
- return el.firstChild;
+ case "afterbegin":
+ if(el.firstChild){
+ range.setStartBefore(el.firstChild);
+ frag = range.createContextualFragment(html);
+ el.insertBefore(frag, el.firstChild);
+ return el.firstChild;
+ }else{
+ el.innerHTML = html;
+ return el.firstChild;
+ }
case "beforeend":
- el.insertAdjacentHTML('BeforeEnd', html);
- return el.lastChild;
+ if(el.lastChild){
+ range.setStartAfter(el.lastChild);
+ frag = range.createContextualFragment(html);
+ el.appendChild(frag);
+ return el.lastChild;
+ }else{
+ el.innerHTML = html;
+ return el.lastChild;
+ }
case "afterend":
- el.insertAdjacentHTML('AfterEnd', html);
- return el.nextSibling;
- }
- throw 'Illegal insertion point -> "' + where + '"';
- }
- var range = el.ownerDocument.createRange();
- var frag;
- switch(where){
- case "beforebegin":
- range.setStartBefore(el);
- frag = range.createContextualFragment(html);
- el.parentNode.insertBefore(frag, el);
- return el.previousSibling;
- case "afterbegin":
- if(el.firstChild){
- range.setStartBefore(el.firstChild);
+ range.setStartAfter(el);
frag = range.createContextualFragment(html);
- el.insertBefore(frag, el.firstChild);
- return el.firstChild;
- }else{
- el.innerHTML = html;
- return el.firstChild;
+ el.parentNode.insertBefore(frag, el.nextSibling);
+ return el.nextSibling;
}
- case "beforeend":
- if(el.lastChild){
- range.setStartAfter(el.lastChild);
- frag = range.createContextualFragment(html);
- el.appendChild(frag);
- return el.lastChild;
- }else{
- el.innerHTML = html;
- return el.lastChild;
+ throw 'Illegal insertion point -> "' + where + '"';
+ },
+
+ /**
+ * Creates new Dom element(s) and inserts them before el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
+ * @param {Boolean} returnElement (optional) true to return a Roo.Element
+ * @return {HTMLElement/Roo.Element} The new node
+ */
+ insertBefore : function(el, o, returnElement){
+ return this.doInsert(el, o, returnElement, "beforeBegin");
+ },
+
+ /**
+ * Creates new Dom element(s) and inserts them after el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} o The Dom object spec (and children)
+ * @param {Boolean} returnElement (optional) true to return a Roo.Element
+ * @return {HTMLElement/Roo.Element} The new node
+ */
+ insertAfter : function(el, o, returnElement){
+ return this.doInsert(el, o, returnElement, "afterEnd", "nextSibling");
+ },
+
+ /**
+ * Creates new Dom element(s) and inserts them as the first child of el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
+ * @param {Boolean} returnElement (optional) true to return a Roo.Element
+ * @return {HTMLElement/Roo.Element} The new node
+ */
+ insertFirst : function(el, o, returnElement){
+ return this.doInsert(el, o, returnElement, "afterBegin");
+ },
+
+ // private
+ doInsert : function(el, o, returnElement, pos, sibling){
+ el = Roo.getDom(el);
+ var newNode;
+ if(this.useDom || o.ns){
+ newNode = createDom(o, null);
+ el.parentNode.insertBefore(newNode, sibling ? el[sibling] : el);
+ }else{
+ var html = createHtml(o);
+ newNode = this.insertHtml(pos, el, html);
+ }
+ return returnElement ? Roo.get(newNode, true) : newNode;
+ },
+
+ /**
+ * Creates new Dom element(s) and appends them to el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
+ * @param {Boolean} returnElement (optional) true to return a Roo.Element
+ * @return {HTMLElement/Roo.Element} The new node
+ */
+ append : function(el, o, returnElement){
+ el = Roo.getDom(el);
+ var newNode;
+ if(this.useDom || o.ns){
+ newNode = createDom(o, null);
+ el.appendChild(newNode);
+ }else{
+ var html = createHtml(o);
+ newNode = this.insertHtml("beforeEnd", el, html);
+ }
+ return returnElement ? Roo.get(newNode, true) : newNode;
+ },
+
+ /**
+ * Creates new Dom element(s) and overwrites the contents of el with them
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
+ * @param {Boolean} returnElement (optional) true to return a Roo.Element
+ * @return {HTMLElement/Roo.Element} The new node
+ */
+ overwrite : function(el, o, returnElement)
+ {
+ el = Roo.getDom(el);
+ if (o.ns) {
+
+ while (el.childNodes.length) {
+ el.removeChild(el.firstChild);
}
- case "afterend":
- range.setStartAfter(el);
- frag = range.createContextualFragment(html);
- el.parentNode.insertBefore(frag, el.nextSibling);
- return el.nextSibling;
+ createDom(o, el);
+ } else {
+ el.innerHTML = createHtml(o);
}
- throw 'Illegal insertion point -> "' + where + '"';
- },
-
- /**
- * Creates new Dom element(s) and inserts them before el
- * @param {String/HTMLElement/Element} el The context element
- * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
- * @param {Boolean} returnElement (optional) true to return a Roo.Element
- * @return {HTMLElement/Roo.Element} The new node
- */
- insertBefore : function(el, o, returnElement){
- return this.doInsert(el, o, returnElement, "beforeBegin");
- },
-
- /**
- * Creates new Dom element(s) and inserts them after el
- * @param {String/HTMLElement/Element} el The context element
- * @param {Object} o The Dom object spec (and children)
- * @param {Boolean} returnElement (optional) true to return a Roo.Element
- * @return {HTMLElement/Roo.Element} The new node
- */
- insertAfter : function(el, o, returnElement){
- return this.doInsert(el, o, returnElement, "afterEnd", "nextSibling");
- },
-
- /**
- * Creates new Dom element(s) and inserts them as the first child of el
- * @param {String/HTMLElement/Element} el The context element
- * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
- * @param {Boolean} returnElement (optional) true to return a Roo.Element
- * @return {HTMLElement/Roo.Element} The new node
- */
- insertFirst : function(el, o, returnElement){
- return this.doInsert(el, o, returnElement, "afterBegin");
- },
-
- // private
- doInsert : function(el, o, returnElement, pos, sibling){
- el = Roo.getDom(el);
- var newNode;
- if(this.useDom || o.ns){
- newNode = createDom(o, null);
- el.parentNode.insertBefore(newNode, sibling ? el[sibling] : el);
- }else{
- var html = createHtml(o);
- newNode = this.insertHtml(pos, el, html);
- }
- return returnElement ? Roo.get(newNode, true) : newNode;
- },
-
- /**
- * Creates new Dom element(s) and appends them to el
- * @param {String/HTMLElement/Element} el The context element
- * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
- * @param {Boolean} returnElement (optional) true to return a Roo.Element
- * @return {HTMLElement/Roo.Element} The new node
- */
- append : function(el, o, returnElement){
- el = Roo.getDom(el);
- var newNode;
- if(this.useDom || o.ns){
- newNode = createDom(o, null);
- el.appendChild(newNode);
- }else{
+
+ return returnElement ? Roo.get(el.firstChild, true) : el.firstChild;
+ },
+
+ /**
+ * Creates a new Roo.DomHelper.Template from the Dom object spec
+ * @param {Object} o The Dom object spec (and children)
+ * @return {Roo.DomHelper.Template} The new template
+ */
+ createTemplate : function(o){
var html = createHtml(o);
- newNode = this.insertHtml("beforeEnd", el, html);
- }
- return returnElement ? Roo.get(newNode, true) : newNode;
- },
-
- /**
- * Creates new Dom element(s) and overwrites the contents of el with them
- * @param {String/HTMLElement/Element} el The context element
- * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
- * @param {Boolean} returnElement (optional) true to return a Roo.Element
- * @return {HTMLElement/Roo.Element} The new node
- */
- overwrite : function(el, o, returnElement){
- el = Roo.getDom(el);
- if (o.ns) {
-
- while (el.childNodes.length) {
- el.removeChild(el.firstChild);
- }
- createDom(o, el);
- } else {
- el.innerHTML = createHtml(o);
+ return new Roo.Template(html);
+ },
+ /**
+ * Updates the first element with the spec from the o (replacing if necessary)
+ * This iterates through the children, and updates attributes / children etc..
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
+ */
+
+ update : function(el, o)
+ {
+ updateNode(Roo.getDom(el), createDom(o));
+
}
- return returnElement ? Roo.get(el.firstChild, true) : el.firstChild;
- },
-
- /**
- * Creates a new Roo.DomHelper.Template from the Dom object spec
- * @param {Object} o The Dom object spec (and children)
- * @return {Roo.DomHelper.Template} The new template
- */
- createTemplate : function(o){
- var html = createHtml(o);
- return new Roo.Template(html);
- }
+
};
}();
/*
};
Roo.Template.prototype = {
+ /**
+ * @cfg {Function} onLoad Called after the template has been loaded and complied (usually from a remove source)
+ */
+ onLoad : false,
+
+
/**
* @cfg {String} url The Url to load the template from. beware if you are loading from a url, the data may not be ready if you use it instantly..
* it should be fixed so that template is observable...
* @cfg {String} html The HTML fragment or an array of fragments to join("") or multiple arguments to join("")
*/
html : '',
+
+
+ compiled : false,
+ loaded : false,
/**
* Returns an HTML fragment of this template with the specified values applied.
* @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
* @return {String} The HTML fragment
*/
+
+
+
applyTemplate : function(values){
//Roo.log(["applyTemplate", values]);
try {
method : 'GET',
success : function (response) {
_t.loading = false;
- _t.html = response.responseText;
_t.url = false;
- _t.compile();
+
+ _t.set(response.responseText,true);
+ _t.loaded = true;
+ if (_t.onLoad) {
+ _t.onLoad();
+ }
},
failure : function(response) {
Roo.log("Template failed to load from " + _t.url);
*/
set : function(html, compile){
this.html = html;
- this.compiled = null;
+ this.compiled = false;
if(compile){
this.compile();
}
<li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
<li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
</ul>
- * @singleton
+ * @static
*/
Roo.DomQuery = function(){
var cache = {}, simpleCache = {}, valueCache = {};
}
var r = [], ri = -1, cn;
for(var i = 0, ci; ci = c[i]; i++){
- if((' '+ci.className+' ').indexOf(v) != -1){
+
+
+ if((' '+
+ ( (ci instanceof SVGElement) ? ci.className.baseVal : ci.className)
+ +' ').indexOf(v) != -1){
r[++ri] = ci;
}
}
return n.htmlFor;
}
if(attr == "class" || attr == "className"){
- return n.className;
+ return (n instanceof SVGElement) ? n.className.baseVal : n.className;
}
return n.getAttribute(attr) || n[attr];
a = Roo.DomQuery.getStyle(ci, attr);
}
else if(attr == "class" || attr == "className"){
- a = ci.className;
+ a = (ci instanceof SVGElement) ? ci.className.baseVal : ci.className;
}else if(attr == "for"){
a = ci.htmlFor;
}else if(attr == "href"){
relayEvents : function(o, events){
var createHandler = function(ename){
return function(){
+
return this.fireEvent.apply(this, Roo.combine(ename, Array.prototype.slice.call(arguments, 0)));
};
};
for(var i = 0, len = events.length; i < len; i++){
var ename = events[i];
- if(!this.events[ename]){ this.events[ename] = true; };
+ if(!this.events[ename]){
+ this.events[ename] = true;
+ };
o.on(ename, createHandler(ename), this);
}
},
* Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
* several useful events directly.
* See {@link Roo.EventObject} for more details on normalized event objects.
- * @singleton
+ * @static
*/
Roo.EventManager = function(){
var docReadyEvent, docReadyProcId, docReadyState = false;
}
}
+
- var listen = function(element, ename, opt, fn, scope){
+ var listen = function(element, ename, opt, fn, scope)
+ {
var o = (!opt || typeof opt == "boolean") ? {} : opt;
fn = fn || o.fn; scope = scope || o.scope;
var el = Roo.getDom(element);
- E.on(el, ename, h);
+ E.on(el, ename, h); // this adds the actuall listener to the object..
+
+
if(ename == "mousewheel" && el.addEventListener){ // workaround for jQuery
el.addEventListener("DOMMouseScroll", h, false);
E.on(window, 'unload', function(){
* @param {Object} scope An object that becomes the scope of the handler
* @param {boolean} options
*/
- onWindowResize : function(fn, scope, options){
+ onWindowResize : function(fn, scope, options)
+ {
if(!resizeEvent){
resizeEvent = new Roo.util.Event();
resizeTask = new Roo.util.DelayedTask(function(){
resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
});
- E.on(window, "resize", function(){
- if(Roo.isIE){
+ E.on(window, "resize", function()
+ {
+ if (Roo.isIE) {
resizeTask.delay(50);
- }else{
+ } else {
resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
}
});
Roo.EventManager.on("myDiv", 'click', handleClick);
Roo.EventManager.addListener("myDiv", 'click', handleClick);
</code></pre>
- * @singleton
+ * @static
*/
Roo.EventObject = function(){
* @param {String/HTMLElement} element
* @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
*/
- Roo.Element = function(element, forceNew){
+ Roo.Element = function(element, forceNew)
+ {
var dom = typeof element == "string" ?
document.getElementById(element) : element;
+
+ this.listeners = {};
+
if(!dom){ // invalid id/element
return null;
}
* @type String
*/
this.id = id || Roo.id(dom);
+
+ return this; // assumed for cctor?
};
var El = Roo.Element;
}
}else{
if(className && !this.hasClass(className)){
- this.dom.className = this.dom.className + " " + className;
+ if (this.dom instanceof SVGElement) {
+ this.dom.className.baseVal =this.dom.className.baseVal + " " + className;
+ } else {
+ this.dom.className = this.dom.className + " " + className;
+ }
}
}
return this;
* @return {Roo.Element} this
*/
removeClass : function(className){
- if(!className || !this.dom.className){
+
+ var cn = this.dom instanceof SVGElement ? this.dom.className.baseVal : this.dom.className;
+ if(!className || !cn){
return this;
}
if(className instanceof Array){
re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)', "g");
this.classReCache[className] = re;
}
- this.dom.className =
- this.dom.className.replace(re, " ");
+ if (this.dom instanceof SVGElement) {
+ this.dom.className.baseVal = cn.replace(re, " ");
+ } else {
+ this.dom.className = cn.replace(re, " ");
+ }
}
}
return this;
* @return {Boolean} True if the class exists, else false
*/
hasClass : function(className){
+ if (this.dom instanceof SVGElement) {
+ return className && (' '+this.dom.className.baseVal +' ').indexOf(' '+className+' ') != -1;
+ }
return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
},
* @param {Object} scope (optional) The scope (this object) of the fn
* @param {Object} options (optional)An object with standard {@link Roo.EventManager#addListener} options
*/
- addListener : function(eventName, fn, scope, options){
- if (this.dom) {
- Roo.EventManager.on(this.dom, eventName, fn, scope || this, options);
+ addListener : function(eventName, fn, scope, options)
+ {
+ if (eventName == 'dblclick') { // doublclick (touchstart) - faked on touch.
+ this.addListener('touchstart', this.onTapHandler, this);
+ }
+
+ // we need to handle a special case where dom element is a svg element.
+ // in this case we do not actua
+ if (!this.dom) {
+ return;
+ }
+
+ if (this.dom instanceof SVGElement && !(this.dom instanceof SVGSVGElement)) {
+ if (typeof(this.listeners[eventName]) == 'undefined') {
+ this.listeners[eventName] = new Roo.util.Event(this, eventName);
+ }
+ this.listeners[eventName].addListener(fn, scope, options);
+ return;
}
+
+
+ Roo.EventManager.on(this.dom, eventName, fn, scope || this, options);
+
+
},
-
+ tapedTwice : false,
+ onTapHandler : function(event)
+ {
+ if(!this.tapedTwice) {
+ this.tapedTwice = true;
+ var s = this;
+ setTimeout( function() {
+ s.tapedTwice = false;
+ }, 300 );
+ return;
+ }
+ event.preventDefault();
+ var revent = new MouseEvent('dblclick', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ });
+
+ this.dom.dispatchEvent(revent);
+ //action on double tap goes below
+
+ },
+
/**
* Removes an event handler from this element
* @param {String} eventName the type of event to remove
* @param {Function} fn the method the event invokes
+ * @param {Function} scope (needed for svg fake listeners)
* @return {Roo.Element} this
*/
- removeListener : function(eventName, fn){
+ removeListener : function(eventName, fn, scope){
Roo.EventManager.removeListener(this.dom, eventName, fn);
+ if (typeof(this.listeners) == 'undefined' || typeof(this.listeners[eventName]) == 'undefined') {
+ return this;
+ }
+ this.listeners[eventName].removeListener(fn, scope);
return this;
},
*/
removeAllListeners : function(){
E.purgeElement(this.dom);
+ this.listeners = {};
return this;
},
});
},
+
/**
* Set the opacity of the element
* @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
* Puts a mask over this element to disable user interaction. Requires core.css.
* This method can only be applied to elements which accept child nodes.
* @param {String} msg (optional) A message to display in the mask
- * @param {String} msgCls (optional) A css class to apply to the msg element
+ * @param {String} msgCls (optional) A css class to apply to the msg element - use no-spinner to hide the spinner on bootstrap
* @return {Element} The mask element
*/
mask : function(msg, msgCls)
/**
* @private
*/
- fitToParentDelegate : Roo.emptyFn, // keep a reference to the fitToParent delegate
+ fitToParentDelegate : Roo.emptyFn, // keep a reference to the fitToParent delegate
/**
* Sizes this element to its parent element's dimensions performing
Roo.EventManager.removeResizeListener(this.fitToParentDelegate); // always remove previous fitToParent delegate from onWindowResize
this.fitToParentDelegate = Roo.emptyFn; // remove reference to previous delegate
if (monitorResize === true && !this.dom.parentNode) { // check if this Element still exists
- return;
+ return this;
}
var p = Roo.get(targetParent || this.dom.parentNode);
this.setSize(p.getComputedWidth() - p.getFrameWidth('lr'), p.getComputedHeight() - p.getFrameWidth('tb'));
* Fork - LGPL
* <script type="text/javascript">
*/
-
+/**
+ * @class Roo.util.TaskRunner
+ * Manage background tasks - not sure why this is better that setInterval?
+ * @static
+ *
+ */
Roo.util.TaskRunner = function(interval){
interval = interval || 10;
/**
* Queues a new task.
* @param {Object} task
+ *
+ * Task property : interval = how frequent to run.
+ * Task object should implement
+ * function run()
+ * Task object may implement
+ * function onStop()
*/
this.start = function(task){
tasks.push(task);
startThread();
return task;
};
-
+ /**
+ * Stop new task.
+ * @param {Object} task
+ */
this.stop = function(task){
removeTask(task);
return task;
};
-
+ /**
+ * Stop all Tasks
+ */
this.stopAll = function(){
stopThread();
for(var i = 0, len = tasks.length; i < len; i++){
* Modified version of Douglas Crockford"s json.js that doesn"t
* mess with the Object prototype
* http://www.json.org/js.html
- * @singleton
+ * @static
*/
Roo.util.JSON = new (function(){
var useHasOwn = {}.hasOwnProperty ? true : false;
/**
* @class Roo.util.Format
* Reusable data formatting functions
- * @singleton
+ * @static
*/
Roo.util.Format = function(){
var trimRe = /^\s+|\s+$/g;
*/
stripTags : function(v){
return !v ? v : String(v).replace(this.stripTagsRE, "");
+ },
+
+ /**
+ * Size in Mb,Gb etc.
+ * @param {Number} value The number to be formated
+ * @param {number} decimals how many decimal places
+ * @return {String} the formated string
+ */
+ size : function(value, decimals)
+ {
+ var sizes = ['b', 'k', 'M', 'G', 'T'];
+ if (value == 0) {
+ return 0;
+ }
+ var i = parseInt(Math.floor(Math.log(value) / Math.log(1024)));
+ return Roo.util.Format.number(value/ Math.pow(1024, i) ,decimals) + sizes[i];
}
+
+
+
};
}();
Roo.util.Format.defaults = {
/**
* @class Roo.util.CSS
* Utility class for manipulating CSS rules
- * @singleton
+ * @static
+
*/
Roo.util.CSS = function(){
var rules = null;
this.el.removeClass(this.pressClass);
this.fireEvent("mouseup", this);
}
-});/*
+});/**
+ * @class Roo.util.Clipboard
+ * @static
+ *
+ * Clipboard UTILS
+ *
+ **/
+Roo.util.Clipboard = {
+ /**
+ * Writes a string to the clipboard - using the Clipboard API if https, otherwise using text area.
+ * @param {String} text to copy to clipboard
+ */
+ write : function(text) {
+ // navigator clipboard api needs a secure context (https)
+ if (navigator.clipboard && window.isSecureContext) {
+ // navigator clipboard api method'
+ navigator.clipboard.writeText(text);
+ return ;
+ }
+ // text area method
+ var ta = document.createElement("textarea");
+ ta.value = text;
+ // make the textarea out of viewport
+ ta.style.position = "fixed";
+ ta.style.left = "-999999px";
+ ta.style.top = "-999999px";
+ document.body.appendChild(ta);
+ ta.focus();
+ ta.select();
+ document.execCommand('copy');
+ (function() {
+ ta.remove();
+ }).defer(100);
+
+ }
+
+}
+ /*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* @class Roo.util.TextMetrics
* Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
* wide, in pixels, a given block of text will be.
- * @singleton
+ * @static
*/
Roo.util.TextMetrics = function(){
var shared;
};
}();
-
+/**
+ * @class Roo.util.TextMetrics.Instance
+ * Instance of TextMetrics Calcuation
+ * @constructor
+ * Create a new TextMetrics Instance
+ * @param {Object} bindto
+ * @param {Boolean} fixedWidth
+ */
-Roo.util.TextMetrics.Instance = function(bindTo, fixedWidth){
+Roo.util.TextMetrics.Instance = function(bindTo, fixedWidth)
+{
var ml = new Roo.Element(document.createElement('div'));
document.body.appendChild(ml.dom);
ml.position('absolute');
var instance = {
/**
* Returns the size of the specified text based on the internal element's style and width properties
- * @memberOf Roo.util.TextMetrics.Instance#
* @param {String} text The text to measure
* @return {Object} An object containing the text's size {width: (width), height: (height)}
*/
/**
* Binds this TextMetrics instance to an element from which to copy existing CSS styles
* that can affect the size of the rendered text
- * @memberOf Roo.util.TextMetrics.Instance#
* @param {String/HTMLElement} el The element, dom node or id
*/
bind : function(el){
/**
* Sets a fixed width on the internal measurement element. If the text will be multiline, you have
* to set a fixed width in order to accurately measure the text height.
- * @memberOf Roo.util.TextMetrics.Instance#
* @param {Number} width The width to set on the element
*/
setFixedWidth : function(width){
/**
* Returns the measured width of the specified text
- * @memberOf Roo.util.TextMetrics.Instance#
* @param {String} text The text to measure
* @return {Number} width The width in pixels
*/
/**
* Returns the measured height of the specified text. For multiline text, be sure to call
* {@link #setFixedWidth} if necessary.
- * @memberOf Roo.util.TextMetrics.Instance#
* @param {String} text The text to measure
* @return {Number} height The height in pixels
*/
var dialog = new Roo.BasicDialog(...);
dialog.restoreState();
</code></pre>
- * @singleton
+ * @static
*/
Roo.state.Manager = function(){
var provider = new Roo.state.Provider();
/**
* @class Roo.ComponentMgr
* Provides a common registry of all components on a page so that they can be easily accessed by component id (see {@link Roo.getCmp}).
- * @singleton
+ * @static
*/
Roo.ComponentMgr = function(){
var all = new Roo.util.MixedCollection();
*/
actionMode : "el",
+ /**
+ * @cfg {String} style
+ * css styles to add to component
+ * eg. text-align:right;
+ */
+ style : false,
+
/** @private */
getActionEl : function(){
return this[this.actionMode];
* @extends Roo.Component
* Base class for any visual {@link Roo.Component} that uses a box container. BoxComponent provides automatic box
* model adjustments for sizing and positioning and will work correctly withnin the Component rendering model. All
- * container classes should subclass BoxComponent so that they will work consistently when nested within other Ext
+ * container classes should subclass BoxComponent so that they will work consistently when nested within other Roo
* layout containers.
* @constructor
* @param {Roo.Element/String/Object} config The configuration options.
* all DragDrop items in the window. Generally, you will not call
* this class directly, but it does have helper methods that could
* be useful in your DragDrop implementations.
- * @singleton
+ * @static
*/
Roo.dd.DragDropMgr = function() {
* @class Roo.dd.ScrollManager
* Provides automatic scrolling of overflow regions in the page during drag operations.<br><br>
* <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
- * @singleton
+ * @static
*/
Roo.dd.ScrollManager = function(){
var ddm = Roo.dd.DragDropMgr;
* @class Roo.dd.Registry
* Provides easy access to all drag drop components that are registered on a page. Items can be retrieved either
* directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
- * @singleton
+ * @static
*/
Roo.dd.Registry = function(){
var elements = {};
* target. This default implementation adds the CSS class specified by overClass (if any) to the drop element
* and returns the dropAllowed config value. This method should be overridden if drop validation is required.
*
- * IMPORTANT : it should set this.overClass and this.dropAllowed
+ * IMPORTANT : it should set this.valid to true|false
*
* @param {Roo.dd.DragSource} source The drag source that was dragged over this drop target
* @param {Event} e The event
* This method will be called on every mouse movement while the drag source is over the drop target.
* This default implementation simply returns the dropAllowed config value.
*
- * IMPORTANT : it should set this.dropAllowed
+ * IMPORTANT : it should set this.valid to true|false
*
* @param {Roo.dd.DragSource} source The drag source that was dragged over this drop target
* @param {Event} e The event
* out of the target without dropping. This default implementation simply removes the CSS class specified by
* overClass (if any) from the drop element.
*
+ *
* @param {Roo.dd.DragSource} source The drag source that was dragged over this drop target
* @param {Event} e The event
* @param {Object} data An object containing arbitrary data supplied by the drag source
/**
* @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);
}
* @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
*/
/**
* @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.
*/
/**
* @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>
*/
/**
* @class Roo.data.MemoryProxy
+ * @extends Roo.data.DataProxy
* An implementation of Roo.data.DataProxy that simply passes the data specified in its constructor
* to the Reader when its load method is called.
* @constructor
- * @param {Object} data The data object which the Reader uses to construct a block of Roo.data.Records.
+ * @param {Object} config A config object containing the objects needed for the Store to access data,
*/
-Roo.data.MemoryProxy = function(data){
- if (data.data) {
- data = data.data;
+Roo.data.MemoryProxy = function(config){
+ var data = config;
+ if (typeof(config) != 'undefined' && typeof(config.data) != 'undefined') {
+ data = config.data;
}
Roo.data.MemoryProxy.superclass.constructor.call(this);
this.data = data;
Roo.extend(Roo.data.MemoryProxy, Roo.data.DataProxy, {
+ /**
+ * @cfg {Object} data The data object which the Reader uses to construct a block of Roo.data.Records.
+ */
/**
* Load data from the requested source (in this case an in-memory
* data object passed to the constructor), read the data object into
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;
}
}
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;
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 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 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
+ };
},
// 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;
-
+ // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
+ return typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
+
}
* 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,
this.hide();
}
},
-
+ /**
+ * Similar to toggle, but does not trigger event.
+ * @param {Boolean} state [required] Force a particular state
+ */
+ setPressed : function(state)
+ {
+ if(state != this.pressed){
+ if(state){
+ this.el.addClass("x-btn-pressed");
+ this.pressed = true;
+ }else{
+ this.el.removeClass("x-btn-pressed");
+ this.pressed = false;
+ }
+ }
+ },
+
/**
* If a state it passed, it becomes the pressed state otherwise the current state is toggled.
* @param {Boolean} state (optional) Force a particular state
}
},
+
+
/**
* Focus the button
*/
/**
* @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
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: '',
* 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;
},
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,
}
-});//<script type="text/javascript">
-
-/*
- * Based Ext JS Library 1.1.1
- * Copyright(c) 2006-2007, Ext JS, LLC.
- * LGPL
- *
- */
-
-/**
- * @class Roo.HtmlEditorCore
- * @extends Roo.Component
- * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
- *
- * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
- */
+});Roo.rtf = {}; // namespace
+Roo.rtf.Hex = function(hex)
+{
+ this.hexstr = hex;
+};
+Roo.rtf.Paragraph = function(opts)
+{
+ this.content = []; ///??? is that used?
+};Roo.rtf.Span = function(opts)
+{
+ this.value = opts.value;
+};
-Roo.HtmlEditorCore = function(config){
-
-
- Roo.HtmlEditorCore.superclass.constructor.call(this, config);
-
+Roo.rtf.Group = function(parent)
+{
+ // we dont want to acutally store parent - it will make debug a nightmare..
+ this.content = [];
+ this.cn = [];
+
+
- this.addEvents({
- /**
- * @event initialize
- * Fires when the editor is fully initialized (including the iframe)
- * @param {Roo.HtmlEditorCore} this
- */
- initialize: true,
- /**
- * @event activate
- * Fires when the editor is first receives the focus. Any insertion must wait
- * until after this event.
- * @param {Roo.HtmlEditorCore} this
- */
- activate: true,
- /**
- * @event beforesync
- * Fires before the textarea is updated with content from the editor iframe. Return false
- * to cancel the sync.
- * @param {Roo.HtmlEditorCore} this
- * @param {String} html
- */
- beforesync: true,
- /**
- * @event beforepush
- * Fires before the iframe editor is updated with content from the textarea. Return false
- * to cancel the push.
- * @param {Roo.HtmlEditorCore} this
- * @param {String} html
- */
- beforepush: true,
- /**
- * @event sync
- * Fires when the textarea is updated with content from the editor iframe.
- * @param {Roo.HtmlEditorCore} this
- * @param {String} html
- */
- sync: true,
- /**
- * @event push
- * Fires when the iframe editor is updated with content from the textarea.
- * @param {Roo.HtmlEditorCore} this
- * @param {String} html
- */
- push: true,
+};
+
+Roo.rtf.Group.prototype = {
+ ignorable : false,
+ content: false,
+ cn: false,
+ addContent : function(node) {
+ // could set styles...
+ this.content.push(node);
+ },
+ addChild : function(cn)
+ {
+ this.cn.push(cn);
+ },
+ // only for images really...
+ toDataURL : function()
+ {
+ var mimetype = false;
+ switch(true) {
+ case this.content.filter(function(a) { return a.value == 'pngblip' } ).length > 0:
+ mimetype = "image/png";
+ break;
+ case this.content.filter(function(a) { return a.value == 'jpegblip' } ).length > 0:
+ mimetype = "image/jpeg";
+ break;
+ default :
+ return 'about:blank'; // ?? error?
+ }
- /**
- * @event editorevent
- * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
- * @param {Roo.HtmlEditorCore} this
- */
- editorevent: true
- });
-
- // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
+ var hexstring = this.content[this.content.length-1].value;
+
+ return 'data:' + mimetype + ';base64,' + btoa(hexstring.match(/\w{2}/g).map(function(a) {
+ return String.fromCharCode(parseInt(a, 16));
+ }).join(""));
+ }
- // defaults : white / black...
- this.applyBlacklists();
+};
+// this looks like it's normally the {rtf{ .... }}
+Roo.rtf.Document = function()
+{
+ // we dont want to acutally store parent - it will make debug a nightmare..
+ this.rtlch = [];
+ this.content = [];
+ this.cn = [];
+};
+Roo.extend(Roo.rtf.Document, Roo.rtf.Group, {
+ addChild : function(cn)
+ {
+ this.cn.push(cn);
+ switch(cn.type) {
+ case 'rtlch': // most content seems to be inside this??
+ case 'listtext':
+ case 'shpinst':
+ this.rtlch.push(cn);
+ return;
+ default:
+ this[cn.type] = cn;
+ }
+
+ },
+ getElementsByType : function(type)
+ {
+ var ret = [];
+ this._getElementsByType(type, ret, this.cn, 'rtf');
+ return ret;
+ },
+ _getElementsByType : function (type, ret, search_array, path)
+ {
+ search_array.forEach(function(n,i) {
+ if (n.type == type) {
+ n.path = path + '/' + n.type + ':' + i;
+ ret.push(n);
+ }
+ if (n.cn.length > 0) {
+ this._getElementsByType(type, ret, n.cn, path + '/' + n.type+':'+i);
+ }
+ },this);
+ }
+});
+
+Roo.rtf.Ctrl = function(opts)
+{
+ this.value = opts.value;
+ this.param = opts.param;
};
+/**
+ *
+ *
+ * based on this https://github.com/iarna/rtf-parser
+ * it's really only designed to extract pict from pasted RTF
+ *
+ * usage:
+ *
+ * var images = new Roo.rtf.Parser().parse(a_string).filter(function(g) { return g.type == 'pict'; });
+ *
+ *
+ */
+
-Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
- /**
- * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
- */
+Roo.rtf.Parser = function(text) {
+ //super({objectMode: true})
+ this.text = '';
+ this.parserState = this.parseText;
- owner : false,
+ // these are for interpeter...
+ this.doc = {};
+ ///this.parserState = this.parseTop
+ this.groupStack = [];
+ this.hexStore = [];
+ this.doc = false;
- /**
- * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
- * Roo.resizable.
- */
- resizable : false,
- /**
- * @cfg {Number} height (in pixels)
- */
- height: 300,
- /**
- * @cfg {Number} width (in pixels)
- */
- width: 500,
+ this.groups = []; // where we put the return.
- /**
- * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
- *
- */
- stylesheets: false,
+ for (var ii = 0; ii < text.length; ++ii) {
+ ++this.cpos;
+
+ if (text[ii] === '\n') {
+ ++this.row;
+ this.col = 1;
+ } else {
+ ++this.col;
+ }
+ this.parserState(text[ii]);
+ }
- // id of frame..
- frameId: false,
- // private properties
- validationEvent : false,
- deferHeight: true,
- initialized : false,
- activated : false,
- sourceEditMode : false,
- onFocus : Roo.emptyFn,
- iframePad:3,
- hideMode:'offsets',
- clearUp: true,
+};
+Roo.rtf.Parser.prototype = {
+ text : '', // string being parsed..
+ controlWord : '',
+ controlWordParam : '',
+ hexChar : '',
+ doc : false,
+ group: false,
+ groupStack : false,
+ hexStore : false,
- // blacklist + whitelisted elements..
- black: false,
- white: false,
-
- bodyCls : '',
+
+ cpos : 0,
+ row : 1, // reportin?
+ col : 1, //
- /**
- * 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
- * want to change the initialization markup of the iframe (e.g. to add stylesheets).
- */
- getDocMarkup : function(){
- // body styles..
- var st = '';
-
- // inherit styels from page...??
- if (this.stylesheets === false) {
-
- Roo.get(document.head).select('style').each(function(node) {
- st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
- });
-
- Roo.get(document.head).select('link').each(function(node) {
- st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
- });
-
- } else if (!this.stylesheets.length) {
- // simple..
- st = '<style type="text/css">' +
- 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
- '</style>';
- } else {
- for (var i in this.stylesheets) {
- st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
- }
-
+
+ push : function (el)
+ {
+ var m = 'cmd'+ el.type;
+ if (typeof(this[m]) == 'undefined') {
+ Roo.log('invalid cmd:' + el.type);
+ return;
}
+ this[m](el);
+ //Roo.log(el);
+ },
+ flushHexStore : function()
+ {
+ if (this.hexStore.length < 1) {
+ return;
+ }
+ var hexstr = this.hexStore.map(
+ function(cmd) {
+ return cmd.value;
+ }).join('');
- st += '<style type="text/css">' +
- 'IMG { cursor: pointer } ' +
- '</style>';
-
- var cls = 'roo-htmleditor-body';
+ this.group.addContent( new Roo.rtf.Hex( hexstr ));
+
+
+ this.hexStore.splice(0)
- if(this.bodyCls.length){
- cls += ' ' + this.bodyCls;
+ },
+
+ cmdgroupstart : function()
+ {
+ this.flushHexStore();
+ if (this.group) {
+ this.groupStack.push(this.group);
}
-
- return '<html><head>' + st +
- //<style type="text/css">' +
- //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
- //'</style>' +
- ' </head><body contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
+ // parent..
+ if (this.doc === false) {
+ this.group = this.doc = new Roo.rtf.Document();
+ return;
+
+ }
+ this.group = new Roo.rtf.Group(this.group);
},
-
- // private
- onRender : function(ct, position)
+ cmdignorable : function()
{
- var _t = this;
- //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
- this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
-
-
- this.el.dom.style.border = '0 none';
- this.el.dom.setAttribute('tabIndex', -1);
- this.el.addClass('x-hidden hide');
-
+ this.flushHexStore();
+ this.group.ignorable = true;
+ },
+ cmdendparagraph : function()
+ {
+ this.flushHexStore();
+ this.group.addContent(new Roo.rtf.Paragraph());
+ },
+ cmdgroupend : function ()
+ {
+ this.flushHexStore();
+ var endingGroup = this.group;
- if(Roo.isIE){ // fix IE 1px bogus margin
- this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
+ this.group = this.groupStack.pop();
+ if (this.group) {
+ this.group.addChild(endingGroup);
}
-
-
- this.frameId = Roo.id();
-
-
-
- var iframe = this.owner.wrap.createChild({
- tag: 'iframe',
- cls: 'form-control', // bootstrap..
- id: this.frameId,
- name: this.frameId,
- frameBorder : 'no',
- 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
- }, this.el
- );
- this.iframe = iframe.dom;
-
- this.assignDocWin();
-
- this.doc.designMode = 'on';
-
- this.doc.open();
- this.doc.write(this.getDocMarkup());
- this.doc.close();
-
- var task = { // must defer to wait for browser to be ready
- run : function(){
- //console.log("run task?" + this.doc.readyState);
- this.assignDocWin();
- if(this.doc.body || this.doc.readyState == 'complete'){
- try {
- this.doc.designMode="on";
- } catch (e) {
- return;
- }
- Roo.TaskMgr.stop(task);
- this.initEditor.defer(10, this);
- }
- },
- interval : 10,
- duration: 10000,
- scope: this
- };
- Roo.TaskMgr.start(task);
-
+ var doc = this.group || this.doc;
+ //if (endingGroup instanceof FontTable) {
+ // doc.fonts = endingGroup.table
+ //} else if (endingGroup instanceof ColorTable) {
+ // doc.colors = endingGroup.table
+ //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) {
+ if (endingGroup.ignorable === false) {
+ //code
+ this.groups.push(endingGroup);
+ // Roo.log( endingGroup );
+ }
+ //Roo.each(endingGroup.content, function(item)) {
+ // doc.addContent(item);
+ //}
+ //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable'))
+ //}
},
-
- // private
- onResize : function(w, h)
+ cmdtext : function (cmd)
{
- Roo.log('resize: ' +w + ',' + h );
- //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
- if(!this.iframe){
- return;
+ this.flushHexStore();
+ if (!this.group) { // an RTF fragment, missing the {\rtf1 header
+ //this.group = this.doc
+ return; // we really don't care about stray text...
}
- if(typeof w == 'number'){
-
- this.iframe.style.width = w + 'px';
+ this.group.addContent(new Roo.rtf.Span(cmd));
+ },
+ cmdcontrolword : function (cmd)
+ {
+ this.flushHexStore();
+ if (!this.group.type) {
+ this.group.type = cmd.value;
+ return;
}
- if(typeof h == 'number'){
-
- this.iframe.style.height = h + 'px';
- if(this.doc){
- (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
+ this.group.addContent(new Roo.rtf.Ctrl(cmd));
+ // we actually don't care about ctrl words...
+ return ;
+ /*
+ var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase())
+ if (this[method]) {
+ this[method](cmd.param)
+ } else {
+ if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param)
+ }
+ */
+ },
+ cmdhexchar : function(cmd) {
+ this.hexStore.push(cmd);
+ },
+ cmderror : function(cmd) {
+ throw cmd.value;
+ },
+
+ /*
+ _flush (done) {
+ if (this.text !== '\u0000') this.emitText()
+ done()
+ }
+ */
+
+
+ parseText : function(c)
+ {
+ if (c === '\\') {
+ this.parserState = this.parseEscapes;
+ } else if (c === '{') {
+ this.emitStartGroup();
+ } else if (c === '}') {
+ this.emitEndGroup();
+ } else if (c === '\x0A' || c === '\x0D') {
+ // cr/lf are noise chars
+ } else {
+ this.text += c;
+ }
+ },
+
+ parseEscapes: function (c)
+ {
+ if (c === '\\' || c === '{' || c === '}') {
+ this.text += c;
+ this.parserState = this.parseText;
+ } else {
+ this.parserState = this.parseControlSymbol;
+ this.parseControlSymbol(c);
+ }
+ },
+ parseControlSymbol: function(c)
+ {
+ if (c === '~') {
+ this.text += '\u00a0'; // nbsp
+ this.parserState = this.parseText
+ } else if (c === '-') {
+ this.text += '\u00ad'; // soft hyphen
+ } else if (c === '_') {
+ this.text += '\u2011'; // non-breaking hyphen
+ } else if (c === '*') {
+ this.emitIgnorable();
+ this.parserState = this.parseText;
+ } else if (c === "'") {
+ this.parserState = this.parseHexChar;
+ } else if (c === '|') { // formula cacter
+ this.emitFormula();
+ this.parserState = this.parseText;
+ } else if (c === ':') { // subentry in an index entry
+ this.emitIndexSubEntry();
+ this.parserState = this.parseText;
+ } else if (c === '\x0a') {
+ this.emitEndParagraph();
+ this.parserState = this.parseText;
+ } else if (c === '\x0d') {
+ this.emitEndParagraph();
+ this.parserState = this.parseText;
+ } else {
+ this.parserState = this.parseControlWord;
+ this.parseControlWord(c);
+ }
+ },
+ parseHexChar: function (c)
+ {
+ if (/^[A-Fa-f0-9]$/.test(c)) {
+ this.hexChar += c;
+ if (this.hexChar.length >= 2) {
+ this.emitHexChar();
+ this.parserState = this.parseText;
}
+ return;
}
+ this.emitError("Invalid character \"" + c + "\" in hex literal.");
+ this.parserState = this.parseText;
},
-
- /**
- * Toggles the editor between standard and source edit mode.
- * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
- */
- toggleSourceEdit : function(sourceEditMode){
-
- this.sourceEditMode = sourceEditMode === true;
-
- if(this.sourceEditMode){
-
- Roo.get(this.iframe).addClass(['x-hidden','hide']); //FIXME - what's the BS styles for these
-
- }else{
- Roo.get(this.iframe).removeClass(['x-hidden','hide']);
- //this.iframe.className = '';
- this.deferFocus();
+ parseControlWord : function(c)
+ {
+ if (c === ' ') {
+ this.emitControlWord();
+ this.parserState = this.parseText;
+ } else if (/^[-\d]$/.test(c)) {
+ this.parserState = this.parseControlWordParam;
+ this.controlWordParam += c;
+ } else if (/^[A-Za-z]$/.test(c)) {
+ this.controlWord += c;
+ } else {
+ this.emitControlWord();
+ this.parserState = this.parseText;
+ this.parseText(c);
+ }
+ },
+ parseControlWordParam : function (c) {
+ if (/^\d$/.test(c)) {
+ this.controlWordParam += c;
+ } else if (c === ' ') {
+ this.emitControlWord();
+ this.parserState = this.parseText;
+ } else {
+ this.emitControlWord();
+ this.parserState = this.parseText;
+ this.parseText(c);
}
- //this.setSize(this.owner.wrap.getSize());
- //this.fireEvent('editmodechange', this, this.sourceEditMode);
},
-
-
-
- /**
- * Protected method that will not generally be called directly. If you need/want
- * custom HTML cleanup, this is the method you should override.
- * @param {String} html The HTML to be cleaned
- * return {String} The cleaned HTML
- */
- cleanHtml : function(html){
- html = String(html);
- if(html.length > 5){
- if(Roo.isSafari){ // strip safari nonsense
- html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
- }
+
+
+
+ emitText : function () {
+ if (this.text === '') {
+ return;
}
- if(html == ' '){
- html = '';
+ this.push({
+ type: 'text',
+ value: this.text,
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
+ this.text = ''
+ },
+ emitControlWord : function ()
+ {
+ this.emitText();
+ if (this.controlWord === '') {
+ // do we want to track this - it seems just to cause problems.
+ //this.emitError('empty control word');
+ } else {
+ this.push({
+ type: 'controlword',
+ value: this.controlWord,
+ param: this.controlWordParam !== '' && Number(this.controlWordParam),
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
}
- return html;
+ this.controlWord = '';
+ this.controlWordParam = '';
+ },
+ emitStartGroup : function ()
+ {
+ this.emitText();
+ this.push({
+ type: 'groupstart',
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
+ },
+ emitEndGroup : function ()
+ {
+ this.emitText();
+ this.push({
+ type: 'groupend',
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
+ },
+ emitIgnorable : function ()
+ {
+ this.emitText();
+ this.push({
+ type: 'ignorable',
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
+ },
+ emitHexChar : function ()
+ {
+ this.emitText();
+ this.push({
+ type: 'hexchar',
+ value: this.hexChar,
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
+ this.hexChar = ''
},
+ emitError : function (message)
+ {
+ this.emitText();
+ this.push({
+ type: 'error',
+ value: message,
+ row: this.row,
+ col: this.col,
+ char: this.cpos //,
+ //stack: new Error().stack
+ });
+ },
+ emitEndParagraph : function () {
+ this.emitText();
+ this.push({
+ type: 'endparagraph',
+ pos: this.cpos,
+ row: this.row,
+ col: this.col
+ });
+ }
+
+} ;
+Roo.htmleditor = {};
+
+/**
+ * @class Roo.htmleditor.Filter
+ * Base Class for filtering htmleditor stuff. - do not use this directly - extend it.
+ * @cfg {DomElement} node The node to iterate and filter
+ * @cfg {boolean|String|Array} tag Tags to replace
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
- /**
- * HTML Editor -> Textarea
- * Protected method that will not generally be called directly. Syncs the contents
- * of the editor iframe with the textarea.
- */
- syncValue : function(){
- if(this.initialized){
- var bd = (this.doc.body || this.doc.documentElement);
- //this.cleanUpPaste(); -- this is done else where and causes havoc..
- var html = bd.innerHTML;
- 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;
- if(m && m[1]){
- html = '<div style="'+m[0]+'">' + html + '</div>';
- }
- }
- 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(/[\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 match;
- }
-
- // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
- return "&#" + cc + ";";
+
+Roo.htmleditor.Filter = function(cfg) {
+ Roo.apply(this.cfg);
+ // this does not actually call walk as it's really just a abstract class
+}
+
+
+Roo.htmleditor.Filter.prototype = {
+
+ node: false,
+
+ tag: false,
+
+ // overrride to do replace comments.
+ replaceComment : false,
+
+ // overrride to do replace or do stuff with tags..
+ replaceTag : false,
+
+ walk : function(dom)
+ {
+ Roo.each( Array.from(dom.childNodes), function( e ) {
+ switch(true) {
+ case e.nodeType == 8 && this.replaceComment !== false: // comment
+ this.replaceComment(e);
+ return;
- });
-
-
-
- if(this.owner.fireEvent('beforesync', this, html) !== false){
- this.el.dom.value = html;
- this.owner.fireEvent('sync', this, html);
+ case e.nodeType != 1: //not a node.
+ return;
+
+ case this.tag === true: // everything
+ case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1:
+ case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":":
+ case typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1: // array and it matches.
+ case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches.
+ if (this.replaceTag && false === this.replaceTag(e)) {
+ return;
+ }
+ if (e.hasChildNodes()) {
+ this.walk(e);
+ }
+ return;
+
+ default: // tags .. that do not match.
+ if (e.hasChildNodes()) {
+ this.walk(e);
+ }
}
- }
- },
-
- /**
- * Protected method that will not generally be called directly. Pushes the value of the textarea
- * into the iframe editor.
- */
- pushValue : function(){
- 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);
- }
- }
+ }, this);
+
},
+
+
+ removeNodeKeepChildren : function( node)
+ {
+
+ ar = Array.from(node.childNodes);
+ for (var i = 0; i < ar.length; i++) {
+
+ node.removeChild(ar[i]);
+ // what if we need to walk these???
+ node.parentNode.insertBefore(ar[i], node);
+
+ }
+ node.parentNode.removeChild(node);
+ }
+};
- // private
- deferFocus : function(){
- this.focus.defer(10, this);
- },
+/**
+ * @class Roo.htmleditor.FilterAttributes
+ * clean attributes and styles including http:// etc.. in attribute
+ * @constructor
+* Run a new Attribute Filter
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterAttributes = function(cfg)
+{
+ Roo.apply(this, cfg);
+ this.attrib_black = this.attrib_black || [];
+ this.attrib_white = this.attrib_white || [];
- // doc'ed in Field
- focus : function(){
- if(this.win && !this.sourceEditMode){
- this.win.focus();
- }else{
- this.el.focus();
- }
- },
+ this.attrib_clean = this.attrib_clean || [];
+ this.style_white = this.style_white || [];
+ this.style_black = this.style_black || [];
+ this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterAttributes, Roo.htmleditor.Filter,
+{
+ tag: true, // all tags
- assignDocWin: function()
+ attrib_black : false, // array
+ attrib_clean : false,
+ attrib_white : false,
+
+ style_white : false,
+ style_black : false,
+
+
+ replaceTag : function(node)
{
- var iframe = this.iframe;
+ if (!node.attributes || !node.attributes.length) {
+ return true;
+ }
- if(Roo.isIE){
- this.doc = iframe.contentWindow.document;
- this.win = iframe.contentWindow;
- } else {
-// if (!Roo.get(this.frameId)) {
-// return;
-// }
-// this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
-// this.win = Roo.get(this.frameId).dom.contentWindow;
+ for (var i = node.attributes.length-1; i > -1 ; i--) {
+ var a = node.attributes[i];
+ //console.log(a);
+ if (this.attrib_white.length && this.attrib_white.indexOf(a.name.toLowerCase()) < 0) {
+ node.removeAttribute(a.name);
+ continue;
+ }
- if (!Roo.get(this.frameId) && !iframe.contentDocument) {
- return;
+
+
+ if (a.name.toLowerCase().substr(0,2)=='on') {
+ node.removeAttribute(a.name);
+ continue;
}
- this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
- this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
+
+ if (this.attrib_black.indexOf(a.name.toLowerCase()) > -1) {
+ node.removeAttribute(a.name);
+ continue;
+ }
+ if (this.attrib_clean.indexOf(a.name.toLowerCase()) > -1) {
+ this.cleanAttr(node,a.name,a.value); // fixme..
+ continue;
+ }
+ if (a.name == 'style') {
+ this.cleanStyle(node,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.removeAttribute('class');
+ }
+
+ if (a.value.match(/^body$/)) {
+ node.removeAttribute('class');
+ }
+ continue;
+ }
+
+
+ // style cleanup!?
+ // class cleanup?
+
}
+ return true; // clean children
},
-
- // private
- initEditor : function(){
- //console.log("INIT EDITOR");
- this.assignDocWin();
-
+ cleanAttr: function(node, n,v)
+ {
- this.doc.designMode="on";
- this.doc.open();
- this.doc.write(this.getDocMarkup());
- this.doc.close();
-
- var dbody = (this.doc.body || this.doc.documentElement);
- //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
- // this copies styles from the containing element into thsi one..
- // not sure why we need all of this..
- //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
-
- //var ss = this.el.getStyles( 'background-image', 'background-repeat');
- //ss['background-attachment'] = 'fixed'; // w3c
- dbody.bgProperties = 'fixed'; // ie
- //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
- });
- if(Roo.isGecko){
- Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
+ if (v.match(/^\./) || v.match(/^\//)) {
+ return;
}
- if(Roo.isIE || Roo.isSafari || Roo.isOpera){
- Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
+ if (v.match(/^(http|https):\/\//)
+ || v.match(/^mailto:/)
+ || v.match(/^ftp:/)
+ || v.match(/^data:/)
+ ) {
+ return;
}
- this.initialized = true;
-
- this.owner.fireEvent('initialize', this);
- this.pushValue();
- },
-
- // private
- onDestroy : function(){
+ if (v.match(/^#/)) {
+ return;
+ }
+ if (v.match(/^\{/)) { // allow template editing.
+ return;
+ }
+// Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
+ node.removeAttribute(n);
+ },
+ cleanStyle : function(node, n,v)
+ {
+ if (v.match(/expression/)) { //XSS?? should we even bother..
+ node.removeAttribute(n);
+ return;
+ }
+ var parts = v.split(/;/);
+ var clean = [];
- if(this.rendered){
+ 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,'');
- //for (var i =0; i < this.toolbars.length;i++) {
- // // fixme - ask toolbars for heights?
- // this.toolbars[i].onDestroy();
- // }
+ if ( this.style_black.length && (this.style_black.indexOf(l) > -1 || this.style_black.indexOf(l.toLowerCase()) > -1)) {
+ return true;
+ }
+ //Roo.log()
+ // only allow 'c whitelisted system attributes'
+ if ( this.style_white.length && style_white.indexOf(l) < 0 && style_white.indexOf(l.toLowerCase()) < 0 ) {
+ return true;
+ }
- //this.wrap.dom.innerHTML = '';
- //this.wrap.remove();
+
+ clean.push(p);
+ return true;
+ },this);
+ if (clean.length) {
+ node.setAttribute(n, clean.join(';'));
+ } else {
+ node.removeAttribute(n);
}
- },
-
- // private
- onFirstFocus : function(){
- this.assignDocWin();
+ }
+
- this.activated = true;
-
- if(Roo.isGecko){ // prevent silly gecko errors
- this.win.focus();
- var s = this.win.getSelection();
- if(!s.focusNode || s.focusNode.nodeType != 3){
- var r = s.getRangeAt(0);
- r.selectNodeContents((this.doc.body || this.doc.documentElement));
- r.collapse(true);
- this.deferFocus();
- }
- try{
- this.execCmd('useCSS', true);
- this.execCmd('styleWithCSS', false);
- }catch(e){}
- }
- this.owner.fireEvent('activate', this);
- },
+});/**
+ * @class Roo.htmleditor.FilterBlack
+ * remove blacklisted elements.
+ * @constructor
+ * Run a new Blacklisted Filter
+ * @param {Object} config Configuration options
+ */
- // private
- adjustFont: function(btn){
- var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
- //if(Roo.isSafari){ // safari
- // adjust *= 2;
- // }
- var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
- if(Roo.isSafari){ // safari
- var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
- v = (v < 10) ? 10 : v;
- v = (v > 48) ? 48 : v;
- v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
-
- }
-
-
- v = Math.max(1, v+adjust);
-
- this.execCmd('FontSize', v );
- },
+Roo.htmleditor.FilterBlack = function(cfg)
+{
+ Roo.apply(this, cfg);
+ this.walk(cfg.node);
+}
- onEditorEvent : function(e)
+Roo.extend(Roo.htmleditor.FilterBlack, Roo.htmleditor.Filter,
+{
+ tag : true, // all elements.
+
+ replaceTag : function(n)
{
- this.owner.fireEvent('editorevent', this, e);
- // this.updateToolbar();
- this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
- },
+ n.parentNode.removeChild(n);
+ }
+});
+/**
+ * @class Roo.htmleditor.FilterComment
+ * remove comments.
+ * @constructor
+* Run a new Comments Filter
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterComment = function(cfg)
+{
+ this.walk(cfg.node);
+}
- insertTag : function(tg)
+Roo.extend(Roo.htmleditor.FilterComment, Roo.htmleditor.Filter,
+{
+
+ replaceComment : function(n)
{
- // could be a bit smarter... -> wrap the current selected tRoo..
- 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());
- wrappingNode.appendChild(range.extractContents());
- range.insertNode(wrappingNode);
+ n.parentNode.removeChild(n);
+ }
+});/**
+ * @class Roo.htmleditor.FilterKeepChildren
+ * remove tags but keep children
+ * @constructor
+ * Run a new Keep Children Filter
+ * @param {Object} config Configuration options
+ */
- return;
-
-
-
+Roo.htmleditor.FilterKeepChildren = function(cfg)
+{
+ Roo.apply(this, cfg);
+ if (this.tag === false) {
+ return; // dont walk.. (you can use this to use this just to do a child removal on a single tag )
+ }
+ // hacky?
+ if ((typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1)) {
+ this.cleanNamespace = true;
+ }
+
+ this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterKeepChildren, Roo.htmleditor.FilterBlack,
+{
+ cleanNamespace : false, // should really be an option, rather than using ':' inside of this tag.
+
+ replaceTag : function(node)
+ {
+ // walk children...
+ //Roo.log(node.tagName);
+ var ar = Array.from(node.childNodes);
+ //remove first..
+
+ for (var i = 0; i < ar.length; i++) {
+ var e = ar[i];
+ if (e.nodeType == 1) {
+ if (
+ (typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1)
+ || // array and it matches
+ (typeof(this.tag) == 'string' && this.tag == e.tagName)
+ ||
+ (e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1)
+ ||
+ (e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":")
+ ) {
+ this.replaceTag(ar[i]); // child is blacklisted as well...
+ continue;
+ }
+ }
+ }
+ ar = Array.from(node.childNodes);
+ for (var i = 0; i < ar.length; i++) {
+
+ node.removeChild(ar[i]);
+ // what if we need to walk these???
+ node.parentNode.insertBefore(ar[i], node);
+ if (this.tag !== false) {
+ this.walk(ar[i]);
+
+ }
}
- this.execCmd("formatblock", tg);
+ //Roo.log("REMOVE:" + node.tagName);
+ node.parentNode.removeChild(node);
+ return false; // don't walk children
- },
+
+ }
+});/**
+ * @class Roo.htmleditor.FilterParagraph
+ * paragraphs cause a nightmare for shared content - this filter is designed to be called ? at various points when editing
+ * like on 'push' to remove the <p> tags and replace them with line breaks.
+ * @constructor
+ * Run a new Paragraph Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterParagraph = function(cfg)
+{
+ // no need to apply config.
+ this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterParagraph, Roo.htmleditor.Filter,
+{
- insertText : function(txt)
+
+ tag : 'P',
+
+
+ replaceTag : function(node)
{
+ if (node.childNodes.length == 1 &&
+ node.childNodes[0].nodeType == 3 &&
+ node.childNodes[0].textContent.trim().length < 1
+ ) {
+ // remove and replace with '<BR>';
+ node.parentNode.replaceChild(node.ownerDocument.createElement('BR'),node);
+ return false; // no need to walk..
+ }
+ var ar = Array.from(node.childNodes);
+ for (var i = 0; i < ar.length; i++) {
+ node.removeChild(ar[i]);
+ // what if we need to walk these???
+ node.parentNode.insertBefore(ar[i], node);
+ }
+ // now what about this?
+ // <p> </p>
- var range = this.createRange();
- range.deleteContents();
- //alert(Sender.getAttribute('label'));
-
- range.insertNode(this.doc.createTextNode(txt));
- } ,
+ // double BR.
+ node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node);
+ node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node);
+ node.parentNode.removeChild(node);
+
+ return false;
+
+ }
-
+});/**
+ * @class Roo.htmleditor.FilterSpan
+ * filter span's with no attributes out..
+ * @constructor
+ * Run a new Span Filter
+ * @param {Object} config Configuration options
+ */
- /**
- * Executes a Midas editor command on the editor document and performs necessary focus and
- * toolbar updates. <b>This should only be called after the editor is initialized.</b>
- * @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){
- this.win.focus();
- this.execCmd(cmd, value);
- this.owner.fireEvent('editorevent', this);
- //this.updateToolbar();
- this.owner.deferFocus();
- },
+Roo.htmleditor.FilterSpan = function(cfg)
+{
+ // no need to apply config.
+ this.walk(cfg.node);
+}
- /**
- * Executes a Midas editor command directly on the editor document.
- * For visual commands, you should use {@link #relayCmd} instead.
- * <b>This should only be called after the editor is initialized.</b>
- * @param {String} cmd The Midas command
- * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
- */
- execCmd : function(cmd, value){
- this.doc.execCommand(cmd, false, value === undefined ? null : value);
- this.syncValue();
- },
-
+Roo.extend(Roo.htmleditor.FilterSpan, Roo.htmleditor.FilterKeepChildren,
+{
+
+ tag : 'SPAN',
+
-
- /**
- * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
- * to insert tRoo.
- * @param {String} text | dom node..
- */
- insertAtCursor : function(text)
+ replaceTag : function(node)
{
+ if (node.attributes && node.attributes.length > 0) {
+ return true; // walk if there are any.
+ }
+ Roo.htmleditor.FilterKeepChildren.prototype.replaceTag.call(this, node);
+ return false;
+
+ }
+
+});/**
+ * @class Roo.htmleditor.FilterTableWidth
+ try and remove table width data - as that frequently messes up other stuff.
+ *
+ * was 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..
+ *
+ * @constructor
+ * Run a new Table Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterTableWidth = function(cfg)
+{
+ // no need to apply config.
+ this.tag = ['TABLE', 'TD', 'TR', 'TH', 'THEAD', 'TBODY' ];
+ this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterTableWidth, Roo.htmleditor.Filter,
+{
+
+
+
+ replaceTag: function(node) {
- if(!this.activated){
- return;
+
+
+ if (node.hasAttribute('width')) {
+ node.removeAttribute('width');
}
- /*
- if(Roo.isIE){
- this.win.focus();
- var r = this.doc.selection.createRange();
- if(r){
- r.collapse(true);
- r.pasteHTML(text);
- this.syncValue();
- this.deferFocus();
+
+
+ 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');
}
- return;
}
- */
- if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
- this.win.focus();
-
-
- // from jquery ui (MIT licenced)
- var range, node;
- var win = this.win;
-
- if (win.getSelection && win.getSelection().getRangeAt) {
- range = win.getSelection().getRangeAt(0);
- node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
- range.insertNode(node);
- } 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();
+
+ return true; // continue doing children..
+ }
+});/**
+ * @class Roo.htmleditor.FilterWord
+ * try and clean up all the mess that Word generates.
+ *
+ * This is the 'nice version' - see 'Heavy' that white lists a very short list of elements, and multi-filters
+
+ * @constructor
+ * Run a new Span Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterWord = function(cfg)
+{
+ // no need to apply config.
+ this.replaceDocBullets(cfg.node);
+
+ this.replaceAname(cfg.node);
+ // this is disabled as the removal is done by other filters;
+ // this.walk(cfg.node);
+
+
+}
+
+Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter,
+{
+ tag: true,
+
+
+ /**
+ * Clean up MS wordisms...
+ */
+ replaceTag : function(node)
+ {
+
+ // no idea what this does - span with text, replaceds with just text.
+ if(
+ node.nodeName == 'SPAN' &&
+ !node.hasAttributes() &&
+ node.childNodes.length == 1 &&
+ node.firstChild.nodeName == "#text"
+ ) {
+ var textNode = node.firstChild;
+ node.removeChild(textNode);
+ if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters..
+ node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
+ }
+ node.parentNode.insertBefore(textNode, node);
+ if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters..
+ node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
+ }
- this.deferFocus();
+ node.parentNode.removeChild(node);
+ return false; // dont do chidren - we have remove our node - so no need to do chdhilren?
}
- },
- // private
- mozKeyPress : function(e){
- if(e.ctrlKey){
- var c = e.getCharCode(), cmd;
-
- if(c > 0){
- c = String.fromCharCode(c).toLowerCase();
- switch(c){
- case 'b':
- cmd = 'bold';
- break;
- case 'i':
- cmd = 'italic';
- break;
-
- case 'u':
- cmd = 'underline';
- break;
-
- case 'v':
- this.cleanUpPaste.defer(100, this);
- return;
-
- }
- if(cmd){
- this.win.focus();
- this.execCmd(cmd);
- this.deferFocus();
- e.preventDefault();
+
+
+
+ if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
+ node.parentNode.removeChild(node);
+ return false; // dont do chidlren
+ }
+ //Roo.log(node.tagName);
+ // remove - but keep children..
+ if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
+ //Roo.log('-- removed');
+ while (node.childNodes.length) {
+ var cn = node.childNodes[0];
+ node.removeChild(cn);
+ node.parentNode.insertBefore(cn, node);
+ // move node to parent - and clean it..
+ if (cn.nodeType == 1) {
+ this.replaceTag(cn);
}
}
+ node.parentNode.removeChild(node);
+ /// no need to iterate chidlren = it's got none..
+ //this.iterateChildren(node, this.cleanWord);
+ return false; // no need to iterate children.
}
- },
-
- // private
- fixKeys : function(){ // load time branching for fastest keydown performance
- if(Roo.isIE){
- return function(e){
- var k = e.getKey(), r;
- if(k == e.TAB){
- e.stopEvent();
- r = this.doc.selection.createRange();
- if(r){
- r.collapse(true);
- r.pasteHTML('    ');
- this.deferFocus();
- }
- return;
- }
-
- 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.collapse(false);
- r.select();
- }
- }
- }
- if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
- this.cleanUpPaste.defer(100, this);
- return;
- }
-
-
- };
- }else if(Roo.isOpera){
- return function(e){
- var k = e.getKey();
- if(k == e.TAB){
- e.stopEvent();
- this.win.focus();
- this.execCmd('InsertHTML','    ');
- this.deferFocus();
- }
- if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
- this.cleanUpPaste.defer(100, this);
+ // 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;
}
-
- };
- }else if(Roo.isSafari){
- return function(e){
- var k = e.getKey();
-
- if(k == e.TAB){
- e.stopEvent();
- this.execCmd('InsertText','\t');
- this.deferFocus();
+ cna.push(cls);
+ });
+ node.className = cna.length ? cna.join(' ') : '';
+ if (!cna.length) {
+ node.removeAttribute("class");
+ }
+ }
+
+ 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;
}
- if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
- this.cleanUpPaste.defer(100, this);
+ var kv = s.split(":");
+ if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
return;
}
-
- };
- }
- }(),
-
- getAllAncestors: function()
- {
- var p = this.getSelectedNode();
- var a = [];
- if (!p) {
- a.push(p); // push blank onto stack..
- p = this.getParentElement();
+ // what ever is left... we allow.
+ nstyle.push(s);
+ });
+ node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
+ if (!nstyle.length) {
+ node.removeAttribute('style');
+ }
}
+ return true; // do children
+
- while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
- a.push(p);
- p = p.parentNode;
- }
- a.push(this.doc.body);
- return a;
},
- lastSel : false,
- lastSelNode : false,
+
+ styleToObject: function(node)
+ {
+ var styles = (node.getAttribute("style") || '').split(";");
+ var ret = {};
+ Roo.each(styles, function(s) {
+ if (!s.match(/:/)) {
+ return;
+ }
+ var kv = s.split(":");
+
+ // what ever is left... we allow.
+ ret[kv[0].trim()] = kv[1];
+ });
+ return ret;
+ },
- getSelection : function()
+ replaceAname : function (doc)
{
- this.assignDocWin();
- return Roo.isIE ? this.doc.selection : this.win.getSelection();
+ // replace all the a/name without..
+ var aa = Array.from(doc.getElementsByTagName('a'));
+ for (var i = 0; i < aa.length; i++) {
+ var a = aa[i];
+ if (a.hasAttribute("name")) {
+ a.removeAttribute("name");
+ }
+ if (a.hasAttribute("href")) {
+ continue;
+ }
+ // reparent children.
+ this.removeNodeKeepChildren(a);
+
+ }
+
+
+
},
+
- getSelectedNode: function()
+
+ replaceDocBullets : function(doc)
{
- // this may only work on Gecko!!!
+ // this is a bit odd - but it appears some indents use ql-indent-1
+ //Roo.log(doc.innerHTML);
- // should we cache this!!!!
+ var listpara = Array.from(doc.getElementsByClassName('MsoListParagraphCxSpFirst'));
+ for( var i = 0; i < listpara.length; i ++) {
+ listpara[i].className = "MsoListParagraph";
+ }
+
+ listpara = Array.from(doc.getElementsByClassName('MsoListParagraphCxSpMiddle'));
+ for( var i = 0; i < listpara.length; i ++) {
+ listpara[i].className = "MsoListParagraph";
+ }
+ listpara = Array.from(doc.getElementsByClassName('MsoListParagraphCxSpLast'));
+ for( var i = 0; i < listpara.length; i ++) {
+ listpara[i].className = "MsoListParagraph";
+ }
+ listpara = Array.from(doc.getElementsByClassName('ql-indent-1'));
+ for( var i = 0; i < listpara.length; i ++) {
+ listpara[i].className = "MsoListParagraph";
+ }
+ // this is a bit hacky - we had one word document where h2 had a miso-list attribute.
+ var htwo = Array.from(doc.getElementsByTagName('h2'));
+ for( var i = 0; i < htwo.length; i ++) {
+ if (htwo[i].hasAttribute('style') && htwo[i].getAttribute('style').match(/mso-list:/)) {
+ htwo[i].className = "MsoListParagraph";
+ }
+ }
+ listpara = Array.from(doc.getElementsByClassName('MsoNormal'));
+ for( var i = 0; i < listpara.length; i ++) {
+ if (listpara[i].hasAttribute('style') && listpara[i].getAttribute('style').match(/mso-list:/)) {
+ listpara[i].className = "MsoListParagraph";
+ } else {
+ listpara[i].className = "MsoNormalx";
+ }
+ }
+
+ listpara = doc.getElementsByClassName('MsoListParagraph');
+ // Roo.log(doc.innerHTML);
-
- var range = this.createRange(this.getSelection()).cloneRange();
- if (Roo.isIE) {
- var parent = range.parentElement();
- while (true) {
- var testRange = range.duplicate();
- testRange.moveToElementText(parent);
- if (testRange.inRange(range)) {
- break;
+ while(listpara.length) {
+
+ this.replaceDocBullet(listpara.item(0));
+ }
+
+ },
+
+
+
+ replaceDocBullet : function(p)
+ {
+ // gather all the siblings.
+ var ns = p,
+ parent = p.parentNode,
+ doc = parent.ownerDocument,
+ items = [];
+
+ var listtype = 'ul';
+ while (ns) {
+ if (ns.nodeType != 1) {
+ ns = ns.nextSibling;
+ continue;
+ }
+ if (!ns.className.match(/(MsoListParagraph|ql-indent-1)/i)) {
+ break;
+ }
+ var spans = ns.getElementsByTagName('span');
+ if (ns.hasAttribute('style') && ns.getAttribute('style').match(/mso-list/)) {
+ items.push(ns);
+ ns = ns.nextSibling;
+ has_list = true;
+ if (spans.length && spans[0].hasAttribute('style')) {
+ var style = this.styleToObject(spans[0]);
+ if (typeof(style['font-family']) != 'undefined' && !style['font-family'].match(/Symbol/)) {
+ listtype = 'ol';
+ }
}
- if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
+
+ continue;
+ }
+ var spans = ns.getElementsByTagName('span');
+ if (!spans.length) {
+ break;
+ }
+ var has_list = false;
+ for(var i = 0; i < spans.length; i++) {
+ if (spans[i].hasAttribute('style') && spans[i].getAttribute('style').match(/mso-list/)) {
+ has_list = true;
break;
}
- parent = parent.parentElement;
}
- return parent;
+ if (!has_list) {
+ break;
+ }
+ items.push(ns);
+ ns = ns.nextSibling;
+
+
}
-
- // is ancestor a text element.
- var ac = range.commonAncestorContainer;
- if (ac.nodeType == 3) {
- ac = ac.parentNode;
+ if (!items.length) {
+ ns.className = "";
+ return;
}
- var ar = ac.childNodes;
-
- var nodes = [];
- var other_nodes = [];
- var has_other_nodes = false;
- for (var i=0;i<ar.length;i++) {
- if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
- continue;
+ var ul = parent.ownerDocument.createElement(listtype); // what about number lists...
+ parent.insertBefore(ul, p);
+ var lvl = 0;
+ var stack = [ ul ];
+ var last_li = false;
+
+ var margin_to_depth = {};
+ max_margins = -1;
+
+ items.forEach(function(n, ipos) {
+ //Roo.log("got innertHMLT=" + n.innerHTML);
+
+ var spans = n.getElementsByTagName('span');
+ if (!spans.length) {
+ //Roo.log("No spans found");
+
+ parent.removeChild(n);
+
+
+ return; // skip it...
}
- // fullly contained node.
+
+
+ var num = 1;
+ var style = {};
+ for(var i = 0; i < spans.length; i++) {
- if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
- nodes.push(ar[i]);
- continue;
+ style = this.styleToObject(spans[i]);
+ if (typeof(style['mso-list']) == 'undefined') {
+ continue;
+ }
+ if (listtype == 'ol') {
+ num = spans[i].innerText.replace(/[^0-9]+]/g,'') * 1;
+ }
+ spans[i].parentNode.removeChild(spans[i]); // remove the fake bullet.
+ break;
+ }
+ //Roo.log("NOW GOT innertHMLT=" + n.innerHTML);
+ style = this.styleToObject(n); // mo-list is from the parent node.
+ if (typeof(style['mso-list']) == 'undefined') {
+ //Roo.log("parent is missing level");
+
+ parent.removeChild(n);
+
+ return;
}
- // probably selected..
- if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
- other_nodes.push(ar[i]);
- continue;
+ var margin = style['margin-left'];
+ if (typeof(margin_to_depth[margin]) == 'undefined') {
+ max_margins++;
+ margin_to_depth[margin] = max_margins;
}
- // outer..
- if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
- continue;
+ nlvl = margin_to_depth[margin] ;
+
+ if (nlvl > lvl) {
+ //new indent
+ var nul = doc.createElement(listtype); // what about number lists...
+ if (!last_li) {
+ last_li = doc.createElement('li');
+ stack[lvl].appendChild(last_li);
+ }
+ last_li.appendChild(nul);
+ stack[nlvl] = nul;
+
}
+ lvl = nlvl;
+ // not starting at 1..
+ if (!stack[nlvl].hasAttribute("start") && listtype == "ol") {
+ stack[nlvl].setAttribute("start", num);
+ }
- has_other_nodes = true;
- }
- if (!nodes.length && other_nodes.length) {
- nodes= other_nodes;
- }
- if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
- return false;
- }
+ var nli = stack[nlvl].appendChild(doc.createElement('li'));
+ last_li = nli;
+ nli.innerHTML = n.innerHTML;
+ //Roo.log("innerHTML = " + n.innerHTML);
+ parent.removeChild(n);
+
+
+
+
+ },this);
- return nodes[0];
- },
- createRange: function(sel)
- {
- // this has strange effects when using with
- // top toolbar - not sure if it's a great idea.
- //this.editor.contentWindow.focus();
- if (typeof sel != "undefined") {
- try {
- return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
- } catch(e) {
- return this.doc.createRange();
- }
- } else {
- return this.doc.createRange();
- }
- },
- getParentElement: function()
- {
- this.assignDocWin();
- var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
- var range = this.createRange(sel);
-
- try {
- var p = range.commonAncestorContainer;
- while (p.nodeType == 3) { // text node
- p = p.parentNode;
- }
- return p;
- } catch (e) {
- return null;
- }
+
+ }
- },
- /***
- *
- * Range intersection.. the hard stuff...
- * '-1' = before
- * '0' = hits..
- * '1' = after.
- * [ -- selected range --- ]
- * [fail] [fail]
- *
- * basically..
- * if end is before start or hits it. fail.
- * if start is after end or hits it fail.
- *
- * if either hits (but other is outside. - then it's not
- *
- *
- **/
- // @see http://www.thismuchiknow.co.uk/?p=64.
- rangeIntersectsNode : function(range, node)
- {
- var nodeRange = node.ownerDocument.createRange();
- try {
- nodeRange.selectNode(node);
- } catch (e) {
- nodeRange.selectNodeContents(node);
- }
+});
+/**
+ * @class Roo.htmleditor.FilterStyleToTag
+ * part of the word stuff... - certain 'styles' should be converted to tags.
+ * eg.
+ * font-weight: bold -> bold
+ * ?? super / subscrit etc..
+ *
+ * @constructor
+* Run a new style to tag filter.
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterStyleToTag = function(cfg)
+{
- var rangeStartRange = range.cloneRange();
- rangeStartRange.collapse(true);
+ this.tags = {
+ B : [ 'fontWeight' , 'bold'],
+ I : [ 'fontStyle' , 'italic'],
+ //pre : [ 'font-style' , 'italic'],
+ // h1.. h6 ?? font-size?
+ SUP : [ 'verticalAlign' , 'super' ],
+ SUB : [ 'verticalAlign' , 'sub' ]
+
+
+ };
- var rangeEndRange = range.cloneRange();
- rangeEndRange.collapse(false);
+ Roo.apply(this, cfg);
+
- var nodeStartRange = nodeRange.cloneRange();
- nodeStartRange.collapse(true);
+ this.walk(cfg.node);
- var nodeEndRange = nodeRange.cloneRange();
- nodeEndRange.collapse(false);
- return rangeStartRange.compareBoundaryPoints(
- Range.START_TO_START, nodeEndRange) == -1 &&
- rangeEndRange.compareBoundaryPoints(
- Range.START_TO_START, nodeStartRange) == 1;
-
-
- },
- rangeCompareNode : function(range, node)
- {
- var nodeRange = node.ownerDocument.createRange();
- try {
- nodeRange.selectNode(node);
- } catch (e) {
- nodeRange.selectNodeContents(node);
- }
+
+}
+
+
+Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter,
+{
+ tag: true, // all tags
+
+ tags : false,
+
+
+ replaceTag : function(node)
+ {
- range.collapse(true);
+ if (node.getAttribute("style") === null) {
+ return true;
+ }
+ var inject = [];
+ for (var k in this.tags) {
+ if (node.style[this.tags[k][0]] == this.tags[k][1]) {
+ inject.push(k);
+ node.style.removeProperty(this.tags[k][0]);
+ }
+ }
+ if (!inject.length) {
+ return true;
+ }
+ var cn = Array.from(node.childNodes);
+ var nn = node;
+ Roo.each(inject, function(t) {
+ var nc = node.ownerDocument.createElement(t);
+ nn.appendChild(nc);
+ nn = nc;
+ });
+ for(var i = 0;i < cn.length;cn++) {
+ node.removeChild(cn[i]);
+ nn.appendChild(cn[i]);
+ }
+ return true /// iterate thru
+ }
+
+})/**
+ * @class Roo.htmleditor.FilterLongBr
+ * BR/BR/BR - keep a maximum of 2...
+ * @constructor
+ * Run a new Long BR Filter
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.FilterLongBr = function(cfg)
+{
+ // no need to apply config.
+ this.walk(cfg.node);
+}
+
+Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter,
+{
- nodeRange.collapse(true);
- var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
- var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
-
- //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
+ tag : 'BR',
+
+
+ replaceTag : function(node)
+ {
- var nodeIsBefore = ss == 1;
- var nodeIsAfter = ee == -1;
+ var ps = node.nextSibling;
+ while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
+ ps = ps.nextSibling;
+ }
- if (nodeIsBefore && nodeIsAfter) {
- return 0; // outer
+ if (!ps && [ 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(node.parentNode.tagName) > -1) {
+ node.parentNode.removeChild(node); // remove last BR inside one fo these tags
+ return false;
}
- if (!nodeIsBefore && nodeIsAfter) {
- return 1; //right trailed.
+
+ if (!ps || ps.nodeType != 1) {
+ return false;
}
- if (nodeIsBefore && !nodeIsAfter) {
- return 2; // left trailed.
+ if (!ps || ps.tagName != 'BR') {
+
+ return false;
}
- // 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;
+
+
+
+
+ if (!node.previousSibling) {
+ return false;
}
+ var ps = node.previousSibling;
- },
-
- cleanWordChars : function(input) {// change the chars to hex code
- var he = Roo.HtmlEditorCore;
+ while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
+ ps = ps.previousSibling;
+ }
+ if (!ps || ps.nodeType != 1) {
+ return false;
+ }
+ // if header or BR before.. then it's a candidate for removal.. - as we only want '2' of these..
+ if (!ps || [ 'BR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(ps.tagName) < 0) {
+ return false;
+ }
- var output = input;
- Roo.each(he.swapCodes, function(sw) {
- var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
-
- output = output.replace(swapper, sw[1]);
- });
+ node.parentNode.removeChild(node); // remove me...
- return output;
- },
+ return false; // no need to do children
+
+ }
+});
+
+/**
+ * @class Roo.htmleditor.FilterBlock
+ * removes id / data-block and contenteditable that are associated with blocks
+ * usage should be done on a cloned copy of the dom
+ * @constructor
+* Run a new Attribute Filter { node : xxxx }}
+* @param {Object} config Configuration options
+ */
+Roo.htmleditor.FilterBlock = function(cfg)
+{
+ Roo.apply(this, cfg);
+ var qa = cfg.node.querySelectorAll;
+ this.removeAttributes('data-block');
+ this.removeAttributes('contenteditable');
+ this.removeAttributes('id');
- cleanUpChildren : function (n)
+}
+
+Roo.apply(Roo.htmleditor.FilterBlock.prototype,
+{
+ node: true, // all tags
+
+
+ removeAttributes : function(attr)
{
- if (!n.childNodes.length) {
- return;
+ var ar = this.node.querySelectorAll('*[' + attr + ']');
+ for (var i =0;i<ar.length;i++) {
+ ar[i].removeAttribute(attr);
}
- for (var i = n.childNodes.length-1; i > -1 ; i--) {
- this.cleanUpChild(n.childNodes[i]);
- }
- },
+ }
+
+
+
+});
+/***
+ * This is based loosely on tinymce
+ * @class Roo.htmleditor.TidySerializer
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ * @constructor
+ * @method Serializer
+ * @param {Object} settings Name/value settings object.
+ */
+
+
+Roo.htmleditor.TidySerializer = function(settings)
+{
+ Roo.apply(this, settings);
-
+ this.writer = new Roo.htmleditor.TidyWriter(settings);
- cleanUpChild : function (node)
+
+
+};
+Roo.htmleditor.TidySerializer.prototype = {
+
+ /**
+ * @param {boolean} inner do the inner of the node.
+ */
+ inner : false,
+
+ writer : false,
+
+ /**
+ * Serializes the specified node into a string.
+ *
+ * @example
+ * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
+ * @method serialize
+ * @param {DomElement} node Node instance to serialize.
+ * @return {String} String with HTML based on DOM tree.
+ */
+ serialize : function(node) {
+
+ // = settings.validate;
+ var writer = this.writer;
+ var self = this;
+ this.handlers = {
+ // #text
+ 3: function(node) {
+
+ writer.text(node.nodeValue, node);
+ },
+ // #comment
+ 8: function(node) {
+ writer.comment(node.nodeValue);
+ },
+ // Processing instruction
+ 7: function(node) {
+ writer.pi(node.name, node.nodeValue);
+ },
+ // Doctype
+ 10: function(node) {
+ writer.doctype(node.nodeValue);
+ },
+ // CDATA
+ 4: function(node) {
+ writer.cdata(node.nodeValue);
+ },
+ // Document fragment
+ 11: function(node) {
+ node = node.firstChild;
+ if (!node) {
+ return;
+ }
+ while(node) {
+ self.walk(node);
+ node = node.nextSibling
+ }
+ }
+ };
+ writer.reset();
+ 1 != node.nodeType || this.inner ? this.handlers[11](node) : this.walk(node);
+ return writer.getContent();
+ },
+
+ walk: 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 attrName, attrValue, sortedAttrs, i, l, elementRule,
+ handler = this.handlers[node.nodeType];
+
+ if (handler) {
+ handler(node);
+ 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..
+
+ var name = node.nodeName;
+ var isEmpty = node.childNodes.length < 1;
+
+ var writer = this.writer;
+ var attrs = node.attributes;
+ // Sort attributes
- if (this.black.indexOf(lcname) > -1 && this.clearUp ) {
- // remove node.
- node.parentNode.removeChild(node);
+ writer.start(node.nodeName, attrs, isEmpty, node);
+ if (isEmpty) {
+ return;
+ }
+ node = node.firstChild;
+ if (!node) {
+ writer.end(name);
return;
-
}
+ while (node) {
+ this.walk(node);
+ node = node.nextSibling;
+ }
+ writer.end(name);
- var remove_keep_children= Roo.HtmlEditorCore.remove.indexOf(node.tagName.toLowerCase()) > -1;
+
+ }
+ // Serialize element and treat all non elements as fragments
+
+};
+
+/***
+ * This is based loosely on tinymce
+ * @class Roo.htmleditor.TidyWriter
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ *
+ * Known issues?
+ * - not tested much with 'PRE' formated elements.
+ *
+ *
+ *
+ */
+
+Roo.htmleditor.TidyWriter = function(settings)
+{
+
+ // indent, indentBefore, indentAfter, encode, htmlOutput, html = [];
+ Roo.apply(this, settings);
+ this.html = [];
+ this.state = [];
+
+ this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
+
+}
+Roo.htmleditor.TidyWriter.prototype = {
+
+
+ state : false,
+
+ indent : ' ',
+
+ // part of state...
+ indentstr : '',
+ in_pre: false,
+ in_inline : false,
+ last_inline : false,
+ encode : false,
+
+
+ /**
+ * Writes the a start element such as <p id="a">.
+ *
+ * @method start
+ * @param {String} name Name of the element.
+ * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
+ * @param {Boolean} empty Optional empty state if the tag should end like <br />.
+ */
+ start: function(name, attrs, empty, node)
+ {
+ var i, l, attr, value;
- // spans with no attributes - just remove them..
- if ((!node.attributes || !node.attributes.length) && lcname == 'span') {
- remove_keep_children = true;
- }
+ // there are some situations where adding line break && indentation will not work. will not work.
+ // <span / b / i ... formating?
- // 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..
+ var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
+ var in_pre = this.in_pre || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1;
- //if (node.tagName.toLowerCase() == 'a' && !node.hasAttribute('href')) {
- // remove_keep_children = true;
- //}
+ var is_short = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false;
- 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;
- }
+ var add_lb = name == 'BR' ? false : in_inline;
- if (!node.attributes || !node.attributes.length) {
-
-
-
-
- this.cleanUpChildren(node);
- return;
+ if (!add_lb && !this.in_pre && this.lastElementEndsWS()) {
+ i_inline = false;
}
+
+ var indentstr = this.indentstr;
- 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;
- }
- if (v.match(/^\{/)) { // allow template editing.
- return;
- }
-// Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
- node.removeAttribute(n);
-
- }
+ // e_inline = elements that can be inline, but still allow \n before and after?
+ // only 'BR' ??? any others?
- 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;
+ // ADD LINE BEFORE tage
+ if (!this.in_pre) {
+ if (in_inline) {
+ //code
+ if (name == 'BR') {
+ this.addLine();
+ } else if (this.lastElementEndsWS()) {
+ this.addLine();
+ } else{
+ // otherwise - no new line. (and dont indent.)
+ indentstr = '';
}
-
-
-
- clean.push(p);
- return true;
- });
- if (clean.length) {
- node.setAttribute(n, clean.join(';'));
} else {
- node.removeAttribute(n);
+ this.addLine();
}
-
+ } else {
+ indentstr = '';
}
+ this.html.push(indentstr + '<', name.toLowerCase());
- 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 (attrs) {
+ for (i = 0, l = attrs.length; i < l; i++) {
+ attr = attrs[i];
+ this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
}
- 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;
+ }
+
+ if (empty) {
+ if (is_short) {
+ this.html[this.html.length] = '/>';
+ } else {
+ this.html[this.html.length] = '></' + name.toLowerCase() + '>';
}
- /// clean up MS crap..
- // tecnically this should be a list of valid class'es..
-
+ var e_inline = name == 'BR' ? false : this.in_inline;
- if (a.name == 'class') {
- if (a.value.match(/^Mso/)) {
- node.removeAttribute('class');
- }
-
- if (a.value.match(/^body$/)) {
- node.removeAttribute('class');
+ if (!e_inline && !this.in_pre) {
+ this.addLine();
+ }
+ return;
+
+ }
+ // not empty..
+ this.html[this.html.length] = '>';
+
+ // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
+ /*
+ if (!in_inline && !in_pre) {
+ var cn = node.firstChild;
+ while(cn) {
+ if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
+ in_inline = true
+ break;
}
- continue;
+ cn = cn.nextSibling;
}
-
- // style cleanup!?
- // class cleanup?
-
+
}
+ */
+
+ this.pushState({
+ indentstr : in_pre ? '' : (this.indentstr + this.indent),
+ in_pre : in_pre,
+ in_inline : in_inline
+ });
+ // add a line after if we are not in a
+
+ if (!in_inline && !in_pre) {
+ this.addLine();
+ }
- this.cleanUpChildren(node);
+
+
+ },
+
+ lastElementEndsWS : function()
+ {
+ var value = this.html.length > 0 ? this.html[this.html.length-1] : false;
+ if (value === false) {
+ return true;
+ }
+ return value.match(/\s+$/);
},
/**
- * Clean up MS wordisms...
+ * Writes the a end element such as </p>.
+ *
+ * @method end
+ * @param {String} name Name of the element.
*/
- cleanWord : function(node)
+ end: function(name) {
+ var value;
+ this.popState();
+ var indentstr = '';
+ var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
+
+ if (!this.in_pre && !in_inline) {
+ this.addLine();
+ indentstr = this.indentstr;
+ }
+ this.html.push(indentstr + '</', name.toLowerCase(), '>');
+ this.last_inline = in_inline;
+
+ // pop the indent state..
+ },
+ /**
+ * Writes a text node.
+ *
+ * In pre - we should not mess with the contents.
+ *
+ *
+ * @method text
+ * @param {String} text String to write out.
+ * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
+ */
+ text: function(in_text, node)
{
- if (!node) {
- this.cleanWord(this.doc.body);
+ // if not in whitespace critical
+ if (in_text.length < 1) {
return;
}
+ var text = new XMLSerializer().serializeToString(document.createTextNode(in_text)); // escape it properly?
- if(
- node.nodeName == 'SPAN' &&
- !node.hasAttributes() &&
- node.childNodes.length == 1 &&
- node.firstChild.nodeName == "#text"
- ) {
- var textNode = node.firstChild;
- node.removeChild(textNode);
- if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters..
- node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
- }
- node.parentNode.insertBefore(textNode, node);
- if (node.getAttribute('lang') != 'zh-CN') { // do not space pad on chinese characters..
- node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
+ if (this.in_pre) {
+ this.html[this.html.length] = text;
+ return;
+ }
+
+ if (this.in_inline) {
+ text = text.replace(/\s+/g,' '); // all white space inc line breaks to a slingle' '
+ if (text != ' ') {
+ text = text.replace(/\s+/,' '); // all white space to single white space
+
+
+ // if next tag is '<BR>', then we can trim right..
+ if (node.nextSibling &&
+ node.nextSibling.nodeType == 1 &&
+ node.nextSibling.nodeName == 'BR' )
+ {
+ text = text.replace(/\s+$/g,'');
+ }
+ // if previous tag was a BR, we can also trim..
+ if (node.previousSibling &&
+ node.previousSibling.nodeType == 1 &&
+ node.previousSibling.nodeName == 'BR' )
+ {
+ text = this.indentstr + text.replace(/^\s+/g,'');
+ }
+ if (text.match(/\n/)) {
+ text = text.replace(
+ /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
+ );
+ // remoeve the last whitespace / line break.
+ text = text.replace(/\n\s+$/,'');
+ }
+ // repace long lines
+
}
- node.parentNode.removeChild(node);
+
+ this.html[this.html.length] = text;
+ return;
}
+ // see if previous element was a inline element.
+ var indentstr = this.indentstr;
+
+ text = text.replace(/\s+/g," "); // all whitespace into single white space.
- if (node.nodeName == "#text") {
- // clean up silly Windows -- stuff?
- return;
+ // should trim left?
+ if (node.previousSibling &&
+ node.previousSibling.nodeType == 1 &&
+ Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1)
+ {
+ indentstr = '';
+
+ } else {
+ this.addLine();
+ text = text.replace(/^\s+/,''); // trim left
+
}
- if (node.nodeName == "#comment") {
- node.parentNode.removeChild(node);
- // clean up silly Windows -- stuff?
- return;
+ // should trim right?
+ if (node.nextSibling &&
+ node.nextSibling.nodeType == 1 &&
+ Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.nextSibling.nodeName) > -1)
+ {
+ // noop
+
+ } else {
+ text = text.replace(/\s+$/,''); // trim right
}
+
+
- if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
- node.parentNode.removeChild(node);
+
+
+ if (text.length < 1) {
return;
}
- //Roo.log(node.tagName);
- // remove - but keep children..
- if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
- //Roo.log('-- removed');
- while (node.childNodes.length) {
- var cn = node.childNodes[0];
- node.removeChild(cn);
- node.parentNode.insertBefore(cn, node);
- // move node to parent - and clean it..
- this.cleanWord(cn);
- }
- node.parentNode.removeChild(node);
- /// no need to iterate chidlren = it's got none..
- //this.iterateChildren(node, this.cleanWord);
+ if (!text.match(/\n/)) {
+ this.html.push(indentstr + text);
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");
- }
- }
- if (node.hasAttribute("lang")) {
- node.removeAttribute("lang");
- }
+ text = this.indentstr + text.replace(
+ /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
+ );
+ // remoeve the last whitespace / line break.
+ text = text.replace(/\s+$/,'');
- 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);
+ this.html.push(text);
+ // split and indent..
},
/**
- * 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.
+ * Writes a cdata node such as <![CDATA[data]]>.
+ *
+ * @method cdata
+ * @param {String} text String to write out inside the cdata.
*/
- 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])
- }
+ cdata: function(text) {
+ this.html.push('<![CDATA[', text, ']]>');
+ },
+ /**
+ * Writes a comment node such as <!-- Comment -->.
+ *
+ * @method cdata
+ * @param {String} text String to write out inside the comment.
+ */
+ comment: function(text) {
+ this.html.push('<!--', text, '-->');
+ },
+ /**
+ * Writes a PI node such as <?xml attr="value" ?>.
+ *
+ * @method pi
+ * @param {String} name Name of the pi.
+ * @param {String} text String to write out inside the pi.
+ */
+ pi: function(name, text) {
+ text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>');
+ this.indent != '' && this.html.push('\n');
+ },
+ /**
+ * Writes a doctype node such as <!DOCTYPE data>.
+ *
+ * @method doctype
+ * @param {String} text String to write out inside the doctype.
+ */
+ doctype: function(text) {
+ this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : '');
},
-
-
/**
- * cleanTableWidths.
+ * Resets the internal buffer if one wants to reuse the writer.
*
- * 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..
+ * @method reset
+ */
+ reset: function() {
+ this.html.length = 0;
+ this.state = [];
+ this.pushState({
+ indentstr : '',
+ in_pre : false,
+ in_inline : false
+ })
+ },
+ /**
+ * Returns the contents that got serialized.
*
+ * @method getContent
+ * @return {String} HTML contents that got written down.
*/
- cleanTableWidths : function(node)
+ getContent: function() {
+ return this.html.join('').replace(/\n$/, '');
+ },
+
+ pushState : function(cfg)
{
-
-
- if (!node) {
- this.cleanTableWidths(this.doc.body);
- return;
+ this.state.push(cfg);
+ Roo.apply(this, cfg);
+ },
+
+ popState : function()
+ {
+ if (this.state.length < 1) {
+ return; // nothing to push
}
-
- // ignore list...
- if (node.nodeName == "#text" || node.nodeName == "#comment") {
- return;
+ var cfg = {
+ in_pre: false,
+ indentstr : ''
+ };
+ this.state.pop();
+ if (this.state.length > 0) {
+ cfg = this.state[this.state.length-1];
}
- Roo.log(node.tagName);
- if (!node.tagName.toLowerCase().match(/^(table|td|tr)$/)) {
- this.iterateChildren(node, this.cleanTableWidths);
+ Roo.apply(this, cfg);
+ },
+
+ addLine: function()
+ {
+ if (this.html.length < 1) {
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);
-
- },
-
-
+ var value = this.html[this.html.length - 1];
+ if (value.length > 0 && '\n' !== value) {
+ this.html.push('\n');
+ }
+ }
- domToHTML : function(currentElement, depth, nopadtext) {
-
- depth = depth || 0;
- nopadtext = nopadtext || false;
+//'pre script noscript style textarea video audio iframe object code'
+// shortended... 'area base basefont br col frame hr img input isindex link meta param embed source wbr track');
+// inline
+};
+
+Roo.htmleditor.TidyWriter.inline_elements = [
+ 'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR',
+ 'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A'
+];
+Roo.htmleditor.TidyWriter.shortend_elements = [
+ 'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT',
+ 'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK'
+];
+
+Roo.htmleditor.TidyWriter.whitespace_elements = [
+ 'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE'
+];/***
+ * This is based loosely on tinymce
+ * @class Roo.htmleditor.TidyEntities
+ * @static
+ * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
+ *
+ * Not 100% sure this is actually used or needed.
+ */
+
+Roo.htmleditor.TidyEntities = {
- 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
+ /**
+ * initialize data..
+ */
+ init : function (){
+
+ this.namedEntities = this.buildEntitiesLookup(this.namedEntitiesData, 32);
+
+ },
+
+
+ buildEntitiesLookup: function(items, radix) {
+ var i, chr, entity, lookup = {};
+ if (!items) {
+ return {};
+ }
+ items = typeof(items) == 'string' ? items.split(',') : items;
+ radix = radix || 10;
+ // Build entities lookup table
+ for (i = 0; i < items.length; i += 2) {
+ chr = String.fromCharCode(parseInt(items[i], radix));
+ // Only add non base entities
+ if (!this.baseEntities[chr]) {
+ entity = '&' + items[i + 1] + ';';
+ lookup[chr] = entity;
+ lookup[entity] = chr;
+ }
+ }
+ return lookup;
+
+ },
+
+ asciiMap : {
+ 128: '€',
+ 130: '‚',
+ 131: 'ƒ',
+ 132: '„',
+ 133: '…',
+ 134: '†',
+ 135: '‡',
+ 136: 'ˆ',
+ 137: '‰',
+ 138: 'Š',
+ 139: '‹',
+ 140: 'Œ',
+ 142: 'Ž',
+ 145: '‘',
+ 146: '’',
+ 147: '“',
+ 148: '”',
+ 149: '•',
+ 150: '–',
+ 151: '—',
+ 152: '˜',
+ 153: '™',
+ 154: 'š',
+ 155: '›',
+ 156: 'œ',
+ 158: 'ž',
+ 159: 'Ÿ'
+ },
+ // Raw entities
+ baseEntities : {
+ '"': '"',
+ // Needs to be escaped since the YUI compressor would otherwise break the code
+ '\'': ''',
+ '<': '<',
+ '>': '>',
+ '&': '&',
+ '`': '`'
+ },
+ // Reverse lookup table for raw entities
+ reverseEntities : {
+ '<': '<',
+ '>': '>',
+ '&': '&',
+ '"': '"',
+ ''': '\''
+ },
+
+ attrsCharsRegExp : /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+ textCharsRegExp : /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+ rawCharsRegExp : /[<>&\"\']/g,
+ entityRegExp : /&#([a-z0-9]+);?|&([a-z0-9]+);/gi,
+ namedEntities : false,
+ namedEntitiesData : [
+ '50',
+ 'nbsp',
+ '51',
+ 'iexcl',
+ '52',
+ 'cent',
+ '53',
+ 'pound',
+ '54',
+ 'curren',
+ '55',
+ 'yen',
+ '56',
+ 'brvbar',
+ '57',
+ 'sect',
+ '58',
+ 'uml',
+ '59',
+ 'copy',
+ '5a',
+ 'ordf',
+ '5b',
+ 'laquo',
+ '5c',
+ 'not',
+ '5d',
+ 'shy',
+ '5e',
+ 'reg',
+ '5f',
+ 'macr',
+ '5g',
+ 'deg',
+ '5h',
+ 'plusmn',
+ '5i',
+ 'sup2',
+ '5j',
+ 'sup3',
+ '5k',
+ 'acute',
+ '5l',
+ 'micro',
+ '5m',
+ 'para',
+ '5n',
+ 'middot',
+ '5o',
+ 'cedil',
+ '5p',
+ 'sup1',
+ '5q',
+ 'ordm',
+ '5r',
+ 'raquo',
+ '5s',
+ 'frac14',
+ '5t',
+ 'frac12',
+ '5u',
+ 'frac34',
+ '5v',
+ 'iquest',
+ '60',
+ 'Agrave',
+ '61',
+ 'Aacute',
+ '62',
+ 'Acirc',
+ '63',
+ 'Atilde',
+ '64',
+ 'Auml',
+ '65',
+ 'Aring',
+ '66',
+ 'AElig',
+ '67',
+ 'Ccedil',
+ '68',
+ 'Egrave',
+ '69',
+ 'Eacute',
+ '6a',
+ 'Ecirc',
+ '6b',
+ 'Euml',
+ '6c',
+ 'Igrave',
+ '6d',
+ 'Iacute',
+ '6e',
+ 'Icirc',
+ '6f',
+ 'Iuml',
+ '6g',
+ 'ETH',
+ '6h',
+ 'Ntilde',
+ '6i',
+ 'Ograve',
+ '6j',
+ 'Oacute',
+ '6k',
+ 'Ocirc',
+ '6l',
+ 'Otilde',
+ '6m',
+ 'Ouml',
+ '6n',
+ 'times',
+ '6o',
+ 'Oslash',
+ '6p',
+ 'Ugrave',
+ '6q',
+ 'Uacute',
+ '6r',
+ 'Ucirc',
+ '6s',
+ 'Uuml',
+ '6t',
+ 'Yacute',
+ '6u',
+ 'THORN',
+ '6v',
+ 'szlig',
+ '70',
+ 'agrave',
+ '71',
+ 'aacute',
+ '72',
+ 'acirc',
+ '73',
+ 'atilde',
+ '74',
+ 'auml',
+ '75',
+ 'aring',
+ '76',
+ 'aelig',
+ '77',
+ 'ccedil',
+ '78',
+ 'egrave',
+ '79',
+ 'eacute',
+ '7a',
+ 'ecirc',
+ '7b',
+ 'euml',
+ '7c',
+ 'igrave',
+ '7d',
+ 'iacute',
+ '7e',
+ 'icirc',
+ '7f',
+ 'iuml',
+ '7g',
+ 'eth',
+ '7h',
+ 'ntilde',
+ '7i',
+ 'ograve',
+ '7j',
+ 'oacute',
+ '7k',
+ 'ocirc',
+ '7l',
+ 'otilde',
+ '7m',
+ 'ouml',
+ '7n',
+ 'divide',
+ '7o',
+ 'oslash',
+ '7p',
+ 'ugrave',
+ '7q',
+ 'uacute',
+ '7r',
+ 'ucirc',
+ '7s',
+ 'uuml',
+ '7t',
+ 'yacute',
+ '7u',
+ 'thorn',
+ '7v',
+ 'yuml',
+ 'ci',
+ 'fnof',
+ 'sh',
+ 'Alpha',
+ 'si',
+ 'Beta',
+ 'sj',
+ 'Gamma',
+ 'sk',
+ 'Delta',
+ 'sl',
+ 'Epsilon',
+ 'sm',
+ 'Zeta',
+ 'sn',
+ 'Eta',
+ 'so',
+ 'Theta',
+ 'sp',
+ 'Iota',
+ 'sq',
+ 'Kappa',
+ 'sr',
+ 'Lambda',
+ 'ss',
+ 'Mu',
+ 'st',
+ 'Nu',
+ 'su',
+ 'Xi',
+ 'sv',
+ 'Omicron',
+ 't0',
+ 'Pi',
+ 't1',
+ 'Rho',
+ 't3',
+ 'Sigma',
+ 't4',
+ 'Tau',
+ 't5',
+ 'Upsilon',
+ 't6',
+ 'Phi',
+ 't7',
+ 'Chi',
+ 't8',
+ 'Psi',
+ 't9',
+ 'Omega',
+ 'th',
+ 'alpha',
+ 'ti',
+ 'beta',
+ 'tj',
+ 'gamma',
+ 'tk',
+ 'delta',
+ 'tl',
+ 'epsilon',
+ 'tm',
+ 'zeta',
+ 'tn',
+ 'eta',
+ 'to',
+ 'theta',
+ 'tp',
+ 'iota',
+ 'tq',
+ 'kappa',
+ 'tr',
+ 'lambda',
+ 'ts',
+ 'mu',
+ 'tt',
+ 'nu',
+ 'tu',
+ 'xi',
+ 'tv',
+ 'omicron',
+ 'u0',
+ 'pi',
+ 'u1',
+ 'rho',
+ 'u2',
+ 'sigmaf',
+ 'u3',
+ 'sigma',
+ 'u4',
+ 'tau',
+ 'u5',
+ 'upsilon',
+ 'u6',
+ 'phi',
+ 'u7',
+ 'chi',
+ 'u8',
+ 'psi',
+ 'u9',
+ 'omega',
+ 'uh',
+ 'thetasym',
+ 'ui',
+ 'upsih',
+ 'um',
+ 'piv',
+ '812',
+ 'bull',
+ '816',
+ 'hellip',
+ '81i',
+ 'prime',
+ '81j',
+ 'Prime',
+ '81u',
+ 'oline',
+ '824',
+ 'frasl',
+ '88o',
+ 'weierp',
+ '88h',
+ 'image',
+ '88s',
+ 'real',
+ '892',
+ 'trade',
+ '89l',
+ 'alefsym',
+ '8cg',
+ 'larr',
+ '8ch',
+ 'uarr',
+ '8ci',
+ 'rarr',
+ '8cj',
+ 'darr',
+ '8ck',
+ 'harr',
+ '8dl',
+ 'crarr',
+ '8eg',
+ 'lArr',
+ '8eh',
+ 'uArr',
+ '8ei',
+ 'rArr',
+ '8ej',
+ 'dArr',
+ '8ek',
+ 'hArr',
+ '8g0',
+ 'forall',
+ '8g2',
+ 'part',
+ '8g3',
+ 'exist',
+ '8g5',
+ 'empty',
+ '8g7',
+ 'nabla',
+ '8g8',
+ 'isin',
+ '8g9',
+ 'notin',
+ '8gb',
+ 'ni',
+ '8gf',
+ 'prod',
+ '8gh',
+ 'sum',
+ '8gi',
+ 'minus',
+ '8gn',
+ 'lowast',
+ '8gq',
+ 'radic',
+ '8gt',
+ 'prop',
+ '8gu',
+ 'infin',
+ '8h0',
+ 'ang',
+ '8h7',
+ 'and',
+ '8h8',
+ 'or',
+ '8h9',
+ 'cap',
+ '8ha',
+ 'cup',
+ '8hb',
+ 'int',
+ '8hk',
+ 'there4',
+ '8hs',
+ 'sim',
+ '8i5',
+ 'cong',
+ '8i8',
+ 'asymp',
+ '8j0',
+ 'ne',
+ '8j1',
+ 'equiv',
+ '8j4',
+ 'le',
+ '8j5',
+ 'ge',
+ '8k2',
+ 'sub',
+ '8k3',
+ 'sup',
+ '8k4',
+ 'nsub',
+ '8k6',
+ 'sube',
+ '8k7',
+ 'supe',
+ '8kl',
+ 'oplus',
+ '8kn',
+ 'otimes',
+ '8l5',
+ 'perp',
+ '8m5',
+ 'sdot',
+ '8o8',
+ 'lceil',
+ '8o9',
+ 'rceil',
+ '8oa',
+ 'lfloor',
+ '8ob',
+ 'rfloor',
+ '8p9',
+ 'lang',
+ '8pa',
+ 'rang',
+ '9ea',
+ 'loz',
+ '9j0',
+ 'spades',
+ '9j3',
+ 'clubs',
+ '9j5',
+ 'hearts',
+ '9j6',
+ 'diams',
+ 'ai',
+ 'OElig',
+ 'aj',
+ 'oelig',
+ 'b0',
+ 'Scaron',
+ 'b1',
+ 'scaron',
+ 'bo',
+ 'Yuml',
+ 'm6',
+ 'circ',
+ 'ms',
+ 'tilde',
+ '802',
+ 'ensp',
+ '803',
+ 'emsp',
+ '809',
+ 'thinsp',
+ '80c',
+ 'zwnj',
+ '80d',
+ 'zwj',
+ '80e',
+ 'lrm',
+ '80f',
+ 'rlm',
+ '80j',
+ 'ndash',
+ '80k',
+ 'mdash',
+ '80o',
+ 'lsquo',
+ '80p',
+ 'rsquo',
+ '80q',
+ 'sbquo',
+ '80s',
+ 'ldquo',
+ '80t',
+ 'rdquo',
+ '80u',
+ 'bdquo',
+ '810',
+ 'dagger',
+ '811',
+ 'Dagger',
+ '81g',
+ 'permil',
+ '81p',
+ 'lsaquo',
+ '81q',
+ 'rsaquo',
+ '85c',
+ 'euro'
+ ],
+
+
+ /**
+ * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded.
+ *
+ * @method encodeRaw
+ * @param {String} text Text to encode.
+ * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
+ * @return {String} Entity encoded text.
+ */
+ encodeRaw: function(text, attr)
+ {
+ var t = this;
+ return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
+ return t.baseEntities[chr] || chr;
+ });
+ },
+ /**
+ * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
+ * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
+ * and is exposed as the DOMUtils.encode function.
+ *
+ * @method encodeAllRaw
+ * @param {String} text Text to encode.
+ * @return {String} Entity encoded text.
+ */
+ encodeAllRaw: function(text) {
+ var t = this;
+ return ('' + text).replace(this.rawCharsRegExp, function(chr) {
+ return t.baseEntities[chr] || chr;
+ });
+ },
+ /**
+ * Encodes the specified string using numeric entities. The core entities will be
+ * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
+ *
+ * @method encodeNumeric
+ * @param {String} text Text to encode.
+ * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
+ * @return {String} Entity encoded text.
+ */
+ encodeNumeric: function(text, attr) {
+ var t = this;
+ return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
+ // Multi byte sequence convert it to a single entity
+ if (chr.length > 1) {
+ return '&#' + (1024 * (chr.charCodeAt(0) - 55296) + (chr.charCodeAt(1) - 56320) + 65536) + ';';
}
- } else {
- tagName = false;
+ return t.baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
+ });
+ },
+ /**
+ * Encodes the specified string using named entities. The core entities will be encoded
+ * as named ones but all non lower ascii characters will be encoded into named entities.
+ *
+ * @method encodeNamed
+ * @param {String} text Text to encode.
+ * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
+ * @param {Object} entities Optional parameter with entities to use.
+ * @return {String} Entity encoded text.
+ */
+ encodeNamed: function(text, attr, entities) {
+ var t = this;
+ entities = entities || this.namedEntities;
+ return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
+ return t.baseEntities[chr] || entities[chr] || chr;
+ });
+ },
+ /**
+ * Returns an encode function based on the name(s) and it's optional entities.
+ *
+ * @method getEncodeFunc
+ * @param {String} name Comma separated list of encoders for example named,numeric.
+ * @param {String} entities Optional parameter with entities to use instead of the built in set.
+ * @return {function} Encode function to be used.
+ */
+ getEncodeFunc: function(name, entities) {
+ entities = this.buildEntitiesLookup(entities) || this.namedEntities;
+ var t = this;
+ function encodeNamedAndNumeric(text, attr) {
+ return text.replace(attr ? t.attrsCharsRegExp : t.textCharsRegExp, function(chr) {
+ return t.baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
+ });
}
- if (['IMG', 'BR', 'HR', 'INPUT'].indexOf(tagName) > -1) {
- return ret;
+
+ function encodeCustomNamed(text, attr) {
+ return t.encodeNamed(text, attr, entities);
}
- if (['PRE', 'TEXTAREA', 'TD', 'A', 'SPAN'].indexOf(tagName) > -1) { // or code?
- nopadtext = true;
+ // Replace + with , to be compatible with previous TinyMCE versions
+ name = this.makeMap(name.replace(/\+/g, ','));
+ // Named and numeric encoder
+ if (name.named && name.numeric) {
+ return this.encodeNamedAndNumeric;
}
-
-
- // 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;
+ // Named encoder
+ if (name.named) {
+ // Custom names
+ if (entities) {
+ return encodeCustomNamed;
}
- // 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( " " );
+ return this.encodeNamed;
+ }
+ // Numeric
+ if (name.numeric) {
+ return this.encodeNumeric;
+ }
+ // Raw encoder
+ return this.encodeRaw;
+ },
+ /**
+ * Decodes the specified string, this will replace entities with raw UTF characters.
+ *
+ * @method decode
+ * @param {String} text Text to entity decode.
+ * @return {String} Entity decoded string.
+ */
+ decode: function(text)
+ {
+ var t = this;
+ return text.replace(this.entityRegExp, function(all, numeric) {
+ if (numeric) {
+ numeric = 'x' === numeric.charAt(0).toLowerCase() ? parseInt(numeric.substr(1), 16) : parseInt(numeric, 10);
+ // Support upper UTF
+ if (numeric > 65535) {
+ numeric -= 65536;
+ return String.fromCharCode(55296 + (numeric >> 10), 56320 + (1023 & numeric));
}
- innerHTML += toadd;
-
- i++;
- currentElementChild = currentElement.childNodes.item(i);
- lastNode = '';
- continue;
+ return t.asciiMap[numeric] || String.fromCharCode(numeric);
+ }
+ return t.reverseEntities[all] || t.namedEntities[all] || t.nativeDecode(all);
+ });
+ },
+ nativeDecode : function (text) {
+ return text;
+ },
+ makeMap : function (items, delim, map) {
+ var i;
+ items = items || [];
+ delim = delim || ',';
+ if (typeof items == "string") {
+ items = items.split(delim);
+ }
+ map = map || {};
+ i = items.length;
+ while (i--) {
+ map[items[i]] = {};
+ }
+ return map;
+ }
+};
+
+
+
+Roo.htmleditor.TidyEntities.init();
+/**
+ * @class Roo.htmleditor.KeyEnter
+ * Handle Enter press..
+ * @cfg {Roo.HtmlEditorCore} core the editor.
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+
+
+
+
+Roo.htmleditor.KeyEnter = function(cfg) {
+ Roo.apply(this, cfg);
+ // this does not actually call walk as it's really just a abstract class
+
+ Roo.get(this.core.doc.body).on('keypress', this.keypress, this);
+}
+
+//Roo.htmleditor.KeyEnter.i = 0;
+
+
+Roo.htmleditor.KeyEnter.prototype = {
+
+ core : false,
+
+ keypress : function(e)
+ {
+ if (e.charCode != 13 && e.charCode != 10) {
+ Roo.log([e.charCode,e]);
+ return true;
+ }
+ e.preventDefault();
+ // https://stackoverflow.com/questions/18552336/prevent-contenteditable-adding-div-on-enter-chrome
+ var doc = this.core.doc;
+ //add a new line
+
+
+ var sel = this.core.getSelection();
+ var range = sel.getRangeAt(0);
+ var n = range.commonAncestorContainer;
+ var pc = range.closest([ 'ol', 'ul']);
+ var pli = range.closest('li');
+ if (!pc || e.ctrlKey) {
+ // on it list, or ctrl pressed.
+ if (!e.ctrlKey) {
+ sel.insertNode('br', 'after');
+ } else {
+ // only do this if we have ctrl key..
+ var br = doc.createElement('br');
+ br.className = 'clear';
+ br.setAttribute('style', 'clear: both');
+ sel.insertNode(br, 'after');
}
- 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);
+
+ this.core.undoManager.addEvent();
+ this.core.fireEditorEvent(e);
+ return false;
}
- ret += innerHTML;
-
- if (!allText) {
- // The remaining code is mostly for formatting the tree
- ret+= nopadtext ? '' : "\n" + (new Array( depth )).join( " " );
+ // deal with <li> insetion
+ if (pli.innerText.trim() == '' &&
+ pli.previousSibling &&
+ pli.previousSibling.nodeName == 'LI' &&
+ pli.previousSibling.innerText.trim() == '') {
+ pli.parentNode.removeChild(pli.previousSibling);
+ sel.cursorAfter(pc);
+ this.core.undoManager.addEvent();
+ this.core.fireEditorEvent(e);
+ return false;
+ }
+
+ var li = doc.createElement('LI');
+ li.innerHTML = ' ';
+ if (!pli || !pli.firstSibling) {
+ pc.appendChild(li);
+ } else {
+ pli.parentNode.insertBefore(li, pli.firstSibling);
}
+ sel.cursorText (li.firstChild);
+
+ this.core.undoManager.addEvent();
+ this.core.fireEditorEvent(e);
+
+ return false;
+
+
- if (tagName) {
- ret+= "</"+tagName+">";
+
+ }
+};
+
+/**
+ * @class Roo.htmleditor.Block
+ * Base class for html editor blocks - do not use it directly .. extend it..
+ * @cfg {DomElement} node The node to apply stuff to.
+ * @cfg {String} friendly_name the name that appears in the context bar about this block
+ * @cfg {Object} Context menu - see Roo.form.HtmlEditor.ToolbarContext
+
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.Block = function(cfg)
+{
+ // do nothing .. should not be called really.
+}
+/**
+ * factory method to get the block from an element (using cache if necessary)
+ * @static
+ * @param {HtmlElement} the dom element
+ */
+Roo.htmleditor.Block.factory = function(node)
+{
+ var cc = Roo.htmleditor.Block.cache;
+ var id = Roo.get(node).id;
+ if (typeof(cc[id]) != 'undefined' && (!cc[id].node || cc[id].node.closest('body'))) {
+ Roo.htmleditor.Block.cache[id].readElement(node);
+ return Roo.htmleditor.Block.cache[id];
+ }
+ var db = node.getAttribute('data-block');
+ if (!db) {
+ db = node.nodeName.toLowerCase().toUpperCaseFirst();
+ }
+ var cls = Roo.htmleditor['Block' + db];
+ if (typeof(cls) == 'undefined') {
+ //Roo.log(node.getAttribute('data-block'));
+ Roo.log("OOps missing block : " + 'Block' + db);
+ return false;
+ }
+ Roo.htmleditor.Block.cache[id] = new cls({ node: node });
+ return Roo.htmleditor.Block.cache[id]; /// should trigger update element
+};
+
+/**
+ * initalize all Elements from content that are 'blockable'
+ * @static
+ * @param the body element
+ */
+Roo.htmleditor.Block.initAll = function(body, type)
+{
+ if (typeof(type) == 'undefined') {
+ var ia = Roo.htmleditor.Block.initAll;
+ ia(body,'table');
+ ia(body,'td');
+ ia(body,'figure');
+ return;
+ }
+ Roo.each(Roo.get(body).query(type), function(e) {
+ Roo.htmleditor.Block.factory(e);
+ },this);
+};
+// question goes here... do we need to clear out this cache sometimes?
+// or show we make it relivant to the htmleditor.
+Roo.htmleditor.Block.cache = {};
+
+Roo.htmleditor.Block.prototype = {
+
+ node : false,
+
+ // used by context menu
+ friendly_name : 'Based Block',
+
+ // text for button to delete this element
+ deleteTitle : false,
+
+ context : false,
+ /**
+ * Update a node with values from this object
+ * @param {DomElement} node
+ */
+ updateElement : function(node)
+ {
+ Roo.DomHelper.update(node === undefined ? this.node : node, this.toObject());
+ },
+ /**
+ * convert to plain HTML for calling insertAtCursor..
+ */
+ toHTML : function()
+ {
+ return Roo.DomHelper.markup(this.toObject());
+ },
+ /**
+ * used by readEleemnt to extract data from a node
+ * may need improving as it's pretty basic
+
+ * @param {DomElement} node
+ * @param {String} tag - tag to find, eg. IMG ?? might be better to use DomQuery ?
+ * @param {String} attribute (use html - for contents, style for using next param as style, or false to return the node)
+ * @param {String} style the style property - eg. text-align
+ */
+ getVal : function(node, tag, attr, style)
+ {
+ var n = node;
+ if (tag !== true && n.tagName != tag.toUpperCase()) {
+ // in theory we could do figure[3] << 3rd figure? or some more complex search..?
+ // but kiss for now.
+ n = node.getElementsByTagName(tag).item(0);
+ }
+ if (!n) {
+ return '';
+ }
+ if (attr === false) {
+ return n;
+ }
+ if (attr == 'html') {
+ return n.innerHTML;
+ }
+ if (attr == 'style') {
+ return n.style[style];
}
- return ret;
+ return n.hasAttribute(attr) ? n.getAttribute(attr) : '';
+
},
+ /**
+ * create a DomHelper friendly object - for use with
+ * Roo.DomHelper.markup / overwrite / etc..
+ * (override this)
+ */
+ toObject : function()
+ {
+ return {};
+ },
+ /**
+ * Read a node that has a 'data-block' property - and extract the values from it.
+ * @param {DomElement} node - the node
+ */
+ readElement : function(node)
+ {
- applyBlacklists : function()
+ }
+
+
+};
+
+
+
+/**
+ * @class Roo.htmleditor.BlockFigure
+ * Block that has an image and a figcaption
+ * @cfg {String} image_src the url for the image
+ * @cfg {String} align (left|right) alignment for the block default left
+ * @cfg {String} caption the text to appear below (and in the alt tag)
+ * @cfg {String} caption_display (block|none) display or not the caption
+ * @cfg {String|number} image_width the width of the image number or %?
+ * @cfg {String|number} image_height the height of the image number or %?
+ *
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.BlockFigure = function(cfg)
+{
+ if (cfg.node) {
+ this.readElement(cfg.node);
+ this.updateElement(cfg.node);
+ }
+ Roo.apply(this, cfg);
+}
+Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, {
+
+
+ // setable values.
+ image_src: '',
+ align: 'center',
+ caption : '',
+ caption_display : 'block',
+ width : '100%',
+ cls : '',
+ href: '',
+ video_url : '',
+
+ // margin: '2%', not used
+
+ text_align: 'left', // (left|right) alignment for the text caption default left. - not used at present
+
+
+ // used by context menu
+ friendly_name : 'Image with caption',
+ deleteTitle : "Delete Image and Caption",
+
+ contextMenu : function(toolbar)
{
- var 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.white = [];
- this.black = [];
- Roo.each(Roo.HtmlEditorCore.white, function(tag) {
- if (b.indexOf(tag) > -1) {
- return;
- }
- this.white.push(tag);
-
- }, this);
+ var block = function() {
+ return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
+ };
- Roo.each(w, function(tag) {
- if (b.indexOf(tag) > -1) {
- return;
- }
- if (this.white.indexOf(tag) > -1) {
- return;
- }
- this.white.push(tag);
-
- }, this);
+ var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
- Roo.each(Roo.HtmlEditorCore.black, function(tag) {
- if (w.indexOf(tag) > -1) {
- return;
- }
- this.black.push(tag);
-
- }, this);
+ var syncValue = toolbar.editorcore.syncValue;
- Roo.each(b, function(tag) {
- if (w.indexOf(tag) > -1) {
- return;
+ var fields = {};
+
+ return [
+ {
+ xtype : 'TextItem',
+ text : "Source: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'Button',
+ text: 'Change Image URL',
+
+ listeners : {
+ click: function (btn, state)
+ {
+ var b = block();
+
+ Roo.MessageBox.show({
+ title : "Image Source URL",
+ msg : "Enter the url for the image",
+ buttons: Roo.MessageBox.OKCANCEL,
+ fn: function(btn, val){
+ if (btn != 'ok') {
+ return;
+ }
+ b.image_src = val;
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ },
+ minWidth:250,
+ prompt:true,
+ //multiline: multiline,
+ modal : true,
+ value : b.image_src
+ });
+ }
+ },
+ xns : rooui.Toolbar
+ },
+
+ {
+ xtype : 'Button',
+ text: 'Change Link URL',
+
+ listeners : {
+ click: function (btn, state)
+ {
+ var b = block();
+
+ Roo.MessageBox.show({
+ title : "Link URL",
+ msg : "Enter the url for the link - leave blank to have no link",
+ buttons: Roo.MessageBox.OKCANCEL,
+ fn: function(btn, val){
+ if (btn != 'ok') {
+ return;
+ }
+ b.href = val;
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ },
+ minWidth:250,
+ prompt:true,
+ //multiline: multiline,
+ modal : true,
+ value : b.href
+ });
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ {
+ xtype : 'Button',
+ text: 'Show Video URL',
+
+ listeners : {
+ click: function (btn, state)
+ {
+ Roo.MessageBox.alert("Video URL",
+ block().video_url == '' ? 'This image is not linked ot a video' :
+ 'The image is linked to: <a target="_new" href="' + block().video_url + '">' + block().video_url + '</a>');
+ }
+ },
+ xns : rooui.Toolbar
+ },
+
+
+ {
+ xtype : 'TextItem',
+ text : "Width: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'ComboBox',
+ allowBlank : false,
+ displayField : 'val',
+ editable : true,
+ listWidth : 100,
+ triggerAction : 'all',
+ typeAhead : true,
+ valueField : 'val',
+ width : 70,
+ name : 'width',
+ listeners : {
+ select : function (combo, r, index)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ var b = block();
+ b.width = r.get('val');
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.form,
+ store : {
+ xtype : 'SimpleStore',
+ data : [
+ ['100%'],
+ ['80%'],
+ ['50%'],
+ ['20%'],
+ ['10%']
+ ],
+ fields : [ 'val'],
+ xns : Roo.data
+ }
+ },
+ {
+ xtype : 'TextItem',
+ text : "Align: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'ComboBox',
+ allowBlank : false,
+ displayField : 'val',
+ editable : true,
+ listWidth : 100,
+ triggerAction : 'all',
+ typeAhead : true,
+ valueField : 'val',
+ width : 70,
+ name : 'align',
+ listeners : {
+ select : function (combo, r, index)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ var b = block();
+ b.align = r.get('val');
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.form,
+ store : {
+ xtype : 'SimpleStore',
+ data : [
+ ['left'],
+ ['right'],
+ ['center']
+ ],
+ fields : [ 'val'],
+ xns : Roo.data
+ }
+ },
+
+
+ {
+ xtype : 'Button',
+ text: 'Hide Caption',
+ name : 'caption_display',
+ pressed : false,
+ enableToggle : true,
+ setValue : function(v) {
+ // this trigger toggle.
+
+ this.setText(v ? "Hide Caption" : "Show Caption");
+ this.setPressed(v != 'block');
+ },
+ listeners : {
+ toggle: function (btn, state)
+ {
+ var b = block();
+ b.caption_display = b.caption_display == 'block' ? 'none' : 'block';
+ this.setText(b.caption_display == 'block' ? "Hide Caption" : "Show Caption");
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
}
- if (this.black.indexOf(tag) > -1) {
- return;
+ ];
+
+ },
+ /**
+ * create a DomHelper friendly object - for use with
+ * Roo.DomHelper.markup / overwrite / etc..
+ */
+ toObject : function()
+ {
+ var d = document.createElement('div');
+ d.innerHTML = this.caption;
+
+ var m = this.width != '100%' && this.align == 'center' ? '0 auto' : 0;
+
+ var iw = this.align == 'center' ? this.width : '100%';
+ var img = {
+ tag : 'img',
+ contenteditable : 'false',
+ src : this.image_src,
+ alt : d.innerText.replace(/\n/g, " ").replace(/\s+/g, ' ').trim(), // removeHTML and reduce spaces..
+ style: {
+ width : iw,
+ maxWidth : iw + ' !important', // this is not getting rendered?
+ margin : m
+
}
- this.black.push(tag);
+ };
+ /*
+ '<div class="{0}" width="420" height="315" src="{1}" frameborder="0" allowfullscreen>' +
+ '<a href="{2}">' +
+ '<img class="{0}-thumbnail" src="{3}/Images/{4}/{5}#image-{4}" />' +
+ '</a>' +
+ '</div>',
+ */
+
+ if (this.href.length > 0) {
+ img = {
+ tag : 'a',
+ href: this.href,
+ contenteditable : 'true',
+ cn : [
+ img
+ ]
+ };
+ }
+
+
+ if (this.video_url.length > 0) {
+ img = {
+ tag : 'div',
+ cls : this.cls,
+ frameborder : 0,
+ allowfullscreen : true,
+ width : 420, // these are for video tricks - that we replace the outer
+ height : 315,
+ src : this.video_url,
+ cn : [
+ img
+ ]
+ };
+ }
+ // we remove caption totally if its hidden... - will delete data.. but otherwise we end up with fake caption
+ var captionhtml = this.caption_display == 'none' ? '' : (this.caption.length ? this.caption : "Caption");
+
+
+ var ret = {
+ tag: 'figure',
+ 'data-block' : 'Figure',
+ 'data-width' : this.width,
+ contenteditable : 'false',
- }, this);
+ style : {
+ display: 'block',
+ float : this.align ,
+ maxWidth : this.align == 'center' ? '100% !important' : (this.width + ' !important'),
+ width : this.align == 'center' ? '100%' : this.width,
+ margin: '0px',
+ padding: this.align == 'center' ? '0' : '0 10px' ,
+ textAlign : this.align // seems to work for email..
+
+ },
+
+
+ align : this.align,
+ cn : [
+ img,
+
+ {
+ tag: 'figcaption',
+ 'data-display' : this.caption_display,
+ style : {
+ textAlign : 'left',
+ fontSize : '16px',
+ lineHeight : '24px',
+ display : this.caption_display,
+ maxWidth : (this.align == 'center' ? this.width : '100%' ) + ' !important',
+ margin: m,
+ width: this.align == 'center' ? this.width : '100%'
+
+
+ },
+ cls : this.cls.length > 0 ? (this.cls + '-thumbnail' ) : '',
+ cn : [
+ {
+ tag: 'div',
+ style : {
+ marginTop : '16px',
+ textAlign : 'left'
+ },
+ align: 'left',
+ cn : [
+ {
+ // we can not rely on yahoo syndication to use CSS elements - so have to use '<i>' to encase stuff.
+ tag : 'i',
+ contenteditable : true,
+ html : captionhtml
+ }
+
+ ]
+ }
+
+ ]
+
+ }
+ ]
+ };
+ return ret;
+
+ },
+
+ readElement : function(node)
+ {
+ // this should not really come from the link...
+ this.video_url = this.getVal(node, 'div', 'src');
+ this.cls = this.getVal(node, 'div', 'class');
+ this.href = this.getVal(node, 'a', 'href');
- w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
- b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
+ this.image_src = this.getVal(node, 'img', 'src');
+
+ this.align = this.getVal(node, 'figure', 'align');
+ var figcaption = this.getVal(node, 'figcaption', false);
+ if (figcaption !== '') {
+ this.caption = this.getVal(figcaption, 'i', 'html');
+ }
- this.cwhite = [];
- this.cblack = [];
- Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
- if (b.indexOf(tag) > -1) {
- return;
+
+ this.caption_display = this.getVal(node, 'figcaption', 'data-display');
+ //this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
+ this.width = this.getVal(node, true, 'data-width');
+ //this.margin = this.getVal(node, 'figure', 'style', 'margin');
+
+ },
+ removeNode : function()
+ {
+ return this.node;
+ }
+
+
+
+
+
+
+
+
+})
+
+
+
+/**
+ * @class Roo.htmleditor.BlockTable
+ * Block that manages a table
+ *
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
+ */
+
+Roo.htmleditor.BlockTable = function(cfg)
+{
+ if (cfg.node) {
+ this.readElement(cfg.node);
+ this.updateElement(cfg.node);
+ }
+ Roo.apply(this, cfg);
+ if (!cfg.node) {
+ this.rows = [];
+ for(var r = 0; r < this.no_row; r++) {
+ this.rows[r] = [];
+ for(var c = 0; c < this.no_col; c++) {
+ this.rows[r][c] = this.emptyCell();
}
- this.cwhite.push(tag);
+ }
+ }
+
+
+}
+Roo.extend(Roo.htmleditor.BlockTable, Roo.htmleditor.Block, {
+
+ rows : false,
+ no_col : 1,
+ no_row : 1,
+
+
+ width: '100%',
+
+ // used by context menu
+ friendly_name : 'Table',
+ deleteTitle : 'Delete Table',
+ // context menu is drawn once..
+
+ contextMenu : function(toolbar)
+ {
+
+ var block = function() {
+ return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
+ };
+
+
+ var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
+
+ var syncValue = toolbar.editorcore.syncValue;
+
+ var fields = {};
+
+ return [
+ {
+ xtype : 'TextItem',
+ text : "Width: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'ComboBox',
+ allowBlank : false,
+ displayField : 'val',
+ editable : true,
+ listWidth : 100,
+ triggerAction : 'all',
+ typeAhead : true,
+ valueField : 'val',
+ width : 100,
+ name : 'width',
+ listeners : {
+ select : function (combo, r, index)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ var b = block();
+ b.width = r.get('val');
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.form,
+ store : {
+ xtype : 'SimpleStore',
+ data : [
+ ['100%'],
+ ['auto']
+ ],
+ fields : [ 'val'],
+ xns : Roo.data
+ }
+ },
+ // -------- Cols
- }, this);
+ {
+ xtype : 'TextItem',
+ text : "Columns: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+
+ {
+ xtype : 'Button',
+ text: '-',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ block().removeColumn();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ {
+ xtype : 'Button',
+ text: '+',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ block().addColumn();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ // -------- ROWS
+ {
+ xtype : 'TextItem',
+ text : "Rows: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+
+ {
+ xtype : 'Button',
+ text: '-',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ block().removeRow();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ {
+ xtype : 'Button',
+ text: '+',
+ listeners : {
+ click : function (_self, e)
+ {
+ block().addRow();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ // -------- ROWS
+ {
+ xtype : 'Button',
+ text: 'Reset Column Widths',
+ listeners : {
+
+ click : function (_self, e)
+ {
+ block().resetWidths();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ }
+
+
+
+ ];
- Roo.each(w, function(tag) {
- if (b.indexOf(tag) > -1) {
- return;
- }
- if (this.cwhite.indexOf(tag) > -1) {
- return;
- }
- this.cwhite.push(tag);
+ },
+
+
+ /**
+ * create a DomHelper friendly object - for use with
+ * Roo.DomHelper.markup / overwrite / etc..
+ * ?? should it be called with option to hide all editing features?
+ */
+ toObject : function()
+ {
+
+ var ret = {
+ tag : 'table',
+ contenteditable : 'false', // this stops cell selection from picking the table.
+ 'data-block' : 'Table',
+ style : {
+ width: this.width,
+ border : 'solid 1px #000', // ??? hard coded?
+ 'border-collapse' : 'collapse'
+ },
+ cn : [
+ { tag : 'tbody' , cn : [] }
+ ]
+ };
+
+ // do we have a head = not really
+ var ncols = 0;
+ Roo.each(this.rows, function( row ) {
+ var tr = {
+ tag: 'tr',
+ style : {
+ margin: '6px',
+ border : 'solid 1px #000',
+ textAlign : 'left'
+ },
+ cn : [ ]
+ };
+
+ ret.cn[0].cn.push(tr);
+ // does the row have any properties? ?? height?
+ var nc = 0;
+ Roo.each(row, function( cell ) {
+
+ var td = {
+ tag : 'td',
+ contenteditable : 'true',
+ 'data-block' : 'Td',
+ html : cell.html,
+ style : cell.style
+ };
+ if (cell.colspan > 1) {
+ td.colspan = cell.colspan ;
+ nc += cell.colspan;
+ } else {
+ nc++;
+ }
+ if (cell.rowspan > 1) {
+ td.rowspan = cell.rowspan ;
+ }
+
+
+ // widths ?
+ tr.cn.push(td);
+
+
+ }, this);
+ ncols = Math.max(nc, ncols);
+
}, this);
+ // add the header row..
+ ncols++;
+
- Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
- if (w.indexOf(tag) > -1) {
- return;
- }
- this.cblack.push(tag);
+ return ret;
+
+ },
+
+ readElement : function(node)
+ {
+ node = node ? node : this.node ;
+ this.width = this.getVal(node, true, 'style', 'width') || '100%';
+
+ this.rows = [];
+ this.no_row = 0;
+ var trs = Array.from(node.rows);
+ trs.forEach(function(tr) {
+ var row = [];
+ this.rows.push(row);
- }, this);
+ this.no_row++;
+ var no_column = 0;
+ Array.from(tr.cells).forEach(function(td) {
+
+ var add = {
+ colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan')*1 : 1,
+ rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan')*1 : 1,
+ style : td.hasAttribute('style') ? td.getAttribute('style') : '',
+ html : td.innerHTML
+ };
+ no_column += add.colspan;
+
+
+ row.push(add);
+
+
+ },this);
+ this.no_col = Math.max(this.no_col, no_column);
+
+
+ },this);
- Roo.each(b, function(tag) {
- if (w.indexOf(tag) > -1) {
+
+ },
+ normalizeRows: function()
+ {
+ var ret= [];
+ var rid = -1;
+ this.rows.forEach(function(row) {
+ rid++;
+ ret[rid] = [];
+ row = this.normalizeRow(row);
+ var cid = 0;
+ row.forEach(function(c) {
+ while (typeof(ret[rid][cid]) != 'undefined') {
+ cid++;
+ }
+ if (typeof(ret[rid]) == 'undefined') {
+ ret[rid] = [];
+ }
+ ret[rid][cid] = c;
+ c.row = rid;
+ c.col = cid;
+ if (c.rowspan < 2) {
+ return;
+ }
+
+ for(var i = 1 ;i < c.rowspan; i++) {
+ if (typeof(ret[rid+i]) == 'undefined') {
+ ret[rid+i] = [];
+ }
+ ret[rid+i][cid] = c;
+ }
+ });
+ }, this);
+ return ret;
+
+ },
+
+ normalizeRow: function(row)
+ {
+ var ret= [];
+ row.forEach(function(c) {
+ if (c.colspan < 2) {
+ ret.push(c);
return;
}
- if (this.cblack.indexOf(tag) > -1) {
- return;
+ for(var i =0 ;i < c.colspan; i++) {
+ ret.push(c);
}
- this.cblack.push(tag);
-
- }, this);
+ });
+ return ret;
+
},
- setStylesheets : function(stylesheets)
+ deleteColumn : function(sel)
{
- if(typeof(stylesheets) == 'string'){
- Roo.get(this.iframe.contentDocument.head).createChild({
- tag : 'link',
- rel : 'stylesheet',
- type : 'text/css',
- href : stylesheets
- });
-
+ if (!sel || sel.type != 'col') {
return;
}
- var _this = this;
-
- Roo.each(stylesheets, function(s) {
- if(!s.length){
- return;
+ if (this.no_col < 2) {
+ return;
+ }
+
+ this.rows.forEach(function(row) {
+ var cols = this.normalizeRow(row);
+ var col = cols[sel.col];
+ if (col.colspan > 1) {
+ col.colspan --;
+ } else {
+ row.remove(col);
}
- Roo.get(_this.iframe.contentDocument.head).createChild({
- tag : 'link',
- rel : 'stylesheet',
- type : 'text/css',
- href : s
- });
- });
-
+ }, this);
+ this.no_col--;
},
+ removeColumn : function()
+ {
+ this.deleteColumn({
+ type: 'col',
+ col : this.no_col-1
+ });
+ this.updateElement();
+ },
- removeStylesheets : function()
+
+ addColumn : function()
{
- var _this = this;
- Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
- s.remove();
- });
+ this.rows.forEach(function(row) {
+ row.push(this.emptyCell());
+
+ }, this);
+ this.updateElement();
},
- setStyle : function(style)
+ deleteRow : function(sel)
{
- Roo.get(this.iframe.contentDocument.head).createChild({
- tag : 'style',
- type : 'text/css',
- html : style
+ if (!sel || sel.type != 'row') {
+ return;
+ }
+
+ if (this.no_row < 2) {
+ return;
+ }
+
+ var rows = this.normalizeRows();
+
+
+ rows[sel.row].forEach(function(col) {
+ if (col.rowspan > 1) {
+ col.rowspan--;
+ } else {
+ col.remove = 1; // flage it as removed.
+ }
+
+ }, this);
+ var newrows = [];
+ this.rows.forEach(function(row) {
+ newrow = [];
+ row.forEach(function(c) {
+ if (typeof(c.remove) == 'undefined') {
+ newrow.push(c);
+ }
+
+ });
+ if (newrow.length > 0) {
+ newrows.push(row);
+ }
});
-
- return;
- }
+ this.rows = newrows;
+
+
+
+ this.no_row--;
+ this.updateElement();
+
+ },
+ removeRow : function()
+ {
+ this.deleteRow({
+ type: 'row',
+ row : this.no_row-1
+ });
+
+ },
- // hide stuff that is not compatible
- /**
- * @event blur
- * @hide
- */
- /**
- * @event change
- * @hide
- */
- /**
- * @event focus
- * @hide
- */
- /**
- * @event specialkey
- * @hide
- */
- /**
- * @cfg {String} fieldClass @hide
- */
- /**
- * @cfg {String} focusClass @hide
- */
- /**
- * @cfg {String} autoCreate @hide
- */
- /**
- * @cfg {String} inputType @hide
- */
- /**
- * @cfg {String} invalidClass @hide
- */
- /**
- * @cfg {String} invalidText @hide
- */
- /**
- * @cfg {String} msgFx @hide
- */
- /**
- * @cfg {String} validateOnBlur @hide
- */
-});
-
-Roo.HtmlEditorCore.white = [
- 'area', 'br', 'img', 'input', 'hr', 'wbr',
+
+ addRow : function()
+ {
- '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',
+ var row = [];
+ for (var i = 0; i < this.no_col; i++ ) {
+
+ row.push(this.emptyCell());
+
+ }
+ this.rows.push(row);
+ this.updateElement();
+
+ },
- 'dir', 'menu', 'ol', 'ul', 'dl',
-
- '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..
-];
-Roo.HtmlEditorCore.clean = [
- 'script', 'style', 'title', 'xml'
-];
-Roo.HtmlEditorCore.remove = [
- 'font'
-];
-// attributes..
-
-Roo.HtmlEditorCore.ablack = [
- 'on'
-];
-
-Roo.HtmlEditorCore.aclean = [
- 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
-];
-
-// protocols..
-Roo.HtmlEditorCore.pwhite= [
- 'http', 'https', 'mailto'
-];
-
-// white listed style attributes.
-Roo.HtmlEditorCore.cwhite= [
- // 'text-align', /// default is to allow most things..
-
-
-// 'font-size'//??
-];
-
-// black listed style attributes.
-Roo.HtmlEditorCore.cblack= [
- // 'font-size' -- this can be set by the project
-];
-
-
-Roo.HtmlEditorCore.swapCodes =[
- [ 8211, "--" ],
- [ 8212, "--" ],
- [ 8216, "'" ],
- [ 8217, "'" ],
- [ 8220, '"' ],
- [ 8221, '"' ],
- [ 8226, "*" ],
- [ 8230, "..." ]
-];
-
- //<script type="text/javascript">
-
-/*
- * Ext JS Library 1.1.1
- * Copyright(c) 2006-2007, Ext JS, LLC.
- * Licence LGPL
- *
- */
-
-
-Roo.form.HtmlEditor = function(config){
+ // the default cell object... at present...
+ emptyCell : function() {
+ return (new Roo.htmleditor.BlockTd({})).toObject();
+
+
+ },
+ removeNode : function()
+ {
+ return this.node;
+ },
- Roo.form.HtmlEditor.superclass.constructor.call(this, config);
- if (!this.toolbars) {
- this.toolbars = [];
+ resetWidths : function()
+ {
+ Array.from(this.node.getElementsByTagName('td')).forEach(function(n) {
+ var nn = Roo.htmleditor.Block.factory(n);
+ nn.width = '';
+ nn.updateElement(n);
+ });
}
- this.editorcore = new Roo.HtmlEditorCore(Roo.apply({ owner : this} , config));
-};
+
+
+})
/**
- * @class Roo.form.HtmlEditor
- * @extends Roo.form.Field
- * Provides a lightweight HTML Editor component.
*
- * This has been tested on Fireforx / Chrome.. IE may not be so great..
+ * editing a TD?
+ *
+ * since selections really work on the table cell, then editing really should work from there
+ *
+ * The original plan was to support merging etc... - but that may not be needed yet..
+ *
+ * So this simple version will support:
+ * add/remove cols
+ * adjust the width +/-
+ * reset the width...
+ *
+ *
+ */
+
+
+
+
+/**
+ * @class Roo.htmleditor.BlockTable
+ * Block that manages a table
*
- * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
- * supported by this editor.</b><br/><br/>
- * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
- * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
+ * @constructor
+ * Create a new Filter.
+ * @param {Object} config Configuration options
*/
-Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, {
- /**
- * @cfg {Boolean} clearUp
- */
- clearUp : true,
- /**
- * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
- */
- toolbars : false,
-
- /**
- * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
- * Roo.resizable.
- */
- resizable : false,
- /**
- * @cfg {Number} height (in pixels)
- */
- height: 300,
- /**
- * @cfg {Number} width (in pixels)
- */
- width: 500,
-
- /**
- * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
- *
- */
- stylesheets: false,
+
+Roo.htmleditor.BlockTd = function(cfg)
+{
+ if (cfg.node) {
+ this.readElement(cfg.node);
+ this.updateElement(cfg.node);
+ }
+ Roo.apply(this, cfg);
+
- /**
- * @cfg {Array} blacklist of css styles style attributes (blacklist overrides whitelist)
- *
- */
- cblack: false,
- /**
- * @cfg {Array} whitelist of css styles style attributes (blacklist overrides whitelist)
- *
- */
- cwhite: false,
+}
+Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, {
+
+ node : false,
- /**
- * @cfg {Array} blacklist of html tags - in addition to standard blacklist.
- *
- */
- black: false,
- /**
- * @cfg {Array} whitelist of html tags - in addition to statndard whitelist
- *
- */
- white: false,
+ width: '',
+ textAlign : 'left',
+ valign : 'top',
- // id of frame..
- frameId: false,
+ colspan : 1,
+ rowspan : 1,
- // private properties
- validationEvent : false,
- deferHeight: true,
- initialized : false,
- activated : false,
- onFocus : Roo.emptyFn,
- iframePad:3,
- hideMode:'offsets',
+ // used by context menu
+ friendly_name : 'Table Cell',
+ deleteTitle : false, // use our customer delete
- actionMode : 'container', // defaults to hiding it...
+ // context menu is drawn once..
- defaultAutoCreate : { // modified by initCompnoent..
- tag: "textarea",
- style:"width:500px;height:300px;",
- autocomplete: "new-password"
- },
-
- // private
- initComponent : function(){
- this.addEvents({
- /**
- * @event initialize
- * Fires when the editor is fully initialized (including the iframe)
- * @param {HtmlEditor} this
- */
- initialize: true,
- /**
- * @event activate
- * Fires when the editor is first receives the focus. Any insertion must wait
- * until after this event.
- * @param {HtmlEditor} this
- */
- activate: true,
- /**
- * @event beforesync
- * Fires before the textarea is updated with content from the editor iframe. Return false
- * to cancel the sync.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- beforesync: true,
- /**
- * @event beforepush
- * Fires before the iframe editor is updated with content from the textarea. Return false
- * to cancel the push.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- beforepush: true,
- /**
- * @event sync
- * Fires when the textarea is updated with content from the editor iframe.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- sync: true,
- /**
- * @event push
- * Fires when the iframe editor is updated with content from the textarea.
- * @param {HtmlEditor} this
- * @param {String} html
- */
- push: true,
- /**
- * @event editmodechange
- * Fires when the editor switches edit modes
- * @param {HtmlEditor} this
- * @param {Boolean} sourceEdit True if source edit, false if standard editing.
- */
- editmodechange: true,
- /**
- * @event editorevent
- * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
- * @param {HtmlEditor} this
- */
- editorevent: true,
- /**
- * @event firstfocus
- * Fires when on first focus - needed by toolbars..
- * @param {HtmlEditor} this
- */
- firstfocus: true,
- /**
- * @event autosave
- * Auto save the htmlEditor value as a file into Events
- * @param {HtmlEditor} this
- */
- autosave: true,
- /**
- * @event savedpreview
- * preview the saved version of htmlEditor
- * @param {HtmlEditor} this
- */
- savedpreview: true,
-
- /**
- * @event stylesheetsclick
- * Fires when press the Sytlesheets button
- * @param {Roo.HtmlEditorCore} this
- */
- stylesheetsclick: true
- });
- this.defaultAutoCreate = {
- tag: "textarea",
- style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
- autocomplete: "new-password"
+ contextMenu : function(toolbar)
+ {
+
+ var cell = function() {
+ return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
};
- },
-
- /**
- * Protected method that will not generally be called directly. It
- * is called when the editor creates its toolbar. Override this method if you need to
- * add custom toolbar buttons.
- * @param {HtmlEditor} editor
- */
- createToolbar : function(editor){
- Roo.log("create toolbars");
- if (!editor.toolbars || !editor.toolbars.length) {
- editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
- }
- for (var i =0 ; i < editor.toolbars.length;i++) {
- editor.toolbars[i] = Roo.factory(
- typeof(editor.toolbars[i]) == 'string' ?
- { xtype: editor.toolbars[i]} : editor.toolbars[i],
- Roo.form.HtmlEditor);
- editor.toolbars[i].init(editor);
+ var table = function() {
+ return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table'));
+ };
+
+ var lr = false;
+ var saveSel = function()
+ {
+ lr = toolbar.editorcore.getSelection().getRangeAt(0);
+ }
+ var restoreSel = function()
+ {
+ if (lr) {
+ (function() {
+ toolbar.editorcore.focus();
+ var cr = toolbar.editorcore.getSelection();
+ cr.removeAllRanges();
+ cr.addRange(lr);
+ toolbar.editorcore.onEditorEvent();
+ }).defer(10, this);
+
+
+ }
}
-
- },
-
-
- // private
- onRender : function(ct, position)
- {
- var _t = this;
- Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
+ var rooui = typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
- this.wrap = this.el.wrap({
- cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
- });
+ var syncValue = toolbar.editorcore.syncValue;
- this.editorcore.onRender(ct, position);
-
- if (this.resizable) {
- this.resizeEl = new Roo.Resizable(this.wrap, {
- pinned : true,
- wrap: true,
- dynamic : true,
- minHeight : this.height,
- height: this.height,
- handles : this.resizable,
- width: this.width,
+ var fields = {};
+
+ return [
+ {
+ xtype : 'Button',
+ text : 'Edit Table',
listeners : {
- resize : function(r, w, h) {
- _t.onResize(w,h); // -something
+ click : function() {
+ var t = toolbar.tb.selectedNode.closest('table');
+ toolbar.editorcore.selectNode(t);
+ toolbar.editorcore.onEditorEvent();
}
}
- });
-
- }
- this.createToolbar(this);
-
-
- if(!this.width){
- this.setSize(this.wrap.getSize());
- }
- if (this.resizeEl) {
- this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
- // should trigger onReize..
- }
-
- this.keyNav = new Roo.KeyNav(this.el, {
-
- "tab" : function(e){
- e.preventDefault();
-
- var value = this.getValue();
-
- var start = this.el.dom.selectionStart;
- var end = this.el.dom.selectionEnd;
-
- if(!e.shiftKey){
-
- this.setValue(value.substring(0, start) + "\t" + value.substring(end));
- this.el.dom.setSelectionRange(end + 1, end + 1);
- return;
- }
-
- var f = value.substring(0, start).split("\t");
-
- if(f.pop().length != 0){
- return;
- }
-
- this.setValue(f.join("\t") + value.substring(end));
- this.el.dom.setSelectionRange(start - 1, start - 1);
},
+
+
+
+ {
+ xtype : 'TextItem',
+ text : "Column Width: ",
+ xns : rooui.Toolbar
+
+ },
+ {
+ xtype : 'Button',
+ text: '-',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ cell().shrinkColumn();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ },
+ {
+ xtype : 'Button',
+ text: '+',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ cell().growColumn();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.Toolbar
+ },
- "home" : function(e){
- e.preventDefault();
-
- var curr = this.el.dom.selectionStart;
- var lines = this.getValue().split("\n");
-
- if(!lines.length){
- return;
- }
-
- if(e.ctrlKey){
- this.el.dom.setSelectionRange(0, 0);
- return;
+ {
+ xtype : 'TextItem',
+ text : "Vertical Align: ",
+ xns : rooui.Toolbar //Boostrap?
+ },
+ {
+ xtype : 'ComboBox',
+ allowBlank : false,
+ displayField : 'val',
+ editable : true,
+ listWidth : 100,
+ triggerAction : 'all',
+ typeAhead : true,
+ valueField : 'val',
+ width : 100,
+ name : 'valign',
+ listeners : {
+ select : function (combo, r, index)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ var b = cell();
+ b.valign = r.get('val');
+ b.updateElement();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.form,
+ store : {
+ xtype : 'SimpleStore',
+ data : [
+ ['top'],
+ ['middle'],
+ ['bottom'] // there are afew more...
+ ],
+ fields : [ 'val'],
+ xns : Roo.data
}
-
- var pos = 0;
-
- for (var i = 0; i < lines.length;i++) {
- pos += lines[i].length;
-
- if(i != 0){
- pos += 1;
+ },
+
+ {
+ xtype : 'TextItem',
+ text : "Merge Cells: ",
+ xns : rooui.Toolbar
+
+ },
+
+
+ {
+ xtype : 'Button',
+ text: 'Right',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ cell().mergeRight();
+ //block().growColumn();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
}
-
- if(pos < curr){
- continue;
+ },
+ xns : rooui.Toolbar
+ },
+
+ {
+ xtype : 'Button',
+ text: 'Below',
+ listeners : {
+ click : function (_self, e)
+ {
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ cell().mergeBelow();
+ //block().growColumn();
+ syncValue();
+ toolbar.editorcore.onEditorEvent();
}
-
- pos -= lines[i].length;
-
- break;
- }
-
- if(!e.shiftKey){
- this.el.dom.setSelectionRange(pos, pos);
- return;
- }
-
- this.el.dom.selectionStart = pos;
- this.el.dom.selectionEnd = curr;
+ },
+ xns : rooui.Toolbar
+ },
+ {
+ xtype : 'TextItem',
+ text : "| ",
+ xns : rooui.Toolbar
+
},
- "end" : function(e){
- e.preventDefault();
-
- var curr = this.el.dom.selectionStart;
- var lines = this.getValue().split("\n");
-
- if(!lines.length){
- return;
- }
-
- if(e.ctrlKey){
- this.el.dom.setSelectionRange(this.getValue().length, this.getValue().length);
- return;
- }
-
- var pos = 0;
-
- for (var i = 0; i < lines.length;i++) {
-
- pos += lines[i].length;
-
- if(i != 0){
- pos += 1;
- }
-
- if(pos < curr){
- continue;
+ {
+ xtype : 'Button',
+ text: 'Split',
+ listeners : {
+ click : function (_self, e)
+ {
+ //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ cell().split();
+ syncValue();
+ toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
+ toolbar.editorcore.onEditorEvent();
+
}
-
- break;
- }
-
- if(!e.shiftKey){
- this.el.dom.setSelectionRange(pos, pos);
- return;
- }
-
- this.el.dom.selectionStart = curr;
- this.el.dom.selectionEnd = pos;
+ },
+ xns : rooui.Toolbar
},
-
- scope : this,
-
- doRelay : function(foo, bar, hname){
- return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
+ {
+ xtype : 'Fill',
+ xns : rooui.Toolbar
+
},
-
- forceKeyDown: true
- });
-// if(this.autosave && this.w){
-// this.autoSaveFn = setInterval(this.autosave, 1000);
-// }
+
+ {
+ xtype : 'Button',
+ text: 'Delete',
+
+ xns : rooui.Toolbar,
+ menu : {
+ xtype : 'Menu',
+ xns : rooui.menu,
+ items : [
+ {
+ xtype : 'Item',
+ html: 'Column',
+ listeners : {
+ click : function (_self, e)
+ {
+ var t = table();
+
+ cell().deleteColumn();
+ syncValue();
+ toolbar.editorcore.selectNode(t.node);
+ toolbar.editorcore.onEditorEvent();
+ }
+ },
+ xns : rooui.menu
+ },
+ {
+ xtype : 'Item',
+ html: 'Row',
+ listeners : {
+ click : function (_self, e)
+ {
+ var t = table();
+ cell().deleteRow();
+ syncValue();
+
+ toolbar.editorcore.selectNode(t.node);
+ toolbar.editorcore.onEditorEvent();
+
+ }
+ },
+ xns : rooui.menu
+ },
+ {
+ xtype : 'Separator',
+ xns : rooui.menu
+ },
+ {
+ xtype : 'Item',
+ html: 'Table',
+ listeners : {
+ click : function (_self, e)
+ {
+ var t = table();
+ var nn = t.node.nextSibling || t.node.previousSibling;
+ t.node.parentNode.removeChild(t.node);
+ if (nn) {
+ toolbar.editorcore.selectNode(nn, true);
+ }
+ toolbar.editorcore.onEditorEvent();
+
+ }
+ },
+ xns : rooui.menu
+ }
+ ]
+ }
+ }
+
+ // align... << fixme
+
+ ];
+
},
-
- // private
- onResize : function(w, h)
+
+
+ /**
+ * create a DomHelper friendly object - for use with
+ * Roo.DomHelper.markup / overwrite / etc..
+ * ?? should it be called with option to hide all editing features?
+ */
+ /**
+ * create a DomHelper friendly object - for use with
+ * Roo.DomHelper.markup / overwrite / etc..
+ * ?? should it be called with option to hide all editing features?
+ */
+ toObject : function()
{
- Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
- var ew = false;
- var eh = false;
+ var ret = {
+ tag : 'td',
+ contenteditable : 'true', // this stops cell selection from picking the table.
+ 'data-block' : 'Td',
+ valign : this.valign,
+ style : {
+ 'text-align' : this.textAlign,
+ border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
+ 'border-collapse' : 'collapse',
+ padding : '6px', // 8 for desktop / 4 for mobile
+ 'vertical-align': this.valign
+ },
+ html : this.html
+ };
+ if (this.width != '') {
+ ret.width = this.width;
+ ret.style.width = this.width;
+ }
- if(this.el ){
- if(typeof w == 'number'){
- var aw = w - this.wrap.getFrameWidth('lr');
- this.el.setWidth(this.adjustWidth('textarea', aw));
- ew = aw;
- }
- if(typeof h == 'number'){
- var tbh = 0;
- for (var i =0; i < this.toolbars.length;i++) {
- // fixme - ask toolbars for heights?
- tbh += this.toolbars[i].tb.el.getHeight();
- if (this.toolbars[i].footer) {
- tbh += this.toolbars[i].footer.el.getHeight();
+
+ if (this.colspan > 1) {
+ ret.colspan = this.colspan ;
+ }
+ if (this.rowspan > 1) {
+ ret.rowspan = this.rowspan ;
+ }
+
+
+
+ return ret;
+
+ },
+
+ readElement : function(node)
+ {
+ node = node ? node : this.node ;
+ this.width = node.style.width;
+ this.colspan = Math.max(1,1*node.getAttribute('colspan'));
+ this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
+ this.html = node.innerHTML;
+ if (node.style.textAlign != '') {
+ this.textAlign = node.style.textAlign;
+ }
+
+
+ },
+
+ // the default cell object... at present...
+ emptyCell : function() {
+ return {
+ colspan : 1,
+ rowspan : 1,
+ textAlign : 'left',
+ html : " " // is this going to be editable now?
+ };
+
+ },
+
+ removeNode : function()
+ {
+ return this.node.closest('table');
+
+ },
+
+ cellData : false,
+
+ colWidths : false,
+
+ toTableArray : function()
+ {
+ var ret = [];
+ var tab = this.node.closest('tr').closest('table');
+ Array.from(tab.rows).forEach(function(r, ri){
+ ret[ri] = [];
+ });
+ var rn = 0;
+ this.colWidths = [];
+ var all_auto = true;
+ Array.from(tab.rows).forEach(function(r, ri){
+
+ var cn = 0;
+ Array.from(r.cells).forEach(function(ce, ci){
+ var c = {
+ cell : ce,
+ row : rn,
+ col: cn,
+ colspan : ce.colSpan,
+ rowspan : ce.rowSpan
+ };
+ if (ce.isEqualNode(this.node)) {
+ this.cellData = c;
+ }
+ // if we have been filled up by a row?
+ if (typeof(ret[rn][cn]) != 'undefined') {
+ while(typeof(ret[rn][cn]) != 'undefined') {
+ cn++;
}
+ c.col = cn;
}
+ if (typeof(this.colWidths[cn]) == 'undefined' && c.colspan < 2) {
+ this.colWidths[cn] = ce.style.width;
+ if (this.colWidths[cn] != '') {
+ all_auto = false;
+ }
+ }
+ if (c.colspan < 2 && c.rowspan < 2 ) {
+ ret[rn][cn] = c;
+ cn++;
+ return;
+ }
+ for(var j = 0; j < c.rowspan; j++) {
+ if (typeof(ret[rn+j]) == 'undefined') {
+ continue; // we have a problem..
+ }
+ ret[rn+j][cn] = c;
+ for(var i = 0; i < c.colspan; i++) {
+ ret[rn+j][cn+i] = c;
+ }
+ }
- var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
- ah -= 5; // knock a few pixes off for look..
-// Roo.log(ah);
- this.el.setHeight(this.adjustWidth('textarea', ah));
- var eh = ah;
- }
+ cn += c.colspan;
+ }, this);
+ rn++;
+ }, this);
+
+ // initalize widths.?
+ // either all widths or no widths..
+ if (all_auto) {
+ this.colWidths[0] = false; // no widths flag.
}
- Roo.log('onResize:' + [w,h,ew,eh].join(',') );
- this.editorcore.onResize(ew,eh);
+
+
+ return ret;
},
+
+
+
+
+ mergeRight: function()
+ {
+
+ // get the contents of the next cell along..
+ var tr = this.node.closest('tr');
+ var i = Array.prototype.indexOf.call(tr.childNodes, this.node);
+ if (i >= tr.childNodes.length - 1) {
+ return; // no cells on right to merge with.
+ }
+ var table = this.toTableArray();
+
+ if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
+ return; // nothing right?
+ }
+ var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan];
+ // right cell - must be same rowspan and on the same row.
+ if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) {
+ return; // right hand side is not same rowspan.
+ }
+
+
+
+ this.node.innerHTML += ' ' + rc.cell.innerHTML;
+ tr.removeChild(rc.cell);
+ this.colspan += rc.colspan;
+ this.node.setAttribute('colspan', this.colspan);
- /**
- * Toggles the editor between standard and source edit mode.
- * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
- */
- toggleSourceEdit : function(sourceEditMode)
+ var table = this.toTableArray();
+ this.normalizeWidths(table);
+ this.updateWidths(table);
+ },
+
+
+ mergeBelow : function()
{
- this.editorcore.toggleSourceEdit(sourceEditMode);
+ var table = this.toTableArray();
+ if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
+ return; // no row below
+ }
+ if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
+ return; // nothing right?
+ }
+ var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
- if(this.editorcore.sourceEditMode){
- Roo.log('editor - showing textarea');
-
-// Roo.log('in');
-// Roo.log(this.syncValue());
- this.editorcore.syncValue();
- this.el.removeClass('x-hidden');
- this.el.dom.removeAttribute('tabIndex');
- this.el.focus();
+ if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
+ return; // right hand side is not same rowspan.
+ }
+ this.node.innerHTML = this.node.innerHTML + rc.cell.innerHTML ;
+ rc.cell.parentNode.removeChild(rc.cell);
+ this.rowspan += rc.rowspan;
+ this.node.setAttribute('rowspan', this.rowspan);
+ },
+
+ split: function()
+ {
+ if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
+ return;
+ }
+ var table = this.toTableArray();
+ var cd = this.cellData;
+ this.rowspan = 1;
+ this.colspan = 1;
+
+ for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
+
- for (var i = 0; i < this.toolbars.length; i++) {
- if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
- this.toolbars[i].tb.hide();
- this.toolbars[i].footer.hide();
+ for(var c = cd.col; c < cd.col + cd.colspan; c++) {
+ if (r == cd.row && c == cd.col) {
+ this.node.removeAttribute('rowspan');
+ this.node.removeAttribute('colspan');
}
+
+ var ntd = this.node.cloneNode(); // which col/row should be 0..
+ ntd.removeAttribute('id');
+ ntd.style.width = this.colWidths[c];
+ ntd.innerHTML = '';
+ table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1 };
}
- }else{
- Roo.log('editor - hiding textarea');
-// Roo.log('out')
-// Roo.log(this.pushValue());
- this.editorcore.pushValue();
+ }
+ this.redrawAllCells(table);
+
+ },
+
+
+
+ redrawAllCells: function(table)
+ {
+
+
+ var tab = this.node.closest('tr').closest('table');
+ var ctr = tab.rows[0].parentNode;
+ Array.from(tab.rows).forEach(function(r, ri){
- this.el.addClass('x-hidden');
- this.el.dom.setAttribute('tabIndex', -1);
+ Array.from(r.cells).forEach(function(ce, ci){
+ ce.parentNode.removeChild(ce);
+ });
+ r.parentNode.removeChild(r);
+ });
+ for(var r = 0 ; r < table.length; r++) {
+ var re = tab.rows[r];
- for (var i = 0; i < this.toolbars.length; i++) {
- if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
- this.toolbars[i].tb.show();
- this.toolbars[i].footer.show();
+ var re = tab.ownerDocument.createElement('tr');
+ ctr.appendChild(re);
+ for(var c = 0 ; c < table[r].length; c++) {
+ if (table[r][c].cell === false) {
+ continue;
}
+
+ re.appendChild(table[r][c].cell);
+
+ table[r][c].cell = false;
}
-
- //this.deferFocus();
}
- this.setSize(this.wrap.getSize());
- this.onResize(this.wrap.getSize().width, this.wrap.getSize().height);
-
- this.fireEvent('editmodechange', this, this.editorcore.sourceEditMode);
- },
-
- // private (for BoxComponent)
- adjustSize : Roo.BoxComponent.prototype.adjustSize,
-
- // private (for BoxComponent)
- getResizeEl : function(){
- return this.wrap;
- },
-
- // private (for BoxComponent)
- getPositionEl : function(){
- return this.wrap;
- },
-
- // private
- initEvents : function(){
- this.originalValue = this.getValue();
- },
-
- /**
- * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
- * @method
- */
- markInvalid : Roo.emptyFn,
- /**
- * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
- * @method
- */
- clearInvalid : Roo.emptyFn,
-
- setValue : function(v){
- Roo.form.HtmlEditor.superclass.setValue.call(this, v);
- this.editorcore.pushValue();
- },
-
-
- // private
- deferFocus : function(){
- this.focus.defer(10, this);
},
-
- // doc'ed in Field
- focus : function(){
- this.editorcore.focus();
-
+ updateWidths : function(table)
+ {
+ for(var r = 0 ; r < table.length; r++) {
+
+ for(var c = 0 ; c < table[r].length; c++) {
+ if (table[r][c].cell === false) {
+ continue;
+ }
+
+ if (this.colWidths[0] != false && table[r][c].colspan < 2) {
+ var el = Roo.htmleditor.Block.factory(table[r][c].cell);
+ el.width = Math.floor(this.colWidths[c]) +'%';
+ el.updateElement(el.node);
+ }
+ if (this.colWidths[0] != false && table[r][c].colspan > 1) {
+ var el = Roo.htmleditor.Block.factory(table[r][c].cell);
+ var width = 0;
+ for(var i = 0; i < table[r][c].colspan; i ++) {
+ width += Math.floor(this.colWidths[c + i]);
+ }
+ el.width = width +'%';
+ el.updateElement(el.node);
+ }
+ table[r][c].cell = false; // done
+ }
+ }
},
-
-
- // private
- onDestroy : function(){
-
-
+ normalizeWidths : function(table)
+ {
+ if (this.colWidths[0] === false) {
+ var nw = 100.0 / this.colWidths.length;
+ this.colWidths.forEach(function(w,i) {
+ this.colWidths[i] = nw;
+ },this);
+ return;
+ }
+
+ var t = 0, missing = [];
- if(this.rendered){
-
- for (var i =0; i < this.toolbars.length;i++) {
- // fixme - ask toolbars for heights?
- this.toolbars[i].onDestroy();
+ this.colWidths.forEach(function(w,i) {
+ //if you mix % and
+ this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
+ var add = this.colWidths[i];
+ if (add > 0) {
+ t+=add;
+ return;
}
+ missing.push(i);
- this.wrap.dom.innerHTML = '';
- this.wrap.remove();
- }
- },
-
- // private
- onFirstFocus : function(){
- //Roo.log("onFirstFocus");
- this.editorcore.onFirstFocus();
- for (var i =0; i < this.toolbars.length;i++) {
- this.toolbars[i].onFirstFocus();
+
+ },this);
+ var nc = this.colWidths.length;
+ if (missing.length) {
+ var mult = (nc - missing.length) / (1.0 * nc);
+ var t = mult * t;
+ var ew = (100 -t) / (1.0 * missing.length);
+ this.colWidths.forEach(function(w,i) {
+ if (w > 0) {
+ this.colWidths[i] = w * mult;
+ return;
+ }
+
+ this.colWidths[i] = ew;
+ }, this);
+ // have to make up numbers..
+
}
+ // now we should have all the widths..
+
},
- // private
- syncValue : function()
+ shrinkColumn : function()
{
- this.editorcore.syncValue();
+ var table = this.toTableArray();
+ this.normalizeWidths(table);
+ var col = this.cellData.col;
+ var nw = this.colWidths[col] * 0.8;
+ if (nw < 5) {
+ return;
+ }
+ var otherAdd = (this.colWidths[col] * 0.2) / (this.colWidths.length -1);
+ this.colWidths.forEach(function(w,i) {
+ if (i == col) {
+ this.colWidths[i] = nw;
+ return;
+ }
+ this.colWidths[i] += otherAdd
+ }, this);
+ this.updateWidths(table);
+
},
-
- pushValue : function()
+ growColumn : function()
{
- this.editorcore.pushValue();
+ var table = this.toTableArray();
+ this.normalizeWidths(table);
+ var col = this.cellData.col;
+ var nw = this.colWidths[col] * 1.2;
+ if (nw > 90) {
+ return;
+ }
+ var otherSub = (this.colWidths[col] * 0.2) / (this.colWidths.length -1);
+ this.colWidths.forEach(function(w,i) {
+ if (i == col) {
+ this.colWidths[i] = nw;
+ return;
+ }
+ this.colWidths[i] -= otherSub
+ }, this);
+ this.updateWidths(table);
+
},
-
- setStylesheets : function(stylesheets)
+ deleteRow : function()
{
- this.editorcore.setStylesheets(stylesheets);
+ // delete this rows 'tr'
+ // if any of the cells in this row have a rowspan > 1 && row!= this row..
+ // then reduce the rowspan.
+ var table = this.toTableArray();
+ // this.cellData.row;
+ for (var i =0;i< table[this.cellData.row].length ; i++) {
+ var c = table[this.cellData.row][i];
+ if (c.row != this.cellData.row) {
+
+ c.rowspan--;
+ c.cell.setAttribute('rowspan', c.rowspan);
+ continue;
+ }
+ if (c.rowspan > 1) {
+ c.rowspan--;
+ c.cell.setAttribute('rowspan', c.rowspan);
+ }
+ }
+ table.splice(this.cellData.row,1);
+ this.redrawAllCells(table);
+
},
-
- removeStylesheets : function()
+ deleteColumn : function()
{
- this.editorcore.removeStylesheets();
+ var table = this.toTableArray();
+
+ for (var i =0;i< table.length ; i++) {
+ var c = table[i][this.cellData.col];
+ if (c.col != this.cellData.col) {
+ table[i][this.cellData.col].colspan--;
+ } else if (c.colspan > 1) {
+ c.colspan--;
+ c.cell.setAttribute('colspan', c.colspan);
+ }
+ table[i].splice(this.cellData.col,1);
+ }
+
+ this.redrawAllCells(table);
}
-
- // hide stuff that is not compatible
- /**
- * @event blur
- * @hide
- */
- /**
- * @event change
- * @hide
- */
- /**
- * @event focus
- * @hide
- */
- /**
- * @event specialkey
- * @hide
- */
- /**
- * @cfg {String} fieldClass @hide
- */
- /**
- * @cfg {String} focusClass @hide
- */
- /**
- * @cfg {String} autoCreate @hide
- */
- /**
- * @cfg {String} inputType @hide
- */
- /**
- * @cfg {String} invalidClass @hide
- */
- /**
- * @cfg {String} invalidText @hide
- */
- /**
- * @cfg {String} msgFx @hide
- */
- /**
- * @cfg {String} validateOnBlur @hide
- */
-});
-
- // <script type="text/javascript">
+
+
+
+})
+
+//<script type="text/javascript">
+
/*
- * Based on
- * Ext JS Library 1.1.1
+ * Based Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
- *
-
+ * LGPL
+ *
*/
-
+
/**
- * @class Roo.form.HtmlEditorToolbar1
- * Basic Toolbar
- *
- * Usage:
+ * @class Roo.HtmlEditorCore
+ * @extends Roo.Component
+ * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
*
- new Roo.form.HtmlEditor({
- ....
- toolbars : [
- new Roo.form.HtmlEditorToolbar1({
- disable : { fonts: 1 , format: 1, ..., ... , ...],
- btns : [ .... ]
- })
- }
-
- *
- * @cfg {Object} disable List of elements to disable..
- * @cfg {Array} btns List of additional buttons.
- *
- *
- * NEEDS Extra CSS?
- * .x-html-editor-tb .x-edit-none .x-btn-text { background: none; }
+ * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
*/
-
-Roo.form.HtmlEditor.ToolbarStandard = function(config)
-{
-
- Roo.apply(this, config);
-
- // default disabled, based on 'good practice'..
- this.disable = this.disable || {};
- Roo.applyIf(this.disable, {
- fontSize : true,
- colors : true,
- specialElements : true
- });
-
-
- //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
- // dont call parent... till later.
-}
-Roo.apply(Roo.form.HtmlEditor.ToolbarStandard.prototype, {
-
- tb: false,
-
- rendered: false,
-
- editor : false,
- editorcore : false,
- /**
- * @cfg {Object} disable List of toolbar elements to disable
-
- */
- disable : false,
+Roo.HtmlEditorCore = function(config){
- /**
- * @cfg {String} createLinkText The default text for the create link prompt
- */
- createLinkText : 'Please enter the URL for the link:',
- /**
- * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
- */
- defaultLinkValue : 'http:/'+'/',
-
+ Roo.HtmlEditorCore.superclass.constructor.call(this, config);
- /**
- * @cfg {Array} fontFamilies An array of available font families
- */
- fontFamilies : [
- 'Arial',
- 'Courier New',
- 'Tahoma',
- 'Times New Roman',
- 'Verdana'
- ],
- specialChars : [
- "©",
- "®",
- "™",
- "£" ,
- // "—",
- "…",
- "÷" ,
- // "á" , ?? a acute?
- "€" , //Euro
- // "“" ,
- // "”" ,
- // "•" ,
- "°" // , // degrees
-
- // "é" , // e ecute
- // "ú" , // u ecute?
- ],
-
- specialElements : [
- {
- text: "Insert Table",
- xtype: 'MenuItem',
- xns : Roo.Menu,
- ihtml : '<table><tr><td>Cell</td></tr></table>'
-
- },
- {
- text: "Insert Image",
- xtype: 'MenuItem',
- xns : Roo.Menu,
- ihtml : '<img src="about:blank"/>'
-
- }
+ this.addEvents({
+ /**
+ * @event initialize
+ * Fires when the editor is fully initialized (including the iframe)
+ * @param {Roo.HtmlEditorCore} this
+ */
+ initialize: true,
+ /**
+ * @event activate
+ * Fires when the editor is first receives the focus. Any insertion must wait
+ * until after this event.
+ * @param {Roo.HtmlEditorCore} this
+ */
+ activate: true,
+ /**
+ * @event beforesync
+ * Fires before the textarea is updated with content from the editor iframe. Return false
+ * to cancel the sync.
+ * @param {Roo.HtmlEditorCore} this
+ * @param {String} html
+ */
+ beforesync: true,
+ /**
+ * @event beforepush
+ * Fires before the iframe editor is updated with content from the textarea. Return false
+ * to cancel the push.
+ * @param {Roo.HtmlEditorCore} this
+ * @param {String} html
+ */
+ beforepush: true,
+ /**
+ * @event sync
+ * Fires when the textarea is updated with content from the editor iframe.
+ * @param {Roo.HtmlEditorCore} this
+ * @param {String} html
+ */
+ sync: true,
+ /**
+ * @event push
+ * Fires when the iframe editor is updated with content from the textarea.
+ * @param {Roo.HtmlEditorCore} this
+ * @param {String} html
+ */
+ push: true,
+ /**
+ * @event editorevent
+ * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
+ * @param {Roo.HtmlEditorCore} this
+ */
+ editorevent: true
- ],
+
+ });
+ // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
- inputElements : [
- "form", "input:text", "input:hidden", "input:checkbox", "input:radio", "input:password",
- "input:submit", "input:button", "select", "textarea", "label" ],
- formats : [
- ["p"] ,
- ["h1"],["h2"],["h3"],["h4"],["h5"],["h6"],
- ["pre"],[ "code"],
- ["abbr"],[ "acronym"],[ "address"],[ "cite"],[ "samp"],[ "var"],
- ['div'],['span'],
- ['sup'],['sub']
- ],
+ // defaults : white / black...
+ this.applyBlacklists();
- cleanStyles : [
- "font-size"
- ],
+
+
+};
+
+
+Roo.extend(Roo.HtmlEditorCore, Roo.Component, {
+
+
/**
- * @cfg {String} defaultFont default font to use.
+ * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field
*/
- defaultFont: 'tahoma',
-
- fontSelect : false,
+ owner : false,
- formatCombo : false,
+ /**
+ * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
+ * Roo.resizable.
+ */
+ resizable : false,
+ /**
+ * @cfg {Number} height (in pixels)
+ */
+ height: 300,
+ /**
+ * @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,
- init : function(editor)
- {
- this.editor = editor;
- this.editorcore = editor.editorcore ? editor.editorcore : editor;
- var editorcore = this.editorcore;
+ /**
+ * @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,
+
+ // private properties
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+ sourceEditMode : false,
+ onFocus : Roo.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+
+ clearUp: true,
+
+ // blacklist + whitelisted elements..
+ black: false,
+ white: 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
+ * want to change the initialization markup of the iframe (e.g. to add stylesheets).
+ */
+ getDocMarkup : function(){
+ // body styles..
+ var st = '';
+
+ // inherit styels from page...??
+ if (this.stylesheets === false) {
+
+ Roo.get(document.head).select('style').each(function(node) {
+ st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
+ });
+
+ Roo.get(document.head).select('link').each(function(node) {
+ st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
+ });
+
+ } else if (!this.stylesheets.length) {
+ // simple..
+ st = '<style type="text/css">' +
+ 'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
+ '</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>';
+
+ st += '<meta name="google" content="notranslate">';
+ var cls = 'notranslate roo-htmleditor-body';
+
+ if(this.bodyCls.length){
+ cls += ' ' + this.bodyCls;
+ }
+
+ 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 contenteditable="true" data-enable-grammerly="true" class="' + cls + '"></body></html>';
+ },
+
+ // private
+ onRender : function(ct, position)
+ {
var _t = this;
+ //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
+ this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
- var fid = editorcore.frameId;
- var etb = this;
- function btn(id, toggle, handler){
- var xid = fid + '-'+ id ;
- return {
- id : xid,
- cmd : id,
- cls : 'x-btn-icon x-edit-'+id,
- enableToggle:toggle !== false,
- scope: _t, // was editor...
- handler:handler||_t.relayBtnCmd,
- clickEvent:'mousedown',
- tooltip: etb.buttonTips[id] || undefined, ///tips ???
- tabIndex:-1
- };
+
+ this.el.dom.style.border = '0 none';
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.el.addClass('x-hidden hide');
+
+
+
+ if(Roo.isIE){ // fix IE 1px bogus margin
+ this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
}
+
+ this.frameId = Roo.id();
+
- var tb = new Roo.Toolbar(editor.wrap.dom.firstChild);
- this.tb = tb;
- // stop form submits
- tb.el.on('click', function(e){
- e.preventDefault(); // what does this do?
- });
+ var iframe = this.owner.wrap.createChild({
+ tag: 'iframe',
+ cls: 'form-control', // bootstrap..
+ id: this.frameId,
+ name: this.frameId,
+ frameBorder : 'no',
+ 'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL : "javascript:false"
+ }, this.el
+ );
+
+
+ this.iframe = iframe.dom;
- if(!this.disable.font) { // && !Roo.isSafari){
- /* why no safari for fonts
- editor.fontSelect = tb.el.createChild({
- tag:'select',
- tabIndex: -1,
- cls:'x-font-select',
- html: this.createFontOptions()
- });
-
- editor.fontSelect.on('change', function(){
- var font = editor.fontSelect.dom.value;
- editor.relayCmd('fontname', font);
- editor.deferFocus();
- }, editor);
-
- tb.add(
- editor.fontSelect.dom,
- '-'
- );
- */
-
- };
- if(!this.disable.formats){
- this.formatCombo = new Roo.form.ComboBox({
- store: new Roo.data.SimpleStore({
- id : 'tag',
- fields: ['tag'],
- data : this.formats // from states.js
- }),
- blockFocus : true,
- name : '',
- //autoCreate : {tag: "div", size: "20"},
- displayField:'tag',
- typeAhead: false,
- mode: 'local',
- editable : false,
- triggerAction: 'all',
- emptyText:'Add tag',
- selectOnFocus:true,
- width:135,
- listeners : {
- 'select': function(c, r, i) {
- editorcore.insertTag(r.get('tag'));
- editor.focus();
+ this.assignDocWin();
+
+ this.doc.designMode = 'on';
+
+ this.doc.open();
+ this.doc.write(this.getDocMarkup());
+ this.doc.close();
+
+
+ var task = { // must defer to wait for browser to be ready
+ run : function(){
+ //console.log("run task?" + this.doc.readyState);
+ this.assignDocWin();
+ if(this.doc.body || this.doc.readyState == 'complete'){
+ try {
+ this.doc.designMode="on";
+
+ } catch (e) {
+ return;
}
+ Roo.TaskMgr.stop(task);
+ this.initEditor.defer(10, this);
}
+ },
+ interval : 10,
+ duration: 10000,
+ scope: this
+ };
+ Roo.TaskMgr.start(task);
- });
- tb.addField(this.formatCombo);
+ },
+
+ // private
+ onResize : function(w, h)
+ {
+ Roo.log('resize: ' +w + ',' + h );
+ //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
+ if(!this.iframe){
+ return;
+ }
+ if(typeof w == 'number'){
+ this.iframe.style.width = w + 'px';
+ }
+ if(typeof h == 'number'){
+
+ this.iframe.style.height = h + 'px';
+ if(this.doc){
+ (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
+ }
}
- if(!this.disable.format){
- tb.add(
- btn('bold'),
- btn('italic'),
- btn('underline'),
- btn('strikethrough')
- );
- };
- if(!this.disable.fontSize){
- tb.add(
- '-',
-
-
- btn('increasefontsize', false, editorcore.adjustFont),
- btn('decreasefontsize', false, editorcore.adjustFont)
- );
- };
-
+ },
+
+ /**
+ * Toggles the editor between standard and source edit mode.
+ * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
+ */
+ toggleSourceEdit : function(sourceEditMode){
- if(!this.disable.colors){
- tb.add(
- '-', {
- id:editorcore.frameId +'-forecolor',
- cls:'x-btn-icon x-edit-forecolor',
- clickEvent:'mousedown',
- tooltip: this.buttonTips['forecolor'] || undefined,
- tabIndex:-1,
- menu : new Roo.menu.ColorMenu({
- allowReselect: true,
- focus: Roo.emptyFn,
- value:'000000',
- plain:true,
- selectHandler: function(cp, color){
- editorcore.execCmd('forecolor', Roo.isSafari || Roo.isIE ? '#'+color : color);
- editor.deferFocus();
- },
- scope: editorcore,
- clickEvent:'mousedown'
- })
- }, {
- id:editorcore.frameId +'backcolor',
- cls:'x-btn-icon x-edit-backcolor',
- clickEvent:'mousedown',
- tooltip: this.buttonTips['backcolor'] || undefined,
- tabIndex:-1,
- menu : new Roo.menu.ColorMenu({
- focus: Roo.emptyFn,
- value:'FFFFFF',
- plain:true,
- allowReselect: true,
- selectHandler: function(cp, color){
- if(Roo.isGecko){
- editorcore.execCmd('useCSS', false);
- editorcore.execCmd('hilitecolor', color);
- editorcore.execCmd('useCSS', true);
- editor.deferFocus();
- }else{
- editorcore.execCmd(Roo.isOpera ? 'hilitecolor' : 'backcolor',
- Roo.isSafari || Roo.isIE ? '#'+color : color);
- editor.deferFocus();
- }
- },
- scope:editorcore,
- clickEvent:'mousedown'
- })
- }
- );
- };
- // now add all the items...
+ this.sourceEditMode = sourceEditMode === true;
+ if(this.sourceEditMode){
+
+ 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', 'd-none']);
+ //this.iframe.className = '';
+ this.deferFocus();
+ }
+ //this.setSize(this.owner.wrap.getSize());
+ //this.fireEvent('editmodechange', this, this.sourceEditMode);
+ },
- if(!this.disable.alignments){
- tb.add(
- '-',
- btn('justifyleft'),
- btn('justifycenter'),
- btn('justifyright')
- );
- };
+
+
- //if(!Roo.isSafari){
- if(!this.disable.links){
- tb.add(
- '-',
- btn('createlink', false, this.createLink) /// MOVE TO HERE?!!?!?!?!
- );
- };
+ /**
+ * Protected method that will not generally be called directly. If you need/want
+ * custom HTML cleanup, this is the method you should override.
+ * @param {String} html The HTML to be cleaned
+ * return {String} The cleaned HTML
+ */
+ cleanHtml : function(html)
+ {
+ html = String(html);
+ if(html.length > 5){
+ if(Roo.isSafari){ // strip safari nonsense
+ html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
+ }
+ }
+ if(html == ' '){
+ html = '';
+ }
+ return html;
+ },
- if(!this.disable.lists){
- tb.add(
- '-',
- btn('insertorderedlist'),
- btn('insertunorderedlist')
- );
+ /**
+ * HTML Editor -> Textarea
+ * Protected method that will not generally be called directly. Syncs the contents
+ * of the editor iframe with the textarea.
+ */
+ syncValue : function()
+ {
+ //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
+ if(this.initialized){
+
+ if (this.undoManager) {
+ this.undoManager.addEvent();
}
- if(!this.disable.sourceEdit){
- tb.add(
- '-',
- btn('sourceedit', true, function(btn){
- this.toggleSourceEdit(btn.pressed);
- })
- );
+
+
+ var bd = (this.doc.body || this.doc.documentElement);
+
+
+ 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);
}
- //}
-
- var smenu = { };
- // special menu.. - needs to be tidied up..
- if (!this.disable.special) {
- smenu = {
- text: "©",
- cls: 'x-edit-none',
-
- menu : {
- items : []
- }
- };
- for (var i =0; i < this.specialChars.length; i++) {
- smenu.menu.items.push({
-
- html: this.specialChars[i],
- handler: function(a,b) {
- editorcore.insertAtCursor(String.fromCharCode(a.html.replace('&#','').replace(';', '')));
- //editor.insertAtCursor(a.html);
-
- },
- tabIndex:-1
- });
- }
-
- tb.add(smenu);
+
+ if (this.enableBlocks) {
+ new Roo.htmleditor.FilterBlock({ node : div });
+ }
+ var html = div.innerHTML;
- }
-
- var cmenu = { };
- if (!this.disable.cleanStyles) {
- cmenu = {
- cls: 'x-btn-icon x-btn-clear',
+ //?? tidy?
+ if (this.autoClean) {
- menu : {
- items : []
- }
- };
- for (var i =0; i < this.cleanStyles.length; i++) {
- cmenu.menu.items.push({
- actiontype : this.cleanStyles[i],
- html: 'Remove ' + this.cleanStyles[i],
- handler: function(a,b) {
-// Roo.log(a);
-// Roo.log(b);
- var c = Roo.get(editorcore.doc.body);
- c.select('[style]').each(function(s) {
- s.dom.style.removeProperty(a.actiontype);
- });
- editorcore.syncValue();
- },
- tabIndex:-1
+ new Roo.htmleditor.FilterAttributes({
+ node : div,
+ attrib_white : [
+ 'href',
+ 'src',
+ 'name',
+ 'align',
+ 'colspan',
+ 'rowspan',
+ 'data-display',
+ 'data-width',
+ 'start' ,
+ 'style',
+ // youtube embed.
+ 'class',
+ 'allowfullscreen',
+ 'frameborder',
+ 'width',
+ 'height',
+ 'alt'
+ ],
+ attrib_clean : ['href', 'src' ]
+ });
+
+ var tidy = new Roo.htmleditor.TidySerializer({
+ inner: true
});
+ html = tidy.serialize(div);
+
}
- cmenu.menu.items.push({
- actiontype : 'tablewidths',
- html: 'Remove Table Widths',
- handler: function(a,b) {
- editorcore.cleanTableWidths();
- editorcore.syncValue();
- },
- tabIndex:-1
- });
- cmenu.menu.items.push({
- actiontype : 'word',
- html: 'Remove MS Word Formating',
- handler: function(a,b) {
- editorcore.cleanWord();
- editorcore.syncValue();
- },
- tabIndex:-1
- });
- cmenu.menu.items.push({
- actiontype : 'all',
- html: 'Remove All Styles',
- handler: function(a,b) {
-
- var c = Roo.get(editorcore.doc.body);
- c.select('[style]').each(function(s) {
- s.dom.removeAttribute('style');
- });
- editorcore.syncValue();
- },
- tabIndex:-1
- });
-
- cmenu.menu.items.push({
- actiontype : 'all',
- html: 'Remove All CSS Classes',
- handler: function(a,b) {
-
- var c = Roo.get(editorcore.doc.body);
- c.select('[class]').each(function(s) {
- s.dom.removeAttribute('class');
- });
- editorcore.cleanWord();
- editorcore.syncValue();
- },
- tabIndex:-1
- });
- cmenu.menu.items.push({
- actiontype : 'tidy',
- html: 'Tidy HTML Source',
- handler: function(a,b) {
- editorcore.doc.body.innerHTML = editorcore.domToHTML();
- editorcore.syncValue();
- },
- tabIndex:-1
+ 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;
+ if(m && m[1]){
+ html = '<div style="'+m[0]+'">' + html + '</div>';
+ }
+ }
+ 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(/[\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 match;
+ }
+
+ // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
+ return "&#" + cc + ";";
+
+
});
- tb.add(cmenu);
+
+ if(this.owner.fireEvent('beforesync', this, html) !== false){
+ this.el.dom.value = html;
+ this.owner.fireEvent('sync', this, html);
+ }
}
-
- if (!this.disable.specialElements) {
- var semenu = {
- text: "Other;",
- cls: 'x-edit-none',
- menu : {
- items : []
- }
- };
- for (var i =0; i < this.specialElements.length; i++) {
- semenu.menu.items.push(
- Roo.apply({
- handler: function(a,b) {
- editor.insertAtCursor(this.ihtml);
- }
- }, this.specialElements[i])
- );
-
+ },
+
+ /**
+ * TEXTAREA -> EDITABLE
+ * Protected method that will not generally be called directly. Pushes the value of the textarea
+ * into the iframe editor.
+ */
+ pushValue : function()
+ {
+ //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
+ if(this.initialized){
+ var v = this.el.dom.value.trim();
+
+
+ if(this.owner.fireEvent('beforepush', this, v) !== false){
+ var d = (this.doc.body || this.doc.documentElement);
+ d.innerHTML = v;
+
+ 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);
}
- tb.add(semenu);
+ 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'));
+ }
}
-
+ },
+
+ // private
+ deferFocus : function(){
+ this.focus.defer(10, this);
+ },
+
+ // doc'ed in Field
+ focus : function(){
+ if(this.win && !this.sourceEditMode){
+ this.win.focus();
+ }else{
+ this.el.focus();
+ }
+ },
+
+ assignDocWin: function()
+ {
+ var iframe = this.iframe;
- if (this.btns) {
- for(var i =0; i< this.btns.length;i++) {
- var b = Roo.factory(this.btns[i],Roo.form);
- b.cls = 'x-edit-none';
-
- if(typeof(this.btns[i].cls) != 'undefined' && this.btns[i].cls.indexOf('x-init-enable') !== -1){
- b.cls += ' x-init-enable';
- }
-
- b.scope = editorcore;
- tb.add(b);
+ if(Roo.isIE){
+ this.doc = iframe.contentWindow.document;
+ this.win = iframe.contentWindow;
+ } else {
+// if (!Roo.get(this.frameId)) {
+// return;
+// }
+// this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
+// this.win = Roo.get(this.frameId).dom.contentWindow;
+
+ if (!Roo.get(this.frameId) && !iframe.contentDocument) {
+ return;
}
-
+
+ this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
+ this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
}
+ },
+
+ // private
+ initEditor : function(){
+ //console.log("INIT EDITOR");
+ this.assignDocWin();
- // disable everything...
+ this.doc.designMode="on";
+ this.doc.open();
+ this.doc.write(this.getDocMarkup());
+ this.doc.close();
- this.tb.items.each(function(item){
+ var dbody = (this.doc.body || this.doc.documentElement);
+ //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
+ // this copies styles from the containing element into thsi one..
+ // not sure why we need all of this..
+ //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
+
+ //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, {
+
+ 'mouseup': this.onEditorEvent,
+ 'dblclick': this.onEditorEvent,
+ 'click': this.onEditorEvent,
+ 'keyup': this.onEditorEvent,
- if(
- item.id != editorcore.frameId+ '-sourceedit' &&
- (typeof(item.cls) != 'undefined' && item.cls.indexOf('x-init-enable') === -1)
- ){
-
- item.disable();
- }
+ buffer:100,
+ scope: this
});
- this.rendered = true;
-
- // the all the btns;
- editor.on('editorevent', this.updateToolbar, this);
- // other toolbars need to implement this..
- //editor.on('editmodechange', this.updateToolbar, this);
- },
-
-
- relayBtnCmd : function(btn) {
- this.editorcore.relayCmd(btn.cmd);
- },
- // 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.EventManager.on(this.doc, {
+ 'paste': this.onPasteEvent,
+ scope : this
+ });
+ if(Roo.isGecko){
+ Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
}
- },
-
-
- /**
- * Protected method that will not generally be called directly. It triggers
- * a toolbar update by reading the markup state of the current selection in the editor.
- */
- updateToolbar: function(){
-
- if(!this.editorcore.activated){
- this.editor.onFirstFocus();
- return;
+ //??? needed???
+ if(Roo.isIE || Roo.isSafari || Roo.isOpera){
+ Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
}
+ this.initialized = true;
- var btns = this.tb.items.map,
- doc = this.editorcore.doc,
- frameId = this.editorcore.frameId;
-
- if(!this.disable.font && !Roo.isSafari){
- /*
- var name = (doc.queryCommandValue('FontName')||this.editor.defaultFont).toLowerCase();
- if(name != this.fontSelect.dom.value){
- this.fontSelect.dom.value = name;
- }
- */
+
+ // 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 && 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;
}
- if(!this.disable.format){
- btns[frameId + '-bold'].toggle(doc.queryCommandState('bold'));
- btns[frameId + '-italic'].toggle(doc.queryCommandState('italic'));
- btns[frameId + '-underline'].toggle(doc.queryCommandState('underline'));
- btns[frameId + '-strikethrough'].toggle(doc.queryCommandState('strikethrough'));
+ if (cd.types.indexOf('text/html') < 0 ) {
+ return false;
}
- if(!this.disable.alignments){
- btns[frameId + '-justifyleft'].toggle(doc.queryCommandState('justifyleft'));
- btns[frameId + '-justifycenter'].toggle(doc.queryCommandState('justifycenter'));
- btns[frameId + '-justifyright'].toggle(doc.queryCommandState('justifyright'));
+ var images = [];
+ var html = cd.getData('text/html'); // clipboard event
+ if (cd.types.indexOf('text/rtf') > -1) {
+ var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
+ images = parser.doc ? parser.doc.getElementsByType('pict') : [];
}
- if(!Roo.isSafari && !this.disable.lists){
- btns[frameId + '-insertorderedlist'].toggle(doc.queryCommandState('insertorderedlist'));
- btns[frameId + '-insertunorderedlist'].toggle(doc.queryCommandState('insertunorderedlist'));
+ //Roo.log(images);
+ //Roo.log(imgs);
+ // fixme..
+ images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable|footerf)/); }) // ignore headers/footers etc.
+ .map(function(g) { return g.toDataURL(); })
+ .filter(function(g) { return g != 'about:blank'; });
+
+ //Roo.log(html);
+ 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;
}
- var ans = this.editorcore.getAllAncestors();
- if (this.formatCombo) {
+
+
+ if (images.length > 0) {
+ // replace all v:imagedata - with img.
+ var ar = Array.from(d.getElementsByTagName('v:imagedata'));
+ Roo.each(ar, function(node) {
+ node.parentNode.insertBefore(d.ownerDocument.createElement('img'), node );
+ node.parentNode.removeChild(node);
+ });
- var store = this.formatCombo.store;
- this.formatCombo.setValue("");
- for (var i =0; i < ans.length;i++) {
- if (ans[i] && store.query('tag',ans[i].tagName.toLowerCase(), false).length) {
- // select it..
- this.formatCombo.setValue(ans[i].tagName.toLowerCase());
- break;
+ Roo.each(d.getElementsByTagName('img'), function(img, i) {
+ img.setAttribute('src', images[i]);
+ });
+ }
+ if (this.autoClean) {
+ new Roo.htmleditor.FilterWord({ node : d });
+
+ new Roo.htmleditor.FilterStyleToTag({ node : d });
+ new Roo.htmleditor.FilterAttributes({
+ node : d,
+ attrib_white : ['href', 'src', 'name', 'align', 'colspan', 'rowspan', 'data-display', 'data-width', 'start'],
+ attrib_clean : ['href', 'src' ]
+ });
+ new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
+ // should be fonts..
+ new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', ':' ]} );
+ new Roo.htmleditor.FilterParagraph({ node : d });
+ new Roo.htmleditor.FilterSpan({ node : d });
+ new Roo.htmleditor.FilterLongBr({ node : d });
+ new Roo.htmleditor.FilterComment({ 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);
+ }
+
- // hides menus... - so this cant be on a menu...
- Roo.menu.MenuMgr.hideAll();
-
- //this.editorsyncValue();
+ 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);
},
-
-
- createFontOptions : function(){
- var buf = [], fs = this.fontFamilies, ff, lc;
+ // private
+ onDestroy : function(){
- for(var i = 0, len = fs.length; i< len; i++){
- ff = fs[i];
- lc = ff.toLowerCase();
- buf.push(
- '<option value="',lc,'" style="font-family:',ff,';"',
- (this.defaultFont == lc ? ' selected="true">' : '>'),
- ff,
- '</option>'
- );
+ if(this.rendered){
+
+ //for (var i =0; i < this.toolbars.length;i++) {
+ // // fixme - ask toolbars for heights?
+ // this.toolbars[i].onDestroy();
+ // }
+
+ //this.wrap.dom.innerHTML = '';
+ //this.wrap.remove();
}
- return buf.join('');
},
-
- toggleSourceEdit : function(sourceEditMode){
+
+ // private
+ onFirstFocus : function(){
- Roo.log("toolbar toogle");
- if(sourceEditMode === undefined){
- sourceEditMode = !this.sourceEditMode;
- }
- this.sourceEditMode = sourceEditMode === true;
- var btn = this.tb.items.get(this.editorcore.frameId +'-sourceedit');
- // just toggle the button?
- if(btn.pressed !== this.sourceEditMode){
- btn.toggle(this.sourceEditMode);
- return;
- }
+ this.assignDocWin();
+ this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
- if(sourceEditMode){
- Roo.log("disabling buttons");
- this.tb.items.each(function(item){
- if(item.cmd != 'sourceedit' && (typeof(item.cls) != 'undefined' && item.cls.indexOf('x-init-enable') === -1)){
- item.disable();
- }
- });
-
- }else{
- Roo.log("enabling buttons");
- if(this.editorcore.initialized){
- this.tb.items.each(function(item){
- item.enable();
- });
+ this.activated = true;
+
+
+ if(Roo.isGecko){ // prevent silly gecko errors
+ this.win.focus();
+ var s = this.win.getSelection();
+ if(!s.focusNode || s.focusNode.nodeType != 3){
+ var r = s.getRangeAt(0);
+ r.selectNodeContents((this.doc.body || this.doc.documentElement));
+ r.collapse(true);
+ this.deferFocus();
}
+ try{
+ this.execCmd('useCSS', true);
+ this.execCmd('styleWithCSS', false);
+ }catch(e){}
+ }
+ this.owner.fireEvent('activate', this);
+ },
+
+ // private
+ adjustFont: function(btn){
+ var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
+ //if(Roo.isSafari){ // safari
+ // adjust *= 2;
+ // }
+ var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
+ if(Roo.isSafari){ // safari
+ var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
+ v = (v < 10) ? 10 : v;
+ v = (v > 48) ? 48 : v;
+ v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
}
- Roo.log("calling toggole on editor");
- // tell the editor that it's been pressed..
- this.editor.toggleSourceEdit(sourceEditMode);
-
+
+
+ v = Math.max(1, v+adjust);
+
+ this.execCmd('FontSize', v );
},
- /**
- * Object collection of toolbar tooltips for the buttons in the editor. The key
- * is the command id associated with that button and the value is a valid QuickTips object.
- * For example:
-<pre><code>
-{
- bold : {
- title: 'Bold (Ctrl+B)',
- text: 'Make the selected text bold.',
- cls: 'x-html-editor-tip'
+
+ onEditorEvent : function(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
},
- italic : {
- title: 'Italic (Ctrl+I)',
- text: 'Make the selected text italic.',
- cls: 'x-html-editor-tip'
+
+ fireEditorEvent: function(e)
+ {
+ this.owner.fireEvent('editorevent', this, e);
},
- ...
-</code></pre>
- * @type Object
- */
- buttonTips : {
- bold : {
- title: 'Bold (Ctrl+B)',
- text: 'Make the selected text bold.',
- cls: 'x-html-editor-tip'
- },
- italic : {
- title: 'Italic (Ctrl+I)',
- text: 'Make the selected text italic.',
- cls: 'x-html-editor-tip'
- },
- underline : {
- title: 'Underline (Ctrl+U)',
- text: 'Underline the selected text.',
- cls: 'x-html-editor-tip'
- },
- strikethrough : {
- title: 'Strikethrough',
- text: 'Strikethrough the selected text.',
- cls: 'x-html-editor-tip'
- },
- increasefontsize : {
- title: 'Grow Text',
- text: 'Increase the font size.',
- cls: 'x-html-editor-tip'
- },
- decreasefontsize : {
- title: 'Shrink Text',
- text: 'Decrease the font size.',
- cls: 'x-html-editor-tip'
- },
- backcolor : {
- title: 'Text Highlight Color',
- text: 'Change the background color of the selected text.',
- cls: 'x-html-editor-tip'
- },
- forecolor : {
- title: 'Font Color',
- text: 'Change the color of the selected text.',
- cls: 'x-html-editor-tip'
- },
- justifyleft : {
- title: 'Align Text Left',
- text: 'Align text to the left.',
- cls: 'x-html-editor-tip'
- },
- justifycenter : {
- title: 'Center Text',
- text: 'Center text in the editor.',
- cls: 'x-html-editor-tip'
- },
- justifyright : {
- title: 'Align Text Right',
- text: 'Align text to the right.',
- cls: 'x-html-editor-tip'
- },
- insertunorderedlist : {
- title: 'Bullet List',
- text: 'Start a bulleted list.',
- cls: 'x-html-editor-tip'
- },
- insertorderedlist : {
- title: 'Numbered List',
- text: 'Start a numbered list.',
- cls: 'x-html-editor-tip'
- },
- createlink : {
- title: 'Hyperlink',
- text: 'Make the selected text a hyperlink.',
- cls: 'x-html-editor-tip'
- },
- sourceedit : {
- title: 'Source Edit',
- text: 'Switch to source editing mode.',
- cls: 'x-html-editor-tip'
- }
- },
- // private
- onDestroy : function(){
- if(this.rendered){
-
- this.tb.items.each(function(item){
- if(item.menu){
- item.menu.removeAll();
- if(item.menu.el){
- item.menu.el.destroy();
- }
- }
- item.destroy();
- });
-
- }
- },
- onFirstFocus: function() {
- this.tb.items.each(function(item){
- item.enable();
- });
- }
-});
-
-
-
-
-// <script type="text/javascript">
-/*
- * Based on
- * Ext JS Library 1.1.1
- * Copyright(c) 2006-2007, Ext JS, LLC.
- *
-
- */
-
-
-/**
- * @class Roo.form.HtmlEditor.ToolbarContext
- * Context Toolbar
- *
- * Usage:
- *
- new Roo.form.HtmlEditor({
- ....
- toolbars : [
- { xtype: 'ToolbarStandard', styles : {} }
- { xtype: 'ToolbarContext', disable : {} }
- ]
-})
-
-
- *
- * @config : {Object} disable List of elements to disable.. (not done yet.)
- * @config : {Object} styles Map of styles available.
- *
- */
-
-Roo.form.HtmlEditor.ToolbarContext = function(config)
-{
-
- Roo.apply(this, config);
- //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
- // dont call parent... till later.
- this.styles = this.styles || {};
-}
-
+ insertTag : function(tg)
+ {
+ // could be a bit smarter... -> wrap the current selected tRoo..
+ 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());
+ wrappingNode.appendChild(range.extractContents());
+ range.insertNode(wrappingNode);
-Roo.form.HtmlEditor.ToolbarContext.types = {
- 'IMG' : {
- width : {
- title: "Width",
- width: 40
- },
- height: {
- title: "Height",
- width: 40
- },
- align: {
- title: "Align",
- opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
- width : 80
+ return;
+
- },
- border: {
- title: "Border",
- width: 40
- },
- alt: {
- title: "Alt",
- width: 120
- },
- src : {
- title: "Src",
- width: 220
- }
-
- },
- 'A' : {
- name : {
- title: "Name",
- width: 50
- },
- target: {
- title: "Target",
- width: 120
- },
- 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 : {
- title: "name",
- width: 120
- },
- value : {
- title: "Value",
- width: 120
- },
- width : {
- title: "Width",
- width: 40
- }
- },
- 'LABEL' : {
- 'for' : {
- title: "For",
- width: 120
- }
- },
- 'TEXTAREA' : {
- name : {
- title: "name",
- width: 120
- },
- rows : {
- title: "Rows",
- width: 20
- },
- cols : {
- title: "Cols",
- width: 20
- }
- },
- 'SELECT' : {
- name : {
- title: "name",
- width: 120
- },
- selectoptions : {
- title: "Options",
- width: 200
}
+ this.execCmd("formatblock", tg);
+ this.undoManager.addEvent();
},
- // should we really allow this??
- // should this just be
- 'BODY' : {
- 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
- }
- },
+ insertText : function(txt)
+ {
+
+
+ var range = this.createRange();
+ range.deleteContents();
+ //alert(Sender.getAttribute('label'));
+
+ range.insertNode(this.doc.createTextNode(txt));
+ this.undoManager.addEvent();
+ } ,
- '*' : {
- // empty..
- }
-
-};
-
-// this should be configurable.. - you can either set it up using stores, or modify options somehwere..
-Roo.form.HtmlEditor.ToolbarContext.stores = false;
-
-Roo.form.HtmlEditor.ToolbarContext.options = {
- 'font-family' : [
- [ 'Helvetica,Arial,sans-serif', 'Helvetica'],
- [ 'Courier New', 'Courier New'],
- [ 'Tahoma', 'Tahoma'],
- [ 'Times New Roman,serif', 'Times'],
- [ 'Verdana','Verdana' ]
- ]
-};
-
-// fixme - these need to be configurable..
-
-
-//Roo.form.HtmlEditor.ToolbarContext.types
-
+
-Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, {
-
- tb: false,
-
- rendered: false,
-
- editor : false,
- editorcore : false,
- /**
- * @cfg {Object} disable List of toolbar elements to disable
-
- */
- disable : false,
/**
- * @cfg {Object} styles List of styles
- * eg. { '*' : [ 'headline' ] , 'TD' : [ 'underline', 'double-underline' ] }
- *
- * These must be defined in the page, so they get rendered correctly..
- * .headline { }
- * TD.underline { }
- *
+ * Executes a Midas editor command on the editor document and performs necessary focus and
+ * toolbar updates. <b>This should only be called after the editor is initialized.</b>
+ * @param {String} cmd The Midas command
+ * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
*/
- styles : false,
-
- options: false,
-
- toolbars : false,
-
- init : function(editor)
+ relayCmd : function(cmd, value)
{
- this.editor = editor;
- this.editorcore = editor.editorcore ? editor.editorcore : editor;
- var editorcore = this.editorcore;
-
- var fid = editorcore.frameId;
- var etb = this;
- function btn(id, toggle, handler){
- var xid = fid + '-'+ id ;
- return {
- id : xid,
- cmd : id,
- cls : 'x-btn-icon x-edit-'+id,
- enableToggle:toggle !== false,
- scope: editorcore, // was editor...
- handler:handler||editorcore.relayBtnCmd,
- clickEvent:'mousedown',
- tooltip: etb.buttonTips[id] || undefined, ///tips ???
- tabIndex:-1
- };
- }
- // create a new element.
- var wdiv = editor.wrap.createChild({
- tag: 'div'
- }, editor.wrap.dom.firstChild.nextSibling, true);
- // can we do this more than once??
+ 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;
+
- // stop form submits
-
-
- // disable everything...
- var ty= Roo.form.HtmlEditor.ToolbarContext.types;
- this.toolbars = {};
-
- for (var i in ty) {
-
- this.toolbars[i] = this.buildToolbar(ty[i],i);
+ default:
+ break;
}
- this.tb = this.toolbars.BODY;
- this.tb.el.show();
- this.buildFooter();
- this.footer.show();
- editor.on('hide', function( ) { this.footer.hide() }, this);
- editor.on('show', function( ) { this.footer.show() }, this);
-
- this.rendered = true;
- // the all the btns;
- editor.on('editorevent', this.updateToolbar, this);
- // other toolbars need to implement this..
- //editor.on('editmodechange', this.updateToolbar, this);
+ this.win.focus();
+ this.execCmd(cmd, value);
+ this.owner.fireEvent('editorevent', this);
+ //this.updateToolbar();
+ this.owner.deferFocus();
},
-
-
-
+
/**
- * Protected method that will not generally be called directly. It triggers
- * a toolbar update by reading the markup state of the current selection in the editor.
- *
- * Note you can force an update by calling on('editorevent', scope, false)
+ * Executes a Midas editor command directly on the editor document.
+ * For visual commands, you should use {@link #relayCmd} instead.
+ * <b>This should only be called after the editor is initialized.</b>
+ * @param {String} cmd The Midas command
+ * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
*/
- updateToolbar: function(editor,ev,sel){
-
- //Roo.log(ev);
- // capture mouse up - this is handy for selecting images..
- // perhaps should go somewhere else...
- if(!this.editorcore.activated){
- this.editor.onFirstFocus();
+ execCmd : function(cmd, value){
+ this.doc.execCommand(cmd, false, value === undefined ? null : value);
+ this.syncValue();
+ },
+
+
+
+ /**
+ * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
+ * to insert tRoo.
+ * @param {String} text | dom node..
+ */
+ insertAtCursor : function(text)
+ {
+
+ if(!this.activated){
return;
}
-
-
-
- // 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') {
- // 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);
- }
-
-
- var updateFooter = sel ? false : true;
-
-
- var ans = this.editorcore.getAllAncestors();
-
- // pick
- var ty= Roo.form.HtmlEditor.ToolbarContext.types;
-
- if (!sel) {
- sel = ans.length ? (ans[0] ? ans[0] : ans[1]) : this.editorcore.doc.body;
- sel = sel ? sel : 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 lastSel = this.tb.selectedNode;
-
- this.tb.selectedNode = sel;
-
- // if current menu does not match..
-
- 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 + ': ';
-
+ if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
+ this.win.focus();
- // 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 hasStyles = false;
- for(var i in this.styles) {
- hasStyles = true;
- break;
- }
+ // from jquery ui (MIT licenced)
+ var range, node;
+ var win = this.win;
- // update styles
- if (hasStyles) {
- var st = this.tb.fields.item(0);
+ if (win.getSelection && win.getSelection().getRangeAt) {
- st.store.removeAll();
-
+ // 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);
- var cn = sel.className.split(/\s+/);
- var avs = [];
- if (this.styles['*']) {
+
+ } 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();
+ }
+ },
+ // private
+ mozKeyPress : function(e){
+ if(e.ctrlKey){
+ var c = e.getCharCode(), cmd;
+
+ if(c > 0){
+ c = String.fromCharCode(c).toLowerCase();
+ switch(c){
+ case 'b':
+ cmd = 'bold';
+ break;
+ case 'i':
+ cmd = 'italic';
+ break;
- Roo.each(this.styles['*'], function(v) {
- avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );
- });
+ case 'u':
+ cmd = 'underline';
+ break;
+
+ //case 'v':
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+
}
- if (this.styles[tn]) {
- Roo.each(this.styles[tn], function(v) {
- avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );
- });
+ if(cmd){
+
+ this.relayCmd(cmd);
+ //this.win.focus();
+ //this.execCmd(cmd);
+ //this.deferFocus();
+ e.preventDefault();
}
- st.store.loadData(avs);
- st.collapse();
- st.setValue(cn);
}
- // flag our selected Node.
- this.tb.selectedNode = sel;
-
-
- Roo.menu.MenuMgr.hideAll();
-
}
+ },
+
+ // private
+ fixKeys : function(){ // load time branching for fastest keydown performance
- if (!updateFooter) {
- //this.footDisp.dom.innerHTML = '';
- return;
- }
- // update the footer
- //
- var html = '';
- this.footerEls = ans.reverse();
- Roo.each(this.footerEls, function(a,i) {
- if (!a) { return; }
- html += html.length ? ' > ' : '';
-
- html += '<span class="x-ed-loc-' + i + '">' + a.tagName + '</span>';
-
- });
-
- //
- var sz = this.footDisp.up('td').getSize();
- this.footDisp.dom.style.width = (sz.width -10) + 'px';
- this.footDisp.dom.style.marginLeft = '5px';
+ if(Roo.isIE){
+ return function(e){
+ var k = e.getKey(), r;
+ if(k == e.TAB){
+ e.stopEvent();
+ r = this.doc.selection.createRange();
+ if(r){
+ r.collapse(true);
+ r.pasteHTML('    ');
+ this.deferFocus();
+ }
+ 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.collapse(false);
+ r.select();
+ }
+ }
+ }
+ */
+ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+ //}
+
+
+ };
+ }else if(Roo.isOpera){
+ return function(e){
+ var k = e.getKey();
+ if(k == e.TAB){
+ e.stopEvent();
+ this.win.focus();
+ this.execCmd('InsertHTML','    ');
+ this.deferFocus();
+ }
+
+ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+ //}
+
+ };
+ }else if(Roo.isSafari){
+ return function(e){
+ var k = e.getKey();
+
+ if(k == e.TAB){
+ e.stopEvent();
+ this.execCmd('InsertText','\t');
+ this.deferFocus();
+ return;
+ }
+ this.mozKeyPress(e);
+
+ //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
+ // this.cleanUpPaste.defer(100, this);
+ // return;
+ // }
+
+ };
+ }
+ }(),
+
+ getAllAncestors: function()
+ {
+ var p = this.getSelectedNode();
+ var a = [];
+ if (!p) {
+ a.push(p); // push blank onto stack..
+ p = this.getParentElement();
+ }
- this.footDisp.dom.style.overflow = 'hidden';
- this.footDisp.dom.innerHTML = html;
-
- //this.editorsyncValue();
+ while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
+ a.push(p);
+ p = p.parentNode;
+ }
+ a.push(this.doc.body);
+ return a;
},
-
+ lastSel : false,
+ lastSelNode : false,
-
-
- // private
- onDestroy : function(){
- if(this.rendered){
-
- this.tb.items.each(function(item){
- if(item.menu){
- item.menu.removeAll();
- if(item.menu.el){
- item.menu.el.destroy();
- }
- }
- item.destroy();
- });
-
- }
+
+ getSelection : function()
+ {
+ this.assignDocWin();
+ return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
},
- onFirstFocus: function() {
- // need to do this for all the toolbars..
- this.tb.items.each(function(item){
- item.enable();
- });
+ /**
+ * 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);
},
- buildToolbar: function(tlist, nm)
+
+ getSelectedNode: function()
{
- var editor = this.editor;
- var editorcore = this.editorcore;
- // create a new element.
- var wdiv = editor.wrap.createChild({
- tag: 'div'
- }, editor.wrap.dom.firstChild.nextSibling, true);
-
-
- var tb = new Roo.Toolbar(wdiv);
- // add the name..
+ // this may only work on Gecko!!!
- tb.add(nm+ ": ");
+ // should we cache this!!!!
- var styles = [];
- for(var i in this.styles) {
- styles.push(i);
- }
+
+
+ var range = this.createRange(this.getSelection()).cloneRange();
- // styles...
- if (styles && styles.length) {
-
- // this needs a multi-select checkbox...
- tb.addField( new Roo.form.ComboBox({
- store: new Roo.data.SimpleStore({
- id : 'val',
- fields: ['val', 'selected'],
- data : []
- }),
- name : '-roo-edit-className',
- attrname : 'className',
- displayField: 'val',
- typeAhead: false,
- mode: 'local',
- editable : false,
- triggerAction: 'all',
- emptyText:'Select Style',
- selectOnFocus:true,
- width: 130,
- listeners : {
- 'select': function(c, r, i) {
- // initial support only for on class per el..
- tb.selectedNode.className = r ? r.get('val') : '';
- editorcore.syncValue();
- }
+ if (Roo.isIE) {
+ var parent = range.parentElement();
+ while (true) {
+ var testRange = range.duplicate();
+ testRange.moveToElementText(parent);
+ if (testRange.inRange(range)) {
+ break;
}
-
- }));
+ if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
+ break;
+ }
+ parent = parent.parentElement;
+ }
+ return parent;
}
- var tbc = Roo.form.HtmlEditor.ToolbarContext;
- var tbops = tbc.options;
+ // is ancestor a text element.
+ var ac = range.commonAncestorContainer;
+ if (ac.nodeType == 3) {
+ ac = ac.parentNode;
+ }
- for (var i in tlist) {
-
- var item = tlist[i];
- tb.add(item.title + ": ");
-
+ var ar = ac.childNodes;
+
+ var nodes = [];
+ var other_nodes = [];
+ var has_other_nodes = false;
+ for (var i=0;i<ar.length;i++) {
+ if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ?
+ continue;
+ }
+ // fullly contained node.
- //optname == used so you can configure the options available..
- var opts = item.opts ? item.opts : false;
- if (item.optname) {
- opts = tbops[item.optname];
-
+ if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
+ nodes.push(ar[i]);
+ continue;
}
- if (opts) {
- // opts == pulldown..
- tb.addField( new Roo.form.ComboBox({
- store: typeof(tbc.stores[i]) != 'undefined' ? Roo.factory(tbc.stores[i],Roo.data) : new Roo.data.SimpleStore({
- id : 'val',
- fields: ['val', 'display'],
- data : opts
- }),
- name : '-roo-edit-' + i,
- attrname : i,
- stylename : item.style ? item.style : false,
- displayField: item.displayField ? item.displayField : 'val',
- valueField : 'val',
- typeAhead: false,
- mode: typeof(tbc.stores[i]) != 'undefined' ? 'remote' : 'local',
- editable : false,
- triggerAction: 'all',
- emptyText:'Select',
- selectOnFocus:true,
- width: item.width ? item.width : 130,
- listeners : {
- 'select': function(c, r, i) {
- if (c.stylename) {
- tb.selectedNode.style[c.stylename] = r.get('val');
- return;
- }
- tb.selectedNode.setAttribute(c.attrname, r.get('val'));
- }
- }
-
- }));
+ // probably selected..
+ if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
+ other_nodes.push(ar[i]);
continue;
-
-
-
- tb.addField( new Roo.form.TextField({
- name: i,
- width: 100,
- //allowBlank:false,
- value: ''
- }));
+ }
+ // outer..
+ if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0)) {
continue;
}
- tb.addField( new Roo.form.TextField({
- name: '-roo-edit-' + i,
- attrname : i,
-
- width: item.width,
- //allowBlank:true,
- value: '',
- listeners: {
- 'change' : function(f, nv, ov) {
- tb.selectedNode.setAttribute(f.attrname, nv);
- editorcore.syncValue();
- }
- }
- }));
-
+
+
+ has_other_nodes = true;
}
-
- var _this = this;
-
- if(nm == 'BODY'){
- tb.addSeparator();
-
- tb.addButton( {
- text: 'Stylesheets',
-
- listeners : {
- click : function ()
- {
- _this.editor.fireEvent('stylesheetsclick', _this.editor);
- }
- }
- });
+ if (!nodes.length && other_nodes.length) {
+ nodes= other_nodes;
+ }
+ if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
+ return false;
}
- tb.addFill();
- tb.addButton( {
- text: 'Remove Tag',
+ return nodes[0];
+ },
- 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);
-
- }
- 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 = '';
- }
+
+ createRange: function(sel)
+ {
+ // this has strange effects when using with
+ // top toolbar - not sure if it's a great idea.
+ //this.editor.contentWindow.focus();
+ if (typeof sel != "undefined") {
+ try {
+ return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
+ } catch(e) {
+ return this.doc.createRange();
}
-
-
-
-
- });
+ } else {
+ return this.doc.createRange();
+ }
+ },
+ getParentElement: function()
+ {
+ this.assignDocWin();
+ var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
- 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;
+ var range = this.createRange(sel);
-
+ try {
+ var p = range.commonAncestorContainer;
+ while (p.nodeType == 3) { // text node
+ p = p.parentNode;
+ }
+ return p;
+ } catch (e) {
+ return null;
+ }
+
},
- buildFooter : function()
+ /***
+ *
+ * Range intersection.. the hard stuff...
+ * '-1' = before
+ * '0' = hits..
+ * '1' = after.
+ * [ -- selected range --- ]
+ * [fail] [fail]
+ *
+ * basically..
+ * if end is before start or hits it. fail.
+ * if start is after end or hits it fail.
+ *
+ * if either hits (but other is outside. - then it's not
+ *
+ *
+ **/
+
+
+ // @see http://www.thismuchiknow.co.uk/?p=64.
+ rangeIntersectsNode : function(range, node)
{
+ var nodeRange = node.ownerDocument.createRange();
+ try {
+ nodeRange.selectNode(node);
+ } catch (e) {
+ nodeRange.selectNodeContents(node);
+ }
+
+ var rangeStartRange = range.cloneRange();
+ rangeStartRange.collapse(true);
+
+ var rangeEndRange = range.cloneRange();
+ rangeEndRange.collapse(false);
+
+ var nodeStartRange = nodeRange.cloneRange();
+ nodeStartRange.collapse(true);
+
+ var nodeEndRange = nodeRange.cloneRange();
+ nodeEndRange.collapse(false);
+
+ return rangeStartRange.compareBoundaryPoints(
+ Range.START_TO_START, nodeEndRange) == -1 &&
+ rangeEndRange.compareBoundaryPoints(
+ Range.START_TO_START, nodeStartRange) == 1;
- var fel = this.editor.wrap.createChild();
- this.footer = new Roo.Toolbar(fel);
- // toolbar has scrolly on left / right?
- var footDisp= new Roo.Toolbar.Fill();
- var _t = this;
- this.footer.add(
- {
- text : '<',
- xtype: 'Button',
- handler : function() {
- _t.footDisp.scrollTo('left',0,true)
- }
- }
- );
- this.footer.add( footDisp );
- this.footer.add(
- {
- text : '>',
- xtype: 'Button',
- handler : function() {
- // no animation..
- _t.footDisp.select('span').last().scrollIntoView(_t.footDisp,true);
- }
- }
- );
- var fel = Roo.get(footDisp.el);
- fel.addClass('x-editor-context');
- this.footDispWrap = fel;
- this.footDispWrap.overflow = 'hidden';
-
- this.footDisp = fel.createChild();
- this.footDispWrap.on('click', this.onContextClick, this)
-
-
+
},
- onContextClick : function (ev,dom)
+ rangeCompareNode : function(range, node)
{
- ev.preventDefault();
- var cn = dom.className;
- //Roo.log(cn);
- if (!cn.match(/x-ed-loc-/)) {
- return;
+ var nodeRange = node.ownerDocument.createRange();
+ try {
+ nodeRange.selectNode(node);
+ } catch (e) {
+ nodeRange.selectNodeContents(node);
}
- var n = cn.split('-').pop();
- 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);
+ range.collapse(true);
+
+ nodeRange.collapse(true);
+
+ var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
+ var ee = range.compareBoundaryPoints( Range.END_TO_END, nodeRange);
+
+ //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
+ var nodeIsBefore = ss == 1;
+ var nodeIsAfter = ee == -1;
+ if (nodeIsBefore && nodeIsAfter) {
+ return 0; // outer
+ }
+ if (!nodeIsBefore && nodeIsAfter) {
+ return 1; //right trailed.
+ }
- this.updateToolbar(null, null, sel);
+ if (nodeIsBefore && !nodeIsAfter) {
+ return 2; // left trailed.
+ }
+ // fully contined.
+ return 3;
+ },
+
+ cleanWordChars : function(input) {// change the chars to hex code
+ var swapCodes = [
+ [ 8211, "–" ],
+ [ 8212, "—" ],
+ [ 8216, "'" ],
+ [ 8217, "'" ],
+ [ 8220, '"' ],
+ [ 8221, '"' ],
+ [ 8226, "*" ],
+ [ 8230, "..." ]
+ ];
+ var output = input;
+ 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;
+ },
+
+
+ cleanUpChild : function (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)
+ {
+ new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
+ new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
+
+ },
+
-});
-
-
-
-
+ /**
-/*
- * 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">
- */
-
-/**
- * @class Roo.form.BasicForm
- * @extends Roo.util.Observable
- * Supplies the functionality to do "actions" on forms and initialize Roo.form.Field types on existing markup.
- * @constructor
- * @param {String/HTMLElement/Roo.Element} el The form element or its id
- * @param {Object} config Configuration options
- */
-Roo.form.BasicForm = function(el, config){
- this.allItems = [];
- this.childForms = [];
- Roo.apply(this, config);
- /*
- * The Roo.form.Field items in this form.
- * @type MixedCollection
+ * @deprecated - use filters
*/
+ cleanTableWidths : function(node)
+ {
+ new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
+
+
+ },
+
+
+ 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) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ this.white.push(tag);
+
+ }, this);
+
+ Roo.each(w, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.white.indexOf(tag) > -1) {
+ return;
+ }
+ this.white.push(tag);
+
+ }, this);
+
+
+ Roo.each(Roo.HtmlEditorCore.black, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ this.black.push(tag);
+
+ }, this);
+
+ Roo.each(b, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.black.indexOf(tag) > -1) {
+ return;
+ }
+ this.black.push(tag);
+
+ }, this);
+
+
+ w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite : [];
+ b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack : [];
+
+ this.cwhite = [];
+ this.cblack = [];
+ Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ this.cwhite.push(tag);
+
+ }, this);
+
+ Roo.each(w, function(tag) {
+ if (b.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.cwhite.indexOf(tag) > -1) {
+ return;
+ }
+ this.cwhite.push(tag);
+
+ }, this);
+
+
+ Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ this.cblack.push(tag);
+
+ }, this);
+
+ Roo.each(b, function(tag) {
+ if (w.indexOf(tag) > -1) {
+ return;
+ }
+ if (this.cblack.indexOf(tag) > -1) {
+ return;
+ }
+ this.cblack.push(tag);
+
+ }, this);
+ },
+
+ setStylesheets : function(stylesheets)
+ {
+ if(typeof(stylesheets) == 'string'){
+ Roo.get(this.iframe.contentDocument.head).createChild({
+ tag : 'link',
+ rel : 'stylesheet',
+ type : 'text/css',
+ href : stylesheets
+ });
+
+ return;
+ }
+ var _this = this;
- this.items = new Roo.util.MixedCollection(false, function(o){
- return o.id || (o.id = Roo.id());
- });
- this.addEvents({
- /**
- * @event beforeaction
- * Fires before any action is performed. Return false to cancel the action.
- * @param {Form} this
- * @param {Action} action The action to be performed
- */
- beforeaction: true,
- /**
- * @event actionfailed
- * Fires when an action fails.
- * @param {Form} this
- * @param {Action} action The action that failed
- */
- actionfailed : true,
- /**
- * @event actioncomplete
- * Fires when an action is completed.
- * @param {Form} this
- * @param {Action} action The action that completed
- */
- actioncomplete : true
- });
- if(el){
- this.initEl(el);
- }
- Roo.form.BasicForm.superclass.constructor.call(this);
+ Roo.each(stylesheets, function(s) {
+ if(!s.length){
+ return;
+ }
+
+ Roo.get(_this.iframe.contentDocument.head).createChild({
+ tag : 'link',
+ rel : 'stylesheet',
+ type : 'text/css',
+ href : s
+ });
+ });
+
+
+ },
- Roo.form.BasicForm.popover.apply();
-};
+
+ 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.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
+ s.remove();
+ });
+ },
+
+ setStyle : function(style)
+ {
+ Roo.get(this.iframe.contentDocument.head).createChild({
+ tag : 'style',
+ type : 'text/css',
+ html : style
+ });
-Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
+ return;
+ }
+
+ // hide stuff that is not compatible
/**
- * @cfg {String} method
- * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
+ * @event blur
+ * @hide
*/
/**
- * @cfg {DataReader} reader
- * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
- * This is optional as there is built-in support for processing JSON.
+ * @event change
+ * @hide
*/
/**
- * @cfg {DataReader} errorReader
- * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
- * This is completely optional as there is built-in support for processing JSON.
+ * @event focus
+ * @hide
*/
/**
- * @cfg {String} url
- * The URL to use for form actions if one isn't supplied in the action options.
+ * @event specialkey
+ * @hide
*/
/**
- * @cfg {Boolean} fileUpload
- * Set to true if this form is a file upload.
+ * @cfg {String} fieldClass @hide
*/
-
/**
- * @cfg {Object} baseParams
- * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
+ * @cfg {String} focusClass @hide
*/
- /**
-
/**
- * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
+ * @cfg {String} autoCreate @hide
*/
- timeout: 30,
-
- // private
- activeAction : null,
-
/**
- * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
- * or setValues() data instead of when the form was first created.
+ * @cfg {String} inputType @hide
*/
- trackResetOnLoad : false,
-
-
/**
- * childForms - used for multi-tab forms
- * @type {Array}
+ * @cfg {String} invalidClass @hide
*/
- childForms : false,
-
/**
- * allItems - full list of fields.
- * @type {Array}
+ * @cfg {String} invalidText @hide
*/
- allItems : false,
+ /**
+ * @cfg {String} msgFx @hide
+ */
+ /**
+ * @cfg {String} validateOnBlur @hide
+ */
+});
+
+Roo.HtmlEditorCore.white = [
+ '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',
+
+ 'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH',
+ 'THEAD', 'TR',
+
+ 'DIR', 'MENU', 'OL', 'UL', 'DL',
+
+ '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',
+ //'FONT' // CLEAN LATER..
+ 'COLGROUP', 'COL' // messy tables.
+
+
+];
+Roo.HtmlEditorCore.clean = [ // ?? needed???
+ 'SCRIPT', 'STYLE', 'TITLE', 'XML'
+];
+Roo.HtmlEditorCore.tag_remove = [
+ 'FONT', 'TBODY'
+];
+// attributes..
+
+Roo.HtmlEditorCore.ablack = [
+ 'on'
+];
+
+Roo.HtmlEditorCore.aclean = [
+ 'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc'
+];
+
+// protocols..
+Roo.HtmlEditorCore.pwhite= [
+ 'http', 'https', 'mailto'
+];
+
+// white listed style attributes.
+Roo.HtmlEditorCore.cwhite= [
+ // 'text-align', /// default is to allow most things..
+
+
+// 'font-size'//??
+];
+
+// black listed style attributes.
+Roo.HtmlEditorCore.cblack= [
+ // 'font-size' -- this can be set by the project
+];
+
+
+
+
+ //<script type="text/javascript">
+
+/*
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ * Licence LGPL
+ *
+ */
+
+
+Roo.form.HtmlEditor = function(config){
+
+
+
+ Roo.form.HtmlEditor.superclass.constructor.call(this, config);
+ if (!this.toolbars) {
+ this.toolbars = [];
+ }
+ this.editorcore = new Roo.HtmlEditorCore(Roo.apply({ owner : this} , config));
+
+
+};
+
+/**
+ * @class Roo.form.HtmlEditor
+ * @extends Roo.form.Field
+ * Provides a lightweight HTML Editor component.
+ *
+ * This has been tested on Fireforx / Chrome.. IE may not be so great..
+ *
+ * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
+ * supported by this editor.</b><br/><br/>
+ * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
+ * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
+ */
+Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, {
/**
- * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
- * element by passing it or its id or mask the form itself by passing in true.
- * @type Mixed
+ * @cfg {Boolean} clearUp
*/
- waitMsgTarget : false,
+ clearUp : true,
+ /**
+ * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
+ */
+ toolbars : false,
+
+ /**
+ * @cfg {String} resizable 's' or 'se' or 'e' - wrapps the element in a
+ * Roo.resizable.
+ */
+ resizable : false,
+ /**
+ * @cfg {Number} height (in pixels)
+ */
+ height: 300,
+ /**
+ * @cfg {Number} width (in pixels)
+ */
+ width: 500,
/**
- * @type Boolean
+ * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets - this is usally a good idea rootURL + '/roojs1/css/undoreset.css', .
+ *
*/
- disableMask : false,
+ stylesheets: false,
+
+ /**
+ * @cfg {Array} blacklist of css styles style attributes (blacklist overrides whitelist)
+ *
+ */
+ cblack: false,
/**
- * @cfg {Boolean} errorMask (true|false) default false
+ * @cfg {Array} whitelist of css styles style attributes (blacklist overrides whitelist)
+ *
*/
- errorMask : false,
+ cwhite: false,
+ /**
+ * @cfg {Array} blacklist of html tags - in addition to standard blacklist.
+ *
+ */
+ black: false,
/**
- * @cfg {Number} maskOffset Default 100
+ * @cfg {Array} whitelist of html tags - in addition to statndard whitelist
+ *
*/
- maskOffset : 100,
-
- // private
- initEl : function(el){
- this.el = Roo.get(el);
- this.id = this.el.id || Roo.id();
- this.el.on('submit', this.onSubmit, this);
- this.el.addClass('x-form');
- },
-
- // private
- onSubmit : function(e){
- e.stopEvent();
- },
-
+ white: false,
/**
- * Returns true if client-side validation on the form is successful.
- * @return Boolean
+ * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
*/
- isValid : function(){
- var valid = true;
- var target = false;
- this.items.each(function(f){
- if(f.validate()){
- return;
- }
-
- valid = false;
-
- if(!target && f.el.isVisible(true)){
- target = f;
- }
- });
-
- if(this.errorMask && !valid){
- Roo.form.BasicForm.popover.mask(this, target);
- }
-
- return valid;
- },
+ allowComments: false,
/**
- * Returns array of invalid form fields.
- * @return Array
+ * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
*/
-
- invalidFields : function()
- {
- var ret = [];
- this.items.each(function(f){
- if(f.validate()){
- return;
- }
- ret.push(f);
-
- });
-
- return ret;
- },
-
+ enableBlocks : true,
/**
- * DEPRICATED Returns true if any fields in this form have changed since their original load.
- * @return Boolean
+ * @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
*/
- isDirty : function(){
- var dirty = false;
- this.items.each(function(f){
- if(f.isDirty()){
- dirty = true;
- return false;
- }
- });
- return dirty;
- },
-
+ autoClean: true,
/**
- * Returns true if any fields in this form have changed since their original load. (New version)
- * @return Boolean
+ * @cfg {string} bodyCls default '' default classes to add to body of editable area - usually undoreset is a good start..
*/
-
- hasChanged : function()
- {
- var dirty = false;
- this.items.each(function(f){
- if(f.hasChanged()){
- dirty = true;
- return false;
- }
- });
- return dirty;
-
- },
+ bodyCls : '',
/**
- * Resets all hasChanged to 'false' -
- * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
- * So hasChanged storage is only to be used for this purpose
- * @return Boolean
+ * @cfg {String} language default en - language of text (usefull for rtl languages)
+ *
*/
- resetHasChanged : function()
- {
- this.items.each(function(f){
- f.resetHasChanged();
- });
-
- },
+ language: 'en',
+
+ // id of frame..
+ frameId: false,
- /**
- * Performs a predefined action (submit or load) or custom actions you define on this form.
- * @param {String} actionName The name of the action type
- * @param {Object} options (optional) The options to pass to the action. All of the config options listed
- * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
- * accept other config options):
- * <pre>
-Property Type Description
----------------- --------------- ----------------------------------------------------------------------------------
-url String The url for the action (defaults to the form's url)
-method String The form method to use (defaults to the form's method, or POST if not defined)
-params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
-clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
- validate the form on the client (defaults to false)
- * </pre>
- * @return {BasicForm} this
- */
- doAction : function(action, options){
- if(typeof action == 'string'){
- action = new Roo.form.Action.ACTION_TYPES[action](this, options);
- }
- if(this.fireEvent('beforeaction', this, action) !== false){
- this.beforeAction(action);
- action.run.defer(100, action);
- }
- return this;
- },
-
- /**
- * Shortcut to do a submit action.
- * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
- * @return {BasicForm} this
- */
- submit : function(options){
- this.doAction('submit', options);
- return this;
+ // private properties
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+
+ onFocus : Roo.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+
+ actionMode : 'container', // defaults to hiding it...
+
+ defaultAutoCreate : { // modified by initCompnoent..
+ tag: "textarea",
+ style:"width:500px;height:300px;",
+ autocomplete: "new-password"
},
- /**
- * Shortcut to do a load action.
- * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
- * @return {BasicForm} this
- */
- load : function(options){
- this.doAction('load', options);
- return this;
+ // private
+ initComponent : function(){
+ this.addEvents({
+ /**
+ * @event initialize
+ * Fires when the editor is fully initialized (including the iframe)
+ * @param {HtmlEditor} this
+ */
+ initialize: true,
+ /**
+ * @event activate
+ * Fires when the editor is first receives the focus. Any insertion must wait
+ * until after this event.
+ * @param {HtmlEditor} this
+ */
+ activate: true,
+ /**
+ * @event beforesync
+ * Fires before the textarea is updated with content from the editor iframe. Return false
+ * to cancel the sync.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ beforesync: true,
+ /**
+ * @event beforepush
+ * Fires before the iframe editor is updated with content from the textarea. Return false
+ * to cancel the push.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ beforepush: true,
+ /**
+ * @event sync
+ * Fires when the textarea is updated with content from the editor iframe.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ sync: true,
+ /**
+ * @event push
+ * Fires when the iframe editor is updated with content from the textarea.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ push: true,
+ /**
+ * @event editmodechange
+ * Fires when the editor switches edit modes
+ * @param {HtmlEditor} this
+ * @param {Boolean} sourceEdit True if source edit, false if standard editing.
+ */
+ editmodechange: true,
+ /**
+ * @event editorevent
+ * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
+ * @param {HtmlEditor} this
+ */
+ editorevent: true,
+ /**
+ * @event firstfocus
+ * Fires when on first focus - needed by toolbars..
+ * @param {HtmlEditor} this
+ */
+ firstfocus: true,
+ /**
+ * @event autosave
+ * Auto save the htmlEditor value as a file into Events
+ * @param {HtmlEditor} this
+ */
+ autosave: true,
+ /**
+ * @event savedpreview
+ * preview the saved version of htmlEditor
+ * @param {HtmlEditor} this
+ */
+ savedpreview: true,
+
+ /**
+ * @event stylesheetsclick
+ * Fires when press the Sytlesheets button
+ * @param {Roo.HtmlEditorCore} this
+ */
+ stylesheetsclick: true,
+ /**
+ * @event paste
+ * Fires when press user pastes into the editor
+ * @param {Roo.HtmlEditorCore} this
+ */
+ paste: true
+ });
+ this.defaultAutoCreate = {
+ tag: "textarea",
+ style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
+ autocomplete: "new-password"
+ };
},
/**
- * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
- * @param {Record} record The record to edit
- * @return {BasicForm} this
+ * Protected method that will not generally be called directly. It
+ * is called when the editor creates its toolbar. Override this method if you need to
+ * add custom toolbar buttons.
+ * @param {HtmlEditor} editor
*/
- updateRecord : function(record){
- record.beginEdit();
- var fs = record.fields;
- fs.each(function(f){
- var field = this.findField(f.name);
- if(field){
- record.set(f.name, field.getValue());
- }
- }, this);
- record.endEdit();
- return this;
+ createToolbar : function(editor){
+ Roo.log("create toolbars");
+ if (!editor.toolbars || !editor.toolbars.length) {
+ editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
+ }
+
+ for (var i =0 ; i < editor.toolbars.length;i++) {
+ editor.toolbars[i] = Roo.factory(
+ typeof(editor.toolbars[i]) == 'string' ?
+ { xtype: editor.toolbars[i]} : editor.toolbars[i],
+ Roo.form.HtmlEditor);
+ editor.toolbars[i].init(editor);
+ }
+
+
},
-
/**
- * Loads an Roo.data.Record into this form.
- * @param {Record} record The record to load
- * @return {BasicForm} this
+ * get the Context selected node
+ * @returns {DomElement|boolean} selected node if active or false if none
+ *
*/
- loadRecord : function(record){
- this.setValues(record.data);
- return this;
+ getSelectedNode : function()
+ {
+ if (this.toolbars.length < 2 || !this.toolbars[1].tb) {
+ return false;
+ }
+ return this.toolbars[1].tb.selectedNode;
+
},
-
// private
- beforeAction : function(action){
- var o = action.options;
+ onRender : function(ct, position)
+ {
+ var _t = this;
+ Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
- if(!this.disableMask) {
- if(this.waitMsgTarget === true){
- this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
- }else if(this.waitMsgTarget){
- this.waitMsgTarget = Roo.get(this.waitMsgTarget);
- this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
- }else {
- Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
- }
- }
+ this.wrap = this.el.wrap({
+ cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
+ });
+ this.editorcore.onRender(ct, position);
- },
-
- // private
- afterAction : function(action, success){
- this.activeAction = null;
- var o = action.options;
+ if (this.resizable) {
+ this.resizeEl = new Roo.Resizable(this.wrap, {
+ pinned : true,
+ wrap: true,
+ dynamic : true,
+ minHeight : this.height,
+ height: this.height,
+ handles : this.resizable,
+ width: this.width,
+ listeners : {
+ resize : function(r, w, h) {
+ _t.onResize(w,h); // -something
+ }
+ }
+ });
+
+ }
+ this.createToolbar(this);
+
- if(!this.disableMask) {
- if(this.waitMsgTarget === true){
- this.el.unmask();
- }else if(this.waitMsgTarget){
- this.waitMsgTarget.unmask();
- }else{
- Roo.MessageBox.updateProgress(1);
- Roo.MessageBox.hide();
- }
+ if(!this.width){
+ this.setSize(this.wrap.getSize());
+ }
+ if (this.resizeEl) {
+ this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
+ // should trigger onReize..
}
- if(success){
- if(o.reset){
- this.reset();
- }
- Roo.callback(o.success, o.scope, [this, action]);
- this.fireEvent('actioncomplete', this, action);
-
- }else{
+ this.keyNav = new Roo.KeyNav(this.el, {
- // failure condition..
- // we have a scenario where updates need confirming.
- // eg. if a locking scenario exists..
- // we look for { errors : { needs_confirm : true }} in the response.
- if (
- (typeof(action.result) != 'undefined') &&
- (typeof(action.result.errors) != 'undefined') &&
- (typeof(action.result.errors.needs_confirm) != 'undefined')
- ){
- var _t = this;
- Roo.MessageBox.confirm(
- "Change requires confirmation",
- action.result.errorMsg,
- function(r) {
- if (r != 'yes') {
- return;
- }
- _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
- }
+ "tab" : function(e){
+ e.preventDefault();
+
+ var value = this.getValue();
+
+ var start = this.el.dom.selectionStart;
+ var end = this.el.dom.selectionEnd;
+
+ if(!e.shiftKey){
- );
+ this.setValue(value.substring(0, start) + "\t" + value.substring(end));
+ this.el.dom.setSelectionRange(end + 1, end + 1);
+ return;
+ }
+ var f = value.substring(0, start).split("\t");
+
+ if(f.pop().length != 0){
+ return;
+ }
+ this.setValue(f.join("\t") + value.substring(end));
+ this.el.dom.setSelectionRange(start - 1, start - 1);
- return;
- }
+ },
- Roo.callback(o.failure, o.scope, [this, action]);
- // show an error message if no failed handler is set..
- if (!this.hasListener('actionfailed')) {
- Roo.MessageBox.alert("Error",
- (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
- action.result.errorMsg :
- "Saving Failed, please check your entries or try again"
- );
- }
+ "home" : function(e){
+ e.preventDefault();
+
+ var curr = this.el.dom.selectionStart;
+ var lines = this.getValue().split("\n");
+
+ if(!lines.length){
+ return;
+ }
+
+ if(e.ctrlKey){
+ this.el.dom.setSelectionRange(0, 0);
+ return;
+ }
+
+ var pos = 0;
+
+ for (var i = 0; i < lines.length;i++) {
+ pos += lines[i].length;
+
+ if(i != 0){
+ pos += 1;
+ }
+
+ if(pos < curr){
+ continue;
+ }
+
+ pos -= lines[i].length;
+
+ break;
+ }
+
+ if(!e.shiftKey){
+ this.el.dom.setSelectionRange(pos, pos);
+ return;
+ }
+
+ this.el.dom.selectionStart = pos;
+ this.el.dom.selectionEnd = curr;
+ },
- this.fireEvent('actionfailed', this, action);
- }
+ "end" : function(e){
+ e.preventDefault();
+
+ var curr = this.el.dom.selectionStart;
+ var lines = this.getValue().split("\n");
+
+ if(!lines.length){
+ return;
+ }
+
+ if(e.ctrlKey){
+ this.el.dom.setSelectionRange(this.getValue().length, this.getValue().length);
+ return;
+ }
+
+ var pos = 0;
+
+ for (var i = 0; i < lines.length;i++) {
+
+ pos += lines[i].length;
+
+ if(i != 0){
+ pos += 1;
+ }
+
+ if(pos < curr){
+ continue;
+ }
+
+ break;
+ }
+
+ if(!e.shiftKey){
+ this.el.dom.setSelectionRange(pos, pos);
+ return;
+ }
+
+ this.el.dom.selectionStart = curr;
+ this.el.dom.selectionEnd = pos;
+ },
+
+ scope : this,
+
+ doRelay : function(foo, bar, hname){
+ return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
+ },
+
+ forceKeyDown: true
+ });
+// if(this.autosave && this.w){
+// this.autoSaveFn = setInterval(this.autosave, 1000);
+// }
},
- /**
- * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
- * @param {String} id The value to search for
- * @return Field
- */
- findField : function(id){
- var field = this.items.get(id);
- if(!field){
- this.items.each(function(f){
- if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
- field = f;
- return false;
+ // private
+ onResize : function(w, h)
+ {
+ Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
+ var ew = false;
+ var eh = false;
+
+ if(this.el ){
+ if(typeof w == 'number'){
+ var aw = w - this.wrap.getFrameWidth('lr');
+ this.el.setWidth(this.adjustWidth('textarea', aw));
+ ew = aw;
+ }
+ if(typeof h == 'number'){
+ var tbh = 0;
+ for (var i =0; i < this.toolbars.length;i++) {
+ // fixme - ask toolbars for heights?
+ tbh += this.toolbars[i].tb.el.getHeight();
+ if (this.toolbars[i].footer) {
+ tbh += this.toolbars[i].footer.el.getHeight();
+ }
}
- });
+
+
+
+
+ var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
+ ah -= 5; // knock a few pixes off for look..
+// Roo.log(ah);
+ this.el.setHeight(this.adjustWidth('textarea', ah));
+ var eh = ah;
+ }
}
- return field || null;
+ Roo.log('onResize:' + [w,h,ew,eh].join(',') );
+ this.editorcore.onResize(ew,eh);
+
},
/**
- * Add a secondary form to this one,
- * Used to provide tabbed forms. One form is primary, with hidden values
- * which mirror the elements from the other forms.
- *
- * @param {Roo.form.Form} form to add.
- *
+ * Toggles the editor between standard and source edit mode.
+ * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
*/
- addForm : function(form)
+ toggleSourceEdit : function(sourceEditMode)
{
-
- if (this.childForms.indexOf(form) > -1) {
- // already added..
- return;
- }
- this.childForms.push(form);
- var n = '';
- Roo.each(form.allItems, function (fe) {
+ this.editorcore.toggleSourceEdit(sourceEditMode);
+
+ if(this.editorcore.sourceEditMode){
+ Roo.log('editor - showing textarea');
- n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
- if (this.findField(n)) { // already added..
- return;
- }
- var add = new Roo.form.Hidden({
- name : n
- });
- add.render(this.el);
+// Roo.log('in');
+// Roo.log(this.syncValue());
+ this.editorcore.syncValue();
+ this.el.removeClass('x-hidden');
+ this.el.dom.removeAttribute('tabIndex');
+ this.el.focus();
+ this.el.dom.scrollTop = 0;
- this.add( add );
- }, this);
-
- },
- /**
- * Mark fields in this form invalid in bulk.
- * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
- * @return {BasicForm} this
- */
- markInvalid : function(errors){
- if(errors instanceof Array){
- for(var i = 0, len = errors.length; i < len; i++){
- var fieldError = errors[i];
- var f = this.findField(fieldError.id);
- if(f){
- f.markInvalid(fieldError.msg);
+
+ for (var i = 0; i < this.toolbars.length; i++) {
+ if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
+ this.toolbars[i].tb.hide();
+ this.toolbars[i].footer.hide();
}
}
+
}else{
- var field, id;
- for(id in errors){
- if(typeof errors[id] != 'function' && (field = this.findField(id))){
- field.markInvalid(errors[id]);
- }
- }
- }
- Roo.each(this.childForms || [], function (f) {
- f.markInvalid(errors);
- });
-
- return this;
- },
-
- /**
- * Set values for fields in this form in bulk.
- * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
- * @return {BasicForm} this
- */
- setValues : function(values){
- if(values instanceof Array){ // array of objects
- for(var i = 0, len = values.length; i < len; i++){
- var v = values[i];
- var f = this.findField(v.id);
- if(f){
- f.setValue(v.value);
- if(this.trackResetOnLoad){
- f.originalValue = f.getValue();
- }
- }
- }
- }else{ // object hash
- var field, id;
- for(id in values){
- if(typeof values[id] != 'function' && (field = this.findField(id))){
-
- if (field.setFromData &&
- field.valueField &&
- field.displayField &&
- // combos' with local stores can
- // be queried via setValue()
- // to set their value..
- (field.store && !field.store.isLocal)
- ) {
- // it's a combo
- var sd = { };
- sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
- sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
- field.setFromData(sd);
-
- } else {
- field.setValue(values[id]);
- }
-
-
- if(this.trackResetOnLoad){
- field.originalValue = field.getValue();
- }
+ Roo.log('editor - hiding textarea');
+// Roo.log('out')
+// Roo.log(this.pushValue());
+ this.editorcore.pushValue();
+
+ this.el.addClass('x-hidden');
+ this.el.dom.setAttribute('tabIndex', -1);
+
+ for (var i = 0; i < this.toolbars.length; i++) {
+ if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
+ this.toolbars[i].tb.show();
+ this.toolbars[i].footer.show();
}
}
- }
- this.resetHasChanged();
-
-
- Roo.each(this.childForms || [], function (f) {
- f.setValues(values);
- f.resetHasChanged();
- });
-
- 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){
- if (this.childForms) {
- // copy values from the child forms
- Roo.each(this.childForms, function (f) {
- this.setValues(f.getValues());
- }, 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) {
-
- }
+ //this.deferFocus();
}
+ this.setSize(this.wrap.getSize());
+ this.onResize(this.wrap.getSize().width, this.wrap.getSize().height);
- var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
- if(asString === true){
- return fs;
- }
- return Roo.urlDecode(fs);
- },
-
- /**
- * 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.
- * @return {Object}
- */
- getFieldValues : function(with_hidden)
- {
- 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);
- }
-
- var ret = {};
- this.items.each(function(f){
- if (!f.getName()) {
- return;
- }
- var v = f.getValue();
- if (f.inputType =='radio') {
- if (typeof(ret[f.getName()]) == 'undefined') {
- ret[f.getName()] = ''; // empty..
- }
-
- if (!f.el.dom.checked) {
- return;
-
- }
- v = f.el.dom.value;
-
- }
-
- // not sure if this supported any more..
- if ((typeof(v) == 'object') && f.getRawValue) {
- v = f.getRawValue() ; // dates..
- }
- // combo boxes where name != hiddenName...
- if (f.name != f.getName()) {
- ret[f.name] = f.getRawValue();
- }
- ret[f.getName()] = v;
- });
-
- return ret;
+ this.fireEvent('editmodechange', this, this.editorcore.sourceEditMode);
},
+
+ // private (for BoxComponent)
+ adjustSize : Roo.BoxComponent.prototype.adjustSize,
- /**
- * Clears all invalid messages in this form.
- * @return {BasicForm} this
- */
- clearInvalid : function(){
- this.items.each(function(f){
- f.clearInvalid();
- });
-
- Roo.each(this.childForms || [], function (f) {
- f.clearInvalid();
- });
-
-
- return this;
+ // private (for BoxComponent)
+ getResizeEl : function(){
+ return this.wrap;
},
- /**
- * Resets this form.
- * @return {BasicForm} this
- */
- reset : function(){
- this.items.each(function(f){
- f.reset();
- });
-
- Roo.each(this.childForms || [], function (f) {
- f.reset();
- });
- this.resetHasChanged();
-
- return this;
+ // private (for BoxComponent)
+ getPositionEl : function(){
+ return this.wrap;
},
- /**
- * Add Roo.form components to this form.
- * @param {Field} field1
- * @param {Field} field2 (optional)
- * @param {Field} etc (optional)
- * @return {BasicForm} this
- */
- add : function(){
- this.items.addAll(Array.prototype.slice.call(arguments, 0));
- return this;
+ // private
+ initEvents : function(){
+ this.originalValue = this.getValue();
},
-
/**
- * Removes a field from the items collection (does NOT remove its markup).
- * @param {Field} field
- * @return {BasicForm} this
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
*/
- remove : function(field){
- this.items.remove(field);
- return this;
- },
-
+ markInvalid : Roo.emptyFn,
/**
- * Looks at the fields in this form, checks them for an id attribute,
- * and calls applyTo on the existing dom element with that id.
- * @return {BasicForm} this
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
*/
- render : function(){
- this.items.each(function(f){
- if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
- f.applyTo(f.id);
- }
- });
- return this;
- },
+ clearInvalid : Roo.emptyFn,
- /**
- * Calls {@link Ext#apply} for all fields in this form with the passed object.
- * @param {Object} values
- * @return {BasicForm} this
- */
- applyToFields : function(o){
- this.items.each(function(f){
- Roo.apply(f, o);
- });
- return this;
+ setValue : function(v){
+ Roo.form.HtmlEditor.superclass.setValue.call(this, v);
+ this.editorcore.pushValue();
},
/**
- * Calls {@link Ext#applyIf} for all field in this form with the passed object.
- * @param {Object} values
- * @return {BasicForm} this
+ * update the language in the body - really done by core
+ * @param {String} language - eg. en / ar / zh-CN etc..
*/
- applyIfToFields : function(o){
- this.items.each(function(f){
- Roo.applyIf(f, o);
- });
- return this;
- }
-});
-
-// back compat
-Roo.BasicForm = Roo.form.BasicForm;
+ updateLanguage : function(lang)
+ {
+ this.language = lang;
+ this.editorcore.language = lang;
+ this.editorcore.updateLanguage();
+
+ },
+ // private
+ deferFocus : function(){
+ this.focus.defer(10, this);
+ },
-Roo.apply(Roo.form.BasicForm, {
-
- popover : {
-
- padding : 5,
-
- isApplied : false,
-
- isMasked : false,
-
- form : false,
-
- target : false,
+ // doc'ed in Field
+ focus : function(){
+ this.editorcore.focus();
- intervalID : false,
+ },
+
+
+ // private
+ onDestroy : function(){
- maskEl : false,
- apply : function()
- {
- if(this.isApplied){
- return;
- }
-
- this.maskEl = {
- top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
- left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
- bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
- right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
- };
-
- this.maskEl.top.enableDisplayMode("block");
- this.maskEl.left.enableDisplayMode("block");
- this.maskEl.bottom.enableDisplayMode("block");
- this.maskEl.right.enableDisplayMode("block");
-
- Roo.get(document.body).on('click', function(){
- this.unmask();
- }, this);
-
- Roo.get(document.body).on('touchstart', function(){
- this.unmask();
- }, this);
-
- this.isApplied = true
- },
- mask : function(form, target)
- {
- this.form = form;
-
- this.target = target;
-
- if(!this.form.errorMask || !target.el){
- return;
- }
-
- var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
-
- var ot = this.target.el.calcOffsetsTo(scrollable);
-
- var scrollTo = ot[1] - this.form.maskOffset;
-
- scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
-
- scrollable.scrollTo('top', scrollTo);
-
- var el = this.target.wrap || this.target.el;
-
- var box = el.getBox();
-
- this.maskEl.top.setStyle('position', 'absolute');
- this.maskEl.top.setStyle('z-index', 10000);
- this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
- this.maskEl.top.setLeft(0);
- this.maskEl.top.setTop(0);
- this.maskEl.top.show();
-
- this.maskEl.left.setStyle('position', 'absolute');
- this.maskEl.left.setStyle('z-index', 10000);
- this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
- this.maskEl.left.setLeft(0);
- this.maskEl.left.setTop(box.y - this.padding);
- this.maskEl.left.show();
-
- this.maskEl.bottom.setStyle('position', 'absolute');
- this.maskEl.bottom.setStyle('z-index', 10000);
- this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
- this.maskEl.bottom.setLeft(0);
- this.maskEl.bottom.setTop(box.bottom + this.padding);
- this.maskEl.bottom.show();
-
- this.maskEl.right.setStyle('position', 'absolute');
- this.maskEl.right.setStyle('z-index', 10000);
- this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
- this.maskEl.right.setLeft(box.right + this.padding);
- this.maskEl.right.setTop(box.y - this.padding);
- this.maskEl.right.show();
-
- this.intervalID = window.setInterval(function() {
- Roo.form.BasicForm.popover.unmask();
- }, 10000);
-
- window.onwheel = function(){ return false;};
-
- (function(){ this.isMasked = true; }).defer(500, this);
+ if(this.rendered){
- },
-
- unmask : function()
- {
- if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
- return;
+ for (var i =0; i < this.toolbars.length;i++) {
+ // fixme - ask toolbars for heights?
+ this.toolbars[i].onDestroy();
}
- this.maskEl.top.setStyle('position', 'absolute');
- this.maskEl.top.setSize(0, 0).setXY([0, 0]);
- this.maskEl.top.hide();
-
- this.maskEl.left.setStyle('position', 'absolute');
- this.maskEl.left.setSize(0, 0).setXY([0, 0]);
- this.maskEl.left.hide();
-
- this.maskEl.bottom.setStyle('position', 'absolute');
- this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
- this.maskEl.bottom.hide();
+ this.wrap.dom.innerHTML = '';
+ this.wrap.remove();
+ }
+ },
- this.maskEl.right.setStyle('position', 'absolute');
- this.maskEl.right.setSize(0, 0).setXY([0, 0]);
- this.maskEl.right.hide();
-
- window.onwheel = function(){ return true;};
-
- if(this.intervalID){
- window.clearInterval(this.intervalID);
- this.intervalID = false;
- }
-
- this.isMasked = false;
-
+ // private
+ onFirstFocus : function(){
+ //Roo.log("onFirstFocus");
+ this.editorcore.onFirstFocus();
+ for (var i =0; i < this.toolbars.length;i++) {
+ this.toolbars[i].onFirstFocus();
}
- }
+ },
-});/*
- * 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">
- */
-
-/**
- * @class Roo.form.Form
- * @extends Roo.form.BasicForm
- * Adds the ability to dynamically render forms with JavaScript to {@link Roo.form.BasicForm}.
- * @constructor
- * @param {Object} config Configuration options
- */
-Roo.form.Form = function(config){
- var xitems = [];
- if (config.items) {
- xitems = config.items;
- delete config.items;
- }
-
+ // private
+ syncValue : function()
+ {
+ this.editorcore.syncValue();
+ },
- Roo.form.Form.superclass.constructor.call(this, null, config);
- this.url = this.url || this.action;
- if(!this.root){
- this.root = new Roo.form.Layout(Roo.applyIf({
- id: Roo.id()
- }, config));
- }
- this.active = this.root;
- /**
- * Array of all the buttons that have been added to this form via {@link addButton}
- * @type Array
- */
- this.buttons = [];
- this.allItems = [];
- this.addEvents({
- /**
- * @event clientvalidation
- * If the monitorValid config option is true, this event fires repetitively to notify of valid state
- * @param {Form} this
- * @param {Boolean} valid true if the form has passed client-side validation
- */
- clientvalidation: true,
- /**
- * @event rendered
- * Fires when the form is rendered
- * @param {Roo.form.Form} form
- */
- rendered : true
- });
+ pushValue : function()
+ {
+ this.editorcore.pushValue();
+ },
- if (this.progressUrl) {
- // push a hidden field onto the list of fields..
- this.addxtype( {
- xns: Roo.form,
- xtype : 'Hidden',
- name : 'UPLOAD_IDENTIFIER'
- });
- }
-
+ setStylesheets : function(stylesheets)
+ {
+ this.editorcore.setStylesheets(stylesheets);
+ },
- Roo.each(xitems, this.addxtype, this);
+ removeStylesheets : function()
+ {
+ this.editorcore.removeStylesheets();
+ }
+
-};
-
-Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
+ // hide stuff that is not compatible
/**
- * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
+ * @event blur
+ * @hide
*/
/**
- * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
+ * @event change
+ * @hide
*/
/**
- * @cfg {String} buttonAlign Valid values are "left," "center" and "right" (defaults to "center")
+ * @event focus
+ * @hide
*/
- buttonAlign:'center',
-
/**
- * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to 75)
+ * @event specialkey
+ * @hide
*/
- minButtonWidth:75,
-
/**
- * @cfg {String} labelAlign Valid values are "left," "top" and "right" (defaults to "left").
- * This property cascades to child containers if not set.
+ * @cfg {String} fieldClass @hide
*/
- labelAlign:'left',
-
/**
- * @cfg {Boolean} monitorValid If true the form monitors its valid state <b>client-side</b> and
- * fires a looping event with that state. This is required to bind buttons to the valid
- * state using the config value formBind:true on the button.
+ * @cfg {String} focusClass @hide
*/
- monitorValid : false,
-
/**
- * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
+ * @cfg {String} autoCreate @hide
*/
- monitorPoll : 200,
-
/**
- * @cfg {String} progressUrl - Url to return progress data
+ * @cfg {String} inputType @hide
*/
-
- progressUrl : false,
/**
- * @cfg {boolean|FormData} formData - true to use new 'FormData' post, or set to a new FormData({dom form}) Object, if
- * sending a formdata with extra parameters - eg uploaded elements.
+ * @cfg {String} invalidClass @hide
*/
-
- formData : false,
-
/**
- * Opens a new {@link Roo.form.Column} container in the layout stack. If fields are passed after the config, the
- * fields are added and the column is closed. If no fields are passed the column remains open
- * until end() is called.
- * @param {Object} config The config to pass to the column
- * @param {Field} field1 (optional)
- * @param {Field} field2 (optional)
- * @param {Field} etc (optional)
- * @return Column The column container object
+ * @cfg {String} invalidText @hide
*/
- column : function(c){
- var col = new Roo.form.Column(c);
- this.start(col);
- if(arguments.length > 1){ // duplicate code required because of Opera
- this.add.apply(this, Array.prototype.slice.call(arguments, 1));
- this.end();
- }
- return col;
- },
-
/**
- * Opens a new {@link Roo.form.FieldSet} container in the layout stack. If fields are passed after the config, the
- * fields are added and the fieldset is closed. If no fields are passed the fieldset remains open
- * until end() is called.
- * @param {Object} config The config to pass to the fieldset
- * @param {Field} field1 (optional)
- * @param {Field} field2 (optional)
- * @param {Field} etc (optional)
- * @return FieldSet The fieldset container object
+ * @cfg {String} msgFx @hide
*/
- fieldset : function(c){
- var fs = new Roo.form.FieldSet(c);
- this.start(fs);
- if(arguments.length > 1){ // duplicate code required because of Opera
- this.add.apply(this, Array.prototype.slice.call(arguments, 1));
- this.end();
- }
- return fs;
- },
-
/**
- * Opens a new {@link Roo.form.Layout} container in the layout stack. If fields are passed after the config, the
- * fields are added and the container is closed. If no fields are passed the container remains open
- * until end() is called.
- * @param {Object} config The config to pass to the Layout
- * @param {Field} field1 (optional)
- * @param {Field} field2 (optional)
- * @param {Field} etc (optional)
- * @return Layout The container object
+ * @cfg {String} validateOnBlur @hide
*/
- container : function(c){
- var l = new Roo.form.Layout(c);
- this.start(l);
- if(arguments.length > 1){ // duplicate code required because of Opera
- this.add.apply(this, Array.prototype.slice.call(arguments, 1));
- this.end();
- }
- return l;
- },
+});
+
+ /*
+ * Based on
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+
+ */
- /**
- * Opens the passed container in the layout stack. The container can be any {@link Roo.form.Layout} or subclass.
- * @param {Object} container A Roo.form.Layout or subclass of Layout
- * @return {Form} this
- */
- start : function(c){
- // cascade label info
- Roo.applyIf(c, {'labelAlign': this.active.labelAlign, 'labelWidth': this.active.labelWidth, 'itemCls': this.active.itemCls});
- this.active.stack.push(c);
- c.ownerCt = this.active;
- this.active = c;
- return this;
- },
+/**
+ * @class Roo.form.HtmlEditor.ToolbarStandard
+ * Basic Toolbar
+
+ * Usage:
+ *
+ new Roo.form.HtmlEditor({
+ ....
+ toolbars : [
+ new Roo.form.HtmlEditorToolbar1({
+ disable : { fonts: 1 , format: 1, ..., ... , ...],
+ btns : [ .... ]
+ })
+ }
+
+ *
+ * @cfg {Object} disable List of elements to disable..
+ * @cfg {Roo.Toolbar.Item|Roo.Toolbar.Button|Roo.Toolbar.SplitButton|Roo.form.Field} btns[] List of additional buttons.
+ *
+ *
+ * NEEDS Extra CSS?
+ * .x-html-editor-tb .x-edit-none .x-btn-text { background: none; }
+ */
+
+Roo.form.HtmlEditor.ToolbarStandard = function(config)
+{
+
+ Roo.apply(this, config);
+
+ // default disabled, based on 'good practice'..
+ this.disable = this.disable || {};
+ Roo.applyIf(this.disable, {
+ fontSize : true,
+ colors : true,
+ specialElements : true
+ });
+
+
+ //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
+ // dont call parent... till later.
+}
+Roo.form.HtmlEditor.ToolbarStandard.prototype = {
+
+ tb: false,
+
+ rendered: false,
+
+ editor : false,
+ editorcore : false,
/**
- * Closes the current open container
- * @return {Form} this
+ * @cfg {Object} disable List of toolbar elements to disable
+
*/
- end : function(){
- if(this.active == this.root){
- return this;
- }
- this.active = this.active.ownerCt;
- return this;
- },
-
+ disable : false,
+
+
+ /**
+ * @cfg {String} createLinkText The default text for the create link prompt
+ */
+ createLinkText : 'Please enter the URL for the link:',
/**
- * Add Roo.form components to the current open container (e.g. column, fieldset, etc.). Fields added via this method
- * can also be passed with an additional property of fieldLabel, which if supplied, will provide the text to display
- * as the label of the field.
- * @param {Field} field1
- * @param {Field} field2 (optional)
- * @param {Field} etc. (optional)
- * @return {Form} this
+ * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
*/
- add : function(){
- this.active.stack.push.apply(this.active.stack, arguments);
- this.allItems.push.apply(this.allItems,arguments);
- var r = [];
- for(var i = 0, a = arguments, len = a.length; i < len; i++) {
- if(a[i].isFormField){
- r.push(a[i]);
- }
- }
- if(r.length > 0){
- Roo.form.Form.superclass.add.apply(this, r);
- }
- return this;
- },
+ defaultLinkValue : 'http:/'+'/',
+
+
+ /**
+ * @cfg {Array} fontFamilies An array of available font families
+ */
+ fontFamilies : [
+ 'Arial',
+ 'Courier New',
+ 'Tahoma',
+ 'Times New Roman',
+ 'Verdana'
+ ],
+ specialChars : [
+ "©",
+ "®",
+ "™",
+ "£" ,
+ // "—",
+ "…",
+ "÷" ,
+ // "á" , ?? a acute?
+ "€" , //Euro
+ // "“" ,
+ // "”" ,
+ // "•" ,
+ "°" // , // degrees
+ // "é" , // e ecute
+ // "ú" , // u ecute?
+ ],
+
+ specialElements : [
+ {
+ text: "Insert Table",
+ xtype: 'MenuItem',
+ xns : Roo.Menu,
+ ihtml : '<table><tr><td>Cell</td></tr></table>'
+
+ },
+ {
+ text: "Insert Image",
+ xtype: 'MenuItem',
+ xns : Roo.Menu,
+ ihtml : '<img src="about:blank"/>'
+
+ }
+
+
+ ],
+ inputElements : [
+ "form", "input:text", "input:hidden", "input:checkbox", "input:radio", "input:password",
+ "input:submit", "input:button", "select", "textarea", "label" ],
+ formats : [
+ ["p"] ,
+ ["h1"],["h2"],["h3"],["h4"],["h5"],["h6"],
+ ["pre"],[ "code"],
+ ["abbr"],[ "acronym"],[ "address"],[ "cite"],[ "samp"],[ "var"],
+ ['div'],['span'],
+ ['sup'],['sub']
+ ],
+ cleanStyles : [
+ "font-size"
+ ],
/**
- * Find any element that has been added to a form, using it's ID or name
- * This can include framesets, columns etc. along with regular fields..
- * @param {String} id - id or name to find.
-
- * @return {Element} e - or false if nothing found.
+ * @cfg {String} defaultFont default font to use.
*/
- findbyId : function(id)
- {
- var ret = false;
- if (!id) {
- return ret;
- }
- Roo.each(this.allItems, function(f){
- if (f.id == id || f.name == id ){
- ret = f;
- return false;
- }
- });
- return ret;
- },
-
+ defaultFont: 'tahoma',
+
+ fontSelect : false,
- /**
- * Render this form into the passed container. This should only be called once!
- * @param {String/HTMLElement/Element} container The element this component should be rendered into
- * @return {Form} this
- */
- render : function(ct)
+ formatCombo : false,
+
+ init : function(editor)
{
+ this.editor = editor;
+ this.editorcore = editor.editorcore ? editor.editorcore : editor;
+ var editorcore = this.editorcore;
+ var _t = this;
+ var fid = editorcore.frameId;
+ var etb = this;
+ function btn(id, toggle, handler){
+ var xid = fid + '-'+ id ;
+ return {
+ id : xid,
+ cmd : id,
+ cls : 'x-btn-icon x-edit-'+id,
+ enableToggle:toggle !== false,
+ scope: _t, // was editor...
+ handler:handler||_t.relayBtnCmd,
+ clickEvent:'mousedown',
+ tooltip: etb.buttonTips[id] || undefined, ///tips ???
+ tabIndex:-1
+ };
+ }
- ct = Roo.get(ct);
- var o = this.autoCreate || {
- tag: 'form',
- method : this.method || 'POST',
- id : this.id || Roo.id()
- };
- this.initEl(ct.createChild(o));
-
- this.root.render(this.el);
-
-
- this.items.each(function(f){
- f.render('x-form-el-'+f.id);
+
+ var tb = new Roo.Toolbar(editor.wrap.dom.firstChild);
+ this.tb = tb;
+ // stop form submits
+ tb.el.on('click', function(e){
+ e.preventDefault(); // what does this do?
});
- if(this.buttons.length > 0){
- // tables are required to maintain order and for correct IE layout
- var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
- cls:"x-form-btns x-form-btns-"+this.buttonAlign,
- html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
- }}, null, true);
- var tr = tb.getElementsByTagName('tr')[0];
- for(var i = 0, len = this.buttons.length; i < len; i++) {
- var b = this.buttons[i];
- var td = document.createElement('td');
- td.className = 'x-form-btn-td';
- b.render(tr.appendChild(td));
- }
- }
- if(this.monitorValid){ // initialize after render
- this.startMonitoring();
- }
- this.fireEvent('rendered', this);
- return this;
- },
-
- /**
- * Adds a button to the footer of the form - this <b>must</b> be called before the form is rendered.
- * @param {String/Object} config A string becomes the button text, an object can either be a Button config
- * object or a valid Roo.DomHelper element config
- * @param {Function} handler The function called when the button is clicked
- * @param {Object} scope (optional) The scope of the handler function
- * @return {Roo.Button}
- */
- addButton : function(config, handler, scope){
- var bc = {
- handler: handler,
- scope: scope,
- minWidth: this.minButtonWidth,
- hideParent:true
- };
- if(typeof config == "string"){
- bc.text = config;
- }else{
- Roo.apply(bc, config);
- }
- var btn = new Roo.Button(null, bc);
- this.buttons.push(btn);
- return btn;
- },
-
- /**
- * Adds a series of form elements (using the xtype property as the factory method.
- * Valid xtypes are: TextField, TextArea .... Button, Layout, FieldSet, Column, (and 'end' to close a block)
- * @param {Object} config
- */
-
- addxtype : function()
- {
- var ar = Array.prototype.slice.call(arguments, 0);
- var ret = false;
- for(var i = 0; i < ar.length; i++) {
- if (!ar[i]) {
- continue; // skip -- if this happends something invalid got sent, we
- // should ignore it, as basically that interface element will not show up
- // and that should be pretty obvious!!
- }
+ if(!this.disable.font) { // && !Roo.isSafari){
+ /* why no safari for fonts
+ editor.fontSelect = tb.el.createChild({
+ tag:'select',
+ tabIndex: -1,
+ cls:'x-font-select',
+ html: this.createFontOptions()
+ });
- if (Roo.form[ar[i].xtype]) {
- ar[i].form = this;
- var fe = Roo.factory(ar[i], Roo.form);
- if (!ret) {
- ret = fe;
- }
- fe.form = this;
- if (fe.store) {
- fe.store.form = this;
- }
- if (fe.isLayout) {
-
- this.start(fe);
- this.allItems.push(fe);
- if (fe.items && fe.addxtype) {
- fe.addxtype.apply(fe, fe.items);
- delete fe.items;
- }
- this.end();
- continue;
- }
-
-
-
- this.add(fe);
- // console.log('adding ' + ar[i].xtype);
- }
- if (ar[i].xtype == 'Button') {
- //console.log('adding button');
- //console.log(ar[i]);
- this.addButton(ar[i]);
- this.allItems.push(fe);
- continue;
- }
+ editor.fontSelect.on('change', function(){
+ var font = editor.fontSelect.dom.value;
+ editor.relayCmd('fontname', font);
+ editor.deferFocus();
+ }, editor);
- if (ar[i].xtype == 'end') { // so we can add fieldsets... / layout etc.
- alert('end is not supported on xtype any more, use items');
- // this.end();
- // //console.log('adding end');
- }
+ tb.add(
+ editor.fontSelect.dom,
+ '-'
+ );
+ */
- }
- return ret;
- },
-
- /**
- * Starts monitoring of the valid state of this form. Usually this is done by passing the config
- * option "monitorValid"
- */
- startMonitoring : function(){
- if(!this.bound){
- this.bound = true;
- Roo.TaskMgr.start({
- run : this.bindHandler,
- interval : this.monitorPoll || 200,
- scope: this
- });
- }
- },
-
- /**
- * Stops monitoring of the valid state of this form
- */
- stopMonitoring : function(){
- this.bound = false;
- },
+ };
+ if(!this.disable.formats){
+ this.formatCombo = new Roo.form.ComboBox({
+ store: new Roo.data.SimpleStore({
+ id : 'tag',
+ fields: ['tag'],
+ data : this.formats // from states.js
+ }),
+ blockFocus : true,
+ name : '',
+ //autoCreate : {tag: "div", size: "20"},
+ displayField:'tag',
+ typeAhead: false,
+ mode: 'local',
+ editable : false,
+ triggerAction: 'all',
+ emptyText:'Add tag',
+ selectOnFocus:true,
+ width:135,
+ listeners : {
+ 'select': function(c, r, i) {
+ editorcore.insertTag(r.get('tag'));
+ editor.focus();
+ }
+ }
- // private
- bindHandler : function(){
- if(!this.bound){
- return false; // stops binding
+ });
+ tb.addField(this.formatCombo);
+
}
- var valid = true;
- this.items.each(function(f){
- if(!f.isValid(true)){
- valid = false;
- return false;
- }
- });
- for(var i = 0, len = this.buttons.length; i < len; i++){
- var btn = this.buttons[i];
- if(btn.formBind === true && btn.disabled === valid){
- btn.setDisabled(!valid);
- }
- }
- this.fireEvent('clientvalidation', this, valid);
- }
-
-
-
-
-
-
-
-
-});
-
-
-// back compat
-Roo.Form = Roo.form.Form;
-/*
- * 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">
- */
-
-// as we use this in bootstrap.
-Roo.namespace('Roo.form');
- /**
- * @class Roo.form.Action
- * Internal Class used to handle form actions
- * @constructor
- * @param {Roo.form.BasicForm} el The form element or its id
- * @param {Object} config Configuration options
- */
-
-
-
-// define the action interface
-Roo.form.Action = function(form, options){
- this.form = form;
- this.options = options || {};
-};
-/**
- * Client Validation Failed
- * @const
- */
-Roo.form.Action.CLIENT_INVALID = 'client';
-/**
- * Server Validation Failed
- * @const
- */
-Roo.form.Action.SERVER_INVALID = 'server';
- /**
- * Connect to Server Failed
- * @const
- */
-Roo.form.Action.CONNECT_FAILURE = 'connect';
-/**
- * Reading Data from Server Failed
- * @const
- */
-Roo.form.Action.LOAD_FAILURE = 'load';
-
-Roo.form.Action.prototype = {
- type : 'default',
- failureType : undefined,
- response : undefined,
- result : undefined,
-
- // interface method
- run : function(options){
-
- },
-
- // interface method
- success : function(response){
-
- },
+
+ if(!this.disable.format){
+ tb.add(
+ btn('bold'),
+ btn('italic'),
+ btn('underline'),
+ btn('strikethrough')
+ );
+ };
+ if(!this.disable.fontSize){
+ tb.add(
+ '-',
+
+
+ btn('increasefontsize', false, editorcore.adjustFont),
+ btn('decreasefontsize', false, editorcore.adjustFont)
+ );
+ };
+
+
+ if(!this.disable.colors){
+ tb.add(
+ '-', {
+ id:editorcore.frameId +'-forecolor',
+ cls:'x-btn-icon x-edit-forecolor',
+ clickEvent:'mousedown',
+ tooltip: this.buttonTips['forecolor'] || undefined,
+ tabIndex:-1,
+ menu : new Roo.menu.ColorMenu({
+ allowReselect: true,
+ focus: Roo.emptyFn,
+ value:'000000',
+ plain:true,
+ selectHandler: function(cp, color){
+ editorcore.execCmd('forecolor', Roo.isSafari || Roo.isIE ? '#'+color : color);
+ editor.deferFocus();
+ },
+ scope: editorcore,
+ clickEvent:'mousedown'
+ })
+ }, {
+ id:editorcore.frameId +'backcolor',
+ cls:'x-btn-icon x-edit-backcolor',
+ clickEvent:'mousedown',
+ tooltip: this.buttonTips['backcolor'] || undefined,
+ tabIndex:-1,
+ menu : new Roo.menu.ColorMenu({
+ focus: Roo.emptyFn,
+ value:'FFFFFF',
+ plain:true,
+ allowReselect: true,
+ selectHandler: function(cp, color){
+ if(Roo.isGecko){
+ editorcore.execCmd('useCSS', false);
+ editorcore.execCmd('hilitecolor', color);
+ editorcore.execCmd('useCSS', true);
+ editor.deferFocus();
+ }else{
+ editorcore.execCmd(Roo.isOpera ? 'hilitecolor' : 'backcolor',
+ Roo.isSafari || Roo.isIE ? '#'+color : color);
+ editor.deferFocus();
+ }
+ },
+ scope:editorcore,
+ clickEvent:'mousedown'
+ })
+ }
+ );
+ };
+ // now add all the items...
+
- // interface method
- handleResponse : function(response){
+ if(!this.disable.alignments){
+ tb.add(
+ '-',
+ btn('justifyleft'),
+ btn('justifycenter'),
+ btn('justifyright')
+ );
+ };
- },
+ //if(!Roo.isSafari){
+ if(!this.disable.links){
+ tb.add(
+ '-',
+ btn('createlink', false, this.createLink) /// MOVE TO HERE?!!?!?!?!
+ );
+ };
- // default connection failure
- failure : function(response){
+ if(!this.disable.lists){
+ tb.add(
+ '-',
+ btn('insertorderedlist'),
+ btn('insertunorderedlist')
+ );
+ }
+ if(!this.disable.sourceEdit){
+ tb.add(
+ '-',
+ btn('sourceedit', true, function(btn){
+ this.toggleSourceEdit(btn.pressed);
+ })
+ );
+ }
+ //}
- this.response = response;
- this.failureType = Roo.form.Action.CONNECT_FAILURE;
- this.form.afterAction(this, false);
- },
-
- processResponse : function(response){
- this.response = response;
- if(!response.responseText){
- return true;
+ var smenu = { };
+ // special menu.. - needs to be tidied up..
+ if (!this.disable.special) {
+ smenu = {
+ text: "©",
+ cls: 'x-edit-none',
+
+ menu : {
+ items : []
+ }
+ };
+ for (var i =0; i < this.specialChars.length; i++) {
+ smenu.menu.items.push({
+
+ html: this.specialChars[i],
+ handler: function(a,b) {
+ editorcore.insertAtCursor(String.fromCharCode(a.html.replace('&#','').replace(';', '')));
+ //editor.insertAtCursor(a.html);
+
+ },
+ tabIndex:-1
+ });
+ }
+
+
+ tb.add(smenu);
+
+
}
- this.result = this.handleResponse(response);
- return this.result;
- },
-
- // utility functions used internally
- getUrl : function(appendParams){
- var url = this.options.url || this.form.url || this.form.el.dom.action;
- if(appendParams){
- var p = this.getParams();
- if(p){
- url += (url.indexOf('?') != -1 ? '&' : '?') + p;
+
+ var cmenu = { };
+ if (!this.disable.cleanStyles) {
+ cmenu = {
+ cls: 'x-btn-icon x-btn-clear',
+
+ menu : {
+ items : []
+ }
+ };
+ for (var i =0; i < this.cleanStyles.length; i++) {
+ cmenu.menu.items.push({
+ actiontype : this.cleanStyles[i],
+ html: 'Remove ' + this.cleanStyles[i],
+ handler: function(a,b) {
+// Roo.log(a);
+// Roo.log(b);
+ var c = Roo.get(editorcore.doc.body);
+ c.select('[style]').each(function(s) {
+ s.dom.style.removeProperty(a.actiontype);
+ });
+ editorcore.syncValue();
+ },
+ tabIndex:-1
+ });
+ }
+ cmenu.menu.items.push({
+ actiontype : 'tablewidths',
+ html: 'Remove Table Widths',
+ handler: function(a,b) {
+ editorcore.cleanTableWidths();
+ editorcore.syncValue();
+ },
+ tabIndex:-1
+ });
+ cmenu.menu.items.push({
+ actiontype : 'word',
+ html: 'Remove MS Word Formating',
+ handler: function(a,b) {
+ editorcore.cleanWord();
+ editorcore.syncValue();
+ },
+ tabIndex:-1
+ });
+
+ cmenu.menu.items.push({
+ actiontype : 'all',
+ html: 'Remove All Styles',
+ handler: function(a,b) {
+
+ var c = Roo.get(editorcore.doc.body);
+ c.select('[style]').each(function(s) {
+ s.dom.removeAttribute('style');
+ });
+ editorcore.syncValue();
+ },
+ tabIndex:-1
+ });
+
+ cmenu.menu.items.push({
+ actiontype : 'all',
+ html: 'Remove All CSS Classes',
+ handler: function(a,b) {
+
+ var c = Roo.get(editorcore.doc.body);
+ c.select('[class]').each(function(s) {
+ s.dom.removeAttribute('class');
+ });
+ editorcore.cleanWord();
+ editorcore.syncValue();
+ },
+ tabIndex:-1
+ });
+
+ cmenu.menu.items.push({
+ actiontype : 'tidy',
+ html: 'Tidy HTML Source',
+ handler: function(a,b) {
+ new Roo.htmleditor.Tidy(editorcore.doc.body);
+ editorcore.syncValue();
+ },
+ tabIndex:-1
+ });
+
+
+ tb.add(cmenu);
+ }
+
+ if (!this.disable.specialElements) {
+ var semenu = {
+ text: "Other;",
+ cls: 'x-edit-none',
+ menu : {
+ items : []
+ }
+ };
+ for (var i =0; i < this.specialElements.length; i++) {
+ semenu.menu.items.push(
+ Roo.apply({
+ handler: function(a,b) {
+ editor.insertAtCursor(this.ihtml);
+ }
+ }, this.specialElements[i])
+ );
+
+ }
+
+ tb.add(semenu);
+
+
+ }
+
+
+ if (this.btns) {
+ for(var i =0; i< this.btns.length;i++) {
+ 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){
+ b.cls += ' x-init-enable';
+ }
+
+ b.scope = editorcore;
+ tb.add(b);
}
+
}
- return url;
+
+
+
+ // disable everything...
+
+ this.tb.items.each(function(item){
+
+ if(
+ item.id != editorcore.frameId+ '-sourceedit' &&
+ (typeof(item.cls) != 'undefined' && item.cls.indexOf('x-init-enable') === -1)
+ ){
+
+ item.disable();
+ }
+ });
+ this.rendered = true;
+
+ // the all the btns;
+ editor.on('editorevent', this.updateToolbar, this);
+ // other toolbars need to implement this..
+ //editor.on('editmodechange', this.updateToolbar, this);
},
-
- getMethod : function(){
- return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
+
+
+ relayBtnCmd : function(btn) {
+ this.editorcore.relayCmd(btn.cmd);
},
-
- getParams : function(){
- var bp = this.form.baseParams;
- var p = this.options.params;
- if(p){
- if(typeof p == "object"){
- p = Roo.urlEncode(Roo.applyIf(p, bp));
- }else if(typeof p == 'string' && bp){
- p += '&' + Roo.urlEncode(bp);
+ // private used internally
+ createLink : function(){
+ //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;
}
- }else if(bp){
- p = Roo.urlEncode(bp);
}
- return p;
+
+ (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.
+
},
- createCallback : function(){
- return {
- success: this.success,
- failure: this.failure,
- scope: this,
- timeout: (this.form.timeout*1000),
- upload: this.form.fileUpload ? this.success : undefined
- };
- }
-};
+
+ /**
+ * Protected method that will not generally be called directly. It triggers
+ * a toolbar update by reading the markup state of the current selection in the editor.
+ */
+ updateToolbar: function(){
-Roo.form.Action.Submit = function(form, options){
- Roo.form.Action.Submit.superclass.constructor.call(this, form, options);
-};
+ if(!this.editorcore.activated){
+ this.editor.onFirstFocus();
+ return;
+ }
-Roo.extend(Roo.form.Action.Submit, Roo.form.Action, {
- type : 'submit',
+ var btns = this.tb.items.map,
+ doc = this.editorcore.doc,
+ frameId = this.editorcore.frameId;
- haveProgress : false,
- uploadComplete : false,
-
- // uploadProgress indicator.
- uploadProgress : function()
- {
- if (!this.form.progressUrl) {
- return;
+ if(!this.disable.font && !Roo.isSafari){
+ /*
+ var name = (doc.queryCommandValue('FontName')||this.editor.defaultFont).toLowerCase();
+ if(name != this.fontSelect.dom.value){
+ this.fontSelect.dom.value = name;
+ }
+ */
}
-
- if (!this.haveProgress) {
- Roo.MessageBox.progress("Uploading", "Uploading");
+ if(!this.disable.format){
+ btns[frameId + '-bold'].toggle(doc.queryCommandState('bold'));
+ btns[frameId + '-italic'].toggle(doc.queryCommandState('italic'));
+ btns[frameId + '-underline'].toggle(doc.queryCommandState('underline'));
+ btns[frameId + '-strikethrough'].toggle(doc.queryCommandState('strikethrough'));
}
- if (this.uploadComplete) {
- Roo.MessageBox.hide();
- return;
+ if(!this.disable.alignments){
+ btns[frameId + '-justifyleft'].toggle(doc.queryCommandState('justifyleft'));
+ btns[frameId + '-justifycenter'].toggle(doc.queryCommandState('justifycenter'));
+ btns[frameId + '-justifyright'].toggle(doc.queryCommandState('justifyright'));
+ }
+ if(!Roo.isSafari && !this.disable.lists){
+ btns[frameId + '-insertorderedlist'].toggle(doc.queryCommandState('insertorderedlist'));
+ btns[frameId + '-insertunorderedlist'].toggle(doc.queryCommandState('insertunorderedlist'));
}
- this.haveProgress = true;
-
- var uid = this.form.findField('UPLOAD_IDENTIFIER').getValue();
+ var ans = this.editorcore.getAllAncestors();
+ if (this.formatCombo) {
+
+
+ var store = this.formatCombo.store;
+ this.formatCombo.setValue("");
+ for (var i =0; i < ans.length;i++) {
+ if (ans[i] && store.query('tag',ans[i].tagName.toLowerCase(), false).length) {
+ // select it..
+ this.formatCombo.setValue(ans[i].tagName.toLowerCase());
+ break;
+ }
+ }
+ }
- var c = new Roo.data.Connection();
- c.request({
- url : this.form.progressUrl,
- params: {
- id : uid
- },
- method: 'GET',
- success : function(req){
- //console.log(data);
- var rdata = false;
- var edata;
- try {
- rdata = Roo.decode(req.responseText)
- } catch (e) {
- Roo.log("Invalid data from server..");
- Roo.log(edata);
- return;
- }
- if (!rdata || !rdata.success) {
- Roo.log(rdata);
- Roo.MessageBox.alert(Roo.encode(rdata));
- return;
- }
- var data = rdata.data;
-
- if (this.uploadComplete) {
- Roo.MessageBox.hide();
- return;
- }
-
- if (data){
- Roo.MessageBox.updateProgress(data.bytes_uploaded/data.bytes_total,
- Math.floor((data.bytes_total - data.bytes_uploaded)/1000) + 'k remaining'
- );
- }
- this.uploadProgress.defer(2000,this);
- },
-
- failure: function(data) {
- Roo.log('progress url failed ');
- Roo.log(data);
- },
- scope : this
- });
-
+
+
+ // hides menus... - so this cant be on a menu...
+ Roo.menu.MenuMgr.hideAll();
+
+ //this.editorsyncValue();
},
+
-
- run : function()
- {
- // run get Values on the form, so it syncs any secondary forms.
- this.form.getValues();
+ createFontOptions : function(){
+ var buf = [], fs = this.fontFamilies, ff, lc;
- var o = this.options;
- var method = this.getMethod();
- var isPost = method == 'POST';
- if(o.clientValidation === false || this.form.isValid()){
-
- if (this.form.progressUrl) {
- this.form.findField('UPLOAD_IDENTIFIER').setValue(
- (new Date() * 1) + '' + Math.random());
-
- }
-
-
- Roo.Ajax.request(Roo.apply(this.createCallback(), {
- form:this.form.el.dom,
- url:this.getUrl(!isPost),
- method: method,
- params:isPost ? this.getParams() : null,
- isUpload: this.form.fileUpload,
- formData : this.form.formData
- }));
-
- this.uploadProgress();
-
- }else if (o.clientValidation !== false){ // client validation failed
- this.failureType = Roo.form.Action.CLIENT_INVALID;
- this.form.afterAction(this, false);
+
+
+ for(var i = 0, len = fs.length; i< len; i++){
+ ff = fs[i];
+ lc = ff.toLowerCase();
+ buf.push(
+ '<option value="',lc,'" style="font-family:',ff,';"',
+ (this.defaultFont == lc ? ' selected="true">' : '>'),
+ ff,
+ '</option>'
+ );
}
+ return buf.join('');
},
-
- success : function(response)
- {
- this.uploadComplete= true;
- if (this.haveProgress) {
- Roo.MessageBox.hide();
- }
-
+
+ toggleSourceEdit : function(sourceEditMode){
- var result = this.processResponse(response);
- if(result === true || result.success){
- this.form.afterAction(this, true);
+ Roo.log("toolbar toogle");
+ if(sourceEditMode === undefined){
+ sourceEditMode = !this.sourceEditMode;
+ }
+ this.sourceEditMode = sourceEditMode === true;
+ var btn = this.tb.items.get(this.editorcore.frameId +'-sourceedit');
+ // just toggle the button?
+ if(btn.pressed !== this.sourceEditMode){
+ btn.toggle(this.sourceEditMode);
return;
}
- if(result.errors){
- this.form.markInvalid(result.errors);
- this.failureType = Roo.form.Action.SERVER_INVALID;
+
+ if(sourceEditMode){
+ Roo.log("disabling buttons");
+ this.tb.items.each(function(item){
+ if(item.cmd != 'sourceedit' && (typeof(item.cls) != 'undefined' && item.cls.indexOf('x-init-enable') === -1)){
+ item.disable();
+ }
+ });
+
+ }else{
+ Roo.log("enabling buttons");
+ if(this.editorcore.initialized){
+ 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);
+
+ }
+
}
- this.form.afterAction(this, false);
+ Roo.log("calling toggole on editor");
+ // tell the editor that it's been pressed..
+ this.editor.toggleSourceEdit(sourceEditMode);
+
},
- failure : function(response)
- {
- this.uploadComplete= true;
- if (this.haveProgress) {
- Roo.MessageBox.hide();
+ /**
+ * Object collection of toolbar tooltips for the buttons in the editor. The key
+ * is the command id associated with that button and the value is a valid QuickTips object.
+ * For example:
+<pre><code>
+{
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ ...
+</code></pre>
+ * @type Object
+ */
+ buttonTips : {
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ underline : {
+ title: 'Underline (Ctrl+U)',
+ text: 'Underline the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ strikethrough : {
+ title: 'Strikethrough',
+ text: 'Strikethrough the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ increasefontsize : {
+ title: 'Grow Text',
+ text: 'Increase the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ decreasefontsize : {
+ title: 'Shrink Text',
+ text: 'Decrease the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ backcolor : {
+ title: 'Text Highlight Color',
+ text: 'Change the background color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ forecolor : {
+ title: 'Font Color',
+ text: 'Change the color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyleft : {
+ title: 'Align Text Left',
+ text: 'Align text to the left.',
+ cls: 'x-html-editor-tip'
+ },
+ justifycenter : {
+ title: 'Center Text',
+ text: 'Center text in the editor.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyright : {
+ title: 'Align Text Right',
+ text: 'Align text to the right.',
+ cls: 'x-html-editor-tip'
+ },
+ insertunorderedlist : {
+ title: 'Bullet List',
+ text: 'Start a bulleted list.',
+ cls: 'x-html-editor-tip'
+ },
+ insertorderedlist : {
+ title: 'Numbered List',
+ text: 'Start a numbered list.',
+ cls: 'x-html-editor-tip'
+ },
+ createlink : {
+ title: 'Hyperlink',
+ text: 'Make the selected text a hyperlink.',
+ cls: 'x-html-editor-tip'
+ },
+ sourceedit : {
+ title: 'Source Edit',
+ text: 'Switch to source editing mode.',
+ cls: 'x-html-editor-tip'
}
-
- this.response = response;
- this.failureType = Roo.form.Action.CONNECT_FAILURE;
- this.form.afterAction(this, false);
},
-
- handleResponse : function(response){
- if(this.form.errorReader){
- var rs = this.form.errorReader.read(response);
- var errors = [];
- if(rs.records){
- for(var i = 0, len = rs.records.length; i < len; i++) {
- var r = rs.records[i];
- errors[i] = r.data;
+ // private
+ onDestroy : function(){
+ if(this.rendered){
+
+ this.tb.items.each(function(item){
+ if(item.menu){
+ item.menu.removeAll();
+ if(item.menu.el){
+ item.menu.el.destroy();
+ }
}
- }
- if(errors.length < 1){
- errors = null;
- }
- return {
- success : rs.success,
- errors : errors
- };
- }
- var ret = false;
- try {
- ret = Roo.decode(response.responseText);
- } catch (e) {
- ret = {
- success: false,
- errorMsg: "Failed to read server message: " + (response ? response.responseText : ' - no message'),
- errors : []
- };
+ item.destroy();
+ });
+
}
- return ret;
-
+ },
+ onFirstFocus: function() {
+ this.tb.items.each(function(item){
+ item.enable();
+ });
}
-});
-
-
-Roo.form.Action.Load = function(form, options){
- Roo.form.Action.Load.superclass.constructor.call(this, form, options);
- this.reader = this.form.reader;
};
-Roo.extend(Roo.form.Action.Load, Roo.form.Action, {
- type : 'load',
-
- run : function(){
-
- Roo.Ajax.request(Roo.apply(
- this.createCallback(), {
- method:this.getMethod(),
- url:this.getUrl(false),
- params:this.getParams()
- }));
- },
- success : function(response){
-
- var result = this.processResponse(response);
- if(result === true || !result.success || !result.data){
- this.failureType = Roo.form.Action.LOAD_FAILURE;
- this.form.afterAction(this, false);
- return;
- }
- this.form.clearInvalid();
- this.form.setValues(result.data);
- this.form.afterAction(this, true);
- },
- handleResponse : function(response){
- if(this.form.reader){
- var rs = this.form.reader.read(response);
- var data = rs.records && rs.records[0] ? rs.records[0].data : null;
- return {
- success : rs.success,
- data : data
- };
- }
- return Roo.decode(response.responseText);
- }
-});
-Roo.form.Action.ACTION_TYPES = {
- 'load' : Roo.form.Action.Load,
- 'submit' : Roo.form.Action.Submit
-};/*
- * Based on:
+// <script type="text/javascript">
+/*
+ * 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">
+ *
+
*/
+
/**
- * @class Roo.form.Layout
- * @extends Roo.Component
- * Creates a container for layout and rendering of fields in an {@link Roo.form.Form}.
- * @constructor
- * @param {Object} config Configuration options
- */
-Roo.form.Layout = function(config){
- var xitems = [];
- if (config.items) {
- xitems = config.items;
- delete config.items;
- }
- Roo.form.Layout.superclass.constructor.call(this, config);
- this.stack = [];
- Roo.each(xitems, this.addxtype, this);
-
-};
+ * @class Roo.form.HtmlEditor.ToolbarContext
+ * Context Toolbar
+ *
+ * Usage:
+ *
+ new Roo.form.HtmlEditor({
+ ....
+ toolbars : [
+ { xtype: 'ToolbarStandard', styles : {} }
+ { xtype: 'ToolbarContext', disable : {} }
+ ]
+})
-Roo.extend(Roo.form.Layout, Roo.Component, {
- /**
- * @cfg {String/Object} autoCreate
- * A DomHelper element spec used to autocreate the layout (defaults to {tag: 'div', cls: 'x-form-ct'})
- */
- /**
- * @cfg {String/Object/Function} style
- * A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
- * a function which returns such a specification.
- */
- /**
- * @cfg {String} labelAlign
- * Valid values are "left," "top" and "right" (defaults to "left")
- */
- /**
- * @cfg {Number} labelWidth
- * Fixed width in pixels of all field labels (defaults to undefined)
- */
- /**
- * @cfg {Boolean} clear
- * True to add a clearing element at the end of this layout, equivalent to CSS clear: both (defaults to true)
- */
- clear : true,
- /**
- * @cfg {String} labelSeparator
- * The separator to use after field labels (defaults to ':')
- */
- labelSeparator : ':',
- /**
- * @cfg {Boolean} hideLabels
- * True to suppress the display of field labels in this layout (defaults to false)
- */
- hideLabels : false,
+
+ *
+ * @config : {Object} disable List of elements to disable.. (not done yet.)
+ * @config : {Object} styles Map of styles available.
+ *
+ */
- // private
- defaultAutoCreate : {tag: 'div', cls: 'x-form-ct'},
+Roo.form.HtmlEditor.ToolbarContext = function(config)
+{
- isLayout : true,
+ Roo.apply(this, config);
+ //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
+ // dont call parent... till later.
+ this.styles = this.styles || {};
+}
+
+
+
+Roo.form.HtmlEditor.ToolbarContext.types = {
+ 'IMG' : [
+ {
+ name : 'width',
+ title: "Width",
+ width: 40
+ },
+ {
+ name : 'height',
+ title: "Height",
+ width: 40
+ },
+ {
+ name : 'align',
+ title: "Align",
+ opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
+ width : 80
+
+ },
+ {
+ name : 'border',
+ title: "Border",
+ width: 40
+ },
+ {
+ name : 'alt',
+ title: "Alt",
+ width: 120
+ },
+ {
+ name : 'src',
+ title: "Src",
+ width: 220
+ }
+
+ ],
- // private
- onRender : function(ct, position){
- if(this.el){ // from markup
- this.el = Roo.get(this.el);
- }else { // generate
- var cfg = this.getAutoCreate();
- this.el = ct.createChild(cfg, position);
+ 'FIGURE' : [
+ {
+ name : 'align',
+ title: "Align",
+ opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
+ width : 80
}
- if(this.style){
- this.el.applyStyles(this.style);
+ ],
+ 'A' : [
+ {
+ name : 'name',
+ title: "Name",
+ width: 50
+ },
+ {
+ name : 'target',
+ title: "Target",
+ width: 120
+ },
+ {
+ name : 'href',
+ title: "Href",
+ width: 220
+ } // border?
+
+ ],
+
+ 'INPUT' : [
+ {
+ name : 'name',
+ title: "name",
+ width: 120
+ },
+ {
+ name : 'value',
+ title: "Value",
+ width: 120
+ },
+ {
+ name : 'width',
+ title: "Width",
+ width: 40
}
- if(this.labelAlign){
- this.el.addClass('x-form-label-'+this.labelAlign);
+ ],
+ 'LABEL' : [
+ {
+ name : 'for',
+ title: "For",
+ width: 120
}
- if(this.hideLabels){
- this.labelStyle = "display:none";
- this.elementStyle = "padding-left:0;";
- }else{
- if(typeof this.labelWidth == 'number'){
- this.labelStyle = "width:"+this.labelWidth+"px;";
- this.elementStyle = "padding-left:"+((this.labelWidth+(typeof this.labelPad == 'number' ? this.labelPad : 5))+'px')+";";
- }
- if(this.labelAlign == 'top'){
- this.labelStyle = "width:auto;";
- this.elementStyle = "padding-left:0;";
- }
- }
- var stack = this.stack;
- var slen = stack.length;
- if(slen > 0){
- if(!this.fieldTpl){
- var t = new Roo.Template(
- '<div class="x-form-item {5}">',
- '<label for="{0}" style="{2}">{1}{4}</label>',
- '<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
- '</div>',
- '</div><div class="x-form-clear-left"></div>'
- );
- t.disableFormats = true;
- t.compile();
- Roo.form.Layout.prototype.fieldTpl = t;
- }
- for(var i = 0; i < slen; i++) {
- if(stack[i].isFormField){
- this.renderField(stack[i]);
- }else{
- this.renderComponent(stack[i]);
- }
- }
- }
- if(this.clear){
- this.el.createChild({cls:'x-form-clear'});
- }
- },
-
- // private
- renderField : function(f){
- f.fieldEl = Roo.get(this.fieldTpl.append(this.el, [
- f.id, //0
- f.fieldLabel, //1
- f.labelStyle||this.labelStyle||'', //2
- this.elementStyle||'', //3
- typeof f.labelSeparator == 'undefined' ? this.labelSeparator : f.labelSeparator, //4
- f.itemCls||this.itemCls||'' //5
- ], true).getPrevSibling());
- },
-
- // private
- renderComponent : function(c){
- c.render(c.isLayout ? this.el : this.el.createChild());
- },
- /**
- * Adds a object form elements (using the xtype property as the factory method.)
- * Valid xtypes are: TextField, TextArea .... Button, Layout, FieldSet, Column
- * @param {Object} config
- */
- addxtype : function(o)
- {
- // create the lement.
- o.form = this.form;
- var fe = Roo.factory(o, Roo.form);
- this.form.allItems.push(fe);
- this.stack.push(fe);
-
- if (fe.isFormField) {
- this.form.items.add(fe);
+ ],
+ 'TEXTAREA' : [
+ {
+ name : 'name',
+ title: "name",
+ width: 120
+ },
+ {
+ name : 'rows',
+ title: "Rows",
+ width: 20
+ },
+ {
+ name : 'cols',
+ title: "Cols",
+ width: 20
}
-
- return fe;
- }
-});
-
-/**
- * @class Roo.form.Column
- * @extends Roo.form.Layout
- * Creates a column container for layout and rendering of fields in an {@link Roo.form.Form}.
- * @constructor
- * @param {Object} config Configuration options
- */
-Roo.form.Column = function(config){
- Roo.form.Column.superclass.constructor.call(this, config);
-};
-
-Roo.extend(Roo.form.Column, Roo.form.Layout, {
- /**
- * @cfg {Number/String} width
- * The fixed width of the column in pixels or CSS value (defaults to "auto")
- */
- /**
- * @cfg {String/Object} autoCreate
- * A DomHelper element spec used to autocreate the column (defaults to {tag: 'div', cls: 'x-form-ct x-form-column'})
- */
-
- // private
- defaultAutoCreate : {tag: 'div', cls: 'x-form-ct x-form-column'},
-
- // private
- onRender : function(ct, position){
- Roo.form.Column.superclass.onRender.call(this, ct, position);
- if(this.width){
- this.el.setWidth(this.width);
+ ],
+ 'SELECT' : [
+ {
+ name : 'name',
+ title: "name",
+ width: 120
+ },
+ {
+ name : 'selectoptions',
+ title: "Options",
+ width: 200
}
- }
-});
-
-
-/**
- * @class Roo.form.Row
- * @extends Roo.form.Layout
- * Creates a row container for layout and rendering of fields in an {@link Roo.form.Form}.
- * @constructor
- * @param {Object} config Configuration options
- */
-
-
-Roo.form.Row = function(config){
- Roo.form.Row.superclass.constructor.call(this, config);
-};
-
-Roo.extend(Roo.form.Row, Roo.form.Layout, {
- /**
- * @cfg {Number/String} width
- * The fixed width of the column in pixels or CSS value (defaults to "auto")
- */
- /**
- * @cfg {Number/String} height
- * The fixed height of the column in pixels or CSS value (defaults to "auto")
- */
- defaultAutoCreate : {tag: 'div', cls: 'x-form-ct x-form-row'},
+ ],
- padWidth : 20,
- // private
- onRender : function(ct, position){
- //console.log('row render');
- if(!this.rowTpl){
- var t = new Roo.Template(
- '<div class="x-form-item {5}" style="float:left;width:{6}px">',
- '<label for="{0}" style="{2}">{1}{4}</label>',
- '<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
- '</div>',
- '</div>'
- );
- t.disableFormats = true;
- t.compile();
- Roo.form.Layout.prototype.rowTpl = t;
- }
- this.fieldTpl = this.rowTpl;
-
- //console.log('lw' + this.labelWidth +', la:' + this.labelAlign);
- var labelWidth = 100;
-
- if ((this.labelAlign != 'top')) {
- if (typeof this.labelWidth == 'number') {
- labelWidth = this.labelWidth
- }
- this.padWidth = 20 + labelWidth;
-
- }
+ // should we really allow this??
+ // should this just be
+ 'BODY' : [
- Roo.form.Column.superclass.onRender.call(this, ct, position);
- if(this.width){
- this.el.setWidth(this.width);
- }
- if(this.height){
- this.el.setHeight(this.height);
+ {
+ name : 'title',
+ title: "Title",
+ width: 200,
+ disabled : true
}
- },
-
- // private
- renderField : function(f){
- f.fieldEl = this.fieldTpl.append(this.el, [
- f.id, f.fieldLabel,
- f.labelStyle||this.labelStyle||'',
- this.elementStyle||'',
- typeof f.labelSeparator == 'undefined' ? this.labelSeparator : f.labelSeparator,
- f.itemCls||this.itemCls||'',
- f.width ? f.width + this.padWidth : 160 + this.padWidth
- ],true);
- }
-});
+ ],
+ '*' : [
+ // empty.
+ ]
-/**
- * @class Roo.form.FieldSet
- * @extends Roo.form.Layout
- * Creates a fieldset container for layout and rendering of fields in an {@link Roo.form.Form}.
- * @constructor
- * @param {Object} config Configuration options
- */
-Roo.form.FieldSet = function(config){
- Roo.form.FieldSet.superclass.constructor.call(this, config);
};
-Roo.extend(Roo.form.FieldSet, Roo.form.Layout, {
- /**
- * @cfg {String} legend
- * The text to display as the legend for the FieldSet (defaults to '')
- */
- /**
- * @cfg {String/Object} autoCreate
- * A DomHelper element spec used to autocreate the fieldset (defaults to {tag: 'fieldset', cn: {tag:'legend'}})
- */
-
- // private
- defaultAutoCreate : {tag: 'fieldset', cn: {tag:'legend'}},
-
- // private
- onRender : function(ct, position){
- Roo.form.FieldSet.superclass.onRender.call(this, ct, position);
- if(this.legend){
- this.setLegend(this.legend);
- }
- },
+// this should be configurable.. - you can either set it up using stores, or modify options somehwere..
+Roo.form.HtmlEditor.ToolbarContext.stores = false;
- // private
- setLegend : function(text){
- if(this.rendered){
- this.el.child('legend').update(text);
- }
- }
-});/*
- * 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">
- */
-/**
- * @class Roo.form.VTypes
- * Overridable validation definitions. The validations provided are basic and intended to be easily customizable and extended.
- * @singleton
- */
-Roo.form.VTypes = function(){
- // closure these in so they are only created once.
- var alpha = /^[a-zA-Z_]+$/;
- var alphanum = /^[a-zA-Z0-9_]+$/;
- var email = /^([\w]+)(.[\w]+)*@([\w-]+\.){1,5}([A-Za-z]){2,24}$/;
- var url = /(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+Roo.form.HtmlEditor.ToolbarContext.options = {
+ 'font-family' : [
+ [ 'Helvetica,Arial,sans-serif', 'Helvetica'],
+ [ 'Courier New', 'Courier New'],
+ [ 'Tahoma', 'Tahoma'],
+ [ 'Times New Roman,serif', 'Times'],
+ [ 'Verdana','Verdana' ]
+ ]
+};
- // All these messages and functions are configurable
- return {
- /**
- * The function used to validate email addresses
- * @param {String} value The email address
- */
- 'email' : function(v){
- return email.test(v);
- },
- /**
- * The error text to display when the email validation function returns false
- * @type String
- */
- 'emailText' : 'This field should be an e-mail address in the format "user@domain.com"',
- /**
- * The keystroke filter mask to be applied on email input
- * @type RegExp
- */
- 'emailMask' : /[a-z0-9_\.\-@]/i,
+// fixme - these need to be configurable..
+
- /**
- * The function used to validate URLs
- * @param {String} value The URL
- */
- 'url' : function(v){
- return url.test(v);
- },
- /**
- * The error text to display when the url validation function returns false
- * @type String
- */
- 'urlText' : 'This field should be a URL in the format "http:/'+'/www.domain.com"',
-
- /**
- * The function used to validate alpha values
- * @param {String} value The value
- */
- 'alpha' : function(v){
- return alpha.test(v);
- },
- /**
- * The error text to display when the alpha validation function returns false
- * @type String
- */
- 'alphaText' : 'This field should only contain letters and _',
- /**
- * The keystroke filter mask to be applied on alpha input
- * @type RegExp
- */
- 'alphaMask' : /[a-z_]/i,
+//Roo.form.HtmlEditor.ToolbarContext.types
- /**
- * The function used to validate alphanumeric values
- * @param {String} value The value
- */
- 'alphanum' : function(v){
- return alphanum.test(v);
- },
- /**
- * The error text to display when the alphanumeric validation function returns false
- * @type String
- */
- 'alphanumText' : 'This field should only contain letters, numbers and _',
- /**
- * The keystroke filter mask to be applied on alphanumeric input
- * @type RegExp
- */
- 'alphanumMask' : /[a-z0-9_]/i
- };
-}();//<script type="text/javascript">
-/**
- * @class Roo.form.FCKeditor
- * @extends Roo.form.TextArea
- * Wrapper around the FCKEditor http://www.fckeditor.net
- * @constructor
- * Creates a new FCKeditor
- * @param {Object} config Configuration options
- */
-Roo.form.FCKeditor = function(config){
- Roo.form.FCKeditor.superclass.constructor.call(this, config);
- this.addEvents({
- /**
- * @event editorinit
- * Fired when the editor is initialized - you can add extra handlers here..
- * @param {FCKeditor} this
- * @param {Object} the FCK object.
- */
- editorinit : true
- });
+Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype, {
+ tb: false,
-};
-Roo.form.FCKeditor.editors = { };
-Roo.extend(Roo.form.FCKeditor, Roo.form.TextArea,
-{
- //defaultAutoCreate : {
- // tag : "textarea",style : "width:100px;height:60px;" ,autocomplete : "off"
- //},
- // private
- /**
- * @cfg {Object} fck options - see fck manual for details.
- */
- fckconfig : false,
+ rendered: false,
+ editor : false,
+ editorcore : false,
/**
- * @cfg {Object} fck toolbar set (Basic or Default)
+ * @cfg {Object} disable List of toolbar elements to disable
+
*/
- toolbarSet : 'Basic',
+ disable : false,
/**
- * @cfg {Object} fck BasePath
- */
- basePath : '/fckeditor/',
-
+ * @cfg {Object} styles List of styles
+ * eg. { '*' : [ 'headline' ] , 'TD' : [ 'underline', 'double-underline' ] }
+ *
+ * These must be defined in the page, so they get rendered correctly..
+ * .headline { }
+ * TD.underline { }
+ *
+ */
+ styles : false,
- frame : false,
+ options: false,
- value : '',
+ toolbars : false,
-
- onRender : function(ct, position)
+ init : function(editor)
{
- if(!this.el){
- this.defaultAutoCreate = {
- tag: "textarea",
- style:"width:300px;height:60px;",
- autocomplete: "new-password"
+ this.editor = editor;
+ this.editorcore = editor.editorcore ? editor.editorcore : editor;
+ var editorcore = this.editorcore;
+
+ var fid = editorcore.frameId;
+ var etb = this;
+ function btn(id, toggle, handler){
+ var xid = fid + '-'+ id ;
+ return {
+ id : xid,
+ cmd : id,
+ cls : 'x-btn-icon x-edit-'+id,
+ enableToggle:toggle !== false,
+ scope: editorcore, // was editor...
+ handler:handler||editorcore.relayBtnCmd,
+ clickEvent:'mousedown',
+ tooltip: etb.buttonTips[id] || undefined, ///tips ???
+ tabIndex:-1
};
}
- Roo.form.FCKeditor.superclass.onRender.call(this, ct, position);
- /*
- if(this.grow){
- this.textSizeEl = Roo.DomHelper.append(document.body, {tag: "pre", cls: "x-form-grow-sizer"});
- if(this.preventScrollbars){
- this.el.setStyle("overflow", "hidden");
- }
- this.el.setHeight(this.growMin);
+ // create a new element.
+ var wdiv = editor.wrap.createChild({
+ tag: 'div'
+ }, editor.wrap.dom.firstChild.nextSibling, true);
+
+ // can we do this more than once??
+
+ // stop form submits
+
+
+ // 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);
}
- */
- //console.log('onrender' + this.getId() );
- Roo.form.FCKeditor.editors[this.getId()] = this;
+ this.tb = this.toolbars.BODY;
+ this.tb.el.show();
+ this.buildFooter();
+ this.footer.show();
+ editor.on('hide', function( ) { this.footer.hide() }, this);
+ editor.on('show', function( ) { this.footer.show() }, this);
+
-
- this.replaceTextarea() ;
+ this.rendered = true;
+ // the all the btns;
+ editor.on('editorevent', this.updateToolbar, this);
+ // other toolbars need to implement this..
+ //editor.on('editmodechange', this.updateToolbar, this);
},
- getEditor : function() {
- return this.fckEditor;
- },
- /**
- * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}.
- * @param {Mixed} value The value to set
- */
- setValue : function(value)
+ /**
+ * Protected method that will not generally be called directly. It triggers
+ * a toolbar update by reading the markup state of the current selection in the editor.
+ *
+ * Note you can force an update by calling on('editorevent', scope, false)
+ */
+ updateToolbar: function(editor ,ev, sel)
{
- //console.log('setValue: ' + value);
- if(typeof(value) == 'undefined') { // not sure why this is happending...
- return;
+ if (ev) {
+ ev.stopEvent(); // se if we can stop this looping with mutiple events.
}
- Roo.form.FCKeditor.superclass.setValue.apply(this,[value]);
-
- //if(!this.el || !this.getEditor()) {
- // this.value = value;
- //this.setValue.defer(100,this,[value]);
- // return;
- //}
- if(!this.getEditor()) {
+ //Roo.log(ev);
+ // capture mouse up - this is handy for selecting images..
+ // perhaps should go somewhere else...
+ if(!this.editorcore.activated){
+ this.editor.onFirstFocus();
return;
}
+ //Roo.log(ev ? ev.target : 'NOTARGET');
- this.getEditor().SetData(value);
- //
-
- },
-
- /**
- * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}.
- * @return {Mixed} value The field value
- */
- getValue : function()
- {
+ // http://developer.yahoo.com/yui/docs/simple-editor.js.html
+ // selectNode - might want to handle IE?
- if (this.frame && this.frame.dom.style.display == 'none') {
- return Roo.form.FCKeditor.superclass.getValue.call(this);
- }
- if(!this.el || !this.getEditor()) {
-
- // this.getValue.defer(100,this);
- return this.value;
+
+ if (ev &&
+ (ev.type == 'mouseup' || ev.type == 'click' ) &&
+ 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;
+
+ // this triggers looping?
+ //this.editorcore.selectNode(sel);
+
}
-
- var value=this.getEditor().GetData();
- Roo.form.FCKeditor.superclass.setValue.apply(this,[value]);
- return Roo.form.FCKeditor.superclass.getValue.call(this);
+ // 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;
-
- },
-
- /**
- * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
- * @return {Mixed} value The field value
- */
- getRawValue : function()
- {
- if (this.frame && this.frame.dom.style.display == 'none') {
- return Roo.form.FCKeditor.superclass.getRawValue.call(this);
+
+ var ans = this.editorcore.getAllAncestors();
+
+ // pick
+ var ty = Roo.form.HtmlEditor.ToolbarContext.types;
+
+ if (!sel) {
+ sel = ans.length ? (ans[0] ? ans[0] : ans[1]) : this.editorcore.doc.body;
+ sel = sel ? sel : this.editorcore.doc.body;
+ sel = sel.tagName.length ? sel : this.editorcore.doc.body;
+
}
- if(!this.el || !this.getEditor()) {
- //this.getRawValue.defer(100,this);
- return this.value;
- return;
+ var tn = sel.tagName.toUpperCase();
+ var lastSel = this.tb.selectedNode;
+ this.tb.selectedNode = sel;
+ var left_label = tn;
+
+ // ok see if we are editing a block?
+
+ 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]')) {
+
+ 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);
+
+
+ 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');
+
+ //this.editorcore.selectNode(db);
+ if (typeof(this.toolbars[tn]) == 'undefined') {
+ this.toolbars[tn] = this.buildToolbar( false ,tn ,block.friendly_name, block);
+ }
+ this.toolbars[tn].selectedNode = db;
+ left_label = block.friendly_name;
+ ans = this.editorcore.getAllAncestors();
+ }
+
+
+
+ }
+
- var value=this.getEditor().GetData();
- Roo.form.FCKeditor.superclass.setRawValue.apply(this,[value]);
- return Roo.form.FCKeditor.superclass.getRawValue.call(this);
-
- },
-
- setSize : function(w,h) {
+ 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 + ': ';
- //if (this.frame && this.frame.dom.style.display == 'none') {
- // Roo.form.FCKeditor.superclass.setSize.apply(this, [w, h]);
- // return;
- //}
- //if(!this.el || !this.getEditor()) {
- // this.setSize.defer(100,this, [w,h]);
- // return;
- //}
+ // 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.form.FCKeditor.superclass.setSize.apply(this, [w, h]);
+
+ Roo.menu.MenuMgr.hideAll();
+
- this.frame.dom.setAttribute('width', w);
- this.frame.dom.setAttribute('height', h);
- this.frame.setSize(w,h);
+
+ // update the footer
+ //
+ this.updateFooter(ans);
+
},
- toggleSourceEdit : function(value) {
-
-
-
- this.el.dom.style.display = value ? '' : 'none';
- this.frame.dom.style.display = value ? 'none' : '';
+ 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);
+ }
},
-
- focus: function(tag)
+
+ updateFooter : function(ans)
{
- if (this.frame.dom.style.display == 'none') {
- return Roo.form.FCKeditor.superclass.focus.call(this);
- }
- if(!this.el || !this.getEditor()) {
- this.focus.defer(100,this, [tag]);
+ var html = '';
+ if (ans === false) {
+ this.footDisp.dom.innerHTML = '';
return;
}
+ this.footerEls = ans.reverse();
+ Roo.each(this.footerEls, function(a,i) {
+ if (!a) { return; }
+ html += html.length ? ' > ' : '';
+
+ html += '<span class="x-ed-loc-' + i + '">' + a.tagName + '</span>';
+
+ });
+
+ //
+ var sz = this.footDisp.up('td').getSize();
+ this.footDisp.dom.style.width = (sz.width -10) + 'px';
+ this.footDisp.dom.style.marginLeft = '5px';
+ this.footDisp.dom.style.overflow = 'hidden';
-
- var tgs = this.getEditor().EditorDocument.getElementsByTagName(tag);
- this.getEditor().Focus();
- if (tgs.length) {
- if (!this.getEditor().Selection.GetSelection()) {
- this.focus.defer(100,this, [tag]);
- return;
- }
+ this.footDisp.dom.innerHTML = html;
+
+ },
+
+
+ // private
+ onDestroy : function(){
+ if(this.rendered){
- var r = this.getEditor().EditorDocument.createRange();
- r.setStart(tgs[0],0);
- r.setEnd(tgs[0],0);
- this.getEditor().Selection.GetSelection().removeAllRanges();
- this.getEditor().Selection.GetSelection().addRange(r);
- this.getEditor().Focus();
+ this.tb.items.each(function(item){
+ if(item.menu){
+ item.menu.removeAll();
+ if(item.menu.el){
+ item.menu.el.destroy();
+ }
+ }
+ item.destroy();
+ });
+
}
-
},
-
-
-
- replaceTextarea : function()
+ onFirstFocus: function() {
+ // need to do this for all the toolbars..
+ this.tb.items.each(function(item){
+ item.enable();
+ });
+ },
+ buildToolbar: function(tlist, nm, friendly_name, block)
{
- if ( document.getElementById( this.getId() + '___Frame' ) ) {
- return ;
- }
- //if ( !this.checkBrowser || this._isCompatibleBrowser() )
- //{
- // We must check the elements firstly using the Id and then the name.
- var oTextarea = document.getElementById( this.getId() );
+ var editor = this.editor;
+ var editorcore = this.editorcore;
+ // create a new element.
+ var wdiv = editor.wrap.createChild({
+ tag: 'div'
+ }, editor.wrap.dom.firstChild.nextSibling, true);
- var colElementsByName = document.getElementsByName( this.getId() ) ;
-
- oTextarea.style.display = 'none' ;
-
- if ( oTextarea.tabIndex ) {
- this.TabIndex = oTextarea.tabIndex ;
+
+ var tb = new Roo.Toolbar(wdiv);
+ ///this.tb = tb; // << this sets the active toolbar..
+ if (tlist === false && block) {
+ tlist = block.contextMenu(this);
}
- this._insertHtmlBefore( this._getConfigHtml(), oTextarea ) ;
- this._insertHtmlBefore( this._getIFrameHtml(), oTextarea ) ;
- this.frame = Roo.get(this.getId() + '___Frame')
- },
+ tb.hasStyles = false;
+ tb.name = nm;
+
+ tb.add((typeof(friendly_name) == 'undefined' ? nm : friendly_name) + ": ");
+
+ var styles = Array.from(this.styles);
+
+
+ // 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({
+ id : 'val',
+ fields: ['val', 'selected'],
+ data : []
+ }),
+ name : '-roo-edit-className',
+ attrname : 'className',
+ displayField: 'val',
+ typeAhead: false,
+ mode: 'local',
+ editable : false,
+ triggerAction: 'all',
+ emptyText:'Select Style',
+ selectOnFocus:true,
+ width: 130,
+ listeners : {
+ 'select': function(c, r, i) {
+ // initial support only for on class per el..
+ tb.selectedNode.className = r ? r.get('val') : '';
+ editorcore.syncValue();
+ }
+ }
- _getConfigHtml : function()
- {
- var sConfig = '' ;
+ }));
+ }
+
+ var tbc = Roo.form.HtmlEditor.ToolbarContext;
+
+
+ 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) { // use the b
+ opts = Roo.form.HtmlEditor.ToolbarContext.options[item.optname];
+
+ }
+
+ if (opts) {
+ // opts == pulldown..
+ tb.addField( new Roo.form.ComboBox({
+ store: typeof(tbc.stores[i]) != 'undefined' ? Roo.factory(tbc.stores[i],Roo.data) : new Roo.data.SimpleStore({
+ id : 'val',
+ fields: ['val', 'display'],
+ data : opts
+ }),
+ 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[tlist[i].name]) != 'undefined' ? 'remote' : 'local',
+ editable : false,
+ triggerAction: 'all',
+ emptyText:'Select',
+ selectOnFocus:true,
+ 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();
+ }
+ }
- for ( var o in this.fckconfig ) {
- sConfig += sConfig.length > 0 ? '&' : '';
- sConfig += encodeURIComponent( o ) + '=' + encodeURIComponent( this.fckconfig[o] ) ;
+ }));
+ continue;
+
+
+ /*
+ tb.addField( new Roo.form.TextField({
+ name: i,
+ width: 100,
+ //allowBlank:false,
+ value: ''
+ }));
+ continue;
+ */
+ }
+ tb.addField( new Roo.form.TextField({
+ 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( {
+ text: 'Stylesheets',
- return '<input type="hidden" id="' + this.getId() + '___Config" value="' + sConfig + '" style="display:none" />' ;
+ listeners : {
+ click : function ()
+ {
+ _this.editor.fireEvent('stylesheetsclick', _this.editor);
+ }
+ }
+ });
+ }
+
+ tb.addFill();
+ 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);
+
+ }
+ }
+
+
+
+
+ });
+ }
+
+ tb.el.on('click', function(e){
+ e.preventDefault(); // what does this do?
+ });
+ tb.el.setVisibilityMode( Roo.Element.DISPLAY);
+ tb.el.hide();
+
+ // dont need to disable them... as they will get hidden
+ return tb;
+
+
},
-
-
- _getIFrameHtml : function()
+ buildFooter : function()
{
- var sFile = 'fckeditor.html' ;
- /* no idea what this is about..
- try
- {
- if ( (/fcksource=true/i).test( window.top.location.search ) )
- sFile = 'fckeditor.original.html' ;
- }
- catch (e) {
- */
-
- var sLink = this.basePath + 'editor/' + sFile + '?InstanceName=' + encodeURIComponent( this.getId() ) ;
- sLink += this.toolbarSet ? ( '&Toolbar=' + this.toolbarSet) : '';
+
+ var fel = this.editor.wrap.createChild();
+ this.footer = new Roo.Toolbar(fel);
+ // toolbar has scrolly on left / right?
+ var footDisp= new Roo.Toolbar.Fill();
+ var _t = this;
+ this.footer.add(
+ {
+ text : '<',
+ xtype: 'Button',
+ handler : function() {
+ _t.footDisp.scrollTo('left',0,true)
+ }
+ }
+ );
+ this.footer.add( footDisp );
+ this.footer.add(
+ {
+ text : '>',
+ xtype: 'Button',
+ handler : function() {
+ // no animation..
+ _t.footDisp.select('span').last().scrollIntoView(_t.footDisp,true);
+ }
+ }
+ );
+ var fel = Roo.get(footDisp.el);
+ fel.addClass('x-editor-context');
+ this.footDispWrap = fel;
+ this.footDispWrap.overflow = 'hidden';
+
+ this.footDisp = fel.createChild();
+ this.footDispWrap.on('click', this.onContextClick, this)
- var html = '<iframe id="' + this.getId() +
- '___Frame" src="' + sLink +
- '" width="' + this.width +
- '" height="' + this.height + '"' +
- (this.tabIndex ? ' tabindex="' + this.tabIndex + '"' :'' ) +
- ' frameborder="0" scrolling="no"></iframe>' ;
-
- return html ;
},
-
- _insertHtmlBefore : function( html, element )
+ // when the footer contect changes
+ onContextClick : function (ev,dom)
{
- if ( element.insertAdjacentHTML ) {
- // IE
- element.insertAdjacentHTML( 'beforeBegin', html ) ;
- } else { // Gecko
- var oRange = document.createRange() ;
- oRange.setStartBefore( element ) ;
- var oFragment = oRange.createContextualFragment( html );
- element.parentNode.insertBefore( oFragment, element ) ;
+ ev.preventDefault();
+ var cn = dom.className;
+ //Roo.log(cn);
+ if (!cn.match(/x-ed-loc-/)) {
+ return;
}
+ var n = cn.split('-').pop();
+ var ans = this.footerEls;
+ var sel = ans[n];
+
+ this.editorcore.selectNode(sel);
+
+
+ this.updateToolbar(null, null, sel);
+
+
}
-
-
-
});
-//Roo.reg('fckeditor', Roo.form.FCKeditor);
-
-function FCKeditor_OnComplete(editorInstance){
- var f = Roo.form.FCKeditor.editors[editorInstance.Name];
- f.fckEditor = editorInstance;
- //console.log("loaded");
- f.fireEvent('editorinit', f, editorInstance);
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//<script type="text/javascript">
+/*
+ * 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">
+ */
+
/**
- * @class Roo.form.GridField
- * @extends Roo.form.Field
- * Embed a grid (or editable grid into a form)
- * STATUS ALPHA
- *
- * This embeds a grid in a form, the value of the field should be the json encoded array of rows
- * it needs
- * xgrid.store = Roo.data.Store
- * xgrid.store.proxy = Roo.data.MemoryProxy (data = [] )
- * xgrid.store.reader = Roo.data.JsonReader
- *
- *
+ * @class Roo.form.BasicForm
+ * @extends Roo.util.Observable
+ * Supplies the functionality to do "actions" on forms and initialize Roo.form.Field types on existing markup.
* @constructor
- * Creates a new GridField
+ * @param {String/HTMLElement/Roo.Element} el The form element or its id
* @param {Object} config Configuration options
*/
-Roo.form.GridField = function(config){
- Roo.form.GridField.superclass.constructor.call(this, config);
+Roo.form.BasicForm = function(el, config){
+ this.allItems = [];
+ this.childForms = [];
+ Roo.apply(this, config);
+ /*
+ * The Roo.form.Field items in this form.
+ * @type MixedCollection
+ */
+
+ this.items = new Roo.util.MixedCollection(false, function(o){
+ return o.id || (o.id = Roo.id());
+ });
+ this.addEvents({
+ /**
+ * @event beforeaction
+ * Fires before any action is performed. Return false to cancel the action.
+ * @param {Form} this
+ * @param {Action} action The action to be performed
+ */
+ beforeaction: true,
+ /**
+ * @event actionfailed
+ * Fires when an action fails.
+ * @param {Form} this
+ * @param {Action} action The action that failed
+ */
+ actionfailed : true,
+ /**
+ * @event actioncomplete
+ * Fires when an action is completed.
+ * @param {Form} this
+ * @param {Action} action The action that completed
+ */
+ actioncomplete : true
+ });
+ if(el){
+ this.initEl(el);
+ }
+ Roo.form.BasicForm.superclass.constructor.call(this);
+
+ Roo.form.BasicForm.popover.apply();
};
-Roo.extend(Roo.form.GridField, Roo.form.Field, {
+Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
/**
- * @cfg {Number} width - used to restrict width of grid..
+ * @cfg {String} method
+ * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
*/
- width : 100,
/**
- * @cfg {Number} height - used to restrict height of grid..
+ * @cfg {DataReader} reader
+ * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
+ * This is optional as there is built-in support for processing JSON.
*/
- height : 50,
- /**
- * @cfg {Object} xgrid (xtype'd description of grid) { xtype : 'Grid', dataSource: .... }
- *
- *}
+ /**
+ * @cfg {DataReader} errorReader
+ * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
+ * This is completely optional as there is built-in support for processing JSON.
*/
- xgrid : false,
/**
- * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
- * {tag: "input", type: "checkbox", autocomplete: "off"})
+ * @cfg {String} url
+ * The URL to use for form actions if one isn't supplied in the action options.
*/
- // defaultAutoCreate : { tag: 'div' },
- defaultAutoCreate : { tag: 'input', type: 'hidden', autocomplete: 'new-password'},
/**
- * @cfg {String} addTitle Text to include for adding a title.
+ * @cfg {Boolean} fileUpload
+ * Set to true if this form is a file upload.
*/
- addTitle : false,
- //
- onResize : function(){
- Roo.form.Field.superclass.onResize.apply(this, arguments);
- },
-
- initEvents : function(){
- // Roo.form.Checkbox.superclass.initEvents.call(this);
- // has no events...
-
- },
-
-
- getResizeEl : function(){
- return this.wrap;
- },
-
- getPositionEl : function(){
- return this.wrap;
- },
-
- // private
- onRender : function(ct, position){
-
- this.style = this.style || 'overflow: hidden; border:1px solid #c3daf9;';
- var style = this.style;
- delete this.style;
-
- Roo.form.GridField.superclass.onRender.call(this, ct, position);
- this.wrap = this.el.wrap({cls: ''}); // not sure why ive done thsi...
- this.viewEl = this.wrap.createChild({ tag: 'div' });
- if (style) {
- this.viewEl.applyStyles(style);
- }
- if (this.width) {
- this.viewEl.setWidth(this.width);
- }
- if (this.height) {
- this.viewEl.setHeight(this.height);
- }
- //if(this.inputValue !== undefined){
- //this.setValue(this.value);
-
-
- this.grid = new Roo.grid[this.xgrid.xtype](this.viewEl, this.xgrid);
-
-
- this.grid.render();
- this.grid.getDataSource().on('remove', this.refreshValue, this);
- this.grid.getDataSource().on('update', this.refreshValue, this);
- this.grid.on('afteredit', this.refreshValue, this);
-
- },
-
/**
- * Sets the value of the item.
- * @param {String} either an object or a string..
+ * @cfg {Object} baseParams
+ * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
*/
- setValue : function(v){
- //this.value = v;
- v = v || []; // empty set..
- // this does not seem smart - it really only affects memoryproxy grids..
- if (this.grid && this.grid.getDataSource() && typeof(v) != 'undefined') {
- var ds = this.grid.getDataSource();
- // assumes a json reader..
- var data = {}
- data[ds.reader.meta.root ] = typeof(v) == 'string' ? Roo.decode(v) : v;
- ds.loadData( data);
- }
- // clear selection so it does not get stale.
- if (this.grid.sm) {
- this.grid.sm.clearSelections();
- }
-
- Roo.form.GridField.superclass.setValue.call(this, v);
- this.refreshValue();
- // should load data in the grid really....
- },
-
- // private
- refreshValue: function() {
- var val = [];
- this.grid.getDataSource().each(function(r) {
- val.push(r.data);
- });
- this.el.dom.value = Roo.encode(val);
- }
-
+ /**
+ /**
+ * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
+ */
+ timeout: 30,
+
+ // private
+ activeAction : null,
+
+ /**
+ * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
+ * or setValues() data instead of when the form was first created.
+ */
+ trackResetOnLoad : false,
-});/*
- * 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">
- */
-/**
- * @class Roo.form.DisplayField
- * @extends Roo.form.Field
- * A generic Field to display non-editable data.
- * @cfg {Boolean} closable (true|false) default false
- * @constructor
- * Creates a new Display Field item.
- * @param {Object} config Configuration options
- */
-Roo.form.DisplayField = function(config){
- Roo.form.DisplayField.superclass.constructor.call(this, config);
-
- this.addEvents({
- /**
- * @event close
- * Fires after the click the close btn
- * @param {Roo.form.DisplayField} this
- */
- close : true
- });
-};
-
-Roo.extend(Roo.form.DisplayField, Roo.form.TextField, {
- inputType: 'hidden',
- allowBlank: true,
- readOnly: true,
+ /**
+ * childForms - used for multi-tab forms
+ * @type {Array}
+ */
+ childForms : false,
-
/**
- * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
+ * allItems - full list of fields.
+ * @type {Array}
*/
- focusClass : undefined,
+ allItems : false,
+
/**
- * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
+ * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
+ * element by passing it or its id or mask the form itself by passing in true.
+ * @type Mixed
*/
- fieldClass: 'x-form-field',
+ waitMsgTarget : false,
- /**
- * @cfg {Function} valueRenderer The renderer for the field (so you can reformat output). should return raw HTML
+ /**
+ * @type Boolean
*/
- valueRenderer: undefined,
+ disableMask : false,
- width: 100,
/**
- * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
- * {tag: "input", type: "checkbox", autocomplete: "off"})
+ * @cfg {Boolean} errorMask (true|false) default false
*/
-
- // defaultAutoCreate : { tag: 'input', type: 'hidden', autocomplete: 'off'},
-
- closable : false,
+ errorMask : false,
- onResize : function(){
- Roo.form.DisplayField.superclass.onResize.apply(this, arguments);
-
- },
-
- initEvents : function(){
- // Roo.form.Checkbox.superclass.initEvents.call(this);
- // has no events...
-
- if(this.closable){
- this.closeEl.on('click', this.onClose, this);
- }
-
- },
-
+ /**
+ * @cfg {Number} maskOffset Default 100
+ */
+ maskOffset : 100,
- getResizeEl : function(){
- return this.wrap;
+ // private
+ initEl : function(el){
+ this.el = Roo.get(el);
+ this.id = this.el.id || Roo.id();
+ this.el.on('submit', this.onSubmit, this);
+ this.el.addClass('x-form');
},
- getPositionEl : function(){
- return this.wrap;
+ // private
+ onSubmit : function(e){
+ e.stopEvent();
},
- // private
- onRender : function(ct, position){
-
- Roo.form.DisplayField.superclass.onRender.call(this, ct, position);
- //if(this.inputValue !== undefined){
- this.wrap = this.el.wrap();
-
- this.viewEl = this.wrap.createChild({ tag: 'div', cls: 'x-form-displayfield'});
-
- if(this.closable){
- this.closeEl = this.wrap.createChild({ tag: 'div', cls: 'x-dlg-close'});
- }
+ /**
+ * Returns true if client-side validation on the form is successful.
+ * @return Boolean
+ */
+ isValid : function(){
+ var valid = true;
+ var target = false;
+ this.items.each(function(f){
+ if(f.validate()){
+ return;
+ }
+
+ valid = false;
+
+ if(!target && f.el.isVisible(true)){
+ target = f;
+ }
+ });
- if (this.bodyStyle) {
- this.viewEl.applyStyles(this.bodyStyle);
+ if(this.errorMask && !valid){
+ Roo.form.BasicForm.popover.mask(this, target);
}
- //this.viewEl.setStyle('padding', '2px');
-
- this.setValue(this.value);
-
- },
-/*
- // private
- initValue : Roo.emptyFn,
-
- */
-
- // private
- onClick : function(){
+ return valid;
},
-
/**
- * Sets the checked state of the checkbox.
- * @param {Boolean/String} checked True, 'true', '1', or 'on' to check the checkbox, any other value will uncheck it.
+ * Returns array of invalid form fields.
+ * @return Array
*/
- setValue : function(v){
- this.value = v;
- var html = this.valueRenderer ? this.valueRenderer(v) : String.format('{0}', v);
- // this might be called before we have a dom element..
- if (!this.viewEl) {
- return;
- }
- this.viewEl.dom.innerHTML = html;
- Roo.form.DisplayField.superclass.setValue.call(this, v);
-
- },
- onClose : function(e)
+ invalidFields : function()
{
- e.preventDefault();
+ var ret = [];
+ this.items.each(function(f){
+ if(f.validate()){
+ return;
+ }
+ ret.push(f);
+
+ });
- this.fireEvent('close', this);
- }
-});/*
- *
- * Licence- LGPL
- *
- */
-
-/**
- * @class Roo.form.DayPicker
- * @extends Roo.form.Field
- * A Day picker show [M] [T] [W] ....
- * @constructor
- * Creates a new Day Picker
- * @param {Object} config Configuration options
- */
-Roo.form.DayPicker= function(config){
- Roo.form.DayPicker.superclass.constructor.call(this, config);
-
-};
-
-Roo.extend(Roo.form.DayPicker, Roo.form.Field, {
+ return ret;
+ },
+
+
/**
- * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
+ * DEPRICATED Returns true if any fields in this form have changed since their original load.
+ * @return Boolean
*/
- focusClass : undefined,
+ isDirty : function(){
+ var dirty = false;
+ this.items.each(function(f){
+ if(f.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+ return dirty;
+ },
+
/**
- * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
+ * Returns true if any fields in this form have changed since their original load. (New version)
+ * @return Boolean
*/
- fieldClass: "x-form-field",
-
+
+ hasChanged : function()
+ {
+ var dirty = false;
+ this.items.each(function(f){
+ if(f.hasChanged()){
+ dirty = true;
+ return false;
+ }
+ });
+ return dirty;
+
+ },
/**
- * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
- * {tag: "input", type: "checkbox", autocomplete: "off"})
+ * Resets all hasChanged to 'false' -
+ * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
+ * So hasChanged storage is only to be used for this purpose
+ * @return Boolean
*/
- defaultAutoCreate : { tag: "input", type: 'hidden', autocomplete: "new-password"},
-
-
- actionMode : 'viewEl',
- //
- // private
-
- inputType : 'hidden',
+ resetHasChanged : function()
+ {
+ this.items.each(function(f){
+ f.resetHasChanged();
+ });
+
+ },
-
- inputElement: false, // real input element?
- basedOn: false, // ????
- isFormField: true, // not sure where this is needed!!!!
-
- onResize : function(){
- Roo.form.Checkbox.superclass.onResize.apply(this, arguments);
- if(!this.boxLabel){
- this.el.alignTo(this.wrap, 'c-c');
+ /**
+ * Performs a predefined action (submit or load) or custom actions you define on this form.
+ * @param {String} actionName The name of the action type
+ * @param {Object} options (optional) The options to pass to the action. All of the config options listed
+ * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
+ * accept other config options):
+ * <pre>
+Property Type Description
+---------------- --------------- ----------------------------------------------------------------------------------
+url String The url for the action (defaults to the form's url)
+method String The form method to use (defaults to the form's method, or POST if not defined)
+params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
+clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
+ validate the form on the client (defaults to false)
+ * </pre>
+ * @return {BasicForm} this
+ */
+ doAction : function(action, options){
+ if(typeof action == 'string'){
+ action = new Roo.form.Action.ACTION_TYPES[action](this, options);
+ }
+ if(this.fireEvent('beforeaction', this, action) !== false){
+ this.beforeAction(action);
+ action.run.defer(100, action);
}
+ return this;
},
- initEvents : function(){
- Roo.form.Checkbox.superclass.initEvents.call(this);
- this.el.on("click", this.onClick, this);
- this.el.on("change", this.onClick, this);
+ /**
+ * Shortcut to do a submit action.
+ * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
+ * @return {BasicForm} this
+ */
+ submit : function(options){
+ this.doAction('submit', options);
+ return this;
},
+ /**
+ * Shortcut to do a load action.
+ * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
+ * @return {BasicForm} this
+ */
+ load : function(options){
+ this.doAction('load', options);
+ return this;
+ },
- getResizeEl : function(){
- return this.wrap;
+ /**
+ * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
+ * @param {Record} record The record to edit
+ * @return {BasicForm} this
+ */
+ updateRecord : function(record){
+ record.beginEdit();
+ var fs = record.fields;
+ fs.each(function(f){
+ var field = this.findField(f.name);
+ if(field){
+ record.set(f.name, field.getValue());
+ }
+ }, this);
+ record.endEdit();
+ return this;
},
- getPositionEl : function(){
- return this.wrap;
+ /**
+ * Loads an Roo.data.Record into this form.
+ * @param {Record} record The record to load
+ * @return {BasicForm} this
+ */
+ loadRecord : function(record){
+ this.setValues(record.data);
+ return this;
},
-
// private
- onRender : function(ct, position){
- Roo.form.Checkbox.superclass.onRender.call(this, ct, position);
-
- this.wrap = this.el.wrap({cls: 'x-form-daypick-item '});
+ beforeAction : function(action){
+ var o = action.options;
- var r1 = '<table><tr>';
- var r2 = '<tr class="x-form-daypick-icons">';
- for (var i=0; i < 7; i++) {
- r1+= '<td><div>' + Date.dayNames[i].substring(0,3) + '</div></td>';
- r2+= '<td><img class="x-menu-item-icon" src="' + Roo.BLANK_IMAGE_URL +'"></td>';
+ if(!this.disableMask) {
+ if(this.waitMsgTarget === true){
+ this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
+ }else if(this.waitMsgTarget){
+ this.waitMsgTarget = Roo.get(this.waitMsgTarget);
+ this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
+ }else {
+ Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
+ }
}
- var viewEl = this.wrap.createChild( r1 + '</tr>' + r2 + '</tr></table>');
- viewEl.select('img').on('click', this.onClick, this);
- this.viewEl = viewEl;
-
+
+ },
+
+ // private
+ afterAction : function(action, success){
+ this.activeAction = null;
+ var o = action.options;
- // this will not work on Chrome!!!
- this.el.on('DOMAttrModified', this.setFromHidden, this); //ff
- this.el.on('propertychange', this.setFromHidden, this); //ie
+ if(!this.disableMask) {
+ if(this.waitMsgTarget === true){
+ this.el.unmask();
+ }else if(this.waitMsgTarget){
+ this.waitMsgTarget.unmask();
+ }else{
+ Roo.MessageBox.updateProgress(1);
+ Roo.MessageBox.hide();
+ }
+ }
+ if(success){
+ if(o.reset){
+ this.reset();
+ }
+ Roo.callback(o.success, o.scope, [this, action]);
+ this.fireEvent('actioncomplete', this, action);
+
+ }else{
+
+ // failure condition..
+ // we have a scenario where updates need confirming.
+ // eg. if a locking scenario exists..
+ // we look for { errors : { needs_confirm : true }} in the response.
+ if (
+ (typeof(action.result) != 'undefined') &&
+ (typeof(action.result.errors) != 'undefined') &&
+ (typeof(action.result.errors.needs_confirm) != 'undefined')
+ ){
+ var _t = this;
+ Roo.MessageBox.confirm(
+ "Change requires confirmation",
+ action.result.errorMsg,
+ function(r) {
+ if (r != 'yes') {
+ return;
+ }
+ _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
+ }
+
+ );
+
+
+
+ return;
+ }
+
+ Roo.callback(o.failure, o.scope, [this, action]);
+ // show an error message if no failed handler is set..
+ if (!this.hasListener('actionfailed')) {
+ Roo.MessageBox.alert("Error",
+ (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
+ action.result.errorMsg :
+ "Saving Failed, please check your entries or try again"
+ );
+ }
+
+ this.fireEvent('actionfailed', this, action);
+ }
-
-
},
- // private
- initValue : Roo.emptyFn,
-
/**
- * Returns the checked state of the checkbox.
- * @return {Boolean} True if checked, else false
+ * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
+ * @param {String} id The value to search for
+ * @return Field
*/
- getValue : function(){
- return this.el.dom.value;
-
- },
-
- // private
- onClick : function(e){
- //this.setChecked(!this.checked);
- Roo.get(e.target).toggleClass('x-menu-item-checked');
- this.refreshValue();
- //if(this.el.dom.checked != this.checked){
- // this.setValue(this.el.dom.checked);
- // }
- },
-
- // private
- refreshValue : function()
- {
- var val = '';
- this.viewEl.select('img',true).each(function(e,i,n) {
- val += e.is(".x-menu-item-checked") ? String(n) : '';
- });
- this.setValue(val, true);
+ findField : function(id){
+ var field = this.items.get(id);
+ if(!field){
+ this.items.each(function(f){
+ if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
+ field = f;
+ return false;
+ }
+ });
+ }
+ return field || null;
},
/**
- * Sets the checked state of the checkbox.
- * On is always based on a string comparison between inputValue and the param.
- * @param {Boolean/String} value - the value to set
- * @param {Boolean/String} suppressEvent - whether to suppress the checkchange event.
+ * Add a secondary form to this one,
+ * Used to provide tabbed forms. One form is primary, with hidden values
+ * which mirror the elements from the other forms.
+ *
+ * @param {Roo.form.Form} form to add.
+ *
*/
- setValue : function(v,suppressEvent){
- if (!this.el.dom) {
+ addForm : function(form)
+ {
+
+ if (this.childForms.indexOf(form) > -1) {
+ // already added..
return;
}
- var old = this.el.dom.value ;
- this.el.dom.value = v;
- if (suppressEvent) {
- return ;
- }
-
- // update display..
- this.viewEl.select('img',true).each(function(e,i,n) {
+ this.childForms.push(form);
+ var n = '';
+ Roo.each(form.allItems, function (fe) {
- var on = e.is(".x-menu-item-checked");
- var newv = v.indexOf(String(n)) > -1;
- if (on != newv) {
- e.toggleClass('x-menu-item-checked');
+ n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
+ if (this.findField(n)) { // already added..
+ return;
}
+ var add = new Roo.form.Hidden({
+ name : n
+ });
+ add.render(this.el);
- });
-
-
- this.fireEvent('change', this, v, old);
-
+ this.add( add );
+ }, this);
},
-
- // handle setting of hidden value by some other method!!?!?
- setFromHidden: function()
- {
- if(!this.el){
- return;
+ /**
+ * Mark fields in this form invalid in bulk.
+ * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
+ * @return {BasicForm} this
+ */
+ markInvalid : function(errors){
+ if(errors instanceof Array){
+ for(var i = 0, len = errors.length; i < len; i++){
+ var fieldError = errors[i];
+ var f = this.findField(fieldError.id);
+ if(f){
+ f.markInvalid(fieldError.msg);
+ }
+ }
+ }else{
+ var field, id;
+ for(id in errors){
+ if(typeof errors[id] != 'function' && (field = this.findField(id))){
+ field.markInvalid(errors[id]);
+ }
+ }
}
- //console.log("SET FROM HIDDEN");
- //alert('setFrom hidden');
- this.setValue(this.el.dom.value);
+ Roo.each(this.childForms || [], function (f) {
+ f.markInvalid(errors);
+ });
+
+ return this;
},
-
- onDestroy : function()
- {
- if(this.viewEl){
- Roo.get(this.viewEl).remove();
- }
-
- Roo.form.DayPicker.superclass.onDestroy.call(this);
- }
-
-});/*
- * RooJS Library 1.1.1
- * Copyright(c) 2008-2011 Alan Knowles
- *
- * License - LGPL
- */
-
-/**
- * @class Roo.form.ComboCheck
- * @extends Roo.form.ComboBox
- * A combobox for multiple select items.
- *
- * FIXME - could do with a reset button..
- *
- * @constructor
- * Create a new ComboCheck
- * @param {Object} config Configuration options
- */
-Roo.form.ComboCheck = 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.ComboCheck : missing value for: " + e;
+ /**
+ * Set values for fields in this form in bulk.
+ * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
+ * @return {BasicForm} this
+ */
+ setValues : function(values){
+ if(values instanceof Array){ // array of objects
+ for(var i = 0, len = values.length; i < len; i++){
+ var v = values[i];
+ var f = this.findField(v.id);
+ if(f){
+ f.setValue(v.value);
+ if(this.trackResetOnLoad){
+ f.originalValue = f.getValue();
+ }
+ }
+ }
+ }else{ // object hash
+ var field, id;
+ for(id in values){
+ if(typeof values[id] != 'function' && (field = this.findField(id))){
+
+ if (field.setFromData &&
+ field.valueField &&
+ field.displayField &&
+ // combos' with local stores can
+ // be queried via setValue()
+ // to set their value..
+ (field.store && !field.store.isLocal)
+ ) {
+ // it's a combo
+ var sd = { };
+ sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
+ sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
+ field.setFromData(sd);
+
+ } else {
+ field.setValue(values[id]);
+ }
+
+
+ if(this.trackResetOnLoad){
+ field.originalValue = field.getValue();
+ }
+ }
+ }
}
- });
-
-
-};
-
-Roo.extend(Roo.form.ComboCheck, Roo.form.ComboBox, {
-
-
- editable : false,
-
- selectedClass: 'x-menu-item-checked',
-
- // private
- onRender : function(ct, position){
- var _t = this;
-
+ this.resetHasChanged();
- if(!this.tpl){
- var cls = 'x-combo-list';
-
-
- this.tpl = new Roo.Template({
- html : '<div class="'+cls+'-item x-menu-check-item">' +
- '<img class="x-menu-item-icon" style="margin: 0px;" src="' + Roo.BLANK_IMAGE_URL + '">' +
- '<span>{' + this.displayField + '}</span>' +
- '</div>'
+ Roo.each(this.childForms || [], function (f) {
+ f.setValues(values);
+ f.resetHasChanged();
+ });
- });
- }
+ 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 (default false)
+ * @return {Object}
+ */
+ getValues : function(asString)
+ {
+ if (this.childForms) {
+ // copy values from the child forms
+ Roo.each(this.childForms, function (f) {
+ this.setValues(f.getFieldValues()); // get the full set of data, as we might be copying comboboxes from external into this one.
+ }, this);
+ }
- Roo.form.ComboCheck.superclass.onRender.call(this, ct, position);
- this.view.singleSelect = false;
- this.view.multiSelect = true;
- this.view.toggleSelect = true;
- this.pageTb.add(new Roo.Toolbar.Fill(), {
-
- text: 'Done',
- handler: function()
- {
- _t.collapse();
+ // 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) {
+
}
- });
- },
-
- onViewOver : function(e, t){
- // do nothing...
- return;
+
+ }
- },
-
- onViewClick : function(doFocus,index){
- return;
+ var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
+ if(asString === true){
+ return fs;
+ }
+ return Roo.urlDecode(fs);
},
- select: function () {
- //Roo.log("SELECT CALLED");
- },
-
- selectByValue : function(xv, scrollIntoView){
- var ar = this.getValueArray();
- var sels = [];
+
+ /**
+ * 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_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.getFieldValues());
+ }, this);
+ }
- Roo.each(ar, function(v) {
- if(v === undefined || v === null){
+ 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;
}
- var r = this.findRecord(this.valueField, v);
- if(r){
- sels.push(this.store.indexOf(r))
+ var v = f.getValue();
+ if (f.inputType =='radio') {
+ if (typeof(ret[f.getName()]) == 'undefined') {
+ ret[f.getName()] = ''; // empty..
+ }
+
+ if (!f.el.dom.checked) {
+ return;
+
+ }
+ v = f.el.dom.value;
}
- },this);
- this.view.select(sels);
- return false;
- },
-
-
-
- onSelect : function(record, index){
- // Roo.log("onselect Called");
- // this is only called by the clear button now..
- this.view.clearSelections();
- this.setValue('[]');
- if (this.value != this.valueBefore) {
- this.fireEvent('change', this, this.value, this.valueBefore);
- this.valueBefore = this.value;
- }
- },
- getValueArray : function()
- {
- var ar = [] ;
-
- try {
- //Roo.log(this.value);
- if (typeof(this.value) == 'undefined') {
- return [];
- }
- var ar = Roo.decode(this.value);
- return ar instanceof Array ? ar : []; //?? valid?
- } catch(e) {
- Roo.log(e + "\nRoo.form.ComboCheck:getValueArray invalid data:" + this.getValue());
- return [];
- }
-
+ // not sure if this supported any more..
+ if ((typeof(v) == 'object') && f.getRawValue) {
+ v = f.getRawValue() ; // dates..
+ }
+ // combo boxes where name != hiddenName...
+ if (f.name != f.getName()) {
+ ret[f.name] = f.getRawValue();
+ }
+ ret[f.getName()] = v;
+ });
+
+ return ret;
},
- expand : function ()
- {
+
+ /**
+ * Clears all invalid messages in this form.
+ * @return {BasicForm} this
+ */
+ clearInvalid : function(){
+ this.items.each(function(f){
+ f.clearInvalid();
+ });
- Roo.form.ComboCheck.superclass.expand.call(this);
- this.valueBefore = typeof(this.value) == 'undefined' ? '' : this.value;
- //this.valueBefore = typeof(this.valueBefore) == 'undefined' ? '' : this.valueBefore;
+ Roo.each(this.childForms || [], function (f) {
+ f.clearInvalid();
+ });
-
+
+ return this;
},
-
- collapse : function(){
- Roo.form.ComboCheck.superclass.collapse.call(this);
- var sl = this.view.getSelectedIndexes();
- var st = this.store;
- var nv = [];
- var tv = [];
- var r;
- Roo.each(sl, function(i) {
- r = st.getAt(i);
- nv.push(r.get(this.valueField));
- },this);
- this.setValue(Roo.encode(nv));
- if (this.value != this.valueBefore) {
- this.fireEvent('change', this, this.value, this.valueBefore);
- this.valueBefore = this.value;
- }
+ /**
+ * Resets this form.
+ * @return {BasicForm} this
+ */
+ reset : function(){
+ this.items.each(function(f){
+ f.reset();
+ });
+
+ Roo.each(this.childForms || [], function (f) {
+ f.reset();
+ });
+ this.resetHasChanged();
+ return this;
+ },
+
+ /**
+ * Add Roo.form components to this form.
+ * @param {Field} field1
+ * @param {Field} field2 (optional)
+ * @param {Field} etc (optional)
+ * @return {BasicForm} this
+ */
+ add : function(){
+ this.items.addAll(Array.prototype.slice.call(arguments, 0));
+ return this;
+ },
+
+
+ /**
+ * Removes a field from the items collection (does NOT remove its markup).
+ * @param {Field} field
+ * @return {BasicForm} this
+ */
+ remove : function(field){
+ this.items.remove(field);
+ return this;
+ },
+
+ /**
+ * Looks at the fields in this form, checks them for an id attribute,
+ * and calls applyTo on the existing dom element with that id.
+ * @return {BasicForm} this
+ */
+ render : function(){
+ this.items.each(function(f){
+ if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
+ f.applyTo(f.id);
+ }
+ });
+ return this;
+ },
+
+ /**
+ * Calls {@link Ext#apply} for all fields in this form with the passed object.
+ * @param {Object} values
+ * @return {BasicForm} this
+ */
+ applyToFields : function(o){
+ this.items.each(function(f){
+ Roo.apply(f, o);
+ });
+ return this;
},
+
+ /**
+ * Calls {@link Ext#applyIf} for all field in this form with the passed object.
+ * @param {Object} values
+ * @return {BasicForm} this
+ */
+ applyIfToFields : function(o){
+ this.items.each(function(f){
+ Roo.applyIf(f, o);
+ });
+ return this;
+ }
+});
+
+// back compat
+Roo.BasicForm = Roo.form.BasicForm;
+
+Roo.apply(Roo.form.BasicForm, {
- setValue : function(v){
- // Roo.log(v);
- this.value = v;
+ popover : {
- var vals = this.getValueArray();
- var tv = [];
- Roo.each(vals, function(k) {
- var r = this.findRecord(this.valueField, k);
- if(r){
- tv.push(r.data[this.displayField]);
- }else if(this.valueNotFoundText !== undefined){
- tv.push( this.valueNotFoundText );
+ padding : 5,
+
+ isApplied : false,
+
+ isMasked : false,
+
+ form : false,
+
+ target : false,
+
+ intervalID : false,
+
+ maskEl : false,
+
+ apply : function()
+ {
+ if(this.isApplied){
+ return;
}
- },this);
- // Roo.log(tv);
+
+ this.maskEl = {
+ top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
+ left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
+ bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
+ right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
+ };
+
+ this.maskEl.top.enableDisplayMode("block");
+ this.maskEl.left.enableDisplayMode("block");
+ this.maskEl.bottom.enableDisplayMode("block");
+ this.maskEl.right.enableDisplayMode("block");
+
+ Roo.get(document.body).on('click', function(){
+ this.unmask();
+ }, this);
+
+ Roo.get(document.body).on('touchstart', function(){
+ this.unmask();
+ }, this);
+
+ this.isApplied = true
+ },
+
+ mask : function(form, target)
+ {
+ this.form = form;
+
+ this.target = target;
+
+ if(!this.form.errorMask || !target.el){
+ return;
+ }
+
+ var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
+
+ var ot = this.target.el.calcOffsetsTo(scrollable);
+
+ var scrollTo = ot[1] - this.form.maskOffset;
+
+ scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
+
+ scrollable.scrollTo('top', scrollTo);
+
+ var el = this.target.wrap || this.target.el;
+
+ var box = el.getBox();
+
+ this.maskEl.top.setStyle('position', 'absolute');
+ this.maskEl.top.setStyle('z-index', 10000);
+ this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
+ this.maskEl.top.setLeft(0);
+ this.maskEl.top.setTop(0);
+ this.maskEl.top.show();
+
+ this.maskEl.left.setStyle('position', 'absolute');
+ this.maskEl.left.setStyle('z-index', 10000);
+ this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
+ this.maskEl.left.setLeft(0);
+ this.maskEl.left.setTop(box.y - this.padding);
+ this.maskEl.left.show();
+
+ this.maskEl.bottom.setStyle('position', 'absolute');
+ this.maskEl.bottom.setStyle('z-index', 10000);
+ this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
+ this.maskEl.bottom.setLeft(0);
+ this.maskEl.bottom.setTop(box.bottom + this.padding);
+ this.maskEl.bottom.show();
+
+ this.maskEl.right.setStyle('position', 'absolute');
+ this.maskEl.right.setStyle('z-index', 10000);
+ this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
+ this.maskEl.right.setLeft(box.right + this.padding);
+ this.maskEl.right.setTop(box.y - this.padding);
+ this.maskEl.right.show();
+
+ this.intervalID = window.setInterval(function() {
+ Roo.form.BasicForm.popover.unmask();
+ }, 10000);
+
+ window.onwheel = function(){ return false;};
+
+ (function(){ this.isMasked = true; }).defer(500, this);
+
+ },
+
+ unmask : function()
+ {
+ if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
+ return;
+ }
+
+ this.maskEl.top.setStyle('position', 'absolute');
+ this.maskEl.top.setSize(0, 0).setXY([0, 0]);
+ this.maskEl.top.hide();
+
+ this.maskEl.left.setStyle('position', 'absolute');
+ this.maskEl.left.setSize(0, 0).setXY([0, 0]);
+ this.maskEl.left.hide();
+
+ this.maskEl.bottom.setStyle('position', 'absolute');
+ this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
+ this.maskEl.bottom.hide();
+
+ this.maskEl.right.setStyle('position', 'absolute');
+ this.maskEl.right.setSize(0, 0).setXY([0, 0]);
+ this.maskEl.right.hide();
+
+ window.onwheel = function(){ return true;};
+
+ if(this.intervalID){
+ window.clearInterval(this.intervalID);
+ this.intervalID = false;
+ }
+
+ this.isMasked = false;
+
+ }
- Roo.form.ComboBox.superclass.setValue.call(this, tv.join(', '));
- this.hiddenField.value = v;
- this.value = v;
}
});/*
* Fork - LGPL
* <script type="text/javascript">
*/
-
+
/**
- * @class Roo.form.Signature
- * @extends Roo.form.Field
- * Signature field.
+ * @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.form.Signature = function(config){
- Roo.form.Signature.superclass.constructor.call(this, config);
+Roo.form.Form = function(config){
+ var xitems = [];
+ if (config.items) {
+ xitems = config.items;
+ delete config.items;
+ }
+
- this.addEvents({// not in used??
- /**
- * @event confirm
- * Fires when the 'confirm' icon is pressed (add a listener to enable add button)
- * @param {Roo.form.Signature} combo This combo box
- */
- 'confirm' : true,
+ Roo.form.Form.superclass.constructor.call(this, null, config);
+ this.url = this.url || this.action;
+ if(!this.root){
+ this.root = new Roo.form.Layout(Roo.applyIf({
+ id: Roo.id()
+ }, config));
+ }
+ this.active = this.root;
+ /**
+ * Array of all the buttons that have been added to this form via {@link addButton}
+ * @type Array
+ */
+ this.buttons = [];
+ this.allItems = [];
+ this.addEvents({
/**
- * @event reset
- * Fires when the 'edit' icon is pressed (add a listener to enable add button)
- * @param {Roo.form.ComboBox} combo This combo box
- * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
- */
- 'reset' : true
+ * @event clientvalidation
+ * If the monitorValid config option is true, this event fires repetitively to notify of valid state
+ * @param {Form} this
+ * @param {Boolean} valid true if the form has passed client-side validation
+ */
+ clientvalidation: true,
+ /**
+ * @event rendered
+ * Fires when the form is rendered
+ * @param {Roo.form.Form} form
+ */
+ rendered : true
});
+
+ if (this.progressUrl) {
+ // push a hidden field onto the list of fields..
+ this.addxtype( {
+ xns: Roo.form,
+ xtype : 'Hidden',
+ name : 'UPLOAD_IDENTIFIER'
+ });
+ }
+
+
+ Roo.each(xitems, this.addxtype, this);
+
};
-Roo.extend(Roo.form.Signature, Roo.form.Field, {
+Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
+ /**
+ * @cfg {Roo.Button} buttons[] buttons at bottom of form
+ */
+
/**
- * @cfg {Object} labels Label to use when rendering a form.
- * defaults to
- * labels : {
- * clear : "Clear",
- * confirm : "Confirm"
- * }
+ * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
*/
- labels : {
- clear : "Clear",
- confirm : "Confirm"
- },
/**
- * @cfg {Number} width The signature panel width (defaults to 300)
+ * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
*/
- width: 300,
/**
- * @cfg {Number} height The signature panel height (defaults to 100)
+ * @cfg {String} (left|center|right) buttonAlign Valid values are "left," "center" and "right" (defaults to "center")
*/
- height : 100,
+ buttonAlign:'center',
+
/**
- * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to false)
+ * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to 75)
*/
- allowBlank : false,
+ minButtonWidth:75,
+
+ /**
+ * @cfg {String} labelAlign (left|top|right) Valid values are "left," "top" and "right" (defaults to "left").
+ * This property cascades to child containers if not set.
+ */
+ labelAlign:'left',
+
+ /**
+ * @cfg {Boolean} monitorValid If true the form monitors its valid state <b>client-side</b> and
+ * fires a looping event with that state. This is required to bind buttons to the valid
+ * state using the config value formBind:true on the button.
+ */
+ monitorValid : false,
+
+ /**
+ * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
+ */
+ monitorPoll : 200,
- //private
- // {Object} signPanel The signature SVG panel element (defaults to {})
- signPanel : {},
- // {Boolean} isMouseDown False to validate that the mouse down event (defaults to false)
- isMouseDown : false,
- // {Boolean} isConfirmed validate the signature is confirmed or not for submitting form (defaults to false)
- isConfirmed : false,
- // {String} signatureTmp SVG mapping string (defaults to empty string)
- signatureTmp : '',
+ /**
+ * @cfg {String} progressUrl - Url to return progress data
+ */
+
+ progressUrl : false,
+ /**
+ * @cfg {boolean|FormData} formData - true to use new 'FormData' post, or set to a new FormData({dom form}) Object, if
+ * sending a formdata with extra parameters - eg uploaded elements.
+ */
+ formData : false,
- defaultAutoCreate : { // modified by initCompnoent..
- tag: "input",
- type:"hidden"
+ /**
+ * Opens a new {@link Roo.form.Column} container in the layout stack. If fields are passed after the config, the
+ * fields are added and the column is closed. If no fields are passed the column remains open
+ * until end() is called.
+ * @param {Object} config The config to pass to the column
+ * @param {Field} field1 (optional)
+ * @param {Field} field2 (optional)
+ * @param {Field} etc (optional)
+ * @return Column The column container object
+ */
+ column : function(c){
+ var col = new Roo.form.Column(c);
+ this.start(col);
+ if(arguments.length > 1){ // duplicate code required because of Opera
+ this.add.apply(this, Array.prototype.slice.call(arguments, 1));
+ this.end();
+ }
+ return col;
},
- // private
- onRender : function(ct, position){
-
- Roo.form.Signature.superclass.onRender.call(this, ct, position);
-
- this.wrap = this.el.wrap({
- cls:'x-form-signature-wrap', style : 'width: ' + this.width + 'px', cn:{cls:'x-form-signature'}
- });
-
- this.createToolbar(this);
- this.signPanel = this.wrap.createChild({
- tag: 'div',
- style: 'width: ' + this.width + 'px; height: ' + this.height + 'px; border: 0;'
- }, this.el
- );
-
- this.svgID = Roo.id();
- this.svgEl = this.signPanel.createChild({
- xmlns : 'http://www.w3.org/2000/svg',
- tag : 'svg',
- id : this.svgID + "-svg",
- width: this.width,
- height: this.height,
- viewBox: '0 0 '+this.width+' '+this.height,
- cn : [
- {
- tag: "rect",
- id: this.svgID + "-svg-r",
- width: this.width,
- height: this.height,
- fill: "#ffa"
- },
- {
- tag: "line",
- id: this.svgID + "-svg-l",
- x1: "0", // start
- y1: (this.height*0.8), // start set the line in 80% of height
- x2: this.width, // end
- y2: (this.height*0.8), // end set the line in 80% of height
- 'stroke': "#666",
- 'stroke-width': "1",
- 'stroke-dasharray': "3",
- 'shape-rendering': "crispEdges",
- 'pointer-events': "none"
- },
- {
- tag: "path",
- id: this.svgID + "-svg-p",
- 'stroke': "navy",
- 'stroke-width': "3",
- 'fill': "none",
- 'pointer-events': 'none'
- }
- ]
- });
- this.createSVG();
- this.svgBox = this.svgEl.dom.getScreenCTM();
+ /**
+ * Opens a new {@link Roo.form.FieldSet} container in the layout stack. If fields are passed after the config, the
+ * fields are added and the fieldset is closed. If no fields are passed the fieldset remains open
+ * until end() is called.
+ * @param {Object} config The config to pass to the fieldset
+ * @param {Field} field1 (optional)
+ * @param {Field} field2 (optional)
+ * @param {Field} etc (optional)
+ * @return FieldSet The fieldset container object
+ */
+ fieldset : function(c){
+ var fs = new Roo.form.FieldSet(c);
+ this.start(fs);
+ if(arguments.length > 1){ // duplicate code required because of Opera
+ this.add.apply(this, Array.prototype.slice.call(arguments, 1));
+ this.end();
+ }
+ return fs;
},
- createSVG : function(){
- var svg = this.signPanel;
- var r = svg.select('#'+ this.svgID + '-svg-r', true).first().dom;
- var t = this;
- r.addEventListener('mousedown', function(e) { return t.down(e); }, false);
- r.addEventListener('mousemove', function(e) { return t.move(e); }, false);
- r.addEventListener('mouseup', function(e) { return t.up(e); }, false);
- r.addEventListener('mouseout', function(e) { return t.up(e); }, false);
- r.addEventListener('touchstart', function(e) { return t.down(e); }, false);
- r.addEventListener('touchmove', function(e) { return t.move(e); }, false);
- r.addEventListener('touchend', function(e) { return t.up(e); }, false);
-
- },
- isTouchEvent : function(e){
- return e.type.match(/^touch/);
- },
- getCoords : function (e) {
- var pt = this.svgEl.dom.createSVGPoint();
- pt.x = e.clientX;
- pt.y = e.clientY;
- if (this.isTouchEvent(e)) {
- pt.x = e.targetTouches[0].clientX;
- pt.y = e.targetTouches[0].clientY;
+ /**
+ * Opens a new {@link Roo.form.Layout} container in the layout stack. If fields are passed after the config, the
+ * fields are added and the container is closed. If no fields are passed the container remains open
+ * until end() is called.
+ * @param {Object} config The config to pass to the Layout
+ * @param {Field} field1 (optional)
+ * @param {Field} field2 (optional)
+ * @param {Field} etc (optional)
+ * @return Layout The container object
+ */
+ container : function(c){
+ var l = new Roo.form.Layout(c);
+ this.start(l);
+ if(arguments.length > 1){ // duplicate code required because of Opera
+ this.add.apply(this, Array.prototype.slice.call(arguments, 1));
+ this.end();
}
- var a = this.svgEl.dom.getScreenCTM();
- var b = a.inverse();
- var mx = pt.matrixTransform(b);
- return mx.x + ',' + mx.y;
+ return l;
},
- //mouse event headler
- down : function (e) {
- this.signatureTmp += 'M' + this.getCoords(e) + ' ';
- this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr('d', this.signatureTmp);
-
- this.isMouseDown = true;
-
- e.preventDefault();
+
+ /**
+ * Opens the passed container in the layout stack. The container can be any {@link Roo.form.Layout} or subclass.
+ * @param {Object} container A Roo.form.Layout or subclass of Layout
+ * @return {Form} this
+ */
+ start : function(c){
+ // cascade label info
+ Roo.applyIf(c, {'labelAlign': this.active.labelAlign, 'labelWidth': this.active.labelWidth, 'itemCls': this.active.itemCls});
+ this.active.stack.push(c);
+ c.ownerCt = this.active;
+ this.active = c;
+ return this;
},
- move : function (e) {
- if (this.isMouseDown) {
- this.signatureTmp += 'L' + this.getCoords(e) + ' ';
- this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', this.signatureTmp);
+
+ /**
+ * Closes the current open container
+ * @return {Form} this
+ */
+ end : function(){
+ if(this.active == this.root){
+ return this;
}
-
- e.preventDefault();
+ this.active = this.active.ownerCt;
+ return this;
},
- up : function (e) {
- this.isMouseDown = false;
- var sp = this.signatureTmp.split(' ');
-
- if(sp.length > 1){
- if(!sp[sp.length-2].match(/^L/)){
- sp.pop();
- sp.pop();
- sp.push("");
- this.signatureTmp = sp.join(" ");
+
+ /**
+ * Add Roo.form components to the current open container (e.g. column, fieldset, etc.). Fields added via this method
+ * can also be passed with an additional property of fieldLabel, which if supplied, will provide the text to display
+ * as the label of the field.
+ * @param {Field} field1
+ * @param {Field} field2 (optional)
+ * @param {Field} etc. (optional)
+ * @return {Form} this
+ */
+ add : function(){
+ this.active.stack.push.apply(this.active.stack, arguments);
+ this.allItems.push.apply(this.allItems,arguments);
+ var r = [];
+ for(var i = 0, a = arguments, len = a.length; i < len; i++) {
+ if(a[i].isFormField){
+ r.push(a[i]);
}
}
- if(this.getValue() != this.signatureTmp){
- this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
- this.isConfirmed = false;
+ if(r.length > 0){
+ Roo.form.Form.superclass.add.apply(this, r);
}
- e.preventDefault();
+ return this;
},
- /**
- * Protected method that will not generally be called directly. It
- * is called when the editor creates its toolbar. Override this method if you need to
- * add custom toolbar buttons.
- * @param {HtmlEditor} editor
+
+
+
+
+ /**
+ * Find any element that has been added to a form, using it's ID or name
+ * This can include framesets, columns etc. along with regular fields..
+ * @param {String} id - id or name to find.
+
+ * @return {Element} e - or false if nothing found.
*/
- createToolbar : function(editor){
- function btn(id, toggle, handler){
- var xid = fid + '-'+ id ;
- return {
- id : xid,
- cmd : id,
- cls : 'x-btn-icon x-edit-'+id,
- enableToggle:toggle !== false,
- scope: editor, // was editor...
- handler:handler||editor.relayBtnCmd,
- clickEvent:'mousedown',
- tooltip: etb.buttonTips[id] || undefined, ///tips ???
- tabIndex:-1
- };
+ findbyId : function(id)
+ {
+ var ret = false;
+ if (!id) {
+ return ret;
}
+ Roo.each(this.allItems, function(f){
+ if (f.id == id || f.name == id ){
+ ret = f;
+ return false;
+ }
+ });
+ return ret;
+ },
+
+
+
+ /**
+ * Render this form into the passed container. This should only be called once!
+ * @param {String/HTMLElement/Element} container The element this component should be rendered into
+ * @return {Form} this
+ */
+ render : function(ct)
+ {
- var tb = new Roo.Toolbar(editor.wrap.dom.firstChild);
- this.tb = tb;
- this.tb.add(
- {
- cls : ' x-signature-btn x-signature-'+id,
- scope: editor, // was editor...
- handler: this.reset,
- clickEvent:'mousedown',
- text: this.labels.clear
- },
- {
- xtype : 'Fill',
- xns: Roo.Toolbar
- },
- {
- cls : ' x-signature-btn x-signature-'+id,
- scope: editor, // was editor...
- handler: this.confirmHandler,
- clickEvent:'mousedown',
- text: this.labels.confirm
+
+ ct = Roo.get(ct);
+ var o = this.autoCreate || {
+ tag: 'form',
+ method : this.method || 'POST',
+ id : this.id || Roo.id()
+ };
+ this.initEl(ct.createChild(o));
+
+ this.root.render(this.el);
+
+
+
+ this.items.each(function(f){
+ f.render('x-form-el-'+f.id);
+ });
+
+ if(this.buttons.length > 0){
+ // tables are required to maintain order and for correct IE layout
+ var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
+ cls:"x-form-btns x-form-btns-"+this.buttonAlign,
+ html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
+ }}, null, true);
+ var tr = tb.getElementsByTagName('tr')[0];
+ for(var i = 0, len = this.buttons.length; i < len; i++) {
+ var b = this.buttons[i];
+ var td = document.createElement('td');
+ td.className = 'x-form-btn-td';
+ b.render(tr.appendChild(td));
}
- );
-
+ }
+ if(this.monitorValid){ // initialize after render
+ this.startMonitoring();
+ }
+ this.fireEvent('rendered', this);
+ return this;
},
- //public
+
/**
- * when user is clicked confirm then show this image.....
- *
- * @return {String} Image Data URI
+ * Adds a button to the footer of the form - this <b>must</b> be called before the form is rendered.
+ * @param {String/Object} config A string becomes the button text, an object can either be a Button config
+ * object or a valid Roo.DomHelper element config
+ * @param {Function} handler The function called when the button is clicked
+ * @param {Object} scope (optional) The scope of the handler function
+ * @return {Roo.Button}
*/
- getImageDataURI : function(){
- var svg = this.svgEl.dom.parentNode.innerHTML;
- var src = 'data:image/svg+xml;base64,'+window.btoa(svg);
- return src;
+ addButton : function(config, handler, scope){
+ var bc = {
+ handler: handler,
+ scope: scope,
+ minWidth: this.minButtonWidth,
+ hideParent:true
+ };
+ if(typeof config == "string"){
+ bc.text = config;
+ }else{
+ Roo.apply(bc, config);
+ }
+ var btn = new Roo.Button(null, bc);
+ this.buttons.push(btn);
+ return btn;
},
- /**
- *
- * @return {Boolean} this.isConfirmed
+
+ /**
+ * Adds a series of form elements (using the xtype property as the factory method.
+ * Valid xtypes are: TextField, TextArea .... Button, Layout, FieldSet, Column, (and 'end' to close a block)
+ * @param {Object} config
*/
- getConfirmed : function(){
- return this.isConfirmed;
+
+ addxtype : function()
+ {
+ var ar = Array.prototype.slice.call(arguments, 0);
+ var ret = false;
+ for(var i = 0; i < ar.length; i++) {
+ if (!ar[i]) {
+ continue; // skip -- if this happends something invalid got sent, we
+ // should ignore it, as basically that interface element will not show up
+ // and that should be pretty obvious!!
+ }
+
+ if (Roo.form[ar[i].xtype]) {
+ ar[i].form = this;
+ var fe = Roo.factory(ar[i], Roo.form);
+ if (!ret) {
+ ret = fe;
+ }
+ fe.form = this;
+ if (fe.store) {
+ fe.store.form = this;
+ }
+ if (fe.isLayout) {
+
+ this.start(fe);
+ this.allItems.push(fe);
+ if (fe.items && fe.addxtype) {
+ fe.addxtype.apply(fe, fe.items);
+ delete fe.items;
+ }
+ this.end();
+ continue;
+ }
+
+
+
+ this.add(fe);
+ // console.log('adding ' + ar[i].xtype);
+ }
+ if (ar[i].xtype == 'Button') {
+ //console.log('adding button');
+ //console.log(ar[i]);
+ this.addButton(ar[i]);
+ this.allItems.push(fe);
+ continue;
+ }
+
+ if (ar[i].xtype == 'end') { // so we can add fieldsets... / layout etc.
+ alert('end is not supported on xtype any more, use items');
+ // this.end();
+ // //console.log('adding end');
+ }
+
+ }
+ return ret;
},
+
/**
- *
- * @return {Number} this.width
+ * Starts monitoring of the valid state of this form. Usually this is done by passing the config
+ * option "monitorValid"
*/
- getWidth : function(){
- return this.width;
+ startMonitoring : function(){
+ if(!this.bound){
+ this.bound = true;
+ Roo.TaskMgr.start({
+ run : this.bindHandler,
+ interval : this.monitorPoll || 200,
+ scope: this
+ });
+ }
},
+
/**
- *
- * @return {Number} this.height
+ * Stops monitoring of the valid state of this form
*/
- getHeight : function(){
- return this.height;
- },
- // private
- getSignature : function(){
- return this.signatureTmp;
- },
- // private
- reset : function(){
- this.signatureTmp = '';
- this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
- this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', '');
- this.isConfirmed = false;
- Roo.form.Signature.superclass.reset.call(this);
- },
- setSignature : function(s){
- this.signatureTmp = s;
- this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
- this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', s);
- this.setValue(s);
- this.isConfirmed = false;
- Roo.form.Signature.superclass.reset.call(this);
- },
- test : function(){
-// Roo.log(this.signPanel.dom.contentWindow.up())
- },
- //private
- setConfirmed : function(){
-
-
-
-// Roo.log(Roo.get(this.signPanel.dom.contentWindow.r).attr('fill', '#cfc'));
- },
- // private
- confirmHandler : function(){
- if(!this.getSignature()){
- return;
- }
-
- this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#cfc');
- this.setValue(this.getSignature());
- this.isConfirmed = true;
-
- this.fireEvent('confirm', this);
+ stopMonitoring : function(){
+ this.bound = false;
},
+
// private
- // Subclasses should provide the validation implementation by overriding this
- validateValue : function(value){
- if(this.allowBlank){
- return true;
+ bindHandler : function(){
+ if(!this.bound){
+ return false; // stops binding
}
-
- if(this.isConfirmed){
- return true;
+ var valid = true;
+ this.items.each(function(f){
+ if(!f.isValid(true)){
+ valid = false;
+ return false;
+ }
+ });
+ for(var i = 0, len = this.buttons.length; i < len; i++){
+ var btn = this.buttons[i];
+ if(btn.formBind === true && btn.disabled === valid){
+ btn.setDisabled(!valid);
+ }
}
- return false;
+ this.fireEvent('clientvalidation', this, valid);
}
-});/*
+
+
+
+
+
+
+
+
+});
+
+
+// back compat
+Roo.Form = Roo.form.Form;
+/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
-
-/**
- * @class Roo.form.ComboBox
- * @extends Roo.form.TriggerField
- * A combobox control with support for autocomplete, remote-loading, paging and many other features.
+// as we use this in bootstrap.
+Roo.namespace('Roo.form');
+ /**
+ * @class Roo.form.Action
+ * Internal Class used to handle form actions
* @constructor
- * Create a new ComboBox.
+ * @param {Roo.form.BasicForm} el The form element or its id
* @param {Object} config Configuration options
*/
-Roo.form.Select = function(config){
- Roo.form.Select.superclass.constructor.call(this, config);
-
+
+
+
+// define the action interface
+Roo.form.Action = function(form, options){
+ this.form = form;
+ this.options = options || {};
};
+/**
+ * Client Validation Failed
+ * @const
+ */
+Roo.form.Action.CLIENT_INVALID = 'client';
+/**
+ * Server Validation Failed
+ * @const
+ */
+Roo.form.Action.SERVER_INVALID = 'server';
+ /**
+ * Connect to Server Failed
+ * @const
+ */
+Roo.form.Action.CONNECT_FAILURE = 'connect';
+/**
+ * Reading Data from Server Failed
+ * @const
+ */
+Roo.form.Action.LOAD_FAILURE = 'load';
-Roo.extend(Roo.form.Select , Roo.form.ComboBox, {
- /**
- * @cfg {String/HTMLElement/Element} transform The id, DOM node or element of an existing select to convert to a ComboBox
- */
- /**
- * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
- * rendering into an Roo.Editor, defaults to false)
- */
- /**
- * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
- * {tag: "input", type: "text", size: "24", autocomplete: "off"})
- */
- /**
- * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
- */
- /**
- * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
- * the dropdown list (defaults to undefined, with no header element)
- */
+Roo.form.Action.prototype = {
+ type : 'default',
+ failureType : undefined,
+ response : undefined,
+ result : undefined,
- /**
- * @cfg {String/Roo.Template} tpl The template to use to render the output
- */
-
- // private
- defaultAutoCreate : {tag: "select" },
- /**
- * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
- */
- listWidth: undefined,
- /**
- * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
- * mode = 'remote' or 'text' if mode = 'local')
- */
- displayField: undefined,
- /**
- * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
- * mode = 'remote' or 'value' if mode = 'local').
- * Note: use of a valueField requires the user make a selection
- * in order for a value to be mapped.
- */
- valueField: undefined,
-
-
- /**
- * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
- * field's data value (defaults to the underlying DOM element's name)
- */
- hiddenName: undefined,
- /**
- * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
- */
- listClass: '',
- /**
- * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
- */
- selectedClass: 'x-combo-selected',
- /**
- * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always get the
- * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified (defaults to 'x-form-arrow-trigger'
- * which displays a downward arrow icon).
- */
- triggerClass : 'x-form-arrow-trigger',
- /**
- * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
- */
- shadow:'sides',
- /**
- * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
- * anchor positions (defaults to 'tl-bl')
- */
- listAlign: 'tl-bl?',
- /**
- * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
- */
- maxHeight: 300,
- /**
- * @cfg {String} triggerAction The action to execute when the trigger field is activated. Use 'all' to run the
- * query specified by the allQuery config option (defaults to 'query')
- */
- triggerAction: 'query',
- /**
- * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
- * (defaults to 4, does not apply if editable = false)
- */
- minChars : 4,
- /**
- * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
- * delay (typeAheadDelay) if it matches a known value (defaults to false)
- */
- typeAhead: false,
- /**
- * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
- * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
- */
- queryDelay: 500,
- /**
- * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
- * filter queries will execute with page start and limit parameters. Only applies when mode = 'remote' (defaults to 0)
- */
- pageSize: 0,
- /**
- * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus. Only applies
- * when editable = true (defaults to false)
- */
- selectOnFocus:false,
- /**
- * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
- */
- queryParam: 'query',
- /**
- * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
- * when mode = 'remote' (defaults to 'Loading...')
- */
- loadingText: 'Loading...',
- /**
- * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
- */
- resizable: false,
- /**
- * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
- */
- handleHeight : 8,
- /**
- * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
- * traditional select (defaults to true)
- */
- editable: true,
- /**
- * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
- */
- allQuery: '',
- /**
- * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
- */
- mode: 'remote',
- /**
- * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
- * listWidth has a higher value)
- */
- minListWidth : 70,
- /**
- * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
- * allow the user to set arbitrary text into the field (defaults to false)
- */
- forceSelection:false,
- /**
- * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
- * if typeAhead = true (defaults to 250)
- */
- typeAheadDelay : 250,
- /**
- * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
- * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
- */
- valueNotFoundText : undefined,
-
- /**
- * @cfg {String} defaultValue The value displayed after loading the store.
- */
- defaultValue: '',
-
- /**
- * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
- */
- blockFocus : false,
-
- /**
- * @cfg {Boolean} disableClear Disable showing of clear button.
- */
- disableClear : false,
- /**
- * @cfg {Boolean} alwaysQuery Disable caching of results, and always send query
- */
- alwaysQuery : false,
-
- //private
- addicon : false,
- editicon: false,
-
- // element that contains real text value.. (when hidden is used..)
-
- // private
- onRender : function(ct, position){
- Roo.form.Field.prototype.onRender.call(this, ct, position);
-
- if(this.store){
- this.store.on('beforeload', this.onBeforeLoad, this);
- this.store.on('load', this.onLoad, this);
- this.store.on('loadexception', this.onLoadException, this);
- this.store.load({});
- }
-
-
-
- },
+ // interface method
+ run : function(options){
- // private
- initEvents : function(){
- //Roo.form.ComboBox.superclass.initEvents.call(this);
-
},
- onDestroy : function(){
-
- if(this.store){
- this.store.un('beforeload', this.onBeforeLoad, this);
- this.store.un('load', this.onLoad, this);
- this.store.un('loadexception', this.onLoadException, this);
- }
- //Roo.form.ComboBox.superclass.onDestroy.call(this);
+ // interface method
+ success : function(response){
+
},
- // private
- fireKey : function(e){
- if(e.isNavKeyPress() && !this.list.isVisible()){
- this.fireEvent("specialkey", this, e);
- }
+ // interface method
+ handleResponse : function(response){
+
},
- // private
- onResize: function(w, h){
-
- return;
-
+ // default connection failure
+ failure : function(response){
+ this.response = response;
+ this.failureType = Roo.form.Action.CONNECT_FAILURE;
+ this.form.afterAction(this, false);
},
- /**
- * Allow or prevent the user from directly editing the field text. If false is passed,
- * the user will only be able to select from the items defined in the dropdown list. This method
- * is the runtime equivalent of setting the 'editable' config option at config time.
- * @param {Boolean} value True to allow the user to directly edit the field text
- */
- setEditable : function(value){
-
+ processResponse : function(response){
+ this.response = response;
+ if(!response.responseText){
+ return true;
+ }
+ this.result = this.handleResponse(response);
+ return this.result;
},
- // private
- onBeforeLoad : function(){
-
- Roo.log("Select before load");
- return;
-
- this.innerList.update(this.loadingText ?
- '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
- //this.restrictHeight();
- this.selectedIndex = -1;
+ // utility functions used internally
+ getUrl : function(appendParams){
+ var url = this.options.url || this.form.url || this.form.el.dom.action;
+ if(appendParams){
+ var p = this.getParams();
+ if(p){
+ url += (url.indexOf('?') != -1 ? '&' : '?') + p;
+ }
+ }
+ return url;
},
- // private
- onLoad : function(){
+ getMethod : function(){
+ return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
+ },
-
- var dom = this.el.dom;
- dom.innerHTML = '';
- var od = dom.ownerDocument;
-
- if (this.emptyText) {
- var op = od.createElement('option');
- op.setAttribute('value', '');
- op.innerHTML = String.format('{0}', this.emptyText);
- dom.appendChild(op);
- }
- if(this.store.getCount() > 0){
-
- var vf = this.valueField;
- var df = this.displayField;
- this.store.data.each(function(r) {
- // which colmsn to use... testing - cdoe / title..
- var op = od.createElement('option');
- op.setAttribute('value', r.data[vf]);
- op.innerHTML = String.format('{0}', r.data[df]);
- dom.appendChild(op);
- });
- if (typeof(this.defaultValue != 'undefined')) {
- this.setValue(this.defaultValue);
+ getParams : function(){
+ var bp = this.form.baseParams;
+ var p = this.options.params;
+ if(p){
+ if(typeof p == "object"){
+ p = Roo.urlEncode(Roo.applyIf(p, bp));
+ }else if(typeof p == 'string' && bp){
+ p += '&' + Roo.urlEncode(bp);
}
-
-
- }else{
- //this.onEmptyResults();
+ }else if(bp){
+ p = Roo.urlEncode(bp);
}
- //this.el.focus();
+ return p;
},
- // private
- onLoadException : function()
- {
- dom.innerHTML = '';
-
- Roo.log("Select on load exception");
- return;
+
+ createCallback : function(){
+ return {
+ success: this.success,
+ failure: this.failure,
+ scope: this,
+ timeout: (this.form.timeout*1000),
+ upload: this.form.fileUpload ? this.success : undefined
+ };
+ }
+};
+
+Roo.form.Action.Submit = function(form, options){
+ Roo.form.Action.Submit.superclass.constructor.call(this, form, options);
+};
+
+Roo.extend(Roo.form.Action.Submit, Roo.form.Action, {
+ type : 'submit',
+
+ haveProgress : false,
+ uploadComplete : false,
- 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);
+ // uploadProgress indicator.
+ uploadProgress : function()
+ {
+ if (!this.form.progressUrl) {
+ return;
}
+ if (!this.haveProgress) {
+ Roo.MessageBox.progress("Uploading", "Uploading");
+ }
+ if (this.uploadComplete) {
+ Roo.MessageBox.hide();
+ return;
+ }
+ this.haveProgress = true;
+
+ var uid = this.form.findField('UPLOAD_IDENTIFIER').getValue();
+
+ var c = new Roo.data.Connection();
+ c.request({
+ url : this.form.progressUrl,
+ params: {
+ id : uid
+ },
+ method: 'GET',
+ success : function(req){
+ //console.log(data);
+ var rdata = false;
+ var edata;
+ try {
+ rdata = Roo.decode(req.responseText)
+ } catch (e) {
+ Roo.log("Invalid data from server..");
+ Roo.log(edata);
+ return;
+ }
+ if (!rdata || !rdata.success) {
+ Roo.log(rdata);
+ Roo.MessageBox.alert(Roo.encode(rdata));
+ return;
+ }
+ var data = rdata.data;
+
+ if (this.uploadComplete) {
+ Roo.MessageBox.hide();
+ return;
+ }
+
+ if (data){
+ Roo.MessageBox.updateProgress(data.bytes_uploaded/data.bytes_total,
+ Math.floor((data.bytes_total - data.bytes_uploaded)/1000) + 'k remaining'
+ );
+ }
+ this.uploadProgress.defer(2000,this);
+ },
+
+ failure: function(data) {
+ Roo.log('progress url failed ');
+ Roo.log(data);
+ },
+ scope : this
+ });
+
},
- // private
- onTypeAhead : function(){
-
- },
+
+
+ run : function()
+ {
+ // run get Values on the form, so it syncs any secondary forms.
+ this.form.getValues();
+
+ var o = this.options;
+ var method = this.getMethod();
+ var isPost = method == 'POST';
+ if(o.clientValidation === false || this.form.isValid()){
+
+ if (this.form.progressUrl) {
+ this.form.findField('UPLOAD_IDENTIFIER').setValue(
+ (new Date() * 1) + '' + Math.random());
+
+ }
+
+
+ Roo.Ajax.request(Roo.apply(this.createCallback(), {
+ form:this.form.el.dom,
+ url:this.getUrl(!isPost),
+ method: method,
+ params:isPost ? this.getParams() : null,
+ isUpload: this.form.fileUpload,
+ formData : this.form.formData
+ }));
+
+ this.uploadProgress();
- // private
- onSelect : function(record, index){
- Roo.log('on select?');
- return;
- if(this.fireEvent('beforeselect', this, record, index) !== false){
- this.setFromData(index > -1 ? record.data : false);
- this.collapse();
- this.fireEvent('select', this, record, index);
+ }else if (o.clientValidation !== false){ // client validation failed
+ this.failureType = Roo.form.Action.CLIENT_INVALID;
+ this.form.afterAction(this, false);
}
},
- /**
- * Returns the currently selected field value or empty string if no value is set.
- * @return {String} value The selected value
- */
- getValue : function(){
- var dom = this.el.dom;
- this.value = dom.options[dom.selectedIndex].value;
- return this.value;
+ success : function(response)
+ {
+ this.uploadComplete= true;
+ if (this.haveProgress) {
+ Roo.MessageBox.hide();
+ }
- },
-
- /**
- * Clears any text/value currently set in the field
- */
- clearValue : function(){
- this.value = '';
- this.el.dom.selectedIndex = this.emptyText ? 0 : -1;
- },
-
- /**
- * Sets the specified value into the field. If the value finds a match, the corresponding record text
- * will be displayed in the field. If the value does not match the data value of an existing item,
- * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
- * Otherwise the field will be blank (although the value will still be set).
- * @param {String} value The value to match
- */
- setValue : function(v){
- var d = this.el.dom;
- for (var i =0; i < d.options.length;i++) {
- if (v == d.options[i].value) {
- d.selectedIndex = i;
- this.value = v;
- return;
- }
+ var result = this.processResponse(response);
+ if(result === true || result.success){
+ this.form.afterAction(this, true);
+ return;
}
- this.clearValue();
+ if(result.errors){
+ this.form.markInvalid(result.errors);
+ this.failureType = Roo.form.Action.SERVER_INVALID;
+ }
+ this.form.afterAction(this, false);
},
- /**
- * @property {Object} the last set data for the element
- */
-
- lastData : false,
- /**
- * Sets the value of the field based on a object which is related to the record format for the store.
- * @param {Object} value the value to set as. or false on reset?
- */
- setFromData : function(o){
- Roo.log('setfrom data?');
-
-
+ failure : function(response)
+ {
+ this.uploadComplete= true;
+ if (this.haveProgress) {
+ Roo.MessageBox.hide();
+ }
+ this.response = response;
+ this.failureType = Roo.form.Action.CONNECT_FAILURE;
+ this.form.afterAction(this, false);
},
- // private
- reset : function(){
- this.clearValue();
- },
- // private
- findRecord : function(prop, value){
-
- return false;
- var record;
- if(this.store.getCount() > 0){
- this.store.each(function(r){
- if(r.data[prop] == value){
- record = r;
- return false;
+ handleResponse : function(response){
+ if(this.form.errorReader){
+ var rs = this.form.errorReader.read(response);
+ var errors = [];
+ if(rs.records){
+ for(var i = 0, len = rs.records.length; i < len; i++) {
+ var r = rs.records[i];
+ errors[i] = r.data;
}
- return true;
- });
+ }
+ if(errors.length < 1){
+ errors = null;
+ }
+ return {
+ success : rs.success,
+ errors : errors
+ };
}
- return record;
- },
-
- getName: function()
- {
- // returns hidden if it's set..
- if (!this.rendered) {return ''};
- return !this.hiddenName && this.el.dom.name ? this.el.dom.name : (this.hiddenName || '');
+ var ret = false;
+ try {
+ ret = Roo.decode(response.responseText);
+ } catch (e) {
+ ret = {
+ success: false,
+ errorMsg: "Failed to read server message: " + (response ? response.responseText : ' - no message'),
+ errors : []
+ };
+ }
+ return ret;
- },
-
+ }
+});
-
- // private
- onEmptyResults : function(){
- Roo.log('empty results');
- //this.collapse();
- },
+Roo.form.Action.Load = function(form, options){
+ Roo.form.Action.Load.superclass.constructor.call(this, form, options);
+ this.reader = this.form.reader;
+};
- /**
- * Returns true if the dropdown list is expanded, else false.
- */
- isExpanded : function(){
- return false;
- },
+Roo.extend(Roo.form.Action.Load, Roo.form.Action, {
+ type : 'load',
- /**
- * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
- * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
- * @param {String} value The data value of the item to select
- * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
- * selected item if it is not currently in view (defaults to true)
- * @return {Boolean} True if the value matched an item in the list, else false
- */
- selectByValue : function(v, scrollIntoView){
- Roo.log('select By Value');
- return false;
-
- if(v !== undefined && v !== null){
- var r = this.findRecord(this.valueField || this.displayField, v);
- if(r){
- this.select(this.store.indexOf(r), scrollIntoView);
- return true;
- }
- }
- return false;
+ run : function(){
+
+ Roo.Ajax.request(Roo.apply(
+ this.createCallback(), {
+ method:this.getMethod(),
+ url:this.getUrl(false),
+ params:this.getParams()
+ }));
},
- /**
- * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
- * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
- * @param {Number} index The zero-based index of the list item to select
- * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
- * selected item if it is not currently in view (defaults to true)
- */
- select : function(index, scrollIntoView){
- Roo.log('select ');
- return ;
+ success : function(response){
- this.selectedIndex = index;
- this.view.select(index);
- if(scrollIntoView !== false){
- var el = this.view.getNode(index);
- if(el){
- this.innerList.scrollChildIntoView(el, false);
- }
+ var result = this.processResponse(response);
+ if(result === true || !result.success || !result.data){
+ this.failureType = Roo.form.Action.LOAD_FAILURE;
+ this.form.afterAction(this, false);
+ return;
}
+ this.form.clearInvalid();
+ this.form.setValues(result.data);
+ this.form.afterAction(this, true);
},
-
+ handleResponse : function(response){
+ if(this.form.reader){
+ var rs = this.form.reader.read(response);
+ var data = rs.records && rs.records[0] ? rs.records[0].data : null;
+ return {
+ success : rs.success,
+ data : data
+ };
+ }
+ return Roo.decode(response.responseText);
+ }
+});
- // private
- validateBlur : function(){
-
- return;
-
- },
+Roo.form.Action.ACTION_TYPES = {
+ 'load' : Roo.form.Action.Load,
+ 'submit' : Roo.form.Action.Submit
+};/*
+ * 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">
+ */
+
+/**
+ * @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
+ */
+Roo.form.Layout = function(config){
+ var xitems = [];
+ if (config.items) {
+ xitems = config.items;
+ delete config.items;
+ }
+ Roo.form.Layout.superclass.constructor.call(this, config);
+ this.stack = [];
+ Roo.each(xitems, this.addxtype, this);
+
+};
- // private
- initQuery : function(){
- this.doQuery(this.getRawValue());
- },
+Roo.extend(Roo.form.Layout, Roo.Component, {
+ /**
+ * @cfg {String/Object} autoCreate
+ * A DomHelper element spec used to autocreate the layout (defaults to {tag: 'div', cls: 'x-form-ct'})
+ */
+ /**
+ * @cfg {String/Object/Function} style
+ * A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
+ * a function which returns such a specification.
+ */
+ /**
+ * @cfg {String} labelAlign (left|top|right)
+ * Valid values are "left," "top" and "right" (defaults to "left")
+ */
+ /**
+ * @cfg {Number} labelWidth
+ * Fixed width in pixels of all field labels (defaults to undefined)
+ */
+ /**
+ * @cfg {Boolean} clear
+ * True to add a clearing element at the end of this layout, equivalent to CSS clear: both (defaults to true)
+ */
+ clear : true,
+ /**
+ * @cfg {String} labelSeparator
+ * The separator to use after field labels (defaults to ':')
+ */
+ labelSeparator : ':',
+ /**
+ * @cfg {Boolean} hideLabels
+ * True to suppress the display of field labels in this layout (defaults to false)
+ */
+ hideLabels : false,
// private
- doForce : function(){
- if(this.el.dom.value.length > 0){
- this.el.dom.value =
- this.lastSelectionText === undefined ? '' : this.lastSelectionText;
-
+ defaultAutoCreate : {tag: 'div', cls: 'x-form-ct'},
+
+ isLayout : true,
+
+ // private
+ onRender : function(ct, position){
+ if(this.el){ // from markup
+ this.el = Roo.get(this.el);
+ }else { // generate
+ var cfg = this.getAutoCreate();
+ this.el = ct.createChild(cfg, position);
}
- },
-
- /**
- * Execute a query to filter the dropdown list. Fires the beforequery event prior to performing the
- * query allowing the query action to be canceled if needed.
- * @param {String} query The SQL query to execute
- * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
- * in the field than the minimum specified by the minChars config option. It also clears any filter previously
- * saved in the current store (defaults to false)
- */
- doQuery : function(q, forceAll){
-
- Roo.log('doQuery?');
- if(q === undefined || q === null){
- q = '';
+ if(this.style){
+ this.el.applyStyles(this.style);
}
- var qe = {
- query: q,
- forceAll: forceAll,
- combo: this,
- cancel:false
- };
- if(this.fireEvent('beforequery', qe)===false || qe.cancel){
- return false;
+ if(this.labelAlign){
+ this.el.addClass('x-form-label-'+this.labelAlign);
}
- q = qe.query;
- forceAll = qe.forceAll;
- if(forceAll === true || (q.length >= this.minChars)){
- if(this.lastQuery != q || this.alwaysQuery){
- this.lastQuery = q;
- if(this.mode == 'local'){
- this.selectedIndex = -1;
- if(forceAll){
- this.store.clearFilter();
- }else{
- this.store.filter(this.displayField, q);
- }
- this.onLoad();
+ if(this.hideLabels){
+ this.labelStyle = "display:none";
+ this.elementStyle = "padding-left:0;";
+ }else{
+ if(typeof this.labelWidth == 'number'){
+ this.labelStyle = "width:"+this.labelWidth+"px;";
+ this.elementStyle = "padding-left:"+((this.labelWidth+(typeof this.labelPad == 'number' ? this.labelPad : 5))+'px')+";";
+ }
+ if(this.labelAlign == 'top'){
+ this.labelStyle = "width:auto;";
+ this.elementStyle = "padding-left:0;";
+ }
+ }
+ var stack = this.stack;
+ var slen = stack.length;
+ if(slen > 0){
+ if(!this.fieldTpl){
+ var t = new Roo.Template(
+ '<div class="x-form-item {5}">',
+ '<label for="{0}" style="{2}">{1}{4}</label>',
+ '<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
+ '</div>',
+ '</div><div class="x-form-clear-left"></div>'
+ );
+ t.disableFormats = true;
+ t.compile();
+ Roo.form.Layout.prototype.fieldTpl = t;
+ }
+ for(var i = 0; i < slen; i++) {
+ if(stack[i].isFormField){
+ this.renderField(stack[i]);
}else{
- this.store.baseParams[this.queryParam] = q;
- this.store.load({
- params: this.getParams(q)
- });
- this.expand();
+ this.renderComponent(stack[i]);
}
- }else{
- this.selectedIndex = -1;
- this.onLoad();
}
}
+ if(this.clear){
+ this.el.createChild({cls:'x-form-clear'});
+ }
},
// private
- getParams : function(q){
- var p = {};
- //p[this.queryParam] = q;
- if(this.pageSize){
- p.start = 0;
- p.limit = this.pageSize;
- }
- return p;
+ renderField : function(f){
+ f.fieldEl = Roo.get(this.fieldTpl.append(this.el, [
+ f.id, //0
+ f.fieldLabel, //1
+ f.labelStyle||this.labelStyle||'', //2
+ this.elementStyle||'', //3
+ typeof f.labelSeparator == 'undefined' ? this.labelSeparator : f.labelSeparator, //4
+ f.itemCls||this.itemCls||'' //5
+ ], true).getPrevSibling());
},
+ // private
+ renderComponent : function(c){
+ c.render(c.isLayout ? this.el : this.el.createChild());
+ },
/**
- * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
+ * Adds a object form elements (using the xtype property as the factory method.)
+ * Valid xtypes are: TextField, TextArea .... Button, Layout, FieldSet, Column
+ * @param {Object} config
*/
- collapse : function(){
+ addxtype : function(o)
+ {
+ // create the lement.
+ o.form = this.form;
+ var fe = Roo.factory(o, Roo.form);
+ this.form.allItems.push(fe);
+ this.stack.push(fe);
- },
+ if (fe.isFormField) {
+ this.form.items.add(fe);
+ }
+
+ return fe;
+ }
+});
- // private
- collapseIf : function(e){
-
- },
+/**
+ * @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
+ */
+Roo.form.Column = function(config){
+ Roo.form.Column.superclass.constructor.call(this, config);
+};
+
+Roo.extend(Roo.form.Column, Roo.form.Layout, {
/**
- * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
+ * @cfg {Number/String} width
+ * The fixed width of the column in pixels or CSS value (defaults to "auto")
+ */
+ /**
+ * @cfg {String/Object} autoCreate
+ * A DomHelper element spec used to autocreate the column (defaults to {tag: 'div', cls: 'x-form-ct x-form-column'})
*/
- expand : function(){
-
- } ,
// private
-
+ defaultAutoCreate : {tag: 'div', cls: 'x-form-ct x-form-column'},
- /**
- * @cfg {Boolean} grow
- * @hide
- */
- /**
- * @cfg {Number} growMin
- * @hide
- */
- /**
- * @cfg {Number} growMax
- * @hide
- */
+ // private
+ onRender : function(ct, position){
+ Roo.form.Column.superclass.onRender.call(this, ct, position);
+ if(this.width){
+ this.el.setWidth(this.width);
+ }
+ }
+});
+
+/**
+ * @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
+ */
+
+
+Roo.form.Row = function(config){
+ Roo.form.Row.superclass.constructor.call(this, config);
+};
+
+Roo.extend(Roo.form.Row, Roo.form.Layout, {
+ /**
+ * @cfg {Number/String} width
+ * The fixed width of the column in pixels or CSS value (defaults to "auto")
+ */
/**
- * @hide
- * @method autoSize
+ * @cfg {Number/String} height
+ * The fixed height of the column in pixels or CSS value (defaults to "auto")
*/
+ defaultAutoCreate : {tag: 'div', cls: 'x-form-ct x-form-row'},
- setWidth : function()
- {
+ padWidth : 20,
+ // private
+ onRender : function(ct, position){
+ //console.log('row render');
+ if(!this.rowTpl){
+ var t = new Roo.Template(
+ '<div class="x-form-item {5}" style="float:left;width:{6}px">',
+ '<label for="{0}" style="{2}">{1}{4}</label>',
+ '<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
+ '</div>',
+ '</div>'
+ );
+ t.disableFormats = true;
+ t.compile();
+ Roo.form.Layout.prototype.rowTpl = t;
+ }
+ this.fieldTpl = this.rowTpl;
+
+ //console.log('lw' + this.labelWidth +', la:' + this.labelAlign);
+ var labelWidth = 100;
+
+ if ((this.labelAlign != 'top')) {
+ if (typeof this.labelWidth == 'number') {
+ labelWidth = this.labelWidth
+ }
+ this.padWidth = 20 + labelWidth;
+
+ }
+ Roo.form.Column.superclass.onRender.call(this, ct, position);
+ if(this.width){
+ this.el.setWidth(this.width);
+ }
+ if(this.height){
+ this.el.setHeight(this.height);
+ }
},
- getResizeEl : function(){
- return this.el;
+
+ // private
+ renderField : function(f){
+ f.fieldEl = this.fieldTpl.append(this.el, [
+ f.id, f.fieldLabel,
+ f.labelStyle||this.labelStyle||'',
+ this.elementStyle||'',
+ typeof f.labelSeparator == 'undefined' ? this.labelSeparator : f.labelSeparator,
+ f.itemCls||this.itemCls||'',
+ f.width ? f.width + this.padWidth : 160 + this.padWidth
+ ],true);
}
-});//<script type="text/javasscript">
+});
/**
- * @class Roo.DDView
- * A DnD enabled version of Roo.View.
- * @param {Element/String} container The Element in which to create the View.
- * @param {String} tpl The template string used to create the markup for each element of the View
- * @param {Object} config The configuration properties. These include all the config options of
- * {@link Roo.View} plus some specific to this class.<br>
- * <p>
- * Drag/drop is implemented by adding {@link Roo.data.Record}s to the target DDView. If copying is
- * not being performed, the original {@link Roo.data.Record} is removed from the source DDView.<br>
- * <p>
- * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
-.x-view-drag-insert-above {
- border-top:1px dotted #3366cc;
-}
-.x-view-drag-insert-below {
- border-bottom:1px dotted #3366cc;
-}
-</code></pre>
- *
+ * @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
*/
-
-Roo.DDView = function(container, tpl, config) {
- Roo.DDView.superclass.constructor.apply(this, arguments);
- this.getEl().setStyle("outline", "0px none");
- this.getEl().unselectable();
- if (this.dragGroup) {
- this.setDraggable(this.dragGroup.split(","));
- }
- if (this.dropGroup) {
- this.setDroppable(this.dropGroup.split(","));
- }
- if (this.deletable) {
- this.setDeletable();
- }
- this.isDirtyFlag = false;
- this.addEvents({
- "drop" : true
- });
+Roo.form.FieldSet = function(config){
+ Roo.form.FieldSet.superclass.constructor.call(this, config);
};
-Roo.extend(Roo.DDView, Roo.View, {
-/** @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */
-/** @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
-/** @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
-/** @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
-
- isFormField: true,
-
- reset: Roo.emptyFn,
-
- clearInvalid: Roo.form.Field.prototype.clearInvalid,
-
- validate: function() {
- return true;
- },
-
- destroy: function() {
- this.purgeListeners();
- this.getEl.removeAllListeners();
- this.getEl().remove();
- if (this.dragZone) {
- if (this.dragZone.destroy) {
- this.dragZone.destroy();
- }
- }
- if (this.dropZone) {
- if (this.dropZone.destroy) {
- this.dropZone.destroy();
- }
- }
- },
+Roo.extend(Roo.form.FieldSet, Roo.form.Layout, {
+ /**
+ * @cfg {String} legend
+ * The text to display as the legend for the FieldSet (defaults to '')
+ */
+ /**
+ * @cfg {String/Object} autoCreate
+ * A DomHelper element spec used to autocreate the fieldset (defaults to {tag: 'fieldset', cn: {tag:'legend'}})
+ */
-/** Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
- getName: function() {
- return this.name;
- },
+ // private
+ defaultAutoCreate : {tag: 'fieldset', cn: {tag:'legend'}},
-/** Loads the View from a JSON string representing the Records to put into the Store. */
- setValue: function(v) {
- if (!this.store) {
- throw "DDView.setValue(). DDView must be constructed with a valid Store";
- }
- var data = {};
- data[this.store.reader.meta.root] = v ? [].concat(v) : [];
- this.store.proxy = new Roo.data.MemoryProxy(data);
- this.store.load();
- },
+ // private
+ onRender : function(ct, position){
+ Roo.form.FieldSet.superclass.onRender.call(this, ct, position);
+ if(this.legend){
+ this.setLegend(this.legend);
+ }
+ },
-/** @return {String} a parenthesised list of the ids of the Records in the View. */
- getValue: function() {
- var result = '(';
- this.store.each(function(rec) {
- result += rec.id + ',';
- });
- return result.substr(0, result.length - 1) + ')';
- },
-
- getIds: function() {
- var i = 0, result = new Array(this.store.getCount());
- this.store.each(function(rec) {
- result[i++] = rec.id;
- });
- return result;
- },
-
- isDirty: function() {
- return this.isDirtyFlag;
- },
-
-/**
- * Part of the Roo.dd.DropZone interface. If no target node is found, the
- * whole Element becomes the target, and this causes the drop gesture to append.
- */
- getTargetFromEvent : function(e) {
- var target = e.getTarget();
- while ((target !== null) && (target.parentNode != this.el.dom)) {
- target = target.parentNode;
- }
- if (!target) {
- target = this.el.dom.lastChild || this.el.dom;
- }
- return target;
- },
-
-/**
- * Create the drag data which consists of an object which has the property "ddel" as
- * the drag proxy element.
- */
- getDragData : function(e) {
- var target = this.findItemFromChild(e.getTarget());
- if(target) {
- this.handleSelection(e);
- var selNodes = this.getSelectedNodes();
- var dragData = {
- source: this,
- copy: this.copy || (this.allowCopy && e.ctrlKey),
- nodes: selNodes,
- records: []
- };
- var selectedIndices = this.getSelectedIndexes();
- for (var i = 0; i < selectedIndices.length; i++) {
- dragData.records.push(this.store.getAt(selectedIndices[i]));
- }
- if (selNodes.length == 1) {
- dragData.ddel = target.cloneNode(true); // the div element
- } else {
- var div = document.createElement('div'); // create the multi element drag "ghost"
- div.className = 'multi-proxy';
- for (var i = 0, len = selNodes.length; i < len; i++) {
- div.appendChild(selNodes[i].cloneNode(true));
- }
- dragData.ddel = div;
- }
- //console.log(dragData)
- //console.log(dragData.ddel.innerHTML)
- return dragData;
- }
- //console.log('nodragData')
- return false;
- },
-
-/** Specify to which ddGroup items in this DDView may be dragged. */
- setDraggable: function(ddGroup) {
- if (ddGroup instanceof Array) {
- Roo.each(ddGroup, this.setDraggable, this);
- return;
- }
- if (this.dragZone) {
- this.dragZone.addToGroup(ddGroup);
- } else {
- this.dragZone = new Roo.dd.DragZone(this.getEl(), {
- containerScroll: true,
- ddGroup: ddGroup
-
- });
-// Draggability implies selection. DragZone's mousedown selects the element.
- if (!this.multiSelect) { this.singleSelect = true; }
-
-// Wire the DragZone's handlers up to methods in *this*
- this.dragZone.getDragData = this.getDragData.createDelegate(this);
- }
- },
-
-/** Specify from which ddGroup this DDView accepts drops. */
- setDroppable: function(ddGroup) {
- if (ddGroup instanceof Array) {
- Roo.each(ddGroup, this.setDroppable, this);
- return;
- }
- if (this.dropZone) {
- this.dropZone.addToGroup(ddGroup);
- } else {
- this.dropZone = new Roo.dd.DropZone(this.getEl(), {
- containerScroll: true,
- ddGroup: ddGroup
- });
-
-// Wire the DropZone's handlers up to methods in *this*
- this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
- this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
- this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
- this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
- this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
- }
- },
-
-/** Decide whether to drop above or below a View node. */
- getDropPoint : function(e, n, dd){
- if (n == this.el.dom) { return "above"; }
- var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
- var c = t + (b - t) / 2;
- var y = Roo.lib.Event.getPageY(e);
- if(y <= c) {
- return "above";
- }else{
- return "below";
- }
- },
-
- onNodeEnter : function(n, dd, e, data){
- return false;
- },
-
- onNodeOver : function(n, dd, e, data){
- var pt = this.getDropPoint(e, n, dd);
- // set the insert point style on the target node
- var dragElClass = this.dropNotAllowed;
- if (pt) {
- var targetElClass;
- if (pt == "above"){
- dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
- targetElClass = "x-view-drag-insert-above";
- } else {
- dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
- targetElClass = "x-view-drag-insert-below";
- }
- if (this.lastInsertClass != targetElClass){
- Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
- this.lastInsertClass = targetElClass;
- }
- }
- return dragElClass;
- },
-
- onNodeOut : function(n, dd, e, data){
- this.removeDropIndicators(n);
- },
-
- onNodeDrop : function(n, dd, e, data){
- if (this.fireEvent("drop", this, n, dd, e, data) === false) {
- return false;
- }
- var pt = this.getDropPoint(e, n, dd);
- var insertAt = (n == this.el.dom) ? this.nodes.length : n.nodeIndex;
- if (pt == "below") { insertAt++; }
- for (var i = 0; i < data.records.length; i++) {
- var r = data.records[i];
- var dup = this.store.getById(r.id);
- if (dup && (dd != this.dragZone)) {
- Roo.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
- } else {
- if (data.copy) {
- this.store.insert(insertAt++, r.copy());
- } else {
- data.source.isDirtyFlag = true;
- r.store.remove(r);
- this.store.insert(insertAt++, r);
- }
- this.isDirtyFlag = true;
- }
- }
- this.dragZone.cachedTarget = null;
- return true;
- },
-
- removeDropIndicators : function(n){
- if(n){
- Roo.fly(n).removeClass([
- "x-view-drag-insert-above",
- "x-view-drag-insert-below"]);
- this.lastInsertClass = "_noclass";
- }
- },
-
-/**
- * Utility method. Add a delete option to the DDView's context menu.
- * @param {String} imageUrl The URL of the "delete" icon image.
- */
- setDeletable: function(imageUrl) {
- if (!this.singleSelect && !this.multiSelect) {
- this.singleSelect = true;
- }
- var c = this.getContextMenu();
- this.contextMenu.on("itemclick", function(item) {
- switch (item.id) {
- case "delete":
- this.remove(this.getSelectedIndexes());
- break;
- }
- }, this);
- this.contextMenu.add({
- icon: imageUrl,
- id: "delete",
- text: 'Delete'
- });
- },
-
-/** Return the context menu for this DDView. */
- getContextMenu: function() {
- if (!this.contextMenu) {
-// Create the View's context menu
- this.contextMenu = new Roo.menu.Menu({
- id: this.id + "-contextmenu"
- });
- this.el.on("contextmenu", this.showContextMenu, this);
- }
- return this.contextMenu;
- },
-
- disableContextMenu: function() {
- if (this.contextMenu) {
- this.el.un("contextmenu", this.showContextMenu, this);
- }
- },
-
- showContextMenu: function(e, item) {
- item = this.findItemFromChild(e.getTarget());
- if (item) {
- e.stopEvent();
- this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
- this.contextMenu.showAt(e.getXY());
- }
- },
-
-/**
- * Remove {@link Roo.data.Record}s at the specified indices.
- * @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
- */
- remove: function(selectedIndices) {
- selectedIndices = [].concat(selectedIndices);
- for (var i = 0; i < selectedIndices.length; i++) {
- var rec = this.store.getAt(selectedIndices[i]);
- this.store.remove(rec);
- }
- },
-
-/**
- * Double click fires the event, but also, if this is draggable, and there is only one other
- * related DropZone, it transfers the selected node.
- */
- onDblClick : function(e){
- var item = this.findItemFromChild(e.getTarget());
- if(item){
- if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
- return false;
- }
- if (this.dragGroup) {
- var targets = Roo.dd.DragDropMgr.getRelated(this.dragZone, true);
- while (targets.indexOf(this.dropZone) > -1) {
- targets.remove(this.dropZone);
- }
- if (targets.length == 1) {
- this.dragZone.cachedTarget = null;
- var el = Roo.get(targets[0].getEl());
- var box = el.getBox(true);
- targets[0].onNodeDrop(el.dom, {
- target: el.dom,
- xy: [box.x, box.y + box.height - 1]
- }, null, this.getDragData(e));
- }
- }
- }
- },
-
- handleSelection: function(e) {
- this.dragZone.cachedTarget = null;
- var item = this.findItemFromChild(e.getTarget());
- if (!item) {
- this.clearSelections(true);
- return;
+ // private
+ setLegend : function(text){
+ if(this.rendered){
+ this.el.child('legend').update(text);
}
- if (item && (this.multiSelect || this.singleSelect)){
- if(this.multiSelect && e.shiftKey && (!e.ctrlKey) && this.lastSelection){
- this.select(this.getNodes(this.indexOf(this.lastSelection), item.nodeIndex), false);
- }else if (this.isSelected(this.getNode(item)) && e.ctrlKey){
- this.unselect(item);
- } else {
- this.select(item, this.multiSelect && e.ctrlKey);
- this.lastSelection = item;
- }
- }
- },
-
- onItemClick : function(item, index, e){
- if(this.fireEvent("beforeclick", this, index, item, e) === false){
- return false;
- }
- return true;
- },
-
- unselect : function(nodeInfo, suppressEvent){
- var node = this.getNode(nodeInfo);
- if(node && this.isSelected(node)){
- if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
- Roo.fly(node).removeClass(this.selectedClass);
- this.selections.remove(node);
- if(!suppressEvent){
- this.fireEvent("selectionchange", this, this.selections);
- }
- }
- }
}
-});
-/*
+});/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
-
/**
- * @class Roo.LayoutManager
- * @extends Roo.util.Observable
- * Base class for layout managers.
+ * @class Roo.form.VTypes
+ * Overridable validation definitions. The validations provided are basic and intended to be easily customizable and extended.
+ * @static
*/
-Roo.LayoutManager = function(container, config){
- Roo.LayoutManager.superclass.constructor.call(this);
- this.el = Roo.get(container);
- // ie scrollbar fix
- if(this.el.dom == document.body && Roo.isIE && !config.allowScroll){
- document.body.scroll = "no";
- }else if(this.el.dom != document.body && this.el.getStyle('position') == 'static'){
- this.el.position('relative');
- }
- this.id = this.el.id;
- this.el.addClass("x-layout-container");
- /** false to disable window resize monitoring @type Boolean */
- this.monitorWindowResize = true;
- this.regions = {};
- this.addEvents({
+Roo.form.VTypes = function(){
+ // closure these in so they are only created once.
+ var alpha = /^[a-zA-Z_]+$/;
+ var alphanum = /^[a-zA-Z0-9_]+$/;
+ var email = /^([\w]+)(.[\w]+)*@([\w-]+\.){1,5}([A-Za-z]){2,24}$/;
+ var url = /(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+
+ // All these messages and functions are configurable
+ return {
/**
- * @event layout
- * Fires when a layout is performed.
- * @param {Roo.LayoutManager} this
+ * The function used to validate email addresses
+ * @param {String} value The email address
*/
- "layout" : true,
+ 'email' : function(v){
+ return email.test(v);
+ },
/**
- * @event regionresized
- * Fires when the user resizes a region.
- * @param {Roo.LayoutRegion} region The resized region
- * @param {Number} newSize The new size (width for east/west, height for north/south)
+ * The error text to display when the email validation function returns false
+ * @type String
*/
- "regionresized" : true,
+ 'emailText' : 'This field should be an e-mail address in the format "user@domain.com"',
/**
- * @event regioncollapsed
- * Fires when a region is collapsed.
- * @param {Roo.LayoutRegion} region The collapsed region
+ * The keystroke filter mask to be applied on email input
+ * @type RegExp
*/
- "regioncollapsed" : true,
+ 'emailMask' : /[a-z0-9_\.\-@]/i,
+
/**
- * @event regionexpanded
- * Fires when a region is expanded.
- * @param {Roo.LayoutRegion} region The expanded region
+ * The function used to validate URLs
+ * @param {String} value The URL
*/
- "regionexpanded" : true
+ 'url' : function(v){
+ return url.test(v);
+ },
+ /**
+ * The error text to display when the url validation function returns false
+ * @type String
+ */
+ 'urlText' : 'This field should be a URL in the format "http:/'+'/www.domain.com"',
+
+ /**
+ * The function used to validate alpha values
+ * @param {String} value The value
+ */
+ 'alpha' : function(v){
+ return alpha.test(v);
+ },
+ /**
+ * The error text to display when the alpha validation function returns false
+ * @type String
+ */
+ 'alphaText' : 'This field should only contain letters and _',
+ /**
+ * The keystroke filter mask to be applied on alpha input
+ * @type RegExp
+ */
+ 'alphaMask' : /[a-z_]/i,
+
+ /**
+ * The function used to validate alphanumeric values
+ * @param {String} value The value
+ */
+ 'alphanum' : function(v){
+ return alphanum.test(v);
+ },
+ /**
+ * The error text to display when the alphanumeric validation function returns false
+ * @type String
+ */
+ 'alphanumText' : 'This field should only contain letters, numbers and _',
+ /**
+ * The keystroke filter mask to be applied on alphanumeric input
+ * @type RegExp
+ */
+ 'alphanumMask' : /[a-z0-9_]/i
+ };
+}();//<script type="text/javascript">
+
+/**
+ * @class Roo.form.FCKeditor
+ * @extends Roo.form.TextArea
+ * Wrapper around the FCKEditor http://www.fckeditor.net
+ * @constructor
+ * Creates a new FCKeditor
+ * @param {Object} config Configuration options
+ */
+Roo.form.FCKeditor = function(config){
+ Roo.form.FCKeditor.superclass.constructor.call(this, config);
+ this.addEvents({
+ /**
+ * @event editorinit
+ * Fired when the editor is initialized - you can add extra handlers here..
+ * @param {FCKeditor} this
+ * @param {Object} the FCK object.
+ */
+ editorinit : true
});
- this.updating = false;
- Roo.EventManager.onWindowResize(this.onWindowResize, this, true);
+
+
};
-
-Roo.extend(Roo.LayoutManager, Roo.util.Observable, {
+Roo.form.FCKeditor.editors = { };
+Roo.extend(Roo.form.FCKeditor, Roo.form.TextArea,
+{
+ //defaultAutoCreate : {
+ // tag : "textarea",style : "width:100px;height:60px;" ,autocomplete : "off"
+ //},
+ // private
/**
- * Returns true if this layout is currently being updated
- * @return {Boolean}
+ * @cfg {Object} fck options - see fck manual for details.
*/
- isUpdating : function(){
- return this.updating;
- },
+ fckconfig : false,
/**
- * Suspend the LayoutManager from doing auto-layouts while
- * making multiple add or remove calls
+ * @cfg {Object} fck toolbar set (Basic or Default)
*/
- beginUpdate : function(){
- this.updating = true;
- },
-
+ toolbarSet : 'Basic',
/**
- * Restore auto-layouts and optionally disable the manager from performing a layout
- * @param {Boolean} noLayout true to disable a layout update
- */
- endUpdate : function(noLayout){
- this.updating = false;
- if(!noLayout){
- this.layout();
- }
- },
+ * @cfg {Object} fck BasePath
+ */
+ basePath : '/fckeditor/',
- layout: function(){
-
- },
- onRegionResized : function(region, newSize){
- this.fireEvent("regionresized", region, newSize);
- this.layout();
- },
+ frame : false,
- onRegionCollapsed : function(region){
- this.fireEvent("regioncollapsed", region);
- },
+ value : '',
- onRegionExpanded : function(region){
- this.fireEvent("regionexpanded", region);
- },
-
- /**
- * Returns the size of the current view. This method normalizes document.body and element embedded layouts and
- * performs box-model adjustments.
- * @return {Object} The size as an object {width: (the width), height: (the height)}
- */
- getViewSize : function(){
- var size;
- if(this.el.dom != document.body){
- size = this.el.getSize();
- }else{
- size = {width: Roo.lib.Dom.getViewWidth(), height: Roo.lib.Dom.getViewHeight()};
+
+ onRender : function(ct, position)
+ {
+ if(!this.el){
+ this.defaultAutoCreate = {
+ tag: "textarea",
+ style:"width:300px;height:60px;",
+ autocomplete: "new-password"
+ };
}
- size.width -= this.el.getBorderWidth("lr")-this.el.getPadding("lr");
- size.height -= this.el.getBorderWidth("tb")-this.el.getPadding("tb");
- return size;
+ Roo.form.FCKeditor.superclass.onRender.call(this, ct, position);
+ /*
+ if(this.grow){
+ this.textSizeEl = Roo.DomHelper.append(document.body, {tag: "pre", cls: "x-form-grow-sizer"});
+ if(this.preventScrollbars){
+ this.el.setStyle("overflow", "hidden");
+ }
+ this.el.setHeight(this.growMin);
+ }
+ */
+ //console.log('onrender' + this.getId() );
+ Roo.form.FCKeditor.editors[this.getId()] = this;
+
+
+ this.replaceTextarea() ;
+
},
- /**
- * Returns the Element this layout is bound to.
- * @return {Roo.Element}
- */
- getEl : function(){
- return this.el;
+ getEditor : function() {
+ return this.fckEditor;
},
-
/**
- * Returns the specified region.
- * @param {String} target The region key ('center', 'north', 'south', 'east' or 'west')
- * @return {Roo.LayoutRegion}
+ * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}.
+ * @param {Mixed} value The value to set
*/
- getRegion : function(target){
- return this.regions[target.toLowerCase()];
- },
- onWindowResize : function(){
- if(this.monitorWindowResize){
- this.layout();
+
+ setValue : function(value)
+ {
+ //console.log('setValue: ' + value);
+
+ if(typeof(value) == 'undefined') { // not sure why this is happending...
+ return;
}
- }
-});/*
- * 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">
- */
-/**
- * @class Roo.BorderLayout
- * @extends Roo.LayoutManager
- * 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>
- * <a href="http://www.jackslocum.com/yui/2006/10/28/cross-browser-web-20-layouts-part-2-ajax-feed-viewer-20/">Cross Browser Layouts - Part 2</a><br><br>
- * Example:
- <pre><code>
- var layout = new Roo.BorderLayout(document.body, {
- north: {
- initialSize: 25,
- titlebar: false
- },
- west: {
- split:true,
- initialSize: 200,
- minSize: 175,
- maxSize: 400,
- titlebar: true,
- collapsible: true
- },
- east: {
- split:true,
- initialSize: 202,
- minSize: 175,
- maxSize: 400,
- titlebar: true,
- collapsible: true
- },
- south: {
- split:true,
- initialSize: 100,
- minSize: 100,
- maxSize: 200,
- titlebar: true,
- collapsible: true
- },
- center: {
- titlebar: true,
- autoScroll:true,
- resizeTabs: true,
- minTabWidth: 50,
- preferredTabWidth: 150
- }
-});
-
-// shorthand
-var CP = Roo.ContentPanel;
-
-layout.beginUpdate();
-layout.add("north", new CP("north", "North"));
-layout.add("south", new CP("south", {title: "South", closable: true}));
-layout.add("west", new CP("west", {title: "West"}));
-layout.add("east", new CP("autoTabs", {title: "Auto Tabs", closable: true}));
-layout.add("center", new CP("center1", {title: "Close Me", closable: true}));
-layout.add("center", new CP("center2", {title: "Center Panel", closable: false}));
-layout.getRegion("center").showPanel("center1");
-layout.endUpdate();
-</code></pre>
-
-<b>The container the layout is rendered into can be either the body element or any other element.
-If it is not the body element, the container needs to either be an absolute positioned element,
-or you will need to add "position:relative" to the css of the container. You will also need to specify
-the container size if it is not the body element.</b>
+ Roo.form.FCKeditor.superclass.setValue.apply(this,[value]);
+
+ //if(!this.el || !this.getEditor()) {
+ // this.value = value;
+ //this.setValue.defer(100,this,[value]);
+ // return;
+ //}
+
+ if(!this.getEditor()) {
+ return;
+ }
+
+ this.getEditor().SetData(value);
+
+ //
-* @constructor
-* Create a new BorderLayout
-* @param {String/HTMLElement/Element} container The container this layout is bound to
-* @param {Object} config Configuration options
- */
-Roo.BorderLayout = function(container, config){
- config = config || {};
- Roo.BorderLayout.superclass.constructor.call(this, container, config);
- this.factory = config.factory || Roo.BorderLayout.RegionFactory;
- for(var i = 0, len = this.factory.validRegions.length; i < len; i++) {
- var target = this.factory.validRegions[i];
- if(config[target]){
- this.addRegion(target, config[target]);
- }
- }
-};
+ },
-Roo.extend(Roo.BorderLayout, Roo.LayoutManager, {
/**
- * 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).
- * @param {Object} config The regions config object
- * @return {BorderLayoutRegion} The new region
+ * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}.
+ * @return {Mixed} value The field value
*/
- addRegion : function(target, config){
- if(!this.regions[target]){
- var r = this.factory.create(target, this, config);
- this.bindRegion(target, r);
+ getValue : function()
+ {
+
+ if (this.frame && this.frame.dom.style.display == 'none') {
+ return Roo.form.FCKeditor.superclass.getValue.call(this);
}
- return this.regions[target];
- },
+
+ if(!this.el || !this.getEditor()) {
+
+ // this.getValue.defer(100,this);
+ return this.value;
+ }
+
+
+ var value=this.getEditor().GetData();
+ Roo.form.FCKeditor.superclass.setValue.apply(this,[value]);
+ return Roo.form.FCKeditor.superclass.getValue.call(this);
+
- // private (kinda)
- bindRegion : function(name, r){
- this.regions[name] = r;
- r.on("visibilitychange", this.layout, this);
- r.on("paneladded", this.layout, this);
- r.on("panelremoved", this.layout, this);
- r.on("invalidated", this.layout, this);
- r.on("resized", this.onRegionResized, this);
- r.on("collapsed", this.onRegionCollapsed, this);
- r.on("expanded", this.onRegionExpanded, this);
},
/**
- * Performs a layout update.
+ * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
+ * @return {Mixed} value The field value
*/
- layout : function(){
- if(this.updating) {
+ getRawValue : function()
+ {
+ if (this.frame && this.frame.dom.style.display == 'none') {
+ return Roo.form.FCKeditor.superclass.getRawValue.call(this);
+ }
+
+ if(!this.el || !this.getEditor()) {
+ //this.getRawValue.defer(100,this);
+ return this.value;
return;
}
- var size = this.getViewSize();
- var w = size.width;
- var h = size.height;
- var centerW = w;
- var centerH = h;
- var centerY = 0;
- var centerX = 0;
- //var x = 0, y = 0;
-
- var rs = this.regions;
- var north = rs["north"];
- var south = rs["south"];
- var west = rs["west"];
- var east = rs["east"];
- var center = rs["center"];
- //if(this.hideOnLayout){ // not supported anymore
- //c.el.setStyle("display", "none");
+
+
+
+ var value=this.getEditor().GetData();
+ Roo.form.FCKeditor.superclass.setRawValue.apply(this,[value]);
+ return Roo.form.FCKeditor.superclass.getRawValue.call(this);
+
+ },
+
+ setSize : function(w,h) {
+
+
+
+ //if (this.frame && this.frame.dom.style.display == 'none') {
+ // Roo.form.FCKeditor.superclass.setSize.apply(this, [w, h]);
+ // return;
//}
- if(north && north.isVisible()){
- var b = north.getBox();
- var m = north.getMargins();
- b.width = w - (m.left+m.right);
- b.x = m.left;
- b.y = m.top;
- centerY = b.height + b.y + m.bottom;
- centerH -= centerY;
- north.updateBox(this.safeBox(b));
+ //if(!this.el || !this.getEditor()) {
+ // this.setSize.defer(100,this, [w,h]);
+ // return;
+ //}
+
+
+
+ Roo.form.FCKeditor.superclass.setSize.apply(this, [w, h]);
+
+ this.frame.dom.setAttribute('width', w);
+ this.frame.dom.setAttribute('height', h);
+ this.frame.setSize(w,h);
+
+ },
+
+ toggleSourceEdit : function(value) {
+
+
+
+ this.el.dom.style.display = value ? '' : 'none';
+ this.frame.dom.style.display = value ? 'none' : '';
+
+ },
+
+
+ focus: function(tag)
+ {
+ if (this.frame.dom.style.display == 'none') {
+ return Roo.form.FCKeditor.superclass.focus.call(this);
}
- if(south && south.isVisible()){
- var b = south.getBox();
- var m = south.getMargins();
- b.width = w - (m.left+m.right);
- b.x = m.left;
- var totalHeight = (b.height + m.top + m.bottom);
- b.y = h - totalHeight + m.top;
- centerH -= totalHeight;
- south.updateBox(this.safeBox(b));
+ if(!this.el || !this.getEditor()) {
+ this.focus.defer(100,this, [tag]);
+ return;
}
- if(west && west.isVisible()){
- var b = west.getBox();
- var m = west.getMargins();
- b.height = centerH - (m.top+m.bottom);
- b.x = m.left;
- b.y = centerY + m.top;
- var totalWidth = (b.width + m.left + m.right);
- centerX += totalWidth;
- centerW -= totalWidth;
- west.updateBox(this.safeBox(b));
+
+
+
+
+ var tgs = this.getEditor().EditorDocument.getElementsByTagName(tag);
+ this.getEditor().Focus();
+ if (tgs.length) {
+ if (!this.getEditor().Selection.GetSelection()) {
+ this.focus.defer(100,this, [tag]);
+ return;
+ }
+
+
+ var r = this.getEditor().EditorDocument.createRange();
+ r.setStart(tgs[0],0);
+ r.setEnd(tgs[0],0);
+ this.getEditor().Selection.GetSelection().removeAllRanges();
+ this.getEditor().Selection.GetSelection().addRange(r);
+ this.getEditor().Focus();
}
- if(east && east.isVisible()){
- var b = east.getBox();
- var m = east.getMargins();
- b.height = centerH - (m.top+m.bottom);
- var totalWidth = (b.width + m.left + m.right);
- b.x = w - totalWidth + m.left;
- b.y = centerY + m.top;
- centerW -= totalWidth;
- east.updateBox(this.safeBox(b));
+
+ },
+
+
+
+ replaceTextarea : function()
+ {
+ if ( document.getElementById( this.getId() + '___Frame' ) ) {
+ return ;
}
- if(center){
- var m = center.getMargins();
- var centerBox = {
- x: centerX + m.left,
- y: centerY + m.top,
- width: centerW - (m.left+m.right),
- height: centerH - (m.top+m.bottom)
- };
- //if(this.hideOnLayout){
- //center.el.setStyle("display", "block");
- //}
- center.updateBox(this.safeBox(centerBox));
+ //if ( !this.checkBrowser || this._isCompatibleBrowser() )
+ //{
+ // We must check the elements firstly using the Id and then the name.
+ var oTextarea = document.getElementById( this.getId() );
+
+ var colElementsByName = document.getElementsByName( this.getId() ) ;
+
+ oTextarea.style.display = 'none' ;
+
+ if ( oTextarea.tabIndex ) {
+ this.TabIndex = oTextarea.tabIndex ;
}
- this.el.repaint();
- this.fireEvent("layout", this);
+
+ this._insertHtmlBefore( this._getConfigHtml(), oTextarea ) ;
+ this._insertHtmlBefore( this._getIFrameHtml(), oTextarea ) ;
+ this.frame = Roo.get(this.getId() + '___Frame')
},
+
+ _getConfigHtml : function()
+ {
+ var sConfig = '' ;
- // private
- safeBox : function(box){
- box.width = Math.max(0, box.width);
- box.height = Math.max(0, box.height);
- return box;
- },
+ for ( var o in this.fckconfig ) {
+ sConfig += sConfig.length > 0 ? '&' : '';
+ sConfig += encodeURIComponent( o ) + '=' + encodeURIComponent( this.fckconfig[o] ) ;
+ }
- /**
- * Adds a ContentPanel (or subclass) to this layout.
- * @param {String} target The target region key (north, south, east, west or center).
- * @param {Roo.ContentPanel} panel The panel to add
- * @return {Roo.ContentPanel} The added panel
- */
- add : function(target, panel){
-
- target = target.toLowerCase();
- return this.regions[target].add(panel);
+ return '<input type="hidden" id="' + this.getId() + '___Config" value="' + sConfig + '" style="display:none" />' ;
},
+
+
+ _getIFrameHtml : function()
+ {
+ var sFile = 'fckeditor.html' ;
+ /* no idea what this is about..
+ try
+ {
+ if ( (/fcksource=true/i).test( window.top.location.search ) )
+ sFile = 'fckeditor.original.html' ;
+ }
+ catch (e) {
+ */
- /**
- * Remove a ContentPanel (or subclass) to this layout.
- * @param {String} target The target region key (north, south, east, west or center).
- * @param {Number/String/Roo.ContentPanel} panel The index, id or panel to remove
- * @return {Roo.ContentPanel} The removed panel
- */
- remove : function(target, panel){
- target = target.toLowerCase();
- return this.regions[target].remove(panel);
- },
+ var sLink = this.basePath + 'editor/' + sFile + '?InstanceName=' + encodeURIComponent( this.getId() ) ;
+ sLink += this.toolbarSet ? ( '&Toolbar=' + this.toolbarSet) : '';
+
+
+ var html = '<iframe id="' + this.getId() +
+ '___Frame" src="' + sLink +
+ '" width="' + this.width +
+ '" height="' + this.height + '"' +
+ (this.tabIndex ? ' tabindex="' + this.tabIndex + '"' :'' ) +
+ ' frameborder="0" scrolling="no"></iframe>' ;
- /**
- * Searches all regions for a panel with the specified id
- * @param {String} panelId
- * @return {Roo.ContentPanel} The panel or null if it wasn't found
- */
- findPanel : function(panelId){
- var rs = this.regions;
- for(var target in rs){
- if(typeof rs[target] != "function"){
- var p = rs[target].getPanel(panelId);
- if(p){
- return p;
- }
- }
- }
- return null;
+ return html ;
},
+
+ _insertHtmlBefore : function( html, element )
+ {
+ if ( element.insertAdjacentHTML ) {
+ // IE
+ element.insertAdjacentHTML( 'beforeBegin', html ) ;
+ } else { // Gecko
+ var oRange = document.createRange() ;
+ oRange.setStartBefore( element ) ;
+ var oFragment = oRange.createContextualFragment( html );
+ element.parentNode.insertBefore( oFragment, element ) ;
+ }
+ }
+
+
+
+
+
+
+
- /**
- * Searches all regions for a panel with the specified id and activates (shows) it.
- * @param {String/ContentPanel} panelId The panels id or the panel itself
- * @return {Roo.ContentPanel} The shown panel or null
- */
- showPanel : function(panelId) {
- var rs = this.regions;
- for(var target in rs){
- var r = rs[target];
- if(typeof r != "function"){
- if(r.hasPanel(panelId)){
- return r.showPanel(panelId);
- }
- }
- }
- return null;
- },
+});
- /**
- * Restores this layout's state using Roo.state.Manager or the state provided by the passed provider.
- * @param {Roo.state.Provider} provider (optional) An alternate state provider
- */
- restoreState : function(provider){
- if(!provider){
- provider = Roo.state.Manager;
- }
- var sm = new Roo.LayoutStateManager();
- sm.init(this, provider);
- },
+//Roo.reg('fckeditor', Roo.form.FCKeditor);
- /**
- * Adds a batch of multiple ContentPanels dynamically by passing a special regions config object. This config
- * object should contain properties for each region to add ContentPanels to, and each property's value should be
- * a valid ContentPanel config object. Example:
- * <pre><code>
-// Create the main layout
-var layout = new Roo.BorderLayout('main-ct', {
- west: {
- split:true,
- minSize: 175,
- titlebar: true
- },
- center: {
- title:'Components'
- }
-}, 'main-ct');
+function FCKeditor_OnComplete(editorInstance){
+ var f = Roo.form.FCKeditor.editors[editorInstance.Name];
+ f.fckEditor = editorInstance;
+ //console.log("loaded");
+ f.fireEvent('editorinit', f, editorInstance);
+}
+
-// Create and add multiple ContentPanels at once via configs
-layout.batchAdd({
- west: {
- id: 'source-files',
- autoCreate:true,
- title:'Ext Source Files',
- autoScroll:true,
- fitToFrame:true
- },
- center : {
- el: cview,
- autoScroll:true,
- fitToFrame:true,
- toolbar: tb,
- resizeEl:'cbody'
- }
-});
-</code></pre>
- * @param {Object} regions An object containing ContentPanel configs by region name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//<script type="text/javascript">
+/**
+ * @class Roo.form.GridField
+ * @extends Roo.form.Field
+ * Embed a grid (or editable grid into a form)
+ * STATUS ALPHA
+ *
+ * This embeds a grid in a form, the value of the field should be the json encoded array of rows
+ * it needs
+ * xgrid.store = Roo.data.Store
+ * xgrid.store.proxy = Roo.data.MemoryProxy (data = [] )
+ * xgrid.store.reader = Roo.data.JsonReader
+ *
+ *
+ * @constructor
+ * Creates a new GridField
+ * @param {Object} config Configuration options
+ */
+Roo.form.GridField = function(config){
+ Roo.form.GridField.superclass.constructor.call(this, config);
+
+};
+
+Roo.extend(Roo.form.GridField, Roo.form.Field, {
+ /**
+ * @cfg {Number} width - used to restrict width of grid..
*/
- batchAdd : function(regions){
- this.beginUpdate();
- for(var rname in regions){
- var lr = this.regions[rname];
- if(lr){
- this.addTypedPanels(lr, regions[rname]);
- }
- }
- this.endUpdate();
+ width : 100,
+ /**
+ * @cfg {Number} height - used to restrict height of grid..
+ */
+ height : 50,
+ /**
+ * @cfg {Object} xgrid (xtype'd description of grid) { xtype : 'Grid', dataSource: .... }
+ *
+ *}
+ */
+ xgrid : false,
+ /**
+ * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
+ * {tag: "input", type: "checkbox", autocomplete: "off"})
+ */
+ // defaultAutoCreate : { tag: 'div' },
+ defaultAutoCreate : { tag: 'input', type: 'hidden', autocomplete: 'new-password'},
+ /**
+ * @cfg {String} addTitle Text to include for adding a title.
+ */
+ addTitle : false,
+ //
+ onResize : function(){
+ Roo.form.Field.superclass.onResize.apply(this, arguments);
},
- // private
- addTypedPanels : function(lr, ps){
- if(typeof ps == 'string'){
- lr.add(new Roo.ContentPanel(ps));
- }
- else if(ps instanceof Array){
- for(var i =0, len = ps.length; i < len; i++){
- this.addTypedPanels(lr, ps[i]);
- }
- }
- else if(!ps.events){ // raw config?
- var el = ps.el;
- delete ps.el; // prevent conflict
- lr.add(new Roo.ContentPanel(el || Roo.id(), ps));
- }
- else { // panel object assumed!
- lr.add(ps);
- }
+ initEvents : function(){
+ // Roo.form.Checkbox.superclass.initEvents.call(this);
+ // has no events...
+
},
- /**
- * Adds a xtype elements to the layout.
- * <pre><code>
-layout.addxtype({
- xtype : 'ContentPanel',
- region: 'west',
- items: [ .... ]
- }
-);
-layout.addxtype({
- xtype : 'NestedLayoutPanel',
- region: 'west',
- layout: {
- center: { },
- west: { }
- },
- items : [ ... list of content panels or nested layout panels.. ]
- }
-);
-</code></pre>
- * @param {Object} cfg Xtype definition of item to add.
- */
- addxtype : function(cfg)
- {
- // basically accepts a pannel...
- // can accept a layout region..!?!?
- //Roo.log('Roo.BorderLayout add ' + cfg.xtype)
+ getResizeEl : function(){
+ return this.wrap;
+ },
+
+ getPositionEl : function(){
+ return this.wrap;
+ },
+
+ // private
+ onRender : function(ct, position){
- if (!cfg.xtype.match(/Panel$/)) {
- return false;
- }
- var ret = false;
+ this.style = this.style || 'overflow: hidden; border:1px solid #c3daf9;';
+ var style = this.style;
+ delete this.style;
- if (typeof(cfg.region) == 'undefined') {
- Roo.log("Failed to add Panel, region was not set");
- Roo.log(cfg);
- return false;
+ Roo.form.GridField.superclass.onRender.call(this, ct, position);
+ this.wrap = this.el.wrap({cls: ''}); // not sure why ive done thsi...
+ this.viewEl = this.wrap.createChild({ tag: 'div' });
+ if (style) {
+ this.viewEl.applyStyles(style);
}
- var region = cfg.region;
- delete cfg.region;
-
-
- var xitems = [];
- if (cfg.items) {
- xitems = cfg.items;
- delete cfg.items;
+ if (this.width) {
+ this.viewEl.setWidth(this.width);
}
- var nb = false;
+ if (this.height) {
+ this.viewEl.setHeight(this.height);
+ }
+ //if(this.inputValue !== undefined){
+ //this.setValue(this.value);
- switch(cfg.xtype)
- {
- case 'ContentPanel': // ContentPanel (el, cfg)
- case 'ScrollPanel': // ContentPanel (el, cfg)
- case 'ViewPanel':
- if(cfg.autoCreate) {
- ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
- } else {
- var el = this.el.createChild();
- ret = new Roo[cfg.xtype](el, cfg); // new panel!!!!!
- }
-
- this.add(region, ret);
- break;
-
-
- case 'TreePanel': // our new panel!
- cfg.el = this.el.createChild();
- ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
- this.add(region, ret);
- break;
-
- case 'NestedLayoutPanel':
- // create a new Layout (which is a Border Layout...
- var el = this.el.createChild();
- var clayout = cfg.layout;
- delete cfg.layout;
- clayout.items = clayout.items || [];
- // replace this exitems with the clayout ones..
- xitems = clayout.items;
-
-
- if (region == 'center' && this.active && this.getRegion('center').panels.length < 1) {
- cfg.background = false;
- }
- var layout = new Roo.BorderLayout(el, clayout);
-
- ret = new Roo[cfg.xtype](layout, cfg); // new panel!!!!!
- //console.log('adding nested layout panel ' + cfg.toSource());
- this.add(region, ret);
- nb = {}; /// find first...
- break;
-
- case 'GridPanel':
-
- // needs grid and region
-
- //var el = this.getRegion(region).el.createChild();
- var el = this.el.createChild();
- // create the grid first...
-
- var grid = new Roo.grid[cfg.grid.xtype](el, cfg.grid);
- delete cfg.grid;
- if (region == 'center' && this.active ) {
- cfg.background = false;
- }
- ret = new Roo[cfg.xtype](grid, cfg); // new panel!!!!!
-
- this.add(region, ret);
- if (cfg.background) {
- ret.on('activate', function(gp) {
- if (!gp.grid.rendered) {
- gp.grid.render();
- }
- });
- } else {
- grid.render();
- }
- break;
-
-
-
-
-
-
- default:
- if (typeof(Roo[cfg.xtype]) != 'undefined') {
-
- ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
- this.add(region, ret);
- } else {
-
- alert("Can not add '" + cfg.xtype + "' to BorderLayout");
- return null;
- }
-
- // GridPanel (grid, cfg)
-
+
+ this.grid = new Roo.grid[this.xgrid.xtype](this.viewEl, this.xgrid);
+
+
+ this.grid.render();
+ this.grid.getDataSource().on('remove', this.refreshValue, this);
+ this.grid.getDataSource().on('update', this.refreshValue, this);
+ this.grid.on('afteredit', this.refreshValue, this);
+
+ },
+
+
+ /**
+ * Sets the value of the item.
+ * @param {String} either an object or a string..
+ */
+ setValue : function(v){
+ //this.value = v;
+ v = v || []; // empty set..
+ // this does not seem smart - it really only affects memoryproxy grids..
+ if (this.grid && this.grid.getDataSource() && typeof(v) != 'undefined') {
+ var ds = this.grid.getDataSource();
+ // assumes a json reader..
+ var data = {}
+ data[ds.reader.meta.root ] = typeof(v) == 'string' ? Roo.decode(v) : v;
+ ds.loadData( data);
}
- this.beginUpdate();
- // add children..
- var region = '';
- var abn = {};
- Roo.each(xitems, function(i) {
- region = nb && i.region ? i.region : false;
-
- var add = ret.addxtype(i);
-
- if (region) {
- nb[region] = nb[region] == undefined ? 0 : nb[region]+1;
- if (!i.background) {
- abn[region] = nb[region] ;
- }
- }
-
- });
- this.endUpdate();
-
- // make the last non-background panel active..
- //if (nb) { Roo.log(abn); }
- if (nb) {
-
- for(var r in abn) {
- region = this.getRegion(r);
- if (region) {
- // tried using nb[r], but it does not work..
-
- region.showPanel(abn[r]);
-
- }
- }
+ // clear selection so it does not get stale.
+ if (this.grid.sm) {
+ this.grid.sm.clearSelections();
}
- return ret;
- }
-});
-
-/**
- * Shortcut for creating a new BorderLayout object and adding one or more ContentPanels to it in a single step, handling
- * the beginUpdate and endUpdate calls internally. The key to this method is the <b>panels</b> property that can be
- * provided with each region config, which allows you to add ContentPanel configs in addition to the region configs
- * during creation. The following code is equivalent to the constructor-based example at the beginning of this class:
- * <pre><code>
-// shorthand
-var CP = Roo.ContentPanel;
-
-var layout = Roo.BorderLayout.create({
- north: {
- initialSize: 25,
- titlebar: false,
- panels: [new CP("north", "North")]
- },
- west: {
- split:true,
- initialSize: 200,
- minSize: 175,
- maxSize: 400,
- titlebar: true,
- collapsible: true,
- panels: [new CP("west", {title: "West"})]
- },
- east: {
- split:true,
- initialSize: 202,
- minSize: 175,
- maxSize: 400,
- titlebar: true,
- collapsible: true,
- panels: [new CP("autoTabs", {title: "Auto Tabs", closable: true})]
- },
- south: {
- split:true,
- initialSize: 100,
- minSize: 100,
- maxSize: 200,
- titlebar: true,
- collapsible: true,
- panels: [new CP("south", {title: "South", closable: true})]
+ Roo.form.GridField.superclass.setValue.call(this, v);
+ this.refreshValue();
+ // should load data in the grid really....
},
- center: {
- titlebar: true,
- autoScroll:true,
- resizeTabs: true,
- minTabWidth: 50,
- preferredTabWidth: 150,
- panels: [
- new CP("center1", {title: "Close Me", closable: true}),
- new CP("center2", {title: "Center Panel", closable: false})
- ]
- }
-}, document.body);
-
-layout.getRegion("center").showPanel("center1");
-</code></pre>
- * @param config
- * @param targetEl
- */
-Roo.BorderLayout.create = function(config, targetEl){
- var layout = new Roo.BorderLayout(targetEl || document.body, config);
- layout.beginUpdate();
- var regions = Roo.BorderLayout.RegionFactory.validRegions;
- for(var j = 0, jlen = regions.length; j < jlen; j++){
- var lr = regions[j];
- if(layout.regions[lr] && config[lr].panels){
- var r = layout.regions[lr];
- var ps = config[lr].panels;
- layout.addTypedPanels(r, ps);
- }
- }
- layout.endUpdate();
- return layout;
-};
-
-// private
-Roo.BorderLayout.RegionFactory = {
- // private
- validRegions : ["north","south","east","west","center"],
-
+
// private
- create : function(target, mgr, config){
- target = target.toLowerCase();
- if(config.lightweight || config.basic){
- return new Roo.BasicLayoutRegion(mgr, config, target);
- }
- switch(target){
- case "north":
- return new Roo.NorthLayoutRegion(mgr, config);
- case "south":
- return new Roo.SouthLayoutRegion(mgr, config);
- case "east":
- return new Roo.EastLayoutRegion(mgr, config);
- case "west":
- return new Roo.WestLayoutRegion(mgr, config);
- case "center":
- return new Roo.CenterLayoutRegion(mgr, config);
- }
- throw 'Layout region "'+target+'" not supported.';
+ refreshValue: function() {
+ var val = [];
+ this.grid.getDataSource().each(function(r) {
+ val.push(r.data);
+ });
+ this.el.dom.value = Roo.encode(val);
}
-};/*
+
+
+
+
+});/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
-
/**
- * @class Roo.BasicLayoutRegion
- * @extends Roo.util.Observable
- * This class represents a lightweight region in a layout manager. This region does not move dom nodes
- * and does not have a titlebar, tabs or any other features. All it does is size and position
- * panels. To create a BasicLayoutRegion, add lightweight:true or basic:true to your regions config.
+ * @class Roo.form.DisplayField
+ * @extends Roo.form.Field
+ * A generic Field to display non-editable data.
+ * @cfg {Boolean} closable (true|false) default false
+ * @constructor
+ * Creates a new Display Field item.
+ * @param {Object} config Configuration options
*/
-Roo.BasicLayoutRegion = function(mgr, config, pos, skipConfig){
- this.mgr = mgr;
- this.position = pos;
- this.events = {
- /**
- * @scope Roo.BasicLayoutRegion
- */
-
- /**
- * @event beforeremove
- * Fires before a panel is removed (or closed). To cancel the removal set "e.cancel = true" on the event argument.
- * @param {Roo.LayoutRegion} this
- * @param {Roo.ContentPanel} panel The panel
- * @param {Object} e The cancel event object
- */
- "beforeremove" : true,
- /**
- * @event invalidated
- * Fires when the layout for this region is changed.
- * @param {Roo.LayoutRegion} this
- */
- "invalidated" : true,
+Roo.form.DisplayField = function(config){
+ Roo.form.DisplayField.superclass.constructor.call(this, config);
+
+ this.addEvents({
/**
- * @event visibilitychange
- * Fires when this region is shown or hidden
- * @param {Roo.LayoutRegion} this
- * @param {Boolean} visibility true or false
- */
- "visibilitychange" : true,
- /**
- * @event paneladded
- * Fires when a panel is added.
- * @param {Roo.LayoutRegion} this
- * @param {Roo.ContentPanel} panel The panel
- */
- "paneladded" : true,
- /**
- * @event panelremoved
- * Fires when a panel is removed.
- * @param {Roo.LayoutRegion} this
- * @param {Roo.ContentPanel} panel The panel
- */
- "panelremoved" : true,
- /**
- * @event beforecollapse
- * Fires when this region before collapse.
- * @param {Roo.LayoutRegion} this
- */
- "beforecollapse" : true,
- /**
- * @event collapsed
- * Fires when this region is collapsed.
- * @param {Roo.LayoutRegion} this
- */
- "collapsed" : true,
- /**
- * @event expanded
- * Fires when this region is expanded.
- * @param {Roo.LayoutRegion} this
- */
- "expanded" : true,
- /**
- * @event slideshow
- * Fires when this region is slid into view.
- * @param {Roo.LayoutRegion} this
- */
- "slideshow" : true,
- /**
- * @event slidehide
- * Fires when this region slides out of view.
- * @param {Roo.LayoutRegion} this
- */
- "slidehide" : true,
- /**
- * @event panelactivated
- * Fires when a panel is activated.
- * @param {Roo.LayoutRegion} this
- * @param {Roo.ContentPanel} panel The activated panel
- */
- "panelactivated" : true,
- /**
- * @event resized
- * Fires when the user resizes this region.
- * @param {Roo.LayoutRegion} this
- * @param {Number} newSize The new size (width for east/west, height for north/south)
- */
- "resized" : true
- };
- /** A collection of panels in this region. @type Roo.util.MixedCollection */
- this.panels = new Roo.util.MixedCollection();
- this.panels.getKey = this.getPanelId.createDelegate(this);
- this.box = null;
- this.activePanel = null;
- // ensure listeners are added...
-
- if (config.listeners || config.events) {
- Roo.BasicLayoutRegion.superclass.constructor.call(this, {
- listeners : config.listeners || {},
- events : config.events || {}
- });
- }
-
- if(skipConfig !== true){
- this.applyConfig(config);
- }
+ * @event close
+ * Fires after the click the close btn
+ * @param {Roo.form.DisplayField} this
+ */
+ close : true
+ });
};
-Roo.extend(Roo.BasicLayoutRegion, Roo.util.Observable, {
- getPanelId : function(p){
- return p.getId();
- },
-
- applyConfig : function(config){
- this.margins = config.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
- this.config = config;
-
- },
+Roo.extend(Roo.form.DisplayField, Roo.form.TextField, {
+ inputType: 'hidden',
+ allowBlank: true,
+ readOnly: true,
+
/**
- * Resizes the region to the specified size. For vertical regions (west, east) this adjusts
- * the width, for horizontal (north, south) the height.
- * @param {Number} newSize The new width or height
+ * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
*/
- resizeTo : function(newSize){
- var el = this.el ? this.el :
- (this.activePanel ? this.activePanel.getEl() : null);
- if(el){
- switch(this.position){
- case "east":
- case "west":
- el.setWidth(newSize);
- this.fireEvent("resized", this, newSize);
- break;
- case "north":
- case "south":
- el.setHeight(newSize);
- this.fireEvent("resized", this, newSize);
- break;
- }
- }
- },
-
- getBox : function(){
- return this.activePanel ? this.activePanel.getEl().getBox(false, true) : null;
- },
-
- getMargins : function(){
- return this.margins;
- },
-
- updateBox : function(box){
- this.box = box;
- var el = this.activePanel.getEl();
- el.dom.style.left = box.x + "px";
- el.dom.style.top = box.y + "px";
- this.activePanel.setSize(box.width, box.height);
- },
-
+ focusClass : undefined,
/**
- * Returns the container element for this region.
- * @return {Roo.Element}
+ * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
*/
- getEl : function(){
- return this.activePanel;
- },
+ fieldClass: 'x-form-field',
+
+ /**
+ * @cfg {Function} valueRenderer The renderer for the field (so you can reformat output). should return raw HTML
+ */
+ valueRenderer: undefined,
+ width: 100,
/**
- * Returns true if this region is currently visible.
- * @return {Boolean}
+ * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
+ * {tag: "input", type: "checkbox", autocomplete: "off"})
*/
- isVisible : function(){
- return this.activePanel ? true : false;
- },
+
+ // defaultAutoCreate : { tag: 'input', type: 'hidden', autocomplete: 'off'},
+
+ closable : false,
- setActivePanel : function(panel){
- panel = this.getPanel(panel);
- if(this.activePanel && this.activePanel != panel){
- this.activePanel.setActiveState(false);
- this.activePanel.getEl().setLeftTop(-10000,-10000);
+ onResize : function(){
+ Roo.form.DisplayField.superclass.onResize.apply(this, arguments);
+
+ },
+
+ initEvents : function(){
+ // Roo.form.Checkbox.superclass.initEvents.call(this);
+ // has no events...
+
+ if(this.closable){
+ this.closeEl.on('click', this.onClose, this);
}
- this.activePanel = panel;
- panel.setActiveState(true);
- if(this.box){
- panel.setSize(this.box.width, this.box.height);
+
+ },
+
+
+ getResizeEl : function(){
+ return this.wrap;
+ },
+
+ getPositionEl : function(){
+ return this.wrap;
+ },
+
+ // private
+ onRender : function(ct, position){
+
+ Roo.form.DisplayField.superclass.onRender.call(this, ct, position);
+ //if(this.inputValue !== undefined){
+ this.wrap = this.el.wrap();
+
+ this.viewEl = this.wrap.createChild({ tag: 'div', cls: 'x-form-displayfield'});
+
+ if(this.closable){
+ this.closeEl = this.wrap.createChild({ tag: 'div', cls: 'x-dlg-close'});
}
- this.fireEvent("panelactivated", this, panel);
- this.fireEvent("invalidated");
+
+ if (this.bodyStyle) {
+ this.viewEl.applyStyles(this.bodyStyle);
+ }
+ //this.viewEl.setStyle('padding', '2px');
+
+ this.setValue(this.value);
+
},
-
+/*
+ // private
+ initValue : Roo.emptyFn,
+
+ */
+
+ // private
+ onClick : function(){
+
+ },
+
/**
- * Show the specified panel.
- * @param {Number/String/ContentPanel} panelId The panels index, id or the panel itself
- * @return {Roo.ContentPanel} The shown panel or null
+ * Sets the checked state of the checkbox.
+ * @param {Boolean/String} checked True, 'true', '1', or 'on' to check the checkbox, any other value will uncheck it.
*/
- showPanel : function(panel){
- if(panel = this.getPanel(panel)){
- this.setActivePanel(panel);
+ setValue : function(v){
+ this.value = v;
+ var html = this.valueRenderer ? this.valueRenderer(v) : String.format('{0}', v);
+ // this might be called before we have a dom element..
+ if (!this.viewEl) {
+ return;
}
- return panel;
+ this.viewEl.dom.innerHTML = html;
+ Roo.form.DisplayField.superclass.setValue.call(this, v);
+
},
+ onClose : function(e)
+ {
+ e.preventDefault();
+
+ this.fireEvent('close', this);
+ }
+});/*
+ *
+ * Licence- LGPL
+ *
+ */
+
+/**
+ * @class Roo.form.DayPicker
+ * @extends Roo.form.Field
+ * A Day picker show [M] [T] [W] ....
+ * @constructor
+ * Creates a new Day Picker
+ * @param {Object} config Configuration options
+ */
+Roo.form.DayPicker= function(config){
+ Roo.form.DayPicker.superclass.constructor.call(this, config);
+
+};
+
+Roo.extend(Roo.form.DayPicker, Roo.form.Field, {
/**
- * Get the active panel for this region.
- * @return {Roo.ContentPanel} The active panel or null
+ * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
*/
- getActivePanel : function(){
- return this.activePanel;
- },
-
+ focusClass : undefined,
/**
- * Add the passed ContentPanel(s)
- * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
- * @return {Roo.ContentPanel} The panel added (if only one was added)
+ * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
*/
- add : function(panel){
- if(arguments.length > 1){
- for(var i = 0, len = arguments.length; i < len; i++) {
- this.add(arguments[i]);
- }
- return null;
- }
- if(this.hasPanel(panel)){
- this.showPanel(panel);
- return panel;
- }
- var el = panel.getEl();
- if(el.dom.parentNode != this.mgr.el.dom){
- this.mgr.el.dom.appendChild(el.dom);
- }
- if(panel.setRegion){
- panel.setRegion(this);
- }
- this.panels.add(panel);
- el.setStyle("position", "absolute");
- if(!panel.background){
- this.setActivePanel(panel);
- if(this.config.initialSize && this.panels.getCount()==1){
- this.resizeTo(this.config.initialSize);
- }
+ fieldClass: "x-form-field",
+
+ /**
+ * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
+ * {tag: "input", type: "checkbox", autocomplete: "off"})
+ */
+ defaultAutoCreate : { tag: "input", type: 'hidden', autocomplete: "new-password"},
+
+
+ actionMode : 'viewEl',
+ //
+ // private
+
+ inputType : 'hidden',
+
+
+ inputElement: false, // real input element?
+ basedOn: false, // ????
+
+ isFormField: true, // not sure where this is needed!!!!
+
+ onResize : function(){
+ Roo.form.Checkbox.superclass.onResize.apply(this, arguments);
+ if(!this.boxLabel){
+ this.el.alignTo(this.wrap, 'c-c');
}
- this.fireEvent("paneladded", this, panel);
- return panel;
},
+
+ initEvents : function(){
+ Roo.form.Checkbox.superclass.initEvents.call(this);
+ this.el.on("click", this.onClick, this);
+ this.el.on("change", this.onClick, this);
+ },
+
+
+ getResizeEl : function(){
+ return this.wrap;
+ },
+
+ getPositionEl : function(){
+ return this.wrap;
+ },
+
+ // private
+ onRender : function(ct, position){
+ Roo.form.Checkbox.superclass.onRender.call(this, ct, position);
+
+ this.wrap = this.el.wrap({cls: 'x-form-daypick-item '});
+
+ var r1 = '<table><tr>';
+ var r2 = '<tr class="x-form-daypick-icons">';
+ for (var i=0; i < 7; i++) {
+ r1+= '<td><div>' + Date.dayNames[i].substring(0,3) + '</div></td>';
+ r2+= '<td><img class="x-menu-item-icon" src="' + Roo.BLANK_IMAGE_URL +'"></td>';
+ }
+
+ var viewEl = this.wrap.createChild( r1 + '</tr>' + r2 + '</tr></table>');
+ viewEl.select('img').on('click', this.onClick, this);
+ this.viewEl = viewEl;
+
+
+ // this will not work on Chrome!!!
+ this.el.on('DOMAttrModified', this.setFromHidden, this); //ff
+ this.el.on('propertychange', this.setFromHidden, this); //ie
+
+
+
+
+ },
+
+ // private
+ initValue : Roo.emptyFn,
+
/**
- * Returns true if the panel is in this region.
- * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
- * @return {Boolean}
+ * Returns the checked state of the checkbox.
+ * @return {Boolean} True if checked, else false
*/
- hasPanel : function(panel){
- if(typeof panel == "object"){ // must be panel obj
- panel = panel.getId();
- }
- return this.getPanel(panel) ? true : false;
+ getValue : function(){
+ return this.el.dom.value;
+
+ },
+
+ // private
+ onClick : function(e){
+ //this.setChecked(!this.checked);
+ Roo.get(e.target).toggleClass('x-menu-item-checked');
+ this.refreshValue();
+ //if(this.el.dom.checked != this.checked){
+ // this.setValue(this.el.dom.checked);
+ // }
},
+ // private
+ refreshValue : function()
+ {
+ var val = '';
+ this.viewEl.select('img',true).each(function(e,i,n) {
+ val += e.is(".x-menu-item-checked") ? String(n) : '';
+ });
+ this.setValue(val, true);
+ },
+
/**
- * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
- * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
- * @param {Boolean} preservePanel Overrides the config preservePanel option
- * @return {Roo.ContentPanel} The panel that was removed
+ * Sets the checked state of the checkbox.
+ * On is always based on a string comparison between inputValue and the param.
+ * @param {Boolean/String} value - the value to set
+ * @param {Boolean/String} suppressEvent - whether to suppress the checkchange event.
*/
- remove : function(panel, preservePanel){
- panel = this.getPanel(panel);
- if(!panel){
- return null;
+ setValue : function(v,suppressEvent){
+ if (!this.el.dom) {
+ return;
}
- var e = {};
- this.fireEvent("beforeremove", this, panel, e);
- if(e.cancel === true){
- return null;
+ var old = this.el.dom.value ;
+ this.el.dom.value = v;
+ if (suppressEvent) {
+ return ;
}
- var panelId = panel.getId();
- this.panels.removeKey(panelId);
- return panel;
+
+ // update display..
+ this.viewEl.select('img',true).each(function(e,i,n) {
+
+ var on = e.is(".x-menu-item-checked");
+ var newv = v.indexOf(String(n)) > -1;
+ if (on != newv) {
+ e.toggleClass('x-menu-item-checked');
+ }
+
+ });
+
+
+ this.fireEvent('change', this, v, old);
+
+
},
-
- /**
- * Returns the panel specified or null if it's not in this region.
- * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
- * @return {Roo.ContentPanel}
- */
- getPanel : function(id){
- if(typeof id == "object"){ // must be panel obj
- return id;
+
+ // handle setting of hidden value by some other method!!?!?
+ setFromHidden: function()
+ {
+ if(!this.el){
+ return;
}
- return this.panels.get(id);
+ //console.log("SET FROM HIDDEN");
+ //alert('setFrom hidden');
+ this.setValue(this.el.dom.value);
},
- /**
- * Returns this regions position (north/south/east/west/center).
- * @return {String}
- */
- getPosition: function(){
- return this.position;
+ onDestroy : function()
+ {
+ if(this.viewEl){
+ Roo.get(this.viewEl).remove();
+ }
+
+ Roo.form.DayPicker.superclass.onDestroy.call(this);
}
+
});/*
- * 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.
+ * RooJS Library 1.1.1
+ * Copyright(c) 2008-2011 Alan Knowles
*
- * Fork - LGPL
- * <script type="text/javascript">
+ * License - LGPL
*/
+
/**
- * @class Roo.LayoutRegion
- * @extends Roo.BasicLayoutRegion
- * This class represents a region in a layout manager.
- * @cfg {Boolean} collapsible False to disable collapsing (defaults to true)
- * @cfg {Boolean} collapsed True to set the initial display to collapsed (defaults to false)
- * @cfg {Boolean} floatable False to disable floating (defaults to true)
- * @cfg {Object} margins Margins for the element (defaults to {top: 0, left: 0, right:0, bottom: 0})
- * @cfg {Object} cmargins Margins for the element when collapsed (defaults to: north/south {top: 2, left: 0, right:0, bottom: 2} or east/west {top: 0, left: 2, right:2, bottom: 0})
- * @cfg {String} tabPosition (top|bottom) "top" or "bottom" (defaults to "bottom")
- * @cfg {String} collapsedTitle Optional string message to display in the collapsed block of a north or south region
- * @cfg {Boolean} alwaysShowTabs True to always display tabs even when there is only 1 panel (defaults to false)
- * @cfg {Boolean} autoScroll True to enable overflow scrolling (defaults to false)
- * @cfg {Boolean} titlebar True to display a title bar (defaults to true)
- * @cfg {String} title The title for the region (overrides panel titles)
- * @cfg {Boolean} animate True to animate expand/collapse (defaults to false)
- * @cfg {Boolean} autoHide False to disable auto hiding when the mouse leaves the "floated" region (defaults to true)
- * @cfg {Boolean} preservePanels True to preserve removed panels so they can be readded later (defaults to false)
- * @cfg {Boolean} closeOnTab True to place the close icon on the tabs instead of the region titlebar (defaults to false)
- * @cfg {Boolean} hideTabs True to hide the tab strip (defaults to false)
- * @cfg {Boolean} resizeTabs True to enable automatic tab resizing. This will resize the tabs so they are all the same size and fit within
- * the space available, similar to FireFox 1.5 tabs (defaults to false)
- * @cfg {Number} minTabWidth The minimum tab width (defaults to 40)
- * @cfg {Number} preferredTabWidth The preferred tab width (defaults to 150)
- * @cfg {Boolean} showPin True to show a pin button
- * @cfg {Boolean} hidden True to start the region hidden (defaults to false)
- * @cfg {Boolean} hideWhenEmpty True to hide the region when it has no panels
- * @cfg {Boolean} disableTabTips True to disable tab tooltips
- * @cfg {Number} width For East/West panels
- * @cfg {Number} height For North/South panels
- * @cfg {Boolean} split To show the splitter
- * @cfg {Boolean} toolbar xtype configuration for a toolbar - shows on right of tabbar
+ * @class Roo.form.ComboCheck
+ * @extends Roo.form.ComboBox
+ * A combobox for multiple select items.
+ *
+ * FIXME - could do with a reset button..
+ *
+ * @constructor
+ * Create a new ComboCheck
+ * @param {Object} config Configuration options
*/
-Roo.LayoutRegion = function(mgr, config, pos){
- Roo.LayoutRegion.superclass.constructor.call(this, mgr, config, pos, true);
- var dh = Roo.DomHelper;
- /** This region's container element
- * @type Roo.Element */
- this.el = dh.append(mgr.el.dom, {tag: "div", cls: "x-layout-panel x-layout-panel-" + this.position}, true);
- /** This region's title element
- * @type Roo.Element */
-
- this.titleEl = dh.append(this.el.dom, {tag: "div", unselectable: "on", cls: "x-unselectable x-layout-panel-hd x-layout-title-"+this.position, children:[
- {tag: "span", cls: "x-unselectable x-layout-panel-hd-text", unselectable: "on", html: " "},
- {tag: "div", cls: "x-unselectable x-layout-panel-hd-tools", unselectable: "on"}
- ]}, true);
- this.titleEl.enableDisplayMode();
- /** This region's title text element
- * @type HTMLElement */
- this.titleTextEl = this.titleEl.dom.firstChild;
- this.tools = Roo.get(this.titleEl.dom.childNodes[1], true);
- this.closeBtn = this.createTool(this.tools.dom, "x-layout-close");
- this.closeBtn.enableDisplayMode();
- this.closeBtn.on("click", this.closeClicked, this);
- this.closeBtn.hide();
-
- this.createBody(config);
- this.visible = true;
- this.collapsed = false;
-
- if(config.hideWhenEmpty){
- this.hide();
- this.on("paneladded", this.validateVisibility, this);
- this.on("panelremoved", this.validateVisibility, this);
- }
- this.applyConfig(config);
+Roo.form.ComboCheck = 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.ComboCheck : missing value for: " + e;
+ }
+ });
+
+
};
-Roo.extend(Roo.LayoutRegion, Roo.BasicLayoutRegion, {
-
- createBody : function(){
- /** This region's body element
- * @type Roo.Element */
- this.bodyEl = this.el.createChild({tag: "div", cls: "x-layout-panel-body"});
- },
-
- applyConfig : function(c){
- if(c.collapsible && this.position != "center" && !this.collapsedEl){
- var dh = Roo.DomHelper;
- if(c.titlebar !== false){
- this.collapseBtn = this.createTool(this.tools.dom, "x-layout-collapse-"+this.position);
- this.collapseBtn.on("click", this.collapse, this);
- this.collapseBtn.enableDisplayMode();
-
- if(c.showPin === true || this.showPin){
- this.stickBtn = this.createTool(this.tools.dom, "x-layout-stick");
- this.stickBtn.enableDisplayMode();
- this.stickBtn.on("click", this.expand, this);
- this.stickBtn.hide();
- }
- }
- /** This region's collapsed element
- * @type Roo.Element */
- this.collapsedEl = dh.append(this.mgr.el.dom, {cls: "x-layout-collapsed x-layout-collapsed-"+this.position, children:[
- {cls: "x-layout-collapsed-tools", children:[{cls: "x-layout-ctools-inner"}]}
- ]}, true);
- if(c.floatable !== false){
- this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
- this.collapsedEl.on("click", this.collapseClick, this);
- }
+Roo.extend(Roo.form.ComboCheck, Roo.form.ComboBox, {
+
+
+ editable : false,
+
+ selectedClass: 'x-menu-item-checked',
+
+ // private
+ onRender : function(ct, position){
+ var _t = this;
+
+
+
+ if(!this.tpl){
+ var cls = 'x-combo-list';
- if(c.collapsedTitle && (this.position == "north" || this.position== "south")) {
- this.collapsedTitleTextEl = dh.append(this.collapsedEl.dom, {tag: "div", cls: "x-unselectable x-layout-panel-hd-text",
- id: "message", unselectable: "on", style:{"float":"left"}});
- this.collapsedTitleTextEl.innerHTML = c.collapsedTitle;
- }
- this.expandBtn = this.createTool(this.collapsedEl.dom.firstChild.firstChild, "x-layout-expand-"+this.position);
- this.expandBtn.on("click", this.expand, this);
- }
- if(this.collapseBtn){
- this.collapseBtn.setVisible(c.collapsible == true);
- }
- this.cmargins = c.cmargins || this.cmargins ||
- (this.position == "west" || this.position == "east" ?
- {top: 0, left: 2, right:2, bottom: 0} :
- {top: 2, left: 0, right:0, bottom: 2});
- this.margins = c.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
- this.bottomTabs = c.tabPosition != "top";
- this.autoScroll = c.autoScroll || false;
- if(this.autoScroll){
- this.bodyEl.setStyle("overflow", "auto");
- }else{
- this.bodyEl.setStyle("overflow", "hidden");
+
+ this.tpl = new Roo.Template({
+ html : '<div class="'+cls+'-item x-menu-check-item">' +
+ '<img class="x-menu-item-icon" style="margin: 0px;" src="' + Roo.BLANK_IMAGE_URL + '">' +
+ '<span>{' + this.displayField + '}</span>' +
+ '</div>'
+
+ });
}
- //if(c.titlebar !== false){
- if((!c.titlebar && !c.title) || c.titlebar === false){
- this.titleEl.hide();
- }else{
- this.titleEl.show();
- if(c.title){
- this.titleTextEl.innerHTML = c.title;
- }
+
+
+ Roo.form.ComboCheck.superclass.onRender.call(this, ct, position);
+ this.view.singleSelect = false;
+ this.view.multiSelect = true;
+ this.view.toggleSelect = true;
+ this.pageTb.add(new Roo.Toolbar.Fill(), {
+
+ text: 'Done',
+ handler: function()
+ {
+ _t.collapse();
}
- //}
- this.duration = c.duration || .30;
- this.slideDuration = c.slideDuration || .45;
- this.config = c;
- if(c.collapsed){
- this.collapse(true);
- }
- if(c.hidden){
- this.hide();
- }
+ });
},
- /**
- * Returns true if this region is currently visible.
- * @return {Boolean}
- */
- isVisible : function(){
- return this.visible;
+
+ onViewOver : function(e, t){
+ // do nothing...
+ return;
+
},
-
- /**
- * Updates the title for collapsed north/south regions (used with {@link #collapsedTitle} config option)
- * @param {String} title (optional) The title text (accepts HTML markup, defaults to the numeric character reference for a non-breaking space, "&#160;")
- */
- setCollapsedTitle : function(title){
- title = title || " ";
- if(this.collapsedTitleTextEl){
- this.collapsedTitleTextEl.innerHTML = title;
- }
+
+ onViewClick : function(doFocus,index){
+ return;
+
},
-
- getBox : function(){
- var b;
- if(!this.collapsed){
- b = this.el.getBox(false, true);
- }else{
- b = this.collapsedEl.getBox(false, true);
- }
- return b;
+ select: function () {
+ //Roo.log("SELECT CALLED");
},
-
- getMargins : function(){
- return this.collapsed ? this.cmargins : this.margins;
+
+ selectByValue : function(xv, scrollIntoView){
+ var ar = this.getValueArray();
+ var sels = [];
+
+ Roo.each(ar, function(v) {
+ if(v === undefined || v === null){
+ return;
+ }
+ var r = this.findRecord(this.valueField, v);
+ if(r){
+ sels.push(this.store.indexOf(r))
+
+ }
+ },this);
+ this.view.select(sels);
+ return false;
},
-
- highlight : function(){
- this.el.addClass("x-layout-panel-dragover");
+
+
+
+ onSelect : function(record, index){
+ // Roo.log("onselect Called");
+ // this is only called by the clear button now..
+ this.view.clearSelections();
+ this.setValue('[]');
+ if (this.value != this.valueBefore) {
+ this.fireEvent('change', this, this.value, this.valueBefore);
+ this.valueBefore = this.value;
+ }
},
-
- unhighlight : function(){
- this.el.removeClass("x-layout-panel-dragover");
+ getValueArray : function()
+ {
+ var ar = [] ;
+
+ try {
+ //Roo.log(this.value);
+ if (typeof(this.value) == 'undefined') {
+ return [];
+ }
+ var ar = Roo.decode(this.value);
+ return ar instanceof Array ? ar : []; //?? valid?
+
+ } catch(e) {
+ Roo.log(e + "\nRoo.form.ComboCheck:getValueArray invalid data:" + this.getValue());
+ return [];
+ }
+
},
+ expand : function ()
+ {
+
+ Roo.form.ComboCheck.superclass.expand.call(this);
+ this.valueBefore = typeof(this.value) == 'undefined' ? '' : this.value;
+ //this.valueBefore = typeof(this.valueBefore) == 'undefined' ? '' : this.valueBefore;
+
- updateBox : function(box){
- this.box = box;
- if(!this.collapsed){
- this.el.dom.style.left = box.x + "px";
- this.el.dom.style.top = box.y + "px";
- this.updateBody(box.width, box.height);
- }else{
- this.collapsedEl.dom.style.left = box.x + "px";
- this.collapsedEl.dom.style.top = box.y + "px";
- this.collapsedEl.setSize(box.width, box.height);
- }
- if(this.tabs){
- this.tabs.autoSizeTabs();
- }
},
+
+ collapse : function(){
+ Roo.form.ComboCheck.superclass.collapse.call(this);
+ var sl = this.view.getSelectedIndexes();
+ var st = this.store;
+ var nv = [];
+ var tv = [];
+ var r;
+ Roo.each(sl, function(i) {
+ r = st.getAt(i);
+ nv.push(r.get(this.valueField));
+ },this);
+ this.setValue(Roo.encode(nv));
+ if (this.value != this.valueBefore) {
- updateBody : function(w, h){
- if(w !== null){
- this.el.setWidth(w);
- w -= this.el.getBorderWidth("rl");
- if(this.config.adjustments){
- w += this.config.adjustments[0];
- }
- }
- if(h !== null){
- this.el.setHeight(h);
- h = this.titleEl && this.titleEl.isDisplayed() ? h - (this.titleEl.getHeight()||0) : h;
- h -= this.el.getBorderWidth("tb");
- if(this.config.adjustments){
- h += this.config.adjustments[1];
- }
- this.bodyEl.setHeight(h);
- if(this.tabs){
- h = this.tabs.syncHeight(h);
- }
- }
- if(this.panelSize){
- w = w !== null ? w : this.panelSize.width;
- h = h !== null ? h : this.panelSize.height;
- }
- if(this.activePanel){
- var el = this.activePanel.getEl();
- w = w !== null ? w : el.getWidth();
- h = h !== null ? h : el.getHeight();
- this.panelSize = {width: w, height: h};
- this.activePanel.setSize(w, h);
- }
- if(Roo.isIE && this.tabs){
- this.tabs.el.repaint();
+ this.fireEvent('change', this, this.value, this.valueBefore);
+ this.valueBefore = this.value;
}
+
},
+
+ setValue : function(v){
+ // Roo.log(v);
+ this.value = v;
+
+ var vals = this.getValueArray();
+ var tv = [];
+ Roo.each(vals, function(k) {
+ var r = this.findRecord(this.valueField, k);
+ if(r){
+ tv.push(r.data[this.displayField]);
+ }else if(this.valueNotFoundText !== undefined){
+ tv.push( this.valueNotFoundText );
+ }
+ },this);
+ // Roo.log(tv);
+
+ Roo.form.ComboBox.superclass.setValue.call(this, tv.join(', '));
+ this.hiddenField.value = v;
+ this.value = v;
+ }
+
+});/*
+ * 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">
+ */
+
+/**
+ * @class Roo.form.Signature
+ * @extends Roo.form.Field
+ * Signature field.
+ * @constructor
+ *
+ * @param {Object} config Configuration options
+ */
+
+Roo.form.Signature = function(config){
+ Roo.form.Signature.superclass.constructor.call(this, config);
+
+ this.addEvents({// not in used??
+ /**
+ * @event confirm
+ * Fires when the 'confirm' icon is pressed (add a listener to enable add button)
+ * @param {Roo.form.Signature} combo This combo box
+ */
+ 'confirm' : true,
+ /**
+ * @event reset
+ * Fires when the 'edit' icon is pressed (add a listener to enable add button)
+ * @param {Roo.form.ComboBox} combo This combo box
+ * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
+ */
+ 'reset' : true
+ });
+};
+Roo.extend(Roo.form.Signature, Roo.form.Field, {
/**
- * Returns the container element for this region.
- * @return {Roo.Element}
+ * @cfg {Object} labels Label to use when rendering a form.
+ * defaults to
+ * labels : {
+ * clear : "Clear",
+ * confirm : "Confirm"
+ * }
*/
- getEl : function(){
- return this.el;
+ labels : {
+ clear : "Clear",
+ confirm : "Confirm"
},
-
/**
- * Hides this region.
+ * @cfg {Number} width The signature panel width (defaults to 300)
*/
- hide : function(){
- if(!this.collapsed){
- this.el.dom.style.left = "-2000px";
- this.el.hide();
- }else{
- this.collapsedEl.dom.style.left = "-2000px";
- this.collapsedEl.hide();
- }
- this.visible = false;
- this.fireEvent("visibilitychange", this, false);
- },
-
+ width: 300,
/**
- * Shows this region if it was previously hidden.
+ * @cfg {Number} height The signature panel height (defaults to 100)
*/
- show : function(){
- if(!this.collapsed){
- this.el.show();
- }else{
- this.collapsedEl.show();
- }
- this.visible = true;
- this.fireEvent("visibilitychange", this, true);
- },
-
- closeClicked : function(){
- if(this.activePanel){
- this.remove(this.activePanel);
- }
- },
-
- collapseClick : function(e){
- if(this.isSlid){
- e.stopPropagation();
- this.slideIn();
- }else{
- e.stopPropagation();
- this.slideOut();
- }
- },
-
+ height : 100,
/**
- * Collapses this region.
- * @param {Boolean} skipAnim (optional) true to collapse the element without animation (if animate is true)
+ * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to false)
*/
- collapse : function(skipAnim, skipCheck){
- if(this.collapsed) {
- return;
- }
-
- if(skipCheck || this.fireEvent("beforecollapse", this) != false){
-
- this.collapsed = true;
- if(this.split){
- this.split.el.hide();
- }
- if(this.config.animate && skipAnim !== true){
- this.fireEvent("invalidated", this);
- this.animateCollapse();
- }else{
- this.el.setLocation(-20000,-20000);
- this.el.hide();
- this.collapsedEl.show();
- this.fireEvent("collapsed", this);
- this.fireEvent("invalidated", this);
- }
- }
-
+ allowBlank : false,
+
+ //private
+ // {Object} signPanel The signature SVG panel element (defaults to {})
+ signPanel : {},
+ // {Boolean} isMouseDown False to validate that the mouse down event (defaults to false)
+ isMouseDown : false,
+ // {Boolean} isConfirmed validate the signature is confirmed or not for submitting form (defaults to false)
+ isConfirmed : false,
+ // {String} signatureTmp SVG mapping string (defaults to empty string)
+ signatureTmp : '',
+
+
+ defaultAutoCreate : { // modified by initCompnoent..
+ tag: "input",
+ type:"hidden"
},
- animateCollapse : function(){
- // overridden
- },
-
- /**
- * Expands this region if it was previously collapsed.
- * @param {Roo.EventObject} e The event that triggered the expand (or null if calling manually)
- * @param {Boolean} skipAnim (optional) true to expand the element without animation (if animate is true)
- */
- expand : function(e, skipAnim){
- if(e) {
- e.stopPropagation();
- }
- if(!this.collapsed || this.el.hasActiveFx()) {
- return;
- }
- if(this.isSlid){
- this.afterSlideIn();
- skipAnim = true;
- }
- this.collapsed = false;
- if(this.config.animate && skipAnim !== true){
- this.animateExpand();
- }else{
- this.el.show();
- if(this.split){
- this.split.el.show();
- }
- this.collapsedEl.setLocation(-2000,-2000);
- this.collapsedEl.hide();
- this.fireEvent("invalidated", this);
- this.fireEvent("expanded", this);
- }
- },
-
- animateExpand : function(){
- // overridden
- },
-
- initTabs : function()
- {
- this.bodyEl.setStyle("overflow", "hidden");
- var ts = new Roo.TabPanel(
- this.bodyEl.dom,
+ // private
+ onRender : function(ct, position){
+
+ Roo.form.Signature.superclass.onRender.call(this, ct, position);
+
+ this.wrap = this.el.wrap({
+ cls:'x-form-signature-wrap', style : 'width: ' + this.width + 'px', cn:{cls:'x-form-signature'}
+ });
+
+ this.createToolbar(this);
+ this.signPanel = this.wrap.createChild({
+ tag: 'div',
+ style: 'width: ' + this.width + 'px; height: ' + this.height + 'px; border: 0;'
+ }, this.el
+ );
+
+ this.svgID = Roo.id();
+ this.svgEl = this.signPanel.createChild({
+ xmlns : 'http://www.w3.org/2000/svg',
+ tag : 'svg',
+ id : this.svgID + "-svg",
+ width: this.width,
+ height: this.height,
+ viewBox: '0 0 '+this.width+' '+this.height,
+ cn : [
{
- tabPosition: this.bottomTabs ? 'bottom' : 'top',
- disableTooltips: this.config.disableTabTips,
- toolbar : this.config.toolbar
+ tag: "rect",
+ id: this.svgID + "-svg-r",
+ width: this.width,
+ height: this.height,
+ fill: "#ffa"
+ },
+ {
+ tag: "line",
+ id: this.svgID + "-svg-l",
+ x1: "0", // start
+ y1: (this.height*0.8), // start set the line in 80% of height
+ x2: this.width, // end
+ y2: (this.height*0.8), // end set the line in 80% of height
+ 'stroke': "#666",
+ 'stroke-width': "1",
+ 'stroke-dasharray': "3",
+ 'shape-rendering': "crispEdges",
+ 'pointer-events': "none"
+ },
+ {
+ tag: "path",
+ id: this.svgID + "-svg-p",
+ 'stroke': "navy",
+ 'stroke-width': "3",
+ 'fill': "none",
+ 'pointer-events': 'none'
}
- );
- if(this.config.hideTabs){
- ts.stripWrap.setDisplayed(false);
- }
- this.tabs = ts;
- ts.resizeTabs = this.config.resizeTabs === true;
- ts.minTabWidth = this.config.minTabWidth || 40;
- ts.maxTabWidth = this.config.maxTabWidth || 250;
- ts.preferredTabWidth = this.config.preferredTabWidth || 150;
- ts.monitorResize = false;
- ts.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
- ts.bodyEl.addClass('x-layout-tabs-body');
- this.panels.each(this.initPanelAsTab, this);
+ ]
+ });
+ this.createSVG();
+ this.svgBox = this.svgEl.dom.getScreenCTM();
},
+ createSVG : function(){
+ var svg = this.signPanel;
+ var r = svg.select('#'+ this.svgID + '-svg-r', true).first().dom;
+ var t = this;
- initPanelAsTab : function(panel){
- var ti = this.tabs.addTab(panel.getEl().id, panel.getTitle(), null,
- this.config.closeOnTab && panel.isClosable());
- if(panel.tabTip !== undefined){
- ti.setTooltip(panel.tabTip);
- }
- ti.on("activate", function(){
- this.setActivePanel(panel);
- }, this);
- if(this.config.closeOnTab){
- ti.on("beforeclose", function(t, e){
- e.cancel = true;
- this.remove(panel);
- }, this);
- }
- return ti;
+ r.addEventListener('mousedown', function(e) { return t.down(e); }, false);
+ r.addEventListener('mousemove', function(e) { return t.move(e); }, false);
+ r.addEventListener('mouseup', function(e) { return t.up(e); }, false);
+ r.addEventListener('mouseout', function(e) { return t.up(e); }, false);
+ r.addEventListener('touchstart', function(e) { return t.down(e); }, false);
+ r.addEventListener('touchmove', function(e) { return t.move(e); }, false);
+ r.addEventListener('touchend', function(e) { return t.up(e); }, false);
+
},
-
- updatePanelTitle : function(panel, title){
- if(this.activePanel == panel){
- this.updateTitle(title);
- }
- if(this.tabs){
- var ti = this.tabs.getTab(panel.getEl().id);
- ti.setText(title);
- if(panel.tabTip !== undefined){
- ti.setTooltip(panel.tabTip);
- }
- }
+ isTouchEvent : function(e){
+ return e.type.match(/^touch/);
},
-
- updateTitle : function(title){
- if(this.titleTextEl && !this.config.title){
- this.titleTextEl.innerHTML = (typeof title != "undefined" && title.length > 0 ? title : " ");
+ getCoords : function (e) {
+ var pt = this.svgEl.dom.createSVGPoint();
+ pt.x = e.clientX;
+ pt.y = e.clientY;
+ if (this.isTouchEvent(e)) {
+ pt.x = e.targetTouches[0].clientX;
+ pt.y = e.targetTouches[0].clientY;
}
+ var a = this.svgEl.dom.getScreenCTM();
+ var b = a.inverse();
+ var mx = pt.matrixTransform(b);
+ return mx.x + ',' + mx.y;
},
-
- setActivePanel : function(panel){
- panel = this.getPanel(panel);
- if(this.activePanel && this.activePanel != panel){
- this.activePanel.setActiveState(false);
- }
- this.activePanel = panel;
- panel.setActiveState(true);
- if(this.panelSize){
- panel.setSize(this.panelSize.width, this.panelSize.height);
+ //mouse event headler
+ down : function (e) {
+ this.signatureTmp += 'M' + this.getCoords(e) + ' ';
+ this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr('d', this.signatureTmp);
+
+ this.isMouseDown = true;
+
+ e.preventDefault();
+ },
+ move : function (e) {
+ if (this.isMouseDown) {
+ this.signatureTmp += 'L' + this.getCoords(e) + ' ';
+ this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', this.signatureTmp);
}
- if(this.closeBtn){
- this.closeBtn.setVisible(!this.config.closeOnTab && !this.isSlid && panel.isClosable());
+
+ e.preventDefault();
+ },
+ up : function (e) {
+ this.isMouseDown = false;
+ var sp = this.signatureTmp.split(' ');
+
+ if(sp.length > 1){
+ if(!sp[sp.length-2].match(/^L/)){
+ sp.pop();
+ sp.pop();
+ sp.push("");
+ this.signatureTmp = sp.join(" ");
+ }
}
- this.updateTitle(panel.getTitle());
- if(this.tabs){
- this.fireEvent("invalidated", this);
+ if(this.getValue() != this.signatureTmp){
+ this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
+ this.isConfirmed = false;
}
- this.fireEvent("panelactivated", this, panel);
+ e.preventDefault();
},
-
+
/**
- * Shows the specified panel.
- * @param {Number/String/ContentPanel} panelId The panel's index, id or the panel itself
- * @return {Roo.ContentPanel} The shown panel, or null if a panel could not be found from panelId
+ * Protected method that will not generally be called directly. It
+ * is called when the editor creates its toolbar. Override this method if you need to
+ * add custom toolbar buttons.
+ * @param {HtmlEditor} editor
*/
- showPanel : function(panel)
- {
- panel = this.getPanel(panel);
- if(panel){
- if(this.tabs){
- var tab = this.tabs.getTab(panel.getEl().id);
- if(tab.isHidden()){
- this.tabs.unhideTab(tab.id);
- }
- tab.activate();
- }else{
- this.setActivePanel(panel);
- }
+ createToolbar : function(editor){
+ function btn(id, toggle, handler){
+ var xid = fid + '-'+ id ;
+ return {
+ id : xid,
+ cmd : id,
+ cls : 'x-btn-icon x-edit-'+id,
+ enableToggle:toggle !== false,
+ scope: editor, // was editor...
+ handler:handler||editor.relayBtnCmd,
+ clickEvent:'mousedown',
+ tooltip: etb.buttonTips[id] || undefined, ///tips ???
+ tabIndex:-1
+ };
}
- return panel;
+
+
+ var tb = new Roo.Toolbar(editor.wrap.dom.firstChild);
+ this.tb = tb;
+ this.tb.add(
+ {
+ cls : ' x-signature-btn x-signature-'+id,
+ scope: editor, // was editor...
+ handler: this.reset,
+ clickEvent:'mousedown',
+ text: this.labels.clear
+ },
+ {
+ xtype : 'Fill',
+ xns: Roo.Toolbar
+ },
+ {
+ cls : ' x-signature-btn x-signature-'+id,
+ scope: editor, // was editor...
+ handler: this.confirmHandler,
+ clickEvent:'mousedown',
+ text: this.labels.confirm
+ }
+ );
+
},
-
+ //public
/**
- * Get the active panel for this region.
- * @return {Roo.ContentPanel} The active panel or null
+ * when user is clicked confirm then show this image.....
+ *
+ * @return {String} Image Data URI
*/
- getActivePanel : function(){
- return this.activePanel;
- },
-
- validateVisibility : function(){
- if(this.panels.getCount() < 1){
- this.updateTitle(" ");
- this.closeBtn.hide();
- this.hide();
- }else{
- if(!this.isVisible()){
- this.show();
- }
- }
+ getImageDataURI : function(){
+ var svg = this.svgEl.dom.parentNode.innerHTML;
+ var src = 'data:image/svg+xml;base64,'+window.btoa(svg);
+ return src;
},
-
/**
- * Adds the passed ContentPanel(s) to this region.
- * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
- * @return {Roo.ContentPanel} The panel added (if only one was added; null otherwise)
+ *
+ * @return {Boolean} this.isConfirmed
*/
- add : function(panel){
- if(arguments.length > 1){
- for(var i = 0, len = arguments.length; i < len; i++) {
- this.add(arguments[i]);
- }
- return null;
- }
- if(this.hasPanel(panel)){
- this.showPanel(panel);
- return panel;
- }
- panel.setRegion(this);
- this.panels.add(panel);
- if(this.panels.getCount() == 1 && !this.config.alwaysShowTabs){
- this.bodyEl.dom.appendChild(panel.getEl().dom);
- if(panel.background !== true){
- this.setActivePanel(panel);
- }
- this.fireEvent("paneladded", this, panel);
- return panel;
- }
- if(!this.tabs){
- this.initTabs();
- }else{
- this.initPanelAsTab(panel);
- }
- if(panel.background !== true){
- this.tabs.activate(panel.getEl().id);
- }
- this.fireEvent("paneladded", this, panel);
- return panel;
+ getConfirmed : function(){
+ return this.isConfirmed;
},
-
/**
- * Hides the tab for the specified panel.
- * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
+ *
+ * @return {Number} this.width
*/
- hidePanel : function(panel){
- if(this.tabs && (panel = this.getPanel(panel))){
- this.tabs.hideTab(panel.getEl().id);
- }
+ getWidth : function(){
+ return this.width;
},
-
/**
- * Unhides the tab for a previously hidden panel.
- * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
+ *
+ * @return {Number} this.height
*/
- unhidePanel : function(panel){
- if(this.tabs && (panel = this.getPanel(panel))){
- this.tabs.unhideTab(panel.getEl().id);
- }
+ getHeight : function(){
+ return this.height;
},
-
- clearPanels : function(){
- while(this.panels.getCount() > 0){
- this.remove(this.panels.first());
- }
+ // private
+ getSignature : function(){
+ return this.signatureTmp;
},
-
- /**
- * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
- * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
- * @param {Boolean} preservePanel Overrides the config preservePanel option
- * @return {Roo.ContentPanel} The panel that was removed
- */
- remove : function(panel, preservePanel){
- panel = this.getPanel(panel);
- if(!panel){
- return null;
- }
- var e = {};
- this.fireEvent("beforeremove", this, panel, e);
- if(e.cancel === true){
- return null;
- }
- preservePanel = (typeof preservePanel != "undefined" ? preservePanel : (this.config.preservePanels === true || panel.preserve === true));
- var panelId = panel.getId();
- this.panels.removeKey(panelId);
- if(preservePanel){
- document.body.appendChild(panel.getEl().dom);
- }
- if(this.tabs){
- this.tabs.removeTab(panel.getEl().id);
- }else if (!preservePanel){
- this.bodyEl.dom.removeChild(panel.getEl().dom);
- }
- if(this.panels.getCount() == 1 && this.tabs && !this.config.alwaysShowTabs){
- var p = this.panels.first();
- var tempEl = document.createElement("div"); // temp holder to keep IE from deleting the node
- tempEl.appendChild(p.getEl().dom);
- this.bodyEl.update("");
- this.bodyEl.dom.appendChild(p.getEl().dom);
- tempEl = null;
- this.updateTitle(p.getTitle());
- this.tabs = null;
- this.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
- this.setActivePanel(p);
+ // private
+ reset : function(){
+ this.signatureTmp = '';
+ this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
+ this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', '');
+ this.isConfirmed = false;
+ Roo.form.Signature.superclass.reset.call(this);
+ },
+ setSignature : function(s){
+ this.signatureTmp = s;
+ this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
+ this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', s);
+ this.setValue(s);
+ this.isConfirmed = false;
+ Roo.form.Signature.superclass.reset.call(this);
+ },
+ test : function(){
+// Roo.log(this.signPanel.dom.contentWindow.up())
+ },
+ //private
+ setConfirmed : function(){
+
+
+
+// Roo.log(Roo.get(this.signPanel.dom.contentWindow.r).attr('fill', '#cfc'));
+ },
+ // private
+ confirmHandler : function(){
+ if(!this.getSignature()){
+ return;
}
- panel.setRegion(null);
- if(this.activePanel == panel){
- this.activePanel = null;
+
+ this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#cfc');
+ this.setValue(this.getSignature());
+ this.isConfirmed = true;
+
+ this.fireEvent('confirm', this);
+ },
+ // private
+ // Subclasses should provide the validation implementation by overriding this
+ validateValue : function(value){
+ if(this.allowBlank){
+ return true;
}
- if(this.config.autoDestroy !== false && preservePanel !== true){
- try{panel.destroy();}catch(e){}
+
+ if(this.isConfirmed){
+ return true;
}
- this.fireEvent("panelremoved", this, panel);
- return panel;
- },
-
- /**
- * Returns the TabPanel component used by this region
- * @return {Roo.TabPanel}
- */
- getTabs : function(){
- return this.tabs;
- },
-
- createTool : function(parentEl, className){
- var btn = Roo.DomHelper.append(parentEl, {tag: "div", cls: "x-layout-tools-button",
- children: [{tag: "div", cls: "x-layout-tools-button-inner " + className, html: " "}]}, true);
- btn.addClassOnOver("x-layout-tools-button-over");
- return btn;
+ return false;
}
});/*
* Based on:
*/
-
/**
- * @class Roo.SplitLayoutRegion
- * @extends Roo.LayoutRegion
- * Adds a splitbar and other (private) useful functionality to a {@link Roo.LayoutRegion}.
+ * @class Roo.form.ComboBox
+ * @extends Roo.form.TriggerField
+ * A combobox control with support for autocomplete, remote-loading, paging and many other features.
+ * @constructor
+ * Create a new ComboBox.
+ * @param {Object} config Configuration options
*/
-Roo.SplitLayoutRegion = function(mgr, config, pos, cursor){
- this.cursor = cursor;
- Roo.SplitLayoutRegion.superclass.constructor.call(this, mgr, config, pos);
+Roo.form.Select = function(config){
+ Roo.form.Select.superclass.constructor.call(this, config);
+
};
-Roo.extend(Roo.SplitLayoutRegion, Roo.LayoutRegion, {
- splitTip : "Drag to resize.",
- collapsibleSplitTip : "Drag to resize. Double click to hide.",
- useSplitTips : false,
-
- applyConfig : function(config){
- Roo.SplitLayoutRegion.superclass.applyConfig.call(this, config);
- if(config.split){
- if(!this.split){
- var splitEl = Roo.DomHelper.append(this.mgr.el.dom,
- {tag: "div", id: this.el.id + "-split", cls: "x-layout-split x-layout-split-"+this.position, html: " "});
- /** The SplitBar for this region
- * @type Roo.SplitBar */
- this.split = new Roo.SplitBar(splitEl, this.el, this.orientation);
- this.split.on("moved", this.onSplitMove, this);
- this.split.useShim = config.useShim === true;
- this.split.getMaximumSize = this[this.position == 'north' || this.position == 'south' ? 'getVMaxSize' : 'getHMaxSize'].createDelegate(this);
- if(this.useSplitTips){
- this.split.el.dom.title = config.collapsible ? this.collapsibleSplitTip : this.splitTip;
- }
- if(config.collapsible){
- this.split.el.on("dblclick", this.collapse, this);
- }
- }
- if(typeof config.minSize != "undefined"){
- this.split.minSize = config.minSize;
- }
- if(typeof config.maxSize != "undefined"){
- this.split.maxSize = config.maxSize;
- }
- if(config.hideWhenEmpty || config.hidden || config.collapsed){
- this.hideSplitter();
- }
+Roo.extend(Roo.form.Select , Roo.form.ComboBox, {
+ /**
+ * @cfg {String/HTMLElement/Element} transform The id, DOM node or element of an existing select to convert to a ComboBox
+ */
+ /**
+ * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
+ * rendering into an Roo.Editor, defaults to false)
+ */
+ /**
+ * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
+ * {tag: "input", type: "text", size: "24", autocomplete: "off"})
+ */
+ /**
+ * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
+ */
+ /**
+ * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
+ * the dropdown list (defaults to undefined, with no header element)
+ */
+
+ /**
+ * @cfg {String/Roo.Template} tpl The template to use to render the output
+ */
+
+ // private
+ defaultAutoCreate : {tag: "select" },
+ /**
+ * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
+ */
+ listWidth: undefined,
+ /**
+ * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
+ * mode = 'remote' or 'text' if mode = 'local')
+ */
+ displayField: undefined,
+ /**
+ * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
+ * mode = 'remote' or 'value' if mode = 'local').
+ * Note: use of a valueField requires the user make a selection
+ * in order for a value to be mapped.
+ */
+ valueField: undefined,
+
+
+ /**
+ * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
+ * field's data value (defaults to the underlying DOM element's name)
+ */
+ hiddenName: undefined,
+ /**
+ * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
+ */
+ listClass: '',
+ /**
+ * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
+ */
+ selectedClass: 'x-combo-selected',
+ /**
+ * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always get the
+ * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified (defaults to 'x-form-arrow-trigger'
+ * which displays a downward arrow icon).
+ */
+ triggerClass : 'x-form-arrow-trigger',
+ /**
+ * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
+ */
+ shadow:'sides',
+ /**
+ * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
+ * anchor positions (defaults to 'tl-bl')
+ */
+ listAlign: 'tl-bl?',
+ /**
+ * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
+ */
+ maxHeight: 300,
+ /**
+ * @cfg {String} triggerAction The action to execute when the trigger field is activated. Use 'all' to run the
+ * query specified by the allQuery config option (defaults to 'query')
+ */
+ triggerAction: 'query',
+ /**
+ * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
+ * (defaults to 4, does not apply if editable = false)
+ */
+ minChars : 4,
+ /**
+ * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
+ * delay (typeAheadDelay) if it matches a known value (defaults to false)
+ */
+ typeAhead: false,
+ /**
+ * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
+ * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
+ */
+ queryDelay: 500,
+ /**
+ * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
+ * filter queries will execute with page start and limit parameters. Only applies when mode = 'remote' (defaults to 0)
+ */
+ pageSize: 0,
+ /**
+ * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus. Only applies
+ * when editable = true (defaults to false)
+ */
+ selectOnFocus:false,
+ /**
+ * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
+ */
+ queryParam: 'query',
+ /**
+ * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
+ * when mode = 'remote' (defaults to 'Loading...')
+ */
+ loadingText: 'Loading...',
+ /**
+ * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
+ */
+ resizable: false,
+ /**
+ * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
+ */
+ handleHeight : 8,
+ /**
+ * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
+ * traditional select (defaults to true)
+ */
+ editable: true,
+ /**
+ * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
+ */
+ allQuery: '',
+ /**
+ * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
+ */
+ mode: 'remote',
+ /**
+ * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
+ * listWidth has a higher value)
+ */
+ minListWidth : 70,
+ /**
+ * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
+ * allow the user to set arbitrary text into the field (defaults to false)
+ */
+ forceSelection:false,
+ /**
+ * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
+ * if typeAhead = true (defaults to 250)
+ */
+ typeAheadDelay : 250,
+ /**
+ * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
+ * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
+ */
+ valueNotFoundText : undefined,
+
+ /**
+ * @cfg {String} defaultValue The value displayed after loading the store.
+ */
+ defaultValue: '',
+
+ /**
+ * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
+ */
+ blockFocus : false,
+
+ /**
+ * @cfg {Boolean} disableClear Disable showing of clear button.
+ */
+ disableClear : false,
+ /**
+ * @cfg {Boolean} alwaysQuery Disable caching of results, and always send query
+ */
+ alwaysQuery : false,
+
+ //private
+ addicon : false,
+ editicon: false,
+
+ // element that contains real text value.. (when hidden is used..)
+
+ // private
+ onRender : function(ct, position){
+ Roo.form.Field.prototype.onRender.call(this, ct, position);
+
+ if(this.store){
+ this.store.on('beforeload', this.onBeforeLoad, this);
+ this.store.on('load', this.onLoad, this);
+ this.store.on('loadexception', this.onLoadException, this);
+ this.store.load({});
}
+
+
+
},
- getHMaxSize : function(){
- var cmax = this.config.maxSize || 10000;
- var center = this.mgr.getRegion("center");
- return Math.min(cmax, (this.el.getWidth()+center.getEl().getWidth())-center.getMinWidth());
+ // private
+ initEvents : function(){
+ //Roo.form.ComboBox.superclass.initEvents.call(this);
+
},
- getVMaxSize : function(){
- var cmax = this.config.maxSize || 10000;
- var center = this.mgr.getRegion("center");
- return Math.min(cmax, (this.el.getHeight()+center.getEl().getHeight())-center.getMinHeight());
+ onDestroy : function(){
+
+ if(this.store){
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('loadexception', this.onLoadException, this);
+ }
+ //Roo.form.ComboBox.superclass.onDestroy.call(this);
},
- onSplitMove : function(split, newSize){
- this.fireEvent("resized", this, newSize);
+ // private
+ fireKey : function(e){
+ if(e.isNavKeyPress() && !this.list.isVisible()){
+ this.fireEvent("specialkey", this, e);
+ }
},
+
+ // private
+ onResize: function(w, h){
+
+ return;
- /**
- * Returns the {@link Roo.SplitBar} for this region.
- * @return {Roo.SplitBar}
+
+ },
+
+ /**
+ * Allow or prevent the user from directly editing the field text. If false is passed,
+ * the user will only be able to select from the items defined in the dropdown list. This method
+ * is the runtime equivalent of setting the 'editable' config option at config time.
+ * @param {Boolean} value True to allow the user to directly edit the field text
*/
- getSplitBar : function(){
- return this.split;
+ setEditable : function(value){
+
},
+
+ // private
+ onBeforeLoad : function(){
+
+ Roo.log("Select before load");
+ return;
- hide : function(){
- this.hideSplitter();
- Roo.SplitLayoutRegion.superclass.hide.call(this);
+ this.innerList.update(this.loadingText ?
+ '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
+ //this.restrictHeight();
+ this.selectedIndex = -1;
},
- hideSplitter : function(){
- if(this.split){
- this.split.el.setLocation(-2000,-2000);
- this.split.el.hide();
- }
- },
+ // private
+ onLoad : function(){
- show : function(){
- if(this.split){
- this.split.el.show();
- }
- Roo.SplitLayoutRegion.superclass.show.call(this);
- },
- beforeSlide: function(){
- if(Roo.isGecko){// firefox overflow auto bug workaround
- this.bodyEl.clip();
- if(this.tabs) {
- this.tabs.bodyEl.clip();
- }
- if(this.activePanel){
- this.activePanel.getEl().clip();
-
- if(this.activePanel.beforeSlide){
- this.activePanel.beforeSlide();
- }
+ var dom = this.el.dom;
+ dom.innerHTML = '';
+ var od = dom.ownerDocument;
+
+ if (this.emptyText) {
+ var op = od.createElement('option');
+ op.setAttribute('value', '');
+ op.innerHTML = String.format('{0}', this.emptyText);
+ dom.appendChild(op);
+ }
+ if(this.store.getCount() > 0){
+
+ var vf = this.valueField;
+ var df = this.displayField;
+ this.store.data.each(function(r) {
+ // which colmsn to use... testing - cdoe / title..
+ var op = od.createElement('option');
+ op.setAttribute('value', r.data[vf]);
+ op.innerHTML = String.format('{0}', r.data[df]);
+ dom.appendChild(op);
+ });
+ if (typeof(this.defaultValue != 'undefined')) {
+ this.setValue(this.defaultValue);
}
+
+
+ }else{
+ //this.onEmptyResults();
}
+ //this.el.focus();
},
+ // private
+ onLoadException : function()
+ {
+ dom.innerHTML = '';
+
+ Roo.log("Select on load exception");
+ return;
- afterSlide : function(){
- if(Roo.isGecko){// firefox overflow auto bug workaround
- this.bodyEl.unclip();
- if(this.tabs) {
- this.tabs.bodyEl.unclip();
- }
- if(this.activePanel){
- this.activePanel.getEl().unclip();
- if(this.activePanel.afterSlide){
- this.activePanel.afterSlide();
- }
- }
+ 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);
}
+
+
},
-
- initAutoHide : function(){
- if(this.autoHide !== false){
- if(!this.autoHideHd){
- var st = new Roo.util.DelayedTask(this.slideIn, this);
- this.autoHideHd = {
- "mouseout": function(e){
- if(!e.within(this.el, true)){
- st.delay(500);
- }
- },
- "mouseover" : function(e){
- st.cancel();
- },
- scope : this
- };
- }
- this.el.on(this.autoHideHd);
- }
+ // private
+ onTypeAhead : function(){
+
},
- clearAutoHide : function(){
- if(this.autoHide !== false){
- this.el.un("mouseout", this.autoHideHd.mouseout);
- this.el.un("mouseover", this.autoHideHd.mouseover);
+ // private
+ onSelect : function(record, index){
+ Roo.log('on select?');
+ return;
+ if(this.fireEvent('beforeselect', this, record, index) !== false){
+ this.setFromData(index > -1 ? record.data : false);
+ this.collapse();
+ this.fireEvent('select', this, record, index);
}
},
- clearMonitor : function(){
- Roo.get(document).un("click", this.slideInIf, this);
+ /**
+ * Returns the currently selected field value or empty string if no value is set.
+ * @return {String} value The selected value
+ */
+ getValue : function(){
+ var dom = this.el.dom;
+ this.value = dom.options[dom.selectedIndex].value;
+ return this.value;
+
},
- // these names are backwards but not changed for compat
- slideOut : function(){
- if(this.isSlid || this.el.hasActiveFx()){
- return;
- }
- this.isSlid = true;
- if(this.collapseBtn){
- this.collapseBtn.hide();
- }
- this.closeBtnState = this.closeBtn.getStyle('display');
- this.closeBtn.hide();
- if(this.stickBtn){
- this.stickBtn.show();
- }
- this.el.show();
- this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
- this.beforeSlide();
- this.el.setStyle("z-index", 10001);
- this.el.slideIn(this.getSlideAnchor(), {
- callback: function(){
- this.afterSlide();
- this.initAutoHide();
- Roo.get(document).on("click", this.slideInIf, this);
- this.fireEvent("slideshow", this);
- },
- scope: this,
- block: true
- });
+ /**
+ * Clears any text/value currently set in the field
+ */
+ clearValue : function(){
+ this.value = '';
+ this.el.dom.selectedIndex = this.emptyText ? 0 : -1;
+
},
- afterSlideIn : function(){
- this.clearAutoHide();
- this.isSlid = false;
- this.clearMonitor();
- this.el.setStyle("z-index", "");
- if(this.collapseBtn){
- this.collapseBtn.show();
- }
- this.closeBtn.setStyle('display', this.closeBtnState);
- if(this.stickBtn){
- this.stickBtn.hide();
+ /**
+ * Sets the specified value into the field. If the value finds a match, the corresponding record text
+ * will be displayed in the field. If the value does not match the data value of an existing item,
+ * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
+ * Otherwise the field will be blank (although the value will still be set).
+ * @param {String} value The value to match
+ */
+ setValue : function(v){
+ var d = this.el.dom;
+ for (var i =0; i < d.options.length;i++) {
+ if (v == d.options[i].value) {
+ d.selectedIndex = i;
+ this.value = v;
+ return;
+ }
}
- this.fireEvent("slidehide", this);
+ this.clearValue();
},
-
- slideIn : function(cb){
- if(!this.isSlid || this.el.hasActiveFx()){
- Roo.callback(cb);
- return;
- }
- this.isSlid = false;
- this.beforeSlide();
- this.el.slideOut(this.getSlideAnchor(), {
- callback: function(){
- this.el.setLeftTop(-10000, -10000);
- this.afterSlide();
- this.afterSlideIn();
- Roo.callback(cb);
- },
- scope: this,
- block: true
- });
+ /**
+ * @property {Object} the last set data for the element
+ */
+
+ lastData : false,
+ /**
+ * Sets the value of the field based on a object which is related to the record format for the store.
+ * @param {Object} value the value to set as. or false on reset?
+ */
+ setFromData : function(o){
+ Roo.log('setfrom data?');
+
+
+
+ },
+ // private
+ reset : function(){
+ this.clearValue();
},
+ // private
+ findRecord : function(prop, value){
+
+ return false;
- slideInIf : function(e){
- if(!e.within(this.el)){
- this.slideIn();
+ var record;
+ if(this.store.getCount() > 0){
+ this.store.each(function(r){
+ if(r.data[prop] == value){
+ record = r;
+ return false;
+ }
+ return true;
+ });
}
+ return record;
},
-
- animateCollapse : function(){
- this.beforeSlide();
- this.el.setStyle("z-index", 20000);
- var anchor = this.getSlideAnchor();
- this.el.slideOut(anchor, {
- callback : function(){
- this.el.setStyle("z-index", "");
- this.collapsedEl.slideIn(anchor, {duration:.3});
- this.afterSlide();
- this.el.setLocation(-10000,-10000);
- this.el.hide();
- this.fireEvent("collapsed", this);
- },
- scope: this,
- block: true
- });
+
+ getName: function()
+ {
+ // returns hidden if it's set..
+ if (!this.rendered) {return ''};
+ return !this.hiddenName && this.el.dom.name ? this.el.dom.name : (this.hiddenName || '');
+
},
+
- animateExpand : function(){
- this.beforeSlide();
- this.el.alignTo(this.collapsedEl, this.getCollapseAnchor(), this.getExpandAdj());
- this.el.setStyle("z-index", 20000);
- this.collapsedEl.hide({
- duration:.1
- });
- this.el.slideIn(this.getSlideAnchor(), {
- callback : function(){
- this.el.setStyle("z-index", "");
- this.afterSlide();
- if(this.split){
- this.split.el.show();
- }
- this.fireEvent("invalidated", this);
- this.fireEvent("expanded", this);
- },
- scope: this,
- block: true
- });
- },
+
- anchors : {
- "west" : "left",
- "east" : "right",
- "north" : "top",
- "south" : "bottom"
+ // private
+ onEmptyResults : function(){
+ Roo.log('empty results');
+ //this.collapse();
},
- sanchors : {
- "west" : "l",
- "east" : "r",
- "north" : "t",
- "south" : "b"
+ /**
+ * Returns true if the dropdown list is expanded, else false.
+ */
+ isExpanded : function(){
+ return false;
},
- canchors : {
- "west" : "tl-tr",
- "east" : "tr-tl",
- "north" : "tl-bl",
- "south" : "bl-tl"
+ /**
+ * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
+ * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
+ * @param {String} value The data value of the item to select
+ * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
+ * selected item if it is not currently in view (defaults to true)
+ * @return {Boolean} True if the value matched an item in the list, else false
+ */
+ selectByValue : function(v, scrollIntoView){
+ Roo.log('select By Value');
+ return false;
+
+ if(v !== undefined && v !== null){
+ var r = this.findRecord(this.valueField || this.displayField, v);
+ if(r){
+ this.select(this.store.indexOf(r), scrollIntoView);
+ return true;
+ }
+ }
+ return false;
},
- getAnchor : function(){
- return this.anchors[this.position];
+ /**
+ * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
+ * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
+ * @param {Number} index The zero-based index of the list item to select
+ * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
+ * selected item if it is not currently in view (defaults to true)
+ */
+ select : function(index, scrollIntoView){
+ Roo.log('select ');
+ return ;
+
+ this.selectedIndex = index;
+ this.view.select(index);
+ if(scrollIntoView !== false){
+ var el = this.view.getNode(index);
+ if(el){
+ this.innerList.scrollChildIntoView(el, false);
+ }
+ }
},
- getCollapseAnchor : function(){
- return this.canchors[this.position];
- },
+
- getSlideAnchor : function(){
- return this.sanchors[this.position];
+ // private
+ validateBlur : function(){
+
+ return;
+
},
- getAlignAdj : function(){
- var cm = this.cmargins;
- switch(this.position){
- case "west":
- return [0, 0];
- break;
- case "east":
- return [0, 0];
- break;
- case "north":
- return [0, 0];
- break;
- case "south":
- return [0, 0];
- break;
- }
+ // private
+ initQuery : function(){
+ this.doQuery(this.getRawValue());
},
- getExpandAdj : function(){
- var c = this.collapsedEl, cm = this.cmargins;
- switch(this.position){
- case "west":
- return [-(cm.right+c.getWidth()+cm.left), 0];
- break;
- case "east":
- return [cm.right+c.getWidth()+cm.left, 0];
- break;
- case "north":
- return [0, -(cm.top+cm.bottom+c.getHeight())];
- break;
- case "south":
- return [0, cm.top+cm.bottom+c.getHeight()];
- break;
+ // private
+ doForce : function(){
+ if(this.el.dom.value.length > 0){
+ this.el.dom.value =
+ this.lastSelectionText === undefined ? '' : this.lastSelectionText;
+
}
- }
-});/*
- * 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">
- */
-/*
- * These classes are private internal classes
- */
-Roo.CenterLayoutRegion = function(mgr, config){
- Roo.LayoutRegion.call(this, mgr, config, "center");
- this.visible = true;
- this.minWidth = config.minWidth || 20;
- this.minHeight = config.minHeight || 20;
-};
-
-Roo.extend(Roo.CenterLayoutRegion, Roo.LayoutRegion, {
- hide : function(){
- // center panel can't be hidden
- },
-
- show : function(){
- // center panel can't be hidden
- },
-
- getMinWidth: function(){
- return this.minWidth;
},
-
- getMinHeight: function(){
- return this.minHeight;
- }
-});
-
-Roo.NorthLayoutRegion = function(mgr, config){
- Roo.LayoutRegion.call(this, mgr, config, "north", "n-resize");
- if(this.split){
- this.split.placement = Roo.SplitBar.TOP;
- this.split.orientation = Roo.SplitBar.VERTICAL;
- this.split.el.addClass("x-layout-split-v");
- }
- var size = config.initialSize || config.height;
- if(typeof size != "undefined"){
- this.el.setHeight(size);
- }
-};
-Roo.extend(Roo.NorthLayoutRegion, Roo.SplitLayoutRegion, {
- orientation: Roo.SplitBar.VERTICAL,
- getBox : function(){
- if(this.collapsed){
- return this.collapsedEl.getBox();
- }
- var box = this.el.getBox();
- if(this.split){
- box.height += this.split.el.getHeight();
- }
- return box;
- },
-
- updateBox : function(box){
- if(this.split && !this.collapsed){
- box.height -= this.split.el.getHeight();
- this.split.el.setLeft(box.x);
- this.split.el.setTop(box.y+box.height);
- this.split.el.setWidth(box.width);
- }
- if(this.collapsed){
- this.updateBody(box.width, null);
+ /**
+ * Execute a query to filter the dropdown list. Fires the beforequery event prior to performing the
+ * query allowing the query action to be canceled if needed.
+ * @param {String} query The SQL query to execute
+ * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
+ * in the field than the minimum specified by the minChars config option. It also clears any filter previously
+ * saved in the current store (defaults to false)
+ */
+ doQuery : function(q, forceAll){
+
+ Roo.log('doQuery?');
+ if(q === undefined || q === null){
+ q = '';
}
- Roo.LayoutRegion.prototype.updateBox.call(this, box);
- }
-});
-
-Roo.SouthLayoutRegion = function(mgr, config){
- Roo.SplitLayoutRegion.call(this, mgr, config, "south", "s-resize");
- if(this.split){
- this.split.placement = Roo.SplitBar.BOTTOM;
- this.split.orientation = Roo.SplitBar.VERTICAL;
- this.split.el.addClass("x-layout-split-v");
- }
- var size = config.initialSize || config.height;
- if(typeof size != "undefined"){
- this.el.setHeight(size);
- }
-};
-Roo.extend(Roo.SouthLayoutRegion, Roo.SplitLayoutRegion, {
- orientation: Roo.SplitBar.VERTICAL,
- getBox : function(){
- if(this.collapsed){
- return this.collapsedEl.getBox();
+ var qe = {
+ query: q,
+ forceAll: forceAll,
+ combo: this,
+ cancel:false
+ };
+ if(this.fireEvent('beforequery', qe)===false || qe.cancel){
+ return false;
}
- var box = this.el.getBox();
- if(this.split){
- var sh = this.split.el.getHeight();
- box.height += sh;
- box.y -= sh;
+ q = qe.query;
+ forceAll = qe.forceAll;
+ if(forceAll === true || (q.length >= this.minChars)){
+ if(this.lastQuery != q || this.alwaysQuery){
+ this.lastQuery = q;
+ if(this.mode == 'local'){
+ this.selectedIndex = -1;
+ if(forceAll){
+ this.store.clearFilter();
+ }else{
+ this.store.filter(this.displayField, q);
+ }
+ this.onLoad();
+ }else{
+ this.store.baseParams[this.queryParam] = q;
+ this.store.load({
+ params: this.getParams(q)
+ });
+ this.expand();
+ }
+ }else{
+ this.selectedIndex = -1;
+ this.onLoad();
+ }
}
- return box;
},
-
- updateBox : function(box){
- if(this.split && !this.collapsed){
- var sh = this.split.el.getHeight();
- box.height -= sh;
- box.y += sh;
- this.split.el.setLeft(box.x);
- this.split.el.setTop(box.y-sh);
- this.split.el.setWidth(box.width);
- }
- if(this.collapsed){
- this.updateBody(box.width, null);
- }
- Roo.LayoutRegion.prototype.updateBox.call(this, box);
- }
-});
-Roo.EastLayoutRegion = function(mgr, config){
- Roo.SplitLayoutRegion.call(this, mgr, config, "east", "e-resize");
- if(this.split){
- this.split.placement = Roo.SplitBar.RIGHT;
- this.split.orientation = Roo.SplitBar.HORIZONTAL;
- this.split.el.addClass("x-layout-split-h");
- }
- var size = config.initialSize || config.width;
- if(typeof size != "undefined"){
- this.el.setWidth(size);
- }
-};
-Roo.extend(Roo.EastLayoutRegion, Roo.SplitLayoutRegion, {
- orientation: Roo.SplitBar.HORIZONTAL,
- getBox : function(){
- if(this.collapsed){
- return this.collapsedEl.getBox();
- }
- var box = this.el.getBox();
- if(this.split){
- var sw = this.split.el.getWidth();
- box.width += sw;
- box.x -= sw;
+ // private
+ getParams : function(q){
+ var p = {};
+ //p[this.queryParam] = q;
+ if(this.pageSize){
+ p.start = 0;
+ p.limit = this.pageSize;
}
- return box;
+ return p;
},
- updateBox : function(box){
- if(this.split && !this.collapsed){
- var sw = this.split.el.getWidth();
- box.width -= sw;
- this.split.el.setLeft(box.x);
- this.split.el.setTop(box.y);
- this.split.el.setHeight(box.height);
- box.x += sw;
- }
- if(this.collapsed){
- this.updateBody(null, box.height);
- }
- Roo.LayoutRegion.prototype.updateBox.call(this, box);
- }
-});
+ /**
+ * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
+ */
+ collapse : function(){
+
+ },
-Roo.WestLayoutRegion = function(mgr, config){
- Roo.SplitLayoutRegion.call(this, mgr, config, "west", "w-resize");
- if(this.split){
- this.split.placement = Roo.SplitBar.LEFT;
- this.split.orientation = Roo.SplitBar.HORIZONTAL;
- this.split.el.addClass("x-layout-split-h");
- }
- var size = config.initialSize || config.width;
- if(typeof size != "undefined"){
- this.el.setWidth(size);
- }
-};
-Roo.extend(Roo.WestLayoutRegion, Roo.SplitLayoutRegion, {
- orientation: Roo.SplitBar.HORIZONTAL,
- getBox : function(){
- if(this.collapsed){
- return this.collapsedEl.getBox();
- }
- var box = this.el.getBox();
- if(this.split){
- box.width += this.split.el.getWidth();
- }
- return box;
+ // private
+ collapseIf : function(e){
+
},
+
+ /**
+ * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
+ */
+ expand : function(){
+
+ } ,
+
+ // private
+
+
+ /**
+ * @cfg {Boolean} grow
+ * @hide
+ */
+ /**
+ * @cfg {Number} growMin
+ * @hide
+ */
+ /**
+ * @cfg {Number} growMax
+ * @hide
+ */
+ /**
+ * @hide
+ * @method autoSize
+ */
- updateBox : function(box){
- if(this.split && !this.collapsed){
- var sw = this.split.el.getWidth();
- box.width -= sw;
- this.split.el.setLeft(box.x+box.width);
- this.split.el.setTop(box.y);
- this.split.el.setHeight(box.height);
- }
- if(this.collapsed){
- this.updateBody(null, box.height);
- }
- Roo.LayoutRegion.prototype.updateBox.call(this, box);
+ setWidth : function()
+ {
+
+ },
+ getResizeEl : function(){
+ return this.el;
}
-});
-/*
- * 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">
- */
-
+});//<script type="text/javasscript">
-/*
- * Private internal class for reading and applying state
+
+/**
+ * @class Roo.DDView
+ * A DnD enabled version of Roo.View.
+ * @param {Element/String} container The Element in which to create the View.
+ * @param {String} tpl The template string used to create the markup for each element of the View
+ * @param {Object} config The configuration properties. These include all the config options of
+ * {@link Roo.View} plus some specific to this class.<br>
+ * <p>
+ * Drag/drop is implemented by adding {@link Roo.data.Record}s to the target DDView. If copying is
+ * not being performed, the original {@link Roo.data.Record} is removed from the source DDView.<br>
+ * <p>
+ * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
+.x-view-drag-insert-above {
+ border-top:1px dotted #3366cc;
+}
+.x-view-drag-insert-below {
+ border-bottom:1px dotted #3366cc;
+}
+</code></pre>
+ *
*/
-Roo.LayoutStateManager = function(layout){
- // default empty state
- this.state = {
- north: {},
- south: {},
- east: {},
- west: {}
- };
+
+Roo.DDView = function(container, tpl, config) {
+ Roo.DDView.superclass.constructor.apply(this, arguments);
+ this.getEl().setStyle("outline", "0px none");
+ this.getEl().unselectable();
+ if (this.dragGroup) {
+ this.setDraggable(this.dragGroup.split(","));
+ }
+ if (this.dropGroup) {
+ this.setDroppable(this.dropGroup.split(","));
+ }
+ if (this.deletable) {
+ this.setDeletable();
+ }
+ this.isDirtyFlag = false;
+ this.addEvents({
+ "drop" : true
+ });
};
-Roo.LayoutStateManager.prototype = {
- init : function(layout, provider){
- this.provider = provider;
- var state = provider.get(layout.id+"-layout-state");
- if(state){
- var wasUpdating = layout.isUpdating();
- if(!wasUpdating){
- layout.beginUpdate();
- }
- for(var key in state){
- if(typeof state[key] != "function"){
- var rstate = state[key];
- var r = layout.getRegion(key);
- if(r && rstate){
- if(rstate.size){
- r.resizeTo(rstate.size);
- }
- if(rstate.collapsed == true){
- r.collapse(true);
- }else{
- r.expand(null, true);
- }
- }
- }
- }
- if(!wasUpdating){
- layout.endUpdate();
- }
- this.state = state;
- }
- this.layout = layout;
- layout.on("regionresized", this.onRegionResized, this);
- layout.on("regioncollapsed", this.onRegionCollapsed, this);
- layout.on("regionexpanded", this.onRegionExpanded, this);
+Roo.extend(Roo.DDView, Roo.View, {
+/** @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */
+/** @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
+/** @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
+/** @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
+
+ isFormField: true,
+
+ reset: Roo.emptyFn,
+
+ clearInvalid: Roo.form.Field.prototype.clearInvalid,
+
+ validate: function() {
+ return true;
+ },
+
+ destroy: function() {
+ this.purgeListeners();
+ this.getEl.removeAllListeners();
+ this.getEl().remove();
+ if (this.dragZone) {
+ if (this.dragZone.destroy) {
+ this.dragZone.destroy();
+ }
+ }
+ if (this.dropZone) {
+ if (this.dropZone.destroy) {
+ this.dropZone.destroy();
+ }
+ }
+ },
+
+/** Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
+ getName: function() {
+ return this.name;
+ },
+
+/** Loads the View from a JSON string representing the Records to put into the Store. */
+ setValue: function(v) {
+ if (!this.store) {
+ throw "DDView.setValue(). DDView must be constructed with a valid Store";
+ }
+ var data = {};
+ data[this.store.reader.meta.root] = v ? [].concat(v) : [];
+ this.store.proxy = new Roo.data.MemoryProxy(data);
+ this.store.load();
+ },
+
+/** @return {String} a parenthesised list of the ids of the Records in the View. */
+ getValue: function() {
+ var result = '(';
+ this.store.each(function(rec) {
+ result += rec.id + ',';
+ });
+ return result.substr(0, result.length - 1) + ')';
+ },
+
+ getIds: function() {
+ var i = 0, result = new Array(this.store.getCount());
+ this.store.each(function(rec) {
+ result[i++] = rec.id;
+ });
+ return result;
+ },
+
+ isDirty: function() {
+ return this.isDirtyFlag;
+ },
+
+/**
+ * Part of the Roo.dd.DropZone interface. If no target node is found, the
+ * whole Element becomes the target, and this causes the drop gesture to append.
+ */
+ getTargetFromEvent : function(e) {
+ var target = e.getTarget();
+ while ((target !== null) && (target.parentNode != this.el.dom)) {
+ target = target.parentNode;
+ }
+ if (!target) {
+ target = this.el.dom.lastChild || this.el.dom;
+ }
+ return target;
},
-
- storeState : function(){
- this.provider.set(this.layout.id+"-layout-state", this.state);
+
+/**
+ * Create the drag data which consists of an object which has the property "ddel" as
+ * the drag proxy element.
+ */
+ getDragData : function(e) {
+ var target = this.findItemFromChild(e.getTarget());
+ if(target) {
+ this.handleSelection(e);
+ var selNodes = this.getSelectedNodes();
+ var dragData = {
+ source: this,
+ copy: this.copy || (this.allowCopy && e.ctrlKey),
+ nodes: selNodes,
+ records: []
+ };
+ var selectedIndices = this.getSelectedIndexes();
+ for (var i = 0; i < selectedIndices.length; i++) {
+ dragData.records.push(this.store.getAt(selectedIndices[i]));
+ }
+ if (selNodes.length == 1) {
+ dragData.ddel = target.cloneNode(true); // the div element
+ } else {
+ var div = document.createElement('div'); // create the multi element drag "ghost"
+ div.className = 'multi-proxy';
+ for (var i = 0, len = selNodes.length; i < len; i++) {
+ div.appendChild(selNodes[i].cloneNode(true));
+ }
+ dragData.ddel = div;
+ }
+ //console.log(dragData)
+ //console.log(dragData.ddel.innerHTML)
+ return dragData;
+ }
+ //console.log('nodragData')
+ return false;
},
- onRegionResized : function(region, newSize){
- this.state[region.getPosition()].size = newSize;
- this.storeState();
+/** Specify to which ddGroup items in this DDView may be dragged. */
+ setDraggable: function(ddGroup) {
+ if (ddGroup instanceof Array) {
+ Roo.each(ddGroup, this.setDraggable, this);
+ return;
+ }
+ if (this.dragZone) {
+ this.dragZone.addToGroup(ddGroup);
+ } else {
+ this.dragZone = new Roo.dd.DragZone(this.getEl(), {
+ containerScroll: true,
+ ddGroup: ddGroup
+
+ });
+// Draggability implies selection. DragZone's mousedown selects the element.
+ if (!this.multiSelect) { this.singleSelect = true; }
+
+// Wire the DragZone's handlers up to methods in *this*
+ this.dragZone.getDragData = this.getDragData.createDelegate(this);
+ }
+ },
+
+/** Specify from which ddGroup this DDView accepts drops. */
+ setDroppable: function(ddGroup) {
+ if (ddGroup instanceof Array) {
+ Roo.each(ddGroup, this.setDroppable, this);
+ return;
+ }
+ if (this.dropZone) {
+ this.dropZone.addToGroup(ddGroup);
+ } else {
+ this.dropZone = new Roo.dd.DropZone(this.getEl(), {
+ containerScroll: true,
+ ddGroup: ddGroup
+ });
+
+// Wire the DropZone's handlers up to methods in *this*
+ this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
+ this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
+ this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
+ this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
+ this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
+ }
+ },
+
+/** Decide whether to drop above or below a View node. */
+ getDropPoint : function(e, n, dd){
+ if (n == this.el.dom) { return "above"; }
+ var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
+ var c = t + (b - t) / 2;
+ var y = Roo.lib.Event.getPageY(e);
+ if(y <= c) {
+ return "above";
+ }else{
+ return "below";
+ }
+ },
+
+ onNodeEnter : function(n, dd, e, data){
+ return false;
},
- onRegionCollapsed : function(region){
- this.state[region.getPosition()].collapsed = true;
- this.storeState();
+ onNodeOver : function(n, dd, e, data){
+ var pt = this.getDropPoint(e, n, dd);
+ // set the insert point style on the target node
+ var dragElClass = this.dropNotAllowed;
+ if (pt) {
+ var targetElClass;
+ if (pt == "above"){
+ dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
+ targetElClass = "x-view-drag-insert-above";
+ } else {
+ dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
+ targetElClass = "x-view-drag-insert-below";
+ }
+ if (this.lastInsertClass != targetElClass){
+ Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
+ this.lastInsertClass = targetElClass;
+ }
+ }
+ return dragElClass;
+ },
+
+ onNodeOut : function(n, dd, e, data){
+ this.removeDropIndicators(n);
+ },
+
+ onNodeDrop : function(n, dd, e, data){
+ if (this.fireEvent("drop", this, n, dd, e, data) === false) {
+ return false;
+ }
+ var pt = this.getDropPoint(e, n, dd);
+ var insertAt = (n == this.el.dom) ? this.nodes.length : n.nodeIndex;
+ if (pt == "below") { insertAt++; }
+ for (var i = 0; i < data.records.length; i++) {
+ var r = data.records[i];
+ var dup = this.store.getById(r.id);
+ if (dup && (dd != this.dragZone)) {
+ Roo.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
+ } else {
+ if (data.copy) {
+ this.store.insert(insertAt++, r.copy());
+ } else {
+ data.source.isDirtyFlag = true;
+ r.store.remove(r);
+ this.store.insert(insertAt++, r);
+ }
+ this.isDirtyFlag = true;
+ }
+ }
+ this.dragZone.cachedTarget = null;
+ return true;
+ },
+
+ removeDropIndicators : function(n){
+ if(n){
+ Roo.fly(n).removeClass([
+ "x-view-drag-insert-above",
+ "x-view-drag-insert-below"]);
+ this.lastInsertClass = "_noclass";
+ }
+ },
+
+/**
+ * Utility method. Add a delete option to the DDView's context menu.
+ * @param {String} imageUrl The URL of the "delete" icon image.
+ */
+ setDeletable: function(imageUrl) {
+ if (!this.singleSelect && !this.multiSelect) {
+ this.singleSelect = true;
+ }
+ var c = this.getContextMenu();
+ this.contextMenu.on("itemclick", function(item) {
+ switch (item.id) {
+ case "delete":
+ this.remove(this.getSelectedIndexes());
+ break;
+ }
+ }, this);
+ this.contextMenu.add({
+ icon: imageUrl,
+ id: "delete",
+ text: 'Delete'
+ });
+ },
+
+/** Return the context menu for this DDView. */
+ getContextMenu: function() {
+ if (!this.contextMenu) {
+// Create the View's context menu
+ this.contextMenu = new Roo.menu.Menu({
+ id: this.id + "-contextmenu"
+ });
+ this.el.on("contextmenu", this.showContextMenu, this);
+ }
+ return this.contextMenu;
+ },
+
+ disableContextMenu: function() {
+ if (this.contextMenu) {
+ this.el.un("contextmenu", this.showContextMenu, this);
+ }
+ },
+
+ showContextMenu: function(e, item) {
+ item = this.findItemFromChild(e.getTarget());
+ if (item) {
+ e.stopEvent();
+ this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
+ this.contextMenu.showAt(e.getXY());
+ }
+ },
+
+/**
+ * Remove {@link Roo.data.Record}s at the specified indices.
+ * @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
+ */
+ remove: function(selectedIndices) {
+ selectedIndices = [].concat(selectedIndices);
+ for (var i = 0; i < selectedIndices.length; i++) {
+ var rec = this.store.getAt(selectedIndices[i]);
+ this.store.remove(rec);
+ }
+ },
+
+/**
+ * Double click fires the event, but also, if this is draggable, and there is only one other
+ * related DropZone, it transfers the selected node.
+ */
+ onDblClick : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
+ return false;
+ }
+ if (this.dragGroup) {
+ var targets = Roo.dd.DragDropMgr.getRelated(this.dragZone, true);
+ while (targets.indexOf(this.dropZone) > -1) {
+ targets.remove(this.dropZone);
+ }
+ if (targets.length == 1) {
+ this.dragZone.cachedTarget = null;
+ var el = Roo.get(targets[0].getEl());
+ var box = el.getBox(true);
+ targets[0].onNodeDrop(el.dom, {
+ target: el.dom,
+ xy: [box.x, box.y + box.height - 1]
+ }, null, this.getDragData(e));
+ }
+ }
+ }
},
- onRegionExpanded : function(region){
- this.state[region.getPosition()].collapsed = false;
- this.storeState();
+ handleSelection: function(e) {
+ this.dragZone.cachedTarget = null;
+ var item = this.findItemFromChild(e.getTarget());
+ if (!item) {
+ this.clearSelections(true);
+ return;
+ }
+ if (item && (this.multiSelect || this.singleSelect)){
+ if(this.multiSelect && e.shiftKey && (!e.ctrlKey) && this.lastSelection){
+ this.select(this.getNodes(this.indexOf(this.lastSelection), item.nodeIndex), false);
+ }else if (this.isSelected(this.getNode(item)) && e.ctrlKey){
+ this.unselect(item);
+ } else {
+ this.select(item, this.multiSelect && e.ctrlKey);
+ this.lastSelection = item;
+ }
+ }
+ },
+
+ onItemClick : function(item, index, e){
+ if(this.fireEvent("beforeclick", this, index, item, e) === false){
+ return false;
+ }
+ return true;
+ },
+
+ unselect : function(nodeInfo, suppressEvent){
+ var node = this.getNode(nodeInfo);
+ if(node && this.isSelected(node)){
+ if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
+ Roo.fly(node).removeClass(this.selectedClass);
+ this.selections.remove(node);
+ if(!suppressEvent){
+ this.fireEvent("selectionchange", this, this.selections);
+ }
+ }
+ }
}
-};/*
+});
+/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
+
/**
- * @class Roo.ContentPanel
+ * @class Roo.LayoutManager
* @extends Roo.util.Observable
- * 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} 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 {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 {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
-
- * @constructor
- * Create a new ContentPanel.
- * @param {String/HTMLElement/Roo.Element} el The container element for this panel
- * @param {String/Object} config A string to set only the title or a config object
- * @param {String} content (optional) Set the HTML content for this panel
- * @param {String} region (optional) Used by xtype constructors to add to regions. (values center,east,west,south,north)
+ * Base class for layout managers.
*/
-Roo.ContentPanel = function(el, config, content){
-
-
- /*
- if(el.autoCreate || el.xtype){ // xtype is available if this is called from factory
- config = el;
- el = Roo.id();
- }
- if (config && config.parentLayout) {
- el = config.parentLayout.el.createChild();
- }
- */
- if(el.autoCreate){ // xtype is available if this is called from factory
- config = el;
- el = Roo.id();
- }
- this.el = Roo.get(el);
- if(!this.el && config && config.autoCreate){
- if(typeof config.autoCreate == "object"){
- if(!config.autoCreate.id){
- config.autoCreate.id = config.id||el;
- }
- this.el = Roo.DomHelper.append(document.body,
- config.autoCreate, true);
- }else{
- this.el = Roo.DomHelper.append(document.body,
- {tag: "div", cls: "x-layout-inactive-content", id: config.id||el}, true);
- }
- }
-
-
- this.closable = false;
- this.loaded = false;
- this.active = false;
- if(typeof config == "string"){
- this.title = config;
- }else{
- Roo.apply(this, config);
- }
-
- if (this.toolbar && !this.toolbar.el && this.toolbar.xtype) {
- this.wrapEl = this.el.wrap();
- this.toolbar.container = this.el.insertSibling(false, 'before');
- this.toolbar = new Roo.Toolbar(this.toolbar);
- }
-
- // xtype created footer. - not sure if will work as we normally have to render first..
- if (this.footer && !this.footer.el && this.footer.xtype) {
- if (!this.wrapEl) {
- this.wrapEl = this.el.wrap();
- }
-
- this.footer.container = this.wrapEl.createChild();
-
- this.footer = Roo.factory(this.footer, Roo);
-
- }
-
- if(this.resizeEl){
- this.resizeEl = Roo.get(this.resizeEl, true);
- }else{
- this.resizeEl = this.el;
+Roo.LayoutManager = function(container, config){
+ Roo.LayoutManager.superclass.constructor.call(this);
+ this.el = Roo.get(container);
+ // ie scrollbar fix
+ if(this.el.dom == document.body && Roo.isIE && !config.allowScroll){
+ document.body.scroll = "no";
+ }else if(this.el.dom != document.body && this.el.getStyle('position') == 'static'){
+ this.el.position('relative');
}
- // handle view.xtype
-
-
-
-
+ this.id = this.el.id;
+ this.el.addClass("x-layout-container");
+ /** false to disable window resize monitoring @type Boolean */
+ this.monitorWindowResize = true;
+ this.regions = {};
this.addEvents({
/**
- * @event activate
- * Fires when this panel is activated.
- * @param {Roo.ContentPanel} this
- */
- "activate" : true,
+ * @event layout
+ * Fires when a layout is performed.
+ * @param {Roo.LayoutManager} this
+ */
+ "layout" : true,
/**
- * @event deactivate
- * Fires when this panel is activated.
- * @param {Roo.ContentPanel} this
+ * @event regionresized
+ * Fires when the user resizes a region.
+ * @param {Roo.LayoutRegion} region The resized region
+ * @param {Number} newSize The new size (width for east/west, height for north/south)
*/
- "deactivate" : true,
-
+ "regionresized" : true,
/**
- * @event resize
- * Fires when this panel is resized if fitToFrame is true.
- * @param {Roo.ContentPanel} this
- * @param {Number} width The width after any component adjustments
- * @param {Number} height The height after any component adjustments
+ * @event regioncollapsed
+ * Fires when a region is collapsed.
+ * @param {Roo.LayoutRegion} region The collapsed region
*/
- "resize" : true,
-
- /**
- * @event render
- * Fires when this tab is created
- * @param {Roo.ContentPanel} this
+ "regioncollapsed" : true,
+ /**
+ * @event regionexpanded
+ * Fires when a region is expanded.
+ * @param {Roo.LayoutRegion} region The expanded region
*/
- "render" : true
-
-
+ "regionexpanded" : true
});
-
-
-
-
- if(this.autoScroll){
- this.resizeEl.setStyle("overflow", "auto");
- } else {
- // fix randome scrolling
- this.el.on('scroll', function() {
- Roo.log('fix random scolling');
- this.scrollTo('top',0);
- });
- }
- content = content || this.content;
- if(content){
- this.setContent(content);
- }
- if(config && config.url){
- this.setUrl(this.url, this.params, this.loadOnce);
- }
-
-
-
- Roo.ContentPanel.superclass.constructor.call(this);
-
- if (this.view && typeof(this.view.xtype) != 'undefined') {
- this.view.el = this.el.appendChild(document.createElement("div"));
- this.view = Roo.factory(this.view);
- this.view.render && this.view.render(false, '');
- }
-
-
- this.fireEvent('render', this);
+ this.updating = false;
+ Roo.EventManager.onWindowResize(this.onWindowResize, this, true);
};
-Roo.extend(Roo.ContentPanel, Roo.util.Observable, {
- tabTip:'',
- setRegion : function(region){
- this.region = region;
- if(region){
- this.el.replaceClass("x-layout-inactive-content", "x-layout-active-content");
- }else{
- this.el.replaceClass("x-layout-active-content", "x-layout-inactive-content");
- }
- },
-
+Roo.extend(Roo.LayoutManager, Roo.util.Observable, {
/**
- * Returns the toolbar for this Panel if one was configured.
- * @return {Roo.Toolbar}
+ * Returns true if this layout is currently being updated
+ * @return {Boolean}
*/
- getToolbar : function(){
- return this.toolbar;
+ isUpdating : function(){
+ return this.updating;
},
- setActiveState : function(active){
- this.active = active;
- if(!active){
- this.fireEvent("deactivate", this);
- }else{
- this.fireEvent("activate", this);
- }
- },
- /**
- * Updates this panel's element
- * @param {String} content The new content
- * @param {Boolean} loadScripts (optional) true to look for and process scripts
- */
- setContent : function(content, loadScripts){
- this.el.update(content, loadScripts);
- },
-
- ignoreResize : function(w, h){
- if(this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
- return true;
- }else{
- this.lastSize = {width: w, height: h};
- return false;
- }
- },
- /**
- * Get the {@link Roo.UpdateManager} for this panel. Enables you to perform Ajax updates.
- * @return {Roo.UpdateManager} The UpdateManager
- */
- getUpdateManager : function(){
- return this.el.getUpdateManager();
- },
- /**
- * Loads this content panel immediately with content from XHR. Note: to delay loading until the panel is activated, use {@link #setUrl}.
- * @param {Object/String/Function} url The url for this request or a function to call to get the url or a config object containing any of the following options:
-<pre><code>
-panel.load({
- url: "your-url.php",
- params: {param1: "foo", param2: "bar"}, // or a URL encoded string
- callback: yourFunction,
- scope: yourObject, //(optional scope)
- discardUrl: false,
- nocache: false,
- text: "Loading...",
- timeout: 30,
- scripts: false
-});
-</code></pre>
- * The only required property is <i>url</i>. The optional properties <i>nocache</i>, <i>text</i> and <i>scripts</i>
- * are shorthand for <i>disableCaching</i>, <i>indicatorText</i> and <i>loadScripts</i> and are used to set their associated property on this panel UpdateManager instance.
- * @param {String/Object} params (optional) The parameters to pass as either a URL encoded string "param1=1&param2=2" or an object {param1: 1, param2: 2}
- * @param {Function} callback (optional) Callback when transaction is complete -- called with signature (oElement, bSuccess, oResponse)
- * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used URL. If true, it will not store the URL.
- * @return {Roo.ContentPanel} this
- */
- load : function(){
- var um = this.el.getUpdateManager();
- um.update.apply(um, arguments);
- return this;
- },
-
-
/**
- * Set a URL to be used to load the content for this panel. When this panel is activated, the content will be loaded from that URL.
- * @param {String/Function} url The URL to load the content from or a function to call to get the URL
- * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Roo.UpdateManager#update} for more details. (Defaults to null)
- * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
- * @return {Roo.UpdateManager} The UpdateManager
+ * Suspend the LayoutManager from doing auto-layouts while
+ * making multiple add or remove calls
*/
- setUrl : function(url, params, loadOnce){
- if(this.refreshDelegate){
- this.removeListener("activate", this.refreshDelegate);
- }
- this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
- this.on("activate", this.refreshDelegate);
- return this.el.getUpdateManager();
- },
-
- _handleRefresh : function(url, params, loadOnce){
- if(!loadOnce || !this.loaded){
- var updater = this.el.getUpdateManager();
- updater.update(url, params, this._setLoaded.createDelegate(this));
- }
+ beginUpdate : function(){
+ this.updating = true;
},
- _setLoaded : function(){
- this.loaded = true;
- },
-
/**
- * Returns this panel's id
- * @return {String}
+ * Restore auto-layouts and optionally disable the manager from performing a layout
+ * @param {Boolean} noLayout true to disable a layout update
*/
- getId : function(){
- return this.el.id;
+ endUpdate : function(noLayout){
+ this.updating = false;
+ if(!noLayout){
+ this.layout();
+ }
},
- /**
- * Returns this panel's element - used by regiosn to add.
- * @return {Roo.Element}
- */
- getEl : function(){
- return this.wrapEl || this.el;
+ layout: function(){
+
},
- adjustForComponents : function(width, height)
- {
- //Roo.log('adjustForComponents ');
- if(this.resizeEl != this.el){
- width -= this.el.getFrameWidth('lr');
- height -= this.el.getFrameWidth('tb');
- }
- if(this.toolbar){
- var te = this.toolbar.getEl();
- height -= te.getHeight();
- te.setWidth(width);
- }
- if(this.footer){
- var te = this.footer.getEl();
- //Roo.log("footer:" + te.getHeight());
-
- height -= te.getHeight();
- te.setWidth(width);
- }
-
-
- if(this.adjustments){
- width += this.adjustments[0];
- height += this.adjustments[1];
- }
- return {"width": width, "height": height};
+ onRegionResized : function(region, newSize){
+ this.fireEvent("regionresized", region, newSize);
+ this.layout();
},
- setSize : function(width, height){
- if(this.fitToFrame && !this.ignoreResize(width, height)){
- if(this.fitContainer && this.resizeEl != this.el){
- this.el.setSize(width, height);
- }
- var size = this.adjustForComponents(width, height);
- this.resizeEl.setSize(this.autoWidth ? "auto" : size.width, this.autoHeight ? "auto" : size.height);
- this.fireEvent('resize', this, size.width, size.height);
- }
+ onRegionCollapsed : function(region){
+ this.fireEvent("regioncollapsed", region);
},
+ onRegionExpanded : function(region){
+ this.fireEvent("regionexpanded", region);
+ },
+
/**
- * Returns this panel's title
- * @return {String}
+ * Returns the size of the current view. This method normalizes document.body and element embedded layouts and
+ * performs box-model adjustments.
+ * @return {Object} The size as an object {width: (the width), height: (the height)}
*/
- getTitle : function(){
- return this.title;
+ getViewSize : function(){
+ var size;
+ if(this.el.dom != document.body){
+ size = this.el.getSize();
+ }else{
+ size = {width: Roo.lib.Dom.getViewWidth(), height: Roo.lib.Dom.getViewHeight()};
+ }
+ size.width -= this.el.getBorderWidth("lr")-this.el.getPadding("lr");
+ size.height -= this.el.getBorderWidth("tb")-this.el.getPadding("tb");
+ return size;
},
/**
- * Set this panel's title
- * @param {String} title
+ * Returns the Element this layout is bound to.
+ * @return {Roo.Element}
*/
- setTitle : function(title){
- this.title = title;
- if(this.region){
- this.region.updatePanelTitle(this, title);
- }
+ getEl : function(){
+ return this.el;
},
/**
- * Returns true is this panel was configured to be closable
- * @return {Boolean}
+ * Returns the specified region.
+ * @param {String} target The region key ('center', 'north', 'south', 'east' or 'west')
+ * @return {Roo.LayoutRegion}
*/
- isClosable : function(){
- return this.closable;
+ getRegion : function(target){
+ return this.regions[target.toLowerCase()];
},
- beforeSlide : function(){
- this.el.clip();
- this.resizeEl.clip();
+ onWindowResize : function(){
+ if(this.monitorWindowResize){
+ this.layout();
+ }
+ }
+});/*
+ * 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">
+ */
+/**
+ * @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>
+ * <a href="http://www.jackslocum.com/yui/2006/10/28/cross-browser-web-20-layouts-part-2-ajax-feed-viewer-20/">Cross Browser Layouts - Part 2</a><br><br>
+ * Example:
+ <pre><code>
+ var layout = new Roo.BorderLayout(document.body, {
+ north: {
+ initialSize: 25,
+ titlebar: false
},
-
- afterSlide : function(){
- this.el.unclip();
- this.resizeEl.unclip();
+ west: {
+ split:true,
+ initialSize: 200,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true
},
-
- /**
- * Force a content refresh from the URL specified in the {@link #setUrl} method.
- * Will fail silently if the {@link #setUrl} method has not been called.
- * This does not activate the panel, just updates its content.
- */
- refresh : function(){
- if(this.refreshDelegate){
- this.loaded = false;
- this.refreshDelegate();
- }
+ east: {
+ split:true,
+ initialSize: 202,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true
},
-
- /**
- * Destroys this panel
- */
- destroy : function(){
- this.el.removeAllListeners();
- var tempEl = document.createElement("span");
- tempEl.appendChild(this.el.dom);
- tempEl.innerHTML = "";
- this.el.remove();
- this.el = null;
+ south: {
+ split:true,
+ initialSize: 100,
+ minSize: 100,
+ maxSize: 200,
+ titlebar: true,
+ collapsible: true
},
-
- /**
- * form - if the content panel contains a form - this is a reference to it.
- * @type {Roo.form.Form}
- */
- form : false,
- /**
- * view - if the content panel contains a view (Roo.DatePicker / Roo.View / Roo.JsonView)
- * This contains a reference to it.
- * @type {Roo.View}
- */
- view : false,
-
- /**
- * Adds a xtype elements to the panel - currently only supports Forms, View, JsonView.
- * <pre><code>
+ center: {
+ titlebar: true,
+ autoScroll:true,
+ resizeTabs: true,
+ minTabWidth: 50,
+ preferredTabWidth: 150
+ }
+});
-layout.addxtype({
- xtype : 'Form',
- items: [ .... ]
- }
-);
+// shorthand
+var CP = Roo.ContentPanel;
+layout.beginUpdate();
+layout.add("north", new CP("north", "North"));
+layout.add("south", new CP("south", {title: "South", closable: true}));
+layout.add("west", new CP("west", {title: "West"}));
+layout.add("east", new CP("autoTabs", {title: "Auto Tabs", closable: true}));
+layout.add("center", new CP("center1", {title: "Close Me", closable: true}));
+layout.add("center", new CP("center2", {title: "Center Panel", closable: false}));
+layout.getRegion("center").showPanel("center1");
+layout.endUpdate();
</code></pre>
- * @param {Object} cfg Xtype definition of item to add.
- */
-
- addxtype : function(cfg) {
- // add form..
- if (cfg.xtype.match(/^Form$/)) {
-
- var el;
- //if (this.footer) {
- // el = this.footer.container.insertSibling(false, 'before');
- //} else {
- el = this.el.createChild();
- //}
- this.form = new Roo.form.Form(cfg);
-
-
- if ( this.form.allItems.length) {
- this.form.render(el.dom);
- }
- return this.form;
- }
- // should only have one of theses..
- if ([ 'View', 'JsonView', 'DatePicker'].indexOf(cfg.xtype) > -1) {
- // views.. should not be just added - used named prop 'view''
-
- cfg.el = this.el.appendChild(document.createElement("div"));
- // factory?
-
- var ret = new Roo.factory(cfg);
-
- ret.render && ret.render(false, ''); // render blank..
- this.view = ret;
- return ret;
- }
- return false;
- }
-});
+<b>The container the layout is rendered into can be either the body element or any other element.
+If it is not the body element, the container needs to either be an absolute positioned element,
+or you will need to add "position:relative" to the css of the container. You will also need to specify
+the container size if it is not the body element.</b>
-/**
- * @class Roo.GridPanel
- * @extends Roo.ContentPanel
- * @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
+* @constructor
+* Create a new BorderLayout
+* @param {String/HTMLElement/Element} container The container this layout is bound to
+* @param {Object} config Configuration options
*/
-Roo.GridPanel = function(grid, config){
-
-
- 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);
-
- this.wrapper.dom.appendChild(grid.getGridEl().dom);
-
- Roo.GridPanel.superclass.constructor.call(this, this.wrapper, config);
-
- if(this.toolbar){
- this.toolbar.el.insertBefore(this.wrapper.dom.firstChild);
- }
- // xtype created footer. - not sure if will work as we normally have to render first..
- if (this.footer && !this.footer.el && this.footer.xtype) {
-
- this.footer.container = this.grid.getView().getFooterPanel(true);
- this.footer.dataSource = this.grid.dataSource;
- this.footer = Roo.factory(this.footer, Roo);
-
+Roo.BorderLayout = function(container, config){
+ config = config || {};
+ Roo.BorderLayout.superclass.constructor.call(this, container, config);
+ this.factory = config.factory || Roo.BorderLayout.RegionFactory;
+ for(var i = 0, len = this.factory.validRegions.length; i < len; i++) {
+ var target = this.factory.validRegions[i];
+ if(config[target]){
+ this.addRegion(target, config[target]);
+ }
}
-
- grid.monitorWindowResize = false; // turn off autosizing
- grid.autoHeight = false;
- grid.autoWidth = false;
- this.grid = grid;
- this.grid.getGridEl().replaceClass("x-layout-inactive-content", "x-layout-component-panel");
};
-Roo.extend(Roo.GridPanel, Roo.ContentPanel, {
- getId : function(){
- return this.grid.id;
- },
-
+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
+ */
/**
- * Returns the grid for this panel
- * @return {Roo.grid.Grid}
+ * 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).
+ * @param {Object} config The regions config object
+ * @return {BorderLayoutRegion} The new region
*/
- getGrid : function(){
- return this.grid;
- },
-
- setSize : function(width, height){
- if(!this.ignoreResize(width, height)){
- var grid = this.grid;
- var size = this.adjustForComponents(width, height);
- grid.getGridEl().setSize(size.width, size.height);
- grid.autoSize();
+ addRegion : function(target, config){
+ if(!this.regions[target]){
+ var r = this.factory.create(target, this, config);
+ this.bindRegion(target, r);
}
+ return this.regions[target];
},
-
- beforeSlide : function(){
- this.grid.getView().scroller.clip();
+
+ // private (kinda)
+ bindRegion : function(name, r){
+ this.regions[name] = r;
+ r.on("visibilitychange", this.layout, this);
+ r.on("paneladded", this.layout, this);
+ r.on("panelremoved", this.layout, this);
+ r.on("invalidated", this.layout, this);
+ r.on("resized", this.onRegionResized, this);
+ r.on("collapsed", this.onRegionCollapsed, this);
+ r.on("expanded", this.onRegionExpanded, this);
},
-
- afterSlide : function(){
- this.grid.getView().scroller.unclip();
+
+ /**
+ * Performs a layout update.
+ */
+ layout : function(){
+ if(this.updating) {
+ return;
+ }
+ var size = this.getViewSize();
+ var w = size.width;
+ var h = size.height;
+ var centerW = w;
+ var centerH = h;
+ var centerY = 0;
+ var centerX = 0;
+ //var x = 0, y = 0;
+
+ var rs = this.regions;
+ var north = rs["north"];
+ var south = rs["south"];
+ var west = rs["west"];
+ var east = rs["east"];
+ var center = rs["center"];
+ //if(this.hideOnLayout){ // not supported anymore
+ //c.el.setStyle("display", "none");
+ //}
+ if(north && north.isVisible()){
+ var b = north.getBox();
+ var m = north.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ b.y = m.top;
+ centerY = b.height + b.y + m.bottom;
+ centerH -= centerY;
+ north.updateBox(this.safeBox(b));
+ }
+ if(south && south.isVisible()){
+ var b = south.getBox();
+ var m = south.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ var totalHeight = (b.height + m.top + m.bottom);
+ b.y = h - totalHeight + m.top;
+ centerH -= totalHeight;
+ south.updateBox(this.safeBox(b));
+ }
+ if(west && west.isVisible()){
+ var b = west.getBox();
+ var m = west.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ b.x = m.left;
+ b.y = centerY + m.top;
+ var totalWidth = (b.width + m.left + m.right);
+ centerX += totalWidth;
+ centerW -= totalWidth;
+ west.updateBox(this.safeBox(b));
+ }
+ if(east && east.isVisible()){
+ var b = east.getBox();
+ var m = east.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ var totalWidth = (b.width + m.left + m.right);
+ b.x = w - totalWidth + m.left;
+ b.y = centerY + m.top;
+ centerW -= totalWidth;
+ east.updateBox(this.safeBox(b));
+ }
+ if(center){
+ var m = center.getMargins();
+ var centerBox = {
+ x: centerX + m.left,
+ y: centerY + m.top,
+ width: centerW - (m.left+m.right),
+ height: centerH - (m.top+m.bottom)
+ };
+ //if(this.hideOnLayout){
+ //center.el.setStyle("display", "block");
+ //}
+ center.updateBox(this.safeBox(centerBox));
+ }
+ this.el.repaint();
+ this.fireEvent("layout", this);
},
-
- destroy : function(){
- this.grid.destroy();
- delete this.grid;
- Roo.GridPanel.superclass.destroy.call(this);
- }
-});
+ // private
+ safeBox : function(box){
+ box.width = Math.max(0, box.width);
+ box.height = Math.max(0, box.height);
+ return box;
+ },
-/**
- * @class Roo.NestedLayoutPanel
- * @extends Roo.ContentPanel
- * @constructor
- * Create a new NestedLayoutPanel.
- *
- *
- * @param {Roo.BorderLayout} layout 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)
-{
- // construct with only one argument..
- /* FIXME - implement nicer consturctors
- if (layout.layout) {
- config = layout;
- layout = config.layout;
- delete config.layout;
- }
- if (layout.xtype && !layout.getEl) {
- // then layout needs constructing..
- layout = Roo.factory(layout, Roo);
- }
- */
-
-
- Roo.NestedLayoutPanel.superclass.constructor.call(this, layout.getEl(), config);
-
- layout.monitorWindowResize = false; // turn off autosizing
- this.layout = layout;
- this.layout.getEl().addClass("x-layout-nested-layout");
-
-
-
-
-};
+ /**
+ * Adds a ContentPanel (or subclass) to this layout.
+ * @param {String} target The target region key (north, south, east, west or center).
+ * @param {Roo.ContentPanel} panel The panel to add
+ * @return {Roo.ContentPanel} The added panel
+ */
+ add : function(target, panel){
+
+ target = target.toLowerCase();
+ return this.regions[target].add(panel);
+ },
-Roo.extend(Roo.NestedLayoutPanel, Roo.ContentPanel, {
+ /**
+ * Remove a ContentPanel (or subclass) to this layout.
+ * @param {String} target The target region key (north, south, east, west or center).
+ * @param {Number/String/Roo.ContentPanel} panel The index, id or panel to remove
+ * @return {Roo.ContentPanel} The removed panel
+ */
+ remove : function(target, panel){
+ target = target.toLowerCase();
+ return this.regions[target].remove(panel);
+ },
- setSize : function(width, height){
- if(!this.ignoreResize(width, height)){
- var size = this.adjustForComponents(width, height);
- var el = this.layout.getEl();
- el.setSize(size.width, size.height);
- var touch = el.dom.offsetWidth;
- this.layout.layout();
- // ie requires a double layout on the first pass
- if(Roo.isIE && !this.initialized){
- this.initialized = true;
- this.layout.layout();
+ /**
+ * Searches all regions for a panel with the specified id
+ * @param {String} panelId
+ * @return {Roo.ContentPanel} The panel or null if it wasn't found
+ */
+ findPanel : function(panelId){
+ var rs = this.regions;
+ for(var target in rs){
+ if(typeof rs[target] != "function"){
+ var p = rs[target].getPanel(panelId);
+ if(p){
+ return p;
+ }
}
}
+ return null;
},
-
- // activate all subpanels if not currently active..
-
- setActiveState : function(active){
- this.active = active;
- if(!active){
- this.fireEvent("deactivate", this);
- return;
- }
-
- this.fireEvent("activate", this);
- // not sure if this should happen before or after..
- if (!this.layout) {
- return; // should not happen..
- }
- var reg = false;
- for (var r in this.layout.regions) {
- reg = this.layout.getRegion(r);
- if (reg.getActivePanel()) {
- //reg.showPanel(reg.getActivePanel()); // force it to activate..
- reg.setActivePanel(reg.getActivePanel());
- continue;
- }
- if (!reg.panels.length) {
- continue;
+
+ /**
+ * Searches all regions for a panel with the specified id and activates (shows) it.
+ * @param {String/ContentPanel} panelId The panels id or the panel itself
+ * @return {Roo.ContentPanel} The shown panel or null
+ */
+ showPanel : function(panelId) {
+ var rs = this.regions;
+ for(var target in rs){
+ var r = rs[target];
+ if(typeof r != "function"){
+ if(r.hasPanel(panelId)){
+ return r.showPanel(panelId);
}
- reg.showPanel(reg.getPanel(0));
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Restores this layout's state using Roo.state.Manager or the state provided by the passed provider.
+ * @param {Roo.state.Provider} provider (optional) An alternate state provider
+ */
+ restoreState : function(provider){
+ if(!provider){
+ provider = Roo.state.Manager;
}
-
-
-
-
+ var sm = new Roo.LayoutStateManager();
+ sm.init(this, provider);
},
-
+
/**
- * Returns the nested BorderLayout for this panel
- * @return {Roo.BorderLayout}
+ * Adds a batch of multiple ContentPanels dynamically by passing a special regions config object. This config
+ * object should contain properties for each region to add ContentPanels to, and each property's value should be
+ * a valid ContentPanel config object. Example:
+ * <pre><code>
+// Create the main layout
+var layout = new Roo.BorderLayout('main-ct', {
+ west: {
+ split:true,
+ minSize: 175,
+ titlebar: true
+ },
+ center: {
+ title:'Components'
+ }
+}, 'main-ct');
+
+// Create and add multiple ContentPanels at once via configs
+layout.batchAdd({
+ west: {
+ id: 'source-files',
+ autoCreate:true,
+ title:'Ext Source Files',
+ autoScroll:true,
+ fitToFrame:true
+ },
+ center : {
+ el: cview,
+ autoScroll:true,
+ fitToFrame:true,
+ toolbar: tb,
+ resizeEl:'cbody'
+ }
+});
+</code></pre>
+ * @param {Object} regions An object containing ContentPanel configs by region name
*/
- getLayout : function(){
- return this.layout;
+ batchAdd : function(regions){
+ this.beginUpdate();
+ for(var rname in regions){
+ var lr = this.regions[rname];
+ if(lr){
+ this.addTypedPanels(lr, regions[rname]);
+ }
+ }
+ this.endUpdate();
},
-
- /**
- * Adds a xtype elements to the layout of the nested panel
+
+ // private
+ addTypedPanels : function(lr, ps){
+ if(typeof ps == 'string'){
+ lr.add(new Roo.ContentPanel(ps));
+ }
+ else if(ps instanceof Array){
+ for(var i =0, len = ps.length; i < len; i++){
+ this.addTypedPanels(lr, ps[i]);
+ }
+ }
+ else if(!ps.events){ // raw config?
+ var el = ps.el;
+ delete ps.el; // prevent conflict
+ lr.add(new Roo.ContentPanel(el || Roo.id(), ps));
+ }
+ else { // panel object assumed!
+ lr.add(ps);
+ }
+ },
+ /**
+ * Adds a xtype elements to the layout.
* <pre><code>
-panel.addxtype({
+layout.addxtype({
xtype : 'ContentPanel',
region: 'west',
items: [ .... ]
}
);
-panel.addxtype({
+layout.addxtype({
xtype : 'NestedLayoutPanel',
region: 'west',
layout: {
</code></pre>
* @param {Object} cfg Xtype definition of item to add.
*/
- addxtype : function(cfg) {
- return this.layout.addxtype(cfg);
-
- }
-});
-
-Roo.ScrollPanel = function(el, config, content){
- config = config || {};
- config.fitToFrame = true;
- Roo.ScrollPanel.superclass.constructor.call(this, el, config, content);
-
- this.el.dom.style.overflow = "hidden";
- var wrap = this.el.wrap({cls: "x-scroller x-layout-inactive-content"});
- this.el.removeClass("x-layout-inactive-content");
- this.el.on("mousewheel", this.onWheel, this);
-
- var up = wrap.createChild({cls: "x-scroller-up", html: " "}, this.el.dom);
- var down = wrap.createChild({cls: "x-scroller-down", html: " "});
- up.unselectable(); down.unselectable();
- up.on("click", this.scrollUp, this);
- down.on("click", this.scrollDown, this);
- up.addClassOnOver("x-scroller-btn-over");
- down.addClassOnOver("x-scroller-btn-over");
- up.addClassOnClick("x-scroller-btn-click");
- down.addClassOnClick("x-scroller-btn-click");
- this.adjustments = [0, -(up.getHeight() + down.getHeight())];
-
- this.resizeEl = this.el;
- this.el = wrap; this.up = up; this.down = down;
-};
-
-Roo.extend(Roo.ScrollPanel, Roo.ContentPanel, {
- increment : 100,
- wheelIncrement : 5,
- scrollUp : function(){
- this.resizeEl.scroll("up", this.increment, {callback: this.afterScroll, scope: this});
- },
-
- scrollDown : function(){
- this.resizeEl.scroll("down", this.increment, {callback: this.afterScroll, scope: this});
- },
-
- afterScroll : function(){
- var el = this.resizeEl;
- var t = el.dom.scrollTop, h = el.dom.scrollHeight, ch = el.dom.clientHeight;
- this.up[t == 0 ? "addClass" : "removeClass"]("x-scroller-btn-disabled");
- this.down[h - t <= ch ? "addClass" : "removeClass"]("x-scroller-btn-disabled");
- },
-
- setSize : function(){
- Roo.ScrollPanel.superclass.setSize.apply(this, arguments);
- this.afterScroll();
- },
-
- onWheel : function(e){
- var d = e.getWheelDelta();
- this.resizeEl.dom.scrollTop -= (d*this.wheelIncrement);
- this.afterScroll();
- e.stopEvent();
- },
-
- setContent : function(content, loadScripts){
- this.resizeEl.update(content, loadScripts);
- }
-
-});
-
-
-
-
-
-
-
-
-
-/**
- * @class Roo.TreePanel
- * @extends Roo.ContentPanel
- * @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;
- var tree = config.tree;
- delete config.tree;
- delete config.el; // hopefull!
-
- // wrapper for IE7 strict & safari scroll issue
-
- var treeEl = el.createChild();
- config.resizeEl = treeEl;
-
-
-
- Roo.TreePanel.superclass.constructor.call(this, el, config);
-
-
- this.tree = new Roo.tree.TreePanel(treeEl , tree);
- //console.log(tree);
- this.on('activate', function()
+ addxtype : function(cfg)
{
- if (this.tree.rendered) {
- return;
+ // basically accepts a pannel...
+ // can accept a layout region..!?!?
+ //Roo.log('Roo.BorderLayout add ' + cfg.xtype)
+
+ if (!cfg.xtype.match(/Panel$/)) {
+ return false;
}
- //console.log('render tree');
- this.tree.render();
- });
- // this should not be needed.. - it's actually the 'el' that resizes?
- // actuall it breaks the containerScroll - dragging nodes auto scroll at top
-
- //this.on('resize', function (cp, w, h) {
- // this.tree.innerCt.setWidth(w);
- // this.tree.innerCt.setHeight(h);
- // //this.tree.innerCt.setStyle('overflow-y', 'auto');
- //});
-
+ var ret = false;
-
-};
-
-Roo.extend(Roo.TreePanel, Roo.ContentPanel, {
- fitToFrame : true,
- autoScroll : true
-});
-
-
-
-
-
-
-
-
-
-
-
-/*
- * 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">
- */
-
-
-/**
- * @class Roo.ReaderLayout
- * @extends Roo.BorderLayout
- * This is a pre-built layout that represents a classic, 5-pane application. It consists of a header, a primary
- * center region containing two nested regions (a top one for a list view and one for item preview below),
- * and regions on either side that can be used for navigation, application commands, informational displays, etc.
- * The setup and configuration work exactly the same as it does for a {@link Roo.BorderLayout} - this class simply
- * expedites the setup of the overall layout and regions for this common application style.
- * Example:
- <pre><code>
-var reader = new Roo.ReaderLayout();
-var CP = Roo.ContentPanel; // shortcut for adding
-
-reader.beginUpdate();
-reader.add("north", new CP("north", "North"));
-reader.add("west", new CP("west", {title: "West"}));
-reader.add("east", new CP("east", {title: "East"}));
-
-reader.regions.listView.add(new CP("listView", "List"));
-reader.regions.preview.add(new CP("preview", "Preview"));
-reader.endUpdate();
-</code></pre>
-* @constructor
-* Create a new ReaderLayout
-* @param {Object} config Configuration options
-* @param {String/HTMLElement/Element} container (optional) The container this layout is bound to (defaults to
-* document.body if omitted)
-*/
-Roo.ReaderLayout = function(config, renderTo){
- var c = config || {size:{}};
- Roo.ReaderLayout.superclass.constructor.call(this, renderTo || document.body, {
- north: c.north !== false ? Roo.apply({
- split:false,
- initialSize: 32,
- titlebar: false
- }, c.north) : false,
- west: c.west !== false ? Roo.apply({
- split:true,
- initialSize: 200,
- minSize: 175,
- maxSize: 400,
- titlebar: true,
- collapsible: true,
- animate: true,
- margins:{left:5,right:0,bottom:5,top:5},
- cmargins:{left:5,right:5,bottom:5,top:5}
- }, c.west) : false,
- east: c.east !== false ? Roo.apply({
- split:true,
- initialSize: 200,
- minSize: 175,
- maxSize: 400,
- titlebar: true,
- collapsible: true,
- animate: true,
- margins:{left:0,right:5,bottom:5,top:5},
- cmargins:{left:5,right:5,bottom:5,top:5}
- }, c.east) : false,
- center: Roo.apply({
- tabPosition: 'top',
- autoScroll:false,
- closeOnTab: true,
- titlebar:false,
- margins:{left:c.west!==false ? 0 : 5,right:c.east!==false ? 0 : 5,bottom:5,top:2}
- }, c.center)
- });
-
- this.el.addClass('x-reader');
+ if (typeof(cfg.region) == 'undefined') {
+ Roo.log("Failed to add Panel, region was not set");
+ Roo.log(cfg);
+ return false;
+ }
+ var region = cfg.region;
+ delete cfg.region;
+
+
+ var xitems = [];
+ if (cfg.items) {
+ xitems = cfg.items;
+ delete cfg.items;
+ }
+ var nb = false;
+
+ switch(cfg.xtype)
+ {
+ case 'ContentPanel': // ContentPanel (el, cfg)
+ case 'ScrollPanel': // ContentPanel (el, cfg)
+ case 'ViewPanel':
+ if(cfg.autoCreate) {
+ ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
+ } else {
+ var el = this.el.createChild();
+ ret = new Roo[cfg.xtype](el, cfg); // new panel!!!!!
+ }
+
+ this.add(region, ret);
+ break;
+
+
+ case 'TreePanel': // our new panel!
+ cfg.el = this.el.createChild();
+ ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
+ this.add(region, ret);
+ break;
+
+ case 'NestedLayoutPanel':
+ // create a new Layout (which is a Border Layout...
+ var el = this.el.createChild();
+ var clayout = cfg.layout;
+ delete cfg.layout;
+ clayout.items = clayout.items || [];
+ // replace this exitems with the clayout ones..
+ xitems = clayout.items;
+
+
+ if (region == 'center' && this.active && this.getRegion('center').panels.length < 1) {
+ cfg.background = false;
+ }
+ var layout = new Roo.BorderLayout(el, clayout);
+
+ ret = new Roo[cfg.xtype](layout, cfg); // new panel!!!!!
+ //console.log('adding nested layout panel ' + cfg.toSource());
+ this.add(region, ret);
+ nb = {}; /// find first...
+ break;
+
+ case 'GridPanel':
+
+ // needs grid and region
+
+ //var el = this.getRegion(region).el.createChild();
+ var el = this.el.createChild();
+ // create the grid first...
+
+ var grid = new Roo.grid[cfg.grid.xtype](el, cfg.grid);
+ delete cfg.grid;
+ if (region == 'center' && this.active ) {
+ cfg.background = false;
+ }
+ ret = new Roo[cfg.xtype](grid, cfg); // new panel!!!!!
+
+ this.add(region, ret);
+ if (cfg.background) {
+ ret.on('activate', function(gp) {
+ if (!gp.grid.rendered) {
+ gp.grid.render();
+ }
+ });
+ } else {
+ grid.render();
+ }
+ break;
+
+
+
+
+
+
+ default:
+ if (typeof(Roo[cfg.xtype]) != 'undefined') {
+
+ ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
+ this.add(region, ret);
+ } else {
+
+ alert("Can not add '" + cfg.xtype + "' to BorderLayout");
+ return null;
+ }
+
+ // GridPanel (grid, cfg)
+
+ }
+ this.beginUpdate();
+ // add children..
+ var region = '';
+ var abn = {};
+ Roo.each(xitems, function(i) {
+ region = nb && i.region ? i.region : false;
+
+ var add = ret.addxtype(i);
+
+ if (region) {
+ nb[region] = nb[region] == undefined ? 0 : nb[region]+1;
+ if (!i.background) {
+ abn[region] = nb[region] ;
+ }
+ }
+
+ });
+ this.endUpdate();
- this.beginUpdate();
+ // make the last non-background panel active..
+ //if (nb) { Roo.log(abn); }
+ if (nb) {
+
+ for(var r in abn) {
+ region = this.getRegion(r);
+ if (region) {
+ // tried using nb[r], but it does not work..
+
+ region.showPanel(abn[r]);
+
+ }
+ }
+ }
+ return ret;
+
+ }
+});
- var inner = new Roo.BorderLayout(Roo.get(document.body).createChild(), {
- south: c.preview !== false ? Roo.apply({
- split:true,
- initialSize: 200,
- minSize: 100,
- autoScroll:true,
- collapsible:true,
- titlebar: true,
- cmargins:{top:5,left:0, right:0, bottom:0}
- }, c.preview) : false,
- center: Roo.apply({
- autoScroll:false,
- titlebar:false,
- minHeight:200
- }, c.listView)
- });
- this.add('center', new Roo.NestedLayoutPanel(inner,
- Roo.apply({title: c.mainTitle || '',tabTip:''},c.innerPanelCfg)));
+/**
+ * Shortcut for creating a new BorderLayout object and adding one or more ContentPanels to it in a single step, handling
+ * the beginUpdate and endUpdate calls internally. The key to this method is the <b>panels</b> property that can be
+ * provided with each region config, which allows you to add ContentPanel configs in addition to the region configs
+ * during creation. The following code is equivalent to the constructor-based example at the beginning of this class:
+ * <pre><code>
+// shorthand
+var CP = Roo.ContentPanel;
- this.endUpdate();
+var layout = Roo.BorderLayout.create({
+ north: {
+ initialSize: 25,
+ titlebar: false,
+ panels: [new CP("north", "North")]
+ },
+ west: {
+ split:true,
+ initialSize: 200,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true,
+ panels: [new CP("west", {title: "West"})]
+ },
+ east: {
+ split:true,
+ initialSize: 202,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true,
+ panels: [new CP("autoTabs", {title: "Auto Tabs", closable: true})]
+ },
+ south: {
+ split:true,
+ initialSize: 100,
+ minSize: 100,
+ maxSize: 200,
+ titlebar: true,
+ collapsible: true,
+ panels: [new CP("south", {title: "South", closable: true})]
+ },
+ center: {
+ titlebar: true,
+ autoScroll:true,
+ resizeTabs: true,
+ minTabWidth: 50,
+ preferredTabWidth: 150,
+ panels: [
+ new CP("center1", {title: "Close Me", closable: true}),
+ new CP("center2", {title: "Center Panel", closable: false})
+ ]
+ }
+}, document.body);
- this.regions.preview = inner.getRegion('south');
- this.regions.listView = inner.getRegion('center');
+layout.getRegion("center").showPanel("center1");
+</code></pre>
+ * @param config
+ * @param targetEl
+ */
+Roo.BorderLayout.create = function(config, targetEl){
+ var layout = new Roo.BorderLayout(targetEl || document.body, config);
+ layout.beginUpdate();
+ var regions = Roo.BorderLayout.RegionFactory.validRegions;
+ for(var j = 0, jlen = regions.length; j < jlen; j++){
+ var lr = regions[j];
+ if(layout.regions[lr] && config[lr].panels){
+ var r = layout.regions[lr];
+ var ps = config[lr].panels;
+ layout.addTypedPanels(r, ps);
+ }
+ }
+ layout.endUpdate();
+ return layout;
};
-Roo.extend(Roo.ReaderLayout, Roo.BorderLayout);/*
+// private
+Roo.BorderLayout.RegionFactory = {
+ // private
+ validRegions : ["north","south","east","west","center"],
+
+ // private
+ create : function(target, mgr, config){
+ target = target.toLowerCase();
+ if(config.lightweight || config.basic){
+ return new Roo.BasicLayoutRegion(mgr, config, target);
+ }
+ switch(target){
+ case "north":
+ return new Roo.NorthLayoutRegion(mgr, config);
+ case "south":
+ return new Roo.SouthLayoutRegion(mgr, config);
+ case "east":
+ return new Roo.EastLayoutRegion(mgr, config);
+ case "west":
+ return new Roo.WestLayoutRegion(mgr, config);
+ case "center":
+ return new Roo.CenterLayoutRegion(mgr, config);
+ }
+ throw 'Layout region "'+target+'" not supported.';
+ }
+};/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
*/
/**
- * @class Roo.grid.Grid
+ * @class Roo.BasicLayoutRegion
* @extends Roo.util.Observable
- * This class represents the primary interface of a component based grid control.
- * <br><br>Usage:<pre><code>
- var grid = new Roo.grid.Grid("my-container-id", {
- ds: myDataStore,
- cm: myColModel,
- selModel: mySelectionModel,
- autoSizeColumns: true,
- monitorWindowResize: false,
- trackMouseOver: true
- });
- // set any options
- grid.render();
- * </code></pre>
- * <b>Common Problems:</b><br/>
- * - Grid does not resize properly when going smaller: Setting overflow hidden on the container
- * element will correct this<br/>
- * - If you get el.style[camel]= NaNpx or -2px or something related, be certain you have given your container element
- * dimensions. The grid adapts to your container's size, if your container has no size defined then the results
- * are unpredictable.<br/>
- * - Do not render the grid into an element with display:none. Try using visibility:hidden. Otherwise there is no way for the
- * grid to calculate dimensions/offsets.<br/>
- * @constructor
- * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
- * The container MUST have some type of size defined for the grid to fill. The container will be
- * automatically set to position relative if it isn't already.
- * @param {Object} config A config object that sets properties on this grid.
+ * This class represents a lightweight region in a layout manager. This region does not move dom nodes
+ * and does not have a titlebar, tabs or any other features. All it does is size and position
+ * panels. To create a BasicLayoutRegion, add lightweight:true or basic:true to your regions config.
*/
-Roo.grid.Grid = function(container, config){
- // initialize the container
- this.container = Roo.get(container);
- this.container.update("");
- this.container.setStyle("overflow", "hidden");
- this.container.addClass('x-grid-container');
-
- this.id = this.container.id;
-
- Roo.apply(this, config);
- // check and correct shorthanded configs
- if(this.ds){
- this.dataSource = this.ds;
- delete this.ds;
- }
- if(this.cm){
- this.colModel = this.cm;
- delete this.cm;
- }
- if(this.sm){
- this.selModel = this.sm;
- delete this.sm;
- }
-
- if (this.selModel) {
- this.selModel = Roo.factory(this.selModel, Roo.grid);
- this.sm = this.selModel;
- this.sm.xmodule = this.xmodule || false;
- }
- if (typeof(this.colModel.config) == 'undefined') {
- this.colModel = new Roo.grid.ColumnModel(this.colModel);
- this.cm = this.colModel;
- this.cm.xmodule = this.xmodule || false;
- }
- if (this.dataSource) {
- this.dataSource= Roo.factory(this.dataSource, Roo.data);
- this.ds = this.dataSource;
- this.ds.xmodule = this.xmodule || false;
-
- }
-
-
-
- if(this.width){
- this.container.setWidth(this.width);
- }
-
- if(this.height){
- this.container.setHeight(this.height);
- }
- /** @private */
- this.addEvents({
- // raw events
+Roo.BasicLayoutRegion = function(mgr, config, pos, skipConfig){
+ this.mgr = mgr;
+ this.position = pos;
+ this.events = {
/**
- * @event click
- * The raw click event for the entire grid.
- * @param {Roo.EventObject} e
+ * @scope Roo.BasicLayoutRegion
*/
- "click" : true,
+
/**
- * @event dblclick
- * The raw dblclick event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event beforeremove
+ * Fires before a panel is removed (or closed). To cancel the removal set "e.cancel = true" on the event argument.
+ * @param {Roo.LayoutRegion} this
+ * @param {Roo.ContentPanel} panel The panel
+ * @param {Object} e The cancel event object
*/
- "dblclick" : true,
+ "beforeremove" : true,
/**
- * @event contextmenu
- * The raw contextmenu event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event invalidated
+ * Fires when the layout for this region is changed.
+ * @param {Roo.LayoutRegion} this
*/
- "contextmenu" : true,
+ "invalidated" : true,
/**
- * @event mousedown
- * The raw mousedown event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event visibilitychange
+ * Fires when this region is shown or hidden
+ * @param {Roo.LayoutRegion} this
+ * @param {Boolean} visibility true or false
*/
- "mousedown" : true,
+ "visibilitychange" : true,
/**
- * @event mouseup
- * The raw mouseup event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event paneladded
+ * Fires when a panel is added.
+ * @param {Roo.LayoutRegion} this
+ * @param {Roo.ContentPanel} panel The panel
*/
- "mouseup" : true,
+ "paneladded" : true,
/**
- * @event mouseover
- * The raw mouseover event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event panelremoved
+ * Fires when a panel is removed.
+ * @param {Roo.LayoutRegion} this
+ * @param {Roo.ContentPanel} panel The panel
*/
- "mouseover" : true,
+ "panelremoved" : true,
/**
- * @event mouseout
- * The raw mouseout event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event beforecollapse
+ * Fires when this region before collapse.
+ * @param {Roo.LayoutRegion} this
*/
- "mouseout" : true,
+ "beforecollapse" : true,
/**
- * @event keypress
- * The raw keypress event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event collapsed
+ * Fires when this region is collapsed.
+ * @param {Roo.LayoutRegion} this
*/
- "keypress" : true,
+ "collapsed" : true,
/**
- * @event keydown
- * The raw keydown event for the entire grid.
- * @param {Roo.EventObject} e
+ * @event expanded
+ * Fires when this region is expanded.
+ * @param {Roo.LayoutRegion} this
*/
- "keydown" : true,
-
- // custom events
-
+ "expanded" : true,
/**
- * @event cellclick
- * Fires when a cell is clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
+ * @event slideshow
+ * Fires when this region is slid into view.
+ * @param {Roo.LayoutRegion} this
*/
- "cellclick" : true,
+ "slideshow" : true,
/**
- * @event celldblclick
- * Fires when a cell is double clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
+ * @event slidehide
+ * Fires when this region slides out of view.
+ * @param {Roo.LayoutRegion} this
*/
- "celldblclick" : true,
+ "slidehide" : true,
/**
- * @event rowclick
- * Fires when a row is clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Roo.EventObject} e
+ * @event panelactivated
+ * Fires when a panel is activated.
+ * @param {Roo.LayoutRegion} this
+ * @param {Roo.ContentPanel} panel The activated panel
*/
- "rowclick" : true,
+ "panelactivated" : true,
/**
- * @event rowdblclick
- * Fires when a row is double clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Roo.EventObject} e
- */
- "rowdblclick" : true,
- /**
- * @event headerclick
- * Fires when a header is clicked
- * @param {Grid} this
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "headerclick" : true,
- /**
- * @event headerdblclick
- * Fires when a header cell is double clicked
- * @param {Grid} this
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "headerdblclick" : true,
- /**
- * @event rowcontextmenu
- * Fires when a row is right clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Roo.EventObject} e
- */
- "rowcontextmenu" : true,
- /**
- * @event cellcontextmenu
- * Fires when a cell is right clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Number} cellIndex
- * @param {Roo.EventObject} e
- */
- "cellcontextmenu" : true,
- /**
- * @event headercontextmenu
- * Fires when a header is right clicked
- * @param {Grid} this
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "headercontextmenu" : true,
- /**
- * @event bodyscroll
- * Fires when the body element is scrolled
- * @param {Number} scrollLeft
- * @param {Number} scrollTop
- */
- "bodyscroll" : true,
- /**
- * @event columnresize
- * Fires when the user resizes a column
- * @param {Number} columnIndex
- * @param {Number} newSize
- */
- "columnresize" : true,
- /**
- * @event columnmove
- * Fires when the user moves a column
- * @param {Number} oldIndex
- * @param {Number} newIndex
- */
- "columnmove" : true,
- /**
- * @event startdrag
- * Fires when row(s) start being dragged
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {event} e The raw browser event
- */
- "startdrag" : true,
- /**
- * @event enddrag
- * Fires when a drag operation is complete
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {event} e The raw browser event
- */
- "enddrag" : true,
- /**
- * @event dragdrop
- * Fires when dragged row(s) are dropped on a valid DD target
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
- */
- "dragdrop" : true,
- /**
- * @event dragover
- * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
- */
- "dragover" : true,
- /**
- * @event dragenter
- * Fires when the dragged row(s) first cross another DD target while being dragged
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
- */
- "dragenter" : true,
- /**
- * @event dragout
- * Fires when the dragged row(s) leave another DD target while being dragged
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
- */
- "dragout" : true,
- /**
- * @event rowclass
- * Fires when a row is rendered, so you can change add a style to it.
- * @param {GridView} gridview The grid view
- * @param {Object} rowcfg contains record rowIndex and rowClass - set rowClass to add a style.
- */
- 'rowclass' : true,
-
- /**
- * @event render
- * Fires when the grid is rendered
- * @param {Grid} grid
+ * @event resized
+ * Fires when the user resizes this region.
+ * @param {Roo.LayoutRegion} this
+ * @param {Number} newSize The new size (width for east/west, height for north/south)
*/
- 'render' : true
- });
-
- Roo.grid.Grid.superclass.constructor.call(this);
-};
-Roo.extend(Roo.grid.Grid, Roo.util.Observable, {
+ "resized" : true
+ };
+ /** A collection of panels in this region. @type Roo.util.MixedCollection */
+ this.panels = new Roo.util.MixedCollection();
+ this.panels.getKey = this.getPanelId.createDelegate(this);
+ this.box = null;
+ this.activePanel = null;
+ // ensure listeners are added...
- /**
- * @cfg {String} ddGroup - drag drop group.
- */
-
- /**
- * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Default is 25.
- */
- minColumnWidth : 25,
-
- /**
- * @cfg {Boolean} autoSizeColumns True to automatically resize the columns to fit their content
- * <b>on initial render.</b> It is more efficient to explicitly size the columns
- * through the ColumnModel's {@link Roo.grid.ColumnModel#width} config option. Default is false.
- */
- autoSizeColumns : false,
-
- /**
- * @cfg {Boolean} autoSizeHeaders True to measure headers with column data when auto sizing columns. Default is true.
- */
- autoSizeHeaders : true,
-
- /**
- * @cfg {Boolean} monitorWindowResize True to autoSize the grid when the window resizes. Default is true.
- */
- monitorWindowResize : true,
-
- /**
- * @cfg {Boolean} maxRowsToMeasure If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
- * rows measured to get a columns size. Default is 0 (all rows).
- */
- maxRowsToMeasure : 0,
-
- /**
- * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is true.
- */
- trackMouseOver : true,
+ if (config.listeners || config.events) {
+ Roo.BasicLayoutRegion.superclass.constructor.call(this, {
+ listeners : config.listeners || {},
+ events : config.events || {}
+ });
+ }
+
+ if(skipConfig !== true){
+ this.applyConfig(config);
+ }
+};
- /**
- * @cfg {Boolean} enableDrag True to enable drag of rows. Default is false. (double check if this is needed?)
- */
+Roo.extend(Roo.BasicLayoutRegion, Roo.util.Observable, {
+ getPanelId : function(p){
+ return p.getId();
+ },
- /**
- * @cfg {Boolean} enableDragDrop True to enable drag and drop of rows. Default is false.
- */
- enableDragDrop : false,
+ applyConfig : function(config){
+ this.margins = config.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
+ this.config = config;
+
+ },
/**
- * @cfg {Boolean} enableColumnMove True to enable drag and drop reorder of columns. Default is true.
- */
- enableColumnMove : true,
+ * Resizes the region to the specified size. For vertical regions (west, east) this adjusts
+ * the width, for horizontal (north, south) the height.
+ * @param {Number} newSize The new width or height
+ */
+ resizeTo : function(newSize){
+ var el = this.el ? this.el :
+ (this.activePanel ? this.activePanel.getEl() : null);
+ if(el){
+ switch(this.position){
+ case "east":
+ case "west":
+ el.setWidth(newSize);
+ this.fireEvent("resized", this, newSize);
+ break;
+ case "north":
+ case "south":
+ el.setHeight(newSize);
+ this.fireEvent("resized", this, newSize);
+ break;
+ }
+ }
+ },
- /**
- * @cfg {Boolean} enableColumnHide True to enable hiding of columns with the header context menu. Default is true.
- */
- enableColumnHide : true,
+ getBox : function(){
+ return this.activePanel ? this.activePanel.getEl().getBox(false, true) : null;
+ },
- /**
- * @cfg {Boolean} enableRowHeightSync True to manually sync row heights across locked and not locked rows. Default is false.
- */
- enableRowHeightSync : false,
+ getMargins : function(){
+ return this.margins;
+ },
- /**
- * @cfg {Boolean} stripeRows True to stripe the rows. Default is true.
- */
- stripeRows : true,
+ updateBox : function(box){
+ this.box = box;
+ var el = this.activePanel.getEl();
+ el.dom.style.left = box.x + "px";
+ el.dom.style.top = box.y + "px";
+ this.activePanel.setSize(box.width, box.height);
+ },
/**
- * @cfg {Boolean} autoHeight True to fit the height of the grid container to the height of the data. Default is false.
- */
- autoHeight : false,
-
- /**
- * @cfg {String} autoExpandColumn The id (or dataIndex) of a column in this grid that should expand to fill unused space. This id can not be 0. Default is false.
+ * Returns the container element for this region.
+ * @return {Roo.Element}
*/
- autoExpandColumn : false,
-
- /**
- * @cfg {Number} autoExpandMin The minimum width the autoExpandColumn can have (if enabled).
- * Default is 50.
- */
- autoExpandMin : 50,
-
- /**
- * @cfg {Number} autoExpandMax The maximum width the autoExpandColumn can have (if enabled). Default is 1000.
- */
- autoExpandMax : 1000,
-
- /**
- * @cfg {Object} view The {@link Roo.grid.GridView} used by the grid. This can be set before a call to render().
- */
- view : null,
-
- /**
- * @cfg {Object} loadMask An {@link Roo.LoadMask} config or true to mask the grid while loading. Default is false.
- */
- loadMask : false,
+ getEl : function(){
+ return this.activePanel;
+ },
+
/**
- * @cfg {Roo.dd.DropTarget} dropTarget An {@link Roo.dd.DropTarget} config
- */
- dropTarget: false,
+ * Returns true if this region is currently visible.
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return this.activePanel ? true : false;
+ },
-
+ setActivePanel : function(panel){
+ panel = this.getPanel(panel);
+ if(this.activePanel && this.activePanel != panel){
+ this.activePanel.setActiveState(false);
+ this.activePanel.getEl().setLeftTop(-10000,-10000);
+ }
+ this.activePanel = panel;
+ panel.setActiveState(true);
+ if(this.box){
+ panel.setSize(this.box.width, this.box.height);
+ }
+ this.fireEvent("panelactivated", this, panel);
+ this.fireEvent("invalidated");
+ },
- // private
- rendered : false,
-
/**
- * @cfg {Boolean} autoWidth True to set the grid's width to the default total width of the grid's columns instead
- * of a fixed width. Default is false.
- */
+ * Show the specified panel.
+ * @param {Number/String/ContentPanel} panelId The panels index, id or the panel itself
+ * @return {Roo.ContentPanel} The shown panel or null
+ */
+ showPanel : function(panel){
+ if(panel = this.getPanel(panel)){
+ this.setActivePanel(panel);
+ }
+ return panel;
+ },
+
/**
- * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if autoHeight is not on.
- */
+ * Get the active panel for this region.
+ * @return {Roo.ContentPanel} The active panel or null
+ */
+ getActivePanel : function(){
+ return this.activePanel;
+ },
+
/**
- * Called once after all setup has been completed and the grid is ready to be rendered.
- * @return {Roo.grid.Grid} this
+ * Add the passed ContentPanel(s)
+ * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
+ * @return {Roo.ContentPanel} The panel added (if only one was added)
*/
- render : function()
- {
- var c = this.container;
- // try to detect autoHeight/width mode
- if((!c.dom.offsetHeight || c.dom.offsetHeight < 20) || c.getStyle("height") == "auto"){
- this.autoHeight = true;
- }
- var view = this.getView();
- view.init(this);
-
- c.on("click", this.onClick, this);
- c.on("dblclick", this.onDblClick, this);
- c.on("contextmenu", this.onContextMenu, this);
- c.on("keydown", this.onKeyDown, this);
- if (Roo.isTouch) {
- c.on("touchstart", this.onTouchStart, this);
+ add : function(panel){
+ if(arguments.length > 1){
+ for(var i = 0, len = arguments.length; i < len; i++) {
+ this.add(arguments[i]);
+ }
+ return null;
}
-
- this.relayEvents(c, ["mousedown","mouseup","mouseover","mouseout","keypress"]);
-
- this.getSelectionModel().init(this);
-
- view.render();
-
- if(this.loadMask){
- this.loadMask = new Roo.LoadMask(this.container,
- Roo.apply({store:this.dataSource}, this.loadMask));
+ if(this.hasPanel(panel)){
+ this.showPanel(panel);
+ return panel;
}
-
-
- if (this.toolbar && this.toolbar.xtype) {
- this.toolbar.container = this.getView().getHeaderPanel(true);
- this.toolbar = new Roo.Toolbar(this.toolbar);
+ var el = panel.getEl();
+ if(el.dom.parentNode != this.mgr.el.dom){
+ this.mgr.el.dom.appendChild(el.dom);
}
- if (this.footer && this.footer.xtype) {
- this.footer.dataSource = this.getDataSource();
- this.footer.container = this.getView().getFooterPanel(true);
- this.footer = Roo.factory(this.footer, Roo);
+ if(panel.setRegion){
+ panel.setRegion(this);
}
- if (this.dropTarget && this.dropTarget.xtype) {
- delete this.dropTarget.xtype;
- this.dropTarget = new Roo.dd.DropTarget(this.getView().mainBody, this.dropTarget);
+ this.panels.add(panel);
+ el.setStyle("position", "absolute");
+ if(!panel.background){
+ this.setActivePanel(panel);
+ if(this.config.initialSize && this.panels.getCount()==1){
+ this.resizeTo(this.config.initialSize);
+ }
}
-
-
- this.rendered = true;
- this.fireEvent('render', this);
- return this;
+ this.fireEvent("paneladded", this, panel);
+ return panel;
},
-
+
/**
- * 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
+ * Returns true if the panel is in this region.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @return {Boolean}
*/
- reconfigure : function(dataSource, colModel){
- if(this.loadMask){
- this.loadMask.destroy();
- this.loadMask = new Roo.LoadMask(this.container,
- Roo.apply({store:dataSource}, this.loadMask));
+ hasPanel : function(panel){
+ if(typeof panel == "object"){ // must be panel obj
+ panel = panel.getId();
}
- this.view.bind(dataSource, colModel);
- this.dataSource = dataSource;
- this.colModel = colModel;
- this.view.refresh(true);
+ return this.getPanel(panel) ? true : false;
},
+
/**
- * 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}
+ * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @param {Boolean} preservePanel Overrides the config preservePanel option
+ * @return {Roo.ContentPanel} The panel that was removed
*/
- 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;
+ remove : function(panel, preservePanel){
+ panel = this.getPanel(panel);
+ if(!panel){
+ return null;
}
-
-
- 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);
-
+ var e = {};
+ this.fireEvent("beforeremove", this, panel, e);
+ if(e.cancel === true){
+ return null;
+ }
+ var panelId = panel.getId();
+ this.panels.removeKey(panelId);
+ return panel;
},
-
-
-
- // private
- onKeyDown : function(e){
- this.fireEvent("keydown", e);
- },
-
/**
- * Destroy this grid.
- * @param {Boolean} removeEl True to remove the element
+ * Returns the panel specified or null if it's not in this region.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @return {Roo.ContentPanel}
*/
- destroy : function(removeEl, keepListeners){
- if(this.loadMask){
- this.loadMask.destroy();
- }
- var c = this.container;
- c.removeAllListeners();
- this.view.destroy();
- this.colModel.purgeListeners();
- if(!keepListeners){
- this.purgeListeners();
- }
- c.update("");
- if(removeEl === true){
- c.remove();
+ getPanel : function(id){
+ if(typeof id == "object"){ // must be panel obj
+ return id;
}
+ return this.panels.get(id);
},
+
+ /**
+ * Returns this regions position (north/south/east/west/center).
+ * @return {String}
+ */
+ getPosition: function(){
+ return this.position;
+ }
+});/*
+ * 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">
+ */
+
+/**
+ * @class Roo.LayoutRegion
+ * @extends Roo.BasicLayoutRegion
+ * This class represents a region in a layout manager.
+ * @cfg {Boolean} collapsible False to disable collapsing (defaults to true)
+ * @cfg {Boolean} collapsed True to set the initial display to collapsed (defaults to false)
+ * @cfg {Boolean} floatable False to disable floating (defaults to true)
+ * @cfg {Object} margins Margins for the element (defaults to {top: 0, left: 0, right:0, bottom: 0})
+ * @cfg {Object} cmargins Margins for the element when collapsed (defaults to: north/south {top: 2, left: 0, right:0, bottom: 2} or east/west {top: 0, left: 2, right:2, bottom: 0})
+ * @cfg {String} tabPosition (top|bottom) "top" or "bottom" (defaults to "bottom")
+ * @cfg {String} collapsedTitle Optional string message to display in the collapsed block of a north or south region
+ * @cfg {Boolean} alwaysShowTabs True to always display tabs even when there is only 1 panel (defaults to false)
+ * @cfg {Boolean} autoScroll True to enable overflow scrolling (defaults to false)
+ * @cfg {Boolean} titlebar True to display a title bar (defaults to true)
+ * @cfg {String} title The title for the region (overrides panel titles)
+ * @cfg {Boolean} animate True to animate expand/collapse (defaults to false)
+ * @cfg {Boolean} autoHide False to disable auto hiding when the mouse leaves the "floated" region (defaults to true)
+ * @cfg {Boolean} preservePanels True to preserve removed panels so they can be readded later (defaults to false)
+ * @cfg {Boolean} closeOnTab True to place the close icon on the tabs instead of the region titlebar (defaults to false)
+ * @cfg {Boolean} hideTabs True to hide the tab strip (defaults to false)
+ * @cfg {Boolean} resizeTabs True to enable automatic tab resizing. This will resize the tabs so they are all the same size and fit within
+ * the space available, similar to FireFox 1.5 tabs (defaults to false)
+ * @cfg {Number} minTabWidth The minimum tab width (defaults to 40)
+ * @cfg {Number} preferredTabWidth The preferred tab width (defaults to 150)
+ * @cfg {Boolean} showPin True to show a pin button
+ * @cfg {Boolean} hidden True to start the region hidden (defaults to false)
+ * @cfg {Boolean} hideWhenEmpty True to hide the region when it has no panels
+ * @cfg {Boolean} disableTabTips True to disable tab tooltips
+ * @cfg {Number} width For East/West panels
+ * @cfg {Number} height For North/South panels
+ * @cfg {Boolean} split To show the splitter
+ * @cfg {Boolean} toolbar xtype configuration for a toolbar - shows on right of tabbar
+ */
+Roo.LayoutRegion = function(mgr, config, pos){
+ Roo.LayoutRegion.superclass.constructor.call(this, mgr, config, pos, true);
+ var dh = Roo.DomHelper;
+ /** This region's container element
+ * @type Roo.Element */
+ this.el = dh.append(mgr.el.dom, {tag: "div", cls: "x-layout-panel x-layout-panel-" + this.position}, true);
+ /** This region's title element
+ * @type Roo.Element */
- // private
- processEvent : function(name, e){
- // does this fire select???
- //Roo.log('grid:processEvent ' + name);
-
- if (name != 'touchstart' ) {
- this.fireEvent(name, e);
- }
-
- var t = e.getTarget();
- var v = this.view;
- var header = v.findHeaderIndex(t);
- if(header !== false){
- var ename = name == 'touchstart' ? 'click' : name;
-
- this.fireEvent("header" + ename, this, header, e);
- }else{
- var row = v.findRowIndex(t);
- var cell = v.findCellIndex(t);
- if (name == 'touchstart') {
- // first touch is always a click.
- // hopefull this happens after selection is updated.?
- name = false;
-
- if (typeof(this.selModel.getSelectedCell) != 'undefined') {
- var cs = this.selModel.getSelectedCell();
- if (row == cs[0] && cell == cs[1]){
- name = 'dblclick';
- }
- }
- if (typeof(this.selModel.getSelections) != 'undefined') {
- var cs = this.selModel.getSelections();
- var ds = this.dataSource;
- if (cs.length == 1 && ds.getAt(row) == cs[0]){
- name = 'dblclick';
- }
- }
- if (!name) {
- return;
+ this.titleEl = dh.append(this.el.dom, {tag: "div", unselectable: "on", cls: "x-unselectable x-layout-panel-hd x-layout-title-"+this.position, children:[
+ {tag: "span", cls: "x-unselectable x-layout-panel-hd-text", unselectable: "on", html: " "},
+ {tag: "div", cls: "x-unselectable x-layout-panel-hd-tools", unselectable: "on"}
+ ]}, true);
+ this.titleEl.enableDisplayMode();
+ /** This region's title text element
+ * @type HTMLElement */
+ this.titleTextEl = this.titleEl.dom.firstChild;
+ this.tools = Roo.get(this.titleEl.dom.childNodes[1], true);
+ this.closeBtn = this.createTool(this.tools.dom, "x-layout-close");
+ this.closeBtn.enableDisplayMode();
+ this.closeBtn.on("click", this.closeClicked, this);
+ this.closeBtn.hide();
+
+ this.createBody(config);
+ this.visible = true;
+ this.collapsed = false;
+
+ if(config.hideWhenEmpty){
+ this.hide();
+ this.on("paneladded", this.validateVisibility, this);
+ this.on("panelremoved", this.validateVisibility, this);
+ }
+ this.applyConfig(config);
+};
+
+Roo.extend(Roo.LayoutRegion, Roo.BasicLayoutRegion, {
+
+ createBody : function(){
+ /** This region's body element
+ * @type Roo.Element */
+ this.bodyEl = this.el.createChild({tag: "div", cls: "x-layout-panel-body"});
+ },
+
+ applyConfig : function(c){
+ if(c.collapsible && this.position != "center" && !this.collapsedEl){
+ var dh = Roo.DomHelper;
+ if(c.titlebar !== false){
+ this.collapseBtn = this.createTool(this.tools.dom, "x-layout-collapse-"+this.position);
+ this.collapseBtn.on("click", this.collapse, this);
+ this.collapseBtn.enableDisplayMode();
+
+ if(c.showPin === true || this.showPin){
+ this.stickBtn = this.createTool(this.tools.dom, "x-layout-stick");
+ this.stickBtn.enableDisplayMode();
+ this.stickBtn.on("click", this.expand, this);
+ this.stickBtn.hide();
}
}
-
-
- if(row !== false){
- this.fireEvent("row" + name, this, row, e);
- if(cell !== false){
- this.fireEvent("cell" + name, this, row, cell, e);
+ /** This region's collapsed element
+ * @type Roo.Element */
+ this.collapsedEl = dh.append(this.mgr.el.dom, {cls: "x-layout-collapsed x-layout-collapsed-"+this.position, children:[
+ {cls: "x-layout-collapsed-tools", children:[{cls: "x-layout-ctools-inner"}]}
+ ]}, true);
+ if(c.floatable !== false){
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on("click", this.collapseClick, this);
+ }
+
+ if(c.collapsedTitle && (this.position == "north" || this.position== "south")) {
+ this.collapsedTitleTextEl = dh.append(this.collapsedEl.dom, {tag: "div", cls: "x-unselectable x-layout-panel-hd-text",
+ id: "message", unselectable: "on", style:{"float":"left"}});
+ this.collapsedTitleTextEl.innerHTML = c.collapsedTitle;
+ }
+ this.expandBtn = this.createTool(this.collapsedEl.dom.firstChild.firstChild, "x-layout-expand-"+this.position);
+ this.expandBtn.on("click", this.expand, this);
+ }
+ if(this.collapseBtn){
+ this.collapseBtn.setVisible(c.collapsible == true);
+ }
+ this.cmargins = c.cmargins || this.cmargins ||
+ (this.position == "west" || this.position == "east" ?
+ {top: 0, left: 2, right:2, bottom: 0} :
+ {top: 2, left: 0, right:0, bottom: 2});
+ this.margins = c.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
+ this.bottomTabs = c.tabPosition != "top";
+ this.autoScroll = c.autoScroll || false;
+ if(this.autoScroll){
+ this.bodyEl.setStyle("overflow", "auto");
+ }else{
+ this.bodyEl.setStyle("overflow", "hidden");
+ }
+ //if(c.titlebar !== false){
+ if((!c.titlebar && !c.title) || c.titlebar === false){
+ this.titleEl.hide();
+ }else{
+ this.titleEl.show();
+ if(c.title){
+ this.titleTextEl.innerHTML = c.title;
}
}
+ //}
+ this.duration = c.duration || .30;
+ this.slideDuration = c.slideDuration || .45;
+ this.config = c;
+ if(c.collapsed){
+ this.collapse(true);
+ }
+ if(c.hidden){
+ this.hide();
}
},
+ /**
+ * Returns true if this region is currently visible.
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return this.visible;
+ },
- // private
- onClick : function(e){
- this.processEvent("click", e);
+ /**
+ * Updates the title for collapsed north/south regions (used with {@link #collapsedTitle} config option)
+ * @param {String} title (optional) The title text (accepts HTML markup, defaults to the numeric character reference for a non-breaking space, "&#160;")
+ */
+ setCollapsedTitle : function(title){
+ title = title || " ";
+ if(this.collapsedTitleTextEl){
+ this.collapsedTitleTextEl.innerHTML = title;
+ }
},
- // private
- onTouchStart : function(e){
- this.processEvent("touchstart", e);
+
+ getBox : function(){
+ var b;
+ if(!this.collapsed){
+ b = this.el.getBox(false, true);
+ }else{
+ b = this.collapsedEl.getBox(false, true);
+ }
+ return b;
},
- // private
- onContextMenu : function(e, t){
- this.processEvent("contextmenu", e);
+ getMargins : function(){
+ return this.collapsed ? this.cmargins : this.margins;
},
- // private
- onDblClick : function(e){
- this.processEvent("dblclick", e);
+ highlight : function(){
+ this.el.addClass("x-layout-panel-dragover");
},
- // private
- walkCells : function(row, col, step, fn, scope){
- var cm = this.colModel, clen = cm.getColumnCount();
- var ds = this.dataSource, rlen = ds.getCount(), first = true;
- if(step < 0){
- if(col < 0){
- row--;
- first = false;
- }
- while(row >= 0){
- if(!first){
- col = clen-1;
- }
- first = false;
- while(col >= 0){
- if(fn.call(scope || this, row, col, cm) === true){
- return [row, col];
- }
- col--;
- }
- row--;
+ unhighlight : function(){
+ this.el.removeClass("x-layout-panel-dragover");
+ },
+
+ updateBox : function(box){
+ this.box = box;
+ if(!this.collapsed){
+ this.el.dom.style.left = box.x + "px";
+ this.el.dom.style.top = box.y + "px";
+ this.updateBody(box.width, box.height);
+ }else{
+ this.collapsedEl.dom.style.left = box.x + "px";
+ this.collapsedEl.dom.style.top = box.y + "px";
+ this.collapsedEl.setSize(box.width, box.height);
+ }
+ if(this.tabs){
+ this.tabs.autoSizeTabs();
+ }
+ },
+
+ updateBody : function(w, h){
+ if(w !== null){
+ this.el.setWidth(w);
+ w -= this.el.getBorderWidth("rl");
+ if(this.config.adjustments){
+ w += this.config.adjustments[0];
}
- } else {
- if(col >= clen){
- row++;
- first = false;
+ }
+ if(h !== null){
+ this.el.setHeight(h);
+ h = this.titleEl && this.titleEl.isDisplayed() ? h - (this.titleEl.getHeight()||0) : h;
+ h -= this.el.getBorderWidth("tb");
+ if(this.config.adjustments){
+ h += this.config.adjustments[1];
}
- while(row < rlen){
- if(!first){
- col = 0;
- }
- first = false;
- while(col < clen){
- if(fn.call(scope || this, row, col, cm) === true){
- return [row, col];
- }
- col++;
- }
- row++;
+ this.bodyEl.setHeight(h);
+ if(this.tabs){
+ h = this.tabs.syncHeight(h);
}
}
- return null;
+ if(this.panelSize){
+ w = w !== null ? w : this.panelSize.width;
+ h = h !== null ? h : this.panelSize.height;
+ }
+ if(this.activePanel){
+ var el = this.activePanel.getEl();
+ w = w !== null ? w : el.getWidth();
+ h = h !== null ? h : el.getHeight();
+ this.panelSize = {width: w, height: h};
+ this.activePanel.setSize(w, h);
+ }
+ if(Roo.isIE && this.tabs){
+ this.tabs.el.repaint();
+ }
},
- // private
- getSelections : function(){
- return this.selModel.getSelections();
+ /**
+ * Returns the container element for this region.
+ * @return {Roo.Element}
+ */
+ getEl : function(){
+ return this.el;
},
/**
- * Causes the grid to manually recalculate its dimensions. Generally this is done automatically,
- * but if manual update is required this method will initiate it.
+ * Hides this region.
*/
- autoSize : function(){
- if(this.rendered){
- this.view.layout();
- if(this.view.adjustForScroll){
- this.view.adjustForScroll();
- }
+ hide : function(){
+ if(!this.collapsed){
+ this.el.dom.style.left = "-2000px";
+ this.el.hide();
+ }else{
+ this.collapsedEl.dom.style.left = "-2000px";
+ this.collapsedEl.hide();
}
+ this.visible = false;
+ this.fireEvent("visibilitychange", this, false);
},
/**
- * Returns the grid's underlying element.
- * @return {Element} The element
+ * Shows this region if it was previously hidden.
*/
- getGridEl : function(){
- return this.container;
+ show : function(){
+ if(!this.collapsed){
+ this.el.show();
+ }else{
+ this.collapsedEl.show();
+ }
+ this.visible = true;
+ this.fireEvent("visibilitychange", this, true);
},
- // private for compatibility, overridden by editor grid
- stopEditing : function(){},
+ closeClicked : function(){
+ if(this.activePanel){
+ this.remove(this.activePanel);
+ }
+ },
- /**
- * Returns the grid's SelectionModel.
- * @return {SelectionModel}
- */
- getSelectionModel : function(){
- if(!this.selModel){
- this.selModel = new Roo.grid.RowSelectionModel();
+ collapseClick : function(e){
+ if(this.isSlid){
+ e.stopPropagation();
+ this.slideIn();
+ }else{
+ e.stopPropagation();
+ this.slideOut();
}
- return this.selModel;
},
/**
- * Returns the grid's DataSource.
- * @return {DataSource}
+ * Collapses this region.
+ * @param {Boolean} skipAnim (optional) true to collapse the element without animation (if animate is true)
*/
- getDataSource : function(){
- return this.dataSource;
+ collapse : function(skipAnim, skipCheck){
+ if(this.collapsed) {
+ return;
+ }
+
+ if(skipCheck || this.fireEvent("beforecollapse", this) != false){
+
+ this.collapsed = true;
+ if(this.split){
+ this.split.el.hide();
+ }
+ if(this.config.animate && skipAnim !== true){
+ this.fireEvent("invalidated", this);
+ this.animateCollapse();
+ }else{
+ this.el.setLocation(-20000,-20000);
+ this.el.hide();
+ this.collapsedEl.show();
+ this.fireEvent("collapsed", this);
+ this.fireEvent("invalidated", this);
+ }
+ }
+
},
- /**
- * Returns the grid's ColumnModel.
- * @return {ColumnModel}
- */
- getColumnModel : function(){
- return this.colModel;
+ animateCollapse : function(){
+ // overridden
},
/**
- * Returns the grid's GridView object.
- * @return {GridView}
+ * Expands this region if it was previously collapsed.
+ * @param {Roo.EventObject} e The event that triggered the expand (or null if calling manually)
+ * @param {Boolean} skipAnim (optional) true to expand the element without animation (if animate is true)
*/
- getView : function(){
- if(!this.view){
- this.view = new Roo.grid.GridView(this.viewConfig);
+ expand : function(e, skipAnim){
+ if(e) {
+ e.stopPropagation();
+ }
+ if(!this.collapsed || this.el.hasActiveFx()) {
+ return;
+ }
+ if(this.isSlid){
+ this.afterSlideIn();
+ skipAnim = true;
+ }
+ this.collapsed = false;
+ if(this.config.animate && skipAnim !== true){
+ this.animateExpand();
+ }else{
+ this.el.show();
+ if(this.split){
+ this.split.el.show();
+ }
+ this.collapsedEl.setLocation(-2000,-2000);
+ this.collapsedEl.hide();
+ this.fireEvent("invalidated", this);
+ this.fireEvent("expanded", this);
}
- return this.view;
},
- /**
- * Called to get grid's drag proxy text, by default returns this.ddText.
- * @return {String}
- */
- getDragDropText : function(){
- var count = this.selModel.getCount();
- 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.
- *
- * Originally Released Under LGPL - original licence link has changed is not relivant.
- *
- * Fork - LGPL
- * <script type="text/javascript">
- */
-
-Roo.grid.AbstractGridView = function(){
- this.grid = null;
-
- this.events = {
- "beforerowremoved" : true,
- "beforerowsinserted" : true,
- "beforerefresh" : true,
- "rowremoved" : true,
- "rowsinserted" : true,
- "rowupdated" : true,
- "refresh" : true
- };
- Roo.grid.AbstractGridView.superclass.constructor.call(this);
-};
-Roo.extend(Roo.grid.AbstractGridView, Roo.util.Observable, {
- rowClass : "x-grid-row",
- cellClass : "x-grid-cell",
- tdClass : "x-grid-td",
- hdClass : "x-grid-hd",
- splitClass : "x-grid-hd-split",
-
- init: function(grid){
- this.grid = grid;
- var cid = this.grid.getGridEl().id;
- this.colSelector = "#" + cid + " ." + this.cellClass + "-";
- this.tdSelector = "#" + cid + " ." + this.tdClass + "-";
- this.hdSelector = "#" + cid + " ." + this.hdClass + "-";
- this.splitSelector = "#" + cid + " ." + this.splitClass + "-";
- },
-
- getColumnRenderers : function(){
- var renderers = [];
- var cm = this.grid.colModel;
- var colCount = cm.getColumnCount();
- for(var i = 0; i < colCount; i++){
- renderers[i] = cm.getRenderer(i);
- }
- return renderers;
+ animateExpand : function(){
+ // overridden
},
-
- getColumnIds : function(){
- var ids = [];
- var cm = this.grid.colModel;
- var colCount = cm.getColumnCount();
- for(var i = 0; i < colCount; i++){
- ids[i] = cm.getColumnId(i);
+
+ initTabs : function()
+ {
+ this.bodyEl.setStyle("overflow", "hidden");
+ var ts = new Roo.TabPanel(
+ this.bodyEl.dom,
+ {
+ tabPosition: this.bottomTabs ? 'bottom' : 'top',
+ disableTooltips: this.config.disableTabTips,
+ toolbar : this.config.toolbar
+ }
+ );
+ if(this.config.hideTabs){
+ ts.stripWrap.setDisplayed(false);
}
- return ids;
+ this.tabs = ts;
+ ts.resizeTabs = this.config.resizeTabs === true;
+ ts.minTabWidth = this.config.minTabWidth || 40;
+ ts.maxTabWidth = this.config.maxTabWidth || 250;
+ ts.preferredTabWidth = this.config.preferredTabWidth || 150;
+ ts.monitorResize = false;
+ ts.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
+ ts.bodyEl.addClass('x-layout-tabs-body');
+ this.panels.each(this.initPanelAsTab, this);
},
-
- getDataIndexes : function(){
- if(!this.indexMap){
- this.indexMap = this.buildIndexMap();
+
+ initPanelAsTab : function(panel){
+ var ti = this.tabs.addTab(panel.getEl().id, panel.getTitle(), null,
+ this.config.closeOnTab && panel.isClosable());
+ if(panel.tabTip !== undefined){
+ ti.setTooltip(panel.tabTip);
}
- return this.indexMap.colToData;
- },
-
- getColumnIndexByDataIndex : function(dataIndex){
- if(!this.indexMap){
- this.indexMap = this.buildIndexMap();
+ ti.on("activate", function(){
+ this.setActivePanel(panel);
+ }, this);
+ if(this.config.closeOnTab){
+ ti.on("beforeclose", function(t, e){
+ e.cancel = true;
+ this.remove(panel);
+ }, this);
}
- return this.indexMap.dataToCol[dataIndex];
- },
-
- /**
- * Set a css style for a column dynamically.
- * @param {Number} colIndex The index of the column
- * @param {String} name The css property name
- * @param {String} value The css value
- */
- setCSSStyle : function(colIndex, name, value){
- var selector = "#" + this.grid.id + " .x-grid-col-" + colIndex;
- Roo.util.CSS.updateRule(selector, name, value);
+ return ti;
},
-
- generateRules : function(cm){
- var ruleBuf = [], rulesId = this.grid.id + '-cssrules';
- Roo.util.CSS.removeStyleSheet(rulesId);
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- var cid = cm.getColumnId(i);
- ruleBuf.push(this.colSelector, cid, " {\n", cm.config[i].css, "}\n",
- this.tdSelector, cid, " {\n}\n",
- this.hdSelector, cid, " {\n}\n",
- this.splitSelector, cid, " {\n}\n");
- }
- return Roo.util.CSS.createStyleSheet(ruleBuf.join(""), rulesId);
- }
-});/*
- * 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">
- */
-// private
-// This is a support class used internally by the Grid components
-Roo.grid.HeaderDragZone = function(grid, hd, hd2){
- this.grid = grid;
- this.view = grid.getView();
- this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
- Roo.grid.HeaderDragZone.superclass.constructor.call(this, hd);
- if(hd2){
- this.setHandleElId(Roo.id(hd));
- this.setOuterHandleElId(Roo.id(hd2));
- }
- this.scroll = false;
-};
-Roo.extend(Roo.grid.HeaderDragZone, Roo.dd.DragZone, {
- maxDragWidth: 120,
- getDragData : function(e){
- var t = Roo.lib.Event.getTarget(e);
- var h = this.view.findHeaderCell(t);
- if(h){
- return {ddel: h.firstChild, header:h};
+ updatePanelTitle : function(panel, title){
+ if(this.activePanel == panel){
+ this.updateTitle(title);
+ }
+ if(this.tabs){
+ var ti = this.tabs.getTab(panel.getEl().id);
+ ti.setText(title);
+ if(panel.tabTip !== undefined){
+ ti.setTooltip(panel.tabTip);
+ }
}
- return false;
},
- onInitDrag : function(e){
- this.view.headersDisabled = true;
- var clone = this.dragData.ddel.cloneNode(true);
- clone.id = Roo.id();
- clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
- this.proxy.update(clone);
- return true;
+ updateTitle : function(title){
+ if(this.titleTextEl && !this.config.title){
+ this.titleTextEl.innerHTML = (typeof title != "undefined" && title.length > 0 ? title : " ");
+ }
},
- afterValidDrop : function(){
- var v = this.view;
- setTimeout(function(){
- v.headersDisabled = false;
- }, 50);
+ setActivePanel : function(panel){
+ panel = this.getPanel(panel);
+ if(this.activePanel && this.activePanel != panel){
+ this.activePanel.setActiveState(false);
+ }
+ this.activePanel = panel;
+ panel.setActiveState(true);
+ if(this.panelSize){
+ panel.setSize(this.panelSize.width, this.panelSize.height);
+ }
+ if(this.closeBtn){
+ this.closeBtn.setVisible(!this.config.closeOnTab && !this.isSlid && panel.isClosable());
+ }
+ this.updateTitle(panel.getTitle());
+ if(this.tabs){
+ this.fireEvent("invalidated", this);
+ }
+ this.fireEvent("panelactivated", this, panel);
},
- afterInvalidDrop : function(){
- var v = this.view;
- setTimeout(function(){
- v.headersDisabled = false;
- }, 50);
- }
-});
-/*
- * 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">
- */
-// private
-// This is a support class used internally by the Grid components
-Roo.grid.HeaderDropZone = function(grid, hd, hd2){
- this.grid = grid;
- this.view = grid.getView();
- // split the proxies so they don't interfere with mouse events
- this.proxyTop = Roo.DomHelper.append(document.body, {
- cls:"col-move-top", html:" "
- }, true);
- this.proxyBottom = Roo.DomHelper.append(document.body, {
- cls:"col-move-bottom", html:" "
- }, true);
- this.proxyTop.hide = this.proxyBottom.hide = function(){
- this.setLeftTop(-100,-100);
- this.setStyle("visibility", "hidden");
- };
- this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
- // temporarily disabled
- //Roo.dd.ScrollManager.register(this.view.scroller.dom);
- Roo.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
-};
-Roo.extend(Roo.grid.HeaderDropZone, Roo.dd.DropZone, {
- proxyOffsets : [-4, -9],
- fly: Roo.Element.fly,
-
- getTargetFromEvent : function(e){
- var t = Roo.lib.Event.getTarget(e);
- var cindex = this.view.findCellIndex(t);
- if(cindex !== false){
- return this.view.getHeaderCell(cindex);
+ /**
+ * Shows the specified panel.
+ * @param {Number/String/ContentPanel} panelId The panel's index, id or the panel itself
+ * @return {Roo.ContentPanel} The shown panel, or null if a panel could not be found from panelId
+ */
+ showPanel : function(panel)
+ {
+ panel = this.getPanel(panel);
+ if(panel){
+ if(this.tabs){
+ var tab = this.tabs.getTab(panel.getEl().id);
+ if(tab.isHidden()){
+ this.tabs.unhideTab(tab.id);
+ }
+ tab.activate();
+ }else{
+ this.setActivePanel(panel);
+ }
}
- return null;
+ return panel;
},
- nextVisible : function(h){
- var v = this.view, cm = this.grid.colModel;
- h = h.nextSibling;
- while(h){
- if(!cm.isHidden(v.getCellIndex(h))){
- return h;
+ /**
+ * Get the active panel for this region.
+ * @return {Roo.ContentPanel} The active panel or null
+ */
+ getActivePanel : function(){
+ return this.activePanel;
+ },
+
+ validateVisibility : function(){
+ if(this.panels.getCount() < 1){
+ this.updateTitle(" ");
+ this.closeBtn.hide();
+ this.hide();
+ }else{
+ if(!this.isVisible()){
+ this.show();
}
- h = h.nextSibling;
}
- return null;
},
- prevVisible : function(h){
- var v = this.view, cm = this.grid.colModel;
- h = h.prevSibling;
- while(h){
- if(!cm.isHidden(v.getCellIndex(h))){
- return h;
+ /**
+ * Adds the passed ContentPanel(s) to this region.
+ * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
+ * @return {Roo.ContentPanel} The panel added (if only one was added; null otherwise)
+ */
+ add : function(panel){
+ if(arguments.length > 1){
+ for(var i = 0, len = arguments.length; i < len; i++) {
+ this.add(arguments[i]);
}
- h = h.prevSibling;
+ return null;
}
- return null;
+ if(this.hasPanel(panel)){
+ this.showPanel(panel);
+ return panel;
+ }
+ panel.setRegion(this);
+ this.panels.add(panel);
+ if(this.panels.getCount() == 1 && !this.config.alwaysShowTabs){
+ this.bodyEl.dom.appendChild(panel.getEl().dom);
+ if(panel.background !== true){
+ this.setActivePanel(panel);
+ }
+ this.fireEvent("paneladded", this, panel);
+ return panel;
+ }
+ if(!this.tabs){
+ this.initTabs();
+ }else{
+ this.initPanelAsTab(panel);
+ }
+ if(panel.background !== true){
+ this.tabs.activate(panel.getEl().id);
+ }
+ this.fireEvent("paneladded", this, panel);
+ return panel;
},
- positionIndicator : function(h, n, e){
- var x = Roo.lib.Event.getPageX(e);
- var r = Roo.lib.Dom.getRegion(n.firstChild);
- var px, pt, py = r.top + this.proxyOffsets[1];
- if((r.right - x) <= (r.right-r.left)/2){
- px = r.right+this.view.borderWidth;
- pt = "after";
- }else{
- px = r.left;
- pt = "before";
+ /**
+ * Hides the tab for the specified panel.
+ * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
+ */
+ hidePanel : function(panel){
+ if(this.tabs && (panel = this.getPanel(panel))){
+ this.tabs.hideTab(panel.getEl().id);
}
- var oldIndex = this.view.getCellIndex(h);
- var newIndex = this.view.getCellIndex(n);
+ },
- if(this.grid.colModel.isFixed(newIndex)){
- return false;
+ /**
+ * Unhides the tab for a previously hidden panel.
+ * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
+ */
+ unhidePanel : function(panel){
+ if(this.tabs && (panel = this.getPanel(panel))){
+ this.tabs.unhideTab(panel.getEl().id);
}
+ },
- var locked = this.grid.colModel.isLocked(newIndex);
+ clearPanels : function(){
+ while(this.panels.getCount() > 0){
+ this.remove(this.panels.first());
+ }
+ },
- if(pt == "after"){
- newIndex++;
+ /**
+ * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
+ * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
+ * @param {Boolean} preservePanel Overrides the config preservePanel option
+ * @return {Roo.ContentPanel} The panel that was removed
+ */
+ remove : function(panel, preservePanel){
+ panel = this.getPanel(panel);
+ if(!panel){
+ return null;
}
- if(oldIndex < newIndex){
- newIndex--;
+ var e = {};
+ this.fireEvent("beforeremove", this, panel, e);
+ if(e.cancel === true){
+ return null;
}
- if(oldIndex == newIndex && (locked == this.grid.colModel.isLocked(oldIndex))){
- return false;
+ preservePanel = (typeof preservePanel != "undefined" ? preservePanel : (this.config.preservePanels === true || panel.preserve === true));
+ var panelId = panel.getId();
+ this.panels.removeKey(panelId);
+ if(preservePanel){
+ document.body.appendChild(panel.getEl().dom);
}
- px += this.proxyOffsets[0];
- this.proxyTop.setLeftTop(px, py);
- this.proxyTop.show();
- if(!this.bottomOffset){
- this.bottomOffset = this.view.mainHd.getHeight();
+ if(this.tabs){
+ this.tabs.removeTab(panel.getEl().id);
+ }else if (!preservePanel){
+ this.bodyEl.dom.removeChild(panel.getEl().dom);
}
- this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
- this.proxyBottom.show();
- return pt;
- },
-
- onNodeEnter : function(n, dd, e, data){
- if(data.header != n){
- this.positionIndicator(data.header, n, e);
+ if(this.panels.getCount() == 1 && this.tabs && !this.config.alwaysShowTabs){
+ var p = this.panels.first();
+ var tempEl = document.createElement("div"); // temp holder to keep IE from deleting the node
+ tempEl.appendChild(p.getEl().dom);
+ this.bodyEl.update("");
+ this.bodyEl.dom.appendChild(p.getEl().dom);
+ tempEl = null;
+ this.updateTitle(p.getTitle());
+ this.tabs = null;
+ this.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
+ this.setActivePanel(p);
}
- },
-
- onNodeOver : function(n, dd, e, data){
- var result = false;
- if(data.header != n){
- result = this.positionIndicator(data.header, n, e);
+ panel.setRegion(null);
+ if(this.activePanel == panel){
+ this.activePanel = null;
}
- if(!result){
- this.proxyTop.hide();
- this.proxyBottom.hide();
+ if(this.config.autoDestroy !== false && preservePanel !== true){
+ try{panel.destroy();}catch(e){}
}
- return result ? this.dropAllowed : this.dropNotAllowed;
+ this.fireEvent("panelremoved", this, panel);
+ return panel;
},
- onNodeOut : function(n, dd, e, data){
- this.proxyTop.hide();
- this.proxyBottom.hide();
+ /**
+ * Returns the TabPanel component used by this region
+ * @return {Roo.TabPanel}
+ */
+ getTabs : function(){
+ return this.tabs;
},
- onNodeDrop : function(n, dd, e, data){
- var h = data.header;
- if(h != n){
- var cm = this.grid.colModel;
- var x = Roo.lib.Event.getPageX(e);
- var r = Roo.lib.Dom.getRegion(n.firstChild);
- var pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before";
- var oldIndex = this.view.getCellIndex(h);
- var newIndex = this.view.getCellIndex(n);
- var locked = cm.isLocked(newIndex);
- if(pt == "after"){
- newIndex++;
- }
- if(oldIndex < newIndex){
- newIndex--;
- }
- if(oldIndex == newIndex && (locked == cm.isLocked(oldIndex))){
- return false;
- }
- cm.setLocked(oldIndex, locked, true);
- cm.moveColumn(oldIndex, newIndex);
- this.grid.fireEvent("columnmove", oldIndex, newIndex);
- return true;
- }
- return false;
+ createTool : function(parentEl, className){
+ var btn = Roo.DomHelper.append(parentEl, {tag: "div", cls: "x-layout-tools-button",
+ children: [{tag: "div", cls: "x-layout-tools-button-inner " + className, html: " "}]}, true);
+ btn.addClassOnOver("x-layout-tools-button-over");
+ return btn;
}
-});
-/*
+});/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
-
+
+
+
/**
- * @class Roo.grid.GridView
- * @extends Roo.util.Observable
- *
- * @constructor
- * @param {Object} config
+ * @class Roo.SplitLayoutRegion
+ * @extends Roo.LayoutRegion
+ * Adds a splitbar and other (private) useful functionality to a {@link Roo.LayoutRegion}.
*/
-Roo.grid.GridView = function(config){
- Roo.grid.GridView.superclass.constructor.call(this);
- this.el = null;
-
- Roo.apply(this, config);
+Roo.SplitLayoutRegion = function(mgr, config, pos, cursor){
+ this.cursor = cursor;
+ Roo.SplitLayoutRegion.superclass.constructor.call(this, mgr, config, pos);
};
-Roo.extend(Roo.grid.GridView, Roo.grid.AbstractGridView, {
+Roo.extend(Roo.SplitLayoutRegion, Roo.LayoutRegion, {
+ splitTip : "Drag to resize.",
+ collapsibleSplitTip : "Drag to resize. Double click to hide.",
+ useSplitTips : false,
- unselectable : 'unselectable="on"',
- unselectableCls : 'x-unselectable',
-
-
- rowClass : "x-grid-row",
+ applyConfig : function(config){
+ Roo.SplitLayoutRegion.superclass.applyConfig.call(this, config);
+ if(config.split){
+ if(!this.split){
+ var splitEl = Roo.DomHelper.append(this.mgr.el.dom,
+ {tag: "div", id: this.el.id + "-split", cls: "x-layout-split x-layout-split-"+this.position, html: " "});
+ /** The SplitBar for this region
+ * @type Roo.SplitBar */
+ this.split = new Roo.SplitBar(splitEl, this.el, this.orientation);
+ this.split.on("moved", this.onSplitMove, this);
+ this.split.useShim = config.useShim === true;
+ this.split.getMaximumSize = this[this.position == 'north' || this.position == 'south' ? 'getVMaxSize' : 'getHMaxSize'].createDelegate(this);
+ if(this.useSplitTips){
+ this.split.el.dom.title = config.collapsible ? this.collapsibleSplitTip : this.splitTip;
+ }
+ if(config.collapsible){
+ this.split.el.on("dblclick", this.collapse, this);
+ }
+ }
+ if(typeof config.minSize != "undefined"){
+ this.split.minSize = config.minSize;
+ }
+ if(typeof config.maxSize != "undefined"){
+ this.split.maxSize = config.maxSize;
+ }
+ if(config.hideWhenEmpty || config.hidden || config.collapsed){
+ this.hideSplitter();
+ }
+ }
+ },
- cellClass : "x-grid-col",
-
- tdClass : "x-grid-td",
-
- hdClass : "x-grid-hd",
-
- splitClass : "x-grid-split",
-
- sortClasses : ["sort-asc", "sort-desc"],
-
- enableMoveAnim : false,
-
- hlColor: "C3DAF9",
-
- dh : Roo.DomHelper,
-
- fly : Roo.Element.fly,
-
- css : Roo.util.CSS,
-
- borderWidth: 1,
-
- splitOffset: 3,
-
- scrollIncrement : 22,
-
- cellRE: /(?:.*?)x-grid-(?:hd|cell|csplit)-(?:[\d]+)-([\d]+)(?:.*?)/,
-
- findRE: /\s?(?:x-grid-hd|x-grid-col|x-grid-csplit)\s/,
-
- bind : function(ds, cm){
- if(this.ds){
- this.ds.un("load", this.onLoad, this);
- this.ds.un("datachanged", this.onDataChange, this);
- this.ds.un("add", this.onAdd, this);
- this.ds.un("remove", this.onRemove, this);
- this.ds.un("update", this.onUpdate, this);
- this.ds.un("clear", this.onClear, this);
- }
- if(ds){
- ds.on("load", this.onLoad, this);
- ds.on("datachanged", this.onDataChange, this);
- ds.on("add", this.onAdd, this);
- ds.on("remove", this.onRemove, this);
- ds.on("update", this.onUpdate, this);
- ds.on("clear", this.onClear, this);
- }
- this.ds = ds;
-
- if(this.cm){
- this.cm.un("widthchange", this.onColWidthChange, this);
- this.cm.un("headerchange", this.onHeaderChange, this);
- this.cm.un("hiddenchange", this.onHiddenChange, this);
- this.cm.un("columnmoved", this.onColumnMove, this);
- this.cm.un("columnlockchange", this.onColumnLock, this);
- }
- if(cm){
- this.generateRules(cm);
- cm.on("widthchange", this.onColWidthChange, this);
- cm.on("headerchange", this.onHeaderChange, this);
- cm.on("hiddenchange", this.onHiddenChange, this);
- cm.on("columnmoved", this.onColumnMove, this);
- cm.on("columnlockchange", this.onColumnLock, this);
- }
- this.cm = cm;
+ getHMaxSize : function(){
+ var cmax = this.config.maxSize || 10000;
+ var center = this.mgr.getRegion("center");
+ return Math.min(cmax, (this.el.getWidth()+center.getEl().getWidth())-center.getMinWidth());
},
- init: function(grid){
- Roo.grid.GridView.superclass.init.call(this, grid);
-
- this.bind(grid.dataSource, grid.colModel);
-
- grid.on("headerclick", this.handleHeaderClick, this);
-
- if(grid.trackMouseOver){
- grid.on("mouseover", this.onRowOver, this);
- grid.on("mouseout", this.onRowOut, this);
- }
- grid.cancelTextSelection = function(){};
- this.gridId = grid.id;
-
- var tpls = this.templates || {};
-
- if(!tpls.master){
- tpls.master = new Roo.Template(
- '<div class="x-grid" hidefocus="true">',
- '<a href="#" class="x-grid-focus" tabIndex="-1"></a>',
- '<div class="x-grid-topbar"></div>',
- '<div class="x-grid-scroller"><div></div></div>',
- '<div class="x-grid-locked">',
- '<div class="x-grid-header">{lockedHeader}</div>',
- '<div class="x-grid-body">{lockedBody}</div>',
- "</div>",
- '<div class="x-grid-viewport">',
- '<div class="x-grid-header">{header}</div>',
- '<div class="x-grid-body">{body}</div>',
- "</div>",
- '<div class="x-grid-bottombar"></div>',
-
- '<div class="x-grid-resize-proxy"> </div>',
- "</div>"
- );
- tpls.master.disableformats = true;
- }
-
- if(!tpls.header){
- tpls.header = new Roo.Template(
- '<table border="0" cellspacing="0" cellpadding="0">',
- '<tbody><tr class="x-grid-hd-row">{cells}</tr></tbody>',
- "</table>{splits}"
- );
- tpls.header.disableformats = true;
- }
- tpls.header.compile();
-
- if(!tpls.hcell){
- tpls.hcell = new Roo.Template(
- '<td class="x-grid-hd x-grid-td-{id} {cellId}"><div title="{title}" class="x-grid-hd-inner x-grid-hd-{id}">',
- '<div class="x-grid-hd-text ' + this.unselectableCls + '" ' + this.unselectable +'>{value}<img class="x-grid-sort-icon" src="', Roo.BLANK_IMAGE_URL, '" /></div>',
- "</div></td>"
- );
- tpls.hcell.disableFormats = true;
- }
- tpls.hcell.compile();
-
- if(!tpls.hsplit){
- tpls.hsplit = new Roo.Template('<div class="x-grid-split {splitId} x-grid-split-{id}" style="{style} ' +
- this.unselectableCls + '" ' + this.unselectable +'> </div>');
- tpls.hsplit.disableFormats = true;
- }
- tpls.hsplit.compile();
-
- if(!tpls.body){
- tpls.body = new Roo.Template(
- '<table border="0" cellspacing="0" cellpadding="0">',
- "<tbody>{rows}</tbody>",
- "</table>"
- );
- tpls.body.disableFormats = true;
- }
- tpls.body.compile();
-
- if(!tpls.row){
- tpls.row = new Roo.Template('<tr class="x-grid-row {alt}">{cells}</tr>');
- tpls.row.disableFormats = true;
- }
- tpls.row.compile();
-
- if(!tpls.cell){
- tpls.cell = new Roo.Template(
- '<td class="x-grid-col x-grid-td-{id} {cellId} {css}" tabIndex="0">',
- '<div class="x-grid-col-{id} x-grid-cell-inner"><div class="x-grid-cell-text ' +
- this.unselectableCls + '" ' + this.unselectable +'" {attr}>{value}</div></div>',
- "</td>"
- );
- tpls.cell.disableFormats = true;
- }
- tpls.cell.compile();
-
- this.templates = tpls;
+ getVMaxSize : function(){
+ var cmax = this.config.maxSize || 10000;
+ var center = this.mgr.getRegion("center");
+ return Math.min(cmax, (this.el.getHeight()+center.getEl().getHeight())-center.getMinHeight());
},
- // remap these for backwards compat
- onColWidthChange : function(){
- this.updateColumns.apply(this, arguments);
- },
- onHeaderChange : function(){
- this.updateHeaders.apply(this, arguments);
- },
- onHiddenChange : function(){
- this.handleHiddenChange.apply(this, arguments);
+ onSplitMove : function(split, newSize){
+ this.fireEvent("resized", this, newSize);
},
- onColumnMove : function(){
- this.handleColumnMove.apply(this, arguments);
+
+ /**
+ * Returns the {@link Roo.SplitBar} for this region.
+ * @return {Roo.SplitBar}
+ */
+ getSplitBar : function(){
+ return this.split;
},
- onColumnLock : function(){
- this.handleLockChange.apply(this, arguments);
+
+ hide : function(){
+ this.hideSplitter();
+ Roo.SplitLayoutRegion.superclass.hide.call(this);
},
- onDataChange : function(){
- this.refresh();
- this.updateHeaderSortState();
+ hideSplitter : function(){
+ if(this.split){
+ this.split.el.setLocation(-2000,-2000);
+ this.split.el.hide();
+ }
},
- onClear : function(){
- this.refresh();
+ show : function(){
+ if(this.split){
+ this.split.el.show();
+ }
+ Roo.SplitLayoutRegion.superclass.show.call(this);
+ },
+
+ beforeSlide: function(){
+ if(Roo.isGecko){// firefox overflow auto bug workaround
+ this.bodyEl.clip();
+ if(this.tabs) {
+ this.tabs.bodyEl.clip();
+ }
+ if(this.activePanel){
+ this.activePanel.getEl().clip();
+
+ if(this.activePanel.beforeSlide){
+ this.activePanel.beforeSlide();
+ }
+ }
+ }
+ },
+
+ afterSlide : function(){
+ if(Roo.isGecko){// firefox overflow auto bug workaround
+ this.bodyEl.unclip();
+ if(this.tabs) {
+ this.tabs.bodyEl.unclip();
+ }
+ if(this.activePanel){
+ this.activePanel.getEl().unclip();
+ if(this.activePanel.afterSlide){
+ this.activePanel.afterSlide();
+ }
+ }
+ }
},
- onUpdate : function(ds, record){
- this.refreshRow(record);
+ initAutoHide : function(){
+ if(this.autoHide !== false){
+ if(!this.autoHideHd){
+ var st = new Roo.util.DelayedTask(this.slideIn, this);
+ this.autoHideHd = {
+ "mouseout": function(e){
+ if(!e.within(this.el, true)){
+ st.delay(500);
+ }
+ },
+ "mouseover" : function(e){
+ st.cancel();
+ },
+ scope : this
+ };
+ }
+ this.el.on(this.autoHideHd);
+ }
},
- refreshRow : function(record){
- var ds = this.ds, index;
- if(typeof record == 'number'){
- index = record;
- record = ds.getAt(index);
- }else{
- index = ds.indexOf(record);
+ clearAutoHide : function(){
+ if(this.autoHide !== false){
+ this.el.un("mouseout", this.autoHideHd.mouseout);
+ this.el.un("mouseover", this.autoHideHd.mouseover);
}
- this.insertRows(ds, index, index, true);
- this.onRemove(ds, record, index+1, true);
- this.syncRowHeights(index, index);
- this.layout();
- this.fireEvent("rowupdated", this, index, record);
},
- onAdd : function(ds, records, index){
- this.insertRows(ds, index, index + (records.length-1));
+ clearMonitor : function(){
+ Roo.get(document).un("click", this.slideInIf, this);
},
- onRemove : function(ds, record, index, isUpdate){
- if(isUpdate !== true){
- this.fireEvent("beforerowremoved", this, index, record);
- }
- var bt = this.getBodyTable(), lt = this.getLockedTable();
- if(bt.rows[index]){
- bt.firstChild.removeChild(bt.rows[index]);
+ // these names are backwards but not changed for compat
+ slideOut : function(){
+ if(this.isSlid || this.el.hasActiveFx()){
+ return;
}
- if(lt.rows[index]){
- lt.firstChild.removeChild(lt.rows[index]);
+ this.isSlid = true;
+ if(this.collapseBtn){
+ this.collapseBtn.hide();
}
- if(isUpdate !== true){
- this.stripeRows(index);
- this.syncRowHeights(index, index);
- this.layout();
- this.fireEvent("rowremoved", this, index, record);
+ this.closeBtnState = this.closeBtn.getStyle('display');
+ this.closeBtn.hide();
+ if(this.stickBtn){
+ this.stickBtn.show();
}
+ this.el.show();
+ this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
+ this.beforeSlide();
+ this.el.setStyle("z-index", 10001);
+ this.el.slideIn(this.getSlideAnchor(), {
+ callback: function(){
+ this.afterSlide();
+ this.initAutoHide();
+ Roo.get(document).on("click", this.slideInIf, this);
+ this.fireEvent("slideshow", this);
+ },
+ scope: this,
+ block: true
+ });
},
- onLoad : function(){
- this.scrollToTop();
- },
-
- /**
- * Scrolls the grid to the top
- */
- scrollToTop : function(){
- if(this.scroller){
- this.scroller.dom.scrollTop = 0;
- this.syncScroll();
+ afterSlideIn : function(){
+ this.clearAutoHide();
+ this.isSlid = false;
+ this.clearMonitor();
+ this.el.setStyle("z-index", "");
+ if(this.collapseBtn){
+ this.collapseBtn.show();
+ }
+ this.closeBtn.setStyle('display', this.closeBtnState);
+ if(this.stickBtn){
+ this.stickBtn.hide();
}
+ this.fireEvent("slidehide", this);
},
- /**
- * Gets a panel in the header of the grid that can be used for toolbars etc.
- * After modifying the contents of this panel a call to grid.autoSize() may be
- * required to register any changes in size.
- * @param {Boolean} doShow By default the header is hidden. Pass true to show the panel
- * @return Roo.Element
- */
- getHeaderPanel : function(doShow){
- if(doShow){
- this.headerPanel.show();
+ slideIn : function(cb){
+ if(!this.isSlid || this.el.hasActiveFx()){
+ Roo.callback(cb);
+ return;
}
- return this.headerPanel;
+ this.isSlid = false;
+ this.beforeSlide();
+ this.el.slideOut(this.getSlideAnchor(), {
+ callback: function(){
+ this.el.setLeftTop(-10000, -10000);
+ this.afterSlide();
+ this.afterSlideIn();
+ Roo.callback(cb);
+ },
+ scope: this,
+ block: true
+ });
},
-
- /**
- * Gets a panel in the footer of the grid that can be used for toolbars etc.
- * After modifying the contents of this panel a call to grid.autoSize() may be
- * required to register any changes in size.
- * @param {Boolean} doShow By default the footer is hidden. Pass true to show the panel
- * @return Roo.Element
- */
- getFooterPanel : function(doShow){
- if(doShow){
- this.footerPanel.show();
+
+ slideInIf : function(e){
+ if(!e.within(this.el)){
+ this.slideIn();
}
- return this.footerPanel;
},
- initElements : function(){
- var E = Roo.Element;
- var el = this.grid.getGridEl().dom.firstChild;
- var cs = el.childNodes;
-
- this.el = new E(el);
-
- this.focusEl = new E(el.firstChild);
- this.focusEl.swallowEvent("click", true);
-
- this.headerPanel = new E(cs[1]);
- this.headerPanel.enableDisplayMode("block");
-
- this.scroller = new E(cs[2]);
- this.scrollSizer = new E(this.scroller.dom.firstChild);
-
- this.lockedWrap = new E(cs[3]);
- this.lockedHd = new E(this.lockedWrap.dom.firstChild);
- this.lockedBody = new E(this.lockedWrap.dom.childNodes[1]);
-
- this.mainWrap = new E(cs[4]);
- this.mainHd = new E(this.mainWrap.dom.firstChild);
- this.mainBody = new E(this.mainWrap.dom.childNodes[1]);
-
- this.footerPanel = new E(cs[5]);
- this.footerPanel.enableDisplayMode("block");
-
- this.resizeProxy = new E(cs[6]);
-
- this.headerSelector = String.format(
- '#{0} td.x-grid-hd, #{1} td.x-grid-hd',
- this.lockedHd.id, this.mainHd.id
- );
-
- this.splitterSelector = String.format(
- '#{0} div.x-grid-split, #{1} div.x-grid-split',
- this.idToCssName(this.lockedHd.id), this.idToCssName(this.mainHd.id)
- );
+ animateCollapse : function(){
+ this.beforeSlide();
+ this.el.setStyle("z-index", 20000);
+ var anchor = this.getSlideAnchor();
+ this.el.slideOut(anchor, {
+ callback : function(){
+ this.el.setStyle("z-index", "");
+ this.collapsedEl.slideIn(anchor, {duration:.3});
+ this.afterSlide();
+ this.el.setLocation(-10000,-10000);
+ this.el.hide();
+ this.fireEvent("collapsed", this);
+ },
+ scope: this,
+ block: true
+ });
},
- idToCssName : function(s)
- {
- return s.replace(/[^a-z0-9]+/ig, '-');
+
+ animateExpand : function(){
+ this.beforeSlide();
+ this.el.alignTo(this.collapsedEl, this.getCollapseAnchor(), this.getExpandAdj());
+ this.el.setStyle("z-index", 20000);
+ this.collapsedEl.hide({
+ duration:.1
+ });
+ this.el.slideIn(this.getSlideAnchor(), {
+ callback : function(){
+ this.el.setStyle("z-index", "");
+ this.afterSlide();
+ if(this.split){
+ this.split.el.show();
+ }
+ this.fireEvent("invalidated", this);
+ this.fireEvent("expanded", this);
+ },
+ scope: this,
+ block: true
+ });
},
- getHeaderCell : function(index){
- return Roo.DomQuery.select(this.headerSelector)[index];
+ anchors : {
+ "west" : "left",
+ "east" : "right",
+ "north" : "top",
+ "south" : "bottom"
},
- getHeaderCellMeasure : function(index){
- return this.getHeaderCell(index).firstChild;
+ sanchors : {
+ "west" : "l",
+ "east" : "r",
+ "north" : "t",
+ "south" : "b"
},
- getHeaderCellText : function(index){
- return this.getHeaderCell(index).firstChild.firstChild;
+ canchors : {
+ "west" : "tl-tr",
+ "east" : "tr-tl",
+ "north" : "tl-bl",
+ "south" : "bl-tl"
},
- getLockedTable : function(){
- return this.lockedBody.dom.firstChild;
+ getAnchor : function(){
+ return this.anchors[this.position];
},
- getBodyTable : function(){
- return this.mainBody.dom.firstChild;
+ getCollapseAnchor : function(){
+ return this.canchors[this.position];
},
- getLockedRow : function(index){
- return this.getLockedTable().rows[index];
+ getSlideAnchor : function(){
+ return this.sanchors[this.position];
},
- getRow : function(index){
- return this.getBodyTable().rows[index];
+ getAlignAdj : function(){
+ var cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [0, 0];
+ break;
+ case "east":
+ return [0, 0];
+ break;
+ case "north":
+ return [0, 0];
+ break;
+ case "south":
+ return [0, 0];
+ break;
+ }
},
- getRowComposite : function(index){
- if(!this.rowEl){
- this.rowEl = new Roo.CompositeElementLite();
- }
- var els = [], lrow, mrow;
- if(lrow = this.getLockedRow(index)){
- els.push(lrow);
- }
- if(mrow = this.getRow(index)){
- els.push(mrow);
- }
- this.rowEl.elements = els;
- return this.rowEl;
- },
- /**
- * Gets the 'td' of the cell
- *
- * @param {Integer} rowIndex row to select
- * @param {Integer} colIndex column to select
- *
- * @return {Object}
- */
- getCell : function(rowIndex, colIndex){
- var locked = this.cm.getLockedCount();
- var source;
- if(colIndex < locked){
- source = this.lockedBody.dom.firstChild;
- }else{
- source = this.mainBody.dom.firstChild;
- colIndex -= locked;
+ getExpandAdj : function(){
+ var c = this.collapsedEl, cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [-(cm.right+c.getWidth()+cm.left), 0];
+ break;
+ case "east":
+ return [cm.right+c.getWidth()+cm.left, 0];
+ break;
+ case "north":
+ return [0, -(cm.top+cm.bottom+c.getHeight())];
+ break;
+ case "south":
+ return [0, cm.top+cm.bottom+c.getHeight()];
+ break;
}
- return source.rows[rowIndex].childNodes[colIndex];
- },
-
- getCellText : function(rowIndex, colIndex){
- return this.getCell(rowIndex, colIndex).firstChild.firstChild;
- },
+ }
+});/*
+ * 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">
+ */
+/*
+ * These classes are private internal classes
+ */
+Roo.CenterLayoutRegion = function(mgr, config){
+ Roo.LayoutRegion.call(this, mgr, config, "center");
+ this.visible = true;
+ this.minWidth = config.minWidth || 20;
+ this.minHeight = config.minHeight || 20;
+};
- getCellBox : function(cell){
- var b = this.fly(cell).getBox();
- if(Roo.isOpera){ // opera fails to report the Y
- b.y = cell.offsetTop + this.mainBody.getY();
- }
- return b;
+Roo.extend(Roo.CenterLayoutRegion, Roo.LayoutRegion, {
+ hide : function(){
+ // center panel can't be hidden
},
-
- getCellIndex : function(cell){
- var id = String(cell.className).match(this.cellRE);
- if(id){
- return parseInt(id[1], 10);
- }
- return 0;
+
+ show : function(){
+ // center panel can't be hidden
},
-
- findHeaderIndex : function(n){
- var r = Roo.fly(n).findParent("td." + this.hdClass, 6);
- return r ? this.getCellIndex(r) : false;
+
+ getMinWidth: function(){
+ return this.minWidth;
},
+
+ getMinHeight: function(){
+ return this.minHeight;
+ }
+});
- findHeaderCell : function(n){
- var r = Roo.fly(n).findParent("td." + this.hdClass, 6);
- return r ? r : false;
- },
- findRowIndex : function(n){
- if(!n){
- return false;
+Roo.NorthLayoutRegion = function(mgr, config){
+ Roo.LayoutRegion.call(this, mgr, config, "north", "n-resize");
+ if(this.split){
+ this.split.placement = Roo.SplitBar.TOP;
+ this.split.orientation = Roo.SplitBar.VERTICAL;
+ this.split.el.addClass("x-layout-split-v");
+ }
+ var size = config.initialSize || config.height;
+ if(typeof size != "undefined"){
+ this.el.setHeight(size);
+ }
+};
+Roo.extend(Roo.NorthLayoutRegion, Roo.SplitLayoutRegion, {
+ orientation: Roo.SplitBar.VERTICAL,
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
}
- var r = Roo.fly(n).findParent("tr." + this.rowClass, 6);
- return r ? r.rowIndex : false;
- },
-
- findCellIndex : function(node){
- var stop = this.el.dom;
- while(node && node != stop){
- if(this.findRE.test(node.className)){
- return this.getCellIndex(node);
- }
- node = node.parentNode;
+ var box = this.el.getBox();
+ if(this.split){
+ box.height += this.split.el.getHeight();
}
- return false;
- },
-
- getColumnId : function(index){
- return this.cm.getColumnId(index);
- },
-
- getSplitters : function()
- {
- if(this.splitterSelector){
- return Roo.DomQuery.select(this.splitterSelector);
- }else{
- return null;
- }
- },
-
- getSplitter : function(index){
- return this.getSplitters()[index];
+ return box;
},
-
- onRowOver : function(e, t){
- var row;
- if((row = this.findRowIndex(t)) !== false){
- this.getRowComposite(row).addClass("x-grid-row-over");
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ box.height -= this.split.el.getHeight();
+ this.split.el.setLeft(box.x);
+ this.split.el.setTop(box.y+box.height);
+ this.split.el.setWidth(box.width);
}
- },
-
- onRowOut : function(e, t){
- var row;
- if((row = this.findRowIndex(t)) !== false && row !== this.findRowIndex(e.getRelatedTarget())){
- this.getRowComposite(row).removeClass("x-grid-row-over");
+ if(this.collapsed){
+ this.updateBody(box.width, null);
}
- },
+ Roo.LayoutRegion.prototype.updateBox.call(this, box);
+ }
+});
- renderHeaders : function(){
- var cm = this.cm;
- var ct = this.templates.hcell, ht = this.templates.header, st = this.templates.hsplit;
- var cb = [], lb = [], sb = [], lsb = [], p = {};
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- p.cellId = "x-grid-hd-0-" + i;
- p.splitId = "x-grid-csplit-0-" + i;
- p.id = cm.getColumnId(i);
- p.value = cm.getColumnHeader(i) || "";
- p.title = cm.getColumnTooltip(i) || (''+p.value).match(/\</) ? '' : p.value || "";
- p.style = (this.grid.enableColumnResize === false || !cm.isResizable(i) || cm.isFixed(i)) ? 'cursor:default' : '';
- if(!cm.isLocked(i)){
- cb[cb.length] = ct.apply(p);
- sb[sb.length] = st.apply(p);
- }else{
- lb[lb.length] = ct.apply(p);
- lsb[lsb.length] = st.apply(p);
- }
+Roo.SouthLayoutRegion = function(mgr, config){
+ Roo.SplitLayoutRegion.call(this, mgr, config, "south", "s-resize");
+ if(this.split){
+ this.split.placement = Roo.SplitBar.BOTTOM;
+ this.split.orientation = Roo.SplitBar.VERTICAL;
+ this.split.el.addClass("x-layout-split-v");
+ }
+ var size = config.initialSize || config.height;
+ if(typeof size != "undefined"){
+ this.el.setHeight(size);
+ }
+};
+Roo.extend(Roo.SouthLayoutRegion, Roo.SplitLayoutRegion, {
+ orientation: Roo.SplitBar.VERTICAL,
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
}
- return [ht.apply({cells: lb.join(""), splits:lsb.join("")}),
- ht.apply({cells: cb.join(""), splits:sb.join("")})];
- },
-
- updateHeaders : function(){
- var html = this.renderHeaders();
- this.lockedHd.update(html[0]);
- this.mainHd.update(html[1]);
- },
-
- /**
- * Focuses the specified row.
- * @param {Number} row The row index
- */
- focusRow : function(row)
- {
- //Roo.log('GridView.focusRow');
- var x = this.scroller.dom.scrollLeft;
- this.focusCell(row, 0, false);
- this.scroller.dom.scrollLeft = x;
- },
-
- /**
- * Focuses the specified cell.
- * @param {Number} row The row index
- * @param {Number} col The column index
- * @param {Boolean} hscroll false to disable horizontal scrolling
- */
- focusCell : function(row, col, hscroll)
- {
- //Roo.log('GridView.focusCell');
- var el = this.ensureVisible(row, col, hscroll);
- this.focusEl.alignTo(el, "tl-tl");
- if(Roo.isGecko){
- this.focusEl.focus();
- }else{
- this.focusEl.focus.defer(1, this.focusEl);
+ var box = this.el.getBox();
+ if(this.split){
+ var sh = this.split.el.getHeight();
+ box.height += sh;
+ box.y -= sh;
}
+ return box;
},
-
- /**
- * Scrolls the specified cell into view
- * @param {Number} row The row index
- * @param {Number} col The column index
- * @param {Boolean} hscroll false to disable horizontal scrolling
- */
- ensureVisible : function(row, col, hscroll)
- {
- //Roo.log('GridView.ensureVisible,' + row + ',' + col);
- //return null; //disable for testing.
- if(typeof row != "number"){
- row = row.rowIndex;
- }
- if(row < 0 && row >= this.ds.getCount()){
- return null;
- }
- col = (col !== undefined ? col : 0);
- var cm = this.grid.colModel;
- while(cm.isHidden(col)){
- col++;
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ var sh = this.split.el.getHeight();
+ box.height -= sh;
+ box.y += sh;
+ this.split.el.setLeft(box.x);
+ this.split.el.setTop(box.y-sh);
+ this.split.el.setWidth(box.width);
}
-
- var el = this.getCell(row, col);
- if(!el){
- return null;
+ if(this.collapsed){
+ this.updateBody(box.width, null);
}
- var c = this.scroller.dom;
+ Roo.LayoutRegion.prototype.updateBox.call(this, box);
+ }
+});
- var ctop = parseInt(el.offsetTop, 10);
- var cleft = parseInt(el.offsetLeft, 10);
- var cbot = ctop + el.offsetHeight;
- var cright = cleft + el.offsetWidth;
-
- var ch = c.clientHeight - this.mainHd.dom.offsetHeight;
- var stop = parseInt(c.scrollTop, 10);
- var sleft = parseInt(c.scrollLeft, 10);
- var sbot = stop + ch;
- var sright = sleft + c.clientWidth;
- /*
- Roo.log('GridView.ensureVisible:' +
- ' ctop:' + ctop +
- ' c.clientHeight:' + c.clientHeight +
- ' this.mainHd.dom.offsetHeight:' + this.mainHd.dom.offsetHeight +
- ' stop:' + stop +
- ' cbot:' + cbot +
- ' sbot:' + sbot +
- ' ch:' + ch
- );
- */
- if(ctop < stop){
- c.scrollTop = ctop;
- //Roo.log("set scrolltop to ctop DISABLE?");
- }else if(cbot > sbot){
- //Roo.log("set scrolltop to cbot-ch");
- c.scrollTop = cbot-ch;
+Roo.EastLayoutRegion = function(mgr, config){
+ Roo.SplitLayoutRegion.call(this, mgr, config, "east", "e-resize");
+ if(this.split){
+ this.split.placement = Roo.SplitBar.RIGHT;
+ this.split.orientation = Roo.SplitBar.HORIZONTAL;
+ this.split.el.addClass("x-layout-split-h");
+ }
+ var size = config.initialSize || config.width;
+ if(typeof size != "undefined"){
+ this.el.setWidth(size);
+ }
+};
+Roo.extend(Roo.EastLayoutRegion, Roo.SplitLayoutRegion, {
+ orientation: Roo.SplitBar.HORIZONTAL,
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
}
-
- if(hscroll !== false){
- if(cleft < sleft){
- c.scrollLeft = cleft;
- }else if(cright > sright){
- c.scrollLeft = cright-c.clientWidth;
- }
+ var box = this.el.getBox();
+ if(this.split){
+ var sw = this.split.el.getWidth();
+ box.width += sw;
+ box.x -= sw;
}
-
- return el;
+ return box;
},
- updateColumns : function(){
- this.grid.stopEditing();
- var cm = this.grid.colModel, colIds = this.getColumnIds();
- //var totalWidth = cm.getTotalWidth();
- var pos = 0;
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- //if(cm.isHidden(i)) continue;
- var w = cm.getColumnWidth(i);
- this.css.updateRule(this.colSelector+this.idToCssName(colIds[i]), "width", (w - this.borderWidth) + "px");
- this.css.updateRule(this.hdSelector+this.idToCssName(colIds[i]), "width", (w - this.borderWidth) + "px");
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ var sw = this.split.el.getWidth();
+ box.width -= sw;
+ this.split.el.setLeft(box.x);
+ this.split.el.setTop(box.y);
+ this.split.el.setHeight(box.height);
+ box.x += sw;
}
- this.updateSplitters();
- },
-
- generateRules : function(cm){
- var ruleBuf = [], rulesId = this.idToCssName(this.grid.id)+ '-cssrules';
- Roo.util.CSS.removeStyleSheet(rulesId);
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- var cid = cm.getColumnId(i);
- var align = '';
- if(cm.config[i].align){
- align = 'text-align:'+cm.config[i].align+';';
- }
- var hidden = '';
- if(cm.isHidden(i)){
- hidden = 'display:none;';
- }
- var width = "width:" + (cm.getColumnWidth(i) - this.borderWidth) + "px;";
- ruleBuf.push(
- this.colSelector, cid, " {\n", cm.config[i].css, align, width, "\n}\n",
- this.hdSelector, cid, " {\n", align, width, "}\n",
- this.tdSelector, cid, " {\n",hidden,"\n}\n",
- this.splitSelector, cid, " {\n", hidden , "\n}\n");
+ if(this.collapsed){
+ this.updateBody(null, box.height);
}
- return Roo.util.CSS.createStyleSheet(ruleBuf.join(""), rulesId);
- },
+ Roo.LayoutRegion.prototype.updateBox.call(this, box);
+ }
+});
- updateSplitters : function(){
- var cm = this.cm, s = this.getSplitters();
- if(s){ // splitters not created yet
- var pos = 0, locked = true;
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- if(cm.isHidden(i)) {
- continue;
- }
- var w = cm.getColumnWidth(i); // make sure it's a number
- if(!cm.isLocked(i) && locked){
- pos = 0;
- locked = false;
- }
- pos += w;
- s[i].style.left = (pos-this.splitOffset) + "px";
- }
+Roo.WestLayoutRegion = function(mgr, config){
+ Roo.SplitLayoutRegion.call(this, mgr, config, "west", "w-resize");
+ if(this.split){
+ this.split.placement = Roo.SplitBar.LEFT;
+ this.split.orientation = Roo.SplitBar.HORIZONTAL;
+ this.split.el.addClass("x-layout-split-h");
+ }
+ var size = config.initialSize || config.width;
+ if(typeof size != "undefined"){
+ this.el.setWidth(size);
+ }
+};
+Roo.extend(Roo.WestLayoutRegion, Roo.SplitLayoutRegion, {
+ orientation: Roo.SplitBar.HORIZONTAL,
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
}
- },
-
- handleHiddenChange : function(colModel, colIndex, hidden){
- if(hidden){
- this.hideColumn(colIndex);
- }else{
- this.unhideColumn(colIndex);
+ var box = this.el.getBox();
+ if(this.split){
+ box.width += this.split.el.getWidth();
}
+ return box;
},
-
- hideColumn : function(colIndex){
- var cid = this.getColumnId(colIndex);
- this.css.updateRule(this.tdSelector+this.idToCssName(cid), "display", "none");
- this.css.updateRule(this.splitSelector+this.idToCssName(cid), "display", "none");
- if(Roo.isSafari){
- this.updateHeaders();
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ var sw = this.split.el.getWidth();
+ box.width -= sw;
+ this.split.el.setLeft(box.x+box.width);
+ this.split.el.setTop(box.y);
+ this.split.el.setHeight(box.height);
}
- this.updateSplitters();
- this.layout();
- },
-
- unhideColumn : function(colIndex){
- var cid = this.getColumnId(colIndex);
- this.css.updateRule(this.tdSelector+this.idToCssName(cid), "display", "");
- this.css.updateRule(this.splitSelector+this.idToCssName(cid), "display", "");
-
- if(Roo.isSafari){
- this.updateHeaders();
+ if(this.collapsed){
+ this.updateBody(null, box.height);
}
- this.updateSplitters();
- this.layout();
- },
+ Roo.LayoutRegion.prototype.updateBox.call(this, box);
+ }
+});
+/*
+ * 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">
+ */
+
+
+/*
+ * Private internal class for reading and applying state
+ */
+Roo.LayoutStateManager = function(layout){
+ // default empty state
+ this.state = {
+ north: {},
+ south: {},
+ east: {},
+ west: {}
+ };
+};
- insertRows : function(dm, firstRow, lastRow, isUpdate){
- if(firstRow == 0 && lastRow == dm.getCount()-1){
- this.refresh();
- }else{
- if(!isUpdate){
- this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
+Roo.LayoutStateManager.prototype = {
+ init : function(layout, provider){
+ this.provider = provider;
+ var state = provider.get(layout.id+"-layout-state");
+ if(state){
+ var wasUpdating = layout.isUpdating();
+ if(!wasUpdating){
+ layout.beginUpdate();
}
- var s = this.getScrollState();
- var markup = this.renderRows(firstRow, lastRow);
- this.bufferRows(markup[0], this.getLockedTable(), firstRow);
- this.bufferRows(markup[1], this.getBodyTable(), firstRow);
- this.restoreScroll(s);
- if(!isUpdate){
- this.fireEvent("rowsinserted", this, firstRow, lastRow);
- this.syncRowHeights(firstRow, lastRow);
- this.stripeRows(firstRow);
- this.layout();
+ for(var key in state){
+ if(typeof state[key] != "function"){
+ var rstate = state[key];
+ var r = layout.getRegion(key);
+ if(r && rstate){
+ if(rstate.size){
+ r.resizeTo(rstate.size);
+ }
+ if(rstate.collapsed == true){
+ r.collapse(true);
+ }else{
+ r.expand(null, true);
+ }
+ }
+ }
+ }
+ if(!wasUpdating){
+ layout.endUpdate();
}
+ this.state = state;
}
+ this.layout = layout;
+ layout.on("regionresized", this.onRegionResized, this);
+ layout.on("regioncollapsed", this.onRegionCollapsed, this);
+ layout.on("regionexpanded", this.onRegionExpanded, this);
+ },
+
+ storeState : function(){
+ this.provider.set(this.layout.id+"-layout-state", this.state);
+ },
+
+ onRegionResized : function(region, newSize){
+ this.state[region.getPosition()].size = newSize;
+ this.storeState();
+ },
+
+ onRegionCollapsed : function(region){
+ this.state[region.getPosition()].collapsed = true;
+ this.storeState();
},
+
+ onRegionExpanded : function(region){
+ this.state[region.getPosition()].collapsed = false;
+ this.storeState();
+ }
+};/*
+ * 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">
+ */
+/**
+ * @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} 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 {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) [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
- bufferRows : function(markup, target, index){
- var before = null, trows = target.rows, tbody = target.tBodies[0];
- if(index < trows.length){
- before = trows[index];
- }
- var b = document.createElement("div");
- b.innerHTML = "<table><tbody>"+markup+"</tbody></table>";
- var rows = b.firstChild.rows;
- for(var i = 0, len = rows.length; i < len; i++){
- if(before){
- tbody.insertBefore(rows[0], before);
- }else{
- tbody.appendChild(rows[0]);
+ * @constructor
+ * Create a new ContentPanel.
+ * @param {String/HTMLElement/Roo.Element} el The container element for this panel
+ * @param {String/Object} config A string to set only the title or a config object
+ * @param {String} content (optional) Set the HTML content for this panel
+ * @param {String} region (optional) Used by xtype constructors to add to regions. (values center,east,west,south,north)
+ */
+Roo.ContentPanel = function(el, config, content){
+
+ /*
+ if(el.autoCreate || el.xtype){ // xtype is available if this is called from factory
+ config = el;
+ el = Roo.id();
+ }
+ if (config && config.parentLayout) {
+ el = config.parentLayout.el.createChild();
+ }
+ */
+ if(el.autoCreate){ // xtype is available if this is called from factory
+ config = el;
+ el = Roo.id();
+ }
+ this.el = Roo.get(el);
+ if(!this.el && config && config.autoCreate){
+ if(typeof config.autoCreate == "object"){
+ if(!config.autoCreate.id){
+ config.autoCreate.id = config.id||el;
}
+ this.el = Roo.DomHelper.append(document.body,
+ config.autoCreate, true);
+ }else{
+ this.el = Roo.DomHelper.append(document.body,
+ {tag: "div", cls: "x-layout-inactive-content", id: config.id||el}, true);
}
- b.innerHTML = "";
- b = null;
- },
+ }
+
+
+ this.closable = false;
+ this.loaded = false;
+ this.active = false;
+ if(typeof config == "string"){
+ this.title = config;
+ }else{
+ Roo.apply(this, config);
+ }
+
+ if (this.toolbar && !this.toolbar.el && this.toolbar.xtype) {
+ this.wrapEl = this.el.wrap();
+ this.toolbar.container = this.el.insertSibling(false, 'before');
+ this.toolbar = new Roo.Toolbar(this.toolbar);
+ }
+
+ // xtype created footer. - not sure if will work as we normally have to render first..
+ if (this.footer && !this.footer.el && this.footer.xtype) {
+ if (!this.wrapEl) {
+ this.wrapEl = this.el.wrap();
+ }
+
+ this.footer.container = this.wrapEl.createChild();
+
+ this.footer = Roo.factory(this.footer, Roo);
+
+ }
+
+ if(this.resizeEl){
+ this.resizeEl = Roo.get(this.resizeEl, true);
+ }else{
+ this.resizeEl = this.el;
+ }
+ // handle view.xtype
+
+
+
+
+ this.addEvents({
+ /**
+ * @event activate
+ * Fires when this panel is activated.
+ * @param {Roo.ContentPanel} this
+ */
+ "activate" : true,
+ /**
+ * @event deactivate
+ * Fires when this panel is activated.
+ * @param {Roo.ContentPanel} this
+ */
+ "deactivate" : true,
- deleteRows : function(dm, firstRow, lastRow){
- if(dm.getRowCount()<1){
- this.fireEvent("beforerefresh", this);
- this.mainBody.update("");
- this.lockedBody.update("");
- this.fireEvent("refresh", this);
+ /**
+ * @event resize
+ * Fires when this panel is resized if fitToFrame is true.
+ * @param {Roo.ContentPanel} this
+ * @param {Number} width The width after any component adjustments
+ * @param {Number} height The height after any component adjustments
+ */
+ "resize" : true,
+
+ /**
+ * @event render
+ * Fires when this tab is created
+ * @param {Roo.ContentPanel} this
+ */
+ "render" : true
+
+
+ });
+
+
+
+
+ if(this.autoScroll){
+ this.resizeEl.setStyle("overflow", "auto");
+ } else {
+ // fix randome scrolling
+ this.el.on('scroll', function() {
+ Roo.log('fix random scolling');
+ this.scrollTo('top',0);
+ });
+ }
+ content = content || this.content;
+ if(content){
+ this.setContent(content);
+ }
+ if(config && config.url){
+ this.setUrl(this.url, this.params, this.loadOnce);
+ }
+
+
+
+ Roo.ContentPanel.superclass.constructor.call(this);
+
+ if (this.view && typeof(this.view.xtype) != 'undefined') {
+ this.view.el = this.el.appendChild(document.createElement("div"));
+ this.view = Roo.factory(this.view);
+ this.view.render && this.view.render(false, '');
+ }
+
+
+ this.fireEvent('render', this);
+};
+
+Roo.extend(Roo.ContentPanel, Roo.util.Observable, {
+ tabTip:'',
+ setRegion : function(region){
+ this.region = region;
+ if(region){
+ this.el.replaceClass("x-layout-inactive-content", "x-layout-active-content");
}else{
- this.fireEvent("beforerowsdeleted", this, firstRow, lastRow);
- var bt = this.getBodyTable();
- var tbody = bt.firstChild;
- var rows = bt.rows;
- for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
- tbody.removeChild(rows[firstRow]);
- }
- this.stripeRows(firstRow);
- this.fireEvent("rowsdeleted", this, firstRow, lastRow);
+ this.el.replaceClass("x-layout-active-content", "x-layout-inactive-content");
+ }
+ },
+
+ /**
+ * Returns the toolbar for this Panel if one was configured.
+ * @return {Roo.Toolbar}
+ */
+ getToolbar : function(){
+ return this.toolbar;
+ },
+
+ setActiveState : function(active){
+ this.active = active;
+ if(!active){
+ this.fireEvent("deactivate", this);
+ }else{
+ this.fireEvent("activate", this);
}
},
-
- updateRows : function(dataSource, firstRow, lastRow){
- var s = this.getScrollState();
- this.refresh();
- this.restoreScroll(s);
+ /**
+ * Updates this panel's element
+ * @param {String} content The new content
+ * @param {Boolean} loadScripts (optional) true to look for and process scripts
+ */
+ setContent : function(content, loadScripts){
+ this.el.update(content, loadScripts);
},
- handleSort : function(dataSource, sortColumnIndex, sortDir, noRefresh){
- if(!noRefresh){
- this.refresh();
+ ignoreResize : function(w, h){
+ if(this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
+ return true;
+ }else{
+ this.lastSize = {width: w, height: h};
+ return false;
}
- this.updateHeaderSortState();
},
-
- getScrollState : function(){
-
- var sb = this.scroller.dom;
- return {left: sb.scrollLeft, top: sb.scrollTop};
+ /**
+ * Get the {@link Roo.UpdateManager} for this panel. Enables you to perform Ajax updates.
+ * @return {Roo.UpdateManager} The UpdateManager
+ */
+ getUpdateManager : function(){
+ return this.el.getUpdateManager();
+ },
+ /**
+ * Loads this content panel immediately with content from XHR. Note: to delay loading until the panel is activated, use {@link #setUrl}.
+ * @param {Object/String/Function} url The url for this request or a function to call to get the url or a config object containing any of the following options:
+<pre><code>
+panel.load({
+ url: "your-url.php",
+ params: {param1: "foo", param2: "bar"}, // or a URL encoded string
+ callback: yourFunction,
+ scope: yourObject, //(optional scope)
+ discardUrl: false,
+ nocache: false,
+ text: "Loading...",
+ timeout: 30,
+ scripts: false
+});
+</code></pre>
+ * The only required property is <i>url</i>. The optional properties <i>nocache</i>, <i>text</i> and <i>scripts</i>
+ * are shorthand for <i>disableCaching</i>, <i>indicatorText</i> and <i>loadScripts</i> and are used to set their associated property on this panel UpdateManager instance.
+ * @param {String/Object} params (optional) The parameters to pass as either a URL encoded string "param1=1&param2=2" or an object {param1: 1, param2: 2}
+ * @param {Function} callback (optional) Callback when transaction is complete -- called with signature (oElement, bSuccess, oResponse)
+ * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used URL. If true, it will not store the URL.
+ * @return {Roo.ContentPanel} this
+ */
+ load : function(){
+ var um = this.el.getUpdateManager();
+ um.update.apply(um, arguments);
+ return this;
},
- stripeRows : function(startRow){
- if(!this.grid.stripeRows || this.ds.getCount() < 1){
- return;
- }
- startRow = startRow || 0;
- var rows = this.getBodyTable().rows;
- var lrows = this.getLockedTable().rows;
- var cls = ' x-grid-row-alt ';
- for(var i = startRow, len = rows.length; i < len; i++){
- var row = rows[i], lrow = lrows[i];
- var isAlt = ((i+1) % 2 == 0);
- var hasAlt = (' '+row.className + ' ').indexOf(cls) != -1;
- if(isAlt == hasAlt){
- continue;
- }
- if(isAlt){
- row.className += " x-grid-row-alt";
- }else{
- row.className = row.className.replace("x-grid-row-alt", "");
- }
- if(lrow){
- lrow.className = row.className;
- }
+
+ /**
+ * Set a URL to be used to load the content for this panel. When this panel is activated, the content will be loaded from that URL.
+ * @param {String/Function} url The URL to load the content from or a function to call to get the URL
+ * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Roo.UpdateManager#update} for more details. (Defaults to null)
+ * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
+ * @return {Roo.UpdateManager} The UpdateManager
+ */
+ setUrl : function(url, params, loadOnce){
+ if(this.refreshDelegate){
+ this.removeListener("activate", this.refreshDelegate);
}
+ this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
+ this.on("activate", this.refreshDelegate);
+ return this.el.getUpdateManager();
},
-
- restoreScroll : function(state){
- //Roo.log('GridView.restoreScroll');
- var sb = this.scroller.dom;
- sb.scrollLeft = state.left;
- sb.scrollTop = state.top;
- this.syncScroll();
+
+ _handleRefresh : function(url, params, loadOnce){
+ if(!loadOnce || !this.loaded){
+ var updater = this.el.getUpdateManager();
+ updater.update(url, params, this._setLoaded.createDelegate(this));
+ }
},
-
- syncScroll : function(){
- //Roo.log('GridView.syncScroll');
- var sb = this.scroller.dom;
- var sh = this.mainHd.dom;
- var bs = this.mainBody.dom;
- var lv = this.lockedBody.dom;
- sh.scrollLeft = bs.scrollLeft = sb.scrollLeft;
- lv.scrollTop = bs.scrollTop = sb.scrollTop;
+
+ _setLoaded : function(){
+ this.loaded = true;
+ },
+
+ /**
+ * Returns this panel's id
+ * @return {String}
+ */
+ getId : function(){
+ return this.el.id;
},
-
- handleScroll : function(e){
- this.syncScroll();
- var sb = this.scroller.dom;
- this.grid.fireEvent("bodyscroll", sb.scrollLeft, sb.scrollTop);
- e.stopEvent();
+
+ /**
+ * Returns this panel's element - used by regiosn to add.
+ * @return {Roo.Element}
+ */
+ getEl : function(){
+ return this.wrapEl || this.el;
},
-
- handleWheel : function(e){
- var d = e.getWheelDelta();
- this.scroller.dom.scrollTop -= d*22;
- // set this here to prevent jumpy scrolling on large tables
- this.lockedBody.dom.scrollTop = this.mainBody.dom.scrollTop = this.scroller.dom.scrollTop;
- e.stopEvent();
+
+ adjustForComponents : function(width, height)
+ {
+ //Roo.log('adjustForComponents ');
+ if(this.resizeEl != this.el){
+ width -= this.el.getFrameWidth('lr');
+ height -= this.el.getFrameWidth('tb');
+ }
+ if(this.toolbar){
+ var te = this.toolbar.getEl();
+ height -= te.getHeight();
+ te.setWidth(width);
+ }
+ if(this.footer){
+ var te = this.footer.getEl();
+ //Roo.log("footer:" + te.getHeight());
+
+ height -= te.getHeight();
+ te.setWidth(width);
+ }
+
+
+ if(this.adjustments){
+ width += this.adjustments[0];
+ height += this.adjustments[1];
+ }
+ return {"width": width, "height": height};
},
-
- renderRows : function(startRow, endRow){
- // pull in all the crap needed to render rows
- var g = this.grid, cm = g.colModel, ds = g.dataSource, stripe = g.stripeRows;
- var colCount = cm.getColumnCount();
-
- if(ds.getCount() < 1){
- return ["", ""];
+
+ setSize : function(width, height){
+ if(this.fitToFrame && !this.ignoreResize(width, height)){
+ if(this.fitContainer && this.resizeEl != this.el){
+ this.el.setSize(width, height);
+ }
+ var size = this.adjustForComponents(width, height);
+ this.resizeEl.setSize(this.autoWidth ? "auto" : size.width, this.autoHeight ? "auto" : size.height);
+ this.fireEvent('resize', this, size.width, size.height);
}
-
- // build a map for all the columns
- var cs = [];
- for(var i = 0; i < colCount; i++){
- var name = cm.getDataIndex(i);
- cs[i] = {
- name : typeof name == 'undefined' ? ds.fields.get(i).name : name,
- renderer : cm.getRenderer(i),
- id : cm.getColumnId(i),
- locked : cm.isLocked(i),
- has_editor : cm.isCellEditable(i)
- };
+ },
+
+ /**
+ * Returns this panel's title
+ * @return {String}
+ */
+ getTitle : function(){
+ return this.title;
+ },
+
+ /**
+ * Set this panel's title
+ * @param {String} title
+ */
+ setTitle : function(title){
+ this.title = title;
+ if(this.region){
+ this.region.updatePanelTitle(this, title);
}
-
- startRow = startRow || 0;
- endRow = typeof endRow == "undefined"? ds.getCount()-1 : endRow;
-
- // records to render
- var rs = ds.getRange(startRow, endRow);
-
- return this.doRender(cs, rs, ds, startRow, colCount, stripe);
},
-
- // As much as I hate to duplicate code, this was branched because FireFox really hates
- // [].join("") on strings. The performance difference was substantial enough to
- // branch this function
- doRender : Roo.isGecko ?
- function(cs, rs, ds, startRow, colCount, stripe){
- var ts = this.templates, ct = ts.cell, rt = ts.row;
- // buffers
- var buf = "", lbuf = "", cb, lcb, c, p = {}, rp = {}, r, rowIndex;
-
- var hasListener = this.grid.hasListener('rowclass');
- var rowcfg = {};
- for(var j = 0, len = rs.length; j < len; j++){
- r = rs[j]; cb = ""; lcb = ""; rowIndex = (j+startRow);
- for(var i = 0; i < colCount; i++){
- c = cs[i];
- p.cellId = "x-grid-cell-" + rowIndex + "-" + i;
- p.id = c.id;
- p.css = p.attr = "";
- p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
- if(p.value == undefined || p.value === "") {
- p.value = " ";
- }
- if(c.has_editor){
- p.css += ' x-grid-editable-cell';
- }
- if(c.dirty && typeof r.modified[c.name] !== 'undefined'){
- p.css += ' x-grid-dirty-cell';
- }
- var markup = ct.apply(p);
- if(!c.locked){
- cb+= markup;
- }else{
- lcb+= markup;
- }
- }
- var alt = [];
- if(stripe && ((rowIndex+1) % 2 == 0)){
- alt.push("x-grid-row-alt")
- }
- if(r.dirty){
- alt.push( " x-grid-dirty-row");
- }
- rp.cells = lcb;
- if(this.getRowClass){
- alt.push(this.getRowClass(r, rowIndex));
- }
- if (hasListener) {
- rowcfg = {
-
- record: r,
- rowIndex : rowIndex,
- rowClass : ''
- };
- this.grid.fireEvent('rowclass', this, rowcfg);
- alt.push(rowcfg.rowClass);
- }
- rp.alt = alt.join(" ");
- lbuf+= rt.apply(rp);
- rp.cells = cb;
- buf+= rt.apply(rp);
- }
- return [lbuf, buf];
- } :
- function(cs, rs, ds, startRow, colCount, stripe){
- var ts = this.templates, ct = ts.cell, rt = ts.row;
- // buffers
- var buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r, rowIndex;
- var hasListener = this.grid.hasListener('rowclass');
-
- var rowcfg = {};
- for(var j = 0, len = rs.length; j < len; j++){
- r = rs[j]; cb = []; lcb = []; rowIndex = (j+startRow);
- for(var i = 0; i < colCount; i++){
- c = cs[i];
- p.cellId = "x-grid-cell-" + rowIndex + "-" + i;
- p.id = c.id;
- p.css = p.attr = "";
- p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
- if(p.value == undefined || p.value === "") {
- p.value = " ";
- }
- //Roo.log(c);
- if(c.has_editor){
- p.css += ' x-grid-editable-cell';
- }
- if(r.dirty && typeof r.modified[c.name] !== 'undefined'){
- p.css += ' x-grid-dirty-cell'
- }
-
- var markup = ct.apply(p);
- if(!c.locked){
- cb[cb.length] = markup;
- }else{
- lcb[lcb.length] = markup;
- }
- }
- var alt = [];
- if(stripe && ((rowIndex+1) % 2 == 0)){
- alt.push( "x-grid-row-alt");
- }
- if(r.dirty){
- alt.push(" x-grid-dirty-row");
- }
- rp.cells = lcb;
- if(this.getRowClass){
- alt.push( this.getRowClass(r, rowIndex));
- }
- if (hasListener) {
- rowcfg = {
-
- record: r,
- rowIndex : rowIndex,
- rowClass : ''
- };
- this.grid.fireEvent('rowclass', this, rowcfg);
- alt.push(rowcfg.rowClass);
- }
-
- rp.alt = alt.join(" ");
- rp.cells = lcb.join("");
- lbuf[lbuf.length] = rt.apply(rp);
- rp.cells = cb.join("");
- buf[buf.length] = rt.apply(rp);
- }
- return [lbuf.join(""), buf.join("")];
- },
-
- renderBody : function(){
- var markup = this.renderRows();
- var bt = this.templates.body;
- return [bt.apply({rows: markup[0]}), bt.apply({rows: markup[1]})];
- },
-
+
/**
- * Refreshes the grid
- * @param {Boolean} headersToo
+ * Returns true is this panel was configured to be closable
+ * @return {Boolean}
*/
- refresh : function(headersToo){
- this.fireEvent("beforerefresh", this);
- this.grid.stopEditing();
- var result = this.renderBody();
- this.lockedBody.update(result[0]);
- this.mainBody.update(result[1]);
- if(headersToo === true){
- this.updateHeaders();
- this.updateColumns();
- this.updateSplitters();
- this.updateHeaderSortState();
- }
- this.syncRowHeights();
- this.layout();
- this.fireEvent("refresh", this);
- },
-
- handleColumnMove : function(cm, oldIndex, newIndex){
- this.indexMap = null;
- var s = this.getScrollState();
- this.refresh(true);
- this.restoreScroll(s);
- this.afterMove(newIndex);
- },
-
- afterMove : function(colIndex){
- if(this.enableMoveAnim && Roo.enableFx){
- this.fly(this.getHeaderCell(colIndex).firstChild).highlight(this.hlColor);
- }
- // if multisort - fix sortOrder, and reload..
- if (this.grid.dataSource.multiSort) {
- // the we can call sort again..
- var dm = this.grid.dataSource;
- var cm = this.grid.colModel;
- var so = [];
- for(var i = 0; i < cm.config.length; i++ ) {
-
- if ((typeof(dm.sortToggle[cm.config[i].dataIndex]) == 'undefined')) {
- continue; // dont' bother, it's not in sort list or being set.
- }
-
- so.push(cm.config[i].dataIndex);
- };
- dm.sortOrder = so;
- dm.load(dm.lastOptions);
-
-
- }
-
+ isClosable : function(){
+ return this.closable;
},
-
- updateCell : function(dm, rowIndex, dataIndex){
- var colIndex = this.getColumnIndexByDataIndex(dataIndex);
- if(typeof colIndex == "undefined"){ // not present in grid
- return;
- }
- var cm = this.grid.colModel;
- var cell = this.getCell(rowIndex, colIndex);
- var cellText = this.getCellText(rowIndex, colIndex);
-
- var p = {
- cellId : "x-grid-cell-" + rowIndex + "-" + colIndex,
- id : cm.getColumnId(colIndex),
- css: colIndex == cm.getColumnCount()-1 ? "x-grid-col-last" : ""
- };
- var renderer = cm.getRenderer(colIndex);
- var val = renderer(dm.getValueAt(rowIndex, dataIndex), p, rowIndex, colIndex, dm);
- if(typeof val == "undefined" || val === "") {
- val = " ";
- }
- cellText.innerHTML = val;
- cell.className = this.cellClass + " " + this.idToCssName(p.cellId) + " " + p.css;
- this.syncRowHeights(rowIndex, rowIndex);
+
+ beforeSlide : function(){
+ this.el.clip();
+ this.resizeEl.clip();
},
-
- calcColumnWidth : function(colIndex, maxRowsToMeasure){
- var maxWidth = 0;
- if(this.grid.autoSizeHeaders){
- var h = this.getHeaderCellMeasure(colIndex);
- maxWidth = Math.max(maxWidth, h.scrollWidth);
- }
- var tb, index;
- if(this.cm.isLocked(colIndex)){
- tb = this.getLockedTable();
- index = colIndex;
- }else{
- tb = this.getBodyTable();
- index = colIndex - this.cm.getLockedCount();
- }
- if(tb && tb.rows){
- var rows = tb.rows;
- var stopIndex = Math.min(maxRowsToMeasure || rows.length, rows.length);
- for(var i = 0; i < stopIndex; i++){
- var cell = rows[i].childNodes[index].firstChild;
- maxWidth = Math.max(maxWidth, cell.scrollWidth);
- }
- }
- return maxWidth + /*margin for error in IE*/ 5;
+
+ afterSlide : function(){
+ this.el.unclip();
+ this.resizeEl.unclip();
},
+
/**
- * Autofit a column to its content.
- * @param {Number} colIndex
- * @param {Boolean} forceMinSize true to force the column to go smaller if possible
+ * Force a content refresh from the URL specified in the {@link #setUrl} method.
+ * Will fail silently if the {@link #setUrl} method has not been called.
+ * This does not activate the panel, just updates its content.
*/
- autoSizeColumn : function(colIndex, forceMinSize, suppressEvent){
- if(this.cm.isHidden(colIndex)){
- return; // can't calc a hidden column
- }
- if(forceMinSize){
- var cid = this.cm.getColumnId(colIndex);
- this.css.updateRule(this.colSelector +this.idToCssName( cid), "width", this.grid.minColumnWidth + "px");
- if(this.grid.autoSizeHeaders){
- this.css.updateRule(this.hdSelector + this.idToCssName(cid), "width", this.grid.minColumnWidth + "px");
- }
- }
- var newWidth = this.calcColumnWidth(colIndex);
- this.cm.setColumnWidth(colIndex,
- Math.max(this.grid.minColumnWidth, newWidth), suppressEvent);
- if(!suppressEvent){
- this.grid.fireEvent("columnresize", colIndex, newWidth);
+ refresh : function(){
+ if(this.refreshDelegate){
+ this.loaded = false;
+ this.refreshDelegate();
}
},
-
+
/**
- * Autofits all columns to their content and then expands to fit any extra space in the grid
+ * Destroys this panel
*/
- autoSizeColumns : function(){
- var cm = this.grid.colModel;
- var colCount = cm.getColumnCount();
- for(var i = 0; i < colCount; i++){
- this.autoSizeColumn(i, true, true);
- }
- if(cm.getTotalWidth() < this.scroller.dom.clientWidth){
- this.fitColumns();
- }else{
- this.updateColumns();
- this.layout();
- }
+ destroy : function(){
+ this.el.removeAllListeners();
+ var tempEl = document.createElement("span");
+ tempEl.appendChild(this.el.dom);
+ tempEl.innerHTML = "";
+ this.el.remove();
+ this.el = null;
},
-
+
/**
- * Autofits all columns to the grid's width proportionate with their current size
- * @param {Boolean} reserveScrollSpace Reserve space for a scrollbar
+ * form - if the content panel contains a form - this is a reference to it.
+ * @type {Roo.form.Form}
*/
- fitColumns : function(reserveScrollSpace){
- var cm = this.grid.colModel;
- var colCount = cm.getColumnCount();
- var cols = [];
- var width = 0;
- var i, w;
- for (i = 0; i < colCount; i++){
- if(!cm.isHidden(i) && !cm.isFixed(i)){
- w = cm.getColumnWidth(i);
- cols.push(i);
- cols.push(w);
- width += w;
- }
- }
- var avail = Math.min(this.scroller.dom.clientWidth, this.el.getWidth());
- if(reserveScrollSpace){
- avail -= 17;
- }
- var frac = (avail - cm.getTotalWidth())/width;
- while (cols.length){
- w = cols.pop();
- i = cols.pop();
- cm.setColumnWidth(i, Math.floor(w + w*frac), true);
- }
- this.updateColumns();
- this.layout();
- },
+ form : false,
+ /**
+ * view - if the content panel contains a view (Roo.DatePicker / Roo.View / Roo.JsonView)
+ * This contains a reference to it.
+ * @type {Roo.View}
+ */
+ view : false,
+
+ /**
+ * Adds a xtype elements to the panel - currently only supports Forms, View, JsonView.
+ * <pre><code>
- onRowSelect : function(rowIndex){
- var row = this.getRowComposite(rowIndex);
- row.addClass("x-grid-row-selected");
- },
+layout.addxtype({
+ xtype : 'Form',
+ items: [ .... ]
+ }
+);
- onRowDeselect : function(rowIndex){
- var row = this.getRowComposite(rowIndex);
- row.removeClass("x-grid-row-selected");
- },
+</code></pre>
+ * @param {Object} cfg Xtype definition of item to add.
+ */
+
+ addxtype : function(cfg) {
+ if(cfg.xtype.match(/^UploadCropbox$/)) {
- onCellSelect : function(row, col){
- var cell = this.getCell(row, col);
- if(cell){
- Roo.fly(cell).addClass("x-grid-cell-selected");
- }
- },
+ this.cropbox = new Roo.factory(cfg);
- onCellDeselect : function(row, col){
- var cell = this.getCell(row, col);
- if(cell){
- Roo.fly(cell).removeClass("x-grid-cell-selected");
+ this.cropbox.render(this.el);
+
+ return this.cropbox;
}
- },
+ // add form..
+ if (cfg.xtype.match(/^Form$/)) {
+
+ var el;
+ //if (this.footer) {
+ // el = this.footer.container.insertSibling(false, 'before');
+ //} else {
+ el = this.el.createChild();
+ //}
- updateHeaderSortState : function(){
-
- // sort state can be single { field: xxx, direction : yyy}
- // or { xxx=>ASC , yyy : DESC ..... }
-
- var mstate = {};
- if (!this.ds.multiSort) {
- var state = this.ds.getSortState();
- if(!state){
- return;
- }
- mstate[state.field] = state.direction;
- // FIXME... - this is not used here.. but might be elsewhere..
- this.sortState = state;
+ this.form = new Roo.form.Form(cfg);
- } else {
- mstate = this.ds.sortToggle;
- }
- //remove existing sort classes..
-
- var sc = this.sortClasses;
- var hds = this.el.select(this.headerSelector).removeClass(sc);
-
- for(var f in mstate) {
-
- var sortColumn = this.cm.findColumnIndex(f);
- if(sortColumn != -1){
- var sortDir = mstate[f];
- hds.item(sortColumn).addClass(sc[sortDir == "DESC" ? 1 : 0]);
+ if ( this.form.allItems.length) {
+ this.form.render(el.dom);
}
+ return this.form;
}
-
-
-
- },
+ // should only have one of theses..
+ if ([ 'View', 'JsonView', 'DatePicker'].indexOf(cfg.xtype) > -1) {
+ // views.. should not be just added - used named prop 'view''
+
+ cfg.el = this.el.appendChild(document.createElement("div"));
+ // factory?
+
+ var ret = new Roo.factory(cfg);
+
+ ret.render && ret.render(false, ''); // render blank..
+ this.view = ret;
+ return ret;
+ }
+ return false;
+ }
+});
- handleHeaderClick : function(g, index,e){
-
- Roo.log("header click");
-
- if (Roo.isTouch) {
- // touch events on header are handled by context
- this.handleHdCtx(g,index,e);
- return;
- }
-
-
- if(this.headersDisabled){
- return;
- }
- var dm = g.dataSource, cm = g.colModel;
- if(!cm.isSortable(index)){
- return;
- }
- g.stopEditing();
-
- if (dm.multiSort) {
- // update the sortOrder
- var so = [];
- for(var i = 0; i < cm.config.length; i++ ) {
-
- if ((typeof(dm.sortToggle[cm.config[i].dataIndex]) == 'undefined') && (index != i)) {
- continue; // dont' bother, it's not in sort list or being set.
- }
-
- so.push(cm.config[i].dataIndex);
- };
- dm.sortOrder = so;
- }
-
-
- dm.sort(cm.getDataIndex(index));
- },
- destroy : function(){
- if(this.colMenu){
- this.colMenu.removeAll();
- Roo.menu.MenuMgr.unregister(this.colMenu);
- this.colMenu.getEl().remove();
- delete this.colMenu;
- }
- if(this.hmenu){
- this.hmenu.removeAll();
- Roo.menu.MenuMgr.unregister(this.hmenu);
- this.hmenu.getEl().remove();
- delete this.hmenu;
- }
- if(this.grid.enableColumnMove){
- var dds = Roo.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
- if(dds){
- for(var dd in dds){
- if(!dds[dd].config.isTarget && dds[dd].dragElId){
- var elid = dds[dd].dragElId;
- dds[dd].unreg();
- Roo.get(elid).remove();
- } else if(dds[dd].config.isTarget){
- dds[dd].proxyTop.remove();
- dds[dd].proxyBottom.remove();
- dds[dd].unreg();
- }
- if(Roo.dd.DDM.locationCache[dd]){
- delete Roo.dd.DDM.locationCache[dd];
- }
- }
- delete Roo.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
- }
- }
- Roo.util.CSS.removeStyleSheet(this.idToCssName(this.grid.id) + '-cssrules');
- this.bind(null, null);
- Roo.EventManager.removeResizeListener(this.onWindowResize, this);
- },
- handleLockChange : function(){
- this.refresh(true);
- },
- onDenyColumnLock : function(){
- },
- onDenyColumnHide : function(){
- },
- handleHdMenuClick : function(item){
- var index = this.hdCtxIndex;
- var cm = this.cm, ds = this.ds;
- switch(item.id){
- case "asc":
- ds.sort(cm.getDataIndex(index), "ASC");
- break;
- case "desc":
- ds.sort(cm.getDataIndex(index), "DESC");
- break;
- case "lock":
- var lc = cm.getLockedCount();
- if(cm.getColumnCount(true) <= lc+1){
- this.onDenyColumnLock();
- return;
- }
- if(lc != index){
- cm.setLocked(index, true, true);
- cm.moveColumn(index, lc);
- this.grid.fireEvent("columnmove", index, lc);
- }else{
- cm.setLocked(index, true);
- }
- break;
- case "unlock":
- var lc = cm.getLockedCount();
- if((lc-1) != index){
- cm.setLocked(index, false, true);
- cm.moveColumn(index, lc-1);
- this.grid.fireEvent("columnmove", index, lc-1);
- }else{
- cm.setLocked(index, false);
- }
- break;
- case 'wider': // used to expand cols on touch..
- case 'narrow':
- var cw = cm.getColumnWidth(index);
- cw += (item.id == 'wider' ? 1 : -1) * 50;
- cw = Math.max(0, cw);
- cw = Math.min(cw,4000);
- cm.setColumnWidth(index, cw);
- break;
-
- default:
- index = cm.getIndexById(item.id.substr(4));
- if(index != -1){
- if(item.checked && cm.getColumnCount(true) <= 1){
- this.onDenyColumnHide();
- return false;
- }
- cm.setHidden(index, item.checked);
- }
- }
- return true;
- },
- beforeColMenuShow : function(){
- var cm = this.cm, colCount = cm.getColumnCount();
- this.colMenu.removeAll();
- for(var i = 0; i < colCount; i++){
- this.colMenu.add(new Roo.menu.CheckItem({
- id: "col-"+cm.getColumnId(i),
- text: cm.getColumnHeader(i),
- checked: !cm.isHidden(i),
- hideOnClick:false
- }));
- }
- },
- handleHdCtx : function(g, index, e){
- e.stopEvent();
- var hd = this.getHeaderCell(index);
- this.hdCtxIndex = index;
- var ms = this.hmenu.items, cm = this.cm;
- ms.get("asc").setDisabled(!cm.isSortable(index));
- ms.get("desc").setDisabled(!cm.isSortable(index));
- if(this.grid.enableColLock !== false){
- ms.get("lock").setDisabled(cm.isLocked(index));
- ms.get("unlock").setDisabled(!cm.isLocked(index));
- }
- this.hmenu.show(hd, "tl-bl");
- },
+/**
+ * @class Roo.GridPanel
+ * @extends Roo.ContentPanel
+ * @parent Roo.BorderLayout Roo.LayoutDialog builder
+ * @constructor
+ * Create a new GridPanel.
+ * @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);
+
+ this.wrapper.dom.appendChild(grid.getGridEl().dom);
+
+ Roo.GridPanel.superclass.constructor.call(this, this.wrapper, config);
+
+ if(this.toolbar){
+ this.toolbar.el.insertBefore(this.wrapper.dom.firstChild);
+ }
+ // xtype created footer. - not sure if will work as we normally have to render first..
+ if (this.footer && !this.footer.el && this.footer.xtype) {
+
+ this.footer.container = this.grid.getView().getFooterPanel(true);
+ this.footer.dataSource = this.grid.dataSource;
+ this.footer = Roo.factory(this.footer, Roo);
+
+ }
+
+ grid.monitorWindowResize = false; // turn off autosizing
+ grid.autoHeight = false;
+ grid.autoWidth = false;
+ this.grid = grid;
+ this.grid.getGridEl().replaceClass("x-layout-inactive-content", "x-layout-component-panel");
+};
- handleHdOver : function(e){
- var hd = this.findHeaderCell(e.getTarget());
- if(hd && !this.headersDisabled){
- if(this.grid.colModel.isSortable(this.getCellIndex(hd))){
- this.fly(hd).addClass("x-grid-hd-over");
- }
- }
+Roo.extend(Roo.GridPanel, Roo.ContentPanel, {
+ getId : function(){
+ return this.grid.id;
},
-
- handleHdOut : function(e){
- var hd = this.findHeaderCell(e.getTarget());
- if(hd){
- this.fly(hd).removeClass("x-grid-hd-over");
- }
+
+ /**
+ * Returns the grid for this panel
+ * @return {Roo.grid.Grid}
+ */
+ getGrid : function(){
+ return this.grid;
},
-
- handleSplitDblClick : function(e, t){
- var i = this.getCellIndex(t);
- if(this.grid.enableColumnResize !== false && this.cm.isResizable(i) && !this.cm.isFixed(i)){
- this.autoSizeColumn(i, true);
- this.layout();
+
+ setSize : function(width, height){
+ if(!this.ignoreResize(width, height)){
+ var grid = this.grid;
+ var size = this.adjustForComponents(width, height);
+ grid.getGridEl().setSize(size.width, size.height);
+ grid.autoSize();
}
},
+
+ beforeSlide : function(){
+ this.grid.getView().scroller.clip();
+ },
+
+ afterSlide : function(){
+ this.grid.getView().scroller.unclip();
+ },
+
+ destroy : function(){
+ this.grid.destroy();
+ delete this.grid;
+ Roo.GridPanel.superclass.destroy.call(this);
+ }
+});
- render : function(){
-
- var cm = this.cm;
- var colCount = cm.getColumnCount();
-
- if(this.grid.monitorWindowResize === true){
- Roo.EventManager.onWindowResize(this.onWindowResize, this, true);
- }
- var header = this.renderHeaders();
- var body = this.templates.body.apply({rows:""});
- var html = this.templates.master.apply({
- lockedBody: body,
- body: body,
- lockedHeader: header[0],
- header: header[1]
- });
-
- //this.updateColumns();
-
- this.grid.getGridEl().dom.innerHTML = html;
-
- this.initElements();
-
- // a kludge to fix the random scolling effect in webkit
- this.el.on("scroll", function() {
- this.el.dom.scrollTop=0; // hopefully not recursive..
- },this);
- this.scroller.on("scroll", this.handleScroll, this);
- this.lockedBody.on("mousewheel", this.handleWheel, this);
- this.mainBody.on("mousewheel", this.handleWheel, this);
+/**
+ * @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 [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)
+{
+ // construct with only one argument..
+ /* FIXME - implement nicer consturctors
+ if (layout.layout) {
+ config = layout;
+ layout = config.layout;
+ delete config.layout;
+ }
+ if (layout.xtype && !layout.getEl) {
+ // then layout needs constructing..
+ layout = Roo.factory(layout, Roo);
+ }
+ */
+
+
+ Roo.NestedLayoutPanel.superclass.constructor.call(this, layout.getEl(), config);
+
+ layout.monitorWindowResize = false; // turn off autosizing
+ this.layout = layout;
+ this.layout.getEl().addClass("x-layout-nested-layout");
+
+
+
+
+};
- this.mainHd.on("mouseover", this.handleHdOver, this);
- this.mainHd.on("mouseout", this.handleHdOut, this);
- this.mainHd.on("dblclick", this.handleSplitDblClick, this,
- {delegate: "."+this.splitClass});
+Roo.extend(Roo.NestedLayoutPanel, Roo.ContentPanel, {
- this.lockedHd.on("mouseover", this.handleHdOver, this);
- this.lockedHd.on("mouseout", this.handleHdOut, this);
- this.lockedHd.on("dblclick", this.handleSplitDblClick, this,
- {delegate: "."+this.splitClass});
+ layout : false,
- if(this.grid.enableColumnResize !== false && Roo.grid.SplitDragZone){
- new Roo.grid.SplitDragZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
+ setSize : function(width, height){
+ if(!this.ignoreResize(width, height)){
+ var size = this.adjustForComponents(width, height);
+ var el = this.layout.getEl();
+ el.setSize(size.width, size.height);
+ var touch = el.dom.offsetWidth;
+ this.layout.layout();
+ // ie requires a double layout on the first pass
+ if(Roo.isIE && !this.initialized){
+ this.initialized = true;
+ this.layout.layout();
+ }
}
-
- this.updateSplitters();
-
- if(this.grid.enableColumnMove && Roo.grid.HeaderDragZone){
- new Roo.grid.HeaderDragZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
- new Roo.grid.HeaderDropZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
+ },
+
+ // activate all subpanels if not currently active..
+
+ setActiveState : function(active){
+ this.active = active;
+ if(!active){
+ this.fireEvent("deactivate", this);
+ return;
}
-
- if(this.grid.enableCtxMenu !== false && Roo.menu.Menu){
- this.hmenu = new Roo.menu.Menu({id: this.grid.id + "-hctx"});
- this.hmenu.add(
- {id:"asc", text: this.sortAscText, cls: "xg-hmenu-sort-asc"},
- {id:"desc", text: this.sortDescText, cls: "xg-hmenu-sort-desc"}
- );
- if(this.grid.enableColLock !== false){
- this.hmenu.add('-',
- {id:"lock", text: this.lockText, cls: "xg-hmenu-lock"},
- {id:"unlock", text: this.unlockText, cls: "xg-hmenu-unlock"}
- );
+
+ this.fireEvent("activate", this);
+ // not sure if this should happen before or after..
+ if (!this.layout) {
+ return; // should not happen..
+ }
+ var reg = false;
+ for (var r in this.layout.regions) {
+ reg = this.layout.getRegion(r);
+ if (reg.getActivePanel()) {
+ //reg.showPanel(reg.getActivePanel()); // force it to activate..
+ reg.setActivePanel(reg.getActivePanel());
+ continue;
}
- if (Roo.isTouch) {
- this.hmenu.add('-',
- {id:"wider", text: this.columnsWiderText},
- {id:"narrow", text: this.columnsNarrowText }
- );
-
-
+ if (!reg.panels.length) {
+ continue;
}
-
- if(this.grid.enableColumnHide !== false){
-
- this.colMenu = new Roo.menu.Menu({id:this.grid.id + "-hcols-menu"});
- this.colMenu.on("beforeshow", this.beforeColMenuShow, this);
- this.colMenu.on("itemclick", this.handleHdMenuClick, this);
+ reg.showPanel(reg.getPanel(0));
+ }
+
+
+
+
+ },
+
+ /**
+ * Returns the nested BorderLayout for this panel
+ * @return {Roo.BorderLayout}
+ */
+ getLayout : function(){
+ return this.layout;
+ },
+
+ /**
+ * Adds a xtype elements to the layout of the nested panel
+ * <pre><code>
- this.hmenu.add('-',
- {id:"columns", text: this.columnsText, menu: this.colMenu}
- );
- }
- this.hmenu.on("itemclick", this.handleHdMenuClick, this);
+panel.addxtype({
+ xtype : 'ContentPanel',
+ region: 'west',
+ items: [ .... ]
+ }
+);
- this.grid.on("headercontextmenu", this.handleHdCtx, this);
- }
+panel.addxtype({
+ xtype : 'NestedLayoutPanel',
+ region: 'west',
+ layout: {
+ center: { },
+ west: { }
+ },
+ items : [ ... list of content panels or nested layout panels.. ]
+ }
+);
+</code></pre>
+ * @param {Object} cfg Xtype definition of item to add.
+ */
+ addxtype : function(cfg) {
+ return this.layout.addxtype(cfg);
+
+ }
+});
- if((this.grid.enableDragDrop || this.grid.enableDrag) && Roo.grid.GridDragZone){
- this.dd = new Roo.grid.GridDragZone(this.grid, {
- ddGroup : this.grid.ddGroup || 'GridDD'
- });
-
- }
+Roo.ScrollPanel = function(el, config, content){
+ config = config || {};
+ config.fitToFrame = true;
+ Roo.ScrollPanel.superclass.constructor.call(this, el, config, content);
+
+ this.el.dom.style.overflow = "hidden";
+ var wrap = this.el.wrap({cls: "x-scroller x-layout-inactive-content"});
+ this.el.removeClass("x-layout-inactive-content");
+ this.el.on("mousewheel", this.onWheel, this);
- /*
- for(var i = 0; i < colCount; i++){
- if(cm.isHidden(i)){
- this.hideColumn(i);
- }
- if(cm.config[i].align){
- this.css.updateRule(this.colSelector + i, "textAlign", cm.config[i].align);
- this.css.updateRule(this.hdSelector + i, "textAlign", cm.config[i].align);
- }
- }*/
-
- this.updateHeaderSortState();
+ var up = wrap.createChild({cls: "x-scroller-up", html: " "}, this.el.dom);
+ var down = wrap.createChild({cls: "x-scroller-down", html: " "});
+ up.unselectable(); down.unselectable();
+ up.on("click", this.scrollUp, this);
+ down.on("click", this.scrollDown, this);
+ up.addClassOnOver("x-scroller-btn-over");
+ down.addClassOnOver("x-scroller-btn-over");
+ up.addClassOnClick("x-scroller-btn-click");
+ down.addClassOnClick("x-scroller-btn-click");
+ this.adjustments = [0, -(up.getHeight() + down.getHeight())];
- this.beforeInitialResize();
- this.layout(true);
+ this.resizeEl = this.el;
+ this.el = wrap; this.up = up; this.down = down;
+};
- // two part rendering gives faster view to the user
- this.renderPhase2.defer(1, this);
+Roo.extend(Roo.ScrollPanel, Roo.ContentPanel, {
+ increment : 100,
+ wheelIncrement : 5,
+ scrollUp : function(){
+ this.resizeEl.scroll("up", this.increment, {callback: this.afterScroll, scope: this});
},
- renderPhase2 : function(){
- // render the rows now
- this.refresh();
- if(this.grid.autoSizeColumns){
- this.autoSizeColumns();
- }
+ scrollDown : function(){
+ this.resizeEl.scroll("down", this.increment, {callback: this.afterScroll, scope: this});
},
- beforeInitialResize : function(){
-
+ afterScroll : function(){
+ var el = this.resizeEl;
+ var t = el.dom.scrollTop, h = el.dom.scrollHeight, ch = el.dom.clientHeight;
+ this.up[t == 0 ? "addClass" : "removeClass"]("x-scroller-btn-disabled");
+ this.down[h - t <= ch ? "addClass" : "removeClass"]("x-scroller-btn-disabled");
},
- onColumnSplitterMoved : function(i, w){
- this.userResized = true;
- var cm = this.grid.colModel;
- cm.setColumnWidth(i, w, true);
- var cid = cm.getColumnId(i);
- this.css.updateRule(this.colSelector + this.idToCssName(cid), "width", (w-this.borderWidth) + "px");
- this.css.updateRule(this.hdSelector + this.idToCssName(cid), "width", (w-this.borderWidth) + "px");
- this.updateSplitters();
- this.layout();
- this.grid.fireEvent("columnresize", i, w);
+ setSize : function(){
+ Roo.ScrollPanel.superclass.setSize.apply(this, arguments);
+ this.afterScroll();
},
- syncRowHeights : function(startIndex, endIndex){
- if(this.grid.enableRowHeightSync === true && this.cm.getLockedCount() > 0){
- startIndex = startIndex || 0;
- var mrows = this.getBodyTable().rows;
- var lrows = this.getLockedTable().rows;
- var len = mrows.length-1;
- endIndex = Math.min(endIndex || len, len);
- for(var i = startIndex; i <= endIndex; i++){
- var m = mrows[i], l = lrows[i];
- var h = Math.max(m.offsetHeight, l.offsetHeight);
- m.style.height = l.style.height = h + "px";
- }
- }
+ onWheel : function(e){
+ var d = e.getWheelDelta();
+ this.resizeEl.dom.scrollTop -= (d*this.wheelIncrement);
+ this.afterScroll();
+ e.stopEvent();
},
- layout : function(initialRender, is2ndPass){
- var g = this.grid;
- var auto = g.autoHeight;
- var scrollOffset = 16;
- var c = g.getGridEl(), cm = this.cm,
- expandCol = g.autoExpandColumn,
- gv = this;
- //c.beginMeasure();
+ setContent : function(content, loadScripts){
+ this.resizeEl.update(content, loadScripts);
+ }
- if(!c.dom.offsetWidth){ // display:none?
- if(initialRender){
- this.lockedWrap.show();
- this.mainWrap.show();
- }
- return;
- }
+});
- var hasLock = this.cm.isLocked(0);
- var tbh = this.headerPanel.getHeight();
- var bbh = this.footerPanel.getHeight();
- if(auto){
- var ch = this.getBodyTable().offsetHeight + tbh + bbh + this.mainHd.getHeight();
- var newHeight = ch + c.getBorderWidth("tb");
- if(g.maxHeight){
- newHeight = Math.min(g.maxHeight, newHeight);
- }
- c.setHeight(newHeight);
+/**
+ * @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
+ */
+Roo.TreePanel = function(config){
+ var el = config.el;
+ var tree = config.tree;
+ delete config.tree;
+ delete config.el; // hopefull!
+
+ // wrapper for IE7 strict & safari scroll issue
+
+ var treeEl = el.createChild();
+ config.resizeEl = treeEl;
+
+
+
+ Roo.TreePanel.superclass.constructor.call(this, el, config);
+
+
+ this.tree = new Roo.tree.TreePanel(treeEl , tree);
+ //console.log(tree);
+ this.on('activate', function()
+ {
+ if (this.tree.rendered) {
+ return;
}
+ //console.log('render tree');
+ this.tree.render();
+ });
+ // this should not be needed.. - it's actually the 'el' that resizes?
+ // actuall it breaks the containerScroll - dragging nodes auto scroll at top
+
+ //this.on('resize', function (cp, w, h) {
+ // this.tree.innerCt.setWidth(w);
+ // this.tree.innerCt.setHeight(h);
+ // //this.tree.innerCt.setStyle('overflow-y', 'auto');
+ //});
- if(g.autoWidth){
- c.setWidth(cm.getTotalWidth()+c.getBorderWidth('lr'));
- }
-
- var s = this.scroller;
-
- var csize = c.getSize(true);
-
- this.el.setSize(csize.width, csize.height);
-
- this.headerPanel.setWidth(csize.width);
- this.footerPanel.setWidth(csize.width);
-
- var hdHeight = this.mainHd.getHeight();
- var vw = csize.width;
- var vh = csize.height - (tbh + bbh);
-
- s.setSize(vw, vh);
-
- var bt = this.getBodyTable();
-
- if(cm.getLockedCount() == cm.config.length){
- bt = this.getLockedTable();
- }
- var ltWidth = hasLock ?
- Math.max(this.getLockedTable().offsetWidth, this.lockedHd.dom.firstChild.offsetWidth) : 0;
-
- var scrollHeight = bt.offsetHeight;
- var scrollWidth = ltWidth + bt.offsetWidth;
- var vscroll = false, hscroll = false;
-
- this.scrollSizer.setSize(scrollWidth, scrollHeight+hdHeight);
-
- var lw = this.lockedWrap, mw = this.mainWrap;
- var lb = this.lockedBody, mb = this.mainBody;
-
- setTimeout(function(){
- var t = s.dom.offsetTop;
- var w = s.dom.clientWidth,
- h = s.dom.clientHeight;
-
- lw.setTop(t);
- lw.setSize(ltWidth, h);
-
- mw.setLeftTop(ltWidth, t);
- mw.setSize(w-ltWidth, h);
-
- lb.setHeight(h-hdHeight);
- mb.setHeight(h-hdHeight);
-
- if(is2ndPass !== true && !gv.userResized && expandCol){
- // high speed resize without full column calculation
-
- var ci = cm.getIndexById(expandCol);
- if (ci < 0) {
- ci = cm.findColumnIndex(expandCol);
- }
- ci = Math.max(0, ci); // make sure it's got at least the first col.
- var expandId = cm.getColumnId(ci);
- var tw = cm.getTotalWidth(false);
- var currentWidth = cm.getColumnWidth(ci);
- var cw = Math.min(Math.max(((w-tw)+currentWidth-2)-/*scrollbar*/(w <= s.dom.offsetWidth ? 0 : 18), g.autoExpandMin), g.autoExpandMax);
- if(currentWidth != cw){
- cm.setColumnWidth(ci, cw, true);
- gv.css.updateRule(gv.colSelector+gv.idToCssName(expandId), "width", (cw - gv.borderWidth) + "px");
- gv.css.updateRule(gv.hdSelector+gv.idToCssName(expandId), "width", (cw - gv.borderWidth) + "px");
- gv.updateSplitters();
- gv.layout(false, true);
- }
- }
-
- if(initialRender){
- lw.show();
- mw.show();
- }
- //c.endMeasure();
- }, 10);
- },
-
- onWindowResize : function(){
- if(!this.grid.monitorWindowResize || this.grid.autoHeight){
- return;
- }
- this.layout();
- },
-
- appendFooter : function(parentEl){
- return null;
- },
-
- sortAscText : "Sort Ascending",
- sortDescText : "Sort Descending",
- lockText : "Lock Column",
- unlockText : "Unlock Column",
- columnsText : "Columns",
-
- columnsWiderText : "Wider",
- columnsNarrowText : "Thinner"
-});
-
-
-Roo.grid.GridView.ColumnDragZone = function(grid, hd){
- Roo.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
- this.proxy.el.addClass('x-grid3-col-dd');
+
};
-Roo.extend(Roo.grid.GridView.ColumnDragZone, Roo.grid.HeaderDragZone, {
- handleMouseDown : function(e){
-
- },
+Roo.extend(Roo.TreePanel, Roo.ContentPanel, {
+ fitToFrame : true,
+ autoScroll : true,
+ /*
+ * @cfg {Roo.tree.TreePanel} tree [required] The tree TreePanel, with config etc.
+ */
+ tree : false
- callHandleMouseDown : function(e){
- Roo.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
- }
});
/*
* Based on:
* <script type="text/javascript">
*/
-// 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
- });
- this.setHandleElId(Roo.id(hd));
- this.setOuterHandleElId(Roo.id(hd2));
- this.scroll = false;
-};
-Roo.extend(Roo.grid.SplitDragZone, Roo.dd.DDProxy, {
- fly: Roo.Element.fly,
-
- b4StartDrag : function(x, y){
- this.view.headersDisabled = true;
- this.proxy.setHeight(this.view.mainWrap.getHeight());
- var w = this.cm.getColumnWidth(this.cellIndex);
- var minw = Math.max(w-this.grid.minColumnWidth, 0);
- this.resetConstraints();
- this.setXConstraint(minw, 1000);
- this.setYConstraint(0, 0);
- this.minX = x - minw;
- this.maxX = x + 1000;
- this.startPos = x;
- Roo.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
- },
-
-
- handleMouseDown : function(e){
- ev = Roo.EventObject.setEvent(e);
- var t = this.fly(ev.getTarget());
- if(t.hasClass("x-grid-split")){
- this.cellIndex = this.view.getCellIndex(t.dom);
- this.split = t.dom;
- this.cm = this.grid.colModel;
- if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
- Roo.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
- }
- }
- },
-
- endDrag : function(e){
- 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);
- },
-
- autoOffset : function(){
- this.setDelta(0,0);
- }
-});/*
- * 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">
- */
-
-// private
-// This is a support class used internally by the Grid components
-Roo.grid.GridDragZone = function(grid, config){
- this.view = grid.getView();
- Roo.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
- if(this.view.lockedBody){
- this.setHandleElId(Roo.id(this.view.mainBody.dom));
- this.setOuterHandleElId(Roo.id(this.view.lockedBody.dom));
- }
- this.scroll = false;
- this.grid = grid;
- this.ddel = document.createElement('div');
- this.ddel.className = 'x-grid-dd-wrap';
-};
-Roo.extend(Roo.grid.GridDragZone, Roo.dd.DragZone, {
- ddGroup : "GridDD",
+/**
+ * @class Roo.ReaderLayout
+ * @extends Roo.BorderLayout
+ * This is a pre-built layout that represents a classic, 5-pane application. It consists of a header, a primary
+ * center region containing two nested regions (a top one for a list view and one for item preview below),
+ * and regions on either side that can be used for navigation, application commands, informational displays, etc.
+ * The setup and configuration work exactly the same as it does for a {@link Roo.BorderLayout} - this class simply
+ * expedites the setup of the overall layout and regions for this common application style.
+ * Example:
+ <pre><code>
+var reader = new Roo.ReaderLayout();
+var CP = Roo.ContentPanel; // shortcut for adding
- getDragData : function(e){
- var t = Roo.lib.Event.getTarget(e);
- var rowIndex = this.view.findRowIndex(t);
- var sm = this.grid.selModel;
-
- //Roo.log(rowIndex);
-
- if (sm.getSelectedCell) {
- // cell selection..
- if (!sm.getSelectedCell()) {
- return false;
- }
- if (rowIndex != sm.getSelectedCell()[0]) {
- return false;
- }
-
- }
-
- if(rowIndex !== false){
-
- // if editorgrid..
-
-
- //Roo.log([ sm.getSelectedCell() ? sm.getSelectedCell()[0] : 'NO' , rowIndex ]);
-
- //if(!sm.isSelected(rowIndex) || e.hasModifier()){
- //
- //}
- if (e.hasModifier()){
- sm.handleMouseDown(e, t); // non modifier buttons are handled by row select.
- }
-
- Roo.log("getDragData");
-
- return {
- grid: this.grid,
- ddel: this.ddel,
- rowIndex: rowIndex,
- selections:sm.getSelections ? sm.getSelections() : (
- sm.getSelectedCell() ? [ this.grid.ds.getAt(sm.getSelectedCell()[0]) ] : []
- )
- };
- }
- return false;
- },
+reader.beginUpdate();
+reader.add("north", new CP("north", "North"));
+reader.add("west", new CP("west", {title: "West"}));
+reader.add("east", new CP("east", {title: "East"}));
- onInitDrag : function(e){
- var data = this.dragData;
- this.ddel.innerHTML = this.grid.getDragDropText();
- this.proxy.update(this.ddel);
- // fire start drag?
- },
+reader.regions.listView.add(new CP("listView", "List"));
+reader.regions.preview.add(new CP("preview", "Preview"));
+reader.endUpdate();
+</code></pre>
+* @constructor
+* Create a new ReaderLayout
+* @param {Object} config Configuration options
+* @param {String/HTMLElement/Element} container (optional) The container this layout is bound to (defaults to
+* document.body if omitted)
+*/
+Roo.ReaderLayout = function(config, renderTo){
+ var c = config || {size:{}};
+ Roo.ReaderLayout.superclass.constructor.call(this, renderTo || document.body, {
+ north: c.north !== false ? Roo.apply({
+ split:false,
+ initialSize: 32,
+ titlebar: false
+ }, c.north) : false,
+ west: c.west !== false ? Roo.apply({
+ split:true,
+ initialSize: 200,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true,
+ animate: true,
+ margins:{left:5,right:0,bottom:5,top:5},
+ cmargins:{left:5,right:5,bottom:5,top:5}
+ }, c.west) : false,
+ east: c.east !== false ? Roo.apply({
+ split:true,
+ initialSize: 200,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true,
+ animate: true,
+ margins:{left:0,right:5,bottom:5,top:5},
+ cmargins:{left:5,right:5,bottom:5,top:5}
+ }, c.east) : false,
+ center: Roo.apply({
+ tabPosition: 'top',
+ autoScroll:false,
+ closeOnTab: true,
+ titlebar:false,
+ margins:{left:c.west!==false ? 0 : 5,right:c.east!==false ? 0 : 5,bottom:5,top:2}
+ }, c.center)
+ });
- afterRepair : function(){
- this.dragging = false;
- },
+ this.el.addClass('x-reader');
- getRepairXY : function(e, data){
- return false;
- },
+ this.beginUpdate();
- onEndDrag : function(data, e){
- // fire end drag?
- },
+ var inner = new Roo.BorderLayout(Roo.get(document.body).createChild(), {
+ south: c.preview !== false ? Roo.apply({
+ split:true,
+ initialSize: 200,
+ minSize: 100,
+ autoScroll:true,
+ collapsible:true,
+ titlebar: true,
+ cmargins:{top:5,left:0, right:0, bottom:0}
+ }, c.preview) : false,
+ center: Roo.apply({
+ autoScroll:false,
+ titlebar:false,
+ minHeight:200
+ }, c.listView)
+ });
+ this.add('center', new Roo.NestedLayoutPanel(inner,
+ Roo.apply({title: c.mainTitle || '',tabTip:''},c.innerPanelCfg)));
- onValidDrop : function(dd, e, id){
- // fire drag drop?
- this.hideProxy();
- },
+ this.endUpdate();
- beforeInvalidDrop : function(e, id){
+ this.regions.preview = inner.getRegion('south');
+ this.regions.listView = inner.getRegion('center');
+};
- }
-});/*
+Roo.extend(Roo.ReaderLayout, Roo.BorderLayout);/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* <script type="text/javascript">
*/
-
/**
- * @class Roo.grid.ColumnModel
+ * @class Roo.grid.Grid
* @extends Roo.util.Observable
- * This is the default implementation of a ColumnModel used by the Grid. It defines
- * the columns in the grid.
- * <br>Usage:<br>
- <pre><code>
- var colModel = new Roo.grid.ColumnModel([
- {header: "Ticker", width: 60, sortable: true, locked: true},
- {header: "Company Name", width: 150, sortable: true},
- {header: "Market Cap.", width: 100, sortable: true},
- {header: "$ Sales", width: 100, sortable: true, renderer: money},
- {header: "Employees", width: 100, sortable: true, resizable: false}
- ]);
- </code></pre>
- * <p>
-
- * The config options listed for this class are options which may appear in each
- * individual column definition.
- * <br/>RooJS Fix - column id's are not sequential but use Roo.id() - fixes bugs with layouts.
- * @constructor
- * @param {Object} config An Array of column config objects. See this class's
- * config objects for details.
-*/
-Roo.grid.ColumnModel = function(config){
- /**
- * The config passed into the constructor
- */
- this.config = config;
- this.lookup = {};
+ * This class represents the primary interface of a component based grid control.
+ * <br><br>Usage:<pre><code>
+ var grid = new Roo.grid.Grid("my-container-id", {
+ ds: myDataStore,
+ cm: myColModel,
+ selModel: mySelectionModel,
+ autoSizeColumns: true,
+ monitorWindowResize: false,
+ trackMouseOver: true
+ });
+ // set any options
+ grid.render();
+ * </code></pre>
+ * <b>Common Problems:</b><br/>
+ * - Grid does not resize properly when going smaller: Setting overflow hidden on the container
+ * element will correct this<br/>
+ * - If you get el.style[camel]= NaNpx or -2px or something related, be certain you have given your container element
+ * dimensions. The grid adapts to your container's size, if your container has no size defined then the results
+ * are unpredictable.<br/>
+ * - Do not render the grid into an element with display:none. Try using visibility:hidden. Otherwise there is no way for the
+ * grid to calculate dimensions/offsets.<br/>
+ * @constructor
+ * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
+ * The container MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} config A config object that sets properties on this grid.
+ */
+Roo.grid.Grid = function(container, config){
+ // initialize the container
+ this.container = Roo.get(container);
+ this.container.update("");
+ this.container.setStyle("overflow", "hidden");
+ this.container.addClass('x-grid-container');
- // 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.id = this.container.id;
- /**
- * The width of columns which have no width specified (defaults to 100)
- * @type Number
- */
- this.defaultWidth = 100;
+ Roo.apply(this, config);
+ // check and correct shorthanded configs
+ if(this.ds){
+ this.dataSource = this.ds;
+ delete this.ds;
+ }
+ if(this.cm){
+ this.colModel = this.cm;
+ delete this.cm;
+ }
+ if(this.sm){
+ this.selModel = this.sm;
+ delete this.sm;
+ }
- /**
- * Default sortable of columns which have no sortable specified (defaults to false)
- * @type Boolean
- */
- this.defaultSortable = false;
+ if (this.selModel) {
+ this.selModel = Roo.factory(this.selModel, Roo.grid);
+ this.sm = this.selModel;
+ this.sm.xmodule = this.xmodule || false;
+ }
+ if (typeof(this.colModel.config) == 'undefined') {
+ this.colModel = new Roo.grid.ColumnModel(this.colModel);
+ this.cm = this.colModel;
+ this.cm.xmodule = this.xmodule || false;
+ }
+ if (this.dataSource) {
+ this.dataSource= Roo.factory(this.dataSource, Roo.data);
+ this.ds = this.dataSource;
+ this.ds.xmodule = this.xmodule || false;
+
+ }
+
+
+
+ if(this.width){
+ this.container.setWidth(this.width);
+ }
- this.addEvents({
- /**
- * @event widthchange
- * Fires when the width of a column changes.
- * @param {ColumnModel} this
- * @param {Number} columnIndex The column index
- * @param {Number} newWidth The new width
- */
- "widthchange": true,
+ if(this.height){
+ this.container.setHeight(this.height);
+ }
+ /** @private */
+ this.addEvents({
+ // raw events
/**
- * @event headerchange
- * Fires when the text of a header changes.
- * @param {ColumnModel} this
- * @param {Number} columnIndex The column index
- * @param {Number} newText The new header text
- */
- "headerchange": true,
+ * @event click
+ * The raw click event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "click" : true,
/**
- * @event hiddenchange
- * Fires when a column is hidden or "unhidden".
- * @param {ColumnModel} this
- * @param {Number} columnIndex The column index
- * @param {Boolean} hidden true if hidden, false otherwise
- */
- "hiddenchange": true,
- /**
- * @event columnmoved
- * Fires when a column is moved.
- * @param {ColumnModel} this
+ * @event dblclick
+ * The raw dblclick event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "dblclick" : true,
+ /**
+ * @event contextmenu
+ * The raw contextmenu event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "contextmenu" : true,
+ /**
+ * @event mousedown
+ * The raw mousedown event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mousedown" : true,
+ /**
+ * @event mouseup
+ * The raw mouseup event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mouseup" : true,
+ /**
+ * @event mouseover
+ * The raw mouseover event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mouseover" : true,
+ /**
+ * @event mouseout
+ * The raw mouseout event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mouseout" : true,
+ /**
+ * @event keypress
+ * The raw keypress event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "keypress" : true,
+ /**
+ * @event keydown
+ * The raw keydown event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "keydown" : true,
+
+ // custom events
+
+ /**
+ * @event cellclick
+ * Fires when a cell is clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "cellclick" : true,
+ /**
+ * @event celldblclick
+ * Fires when a cell is double clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "celldblclick" : true,
+ /**
+ * @event rowclick
+ * Fires when a row is clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Roo.EventObject} e
+ */
+ "rowclick" : true,
+ /**
+ * @event rowdblclick
+ * Fires when a row is double clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Roo.EventObject} e
+ */
+ "rowdblclick" : true,
+ /**
+ * @event headerclick
+ * Fires when a header is clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "headerclick" : true,
+ /**
+ * @event headerdblclick
+ * Fires when a header cell is double clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "headerdblclick" : true,
+ /**
+ * @event rowcontextmenu
+ * Fires when a row is right clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Roo.EventObject} e
+ */
+ "rowcontextmenu" : true,
+ /**
+ * @event cellcontextmenu
+ * Fires when a cell is right clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} cellIndex
+ * @param {Roo.EventObject} e
+ */
+ "cellcontextmenu" : true,
+ /**
+ * @event headercontextmenu
+ * Fires when a header is right clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "headercontextmenu" : true,
+ /**
+ * @event bodyscroll
+ * Fires when the body element is scrolled
+ * @param {Number} scrollLeft
+ * @param {Number} scrollTop
+ */
+ "bodyscroll" : true,
+ /**
+ * @event columnresize
+ * Fires when the user resizes a column
+ * @param {Number} columnIndex
+ * @param {Number} newSize
+ */
+ "columnresize" : true,
+ /**
+ * @event columnmove
+ * Fires when the user moves a column
* @param {Number} oldIndex
* @param {Number} newIndex
*/
- "columnmoved" : true,
+ "columnmove" : true,
/**
- * @event columlockchange
- * Fires when a column's locked state is changed
- * @param {ColumnModel} this
- * @param {Number} colIndex
- * @param {Boolean} locked true if locked
+ * @event startdrag
+ * Fires when row(s) start being dragged
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {event} e The raw browser event
*/
- "columnlockchange" : true
+ "startdrag" : true,
+ /**
+ * @event enddrag
+ * Fires when a drag operation is complete
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {event} e The raw browser event
+ */
+ "enddrag" : true,
+ /**
+ * @event dragdrop
+ * Fires when dragged row(s) are dropped on a valid DD target
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragdrop" : true,
+ /**
+ * @event dragover
+ * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragover" : true,
+ /**
+ * @event dragenter
+ * Fires when the dragged row(s) first cross another DD target while being dragged
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragenter" : true,
+ /**
+ * @event dragout
+ * Fires when the dragged row(s) leave another DD target while being dragged
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragout" : true,
+ /**
+ * @event rowclass
+ * Fires when a row is rendered, so you can change add a style to it.
+ * @param {GridView} gridview The grid view
+ * @param {Object} rowcfg contains record rowIndex and rowClass - set rowClass to add a style.
+ */
+ 'rowclass' : true,
+
+ /**
+ * @event render
+ * Fires when the grid is rendered
+ * @param {Grid} grid
+ */
+ 'render' : true
});
- Roo.grid.ColumnModel.superclass.constructor.call(this);
+
+ Roo.grid.Grid.superclass.constructor.call(this);
};
-Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, {
+Roo.extend(Roo.grid.Grid, Roo.util.Observable, {
+
/**
- * @cfg {String} header The header text to display in the Grid view.
+ * @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.data.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} dataIndex (Optional) The name of the field in the grid's {@link Roo.data.Store}'s
- * {@link Roo.data.Record} definition from which to draw the column's value. If not
- * specified, the column's index is used as an index into the Record's data Array.
+ /**
+ * @cfg {String} dragGroup - drag group (?? not sure if needed.)
*/
+
/**
- * @cfg {Number} width (Optional) The initial width in pixels of the column. Using this
- * instead of {@link Roo.grid.Grid#autoSizeColumns} is more efficient.
+ * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Default is 25.
*/
+ minColumnWidth : 25,
+
/**
- * @cfg {Boolean} sortable (Optional) True if sorting is to be allowed on this column.
- * Defaults to the value of the {@link #defaultSortable} property.
- * Whether local/remote sorting is used is specified in {@link Roo.data.Store#remoteSort}.
+ * @cfg {Boolean} autoSizeColumns True to automatically resize the columns to fit their content
+ * <b>on initial render.</b> It is more efficient to explicitly size the columns
+ * through the ColumnModel's {@link Roo.grid.ColumnModel#width} config option. Default is false.
*/
+ autoSizeColumns : false,
+
/**
- * @cfg {Boolean} locked (Optional) True to lock the column in place while scrolling the Grid. Defaults to false.
+ * @cfg {Boolean} autoSizeHeaders True to measure headers with column data when auto sizing columns. Default is true.
*/
+ autoSizeHeaders : true,
+
/**
- * @cfg {Boolean} fixed (Optional) True if the column width cannot be changed. Defaults to false.
+ * @cfg {Boolean} monitorWindowResize True to autoSize the grid when the window resizes. Default is true.
*/
+ monitorWindowResize : true,
+
/**
- * @cfg {Boolean} resizable (Optional) False to disable column resizing. Defaults to true.
+ * @cfg {Boolean} maxRowsToMeasure If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
+ * rows measured to get a columns size. Default is 0 (all rows).
*/
+ maxRowsToMeasure : 0,
+
/**
- * @cfg {Boolean} hidden (Optional) True to hide the column. Defaults to false.
+ * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is true.
*/
+ trackMouseOver : true,
+
/**
- * @cfg {Function} renderer (Optional) A function used to generate HTML markup for a cell
- * given the cell's data value. See {@link #setRenderer}. If not specified, the
- * default renderer returns the escaped data value. If an object is returned (bootstrap only)
- * then it is treated as a Roo Component object instance, and it is rendered after the initial row is rendered
- */
- /**
- * @cfg {Roo.grid.GridEditor} editor (Optional) For grid editors - returns the grid editor
- */
+ * @cfg {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 {String} align (Optional) Set the CSS text-align property of the column. Defaults to undefined.
- */
+ * @cfg {Boolean} enableDragDrop True to enable drag and drop of rows. Default is false.
+ */
+ enableDragDrop : false,
+
/**
- * @cfg {String} valign (Optional) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc). Defaults to undefined.
- */
+ * @cfg {Boolean} enableColumnMove True to enable drag and drop reorder of columns. Default is true.
+ */
+ enableColumnMove : true,
+
/**
- * @cfg {String} cursor (Optional)
- */
+ * @cfg {Boolean} enableColumnHide True to enable hiding of columns with the header context menu. Default is true.
+ */
+ enableColumnHide : true,
+
/**
- * @cfg {String} tooltip (Optional)
- */
+ * @cfg {Boolean} enableRowHeightSync True to manually sync row heights across locked and not locked rows. Default is false.
+ */
+ enableRowHeightSync : false,
+
/**
- * @cfg {Number} xs (Optional)
- */
+ * @cfg {Boolean} stripeRows True to stripe the rows. Default is true.
+ */
+ stripeRows : true,
+
/**
- * @cfg {Number} sm (Optional)
- */
+ * @cfg {Boolean} autoHeight True to fit the height of the grid container to the height of the data. Default is false.
+ */
+ autoHeight : false,
+
/**
- * @cfg {Number} md (Optional)
+ * @cfg {String} autoExpandColumn The id (or dataIndex) of a column in this grid that should expand to fill unused space. This id can not be 0. Default is false.
*/
+ autoExpandColumn : false,
+
/**
- * @cfg {Number} lg (Optional)
- */
+ * @cfg {Number} autoExpandMin The minimum width the autoExpandColumn can have (if enabled).
+ * Default is 50.
+ */
+ autoExpandMin : 50,
+
/**
- * Returns the id of the column at the specified index.
- * @param {Number} index The column index
- * @return {String} the id
- */
- getColumnId : function(index){
- return this.config[index].id;
- },
+ * @cfg {Number} autoExpandMax The maximum width the autoExpandColumn can have (if enabled). Default is 1000.
+ */
+ autoExpandMax : 1000,
/**
- * Returns the column for a specified id.
- * @param {String} id The column id
- * @return {Object} the column
- */
- getColumnById : function(id){
- return this.lookup[id];
- },
+ * @cfg {Object} view The {@link Roo.grid.GridView} used by the grid. This can be set before a call to render().
+ */
+ view : null,
+ /**
+ * @cfg {Object} loadMask An {@link Roo.LoadMask} config or true to mask the grid while loading. Default is false.
+ */
+ loadMask : false,
+ /**
+ * @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,
+
/**
- * Returns the column for a specified dataIndex.
- * @param {String} dataIndex The column dataIndex
- * @return {Object|Boolean} the column or false if not found
- */
- getColumnByDataIndex: function(dataIndex){
- var index = this.findColumnIndex(dataIndex);
- return index > -1 ? this.config[index] : false;
- },
+ * @cfg {Boolean} autoWidth True to set the grid's width to the default total width of the grid's columns instead
+ * of a fixed width. Default is false.
+ */
+ /**
+ * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if autoHeight is not on.
+ */
+
/**
- * Returns the index for a specified column id.
- * @param {String} id The column id
- * @return {Number} the index, or -1 if not found
- */
- getIndexById : function(id){
- for(var i = 0, len = this.config.length; i < len; i++){
- if(this.config[i].id == id){
- return i;
- }
- }
- return -1;
- },
+ * @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}",
+
/**
- * Returns the index for a specified column dataIndex.
- * @param {String} dataIndex The column dataIndex
- * @return {Number} the index, or -1 if not found
+ * Called once after all setup has been completed and the grid is ready to be rendered.
+ * @return {Roo.grid.Grid} this
*/
-
- findColumnIndex : function(dataIndex){
- for(var i = 0, len = this.config.length; i < len; i++){
- if(this.config[i].dataIndex == dataIndex){
- return i;
- }
+ render : function()
+ {
+ var c = this.container;
+ // try to detect autoHeight/width mode
+ if((!c.dom.offsetHeight || c.dom.offsetHeight < 20) || c.getStyle("height") == "auto"){
+ this.autoHeight = true;
+ }
+ var view = this.getView();
+ view.init(this);
+
+ c.on("click", this.onClick, this);
+ c.on("dblclick", this.onDblClick, this);
+ c.on("contextmenu", this.onContextMenu, this);
+ c.on("keydown", this.onKeyDown, this);
+ if (Roo.isTouch) {
+ c.on("touchstart", this.onTouchStart, this);
}
- return -1;
- },
-
-
- moveColumn : function(oldIndex, newIndex){
- var c = this.config[oldIndex];
- this.config.splice(oldIndex, 1);
- this.config.splice(newIndex, 0, c);
- this.dataMap = null;
- this.fireEvent("columnmoved", this, oldIndex, newIndex);
- },
- isLocked : function(colIndex){
- return this.config[colIndex].locked === true;
- },
+ this.relayEvents(c, ["mousedown","mouseup","mouseover","mouseout","keypress"]);
- setLocked : function(colIndex, value, suppressEvent){
- if(this.isLocked(colIndex) == value){
- return;
+ this.getSelectionModel().init(this);
+
+ view.render();
+
+ if(this.loadMask){
+ this.loadMask = new Roo.LoadMask(this.container,
+ Roo.apply({store:this.dataSource}, this.loadMask));
}
- this.config[colIndex].locked = value;
- if(!suppressEvent){
- this.fireEvent("columnlockchange", this, colIndex, value);
+
+
+ if (this.toolbar && this.toolbar.xtype) {
+ this.toolbar.container = this.getView().getHeaderPanel(true);
+ this.toolbar = new Roo.Toolbar(this.toolbar);
}
- },
-
- getTotalLockedWidth : function(){
- var totalWidth = 0;
- for(var i = 0; i < this.config.length; i++){
- if(this.isLocked(i) && !this.isHidden(i)){
- this.totalWidth += this.getColumnWidth(i);
- }
+ if (this.footer && this.footer.xtype) {
+ this.footer.dataSource = this.getDataSource();
+ this.footer.container = this.getView().getFooterPanel(true);
+ this.footer = Roo.factory(this.footer, Roo);
}
- return totalWidth;
- },
-
- getLockedCount : function(){
- for(var i = 0, len = this.config.length; i < len; i++){
- if(!this.isLocked(i)){
- return i;
- }
+ if (this.dropTarget && this.dropTarget.xtype) {
+ delete this.dropTarget.xtype;
+ this.dropTarget = new Roo.dd.DropTarget(this.getView().mainBody, this.dropTarget);
}
- return this.config.length;
+
+ this.rendered = true;
+ this.fireEvent('render', this);
+ return this;
},
/**
- * Returns the number of columns.
- * @return {Number}
+ * 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
*/
- getColumnCount : function(visibleOnly){
- if(visibleOnly === true){
- var c = 0;
- for(var i = 0, len = this.config.length; i < len; i++){
- if(!this.isHidden(i)){
- c++;
- }
- }
- return c;
+ reconfigure : function(dataSource, colModel){
+ if(this.loadMask){
+ this.loadMask.destroy();
+ this.loadMask = new Roo.LoadMask(this.container,
+ Roo.apply({store:dataSource}, this.loadMask));
}
- return this.config.length;
+ this.view.bind(dataSource, colModel);
+ this.dataSource = dataSource;
+ this.colModel = colModel;
+ this.view.refresh(true);
},
-
/**
- * Returns the column configs that return true by the passed function that is called with (columnConfig, index)
- * @param {Function} fn
- * @param {Object} scope (optional)
- * @return {Array} result
+ * 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}
*/
- getColumnsBy : function(fn, scope){
- var r = [];
- for(var i = 0, len = this.config.length; i < len; i++){
- var c = this.config[i];
- if(fn.call(scope||this, c, i) === true){
- r[r.length] = c;
- }
- }
- return r;
- },
-
- /**
- * Returns true if the specified column is sortable.
- * @param {Number} col The column index
- * @return {Boolean}
- */
- isSortable : function(col){
- if(typeof this.config[col].sortable == "undefined"){
- return this.defaultSortable;
- }
- return this.config[col].sortable;
- },
-
- /**
- * Returns the rendering (formatting) function defined for the column.
- * @param {Number} col The column index.
- * @return {Function} The function used to render the cell. See {@link #setRenderer}.
- */
- getRenderer : function(col){
- if(!this.config[col].renderer){
- return Roo.grid.ColumnModel.defaultRenderer;
+ 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;
}
- return this.config[col].renderer;
- },
-
- /**
- * Sets the rendering (formatting) function for a column.
- * @param {Number} col The column index
- * @param {Function} fn The function to use to process the cell's raw data
- * to return HTML markup for the grid view. The render function is called with
- * the following parameters:<ul>
- * <li>Data value.</li>
- * <li>Cell metadata. An object in which you may set the following attributes:<ul>
- * <li>css A CSS style string to apply to the table cell.</li>
- * <li>attr An HTML attribute definition string to apply to the data container element <i>within</i> the table cell.</li></ul>
- * <li>The {@link Roo.data.Record} from which the data was extracted.</li>
- * <li>Row index</li>
- * <li>Column index</li>
- * <li>The {@link Roo.data.Store} object from which the Record was extracted</li></ul>
- */
- setRenderer : function(col, fn){
- this.config[col].renderer = fn;
+
+
+ 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);
+
},
-
- /**
- * Returns the width for the specified column.
- * @param {Number} col The column index
- * @return {Number}
- */
- getColumnWidth : function(col){
- return this.config[col].width * 1 || this.defaultWidth;
+
+
+
+
+ // private
+ onKeyDown : function(e){
+ this.fireEvent("keydown", e);
},
/**
- * Sets the width for a column.
- * @param {Number} col The column index
- * @param {Number} width The new width
+ * Destroy this grid.
+ * @param {Boolean} removeEl True to remove the element
*/
- setColumnWidth : function(col, width, suppressEvent){
- this.config[col].width = width;
- this.totalWidth = null;
- if(!suppressEvent){
- this.fireEvent("widthchange", this, col, width);
+ destroy : function(removeEl, keepListeners){
+ if(this.loadMask){
+ this.loadMask.destroy();
+ }
+ var c = this.container;
+ c.removeAllListeners();
+ this.view.destroy();
+ this.colModel.purgeListeners();
+ if(!keepListeners){
+ this.purgeListeners();
+ }
+ c.update("");
+ if(removeEl === true){
+ c.remove();
}
},
- /**
- * Returns the total width of all columns.
- * @param {Boolean} includeHidden True to include hidden column widths
- * @return {Number}
- */
- getTotalWidth : function(includeHidden){
- if(!this.totalWidth){
- this.totalWidth = 0;
- for(var i = 0, len = this.config.length; i < len; i++){
- if(includeHidden || !this.isHidden(i)){
- this.totalWidth += this.getColumnWidth(i);
+ // private
+ processEvent : function(name, e){
+ // does this fire select???
+ //Roo.log('grid:processEvent ' + name);
+
+ if (name != 'touchstart' ) {
+ this.fireEvent(name, e);
+ }
+
+ var t = e.getTarget();
+ var v = this.view;
+ var header = v.findHeaderIndex(t);
+ if(header !== false){
+ var ename = name == 'touchstart' ? 'click' : name;
+
+ this.fireEvent("header" + ename, this, header, e);
+ }else{
+ var row = v.findRowIndex(t);
+ var cell = v.findCellIndex(t);
+ if (name == 'touchstart') {
+ // first touch is always a click.
+ // hopefull this happens after selection is updated.?
+ name = false;
+
+ if (typeof(this.selModel.getSelectedCell) != 'undefined') {
+ var cs = this.selModel.getSelectedCell();
+ if (row == cs[0] && cell == cs[1]){
+ name = 'dblclick';
+ }
+ }
+ if (typeof(this.selModel.getSelections) != 'undefined') {
+ var cs = this.selModel.getSelections();
+ var ds = this.dataSource;
+ if (cs.length == 1 && ds.getAt(row) == cs[0]){
+ name = 'dblclick';
+ }
+ }
+ if (!name) {
+ return;
+ }
+ }
+
+
+ if(row !== false){
+ this.fireEvent("row" + name, this, row, e);
+ if(cell !== false){
+ this.fireEvent("cell" + name, this, row, cell, e);
}
}
}
- return this.totalWidth;
},
- /**
- * Returns the header for the specified column.
- * @param {Number} col The column index
- * @return {String}
- */
- getColumnHeader : function(col){
- return this.config[col].header;
+ // private
+ onClick : function(e){
+ this.processEvent("click", e);
},
-
- /**
- * Sets the header for a column.
- * @param {Number} col The column index
- * @param {String} header The new header
- */
- setColumnHeader : function(col, header){
- this.config[col].header = header;
- this.fireEvent("headerchange", this, col, header);
+ // private
+ onTouchStart : function(e){
+ this.processEvent("touchstart", e);
},
- /**
- * Returns the tooltip for the specified column.
- * @param {Number} col The column index
- * @return {String}
- */
- getColumnTooltip : function(col){
- return this.config[col].tooltip;
- },
- /**
- * Sets the tooltip for a column.
- * @param {Number} col The column index
- * @param {String} tooltip The new tooltip
- */
- setColumnTooltip : function(col, tooltip){
- this.config[col].tooltip = tooltip;
+ // private
+ onContextMenu : function(e, t){
+ this.processEvent("contextmenu", e);
},
- /**
- * Returns the dataIndex for the specified column.
- * @param {Number} col The column index
- * @return {Number}
- */
- getDataIndex : function(col){
- return this.config[col].dataIndex;
+ // private
+ onDblClick : function(e){
+ this.processEvent("dblclick", e);
},
- /**
- * Sets the dataIndex for a column.
- * @param {Number} col The column index
- * @param {Number} dataIndex The new dataIndex
- */
- setDataIndex : function(col, dataIndex){
- this.config[col].dataIndex = dataIndex;
+ // private
+ walkCells : function(row, col, step, fn, scope){
+ var cm = this.colModel, clen = cm.getColumnCount();
+ var ds = this.dataSource, rlen = ds.getCount(), first = true;
+ if(step < 0){
+ if(col < 0){
+ row--;
+ first = false;
+ }
+ while(row >= 0){
+ if(!first){
+ col = clen-1;
+ }
+ first = false;
+ while(col >= 0){
+ if(fn.call(scope || this, row, col, cm) === true){
+ return [row, col];
+ }
+ col--;
+ }
+ row--;
+ }
+ } else {
+ if(col >= clen){
+ row++;
+ first = false;
+ }
+ while(row < rlen){
+ if(!first){
+ col = 0;
+ }
+ first = false;
+ while(col < clen){
+ if(fn.call(scope || this, row, col, cm) === true){
+ return [row, col];
+ }
+ col++;
+ }
+ row++;
+ }
+ }
+ return null;
},
-
-
- /**
- * Returns true if the cell is editable.
- * @param {Number} colIndex The column index
- * @param {Number} rowIndex The row index - this is nto actually used..?
- * @return {Boolean}
- */
- isCellEditable : function(colIndex, rowIndex){
- return (this.config[colIndex].editable || (typeof this.config[colIndex].editable == "undefined" && this.config[colIndex].editor)) ? true : false;
+ // private
+ getSelections : function(){
+ return this.selModel.getSelections();
},
/**
- * Returns the editor defined for the cell/column.
- * return false or null to disable editing.
- * @param {Number} colIndex The column index
- * @param {Number} rowIndex The row index
- * @return {Object}
+ * Causes the grid to manually recalculate its dimensions. Generally this is done automatically,
+ * but if manual update is required this method will initiate it.
*/
- getCellEditor : function(colIndex, rowIndex){
- return this.config[colIndex].editor;
+ autoSize : function(){
+ if(this.rendered){
+ this.view.layout();
+ if(this.view.adjustForScroll){
+ this.view.adjustForScroll();
+ }
+ }
},
/**
- * Sets if a column is editable.
- * @param {Number} col The column index
- * @param {Boolean} editable True if the column is editable
+ * Returns the grid's underlying element.
+ * @return {Element} The element
*/
- setEditable : function(col, editable){
- this.config[col].editable = editable;
+ getGridEl : function(){
+ return this.container;
},
+ // private for compatibility, overridden by editor grid
+ stopEditing : function(){},
/**
- * Returns true if the column is hidden.
- * @param {Number} colIndex The column index
- * @return {Boolean}
+ * Returns the grid's SelectionModel.
+ * @return {SelectionModel}
*/
- isHidden : function(colIndex){
- return this.config[colIndex].hidden;
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new Roo.grid.RowSelectionModel();
+ }
+ return this.selModel;
},
-
/**
- * Returns true if the column width cannot be changed
+ * Returns the grid's DataSource.
+ * @return {DataSource}
*/
- isFixed : function(colIndex){
- return this.config[colIndex].fixed;
+ getDataSource : function(){
+ return this.dataSource;
},
/**
- * Returns true if the column can be resized
- * @return {Boolean}
+ * Returns the grid's ColumnModel.
+ * @return {ColumnModel}
*/
- isResizable : function(colIndex){
- return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
+ getColumnModel : function(){
+ return this.colModel;
},
+
/**
- * Sets if a column is hidden.
- * @param {Number} colIndex The column index
- * @param {Boolean} hidden True if the column is hidden
+ * Returns the grid's GridView object.
+ * @return {GridView}
*/
- setHidden : function(colIndex, hidden){
- this.config[colIndex].hidden = hidden;
- this.totalWidth = null;
- this.fireEvent("hiddenchange", this, colIndex, hidden);
+ 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;
},
-
/**
- * Sets the editor for a column.
- * @param {Number} col The column index
- * @param {Object} editor The editor object
+ * 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}
*/
- setEditor : function(col, editor){
- this.config[col].editor = editor;
+ getDragDropText : function(){
+ var count = this.selModel.getCount();
+ return String.format(this.ddText, count, count == 1 ? '' : 's');
}
});
-
-Roo.grid.ColumnModel.defaultRenderer = function(value)
-{
- if(typeof value == "object") {
- return value;
- }
- if(typeof value == "string" && value.length < 1){
- return " ";
- }
-
- return String.format("{0}", value);
-};
-
-// Alias for backwards compatibility
-Roo.grid.DefaultColumnModel = Roo.grid.ColumnModel;
/*
* Based on:
* Ext JS Library 1.1.1
* Fork - LGPL
* <script type="text/javascript">
*/
-
-/**
- * @class Roo.grid.AbstractSelectionModel
+ /**
+ * @class Roo.grid.AbstractGridView
* @extends Roo.util.Observable
- * Abstract base class for grid SelectionModels. It provides the interface that should be
- * implemented by descendant classes. This class should not be directly instantiated.
+ * @abstract
+ * Abstract base class for grid Views
* @constructor
*/
-Roo.grid.AbstractSelectionModel = function(){
- this.locked = false;
- Roo.grid.AbstractSelectionModel.superclass.constructor.call(this);
+Roo.grid.AbstractGridView = function(){
+ this.grid = null;
+
+ this.events = {
+ "beforerowremoved" : true,
+ "beforerowsinserted" : true,
+ "beforerefresh" : true,
+ "rowremoved" : true,
+ "rowsinserted" : true,
+ "rowupdated" : true,
+ "refresh" : true
+ };
+ Roo.grid.AbstractGridView.superclass.constructor.call(this);
};
-Roo.extend(Roo.grid.AbstractSelectionModel, Roo.util.Observable, {
- /** @ignore Called by the grid automatically. Do not call directly. */
- init : function(grid){
+Roo.extend(Roo.grid.AbstractGridView, Roo.util.Observable, {
+ rowClass : "x-grid-row",
+ cellClass : "x-grid-cell",
+ tdClass : "x-grid-td",
+ hdClass : "x-grid-hd",
+ splitClass : "x-grid-hd-split",
+
+ init: function(grid){
this.grid = grid;
- this.initEvents();
+ var cid = this.grid.getGridEl().id;
+ this.colSelector = "#" + cid + " ." + this.cellClass + "-";
+ this.tdSelector = "#" + cid + " ." + this.tdClass + "-";
+ this.hdSelector = "#" + cid + " ." + this.hdClass + "-";
+ this.splitSelector = "#" + cid + " ." + this.splitClass + "-";
+ },
+
+ getColumnRenderers : function(){
+ var renderers = [];
+ var cm = this.grid.colModel;
+ var colCount = cm.getColumnCount();
+ for(var i = 0; i < colCount; i++){
+ renderers[i] = cm.getRenderer(i);
+ }
+ return renderers;
},
-
- /**
- * Locks the selections.
- */
- lock : function(){
- this.locked = true;
+
+ getColumnIds : function(){
+ var ids = [];
+ var cm = this.grid.colModel;
+ var colCount = cm.getColumnCount();
+ for(var i = 0; i < colCount; i++){
+ ids[i] = cm.getColumnId(i);
+ }
+ return ids;
},
-
- /**
- * Unlocks the selections.
- */
- unlock : function(){
- this.locked = false;
+
+ getDataIndexes : function(){
+ if(!this.indexMap){
+ this.indexMap = this.buildIndexMap();
+ }
+ return this.indexMap.colToData;
},
-
+
+ getColumnIndexByDataIndex : function(dataIndex){
+ if(!this.indexMap){
+ this.indexMap = this.buildIndexMap();
+ }
+ return this.indexMap.dataToCol[dataIndex];
+ },
+
/**
- * Returns true if the selections are locked.
- * @return {Boolean}
+ * Set a css style for a column dynamically.
+ * @param {Number} colIndex The index of the column
+ * @param {String} name The css property name
+ * @param {String} value The css value
*/
- isLocked : function(){
- return this.locked;
+ setCSSStyle : function(colIndex, name, value){
+ var selector = "#" + this.grid.id + " .x-grid-col-" + colIndex;
+ Roo.util.CSS.updateRule(selector, name, value);
+ },
+
+ generateRules : function(cm){
+ var ruleBuf = [], rulesId = this.grid.id + '-cssrules';
+ Roo.util.CSS.removeStyleSheet(rulesId);
+ for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+ var cid = cm.getColumnId(i);
+ ruleBuf.push(this.colSelector, cid, " {\n", cm.config[i].css, "}\n",
+ this.tdSelector, cid, " {\n}\n",
+ this.hdSelector, cid, " {\n}\n",
+ this.splitSelector, cid, " {\n}\n");
+ }
+ return Roo.util.CSS.createStyleSheet(ruleBuf.join(""), rulesId);
}
});/*
* Based on:
* Fork - LGPL
* <script type="text/javascript">
*/
-/**
- * @extends Roo.grid.AbstractSelectionModel
- * @class Roo.grid.RowSelectionModel
- * The default SelectionModel used by {@link Roo.grid.Grid}.
- * It supports multiple selections and keyboard selection/navigation.
- * @constructor
- * @param {Object} config
- */
-Roo.grid.RowSelectionModel = function(config){
- Roo.apply(this, config);
- this.selections = new Roo.util.MixedCollection(false, function(o){
- return o.id;
- });
-
- this.last = false;
- this.lastActive = false;
- 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
- */
- "rowdeselect" : true
- });
- Roo.grid.RowSelectionModel.superclass.constructor.call(this);
- this.locked = false;
+// private
+// This is a support class used internally by the Grid components
+Roo.grid.HeaderDragZone = function(grid, hd, hd2){
+ this.grid = grid;
+ this.view = grid.getView();
+ this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
+ Roo.grid.HeaderDragZone.superclass.constructor.call(this, hd);
+ if(hd2){
+ this.setHandleElId(Roo.id(hd));
+ this.setOuterHandleElId(Roo.id(hd2));
+ }
+ this.scroll = false;
};
-
-Roo.extend(Roo.grid.RowSelectionModel, Roo.grid.AbstractSelectionModel, {
- /**
- * @cfg {Boolean} singleSelect
- * True to allow selection of only one row at a time (defaults to false)
- */
- singleSelect : false,
-
- // private
- initEvents : function(){
-
- if(!this.grid.enableDragDrop && !this.grid.enableDrag){
- this.grid.on("mousedown", this.handleMouseDown, this);
- }else{ // allow click to work like normal
- this.grid.on("rowclick", this.handleDragableRowClick, this);
+Roo.extend(Roo.grid.HeaderDragZone, Roo.dd.DragZone, {
+ maxDragWidth: 120,
+ getDragData : function(e){
+ var t = Roo.lib.Event.getTarget(e);
+ var h = this.view.findHeaderCell(t);
+ if(h){
+ return {ddel: h.firstChild, header:h};
}
-
- this.rowNav = new Roo.KeyNav(this.grid.getGridEl(), {
- "up" : function(e){
- if(!e.shiftKey){
- this.selectPrevious(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);
- if(last !== false){
- this.last = last;
- }
- }else{
- this.selectFirstRow();
- }
- this.fireEvent("afterselectionchange", this);
- },
- "down" : function(e){
- if(!e.shiftKey){
- this.selectNext(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);
- if(last !== false){
- this.last = last;
- }
- }else{
- this.selectFirstRow();
- }
- this.fireEvent("afterselectionchange", this);
- },
- 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);
+ return false;
},
- // private
- onRefresh : function(){
- var ds = this.grid.dataSource, i, v = this.grid.view;
- var s = this.selections;
- s.each(function(r){
- if((i = ds.indexOfId(r.id)) != -1){
- v.onRowSelect(i);
- s.add(ds.getAt(i)); // updating the selection relate data
- }else{
- s.remove(r);
- }
- });
+ onInitDrag : function(e){
+ this.view.headersDisabled = true;
+ var clone = this.dragData.ddel.cloneNode(true);
+ clone.id = Roo.id();
+ clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
+ this.proxy.update(clone);
+ return true;
},
- // private
- onRemove : function(v, index, r){
- this.selections.remove(r);
+ afterValidDrop : function(){
+ var v = this.view;
+ setTimeout(function(){
+ v.headersDisabled = false;
+ }, 50);
},
- // private
- onRowUpdated : function(v, index, r){
- if(this.isSelected(r)){
- v.onRowSelect(index);
- }
- },
+ afterInvalidDrop : function(){
+ var v = this.view;
+ setTimeout(function(){
+ v.headersDisabled = false;
+ }, 50);
+ }
+});
+/*
+ * 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">
+ */
+// private
+// This is a support class used internally by the Grid components
+Roo.grid.HeaderDropZone = function(grid, hd, hd2){
+ this.grid = grid;
+ this.view = grid.getView();
+ // split the proxies so they don't interfere with mouse events
+ this.proxyTop = Roo.DomHelper.append(document.body, {
+ cls:"col-move-top", html:" "
+ }, true);
+ this.proxyBottom = Roo.DomHelper.append(document.body, {
+ cls:"col-move-bottom", html:" "
+ }, true);
+ this.proxyTop.hide = this.proxyBottom.hide = function(){
+ this.setLeftTop(-100,-100);
+ this.setStyle("visibility", "hidden");
+ };
+ this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
+ // temporarily disabled
+ //Roo.dd.ScrollManager.register(this.view.scroller.dom);
+ Roo.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
+};
+Roo.extend(Roo.grid.HeaderDropZone, Roo.dd.DropZone, {
+ proxyOffsets : [-4, -9],
+ fly: Roo.Element.fly,
- /**
- * Select records.
- * @param {Array} records The records to select
- * @param {Boolean} keepExisting (optional) True to keep existing selections
- */
- selectRecords : function(records, keepExisting){
- if(!keepExisting){
- this.clearSelections();
- }
- var ds = this.grid.dataSource;
- for(var i = 0, len = records.length; i < len; i++){
- this.selectRow(ds.indexOf(records[i]), true);
+ getTargetFromEvent : function(e){
+ var t = Roo.lib.Event.getTarget(e);
+ var cindex = this.view.findCellIndex(t);
+ if(cindex !== false){
+ return this.view.getHeaderCell(cindex);
}
+ return null;
},
- /**
- * Gets the number of selected rows.
- * @return {Number}
- */
- getCount : function(){
- return this.selections.length;
- },
-
- /**
- * Selects the first row in the grid.
- */
- selectFirstRow : function(){
- this.selectRow(0);
- },
-
- /**
- * Select the last row.
- * @param {Boolean} keepExisting (optional) True to keep existing selections
- */
- selectLastRow : function(keepExisting){
- this.selectRow(this.grid.dataSource.getCount() - 1, keepExisting);
- },
-
- /**
- * Selects the row immediately following the last selected row.
- * @param {Boolean} keepExisting (optional) True to keep existing selections
- */
- selectNext : function(keepExisting){
- if(this.last !== false && (this.last+1) < this.grid.dataSource.getCount()){
- this.selectRow(this.last+1, keepExisting);
- this.grid.getView().focusRow(this.last);
+ nextVisible : function(h){
+ var v = this.view, cm = this.grid.colModel;
+ h = h.nextSibling;
+ while(h){
+ if(!cm.isHidden(v.getCellIndex(h))){
+ return h;
+ }
+ h = h.nextSibling;
}
+ return null;
},
- /**
- * Selects the row that precedes the last selected row.
- * @param {Boolean} keepExisting (optional) True to keep existing selections
- */
- selectPrevious : function(keepExisting){
- if(this.last){
- this.selectRow(this.last-1, keepExisting);
- this.grid.getView().focusRow(this.last);
+ prevVisible : function(h){
+ var v = this.view, cm = this.grid.colModel;
+ h = h.prevSibling;
+ while(h){
+ if(!cm.isHidden(v.getCellIndex(h))){
+ return h;
+ }
+ h = h.prevSibling;
}
+ return null;
},
- /**
- * Returns the selected records
- * @return {Array} Array of selected records
- */
- getSelections : function(){
- return [].concat(this.selections.items);
- },
-
- /**
- * Returns the first selected record.
- * @return {Record}
- */
- getSelected : function(){
- return this.selections.itemAt(0);
- },
-
-
- /**
- * Clears all selections.
- */
- clearSelections : function(fast){
- if(this.locked) {
- return;
- }
- if(fast !== true){
- var ds = this.grid.dataSource;
- var s = this.selections;
- s.each(function(r){
- this.deselectRow(ds.indexOfId(r.id));
- }, this);
- s.clear();
+ positionIndicator : function(h, n, e){
+ var x = Roo.lib.Event.getPageX(e);
+ var r = Roo.lib.Dom.getRegion(n.firstChild);
+ var px, pt, py = r.top + this.proxyOffsets[1];
+ if((r.right - x) <= (r.right-r.left)/2){
+ px = r.right+this.view.borderWidth;
+ pt = "after";
}else{
- this.selections.clear();
+ px = r.left;
+ pt = "before";
}
- this.last = false;
- },
-
+ var oldIndex = this.view.getCellIndex(h);
+ var newIndex = this.view.getCellIndex(n);
- /**
- * Selects all rows.
- */
- selectAll : function(){
- if(this.locked) {
- return;
- }
- this.selections.clear();
- for(var i = 0, len = this.grid.dataSource.getCount(); i < len; i++){
- this.selectRow(i, true);
+ if(this.grid.colModel.isFixed(newIndex)){
+ return false;
}
- },
-
- /**
- * Returns True if there is a selection.
- * @return {Boolean}
- */
- hasSelection : function(){
- return this.selections.length > 0;
- },
-
- /**
- * Returns True if the specified row is selected.
- * @param {Number/Record} record The record or index of the record to check
- * @return {Boolean}
- */
- isSelected : function(index){
- var r = typeof index == "number" ? this.grid.dataSource.getAt(index) : index;
- return (r && this.selections.key(r.id) ? true : false);
- },
-
- /**
- * Returns True if the specified record id is selected.
- * @param {String} id The id of record to check
- * @return {Boolean}
- */
- isIdSelected : function(id){
- return (this.selections.key(id) ? true : false);
- },
- // private
- handleMouseDown : function(e, t){
- var view = this.grid.getView(), rowIndex;
- if(this.isLocked() || (rowIndex = view.findRowIndex(t)) === false){
- return;
- };
- if(e.shiftKey && this.last !== false){
- var last = this.last;
- this.selectRange(last, rowIndex, e.ctrlKey);
- this.last = last; // reset the last
- view.focusRow(rowIndex);
- }else{
- var isSelected = this.isSelected(rowIndex);
- if(e.button !== 0 && isSelected){
- view.focusRow(rowIndex);
- }else if(e.ctrlKey && isSelected){
- this.deselectRow(rowIndex);
- }else if(!isSelected){
- this.selectRow(rowIndex, e.button === 0 && (e.ctrlKey || e.shiftKey));
- view.focusRow(rowIndex);
- }
- }
- this.fireEvent("afterselectionchange", this);
- },
- // private
- handleDragableRowClick : function(grid, rowIndex, e)
- {
- if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
- this.selectRow(rowIndex, false);
- grid.view.focusRow(rowIndex);
- this.fireEvent("afterselectionchange", this);
- }
- },
-
- /**
- * Selects multiple rows.
- * @param {Array} rows Array of the indexes of the row to select
- * @param {Boolean} keepExisting (optional) True to keep existing selections
- */
- selectRows : function(rows, keepExisting){
- if(!keepExisting){
- this.clearSelections();
- }
- for(var i = 0, len = rows.length; i < len; i++){
- this.selectRow(rows[i], true);
- }
- },
+ var locked = this.grid.colModel.isLocked(newIndex);
- /**
- * Selects a range of rows. All rows in between startRow and endRow are also selected.
- * @param {Number} startRow The index of the first row in the range
- * @param {Number} endRow The index of the last row in the range
- * @param {Boolean} keepExisting (optional) True to retain existing selections
- */
- selectRange : function(startRow, endRow, keepExisting){
- if(this.locked) {
- return;
- }
- if(!keepExisting){
- this.clearSelections();
+ if(pt == "after"){
+ newIndex++;
}
- if(startRow <= endRow){
- for(var i = startRow; i <= endRow; i++){
- this.selectRow(i, true);
- }
- }else{
- for(var i = startRow; i >= endRow; i--){
- this.selectRow(i, true);
- }
+ if(oldIndex < newIndex){
+ newIndex--;
}
- },
-
- /**
- * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
- * @param {Number} startRow The index of the first row in the range
- * @param {Number} endRow The index of the last row in the range
- */
- deselectRange : function(startRow, endRow, preventViewNotify){
- if(this.locked) {
- return;
+ if(oldIndex == newIndex && (locked == this.grid.colModel.isLocked(oldIndex))){
+ return false;
}
- for(var i = startRow; i <= endRow; i++){
- this.deselectRow(i, preventViewNotify);
+ px += this.proxyOffsets[0];
+ this.proxyTop.setLeftTop(px, py);
+ this.proxyTop.show();
+ if(!this.bottomOffset){
+ this.bottomOffset = this.view.mainHd.getHeight();
}
+ this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
+ this.proxyBottom.show();
+ return pt;
},
- /**
- * Selects a row.
- * @param {Number} row The index of the row to select
- * @param {Boolean} keepExisting (optional) True to keep existing selections
- */
- selectRow : function(index, keepExisting, preventViewNotify){
- if(this.locked || (index < 0 || index >= this.grid.dataSource.getCount())) {
- return;
- }
- if(this.fireEvent("beforerowselect", this, index, keepExisting) !== false){
- if(!keepExisting || this.singleSelect){
- this.clearSelections();
- }
- var r = this.grid.dataSource.getAt(index);
- this.selections.add(r);
- this.last = this.lastActive = index;
- if(!preventViewNotify){
- this.grid.getView().onRowSelect(index);
- }
- this.fireEvent("rowselect", this, index, r);
- this.fireEvent("selectionchange", this);
+ onNodeEnter : function(n, dd, e, data){
+ if(data.header != n){
+ this.positionIndicator(data.header, n, e);
}
},
- /**
- * Deselects a row.
- * @param {Number} row The index of the row to deselect
- */
- deselectRow : function(index, preventViewNotify){
- if(this.locked) {
- return;
- }
- if(this.last == index){
- this.last = false;
- }
- if(this.lastActive == index){
- this.lastActive = false;
- }
- var r = this.grid.dataSource.getAt(index);
- this.selections.remove(r);
- if(!preventViewNotify){
- this.grid.getView().onRowDeselect(index);
+ onNodeOver : function(n, dd, e, data){
+ var result = false;
+ if(data.header != n){
+ result = this.positionIndicator(data.header, n, e);
}
- this.fireEvent("rowdeselect", this, index);
- this.fireEvent("selectionchange", this);
- },
-
- // private
- restoreLast : function(){
- if(this._last){
- this.last = this._last;
+ if(!result){
+ this.proxyTop.hide();
+ this.proxyBottom.hide();
}
+ return result ? this.dropAllowed : this.dropNotAllowed;
},
- // private
- acceptsNav : function(row, col, cm){
- return !cm.isHidden(col) && cm.isCellEditable(col, row);
+ onNodeOut : function(n, dd, e, data){
+ this.proxyTop.hide();
+ this.proxyBottom.hide();
},
- // private
- onEditorKey : function(field, e){
- var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
- if(k == e.TAB){
- e.stopEvent();
- ed.completeEdit();
- if(e.shiftKey){
- newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
- }else{
- newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
+ onNodeDrop : function(n, dd, e, data){
+ var h = data.header;
+ if(h != n){
+ var cm = this.grid.colModel;
+ var x = Roo.lib.Event.getPageX(e);
+ var r = Roo.lib.Dom.getRegion(n.firstChild);
+ var pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before";
+ var oldIndex = this.view.getCellIndex(h);
+ var newIndex = this.view.getCellIndex(n);
+ var locked = cm.isLocked(newIndex);
+ if(pt == "after"){
+ newIndex++;
}
- }else if(k == e.ENTER && !e.ctrlKey){
- e.stopEvent();
- ed.completeEdit();
- if(e.shiftKey){
- newCell = g.walkCells(ed.row-1, ed.col, -1, this.acceptsNav, this);
- }else{
- newCell = g.walkCells(ed.row+1, ed.col, 1, this.acceptsNav, this);
+ if(oldIndex < newIndex){
+ newIndex--;
}
- }else if(k == e.ESC){
- ed.cancelEdit();
- }
- if(newCell){
- g.startEditing(newCell[0], newCell[1]);
+ if(oldIndex == newIndex && (locked == cm.isLocked(oldIndex))){
+ return false;
+ }
+ cm.setLocked(oldIndex, locked, true);
+ cm.moveColumn(oldIndex, newIndex);
+ this.grid.fireEvent("columnmove", oldIndex, newIndex);
+ return true;
}
+ return false;
}
-});/*
+});
+/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* Fork - LGPL
* <script type="text/javascript">
*/
+
/**
- * @class Roo.grid.CellSelectionModel
- * @extends Roo.grid.AbstractSelectionModel
- * This class provides the basic implementation for cell selection in a grid.
+ * @class Roo.grid.GridView
+ * @extends Roo.util.Observable
+ *
* @constructor
- * @param {Object} config The object containing the configuration of this model.
- * @cfg {Boolean} enter_is_tab Enter behaves the same as tab. (eg. goes to next cell) default: false
+ * @param {Object} config
*/
-Roo.grid.CellSelectionModel = function(config){
- Roo.apply(this, config);
-
- this.selection = null;
+Roo.grid.GridView = function(config){
+ Roo.grid.GridView.superclass.constructor.call(this);
+ this.el = null;
- this.addEvents({
- /**
- * @event beforerowselect
- * Fires before a cell is selected.
- * @param {SelectionModel} this
- * @param {Number} rowIndex The selected row index
- * @param {Number} colIndex The selected cell index
- */
- "beforecellselect" : true,
- /**
- * @event cellselect
- * Fires when a cell is selected.
- * @param {SelectionModel} this
- * @param {Number} rowIndex The selected row index
- * @param {Number} colIndex The selected cell index
- */
- "cellselect" : true,
- /**
- * @event selectionchange
- * Fires when the active selection changes.
- * @param {SelectionModel} this
- * @param {Object} selection null for no selection or an object (o) with two properties
- <ul>
- <li>o.record: the record object for the row the selection is in</li>
- <li>o.cell: An array of [rowIndex, columnIndex]</li>
- </ul>
- */
- "selectionchange" : true,
- /**
- * @event tabend
- * Fires when the tab (or enter) was pressed on the last editable cell
- * You can use this to trigger add new row.
- * @param {SelectionModel} this
- */
- "tabend" : true,
- /**
- * @event beforeeditnext
- * Fires before the next editable sell is made active
- * You can use this to skip to another cell or fire the tabend
- * if you set cell to false
- * @param {Object} eventdata object : { cell : [ row, col ] }
- */
- "beforeeditnext" : true
- });
- Roo.grid.CellSelectionModel.superclass.constructor.call(this);
+ Roo.apply(this, config);
};
-Roo.extend(Roo.grid.CellSelectionModel, Roo.grid.AbstractSelectionModel, {
+Roo.extend(Roo.grid.GridView, Roo.grid.AbstractGridView, {
+
+ unselectable : 'unselectable="on"',
+ unselectableCls : 'x-unselectable',
- enter_is_tab: false,
+
+ rowClass : "x-grid-row",
- /** @ignore */
- initEvents : function(){
- this.grid.on("mousedown", this.handleMouseDown, this);
- this.grid.getGridEl().on(Roo.isIE ? "keydown" : "keypress", this.handleKeyDown, this);
- var view = this.grid.view;
- view.on("refresh", this.onViewChange, this);
- view.on("rowupdated", this.onRowUpdated, this);
- view.on("beforerowremoved", this.clearSelections, this);
- view.on("beforerowsinserted", this.clearSelections, this);
- if(this.grid.isEditor){
- this.grid.on("beforeedit", this.beforeEdit, this);
+ cellClass : "x-grid-col",
+
+ tdClass : "x-grid-td",
+
+ hdClass : "x-grid-hd",
+
+ splitClass : "x-grid-split",
+
+ sortClasses : ["sort-asc", "sort-desc"],
+
+ enableMoveAnim : false,
+
+ hlColor: "C3DAF9",
+
+ dh : Roo.DomHelper,
+
+ fly : Roo.Element.fly,
+
+ css : Roo.util.CSS,
+
+ borderWidth: 1,
+
+ splitOffset: 3,
+
+ scrollIncrement : 22,
+
+ cellRE: /(?:.*?)x-grid-(?:hd|cell|csplit)-(?:[\d]+)-([\d]+)(?:.*?)/,
+
+ findRE: /\s?(?:x-grid-hd|x-grid-col|x-grid-csplit)\s/,
+
+ bind : function(ds, cm){
+ if(this.ds){
+ this.ds.un("load", this.onLoad, this);
+ this.ds.un("datachanged", this.onDataChange, this);
+ this.ds.un("add", this.onAdd, this);
+ this.ds.un("remove", this.onRemove, this);
+ this.ds.un("update", this.onUpdate, this);
+ this.ds.un("clear", this.onClear, this);
}
- },
+ if(ds){
+ ds.on("load", this.onLoad, this);
+ ds.on("datachanged", this.onDataChange, this);
+ ds.on("add", this.onAdd, this);
+ ds.on("remove", this.onRemove, this);
+ ds.on("update", this.onUpdate, this);
+ ds.on("clear", this.onClear, this);
+ }
+ this.ds = ds;
- //private
- beforeEdit : function(e){
- this.select(e.row, e.column, false, true, e.record);
+ if(this.cm){
+ this.cm.un("widthchange", this.onColWidthChange, this);
+ this.cm.un("headerchange", this.onHeaderChange, this);
+ this.cm.un("hiddenchange", this.onHiddenChange, this);
+ this.cm.un("columnmoved", this.onColumnMove, this);
+ this.cm.un("columnlockchange", this.onColumnLock, this);
+ }
+ if(cm){
+ this.generateRules(cm);
+ cm.on("widthchange", this.onColWidthChange, this);
+ cm.on("headerchange", this.onHeaderChange, this);
+ cm.on("hiddenchange", this.onHiddenChange, this);
+ cm.on("columnmoved", this.onColumnMove, this);
+ cm.on("columnlockchange", this.onColumnLock, this);
+ }
+ this.cm = cm;
},
- //private
- onRowUpdated : function(v, index, r){
- if(this.selection && this.selection.record == r){
- v.onCellSelect(index, this.selection.cell[1]);
+ init: function(grid){
+ Roo.grid.GridView.superclass.init.call(this, grid);
+
+ this.bind(grid.dataSource, grid.colModel);
+
+ grid.on("headerclick", this.handleHeaderClick, this);
+
+ if(grid.trackMouseOver){
+ grid.on("mouseover", this.onRowOver, this);
+ grid.on("mouseout", this.onRowOut, this);
}
- },
+ grid.cancelTextSelection = function(){};
+ this.gridId = grid.id;
- //private
- onViewChange : function(){
- this.clearSelections(true);
+ var tpls = this.templates || {};
+
+ if(!tpls.master){
+ tpls.master = new Roo.Template(
+ '<div class="x-grid" hidefocus="true">',
+ '<a href="#" class="x-grid-focus" tabIndex="-1"></a>',
+ '<div class="x-grid-topbar"></div>',
+ '<div class="x-grid-scroller"><div></div></div>',
+ '<div class="x-grid-locked">',
+ '<div class="x-grid-header">{lockedHeader}</div>',
+ '<div class="x-grid-body">{lockedBody}</div>',
+ "</div>",
+ '<div class="x-grid-viewport">',
+ '<div class="x-grid-header">{header}</div>',
+ '<div class="x-grid-body">{body}</div>',
+ "</div>",
+ '<div class="x-grid-bottombar"></div>',
+
+ '<div class="x-grid-resize-proxy"> </div>',
+ "</div>"
+ );
+ tpls.master.disableformats = true;
+ }
+
+ if(!tpls.header){
+ tpls.header = new Roo.Template(
+ '<table border="0" cellspacing="0" cellpadding="0">',
+ '<tbody><tr class="x-grid-hd-row">{cells}</tr></tbody>',
+ "</table>{splits}"
+ );
+ tpls.header.disableformats = true;
+ }
+ tpls.header.compile();
+
+ if(!tpls.hcell){
+ tpls.hcell = new Roo.Template(
+ '<td class="x-grid-hd x-grid-td-{id} {cellId}"><div title="{title}" class="x-grid-hd-inner x-grid-hd-{id}">',
+ '<div class="x-grid-hd-text ' + this.unselectableCls + '" ' + this.unselectable +'>{value}<img class="x-grid-sort-icon" src="', Roo.BLANK_IMAGE_URL, '" /></div>',
+ "</div></td>"
+ );
+ tpls.hcell.disableFormats = true;
+ }
+ tpls.hcell.compile();
+
+ if(!tpls.hsplit){
+ tpls.hsplit = new Roo.Template('<div class="x-grid-split {splitId} x-grid-split-{id}" style="{style} ' +
+ this.unselectableCls + '" ' + this.unselectable +'> </div>');
+ tpls.hsplit.disableFormats = true;
+ }
+ tpls.hsplit.compile();
+
+ if(!tpls.body){
+ tpls.body = new Roo.Template(
+ '<table border="0" cellspacing="0" cellpadding="0">',
+ "<tbody>{rows}</tbody>",
+ "</table>"
+ );
+ tpls.body.disableFormats = true;
+ }
+ tpls.body.compile();
+
+ if(!tpls.row){
+ tpls.row = new Roo.Template('<tr class="x-grid-row {alt}">{cells}</tr>');
+ tpls.row.disableFormats = true;
+ }
+ tpls.row.compile();
+
+ if(!tpls.cell){
+ tpls.cell = new Roo.Template(
+ '<td class="x-grid-col x-grid-td-{id} {cellId} {css}" tabIndex="0">',
+ '<div class="x-grid-col-{id} x-grid-cell-inner"><div class="x-grid-cell-text ' +
+ this.unselectableCls + '" ' + this.unselectable +'" {attr}>{value}</div></div>',
+ "</td>"
+ );
+ tpls.cell.disableFormats = true;
+ }
+ tpls.cell.compile();
+
+ this.templates = tpls;
},
- /**
- * Returns the currently selected cell,.
- * @return {Array} The selected cell (row, column) or null if none selected.
- */
- getSelectedCell : function(){
- return this.selection ? this.selection.cell : null;
+ // remap these for backwards compat
+ onColWidthChange : function(){
+ this.updateColumns.apply(this, arguments);
+ },
+ onHeaderChange : function(){
+ this.updateHeaders.apply(this, arguments);
+ },
+ onHiddenChange : function(){
+ this.handleHiddenChange.apply(this, arguments);
+ },
+ onColumnMove : function(){
+ this.handleColumnMove.apply(this, arguments);
+ },
+ onColumnLock : function(){
+ this.handleLockChange.apply(this, arguments);
},
- /**
- * Clears all selections.
- * @param {Boolean} true to prevent the gridview from being notified about the change.
- */
- clearSelections : function(preventNotify){
- var s = this.selection;
- if(s){
- if(preventNotify !== true){
- this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
- }
- this.selection = null;
- this.fireEvent("selectionchange", this, null);
- }
+ onDataChange : function(){
+ this.refresh();
+ this.updateHeaderSortState();
},
- /**
- * Returns true if there is a selection.
- * @return {Boolean}
- */
- hasSelection : function(){
- return this.selection ? true : false;
+ onClear : function(){
+ this.refresh();
},
- /** @ignore */
- handleMouseDown : function(e, t){
- var v = this.grid.getView();
- if(this.isLocked()){
- return;
- };
- var row = v.findRowIndex(t);
- var cell = v.findCellIndex(t);
- if(row !== false && cell !== false){
- this.select(row, cell);
- }
+ onUpdate : function(ds, record){
+ this.refreshRow(record);
},
- /**
- * Selects a cell.
- * @param {Number} rowIndex
- * @param {Number} collIndex
- */
- select : function(rowIndex, colIndex, preventViewNotify, preventFocus, /*internal*/ r){
- if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){
- this.clearSelections();
- r = r || this.grid.dataSource.getAt(rowIndex);
- this.selection = {
- record : r,
- cell : [rowIndex, colIndex]
- };
- if(!preventViewNotify){
- var v = this.grid.getView();
- v.onCellSelect(rowIndex, colIndex);
- if(preventFocus !== true){
- v.focusCell(rowIndex, colIndex);
- }
- }
- this.fireEvent("cellselect", this, rowIndex, colIndex);
- this.fireEvent("selectionchange", this, this.selection);
+ refreshRow : function(record){
+ var ds = this.ds, index;
+ if(typeof record == 'number'){
+ index = record;
+ record = ds.getAt(index);
+ }else{
+ index = ds.indexOf(record);
}
+ this.insertRows(ds, index, index, true);
+ this.onRemove(ds, record, index+1, true);
+ this.syncRowHeights(index, index);
+ this.layout();
+ this.fireEvent("rowupdated", this, index, record);
},
- //private
- isSelectable : function(rowIndex, colIndex, cm){
- return !cm.isHidden(colIndex);
+ onAdd : function(ds, records, index){
+ this.insertRows(ds, index, index + (records.length-1));
},
- /** @ignore */
- handleKeyDown : function(e){
- //Roo.log('Cell Sel Model handleKeyDown');
- if(!e.isNavKeyPress()){
- return;
+ onRemove : function(ds, record, index, isUpdate){
+ if(isUpdate !== true){
+ this.fireEvent("beforerowremoved", this, index, record);
}
- var g = this.grid, s = this.selection;
- if(!s){
- e.stopEvent();
- var cell = g.walkCells(0, 0, 1, this.isSelectable, this);
- if(cell){
- this.select(cell[0], cell[1]);
- }
- return;
+ var bt = this.getBodyTable(), lt = this.getLockedTable();
+ if(bt.rows[index]){
+ bt.firstChild.removeChild(bt.rows[index]);
}
- var sm = this;
- var walk = function(row, col, step){
- return g.walkCells(row, col, step, sm.isSelectable, sm);
- };
- var k = e.getKey(), r = s.cell[0], c = s.cell[1];
- var newCell;
+ if(lt.rows[index]){
+ lt.firstChild.removeChild(lt.rows[index]);
+ }
+ if(isUpdate !== true){
+ this.stripeRows(index);
+ this.syncRowHeights(index, index);
+ this.layout();
+ this.fireEvent("rowremoved", this, index, record);
+ }
+ },
-
+ onLoad : function(){
+ this.scrollToTop();
+ },
- switch(k){
- case e.TAB:
- // handled by onEditorKey
- if (g.isEditor && g.editing) {
- return;
- }
- if(e.shiftKey) {
- newCell = walk(r, c-1, -1);
- } else {
- newCell = walk(r, c+1, 1);
- }
- break;
-
- case e.DOWN:
- newCell = walk(r+1, c, 1);
- break;
-
- case e.UP:
- newCell = walk(r-1, c, -1);
- break;
-
- case e.RIGHT:
- newCell = walk(r, c+1, 1);
- break;
-
- case e.LEFT:
- newCell = walk(r, c-1, -1);
- break;
-
- case e.ENTER:
-
- if(g.isEditor && !g.editing){
- g.startEditing(r, c);
- e.stopEvent();
- return;
- }
-
-
- break;
- };
- if(newCell){
- this.select(newCell[0], newCell[1]);
- e.stopEvent();
-
+ /**
+ * Scrolls the grid to the top
+ */
+ scrollToTop : function(){
+ if(this.scroller){
+ this.scroller.dom.scrollTop = 0;
+ this.syncScroll();
}
},
- acceptsNav : function(row, col, cm){
- return !cm.isHidden(col) && cm.isCellEditable(col, row);
+ /**
+ * Gets a panel in the header of the grid that can be used for toolbars etc.
+ * After modifying the contents of this panel a call to grid.autoSize() may be
+ * required to register any changes in size.
+ * @param {Boolean} doShow By default the header is hidden. Pass true to show the panel
+ * @return Roo.Element
+ */
+ getHeaderPanel : function(doShow){
+ if(doShow){
+ this.headerPanel.show();
+ }
+ return this.headerPanel;
},
+
/**
- * Selects a cell.
- * @param {Number} field (not used) - as it's normally used as a listener
- * @param {Number} e - event - fake it by using
- *
- * var e = Roo.EventObjectImpl.prototype;
- * e.keyCode = e.TAB
- *
- *
+ * Gets a panel in the footer of the grid that can be used for toolbars etc.
+ * After modifying the contents of this panel a call to grid.autoSize() may be
+ * required to register any changes in size.
+ * @param {Boolean} doShow By default the footer is hidden. Pass true to show the panel
+ * @return Roo.Element
*/
- onEditorKey : function(field, e){
-
- var k = e.getKey(),
- newCell,
- g = this.grid,
- ed = g.activeEditor,
- forward = false;
- ///Roo.log('onEditorKey' + k);
-
-
- if (this.enter_is_tab && k == e.ENTER) {
- k = e.TAB;
+ getFooterPanel : function(doShow){
+ if(doShow){
+ this.footerPanel.show();
}
+ return this.footerPanel;
+ },
+
+ initElements : function(){
+ var E = Roo.Element;
+ var el = this.grid.getGridEl().dom.firstChild;
+ var cs = el.childNodes;
+
+ this.el = new E(el);
- if(k == e.TAB){
- if(e.shiftKey){
- newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
- }else{
- newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
- forward = true;
- }
-
- e.stopEvent();
-
- } else if(k == e.ENTER && !e.ctrlKey){
- ed.completeEdit();
- e.stopEvent();
- newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
+ this.focusEl = new E(el.firstChild);
+ this.focusEl.swallowEvent("click", true);
- } else if(k == e.ESC){
- ed.cancelEdit();
- }
-
- if (newCell) {
- var ecall = { cell : newCell, forward : forward };
- this.fireEvent('beforeeditnext', ecall );
- newCell = ecall.cell;
- forward = ecall.forward;
- }
-
- if(newCell){
- //Roo.log('next cell after edit');
- g.startEditing.defer(100, g, [newCell[0], newCell[1]]);
- } else if (forward) {
- // tabbed past last
- this.fireEvent.defer(100, this, ['tabend',this]);
- }
- }
-});/*
- * 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">
- */
-
-/**
- * @class Roo.grid.EditorGrid
- * @extends Roo.grid.Grid
- * Class for creating and editable grid.
- * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
- * The container MUST have some type of size defined for the grid to fill. The container will be
- * automatically set to position relative if it isn't already.
- * @param {Object} dataSource The data model to bind to
- * @param {Object} colModel The column model with info about this grid's columns
- */
-Roo.grid.EditorGrid = function(container, config){
- Roo.grid.EditorGrid.superclass.constructor.call(this, container, config);
- this.getGridEl().addClass("xedit-grid");
-
- if(!this.selModel){
- this.selModel = new Roo.grid.CellSelectionModel();
- }
+ this.headerPanel = new E(cs[1]);
+ this.headerPanel.enableDisplayMode("block");
- this.activeEditor = null;
+ this.scroller = new E(cs[2]);
+ this.scrollSizer = new E(this.scroller.dom.firstChild);
- this.addEvents({
- /**
- * @event beforeedit
- * Fires before cell editing is triggered. The edit event object has the following properties <br />
- * <ul style="padding:5px;padding-left:16px;">
- * <li>grid - This grid</li>
- * <li>record - The record being edited</li>
- * <li>field - The field name being edited</li>
- * <li>value - The value for the field being edited.</li>
- * <li>row - The grid row index</li>
- * <li>column - The grid column index</li>
- * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
- * </ul>
- * @param {Object} e An edit event (see above for description)
- */
- "beforeedit" : true,
- /**
- * @event afteredit
- * Fires after a cell is edited. <br />
- * <ul style="padding:5px;padding-left:16px;">
- * <li>grid - This grid</li>
- * <li>record - The record being edited</li>
- * <li>field - The field name being edited</li>
- * <li>value - The value being set</li>
- * <li>originalValue - The original value for the field, before the edit.</li>
- * <li>row - The grid row index</li>
- * <li>column - The grid column index</li>
- * </ul>
- * @param {Object} e An edit event (see above for description)
- */
- "afteredit" : true,
- /**
- * @event validateedit
- * Fires after a cell is edited, but before the value is set in the record.
- * You can use this to modify the value being set in the field, Return false
- * to cancel the change. The edit event object has the following properties <br />
- * <ul style="padding:5px;padding-left:16px;">
- * <li>editor - This editor</li>
- * <li>grid - This grid</li>
- * <li>record - The record being edited</li>
- * <li>field - The field name being edited</li>
- * <li>value - The value being set</li>
- * <li>originalValue - The original value for the field, before the edit.</li>
- * <li>row - The grid row index</li>
- * <li>column - The grid column index</li>
- * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
- * </ul>
- * @param {Object} e An edit event (see above for description)
- */
- "validateedit" : true
- });
- this.on("bodyscroll", this.stopEditing, this);
- this.on(this.clicksToEdit == 1 ? "cellclick" : "celldblclick", this.onCellDblClick, this);
-};
+ this.lockedWrap = new E(cs[3]);
+ this.lockedHd = new E(this.lockedWrap.dom.firstChild);
+ this.lockedBody = new E(this.lockedWrap.dom.childNodes[1]);
-Roo.extend(Roo.grid.EditorGrid, Roo.grid.Grid, {
- /**
- * @cfg {Number} clicksToEdit
- * The number of clicks on a cell required to display the cell's editor (defaults to 2)
- */
- clicksToEdit: 2,
+ this.mainWrap = new E(cs[4]);
+ this.mainHd = new E(this.mainWrap.dom.firstChild);
+ this.mainBody = new E(this.mainWrap.dom.childNodes[1]);
- // private
- isEditor : true,
- // private
- trackMouseOver: false, // causes very odd FF errors
+ this.footerPanel = new E(cs[5]);
+ this.footerPanel.enableDisplayMode("block");
- onCellDblClick : function(g, row, col){
- this.startEditing(row, col);
- },
+ this.resizeProxy = new E(cs[6]);
- onEditComplete : function(ed, value, startValue){
- this.editing = false;
- this.activeEditor = null;
- ed.un("specialkey", this.selModel.onEditorKey, this.selModel);
- var r = ed.record;
- var field = this.colModel.getDataIndex(ed.col);
- var e = {
- grid: this,
- record: r,
- field: field,
- originalValue: startValue,
- value: value,
- row: ed.row,
- column: ed.col,
- cancel:false,
- editor: ed
- };
- var cell = Roo.get(this.view.getCell(ed.row,ed.col));
- cell.show();
-
- if(String(value) !== String(startValue)){
-
- if(this.fireEvent("validateedit", e) !== false && !e.cancel){
- r.set(field, e.value);
- // if we are dealing with a combo box..
- // then we also set the 'name' colum to be the displayField
- if (ed.field.displayField && ed.field.name) {
- r.set(ed.field.name, ed.field.el.dom.value);
- }
-
- delete e.cancel; //?? why!!!
- this.fireEvent("afteredit", e);
- }
- } else {
- this.fireEvent("afteredit", e); // always fire it!
- }
- this.view.focusCell(ed.row, ed.col);
- },
+ this.headerSelector = String.format(
+ '#{0} td.x-grid-hd, #{1} td.x-grid-hd',
+ this.lockedHd.id, this.mainHd.id
+ );
- /**
- * Starts editing the specified for the specified row/column
- * @param {Number} rowIndex
- * @param {Number} colIndex
- */
- startEditing : function(row, col){
- this.stopEditing();
- if(this.colModel.isCellEditable(col, row)){
- this.view.ensureVisible(row, col, true);
-
- var r = this.dataSource.getAt(row);
- var field = this.colModel.getDataIndex(col);
- var cell = Roo.get(this.view.getCell(row,col));
- var e = {
- grid: this,
- record: r,
- field: field,
- value: r.data[field],
- row: row,
- column: col,
- cancel:false
- };
- if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
- this.editing = true;
- var ed = this.colModel.getCellEditor(col, row);
-
- if (!ed) {
- return;
- }
- if(!ed.rendered){
- ed.render(ed.parentEl || document.body);
- }
- ed.field.reset();
-
- cell.hide();
-
- (function(){ // complex but required for focus issues in safari, ie and opera
- ed.row = row;
- ed.col = col;
- ed.record = r;
- ed.on("complete", this.onEditComplete, this, {single: true});
- ed.on("specialkey", this.selModel.onEditorKey, this.selModel);
- this.activeEditor = ed;
- var v = r.data[field];
- ed.startEdit(this.view.getCell(row, col), v);
- // combo's with 'displayField and name set
- if (ed.field.displayField && ed.field.name) {
- ed.field.el.dom.value = r.data[ed.field.name];
- }
-
-
- }).defer(50, this);
- }
- }
+ this.splitterSelector = String.format(
+ '#{0} div.x-grid-split, #{1} div.x-grid-split',
+ this.idToCssName(this.lockedHd.id), this.idToCssName(this.mainHd.id)
+ );
},
-
- /**
- * Stops any active editing
- */
- stopEditing : function(){
- if(this.activeEditor){
- this.activeEditor.completeEdit();
- }
- this.activeEditor = null;
+ idToCssName : function(s)
+ {
+ return s.replace(/[^a-z0-9]+/ig, '-');
},
-
- /**
- * Called to get grid's drag proxy text, by default returns this.ddText.
- * @return {String}
- */
- getDragDropText : function(){
- var count = this.selModel.getSelectedCell() ? 1 : 0;
- return String.format(this.ddText, count, count == 1 ? '' : 's');
- }
-
-});/*
- * 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">
- */
-// private - not really -- you end up using it !
-// This is a support class used internally by the Grid components
-
-/**
- * @class Roo.grid.GridEditor
- * @extends Roo.Editor
- * Class for creating and editable grid elements.
- * @param {Object} config any settings (must include field)
- */
-Roo.grid.GridEditor = function(field, config){
- if (!config && field.field) {
- config = field;
- field = Roo.factory(config.field, Roo.form);
- }
- Roo.grid.GridEditor.superclass.constructor.call(this, field, config);
- field.monitorTab = false;
-};
+ getHeaderCell : function(index){
+ return Roo.DomQuery.select(this.headerSelector)[index];
+ },
-Roo.extend(Roo.grid.GridEditor, Roo.Editor, {
-
- /**
- * @cfg {Roo.form.Field} field Field to wrap (or xtyped)
- */
-
- alignment: "tl-tl",
- autoSize: "width",
- hideEl : false,
- cls: "x-small-editor x-grid-editor",
- shim:false,
- shadow:"frame"
-});/*
- * 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">
- */
-
+ getHeaderCellMeasure : function(index){
+ return this.getHeaderCell(index).firstChild;
+ },
-
-Roo.grid.PropertyRecord = Roo.data.Record.create([
- {name:'name',type:'string'}, 'value'
-]);
+ getHeaderCellText : function(index){
+ return this.getHeaderCell(index).firstChild.firstChild;
+ },
+ getLockedTable : function(){
+ return this.lockedBody.dom.firstChild;
+ },
-Roo.grid.PropertyStore = function(grid, source){
- this.grid = grid;
- this.store = new Roo.data.Store({
- recordType : Roo.grid.PropertyRecord
- });
- this.store.on('update', this.onUpdate, this);
- if(source){
- this.setSource(source);
- }
- Roo.grid.PropertyStore.superclass.constructor.call(this);
-};
+ getBodyTable : function(){
+ return this.mainBody.dom.firstChild;
+ },
+ getLockedRow : function(index){
+ return this.getLockedTable().rows[index];
+ },
+ getRow : function(index){
+ return this.getBodyTable().rows[index];
+ },
-Roo.extend(Roo.grid.PropertyStore, Roo.util.Observable, {
- setSource : function(o){
- this.source = o;
- this.store.removeAll();
- var data = [];
- for(var k in o){
- if(this.isEditableValue(o[k])){
- data.push(new Roo.grid.PropertyRecord({name: k, value: o[k]}, k));
- }
+ getRowComposite : function(index){
+ if(!this.rowEl){
+ this.rowEl = new Roo.CompositeElementLite();
}
- this.store.loadRecords({records: data}, {}, true);
+ var els = [], lrow, mrow;
+ if(lrow = this.getLockedRow(index)){
+ els.push(lrow);
+ }
+ if(mrow = this.getRow(index)){
+ els.push(mrow);
+ }
+ this.rowEl.elements = els;
+ return this.rowEl;
},
-
- onUpdate : function(ds, record, type){
- if(type == Roo.data.Record.EDIT){
- var v = record.data['value'];
- var oldValue = record.modified['value'];
- if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
- this.source[record.id] = v;
- record.commit();
- this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
- }else{
- record.reject();
- }
+ /**
+ * Gets the 'td' of the cell
+ *
+ * @param {Integer} rowIndex row to select
+ * @param {Integer} colIndex column to select
+ *
+ * @return {Object}
+ */
+ getCell : function(rowIndex, colIndex){
+ var locked = this.cm.getLockedCount();
+ var source;
+ if(colIndex < locked){
+ source = this.lockedBody.dom.firstChild;
+ }else{
+ source = this.mainBody.dom.firstChild;
+ colIndex -= locked;
}
+ return source.rows[rowIndex].childNodes[colIndex];
},
- getProperty : function(row){
- return this.store.getAt(row);
+ getCellText : function(rowIndex, colIndex){
+ return this.getCell(rowIndex, colIndex).firstChild.firstChild;
},
- isEditableValue: function(val){
- if(val && val instanceof Date){
- return true;
- }else if(typeof val == 'object' || typeof val == 'function'){
- return false;
+ getCellBox : function(cell){
+ var b = this.fly(cell).getBox();
+ if(Roo.isOpera){ // opera fails to report the Y
+ b.y = cell.offsetTop + this.mainBody.getY();
}
- return true;
+ return b;
},
- setValue : function(prop, value){
- this.source[prop] = value;
- this.store.getById(prop).set('value', value);
+ getCellIndex : function(cell){
+ var id = String(cell.className).match(this.cellRE);
+ if(id){
+ return parseInt(id[1], 10);
+ }
+ return 0;
},
- getSource : function(){
- return this.source;
- }
-});
+ findHeaderIndex : function(n){
+ var r = Roo.fly(n).findParent("td." + this.hdClass, 6);
+ return r ? this.getCellIndex(r) : false;
+ },
-Roo.grid.PropertyColumnModel = function(grid, store){
- this.grid = grid;
- var g = Roo.grid;
- g.PropertyColumnModel.superclass.constructor.call(this, [
- {header: this.nameText, sortable: true, dataIndex:'name', id: 'name'},
- {header: this.valueText, resizable:false, dataIndex: 'value', id: 'value'}
- ]);
- this.store = store;
- this.bselect = Roo.DomHelper.append(document.body, {
- tag: 'select', style:'display:none', cls: 'x-grid-editor', children: [
- {tag: 'option', value: 'true', html: 'true'},
- {tag: 'option', value: 'false', html: 'false'}
- ]
- });
- Roo.id(this.bselect);
- var f = Roo.form;
- this.editors = {
- 'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
- 'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
- 'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
- 'int' : new g.GridEditor(new f.NumberField({selectOnFocus:true, allowDecimals:false, style:'text-align:left;'})),
- 'boolean' : new g.GridEditor(new f.Field({el:this.bselect,selectOnFocus:true}))
- };
- this.renderCellDelegate = this.renderCell.createDelegate(this);
- this.renderPropDelegate = this.renderProp.createDelegate(this);
-};
+ findHeaderCell : function(n){
+ var r = Roo.fly(n).findParent("td." + this.hdClass, 6);
+ return r ? r : false;
+ },
-Roo.extend(Roo.grid.PropertyColumnModel, Roo.grid.ColumnModel, {
-
-
- nameText : 'Name',
- valueText : 'Value',
-
- dateFormat : 'm/j/Y',
-
-
- renderDate : function(dateVal){
- return dateVal.dateFormat(this.dateFormat);
+ findRowIndex : function(n){
+ if(!n){
+ return false;
+ }
+ var r = Roo.fly(n).findParent("tr." + this.rowClass, 6);
+ return r ? r.rowIndex : false;
},
- renderBool : function(bVal){
- return bVal ? 'true' : 'false';
+ findCellIndex : function(node){
+ var stop = this.el.dom;
+ while(node && node != stop){
+ if(this.findRE.test(node.className)){
+ return this.getCellIndex(node);
+ }
+ node = node.parentNode;
+ }
+ return false;
},
- isCellEditable : function(colIndex, rowIndex){
- return colIndex == 1;
+ getColumnId : function(index){
+ return this.cm.getColumnId(index);
},
- getRenderer : function(col){
- return col == 1 ?
- this.renderCellDelegate : this.renderPropDelegate;
+ getSplitters : function()
+ {
+ if(this.splitterSelector){
+ return Roo.DomQuery.select(this.splitterSelector);
+ }else{
+ return null;
+ }
},
- renderProp : function(v){
- return this.getPropertyName(v);
+ getSplitter : function(index){
+ return this.getSplitters()[index];
},
- renderCell : function(val){
- var rv = val;
- if(val instanceof Date){
- rv = this.renderDate(val);
- }else if(typeof val == 'boolean'){
- rv = this.renderBool(val);
+ onRowOver : function(e, t){
+ var row;
+ if((row = this.findRowIndex(t)) !== false){
+ this.getRowComposite(row).addClass("x-grid-row-over");
}
- return Roo.util.Format.htmlEncode(rv);
},
- getPropertyName : function(name){
- var pn = this.grid.propertyNames;
- return pn && pn[name] ? pn[name] : name;
+ onRowOut : function(e, t){
+ var row;
+ if((row = this.findRowIndex(t)) !== false && row !== this.findRowIndex(e.getRelatedTarget())){
+ this.getRowComposite(row).removeClass("x-grid-row-over");
+ }
},
- getCellEditor : function(colIndex, rowIndex){
- var p = this.store.getProperty(rowIndex);
- var n = p.data['name'], val = p.data['value'];
-
- if(typeof(this.grid.customEditors[n]) == 'string'){
- return this.editors[this.grid.customEditors[n]];
- }
- if(typeof(this.grid.customEditors[n]) != 'undefined'){
- return this.grid.customEditors[n];
- }
- if(val instanceof Date){
- return this.editors['date'];
- }else if(typeof val == 'number'){
- return this.editors['number'];
- }else if(typeof val == 'boolean'){
- return this.editors['boolean'];
- }else{
- return this.editors['string'];
+ renderHeaders : function(){
+ var cm = this.cm;
+ var ct = this.templates.hcell, ht = this.templates.header, st = this.templates.hsplit;
+ var cb = [], lb = [], sb = [], lsb = [], p = {};
+ for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+ p.cellId = "x-grid-hd-0-" + i;
+ p.splitId = "x-grid-csplit-0-" + i;
+ p.id = cm.getColumnId(i);
+ p.value = cm.getColumnHeader(i) || "";
+ p.title = cm.getColumnTooltip(i) || (''+p.value).match(/\</) ? '' : p.value || "";
+ p.style = (this.grid.enableColumnResize === false || !cm.isResizable(i) || cm.isFixed(i)) ? 'cursor:default' : '';
+ if(!cm.isLocked(i)){
+ cb[cb.length] = ct.apply(p);
+ sb[sb.length] = st.apply(p);
+ }else{
+ lb[lb.length] = ct.apply(p);
+ lsb[lsb.length] = st.apply(p);
+ }
}
- }
-});
-
-/**
- * @class Roo.grid.PropertyGrid
- * @extends Roo.grid.EditorGrid
- * This class represents the interface of a component based property grid control.
- * <br><br>Usage:<pre><code>
- var grid = new Roo.grid.PropertyGrid("my-container-id", {
-
- });
- // set any options
- grid.render();
- * </code></pre>
-
- * @constructor
- * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
- * The container MUST have some type of size defined for the grid to fill. The container will be
- * automatically set to position relative if it isn't already.
- * @param {Object} config A config object that sets properties on this grid.
- */
-Roo.grid.PropertyGrid = function(container, config){
- config = config || {};
- var store = new Roo.grid.PropertyStore(this);
- this.store = store;
- var cm = new Roo.grid.PropertyColumnModel(this, store);
- store.store.sort('name', 'ASC');
- Roo.grid.PropertyGrid.superclass.constructor.call(this, container, Roo.apply({
- ds: store.store,
- cm: cm,
- enableColLock:false,
- enableColumnMove:false,
- stripeRows:false,
- trackMouseOver: false,
- clicksToEdit:1
- }, config));
- this.getGridEl().addClass('x-props-grid');
- this.lastEditRow = null;
- this.on('columnresize', this.onColumnResize, this);
- this.addEvents({
- /**
- * @event beforepropertychange
- * Fires before a property changes (return false to stop?)
- * @param {Roo.grid.PropertyGrid} grid property grid? (check could be store)
- * @param {String} id Record Id
- * @param {String} newval New Value
- * @param {String} oldval Old Value
- */
- "beforepropertychange": true,
- /**
- * @event propertychange
- * Fires after a property changes
- * @param {Roo.grid.PropertyGrid} grid property grid? (check could be store)
- * @param {String} id Record Id
- * @param {String} newval New Value
- * @param {String} oldval Old Value
- */
- "propertychange": true
- });
- this.customEditors = this.customEditors || {};
-};
-Roo.extend(Roo.grid.PropertyGrid, Roo.grid.EditorGrid, {
-
- /**
- * @cfg {Object} customEditors map of colnames=> custom editors.
- * the custom editor can be one of the standard ones (date|string|number|int|boolean), or a
- * grid editor eg. Roo.grid.GridEditor(new Roo.form.TextArea({selectOnFocus:true})),
- * false disables editing of the field.
- */
-
- /**
- * @cfg {Object} propertyNames map of property Names to their displayed value
- */
-
- render : function(){
- Roo.grid.PropertyGrid.superclass.render.call(this);
- this.autoSize.defer(100, this);
+ return [ht.apply({cells: lb.join(""), splits:lsb.join("")}),
+ ht.apply({cells: cb.join(""), splits:sb.join("")})];
},
- autoSize : function(){
- Roo.grid.PropertyGrid.superclass.autoSize.call(this);
- if(this.view){
- this.view.fitColumns();
- }
+ updateHeaders : function(){
+ var html = this.renderHeaders();
+ this.lockedHd.update(html[0]);
+ this.mainHd.update(html[1]);
},
- onColumnResize : function(){
- this.colModel.setColumnWidth(1, this.container.getWidth(true)-this.colModel.getColumnWidth(0));
- this.autoSize();
+ /**
+ * Focuses the specified row.
+ * @param {Number} row The row index
+ */
+ focusRow : function(row)
+ {
+ //Roo.log('GridView.focusRow');
+ var x = this.scroller.dom.scrollLeft;
+ this.focusCell(row, 0, false);
+ this.scroller.dom.scrollLeft = x;
},
+
/**
- * Sets the data for the Grid
- * accepts a Key => Value object of all the elements avaiable.
- * @param {Object} data to appear in grid.
+ * Focuses the specified cell.
+ * @param {Number} row The row index
+ * @param {Number} col The column index
+ * @param {Boolean} hscroll false to disable horizontal scrolling
*/
- setSource : function(source){
- this.store.setSource(source);
- //this.autoSize();
+ focusCell : function(row, col, hscroll)
+ {
+ //Roo.log('GridView.focusCell');
+ var el = this.ensureVisible(row, col, hscroll);
+ this.focusEl.alignTo(el, "tl-tl");
+ if(Roo.isGecko){
+ this.focusEl.focus();
+ }else{
+ this.focusEl.focus.defer(1, this.focusEl);
+ }
},
+
/**
- * Gets all the data from the grid.
- * @return {Object} data data stored in grid
+ * Scrolls the specified cell into view
+ * @param {Number} row The row index
+ * @param {Number} col The column index
+ * @param {Boolean} hscroll false to disable horizontal scrolling
*/
- getSource : function(){
- return this.store.getSource();
- }
-});/*
-
- * Licence LGPL
-
- */
-
-/**
- * @class Roo.grid.Calendar
- * @extends Roo.util.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", {
- ds: myDataStore,
- cm: myColModel,
- selModel: mySelectionModel,
- autoSizeColumns: true,
- monitorWindowResize: false,
- trackMouseOver: true
- eventstore : real data store..
- });
- // set any options
- grid.render();
-
- * @constructor
- * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
- * The container MUST have some type of size defined for the grid to fill. The container will be
- * automatically set to position relative if it isn't already.
- * @param {Object} config A config object that sets properties on this grid.
- */
-Roo.grid.Calendar = function(container, config){
- // initialize the container
- this.container = Roo.get(container);
- this.container.update("");
- this.container.setStyle("overflow", "hidden");
- this.container.addClass('x-grid-container');
+ ensureVisible : function(row, col, hscroll)
+ {
+ //Roo.log('GridView.ensureVisible,' + row + ',' + col);
+ //return null; //disable for testing.
+ if(typeof row != "number"){
+ row = row.rowIndex;
+ }
+ if(row < 0 && row >= this.ds.getCount()){
+ return null;
+ }
+ col = (col !== undefined ? col : 0);
+ var cm = this.grid.colModel;
+ while(cm.isHidden(col)){
+ col++;
+ }
- this.id = this.container.id;
+ var el = this.getCell(row, col);
+ if(!el){
+ return null;
+ }
+ var c = this.scroller.dom;
- Roo.apply(this, config);
- // check and correct shorthanded configs
-
- var rows = [];
- var d =1;
- for (var r = 0;r < 6;r++) {
+ var ctop = parseInt(el.offsetTop, 10);
+ var cleft = parseInt(el.offsetLeft, 10);
+ var cbot = ctop + el.offsetHeight;
+ var cright = cleft + el.offsetWidth;
- rows[r]=[];
- for (var c =0;c < 7;c++) {
- rows[r][c]= '';
+ var ch = c.clientHeight - this.mainHd.dom.offsetHeight;
+ var stop = parseInt(c.scrollTop, 10);
+ var sleft = parseInt(c.scrollLeft, 10);
+ var sbot = stop + ch;
+ var sright = sleft + c.clientWidth;
+ /*
+ Roo.log('GridView.ensureVisible:' +
+ ' ctop:' + ctop +
+ ' c.clientHeight:' + c.clientHeight +
+ ' this.mainHd.dom.offsetHeight:' + this.mainHd.dom.offsetHeight +
+ ' stop:' + stop +
+ ' cbot:' + cbot +
+ ' sbot:' + sbot +
+ ' ch:' + ch
+ );
+ */
+ if(ctop < stop){
+ c.scrollTop = ctop;
+ //Roo.log("set scrolltop to ctop DISABLE?");
+ }else if(cbot > sbot){
+ //Roo.log("set scrolltop to cbot-ch");
+ c.scrollTop = cbot-ch;
+ }
+
+ if(hscroll !== false){
+ if(cleft < sleft){
+ c.scrollLeft = cleft;
+ }else if(cright > sright){
+ c.scrollLeft = cright-c.clientWidth;
+ }
}
- }
- if (this.eventStore) {
- this.eventStore= Roo.factory(this.eventStore, Roo.data);
- this.eventStore.on('load',this.onLoad, this);
- this.eventStore.on('beforeload',this.clearEvents, this);
- }
-
- this.dataSource = new Roo.data.Store({
- proxy: new Roo.data.MemoryProxy(rows),
- reader: new Roo.data.ArrayReader({}, [
- 'weekday0', 'weekday1', 'weekday2', 'weekday3', 'weekday4', 'weekday5', 'weekday6' ])
- });
+ return el;
+ },
- this.dataSource.load();
- this.ds = this.dataSource;
- this.ds.xmodule = this.xmodule || false;
-
-
- var cellRender = function(v,x,r)
- {
- return String.format(
- '<div class="fc-day fc-widget-content"><div>' +
- '<div class="fc-event-container"></div>' +
- '<div class="fc-day-number">{0}</div>'+
-
- '<div class="fc-day-content"><div style="position:relative"></div></div>' +
- '</div></div>', v);
-
- }
-
-
- this.colModel = new Roo.grid.ColumnModel( [
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday0',
- header : 'Sunday',
- renderer : cellRender
- },
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday1',
- header : 'Monday',
- renderer : cellRender
- },
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday2',
- header : 'Tuesday',
- renderer : cellRender
- },
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday3',
- header : 'Wednesday',
- renderer : cellRender
- },
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday4',
- header : 'Thursday',
- renderer : cellRender
- },
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday5',
- header : 'Friday',
- renderer : cellRender
- },
- {
- xtype: 'ColumnModel',
- xns: Roo.grid,
- dataIndex : 'weekday6',
- header : 'Saturday',
- renderer : cellRender
+ updateColumns : function(){
+ this.grid.stopEditing();
+ var cm = this.grid.colModel, colIds = this.getColumnIds();
+ //var totalWidth = cm.getTotalWidth();
+ var pos = 0;
+ for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+ //if(cm.isHidden(i)) continue;
+ var w = cm.getColumnWidth(i);
+ this.css.updateRule(this.colSelector+this.idToCssName(colIds[i]), "width", (w - this.borderWidth) + "px");
+ this.css.updateRule(this.hdSelector+this.idToCssName(colIds[i]), "width", (w - this.borderWidth) + "px");
}
- ]);
- this.cm = this.colModel;
- this.cm.xmodule = this.xmodule || false;
-
-
-
- //this.selModel = new Roo.grid.CellSelectionModel();
- //this.sm = this.selModel;
- //this.selModel.init(this);
-
-
- if(this.width){
- this.container.setWidth(this.width);
- }
+ this.updateSplitters();
+ },
- if(this.height){
- this.container.setHeight(this.height);
- }
- /** @private */
- this.addEvents({
- // raw events
- /**
- * @event click
- * The raw click event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "click" : true,
- /**
- * @event dblclick
- * The raw dblclick event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "dblclick" : true,
- /**
- * @event contextmenu
- * The raw contextmenu event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "contextmenu" : true,
- /**
- * @event mousedown
- * The raw mousedown event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "mousedown" : true,
- /**
- * @event mouseup
- * The raw mouseup event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "mouseup" : true,
- /**
- * @event mouseover
- * The raw mouseover event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "mouseover" : true,
- /**
- * @event mouseout
- * The raw mouseout event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "mouseout" : true,
- /**
- * @event keypress
- * The raw keypress event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "keypress" : true,
- /**
- * @event keydown
- * The raw keydown event for the entire grid.
- * @param {Roo.EventObject} e
- */
- "keydown" : true,
+ generateRules : function(cm){
+ var ruleBuf = [], rulesId = this.idToCssName(this.grid.id)+ '-cssrules';
+ Roo.util.CSS.removeStyleSheet(rulesId);
+ for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+ var cid = cm.getColumnId(i);
+ var align = '';
+ if(cm.config[i].align){
+ align = 'text-align:'+cm.config[i].align+';';
+ }
+ var hidden = '';
+ if(cm.isHidden(i)){
+ hidden = 'display:none;';
+ }
+ var width = "width:" + (cm.getColumnWidth(i) - this.borderWidth) + "px;";
+ ruleBuf.push(
+ this.colSelector, cid, " {\n", cm.config[i].css, align, width, "\n}\n",
+ this.hdSelector, cid, " {\n", align, width, "}\n",
+ this.tdSelector, cid, " {\n",hidden,"\n}\n",
+ this.splitSelector, cid, " {\n", hidden , "\n}\n");
+ }
+ return Roo.util.CSS.createStyleSheet(ruleBuf.join(""), rulesId);
+ },
- // custom events
+ updateSplitters : function(){
+ var cm = this.cm, s = this.getSplitters();
+ if(s){ // splitters not created yet
+ var pos = 0, locked = true;
+ for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+ if(cm.isHidden(i)) {
+ continue;
+ }
+ var w = cm.getColumnWidth(i); // make sure it's a number
+ if(!cm.isLocked(i) && locked){
+ pos = 0;
+ locked = false;
+ }
+ pos += w;
+ s[i].style.left = (pos-this.splitOffset) + "px";
+ }
+ }
+ },
+ handleHiddenChange : function(colModel, colIndex, hidden){
+ if(hidden){
+ this.hideColumn(colIndex);
+ }else{
+ this.unhideColumn(colIndex);
+ }
+ },
+
+ hideColumn : function(colIndex){
+ var cid = this.getColumnId(colIndex);
+ this.css.updateRule(this.tdSelector+this.idToCssName(cid), "display", "none");
+ this.css.updateRule(this.splitSelector+this.idToCssName(cid), "display", "none");
+ if(Roo.isSafari){
+ this.updateHeaders();
+ }
+ this.updateSplitters();
+ this.layout();
+ },
+
+ unhideColumn : function(colIndex){
+ var cid = this.getColumnId(colIndex);
+ this.css.updateRule(this.tdSelector+this.idToCssName(cid), "display", "");
+ this.css.updateRule(this.splitSelector+this.idToCssName(cid), "display", "");
+
+ if(Roo.isSafari){
+ this.updateHeaders();
+ }
+ this.updateSplitters();
+ this.layout();
+ },
+
+ insertRows : function(dm, firstRow, lastRow, isUpdate){
+ if(firstRow == 0 && lastRow == dm.getCount()-1){
+ this.refresh();
+ }else{
+ if(!isUpdate){
+ this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
+ }
+ var s = this.getScrollState();
+ var markup = this.renderRows(firstRow, lastRow);
+ this.bufferRows(markup[0], this.getLockedTable(), firstRow);
+ this.bufferRows(markup[1], this.getBodyTable(), firstRow);
+ this.restoreScroll(s);
+ if(!isUpdate){
+ this.fireEvent("rowsinserted", this, firstRow, lastRow);
+ this.syncRowHeights(firstRow, lastRow);
+ this.stripeRows(firstRow);
+ this.layout();
+ }
+ }
+ },
+
+ bufferRows : function(markup, target, index){
+ var before = null, trows = target.rows, tbody = target.tBodies[0];
+ if(index < trows.length){
+ before = trows[index];
+ }
+ var b = document.createElement("div");
+ b.innerHTML = "<table><tbody>"+markup+"</tbody></table>";
+ var rows = b.firstChild.rows;
+ for(var i = 0, len = rows.length; i < len; i++){
+ if(before){
+ tbody.insertBefore(rows[0], before);
+ }else{
+ tbody.appendChild(rows[0]);
+ }
+ }
+ b.innerHTML = "";
+ b = null;
+ },
+
+ deleteRows : function(dm, firstRow, lastRow){
+ if(dm.getRowCount()<1){
+ this.fireEvent("beforerefresh", this);
+ this.mainBody.update("");
+ this.lockedBody.update("");
+ this.fireEvent("refresh", this);
+ }else{
+ this.fireEvent("beforerowsdeleted", this, firstRow, lastRow);
+ var bt = this.getBodyTable();
+ var tbody = bt.firstChild;
+ var rows = bt.rows;
+ for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ tbody.removeChild(rows[firstRow]);
+ }
+ this.stripeRows(firstRow);
+ this.fireEvent("rowsdeleted", this, firstRow, lastRow);
+ }
+ },
+
+ updateRows : function(dataSource, firstRow, lastRow){
+ var s = this.getScrollState();
+ this.refresh();
+ this.restoreScroll(s);
+ },
+
+ handleSort : function(dataSource, sortColumnIndex, sortDir, noRefresh){
+ if(!noRefresh){
+ this.refresh();
+ }
+ this.updateHeaderSortState();
+ },
+
+ getScrollState : function(){
+
+ var sb = this.scroller.dom;
+ return {left: sb.scrollLeft, top: sb.scrollTop};
+ },
+
+ stripeRows : function(startRow){
+ if(!this.grid.stripeRows || this.ds.getCount() < 1){
+ return;
+ }
+ startRow = startRow || 0;
+ var rows = this.getBodyTable().rows;
+ var lrows = this.getLockedTable().rows;
+ var cls = ' x-grid-row-alt ';
+ for(var i = startRow, len = rows.length; i < len; i++){
+ var row = rows[i], lrow = lrows[i];
+ var isAlt = ((i+1) % 2 == 0);
+ var hasAlt = (' '+row.className + ' ').indexOf(cls) != -1;
+ if(isAlt == hasAlt){
+ continue;
+ }
+ if(isAlt){
+ row.className += " x-grid-row-alt";
+ }else{
+ row.className = row.className.replace("x-grid-row-alt", "");
+ }
+ if(lrow){
+ lrow.className = row.className;
+ }
+ }
+ },
+
+ restoreScroll : function(state){
+ //Roo.log('GridView.restoreScroll');
+ var sb = this.scroller.dom;
+ sb.scrollLeft = state.left;
+ sb.scrollTop = state.top;
+ this.syncScroll();
+ },
+
+ syncScroll : function(){
+ //Roo.log('GridView.syncScroll');
+ var sb = this.scroller.dom;
+ var sh = this.mainHd.dom;
+ var bs = this.mainBody.dom;
+ var lv = this.lockedBody.dom;
+ sh.scrollLeft = bs.scrollLeft = sb.scrollLeft;
+ lv.scrollTop = bs.scrollTop = sb.scrollTop;
+ },
+
+ handleScroll : function(e){
+ this.syncScroll();
+ var sb = this.scroller.dom;
+ this.grid.fireEvent("bodyscroll", sb.scrollLeft, sb.scrollTop);
+ e.stopEvent();
+ },
+
+ handleWheel : function(e){
+ var d = e.getWheelDelta();
+ this.scroller.dom.scrollTop -= d*22;
+ // set this here to prevent jumpy scrolling on large tables
+ this.lockedBody.dom.scrollTop = this.mainBody.dom.scrollTop = this.scroller.dom.scrollTop;
+ e.stopEvent();
+ },
+
+ renderRows : function(startRow, endRow){
+ // pull in all the crap needed to render rows
+ var g = this.grid, cm = g.colModel, ds = g.dataSource, stripe = g.stripeRows;
+ var colCount = cm.getColumnCount();
+
+ if(ds.getCount() < 1){
+ return ["", ""];
+ }
+
+ // build a map for all the columns
+ var cs = [];
+ for(var i = 0; i < colCount; i++){
+ var name = cm.getDataIndex(i);
+ cs[i] = {
+ name : typeof name == 'undefined' ? ds.fields.get(i).name : name,
+ renderer : cm.getRenderer(i),
+ id : cm.getColumnId(i),
+ locked : cm.isLocked(i),
+ has_editor : cm.isCellEditable(i)
+ };
+ }
+
+ startRow = startRow || 0;
+ endRow = typeof endRow == "undefined"? ds.getCount()-1 : endRow;
+
+ // records to render
+ var rs = ds.getRange(startRow, endRow);
+
+ return this.doRender(cs, rs, ds, startRow, colCount, stripe);
+ },
+
+ // As much as I hate to duplicate code, this was branched because FireFox really hates
+ // [].join("") on strings. The performance difference was substantial enough to
+ // branch this function
+ doRender : Roo.isGecko ?
+ function(cs, rs, ds, startRow, colCount, stripe){
+ var ts = this.templates, ct = ts.cell, rt = ts.row;
+ // buffers
+ var buf = "", lbuf = "", cb, lcb, c, p = {}, rp = {}, r, rowIndex;
+
+ var hasListener = this.grid.hasListener('rowclass');
+ var rowcfg = {};
+ for(var j = 0, len = rs.length; j < len; j++){
+ r = rs[j]; cb = ""; lcb = ""; rowIndex = (j+startRow);
+ for(var i = 0; i < colCount; i++){
+ c = cs[i];
+ p.cellId = "x-grid-cell-" + rowIndex + "-" + i;
+ p.id = c.id;
+ p.css = p.attr = "";
+ p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
+ if(p.value == undefined || p.value === "") {
+ p.value = " ";
+ }
+ if(c.has_editor){
+ p.css += ' x-grid-editable-cell';
+ }
+ if(c.dirty && typeof r.modified[c.name] !== 'undefined'){
+ p.css += ' x-grid-dirty-cell';
+ }
+ var markup = ct.apply(p);
+ if(!c.locked){
+ cb+= markup;
+ }else{
+ lcb+= markup;
+ }
+ }
+ var alt = [];
+ if(stripe && ((rowIndex+1) % 2 == 0)){
+ alt.push("x-grid-row-alt")
+ }
+ if(r.dirty){
+ alt.push( " x-grid-dirty-row");
+ }
+ rp.cells = lcb;
+ if(this.getRowClass){
+ alt.push(this.getRowClass(r, rowIndex));
+ }
+ if (hasListener) {
+ rowcfg = {
+
+ record: r,
+ rowIndex : rowIndex,
+ rowClass : ''
+ };
+ this.grid.fireEvent('rowclass', this, rowcfg);
+ alt.push(rowcfg.rowClass);
+ }
+ rp.alt = alt.join(" ");
+ lbuf+= rt.apply(rp);
+ rp.cells = cb;
+ buf+= rt.apply(rp);
+ }
+ return [lbuf, buf];
+ } :
+ function(cs, rs, ds, startRow, colCount, stripe){
+ var ts = this.templates, ct = ts.cell, rt = ts.row;
+ // buffers
+ var buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r, rowIndex;
+ var hasListener = this.grid.hasListener('rowclass');
+
+ var rowcfg = {};
+ for(var j = 0, len = rs.length; j < len; j++){
+ r = rs[j]; cb = []; lcb = []; rowIndex = (j+startRow);
+ for(var i = 0; i < colCount; i++){
+ c = cs[i];
+ p.cellId = "x-grid-cell-" + rowIndex + "-" + i;
+ p.id = c.id;
+ p.css = p.attr = "";
+ p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
+ if(p.value == undefined || p.value === "") {
+ p.value = " ";
+ }
+ //Roo.log(c);
+ if(c.has_editor){
+ p.css += ' x-grid-editable-cell';
+ }
+ if(r.dirty && typeof r.modified[c.name] !== 'undefined'){
+ p.css += ' x-grid-dirty-cell'
+ }
+
+ var markup = ct.apply(p);
+ if(!c.locked){
+ cb[cb.length] = markup;
+ }else{
+ lcb[lcb.length] = markup;
+ }
+ }
+ var alt = [];
+ if(stripe && ((rowIndex+1) % 2 == 0)){
+ alt.push( "x-grid-row-alt");
+ }
+ if(r.dirty){
+ alt.push(" x-grid-dirty-row");
+ }
+ rp.cells = lcb;
+ if(this.getRowClass){
+ alt.push( this.getRowClass(r, rowIndex));
+ }
+ if (hasListener) {
+ rowcfg = {
+
+ record: r,
+ rowIndex : rowIndex,
+ rowClass : ''
+ };
+ this.grid.fireEvent('rowclass', this, rowcfg);
+ alt.push(rowcfg.rowClass);
+ }
+
+ rp.alt = alt.join(" ");
+ rp.cells = lcb.join("");
+ lbuf[lbuf.length] = rt.apply(rp);
+ rp.cells = cb.join("");
+ buf[buf.length] = rt.apply(rp);
+ }
+ return [lbuf.join(""), buf.join("")];
+ },
+
+ renderBody : function(){
+ var markup = this.renderRows();
+ var bt = this.templates.body;
+ return [bt.apply({rows: markup[0]}), bt.apply({rows: markup[1]})];
+ },
+
+ /**
+ * Refreshes the grid
+ * @param {Boolean} headersToo
+ */
+ refresh : function(headersToo){
+ this.fireEvent("beforerefresh", this);
+ this.grid.stopEditing();
+ var result = this.renderBody();
+ this.lockedBody.update(result[0]);
+ this.mainBody.update(result[1]);
+ if(headersToo === true){
+ this.updateHeaders();
+ this.updateColumns();
+ this.updateSplitters();
+ this.updateHeaderSortState();
+ }
+ this.syncRowHeights();
+ this.layout();
+ this.fireEvent("refresh", this);
+ },
+
+ handleColumnMove : function(cm, oldIndex, newIndex){
+ this.indexMap = null;
+ var s = this.getScrollState();
+ this.refresh(true);
+ this.restoreScroll(s);
+ this.afterMove(newIndex);
+ },
+
+ afterMove : function(colIndex){
+ if(this.enableMoveAnim && Roo.enableFx){
+ this.fly(this.getHeaderCell(colIndex).firstChild).highlight(this.hlColor);
+ }
+ // if multisort - fix sortOrder, and reload..
+ if (this.grid.dataSource.multiSort) {
+ // the we can call sort again..
+ var dm = this.grid.dataSource;
+ var cm = this.grid.colModel;
+ var so = [];
+ for(var i = 0; i < cm.config.length; i++ ) {
+
+ if ((typeof(dm.sortToggle[cm.config[i].dataIndex]) == 'undefined')) {
+ continue; // dont' bother, it's not in sort list or being set.
+ }
+
+ so.push(cm.config[i].dataIndex);
+ };
+ dm.sortOrder = so;
+ dm.load(dm.lastOptions);
+
+
+ }
+
+ },
+
+ updateCell : function(dm, rowIndex, dataIndex){
+ var colIndex = this.getColumnIndexByDataIndex(dataIndex);
+ if(typeof colIndex == "undefined"){ // not present in grid
+ return;
+ }
+ var cm = this.grid.colModel;
+ var cell = this.getCell(rowIndex, colIndex);
+ var cellText = this.getCellText(rowIndex, colIndex);
+
+ var p = {
+ cellId : "x-grid-cell-" + rowIndex + "-" + colIndex,
+ id : cm.getColumnId(colIndex),
+ css: colIndex == cm.getColumnCount()-1 ? "x-grid-col-last" : ""
+ };
+ var renderer = cm.getRenderer(colIndex);
+ var val = renderer(dm.getValueAt(rowIndex, dataIndex), p, rowIndex, colIndex, dm);
+ if(typeof val == "undefined" || val === "") {
+ val = " ";
+ }
+ cellText.innerHTML = val;
+ cell.className = this.cellClass + " " + this.idToCssName(p.cellId) + " " + p.css;
+ this.syncRowHeights(rowIndex, rowIndex);
+ },
+
+ calcColumnWidth : function(colIndex, maxRowsToMeasure){
+ var maxWidth = 0;
+ if(this.grid.autoSizeHeaders){
+ var h = this.getHeaderCellMeasure(colIndex);
+ maxWidth = Math.max(maxWidth, h.scrollWidth);
+ }
+ var tb, index;
+ if(this.cm.isLocked(colIndex)){
+ tb = this.getLockedTable();
+ index = colIndex;
+ }else{
+ tb = this.getBodyTable();
+ index = colIndex - this.cm.getLockedCount();
+ }
+ if(tb && tb.rows){
+ var rows = tb.rows;
+ var stopIndex = Math.min(maxRowsToMeasure || rows.length, rows.length);
+ for(var i = 0; i < stopIndex; i++){
+ var cell = rows[i].childNodes[index].firstChild;
+ maxWidth = Math.max(maxWidth, cell.scrollWidth);
+ }
+ }
+ return maxWidth + /*margin for error in IE*/ 5;
+ },
+ /**
+ * Autofit a column to its content.
+ * @param {Number} colIndex
+ * @param {Boolean} forceMinSize true to force the column to go smaller if possible
+ */
+ autoSizeColumn : function(colIndex, forceMinSize, suppressEvent){
+ if(this.cm.isHidden(colIndex)){
+ return; // can't calc a hidden column
+ }
+ if(forceMinSize){
+ var cid = this.cm.getColumnId(colIndex);
+ this.css.updateRule(this.colSelector +this.idToCssName( cid), "width", this.grid.minColumnWidth + "px");
+ if(this.grid.autoSizeHeaders){
+ this.css.updateRule(this.hdSelector + this.idToCssName(cid), "width", this.grid.minColumnWidth + "px");
+ }
+ }
+ var newWidth = this.calcColumnWidth(colIndex);
+ this.cm.setColumnWidth(colIndex,
+ Math.max(this.grid.minColumnWidth, newWidth), suppressEvent);
+ if(!suppressEvent){
+ this.grid.fireEvent("columnresize", colIndex, newWidth);
+ }
+ },
+
+ /**
+ * Autofits all columns to their content and then expands to fit any extra space in the grid
+ */
+ autoSizeColumns : function(){
+ var cm = this.grid.colModel;
+ var colCount = cm.getColumnCount();
+ for(var i = 0; i < colCount; i++){
+ this.autoSizeColumn(i, true, true);
+ }
+ if(cm.getTotalWidth() < this.scroller.dom.clientWidth){
+ this.fitColumns();
+ }else{
+ this.updateColumns();
+ this.layout();
+ }
+ },
+
+ /**
+ * Autofits all columns to the grid's width proportionate with their current size
+ * @param {Boolean} reserveScrollSpace Reserve space for a scrollbar
+ */
+ fitColumns : function(reserveScrollSpace){
+ var cm = this.grid.colModel;
+ var colCount = cm.getColumnCount();
+ var cols = [];
+ var width = 0;
+ var i, w;
+ for (i = 0; i < colCount; i++){
+ if(!cm.isHidden(i) && !cm.isFixed(i)){
+ w = cm.getColumnWidth(i);
+ cols.push(i);
+ cols.push(w);
+ width += w;
+ }
+ }
+ var avail = Math.min(this.scroller.dom.clientWidth, this.el.getWidth());
+ if(reserveScrollSpace){
+ avail -= 17;
+ }
+ var frac = (avail - cm.getTotalWidth())/width;
+ while (cols.length){
+ w = cols.pop();
+ i = cols.pop();
+ cm.setColumnWidth(i, Math.floor(w + w*frac), true);
+ }
+ this.updateColumns();
+ this.layout();
+ },
+
+ onRowSelect : function(rowIndex){
+ var row = this.getRowComposite(rowIndex);
+ row.addClass("x-grid-row-selected");
+ },
+
+ onRowDeselect : function(rowIndex){
+ var row = this.getRowComposite(rowIndex);
+ row.removeClass("x-grid-row-selected");
+ },
+
+ onCellSelect : function(row, col){
+ var cell = this.getCell(row, col);
+ if(cell){
+ Roo.fly(cell).addClass("x-grid-cell-selected");
+ }
+ },
+
+ onCellDeselect : function(row, col){
+ var cell = this.getCell(row, col);
+ if(cell){
+ Roo.fly(cell).removeClass("x-grid-cell-selected");
+ }
+ },
+
+ updateHeaderSortState : function(){
+
+ // sort state can be single { field: xxx, direction : yyy}
+ // or { xxx=>ASC , yyy : DESC ..... }
+
+ var mstate = {};
+ if (!this.ds.multiSort) {
+ var state = this.ds.getSortState();
+ if(!state){
+ return;
+ }
+ mstate[state.field] = state.direction;
+ // FIXME... - this is not used here.. but might be elsewhere..
+ this.sortState = state;
+
+ } else {
+ mstate = this.ds.sortToggle;
+ }
+ //remove existing sort classes..
+
+ var sc = this.sortClasses;
+ var hds = this.el.select(this.headerSelector).removeClass(sc);
+
+ for(var f in mstate) {
+
+ var sortColumn = this.cm.findColumnIndex(f);
+
+ if(sortColumn != -1){
+ var sortDir = mstate[f];
+ hds.item(sortColumn).addClass(sc[sortDir == "DESC" ? 1 : 0]);
+ }
+ }
+
+
+
+ },
+
+
+ handleHeaderClick : function(g, index,e){
+
+ Roo.log("header click");
+
+ if (Roo.isTouch) {
+ // touch events on header are handled by context
+ this.handleHdCtx(g,index,e);
+ return;
+ }
+
+
+ if(this.headersDisabled){
+ return;
+ }
+ var dm = g.dataSource, cm = g.colModel;
+ if(!cm.isSortable(index)){
+ return;
+ }
+ g.stopEditing();
+
+ if (dm.multiSort) {
+ // update the sortOrder
+ var so = [];
+ for(var i = 0; i < cm.config.length; i++ ) {
+
+ if ((typeof(dm.sortToggle[cm.config[i].dataIndex]) == 'undefined') && (index != i)) {
+ continue; // dont' bother, it's not in sort list or being set.
+ }
+
+ so.push(cm.config[i].dataIndex);
+ };
+ dm.sortOrder = so;
+ }
+
+
+ dm.sort(cm.getDataIndex(index));
+ },
+
+
+ destroy : function(){
+ if(this.colMenu){
+ this.colMenu.removeAll();
+ Roo.menu.MenuMgr.unregister(this.colMenu);
+ this.colMenu.getEl().remove();
+ delete this.colMenu;
+ }
+ if(this.hmenu){
+ this.hmenu.removeAll();
+ Roo.menu.MenuMgr.unregister(this.hmenu);
+ this.hmenu.getEl().remove();
+ delete this.hmenu;
+ }
+ if(this.grid.enableColumnMove){
+ var dds = Roo.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
+ if(dds){
+ for(var dd in dds){
+ if(!dds[dd].config.isTarget && dds[dd].dragElId){
+ var elid = dds[dd].dragElId;
+ dds[dd].unreg();
+ Roo.get(elid).remove();
+ } else if(dds[dd].config.isTarget){
+ dds[dd].proxyTop.remove();
+ dds[dd].proxyBottom.remove();
+ dds[dd].unreg();
+ }
+ if(Roo.dd.DDM.locationCache[dd]){
+ delete Roo.dd.DDM.locationCache[dd];
+ }
+ }
+ delete Roo.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
+ }
+ }
+ Roo.util.CSS.removeStyleSheet(this.idToCssName(this.grid.id) + '-cssrules');
+ this.bind(null, null);
+ Roo.EventManager.removeResizeListener(this.onWindowResize, this);
+ },
+
+ handleLockChange : function(){
+ this.refresh(true);
+ },
+
+ onDenyColumnLock : function(){
+
+ },
+
+ onDenyColumnHide : function(){
+
+ },
+
+ handleHdMenuClick : function(item){
+ var index = this.hdCtxIndex;
+ var cm = this.cm, ds = this.ds;
+ switch(item.id){
+ case "asc":
+ ds.sort(cm.getDataIndex(index), "ASC");
+ break;
+ case "desc":
+ ds.sort(cm.getDataIndex(index), "DESC");
+ break;
+ case "lock":
+ var lc = cm.getLockedCount();
+ if(cm.getColumnCount(true) <= lc+1){
+ this.onDenyColumnLock();
+ return;
+ }
+ if(lc != index){
+ cm.setLocked(index, true, true);
+ cm.moveColumn(index, lc);
+ this.grid.fireEvent("columnmove", index, lc);
+ }else{
+ cm.setLocked(index, true);
+ }
+ break;
+ case "unlock":
+ var lc = cm.getLockedCount();
+ if((lc-1) != index){
+ cm.setLocked(index, false, true);
+ cm.moveColumn(index, lc-1);
+ this.grid.fireEvent("columnmove", index, lc-1);
+ }else{
+ cm.setLocked(index, false);
+ }
+ break;
+ case 'wider': // used to expand cols on touch..
+ case 'narrow':
+ var cw = cm.getColumnWidth(index);
+ cw += (item.id == 'wider' ? 1 : -1) * 50;
+ cw = Math.max(0, cw);
+ cw = Math.min(cw,4000);
+ cm.setColumnWidth(index, cw);
+ break;
+
+ default:
+ index = cm.getIndexById(item.id.substr(4));
+ if(index != -1){
+ if(item.checked && cm.getColumnCount(true) <= 1){
+ this.onDenyColumnHide();
+ return false;
+ }
+ cm.setHidden(index, item.checked);
+ }
+ }
+ return true;
+ },
+
+ beforeColMenuShow : function(){
+ var cm = this.cm, colCount = cm.getColumnCount();
+ this.colMenu.removeAll();
+
+ var items = [];
+ for(var i = 0; i < colCount; i++){
+ 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]));
+ }
+ },
+
+ handleHdCtx : function(g, index, e){
+ e.stopEvent();
+ var hd = this.getHeaderCell(index);
+ this.hdCtxIndex = index;
+ var ms = this.hmenu.items, cm = this.cm;
+ ms.get("asc").setDisabled(!cm.isSortable(index));
+ ms.get("desc").setDisabled(!cm.isSortable(index));
+ if(this.grid.enableColLock !== false){
+ ms.get("lock").setDisabled(cm.isLocked(index));
+ ms.get("unlock").setDisabled(!cm.isLocked(index));
+ }
+ this.hmenu.show(hd, "tl-bl");
+ },
+
+ handleHdOver : function(e){
+ var hd = this.findHeaderCell(e.getTarget());
+ if(hd && !this.headersDisabled){
+ if(this.grid.colModel.isSortable(this.getCellIndex(hd))){
+ this.fly(hd).addClass("x-grid-hd-over");
+ }
+ }
+ },
+
+ handleHdOut : function(e){
+ var hd = this.findHeaderCell(e.getTarget());
+ if(hd){
+ this.fly(hd).removeClass("x-grid-hd-over");
+ }
+ },
+
+ handleSplitDblClick : function(e, t){
+ var i = this.getCellIndex(t);
+ if(this.grid.enableColumnResize !== false && this.cm.isResizable(i) && !this.cm.isFixed(i)){
+ this.autoSizeColumn(i, true);
+ this.layout();
+ }
+ },
+
+ render : function(){
+
+ var cm = this.cm;
+ var colCount = cm.getColumnCount();
+
+ if(this.grid.monitorWindowResize === true){
+ Roo.EventManager.onWindowResize(this.onWindowResize, this, true);
+ }
+ var header = this.renderHeaders();
+ var body = this.templates.body.apply({rows:""});
+ var html = this.templates.master.apply({
+ lockedBody: body,
+ body: body,
+ lockedHeader: header[0],
+ header: header[1]
+ });
+
+ //this.updateColumns();
+
+ this.grid.getGridEl().dom.innerHTML = html;
+
+ this.initElements();
+
+ // a kludge to fix the random scolling effect in webkit
+ this.el.on("scroll", function() {
+ this.el.dom.scrollTop=0; // hopefully not recursive..
+ },this);
+
+ this.scroller.on("scroll", this.handleScroll, this);
+ this.lockedBody.on("mousewheel", this.handleWheel, this);
+ this.mainBody.on("mousewheel", this.handleWheel, this);
+
+ this.mainHd.on("mouseover", this.handleHdOver, this);
+ this.mainHd.on("mouseout", this.handleHdOut, this);
+ this.mainHd.on("dblclick", this.handleSplitDblClick, this,
+ {delegate: "."+this.splitClass});
+
+ this.lockedHd.on("mouseover", this.handleHdOver, this);
+ this.lockedHd.on("mouseout", this.handleHdOut, this);
+ this.lockedHd.on("dblclick", this.handleSplitDblClick, this,
+ {delegate: "."+this.splitClass});
+
+ if(this.grid.enableColumnResize !== false && Roo.grid.SplitDragZone){
+ new Roo.grid.SplitDragZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
+ }
+
+ this.updateSplitters();
+
+ if(this.grid.enableColumnMove && Roo.grid.HeaderDragZone){
+ new Roo.grid.HeaderDragZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
+ new Roo.grid.HeaderDropZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
+ }
+
+ if(this.grid.enableCtxMenu !== false && Roo.menu.Menu){
+ this.hmenu = new Roo.menu.Menu({id: this.grid.id + "-hctx"});
+ this.hmenu.add(
+ {id:"asc", text: this.sortAscText, cls: "xg-hmenu-sort-asc"},
+ {id:"desc", text: this.sortDescText, cls: "xg-hmenu-sort-desc"}
+ );
+ if(this.grid.enableColLock !== false){
+ this.hmenu.add('-',
+ {id:"lock", text: this.lockText, cls: "xg-hmenu-lock"},
+ {id:"unlock", text: this.unlockText, cls: "xg-hmenu-unlock"}
+ );
+ }
+ if (Roo.isTouch) {
+ this.hmenu.add('-',
+ {id:"wider", text: this.columnsWiderText},
+ {id:"narrow", text: this.columnsNarrowText }
+ );
+
+
+ }
+
+ if(this.grid.enableColumnHide !== false){
+
+ this.colMenu = new Roo.menu.Menu({id:this.grid.id + "-hcols-menu"});
+ this.colMenu.on("beforeshow", this.beforeColMenuShow, this);
+ this.colMenu.on("itemclick", this.handleHdMenuClick, this);
+
+ this.hmenu.add('-',
+ {id:"columns", text: this.columnsText, menu: this.colMenu}
+ );
+ }
+ this.hmenu.on("itemclick", this.handleHdMenuClick, this);
+
+ this.grid.on("headercontextmenu", this.handleHdCtx, this);
+ }
+
+ if((this.grid.enableDragDrop || this.grid.enableDrag) && Roo.grid.GridDragZone){
+ this.dd = new Roo.grid.GridDragZone(this.grid, {
+ ddGroup : this.grid.ddGroup || 'GridDD'
+ });
+
+ }
+
+ /*
+ for(var i = 0; i < colCount; i++){
+ if(cm.isHidden(i)){
+ this.hideColumn(i);
+ }
+ if(cm.config[i].align){
+ this.css.updateRule(this.colSelector + i, "textAlign", cm.config[i].align);
+ this.css.updateRule(this.hdSelector + i, "textAlign", cm.config[i].align);
+ }
+ }*/
+
+ this.updateHeaderSortState();
+
+ this.beforeInitialResize();
+ this.layout(true);
+
+ // two part rendering gives faster view to the user
+ this.renderPhase2.defer(1, this);
+ },
+
+ renderPhase2 : function(){
+ // render the rows now
+ this.refresh();
+ if(this.grid.autoSizeColumns){
+ this.autoSizeColumns();
+ }
+ },
+
+ beforeInitialResize : function(){
+
+ },
+
+ onColumnSplitterMoved : function(i, w){
+ this.userResized = true;
+ var cm = this.grid.colModel;
+ cm.setColumnWidth(i, w, true);
+ var cid = cm.getColumnId(i);
+ this.css.updateRule(this.colSelector + this.idToCssName(cid), "width", (w-this.borderWidth) + "px");
+ this.css.updateRule(this.hdSelector + this.idToCssName(cid), "width", (w-this.borderWidth) + "px");
+ this.updateSplitters();
+ this.layout();
+ this.grid.fireEvent("columnresize", i, w);
+ },
+
+ syncRowHeights : function(startIndex, endIndex){
+ if(this.grid.enableRowHeightSync === true && this.cm.getLockedCount() > 0){
+ startIndex = startIndex || 0;
+ var mrows = this.getBodyTable().rows;
+ var lrows = this.getLockedTable().rows;
+ var len = mrows.length-1;
+ endIndex = Math.min(endIndex || len, len);
+ for(var i = startIndex; i <= endIndex; i++){
+ var m = mrows[i], l = lrows[i];
+ var h = Math.max(m.offsetHeight, l.offsetHeight);
+ m.style.height = l.style.height = h + "px";
+ }
+ }
+ },
+
+ layout : function(initialRender, is2ndPass)
+ {
+ var g = this.grid;
+ var auto = g.autoHeight;
+ var scrollOffset = 16;
+ var c = g.getGridEl(), cm = this.cm,
+ expandCol = g.autoExpandColumn,
+ gv = this;
+ //c.beginMeasure();
+
+ if(!c.dom.offsetWidth){ // display:none?
+ if(initialRender){
+ this.lockedWrap.show();
+ this.mainWrap.show();
+ }
+ return;
+ }
+
+ var hasLock = this.cm.isLocked(0);
+
+ var tbh = this.headerPanel.getHeight();
+ var bbh = this.footerPanel.getHeight();
+
+ if(auto){
+ var ch = this.getBodyTable().offsetHeight + tbh + bbh + this.mainHd.getHeight();
+ var newHeight = ch + c.getBorderWidth("tb");
+ if(g.maxHeight){
+ newHeight = Math.min(g.maxHeight, newHeight);
+ }
+ c.setHeight(newHeight);
+ }
+
+ if(g.autoWidth){
+ c.setWidth(cm.getTotalWidth()+c.getBorderWidth('lr'));
+ }
+
+ var s = this.scroller;
+
+ var csize = c.getSize(true);
+
+ this.el.setSize(csize.width, csize.height);
+
+ this.headerPanel.setWidth(csize.width);
+ this.footerPanel.setWidth(csize.width);
+
+ var hdHeight = this.mainHd.getHeight();
+ var vw = csize.width;
+ var vh = csize.height - (tbh + bbh);
+
+ s.setSize(vw, vh);
+
+ var bt = this.getBodyTable();
+
+ if(cm.getLockedCount() == cm.config.length){
+ bt = this.getLockedTable();
+ }
+
+ var ltWidth = hasLock ?
+ Math.max(this.getLockedTable().offsetWidth, this.lockedHd.dom.firstChild.offsetWidth) : 0;
+
+ var scrollHeight = bt.offsetHeight;
+ var scrollWidth = ltWidth + bt.offsetWidth;
+ var vscroll = false, hscroll = false;
+
+ this.scrollSizer.setSize(scrollWidth, scrollHeight+hdHeight);
+
+ var lw = this.lockedWrap, mw = this.mainWrap;
+ var lb = this.lockedBody, mb = this.mainBody;
+
+ setTimeout(function(){
+ var t = s.dom.offsetTop;
+ var w = s.dom.clientWidth,
+ h = s.dom.clientHeight;
+
+ lw.setTop(t);
+ lw.setSize(ltWidth, h);
+
+ mw.setLeftTop(ltWidth, t);
+ mw.setSize(w-ltWidth, h);
+
+ lb.setHeight(h-hdHeight);
+ mb.setHeight(h-hdHeight);
+
+ if(is2ndPass !== true && !gv.userResized && expandCol){
+ // high speed resize without full column calculation
+
+ var ci = cm.getIndexById(expandCol);
+ if (ci < 0) {
+ ci = cm.findColumnIndex(expandCol);
+ }
+ ci = Math.max(0, ci); // make sure it's got at least the first col.
+ var expandId = cm.getColumnId(ci);
+ var tw = cm.getTotalWidth(false);
+ var currentWidth = cm.getColumnWidth(ci);
+ var cw = Math.min(Math.max(((w-tw)+currentWidth-2)-/*scrollbar*/(w <= s.dom.offsetWidth ? 0 : 18), g.autoExpandMin), g.autoExpandMax);
+ if(currentWidth != cw){
+ cm.setColumnWidth(ci, cw, true);
+ gv.css.updateRule(gv.colSelector+gv.idToCssName(expandId), "width", (cw - gv.borderWidth) + "px");
+ gv.css.updateRule(gv.hdSelector+gv.idToCssName(expandId), "width", (cw - gv.borderWidth) + "px");
+ gv.updateSplitters();
+ gv.layout(false, true);
+ }
+ }
+
+ if(initialRender){
+ lw.show();
+ mw.show();
+ }
+ //c.endMeasure();
+ }, 10);
+ },
+
+ onWindowResize : function(){
+ if(!this.grid.monitorWindowResize || this.grid.autoHeight){
+ return;
+ }
+ this.layout();
+ },
+
+ appendFooter : function(parentEl){
+ return null;
+ },
+
+ sortAscText : "Sort Ascending",
+ sortDescText : "Sort Descending",
+ lockText : "Lock Column",
+ unlockText : "Unlock Column",
+ columnsText : "Columns",
+
+ columnsWiderText : "Wider",
+ columnsNarrowText : "Thinner"
+});
+
+
+Roo.grid.GridView.ColumnDragZone = function(grid, hd){
+ Roo.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
+ this.proxy.el.addClass('x-grid3-col-dd');
+};
+
+Roo.extend(Roo.grid.GridView.ColumnDragZone, Roo.grid.HeaderDragZone, {
+ handleMouseDown : function(e){
+
+ },
+
+ callHandleMouseDown : function(e){
+ Roo.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
+ }
+});
+/*
+ * 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">
+ */
+ /**
+ * @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, // ID
+ "gridSplitters" + this.grid.getGridEl().id, // SGROUP
+ { // CONFIG
+ dragElId : Roo.id(this.proxy.dom),
+ resizeFrame:false
+ }
+ );
+
+ this.setHandleElId(Roo.id(hd));
+ if (hd2 !== false) {
+ this.setOuterHandleElId(Roo.id(hd2));
+ }
+
+ this.scroll = false;
+};
+Roo.extend(Roo.grid.SplitDragZone, Roo.dd.DDProxy, {
+ fly: Roo.Element.fly,
+
+ b4StartDrag : function(x, y){
+ this.view.headersDisabled = true;
+ 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.setYConstraint(0, 0);
+ 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);
+ },
+
+
+ handleMouseDown : function(e){
+ ev = Roo.EventObject.setEvent(e);
+ var t = this.fly(ev.getTarget());
+ if(t.hasClass("x-grid-split")){
+ this.cellIndex = this.view.getCellIndex(t.dom);
+ this.split = t.dom;
+ this.cm = this.grid.colModel;
+ if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
+ Roo.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
+ }
+ }
+ },
+
+ endDrag : function(e){
+ this.view.headersDisabled = false;
+ var endX = Math.max(this.minX, Roo.lib.Event.getPageX(e));
+ var diff = endX - this.startPos;
+ //
+ var w = this.cm.getColumnWidth(this.cellIndex);
+ if (!this.view.mainWrap) {
+ w = 0;
+ }
+ this.view.onColumnSplitterMoved(this.cellIndex, w+diff);
+ },
+
+ autoOffset : function(){
+ this.setDelta(0,0);
+ }
+});/*
+ * 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">
+ */
+
+// private
+// This is a support class used internally by the Grid components
+Roo.grid.GridDragZone = function(grid, config){
+ this.view = grid.getView();
+ Roo.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
+ if(this.view.lockedBody){
+ this.setHandleElId(Roo.id(this.view.mainBody.dom));
+ this.setOuterHandleElId(Roo.id(this.view.lockedBody.dom));
+ }
+ this.scroll = false;
+ this.grid = grid;
+ this.ddel = document.createElement('div');
+ this.ddel.className = 'x-grid-dd-wrap';
+};
+
+Roo.extend(Roo.grid.GridDragZone, Roo.dd.DragZone, {
+ ddGroup : "GridDD",
+
+ getDragData : function(e){
+ var t = Roo.lib.Event.getTarget(e);
+ var rowIndex = this.view.findRowIndex(t);
+ var sm = this.grid.selModel;
+
+ //Roo.log(rowIndex);
+
+ if (sm.getSelectedCell) {
+ // cell selection..
+ if (!sm.getSelectedCell()) {
+ return false;
+ }
+ if (rowIndex != sm.getSelectedCell()[0]) {
+ return false;
+ }
+
+ }
+ 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..
+
+
+ //Roo.log([ sm.getSelectedCell() ? sm.getSelectedCell()[0] : 'NO' , rowIndex ]);
+
+ //if(!sm.isSelected(rowIndex) || e.hasModifier()){
+ //
+ //}
+ if (e.hasModifier()){
+ sm.handleMouseDown(e, t); // non modifier buttons are handled by row select.
+ }
+
+ Roo.log("getDragData");
+
+ return {
+ grid: this.grid,
+ ddel: this.ddel,
+ rowIndex: rowIndex,
+ 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();
+ this.proxy.update(this.ddel);
+ // fire start drag?
+ },
+
+ afterRepair : function(){
+ this.dragging = false;
+ },
+
+ getRepairXY : function(e, data){
+ return false;
+ },
+
+ onEndDrag : function(data, e){
+ // fire end drag?
+ },
+
+ onValidDrop : function(dd, e, id){
+ // fire drag drop?
+ this.hideProxy();
+ },
+
+ beforeInvalidDrop : function(e, id){
+
+ }
+});/*
+ * 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">
+ */
+
+
+/**
+ * @class Roo.grid.ColumnModel
+ * @extends Roo.util.Observable
+ * This is the default implementation of a ColumnModel used by the Grid. It defines
+ * the columns in the grid.
+ * <br>Usage:<br>
+ <pre><code>
+ var colModel = new Roo.grid.ColumnModel([
+ {header: "Ticker", width: 60, sortable: true, locked: true},
+ {header: "Company Name", width: 150, sortable: true},
+ {header: "Market Cap.", width: 100, sortable: true},
+ {header: "$ Sales", width: 100, sortable: true, renderer: money},
+ {header: "Employees", width: 100, sortable: true, resizable: false}
+ ]);
+ </code></pre>
+ * <p>
+
+ * The config options listed for this class are options which may appear in each
+ * individual column definition.
+ * <br/>RooJS Fix - column id's are not sequential but use Roo.id() - fixes bugs with layouts.
+ * @constructor
+ * @param {Object} config An Array of column config objects. See this class's
+ * config objects for details.
+*/
+Roo.grid.ColumnModel = function(config){
+ /**
+ * The config passed into the constructor
+ */
+ 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++){
+ this.addColumn(config[i]);
+
+ }
+
+ /**
+ * The width of columns which have no width specified (defaults to 100)
+ * @type Number
+ */
+ this.defaultWidth = 100;
+
+ /**
+ * Default sortable of columns which have no sortable specified (defaults to false)
+ * @type Boolean
+ */
+ this.defaultSortable = false;
+
+ this.addEvents({
+ /**
+ * @event widthchange
+ * Fires when the width of a column changes.
+ * @param {ColumnModel} this
+ * @param {Number} columnIndex The column index
+ * @param {Number} newWidth The new width
+ */
+ "widthchange": true,
+ /**
+ * @event headerchange
+ * Fires when the text of a header changes.
+ * @param {ColumnModel} this
+ * @param {Number} columnIndex The column index
+ * @param {Number} newText The new header text
+ */
+ "headerchange": true,
+ /**
+ * @event hiddenchange
+ * Fires when a column is hidden or "unhidden".
+ * @param {ColumnModel} this
+ * @param {Number} columnIndex The column index
+ * @param {Boolean} hidden true if hidden, false otherwise
+ */
+ "hiddenchange": true,
+ /**
+ * @event columnmoved
+ * Fires when a column is moved.
+ * @param {ColumnModel} this
+ * @param {Number} oldIndex
+ * @param {Number} newIndex
+ */
+ "columnmoved" : true,
+ /**
+ * @event columlockchange
+ * Fires when a column's locked state is changed
+ * @param {ColumnModel} this
+ * @param {Number} colIndex
+ * @param {Boolean} locked true if locked
+ */
+ "columnlockchange" : true
+ });
+ Roo.grid.ColumnModel.superclass.constructor.call(this);
+};
+Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, {
+ /**
+ * @cfg {String} header [required] The header text to display in the Grid view.
+ */
+ /**
+ * @cfg {String} xsHeader Header at Bootsrap Extra Small width (default for all)
+ */
+ /**
+ * @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 The name of the field in the grid's {@link Roo.data.Store}'s
+ * {@link Roo.data.Record} definition from which to draw the column's value. If not
+ * specified, the column's index is used as an index into the Record's data Array.
+ */
+ /**
+ * @cfg {Number} width The initial width in pixels of the column. Using this
+ * instead of {@link Roo.grid.Grid#autoSizeColumns} is more efficient.
+ */
+ /**
+ * @cfg {Boolean} sortable True if sorting is to be allowed on this column.
+ * Defaults to the value of the {@link #defaultSortable} property.
+ * Whether local/remote sorting is used is specified in {@link Roo.data.Store#remoteSort}.
+ */
+ /**
+ * @cfg {Boolean} locked True to lock the column in place while scrolling the Grid. Defaults to false.
+ */
+ /**
+ * @cfg {Boolean} fixed True if the column width cannot be changed. Defaults to false.
+ */
+ /**
+ * @cfg {Boolean} resizable False to disable column resizing. Defaults to true.
+ */
+ /**
+ * @cfg {Boolean} hidden True to hide the column. Defaults to false.
+ */
+ /**
+ * @cfg {Function} renderer A function used to generate HTML markup for a cell
+ * given the cell's data value. See {@link #setRenderer}. If not specified, the
+ * default renderer returns the escaped data value. If an object is returned (bootstrap only)
+ * then it is treated as a Roo Component object instance, and it is rendered after the initial row is rendered
+ */
+ /**
+ * @cfg {Roo.grid.GridEditor} editor For grid editors - returns the grid editor
+ */
+ /**
+ * @cfg {String} align (left|right) Set the CSS text-align property of the column. Defaults to undefined (left).
+ */
+ /**
+ * @cfg {String} valign (top|bottom|middle) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc). Defaults to undefined (middle)
+ */
+ /**
+ * @cfg {String} cursor ( auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|e-resize|n-resize|ne-resize|nw-resize|s-resize|se-resize|sw-resize|w-resize|ew-resize|ns-resize|nesw-resize|nwse-resize|col-resize|row-resize|all-scroll|zoom-in|zoom-out|grab|grabbing)
+ */
+ /**
+ * @cfg {String} tooltip mouse over tooltip text
+ */
+ /**
+ * @cfg {Number} xs can be '0' for hidden at this size (number less than 12)
+ */
+ /**
+ * @cfg {Number} sm can be '0' for hidden at this size (number less than 12)
+ */
+ /**
+ * @cfg {Number} md can be '0' for hidden at this size (number less than 12)
+ */
+ /**
+ * @cfg {Number} lg can be '0' for hidden at this size (number less than 12)
+ */
+ /**
+ * @cfg {Number} xl can be '0' for hidden at this size (number less than 12)
+ */
+ /**
+ * Returns the id of the column at the specified index.
+ * @param {Number} index The column index
+ * @return {String} the id
+ */
+ getColumnId : function(index){
+ return this.config[index].id;
+ },
+
+ /**
+ * Returns the column for a specified id.
+ * @param {String} id The column id
+ * @return {Object} the column
+ */
+ getColumnById : function(id){
+ return this.lookup[id];
+ },
+
+
+ /**
+ * Returns the column Object for a specified dataIndex.
+ * @param {String} dataIndex The column dataIndex
+ * @return {Object|Boolean} the column or false if not found
+ */
+ getColumnByDataIndex: function(dataIndex){
+ var index = this.findColumnIndex(dataIndex);
+ return index > -1 ? this.config[index] : false;
+ },
+
+ /**
+ * Returns the index for a specified column id.
+ * @param {String} id The column id
+ * @return {Number} the index, or -1 if not found
+ */
+ getIndexById : function(id){
+ for(var i = 0, len = this.config.length; i < len; i++){
+ if(this.config[i].id == id){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ /**
+ * Returns the index for a specified column dataIndex.
+ * @param {String} dataIndex The column dataIndex
+ * @return {Number} the index, or -1 if not found
+ */
+
+ findColumnIndex : function(dataIndex){
+ for(var i = 0, len = this.config.length; i < len; i++){
+ if(this.config[i].dataIndex == dataIndex){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ moveColumn : function(oldIndex, newIndex){
+ var c = this.config[oldIndex];
+ this.config.splice(oldIndex, 1);
+ this.config.splice(newIndex, 0, c);
+ this.dataMap = null;
+ this.fireEvent("columnmoved", this, oldIndex, newIndex);
+ },
+
+ isLocked : function(colIndex){
+ return this.config[colIndex].locked === true;
+ },
+
+ setLocked : function(colIndex, value, suppressEvent){
+ if(this.isLocked(colIndex) == value){
+ return;
+ }
+ this.config[colIndex].locked = value;
+ if(!suppressEvent){
+ this.fireEvent("columnlockchange", this, colIndex, value);
+ }
+ },
+
+ getTotalLockedWidth : function(){
+ var totalWidth = 0;
+ for(var i = 0; i < this.config.length; i++){
+ if(this.isLocked(i) && !this.isHidden(i)){
+ this.totalWidth += this.getColumnWidth(i);
+ }
+ }
+ return totalWidth;
+ },
+
+ getLockedCount : function(){
+ for(var i = 0, len = this.config.length; i < len; i++){
+ if(!this.isLocked(i)){
+ return i;
+ }
+ }
+
+ return this.config.length;
+ },
+
+ /**
+ * Returns the number of columns.
+ * @return {Number}
+ */
+ getColumnCount : function(visibleOnly){
+ if(visibleOnly === true){
+ var c = 0;
+ for(var i = 0, len = this.config.length; i < len; i++){
+ if(!this.isHidden(i)){
+ c++;
+ }
+ }
+ return c;
+ }
+ return this.config.length;
+ },
+
+ /**
+ * Returns the column configs that return true by the passed function that is called with (columnConfig, index)
+ * @param {Function} fn
+ * @param {Object} scope (optional)
+ * @return {Array} result
+ */
+ getColumnsBy : function(fn, scope){
+ var r = [];
+ for(var i = 0, len = this.config.length; i < len; i++){
+ var c = this.config[i];
+ if(fn.call(scope||this, c, i) === true){
+ r[r.length] = c;
+ }
+ }
+ return r;
+ },
+
+ /**
+ * Returns true if the specified column is sortable.
+ * @param {Number} col The column index
+ * @return {Boolean}
+ */
+ isSortable : function(col){
+ if(typeof this.config[col].sortable == "undefined"){
+ return this.defaultSortable;
+ }
+ return this.config[col].sortable;
+ },
+
+ /**
+ * Returns the rendering (formatting) function defined for the column.
+ * @param {Number} col The column index.
+ * @return {Function} The function used to render the cell. See {@link #setRenderer}.
+ */
+ getRenderer : function(col){
+ if(!this.config[col].renderer){
+ return Roo.grid.ColumnModel.defaultRenderer;
+ }
+ return this.config[col].renderer;
+ },
+
+ /**
+ * Sets the rendering (formatting) function for a column.
+ * @param {Number} col The column index
+ * @param {Function} fn The function to use to process the cell's raw data
+ * to return HTML markup for the grid view. The render function is called with
+ * the following parameters:<ul>
+ * <li>Data value.</li>
+ * <li>Cell metadata. An object in which you may set the following attributes:<ul>
+ * <li>css A CSS style string to apply to the table cell.</li>
+ * <li>attr An HTML attribute definition string to apply to the data container element <i>within</i> the table cell.</li></ul>
+ * <li>The {@link Roo.data.Record} from which the data was extracted.</li>
+ * <li>Row index</li>
+ * <li>Column index</li>
+ * <li>The {@link Roo.data.Store} object from which the Record was extracted</li></ul>
+ */
+ setRenderer : function(col, fn){
+ this.config[col].renderer = fn;
+ },
+
+ /**
+ * 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, 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;
+
+ },
+
+ /**
+ * Sets the width for a column.
+ * @param {Number} col The column index
+ * @param {Number} width The new width
+ */
+ setColumnWidth : function(col, width, suppressEvent){
+ this.config[col].width = width;
+ this.totalWidth = null;
+ if(!suppressEvent){
+ this.fireEvent("widthchange", this, col, width);
+ }
+ },
+
+ /**
+ * Returns the total width of all columns.
+ * @param {Boolean} includeHidden True to include hidden column widths
+ * @return {Number}
+ */
+ getTotalWidth : function(includeHidden){
+ if(!this.totalWidth){
+ this.totalWidth = 0;
+ for(var i = 0, len = this.config.length; i < len; i++){
+ if(includeHidden || !this.isHidden(i)){
+ this.totalWidth += this.getColumnWidth(i);
+ }
+ }
+ }
+ return this.totalWidth;
+ },
+
+ /**
+ * Returns the header for the specified column.
+ * @param {Number} col The column index
+ * @return {String}
+ */
+ getColumnHeader : function(col){
+ return this.config[col].header;
+ },
+
+ /**
+ * Sets the header for a column.
+ * @param {Number} col The column index
+ * @param {String} header The new header
+ */
+ setColumnHeader : function(col, header){
+ this.config[col].header = header;
+ this.fireEvent("headerchange", this, col, header);
+ },
+
+ /**
+ * Returns the tooltip for the specified column.
+ * @param {Number} col The column index
+ * @return {String}
+ */
+ getColumnTooltip : function(col){
+ return this.config[col].tooltip;
+ },
+ /**
+ * Sets the tooltip for a column.
+ * @param {Number} col The column index
+ * @param {String} tooltip The new tooltip
+ */
+ setColumnTooltip : function(col, tooltip){
+ this.config[col].tooltip = tooltip;
+ },
+
+ /**
+ * Returns the dataIndex for the specified column.
+ * @param {Number} col The column index
+ * @return {Number}
+ */
+ getDataIndex : function(col){
+ return this.config[col].dataIndex;
+ },
+
+ /**
+ * Sets the dataIndex for a column.
+ * @param {Number} col The column index
+ * @param {Number} dataIndex The new dataIndex
+ */
+ setDataIndex : function(col, dataIndex){
+ this.config[col].dataIndex = dataIndex;
+ },
+
+
+
+ /**
+ * Returns true if the cell is editable.
+ * @param {Number} colIndex The column index
+ * @param {Number} rowIndex The row index - this is nto actually used..?
+ * @return {Boolean}
+ */
+ isCellEditable : function(colIndex, rowIndex){
+ return (this.config[colIndex].editable || (typeof this.config[colIndex].editable == "undefined" && this.config[colIndex].editor)) ? true : false;
+ },
+
+ /**
+ * Returns the editor defined for the cell/column.
+ * return false or null to disable editing.
+ * @param {Number} colIndex The column index
+ * @param {Number} rowIndex The row index
+ * @return {Object}
+ */
+ getCellEditor : function(colIndex, rowIndex){
+ return this.config[colIndex].editor;
+ },
+
+ /**
+ * Sets if a column is editable.
+ * @param {Number} col The column index
+ * @param {Boolean} editable True if the column is editable
+ */
+ setEditable : function(col, editable){
+ this.config[col].editable = editable;
+ },
+
+
+ /**
+ * Returns true if the column is hidden.
+ * @param {Number} colIndex The column index
+ * @return {Boolean}
+ */
+ isHidden : function(colIndex){
+ return this.config[colIndex].hidden;
+ },
+
+
+ /**
+ * Returns true if the column width cannot be changed
+ */
+ isFixed : function(colIndex){
+ return this.config[colIndex].fixed;
+ },
+
+ /**
+ * Returns true if the column can be resized
+ * @return {Boolean}
+ */
+ isResizable : function(colIndex){
+ return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
+ },
+ /**
+ * Sets if a column is hidden.
+ * @param {Number} colIndex The column index
+ * @param {Boolean} hidden True if the column is hidden
+ */
+ setHidden : function(colIndex, hidden){
+ this.config[colIndex].hidden = hidden;
+ this.totalWidth = null;
+ this.fireEvent("hiddenchange", this, colIndex, hidden);
+ },
+
+ /**
+ * Sets the editor for a column.
+ * @param {Number} col The column index
+ * @param {Object} editor The editor object
+ */
+ 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)
+{
+ if(typeof value == "object") {
+ return value;
+ }
+ if(typeof value == "string" && value.length < 1){
+ return " ";
+ }
+
+ return String.format("{0}", value);
+};
+
+// Alias for backwards compatibility
+Roo.grid.DefaultColumnModel = Roo.grid.ColumnModel;
+/*
+ * 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">
+ */
+
+/**
+ * @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
+ */
+Roo.grid.AbstractSelectionModel = function(){
+ this.locked = false;
+ Roo.grid.AbstractSelectionModel.superclass.constructor.call(this);
+};
+
+Roo.extend(Roo.grid.AbstractSelectionModel, Roo.util.Observable, {
+ /** @ignore Called by the grid automatically. Do not call directly. */
+ init : function(grid){
+ this.grid = grid;
+ this.initEvents();
+ },
+
+ /**
+ * Locks the selections.
+ */
+ lock : function(){
+ this.locked = true;
+ },
+
+ /**
+ * Unlocks the selections.
+ */
+ unlock : function(){
+ this.locked = false;
+ },
+
+ /**
+ * Returns true if the selections are locked.
+ * @return {Boolean}
+ */
+ isLocked : function(){
+ return this.locked;
+ }
+});/*
+ * 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">
+ */
+/**
+ * @extends Roo.grid.AbstractSelectionModel
+ * @class Roo.grid.RowSelectionModel
+ * The default SelectionModel used by {@link Roo.grid.Grid}.
+ * It supports multiple selections and keyboard selection/navigation.
+ * @constructor
+ * @param {Object} config
+ */
+Roo.grid.RowSelectionModel = function(config){
+ Roo.apply(this, config);
+ this.selections = new Roo.util.MixedCollection(false, function(o){
+ return o.id;
+ });
+
+ this.last = false;
+ this.lastActive = false;
+
+ 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
+ */
+ "rowdeselect" : true
+ });
+ Roo.grid.RowSelectionModel.superclass.constructor.call(this);
+ this.locked = false;
+};
+
+Roo.extend(Roo.grid.RowSelectionModel, Roo.grid.AbstractSelectionModel, {
+ /**
+ * @cfg {Boolean} singleSelect
+ * True to allow selection of only one row at a time (defaults to false)
+ */
+ singleSelect : false,
+
+ // private
+ initEvents : function(){
+
+ if(!this.grid.enableDragDrop && !this.grid.enableDrag){
+ this.grid.on("mousedown", this.handleMouseDown, 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){
+ this.selectPrevious(e.shiftKey);
+ }else if(this.last !== false && this.lastActive !== false){
+ var last = this.last;
+ this.selectRange(this.last, this.lastActive-1);
+ view.focusRow(this.lastActive);
+ if(last !== false){
+ this.last = last;
+ }
+ }else{
+ this.selectFirstRow();
+ }
+ this.fireEvent("afterselectionchange", this);
+ },
+ "down" : function(e){
+ if(!e.shiftKey){
+ this.selectNext(e.shiftKey);
+ }else if(this.last !== false && this.lastActive !== false){
+ var last = this.last;
+ this.selectRange(this.last, this.lastActive+1);
+ view.focusRow(this.lastActive);
+ if(last !== false){
+ this.last = last;
+ }
+ }else{
+ this.selectFirstRow();
+ }
+ this.fireEvent("afterselectionchange", this);
+ },
+ scope: this
+ });
+
+
+ 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.ds, i, v = this.grid.view;
+ var s = this.selections;
+ s.each(function(r){
+ if((i = ds.indexOfId(r.id)) != -1){
+ v.onRowSelect(i);
+ s.add(ds.getAt(i)); // updating the selection relate data
+ }else{
+ s.remove(r);
+ }
+ });
+ },
+
+ // private
+ onRemove : function(v, index, r){
+ this.selections.remove(r);
+ },
+
+ // private
+ onRowUpdated : function(v, index, r){
+ if(this.isSelected(r)){
+ v.onRowSelect(index);
+ }
+ },
+
+ /**
+ * Select records.
+ * @param {Array} records The records to select
+ * @param {Boolean} keepExisting (optional) True to keep existing selections
+ */
+ selectRecords : function(records, keepExisting){
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ var ds = this.grid.ds;
+ for(var i = 0, len = records.length; i < len; i++){
+ this.selectRow(ds.indexOf(records[i]), true);
+ }
+ },
+
+ /**
+ * Gets the number of selected rows.
+ * @return {Number}
+ */
+ getCount : function(){
+ return this.selections.length;
+ },
+
+ /**
+ * Selects the first row in the grid.
+ */
+ selectFirstRow : function(){
+ this.selectRow(0);
+ },
+
+ /**
+ * Select the last row.
+ * @param {Boolean} keepExisting (optional) True to keep existing selections
+ */
+ selectLastRow : function(keepExisting){
+ this.selectRow(this.grid.ds.getCount() - 1, keepExisting);
+ },
+
+ /**
+ * Selects the row immediately following the last selected row.
+ * @param {Boolean} keepExisting (optional) True to keep existing selections
+ */
+ selectNext : function(keepExisting){
+ if(this.last !== false && (this.last+1) < this.grid.ds.getCount()){
+ this.selectRow(this.last+1, keepExisting);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.focusRow(this.last);
+ }
+ },
+
+ /**
+ * Selects the row that precedes the last selected row.
+ * @param {Boolean} keepExisting (optional) True to keep existing selections
+ */
+ selectPrevious : function(keepExisting){
+ if(this.last){
+ this.selectRow(this.last-1, keepExisting);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.focusRow(this.last);
+ }
+ },
+
+ /**
+ * Returns the selected records
+ * @return {Array} Array of selected records
+ */
+ getSelections : function(){
+ return [].concat(this.selections.items);
+ },
+
+ /**
+ * Returns the first selected record.
+ * @return {Record}
+ */
+ getSelected : function(){
+ return this.selections.itemAt(0);
+ },
+
+
+ /**
+ * Clears all selections.
+ */
+ clearSelections : function(fast){
+ if(this.locked) {
+ return;
+ }
+ if(fast !== true){
+ var ds = this.grid.ds;
+ var s = this.selections;
+ s.each(function(r){
+ this.deselectRow(ds.indexOfId(r.id));
+ }, this);
+ s.clear();
+ }else{
+ this.selections.clear();
+ }
+ this.last = false;
+ },
+
+
+ /**
+ * Selects all rows.
+ */
+ selectAll : function(){
+ if(this.locked) {
+ return;
+ }
+ this.selections.clear();
+ for(var i = 0, len = this.grid.ds.getCount(); i < len; i++){
+ this.selectRow(i, true);
+ }
+ },
+
+ /**
+ * Returns True if there is a selection.
+ * @return {Boolean}
+ */
+ hasSelection : function(){
+ return this.selections.length > 0;
+ },
+
+ /**
+ * Returns True if the specified row is selected.
+ * @param {Number/Record} record The record or index of the record to check
+ * @return {Boolean}
+ */
+ isSelected : function(index){
+ var r = typeof index == "number" ? this.grid.ds.getAt(index) : index;
+ return (r && this.selections.key(r.id) ? true : false);
+ },
+
+ /**
+ * Returns True if the specified record id is selected.
+ * @param {String} id The id of record to check
+ * @return {Boolean}
+ */
+ isIdSelected : function(id){
+ return (this.selections.key(id) ? true : false);
+ },
+
+ // private
+ 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.shiftKey && this.last !== false){
+ var last = this.last;
+ this.selectRange(last, rowIndex, e.ctrlKey);
+ this.last = last; // reset the last
+ view.focusRow(rowIndex);
+ }else{
+ var isSelected = this.isSelected(rowIndex);
+ if(e.button !== 0 && isSelected){
+ view.focusRow(rowIndex);
+ }else if(e.ctrlKey && isSelected){
+ this.deselectRow(rowIndex);
+ }else if(!isSelected){
+ this.selectRow(rowIndex, e.button === 0 && (e.ctrlKey || e.shiftKey));
+ view.focusRow(rowIndex);
+ }
+ }
+ this.fireEvent("afterselectionchange", this);
+ },
+ // private
+ handleDragableRowClick : function(grid, rowIndex, e)
+ {
+ if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
+ this.selectRow(rowIndex, false);
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.focusRow(rowIndex);
+ this.fireEvent("afterselectionchange", this);
+ }
+ },
+
+ /**
+ * Selects multiple rows.
+ * @param {Array} rows Array of the indexes of the row to select
+ * @param {Boolean} keepExisting (optional) True to keep existing selections
+ */
+ selectRows : function(rows, keepExisting){
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ for(var i = 0, len = rows.length; i < len; i++){
+ this.selectRow(rows[i], true);
+ }
+ },
+
+ /**
+ * Selects a range of rows. All rows in between startRow and endRow are also selected.
+ * @param {Number} startRow The index of the first row in the range
+ * @param {Number} endRow The index of the last row in the range
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectRange : function(startRow, endRow, keepExisting){
+ if(this.locked) {
+ return;
+ }
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ if(startRow <= endRow){
+ for(var i = startRow; i <= endRow; i++){
+ this.selectRow(i, true);
+ }
+ }else{
+ for(var i = startRow; i >= endRow; i--){
+ this.selectRow(i, true);
+ }
+ }
+ },
+
+ /**
+ * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
+ * @param {Number} startRow The index of the first row in the range
+ * @param {Number} endRow The index of the last row in the range
+ */
+ deselectRange : function(startRow, endRow, preventViewNotify){
+ if(this.locked) {
+ return;
+ }
+ for(var i = startRow; i <= endRow; i++){
+ this.deselectRow(i, preventViewNotify);
+ }
+ },
+
+ /**
+ * Selects a row.
+ * @param {Number} row The index of the row to select
+ * @param {Boolean} keepExisting (optional) True to keep existing selections
+ */
+ selectRow : function(index, keepExisting, preventViewNotify){
+ 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.ds.getAt(index);
+ this.selections.add(r);
+ this.last = this.lastActive = index;
+ if(!preventViewNotify){
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.onRowSelect(index);
+ }
+ this.fireEvent("rowselect", this, index, r);
+ this.fireEvent("selectionchange", this);
+ }
+ },
+
+ /**
+ * Deselects a row.
+ * @param {Number} row The index of the row to deselect
+ */
+ deselectRow : function(index, preventViewNotify){
+ if(this.locked) {
+ return;
+ }
+ if(this.last == index){
+ this.last = false;
+ }
+ if(this.lastActive == index){
+ this.lastActive = false;
+ }
+ var r = this.grid.ds.getAt(index);
+ this.selections.remove(r);
+ if(!preventViewNotify){
+ var view = this.grid.view ? this.grid.view : this.grid;
+ view.onRowDeselect(index);
+ }
+ this.fireEvent("rowdeselect", this, index);
+ this.fireEvent("selectionchange", this);
+ },
+
+ // private
+ restoreLast : function(){
+ if(this._last){
+ this.last = this._last;
+ }
+ },
+
+ // private
+ acceptsNav : function(row, col, cm){
+ return !cm.isHidden(col) && cm.isCellEditable(col, row);
+ },
+
+ // private
+ onEditorKey : function(field, e){
+ var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
+ if(k == e.TAB){
+ e.stopEvent();
+ ed.completeEdit();
+ if(e.shiftKey){
+ newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
+ }else{
+ newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
+ }
+ }else if(k == e.ENTER && !e.ctrlKey){
+ e.stopEvent();
+ ed.completeEdit();
+ if(e.shiftKey){
+ newCell = g.walkCells(ed.row-1, ed.col, -1, this.acceptsNav, this);
+ }else{
+ newCell = g.walkCells(ed.row+1, ed.col, 1, this.acceptsNav, this);
+ }
+ }else if(k == e.ESC){
+ ed.cancelEdit();
+ }
+ if(newCell){
+ g.startEditing(newCell[0], newCell[1]);
+ }
+ }
+});/*
+ * 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">
+ */
+/**
+ * @class Roo.grid.CellSelectionModel
+ * @extends Roo.grid.AbstractSelectionModel
+ * This class provides the basic implementation for cell selection in a grid.
+ * @constructor
+ * @param {Object} config The object containing the configuration of this model.
+ * @cfg {Boolean} enter_is_tab Enter behaves the same as tab. (eg. goes to next cell) default: false
+ */
+Roo.grid.CellSelectionModel = function(config){
+ Roo.apply(this, config);
+
+ this.selection = null;
+
+ this.addEvents({
+ /**
+ * @event beforerowselect
+ * Fires before a cell is selected.
+ * @param {SelectionModel} this
+ * @param {Number} rowIndex The selected row index
+ * @param {Number} colIndex The selected cell index
+ */
+ "beforecellselect" : true,
+ /**
+ * @event cellselect
+ * Fires when a cell is selected.
+ * @param {SelectionModel} this
+ * @param {Number} rowIndex The selected row index
+ * @param {Number} colIndex The selected cell index
+ */
+ "cellselect" : true,
+ /**
+ * @event selectionchange
+ * Fires when the active selection changes.
+ * @param {SelectionModel} this
+ * @param {Object} selection null for no selection or an object (o) with two properties
+ <ul>
+ <li>o.record: the record object for the row the selection is in</li>
+ <li>o.cell: An array of [rowIndex, columnIndex]</li>
+ </ul>
+ */
+ "selectionchange" : true,
+ /**
+ * @event tabend
+ * Fires when the tab (or enter) was pressed on the last editable cell
+ * You can use this to trigger add new row.
+ * @param {SelectionModel} this
+ */
+ "tabend" : true,
+ /**
+ * @event beforeeditnext
+ * Fires before the next editable sell is made active
+ * You can use this to skip to another cell or fire the tabend
+ * if you set cell to false
+ * @param {Object} eventdata object : { cell : [ row, col ] }
+ */
+ "beforeeditnext" : true
+ });
+ Roo.grid.CellSelectionModel.superclass.constructor.call(this);
+};
+
+Roo.extend(Roo.grid.CellSelectionModel, Roo.grid.AbstractSelectionModel, {
+
+ enter_is_tab: false,
+
+ /** @ignore */
+ initEvents : function(){
+ this.grid.on("mousedown", this.handleMouseDown, this);
+ this.grid.getGridEl().on(Roo.isIE ? "keydown" : "keypress", this.handleKeyDown, this);
+ var view = this.grid.view;
+ view.on("refresh", this.onViewChange, this);
+ view.on("rowupdated", this.onRowUpdated, this);
+ view.on("beforerowremoved", this.clearSelections, this);
+ view.on("beforerowsinserted", this.clearSelections, this);
+ if(this.grid.isEditor){
+ this.grid.on("beforeedit", this.beforeEdit, this);
+ }
+ },
+
+ //private
+ beforeEdit : function(e){
+ this.select(e.row, e.column, false, true, e.record);
+ },
+
+ //private
+ onRowUpdated : function(v, index, r){
+ if(this.selection && this.selection.record == r){
+ v.onCellSelect(index, this.selection.cell[1]);
+ }
+ },
+
+ //private
+ onViewChange : function(){
+ this.clearSelections(true);
+ },
+
+ /**
+ * Returns the currently selected cell,.
+ * @return {Array} The selected cell (row, column) or null if none selected.
+ */
+ getSelectedCell : function(){
+ return this.selection ? this.selection.cell : null;
+ },
+
+ /**
+ * Clears all selections.
+ * @param {Boolean} true to prevent the gridview from being notified about the change.
+ */
+ clearSelections : function(preventNotify){
+ var s = this.selection;
+ if(s){
+ if(preventNotify !== true){
+ this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
+ }
+ this.selection = null;
+ this.fireEvent("selectionchange", this, null);
+ }
+ },
+
+ /**
+ * Returns true if there is a selection.
+ * @return {Boolean}
+ */
+ hasSelection : function(){
+ return this.selection ? true : false;
+ },
+
+ /** @ignore */
+ handleMouseDown : function(e, t){
+ var v = this.grid.getView();
+ if(this.isLocked()){
+ return;
+ };
+ var row = v.findRowIndex(t);
+ var cell = v.findCellIndex(t);
+ if(row !== false && cell !== false){
+ this.select(row, cell);
+ }
+ },
+
+ /**
+ * Selects a cell.
+ * @param {Number} rowIndex
+ * @param {Number} collIndex
+ */
+ select : function(rowIndex, colIndex, preventViewNotify, preventFocus, /*internal*/ r){
+ if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){
+ this.clearSelections();
+ r = r || this.grid.dataSource.getAt(rowIndex);
+ this.selection = {
+ record : r,
+ cell : [rowIndex, colIndex]
+ };
+ if(!preventViewNotify){
+ var v = this.grid.getView();
+ v.onCellSelect(rowIndex, colIndex);
+ if(preventFocus !== true){
+ v.focusCell(rowIndex, colIndex);
+ }
+ }
+ this.fireEvent("cellselect", this, rowIndex, colIndex);
+ this.fireEvent("selectionchange", this, this.selection);
+ }
+ },
+
+ //private
+ isSelectable : function(rowIndex, colIndex, cm){
+ return !cm.isHidden(colIndex);
+ },
+
+ /** @ignore */
+ handleKeyDown : function(e){
+ //Roo.log('Cell Sel Model handleKeyDown');
+ if(!e.isNavKeyPress()){
+ return;
+ }
+ var g = this.grid, s = this.selection;
+ if(!s){
+ e.stopEvent();
+ var cell = g.walkCells(0, 0, 1, this.isSelectable, this);
+ if(cell){
+ this.select(cell[0], cell[1]);
+ }
+ return;
+ }
+ var sm = this;
+ var walk = function(row, col, step){
+ return g.walkCells(row, col, step, sm.isSelectable, sm);
+ };
+ var k = e.getKey(), r = s.cell[0], c = s.cell[1];
+ var newCell;
+
+
+
+ switch(k){
+ case e.TAB:
+ // handled by onEditorKey
+ if (g.isEditor && g.editing) {
+ return;
+ }
+ if(e.shiftKey) {
+ newCell = walk(r, c-1, -1);
+ } else {
+ newCell = walk(r, c+1, 1);
+ }
+ break;
+
+ case e.DOWN:
+ newCell = walk(r+1, c, 1);
+ break;
+
+ case e.UP:
+ newCell = walk(r-1, c, -1);
+ break;
+
+ case e.RIGHT:
+ newCell = walk(r, c+1, 1);
+ break;
+
+ case e.LEFT:
+ newCell = walk(r, c-1, -1);
+ break;
+
+ case e.ENTER:
+
+ if(g.isEditor && !g.editing){
+ g.startEditing(r, c);
+ e.stopEvent();
+ return;
+ }
+
+
+ break;
+ };
+ if(newCell){
+ this.select(newCell[0], newCell[1]);
+ e.stopEvent();
+
+ }
+ },
+
+ acceptsNav : function(row, col, cm){
+ return !cm.isHidden(col) && cm.isCellEditable(col, row);
+ },
+ /**
+ * Selects a cell.
+ * @param {Number} field (not used) - as it's normally used as a listener
+ * @param {Number} e - event - fake it by using
+ *
+ * var e = Roo.EventObjectImpl.prototype;
+ * e.keyCode = e.TAB
+ *
+ *
+ */
+ onEditorKey : function(field, e){
+
+ var k = e.getKey(),
+ newCell,
+ g = this.grid,
+ ed = g.activeEditor,
+ forward = false;
+ ///Roo.log('onEditorKey' + k);
+
+
+ if (this.enter_is_tab && k == e.ENTER) {
+ k = e.TAB;
+ }
+
+ if(k == e.TAB){
+ if(e.shiftKey){
+ newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
+ }else{
+ newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
+ forward = true;
+ }
+
+ e.stopEvent();
+
+ } else if(k == e.ENTER && !e.ctrlKey){
+ ed.completeEdit();
+ e.stopEvent();
+ newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
+
+ } else if(k == e.ESC){
+ ed.cancelEdit();
+ }
+
+ if (newCell) {
+ var ecall = { cell : newCell, forward : forward };
+ this.fireEvent('beforeeditnext', ecall );
+ newCell = ecall.cell;
+ forward = ecall.forward;
+ }
+
+ if(newCell){
+ //Roo.log('next cell after edit');
+ g.startEditing.defer(100, g, [newCell[0], newCell[1]]);
+ } else if (forward) {
+ // tabbed past last
+ this.fireEvent.defer(100, this, ['tabend',this]);
+ }
+ }
+});/*
+ * 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">
+ */
+
+/**
+ * @class Roo.grid.EditorGrid
+ * @extends Roo.grid.Grid
+ * Class for creating and editable grid.
+ * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
+ * The container MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} dataSource The data model to bind to
+ * @param {Object} colModel The column model with info about this grid's columns
+ */
+Roo.grid.EditorGrid = function(container, config){
+ Roo.grid.EditorGrid.superclass.constructor.call(this, container, config);
+ this.getGridEl().addClass("xedit-grid");
+
+ if(!this.selModel){
+ this.selModel = new Roo.grid.CellSelectionModel();
+ }
+
+ this.activeEditor = null;
+
+ this.addEvents({
+ /**
+ * @event beforeedit
+ * Fires before cell editing is triggered. The edit event object has the following properties <br />
+ * <ul style="padding:5px;padding-left:16px;">
+ * <li>grid - This grid</li>
+ * <li>record - The record being edited</li>
+ * <li>field - The field name being edited</li>
+ * <li>value - The value for the field being edited.</li>
+ * <li>row - The grid row index</li>
+ * <li>column - The grid column index</li>
+ * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
+ * </ul>
+ * @param {Object} e An edit event (see above for description)
+ */
+ "beforeedit" : true,
+ /**
+ * @event afteredit
+ * Fires after a cell is edited. <br />
+ * <ul style="padding:5px;padding-left:16px;">
+ * <li>grid - This grid</li>
+ * <li>record - The record being edited</li>
+ * <li>field - The field name being edited</li>
+ * <li>value - The value being set</li>
+ * <li>originalValue - The original value for the field, before the edit.</li>
+ * <li>row - The grid row index</li>
+ * <li>column - The grid column index</li>
+ * </ul>
+ * @param {Object} e An edit event (see above for description)
+ */
+ "afteredit" : true,
+ /**
+ * @event validateedit
+ * Fires after a cell is edited, but before the value is set in the record.
+ * You can use this to modify the value being set in the field, Return false
+ * to cancel the change. The edit event object has the following properties <br />
+ * <ul style="padding:5px;padding-left:16px;">
+ * <li>editor - This editor</li>
+ * <li>grid - This grid</li>
+ * <li>record - The record being edited</li>
+ * <li>field - The field name being edited</li>
+ * <li>value - The value being set</li>
+ * <li>originalValue - The original value for the field, before the edit.</li>
+ * <li>row - The grid row index</li>
+ * <li>column - The grid column index</li>
+ * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
+ * </ul>
+ * @param {Object} e An edit event (see above for description)
+ */
+ "validateedit" : true
+ });
+ this.on("bodyscroll", this.stopEditing, this);
+ this.on(this.clicksToEdit == 1 ? "cellclick" : "celldblclick", this.onCellDblClick, this);
+};
+
+Roo.extend(Roo.grid.EditorGrid, Roo.grid.Grid, {
+ /**
+ * @cfg {Number} clicksToEdit
+ * The number of clicks on a cell required to display the cell's editor (defaults to 2)
+ */
+ clicksToEdit: 2,
+
+ // private
+ isEditor : true,
+ // private
+ trackMouseOver: false, // causes very odd FF errors
+
+ onCellDblClick : function(g, row, col){
+ this.startEditing(row, col);
+ },
+
+ onEditComplete : function(ed, value, startValue){
+ this.editing = false;
+ this.activeEditor = null;
+ ed.un("specialkey", this.selModel.onEditorKey, this.selModel);
+ var r = ed.record;
+ var field = this.colModel.getDataIndex(ed.col);
+ var e = {
+ grid: this,
+ record: r,
+ field: field,
+ originalValue: startValue,
+ value: value,
+ row: ed.row,
+ column: ed.col,
+ cancel:false,
+ editor: ed
+ };
+ var cell = Roo.get(this.view.getCell(ed.row,ed.col));
+ cell.show();
+
+ if(String(value) !== String(startValue)){
+
+ if(this.fireEvent("validateedit", e) !== false && !e.cancel){
+ r.set(field, e.value);
+ // if we are dealing with a combo box..
+ // then we also set the 'name' colum to be the displayField
+ if (ed.field.displayField && ed.field.name) {
+ r.set(ed.field.name, ed.field.el.dom.value);
+ }
+
+ delete e.cancel; //?? why!!!
+ this.fireEvent("afteredit", e);
+ }
+ } else {
+ this.fireEvent("afteredit", e); // always fire it!
+ }
+ this.view.focusCell(ed.row, ed.col);
+ },
+
+ /**
+ * Starts editing the specified for the specified row/column
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ */
+ startEditing : function(row, col){
+ this.stopEditing();
+ if(this.colModel.isCellEditable(col, row)){
+ this.view.ensureVisible(row, col, true);
+
+ var r = this.dataSource.getAt(row);
+ var field = this.colModel.getDataIndex(col);
+ var cell = Roo.get(this.view.getCell(row,col));
+ var e = {
+ grid: this,
+ record: r,
+ field: field,
+ value: r.data[field],
+ row: row,
+ column: col,
+ cancel:false
+ };
+ if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
+ this.editing = true;
+ var ed = this.colModel.getCellEditor(col, row);
+
+ if (!ed) {
+ return;
+ }
+ if(!ed.rendered){
+ ed.render(ed.parentEl || document.body);
+ }
+ ed.field.reset();
+
+ cell.hide();
+
+ (function(){ // complex but required for focus issues in safari, ie and opera
+ ed.row = row;
+ ed.col = col;
+ ed.record = r;
+ ed.on("complete", this.onEditComplete, this, {single: true});
+ ed.on("specialkey", this.selModel.onEditorKey, this.selModel);
+ this.activeEditor = ed;
+ var v = r.data[field];
+ ed.startEdit(this.view.getCell(row, col), v);
+ // combo's with 'displayField and name set
+ if (ed.field.displayField && ed.field.name) {
+ ed.field.el.dom.value = r.data[ed.field.name];
+ }
+
+
+ }).defer(50, this);
+ }
+ }
+ },
+
+ /**
+ * Stops any active editing
+ */
+ stopEditing : function(){
+ if(this.activeEditor){
+ this.activeEditor.completeEdit();
+ }
+ this.activeEditor = null;
+ },
+
+ /**
+ * Called to get grid's drag proxy text, by default returns this.ddText.
+ * @return {String}
+ */
+ getDragDropText : function(){
+ var count = this.selModel.getSelectedCell() ? 1 : 0;
+ return String.format(this.ddText, count, count == 1 ? '' : 's');
+ }
+
+});/*
+ * 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">
+ */
+
+// private - not really -- you end up using it !
+// This is a support class used internally by the Grid components
+
+/**
+ * @class Roo.grid.GridEditor
+ * @extends Roo.Editor
+ * Class for creating and editable grid elements.
+ * @param {Object} config any settings (must include field)
+ */
+Roo.grid.GridEditor = function(field, config){
+ if (!config && field.field) {
+ config = field;
+ field = Roo.factory(config.field, Roo.form);
+ }
+ Roo.grid.GridEditor.superclass.constructor.call(this, field, config);
+ field.monitorTab = false;
+};
+
+Roo.extend(Roo.grid.GridEditor, Roo.Editor, {
+
+ /**
+ * @cfg {Roo.form.Field} field Field to wrap (or xtyped)
+ */
+
+ alignment: "tl-tl",
+ autoSize: "width",
+ hideEl : false,
+ cls: "x-small-editor x-grid-editor",
+ shim:false,
+ shadow:"frame"
+});/*
+ * 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">
+ */
+
+
+
+Roo.grid.PropertyRecord = Roo.data.Record.create([
+ {name:'name',type:'string'}, 'value'
+]);
+
+
+Roo.grid.PropertyStore = function(grid, source){
+ this.grid = grid;
+ this.store = new Roo.data.Store({
+ recordType : Roo.grid.PropertyRecord
+ });
+ this.store.on('update', this.onUpdate, this);
+ if(source){
+ this.setSource(source);
+ }
+ Roo.grid.PropertyStore.superclass.constructor.call(this);
+};
+
+
+
+Roo.extend(Roo.grid.PropertyStore, Roo.util.Observable, {
+ setSource : function(o){
+ this.source = o;
+ this.store.removeAll();
+ var data = [];
+ for(var k in o){
+ if(this.isEditableValue(o[k])){
+ data.push(new Roo.grid.PropertyRecord({name: k, value: o[k]}, k));
+ }
+ }
+ this.store.loadRecords({records: data}, {}, true);
+ },
+
+ onUpdate : function(ds, record, type){
+ if(type == Roo.data.Record.EDIT){
+ var v = record.data['value'];
+ var oldValue = record.modified['value'];
+ if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
+ this.source[record.id] = v;
+ record.commit();
+ this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
+ }else{
+ record.reject();
+ }
+ }
+ },
+
+ getProperty : function(row){
+ return this.store.getAt(row);
+ },
+
+ isEditableValue: function(val){
+ if(val && val instanceof Date){
+ return true;
+ }else if(typeof val == 'object' || typeof val == 'function'){
+ return false;
+ }
+ return true;
+ },
+
+ setValue : function(prop, value){
+ this.source[prop] = value;
+ this.store.getById(prop).set('value', value);
+ },
+
+ getSource : function(){
+ return this.source;
+ }
+});
+
+Roo.grid.PropertyColumnModel = function(grid, store){
+ this.grid = grid;
+ var g = Roo.grid;
+ g.PropertyColumnModel.superclass.constructor.call(this, [
+ {header: this.nameText, sortable: true, dataIndex:'name', id: 'name'},
+ {header: this.valueText, resizable:false, dataIndex: 'value', id: 'value'}
+ ]);
+ this.store = store;
+ this.bselect = Roo.DomHelper.append(document.body, {
+ tag: 'select', style:'display:none', cls: 'x-grid-editor', children: [
+ {tag: 'option', value: 'true', html: 'true'},
+ {tag: 'option', value: 'false', html: 'false'}
+ ]
+ });
+ Roo.id(this.bselect);
+ var f = Roo.form;
+ this.editors = {
+ 'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
+ 'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
+ 'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
+ 'int' : new g.GridEditor(new f.NumberField({selectOnFocus:true, allowDecimals:false, style:'text-align:left;'})),
+ 'boolean' : new g.GridEditor(new f.Field({el:this.bselect,selectOnFocus:true}))
+ };
+ this.renderCellDelegate = this.renderCell.createDelegate(this);
+ this.renderPropDelegate = this.renderProp.createDelegate(this);
+};
+
+Roo.extend(Roo.grid.PropertyColumnModel, Roo.grid.ColumnModel, {
+
+
+ nameText : 'Name',
+ valueText : 'Value',
+
+ dateFormat : 'm/j/Y',
+
+
+ renderDate : function(dateVal){
+ return dateVal.dateFormat(this.dateFormat);
+ },
+
+ renderBool : function(bVal){
+ return bVal ? 'true' : 'false';
+ },
+
+ isCellEditable : function(colIndex, rowIndex){
+ return colIndex == 1;
+ },
+
+ getRenderer : function(col){
+ return col == 1 ?
+ this.renderCellDelegate : this.renderPropDelegate;
+ },
+
+ renderProp : function(v){
+ return this.getPropertyName(v);
+ },
+
+ renderCell : function(val){
+ var rv = val;
+ if(val instanceof Date){
+ rv = this.renderDate(val);
+ }else if(typeof val == 'boolean'){
+ rv = this.renderBool(val);
+ }
+ return Roo.util.Format.htmlEncode(rv);
+ },
+
+ getPropertyName : function(name){
+ var pn = this.grid.propertyNames;
+ return pn && pn[name] ? pn[name] : name;
+ },
+
+ getCellEditor : function(colIndex, rowIndex){
+ var p = this.store.getProperty(rowIndex);
+ var n = p.data['name'], val = p.data['value'];
+
+ if(typeof(this.grid.customEditors[n]) == 'string'){
+ return this.editors[this.grid.customEditors[n]];
+ }
+ if(typeof(this.grid.customEditors[n]) != 'undefined'){
+ return this.grid.customEditors[n];
+ }
+ if(val instanceof Date){
+ return this.editors['date'];
+ }else if(typeof val == 'number'){
+ return this.editors['number'];
+ }else if(typeof val == 'boolean'){
+ return this.editors['boolean'];
+ }else{
+ return this.editors['string'];
+ }
+ }
+});
+
+/**
+ * @class Roo.grid.PropertyGrid
+ * @extends Roo.grid.EditorGrid
+ * This class represents the interface of a component based property grid control.
+ * <br><br>Usage:<pre><code>
+ var grid = new Roo.grid.PropertyGrid("my-container-id", {
+
+ });
+ // set any options
+ grid.render();
+ * </code></pre>
+
+ * @constructor
+ * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
+ * The container MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} config A config object that sets properties on this grid.
+ */
+Roo.grid.PropertyGrid = function(container, config){
+ config = config || {};
+ var store = new Roo.grid.PropertyStore(this);
+ this.store = store;
+ var cm = new Roo.grid.PropertyColumnModel(this, store);
+ store.store.sort('name', 'ASC');
+ Roo.grid.PropertyGrid.superclass.constructor.call(this, container, Roo.apply({
+ ds: store.store,
+ cm: cm,
+ enableColLock:false,
+ enableColumnMove:false,
+ stripeRows:false,
+ trackMouseOver: false,
+ clicksToEdit:1
+ }, config));
+ this.getGridEl().addClass('x-props-grid');
+ this.lastEditRow = null;
+ this.on('columnresize', this.onColumnResize, this);
+ this.addEvents({
+ /**
+ * @event beforepropertychange
+ * Fires before a property changes (return false to stop?)
+ * @param {Roo.grid.PropertyGrid} grid property grid? (check could be store)
+ * @param {String} id Record Id
+ * @param {String} newval New Value
+ * @param {String} oldval Old Value
+ */
+ "beforepropertychange": true,
+ /**
+ * @event propertychange
+ * Fires after a property changes
+ * @param {Roo.grid.PropertyGrid} grid property grid? (check could be store)
+ * @param {String} id Record Id
+ * @param {String} newval New Value
+ * @param {String} oldval Old Value
+ */
+ "propertychange": true
+ });
+ this.customEditors = this.customEditors || {};
+};
+Roo.extend(Roo.grid.PropertyGrid, Roo.grid.EditorGrid, {
+
+ /**
+ * @cfg {Object} customEditors map of colnames=> custom editors.
+ * the custom editor can be one of the standard ones (date|string|number|int|boolean), or a
+ * grid editor eg. Roo.grid.GridEditor(new Roo.form.TextArea({selectOnFocus:true})),
+ * false disables editing of the field.
+ */
+
+ /**
+ * @cfg {Object} propertyNames map of property Names to their displayed value
+ */
+
+ render : function(){
+ Roo.grid.PropertyGrid.superclass.render.call(this);
+ this.autoSize.defer(100, this);
+ },
+
+ autoSize : function(){
+ Roo.grid.PropertyGrid.superclass.autoSize.call(this);
+ if(this.view){
+ this.view.fitColumns();
+ }
+ },
+
+ onColumnResize : function(){
+ this.colModel.setColumnWidth(1, this.container.getWidth(true)-this.colModel.getColumnWidth(0));
+ this.autoSize();
+ },
+ /**
+ * Sets the data for the Grid
+ * accepts a Key => Value object of all the elements avaiable.
+ * @param {Object} data to appear in grid.
+ */
+ setSource : function(source){
+ this.store.setSource(source);
+ //this.autoSize();
+ },
+ /**
+ * Gets all the data from the grid.
+ * @return {Object} data data stored in grid
+ */
+ getSource : function(){
+ return this.store.getSource();
+ }
+});/*
+
+ * Licence LGPL
+
+ */
+
+/**
+ * @class Roo.grid.Calendar
+ * @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", {
+ ds: myDataStore,
+ cm: myColModel,
+ selModel: mySelectionModel,
+ autoSizeColumns: true,
+ monitorWindowResize: false,
+ trackMouseOver: true
+ eventstore : real data store..
+ });
+ // set any options
+ grid.render();
+
+ * @constructor
+ * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
+ * The container MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} config A config object that sets properties on this grid.
+ */
+Roo.grid.Calendar = function(container, config){
+ // initialize the container
+ this.container = Roo.get(container);
+ this.container.update("");
+ this.container.setStyle("overflow", "hidden");
+ this.container.addClass('x-grid-container');
+
+ this.id = this.container.id;
+
+ Roo.apply(this, config);
+ // check and correct shorthanded configs
+
+ var rows = [];
+ var d =1;
+ for (var r = 0;r < 6;r++) {
+
+ rows[r]=[];
+ for (var c =0;c < 7;c++) {
+ rows[r][c]= '';
+ }
+ }
+ if (this.eventStore) {
+ this.eventStore= Roo.factory(this.eventStore, Roo.data);
+ this.eventStore.on('load',this.onLoad, this);
+ this.eventStore.on('beforeload',this.clearEvents, this);
+
+ }
+
+ this.dataSource = new Roo.data.Store({
+ proxy: new Roo.data.MemoryProxy(rows),
+ reader: new Roo.data.ArrayReader({}, [
+ 'weekday0', 'weekday1', 'weekday2', 'weekday3', 'weekday4', 'weekday5', 'weekday6' ])
+ });
+
+ this.dataSource.load();
+ this.ds = this.dataSource;
+ this.ds.xmodule = this.xmodule || false;
+
+
+ var cellRender = function(v,x,r)
+ {
+ return String.format(
+ '<div class="fc-day fc-widget-content"><div>' +
+ '<div class="fc-event-container"></div>' +
+ '<div class="fc-day-number">{0}</div>'+
+
+ '<div class="fc-day-content"><div style="position:relative"></div></div>' +
+ '</div></div>', v);
+
+ }
+
+
+ this.colModel = new Roo.grid.ColumnModel( [
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday0',
+ header : 'Sunday',
+ renderer : cellRender
+ },
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday1',
+ header : 'Monday',
+ renderer : cellRender
+ },
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday2',
+ header : 'Tuesday',
+ renderer : cellRender
+ },
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday3',
+ header : 'Wednesday',
+ renderer : cellRender
+ },
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday4',
+ header : 'Thursday',
+ renderer : cellRender
+ },
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday5',
+ header : 'Friday',
+ renderer : cellRender
+ },
+ {
+ xtype: 'ColumnModel',
+ xns: Roo.grid,
+ dataIndex : 'weekday6',
+ header : 'Saturday',
+ renderer : cellRender
+ }
+ ]);
+ this.cm = this.colModel;
+ this.cm.xmodule = this.xmodule || false;
+
+
+
+ //this.selModel = new Roo.grid.CellSelectionModel();
+ //this.sm = this.selModel;
+ //this.selModel.init(this);
+
+
+ if(this.width){
+ this.container.setWidth(this.width);
+ }
+
+ if(this.height){
+ this.container.setHeight(this.height);
+ }
+ /** @private */
+ this.addEvents({
+ // raw events
+ /**
+ * @event click
+ * The raw click event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "click" : true,
+ /**
+ * @event dblclick
+ * The raw dblclick event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "dblclick" : true,
+ /**
+ * @event contextmenu
+ * The raw contextmenu event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "contextmenu" : true,
+ /**
+ * @event mousedown
+ * The raw mousedown event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mousedown" : true,
+ /**
+ * @event mouseup
+ * The raw mouseup event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mouseup" : true,
+ /**
+ * @event mouseover
+ * The raw mouseover event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mouseover" : true,
+ /**
+ * @event mouseout
+ * The raw mouseout event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "mouseout" : true,
+ /**
+ * @event keypress
+ * The raw keypress event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "keypress" : true,
+ /**
+ * @event keydown
+ * The raw keydown event for the entire grid.
+ * @param {Roo.EventObject} e
+ */
+ "keydown" : true,
+
+ // custom events
+
+ /**
+ * @event cellclick
+ * Fires when a cell is clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "cellclick" : true,
+ /**
+ * @event celldblclick
+ * Fires when a cell is double clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "celldblclick" : true,
+ /**
+ * @event rowclick
+ * Fires when a row is clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Roo.EventObject} e
+ */
+ "rowclick" : true,
+ /**
+ * @event rowdblclick
+ * Fires when a row is double clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Roo.EventObject} e
+ */
+ "rowdblclick" : true,
+ /**
+ * @event headerclick
+ * Fires when a header is clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "headerclick" : true,
+ /**
+ * @event headerdblclick
+ * Fires when a header cell is double clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "headerdblclick" : true,
+ /**
+ * @event rowcontextmenu
+ * Fires when a row is right clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Roo.EventObject} e
+ */
+ "rowcontextmenu" : true,
+ /**
+ * @event cellcontextmenu
+ * Fires when a cell is right clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} cellIndex
+ * @param {Roo.EventObject} e
+ */
+ "cellcontextmenu" : true,
+ /**
+ * @event headercontextmenu
+ * Fires when a header is right clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {Roo.EventObject} e
+ */
+ "headercontextmenu" : true,
+ /**
+ * @event bodyscroll
+ * Fires when the body element is scrolled
+ * @param {Number} scrollLeft
+ * @param {Number} scrollTop
+ */
+ "bodyscroll" : true,
+ /**
+ * @event columnresize
+ * Fires when the user resizes a column
+ * @param {Number} columnIndex
+ * @param {Number} newSize
+ */
+ "columnresize" : true,
+ /**
+ * @event columnmove
+ * Fires when the user moves a column
+ * @param {Number} oldIndex
+ * @param {Number} newIndex
+ */
+ "columnmove" : true,
+ /**
+ * @event startdrag
+ * Fires when row(s) start being dragged
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {event} e The raw browser event
+ */
+ "startdrag" : true,
+ /**
+ * @event enddrag
+ * Fires when a drag operation is complete
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {event} e The raw browser event
+ */
+ "enddrag" : true,
+ /**
+ * @event dragdrop
+ * Fires when dragged row(s) are dropped on a valid DD target
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragdrop" : true,
+ /**
+ * @event dragover
+ * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragover" : true,
+ /**
+ * @event dragenter
+ * Fires when the dragged row(s) first cross another DD target while being dragged
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragenter" : true,
+ /**
+ * @event dragout
+ * Fires when the dragged row(s) leave another DD target while being dragged
+ * @param {Grid} this
+ * @param {Roo.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ "dragout" : true,
+ /**
+ * @event rowclass
+ * Fires when a row is rendered, so you can change add a style to it.
+ * @param {GridView} gridview The grid view
+ * @param {Object} rowcfg contains record rowIndex and rowClass - set rowClass to add a style.
+ */
+ 'rowclass' : true,
+
+ /**
+ * @event render
+ * Fires when the grid is rendered
+ * @param {Grid} grid
+ */
+ 'render' : true,
+ /**
+ * @event select
+ * Fires when a date is selected
+ * @param {DatePicker} this
+ * @param {Date} date The selected date
+ */
+ 'select': true,
+ /**
+ * @event monthchange
+ * Fires when the displayed month changes
+ * @param {DatePicker} this
+ * @param {Date} date The selected month
+ */
+ 'monthchange': true,
+ /**
+ * @event evententer
+ * Fires when mouse over an event
+ * @param {Calendar} this
+ * @param {event} Event
+ */
+ 'evententer': true,
+ /**
+ * @event eventleave
+ * Fires when the mouse leaves an
+ * @param {Calendar} this
+ * @param {event}
+ */
+ 'eventleave': true,
+ /**
+ * @event eventclick
+ * Fires when the mouse click an
+ * @param {Calendar} this
+ * @param {event}
+ */
+ 'eventclick': true,
+ /**
+ * @event eventrender
+ * Fires before each cell is rendered, so you can modify the contents, like cls / title / qtip
+ * @param {Calendar} this
+ * @param {data} data to be modified
+ */
+ 'eventrender': true
+
+ });
+
+ Roo.grid.Grid.superclass.constructor.call(this);
+ this.on('render', function() {
+ this.view.el.addClass('x-grid-cal');
+
+ (function() { this.setDate(new Date()); }).defer(100,this); //default today..
+
+ },this);
+
+ if (!Roo.grid.Calendar.style) {
+ Roo.grid.Calendar.style = Roo.util.CSS.createStyleSheet({
+
+
+ '.x-grid-cal .x-grid-col' : {
+ height: 'auto !important',
+ 'vertical-align': 'top'
+ },
+ '.x-grid-cal .fc-event-hori' : {
+ height: '14px'
+ }
+
+
+ }, Roo.id());
+ }
+
+
+
+};
+Roo.extend(Roo.grid.Calendar, Roo.grid.Grid, {
+ /**
+ * @cfg {Store} eventStore The store that loads events.
+ */
+ eventStore : 25,
+
+
+ activeDate : false,
+ startDay : 0,
+ autoWidth : true,
+ monitorWindowResize : false,
+
+
+ resizeColumns : function() {
+ var col = (this.view.el.getWidth() / 7) - 3;
+ // loop through cols, and setWidth
+ for(var i =0 ; i < 7 ; i++){
+ this.cm.setColumnWidth(i, col);
+ }
+ },
+ setDate :function(date) {
+
+ Roo.log('setDate?');
+
+ this.resizeColumns();
+ var vd = this.activeDate;
+ this.activeDate = date;
+// if(vd && this.el){
+// var t = date.getTime();
+// if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
+// Roo.log('using add remove');
+//
+// this.fireEvent('monthchange', this, date);
+//
+// this.cells.removeClass("fc-state-highlight");
+// this.cells.each(function(c){
+// if(c.dateValue == t){
+// c.addClass("fc-state-highlight");
+// setTimeout(function(){
+// try{c.dom.firstChild.focus();}catch(e){}
+// }, 50);
+// return false;
+// }
+// return true;
+// });
+// return;
+// }
+// }
+
+ var days = date.getDaysInMonth();
+
+ var firstOfMonth = date.getFirstDateOfMonth();
+ var startingPos = firstOfMonth.getDay()-this.startDay;
+
+ if(startingPos < this.startDay){
+ startingPos += 7;
+ }
+
+ var pm = date.add(Date.MONTH, -1);
+ var prevStart = pm.getDaysInMonth()-startingPos;
+//
+
+
+ this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
+
+ this.textNodes = this.view.el.query('.x-grid-row .x-grid-col .x-grid-cell-text');
+ //this.cells.addClassOnOver('fc-state-hover');
+
+ var cells = this.cells.elements;
+ var textEls = this.textNodes;
+
+ //Roo.each(cells, function(cell){
+ // cell.removeClass([ 'fc-past', 'fc-other-month', 'fc-future', 'fc-state-highlight', 'fc-state-disabled']);
+ //});
+
+ days += startingPos;
+
+ // convert everything to numbers so it's fast
+ var day = 86400000;
+ var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
+ //Roo.log(d);
+ //Roo.log(pm);
+ //Roo.log(prevStart);
+
+ var today = new Date().clearTime().getTime();
+ var sel = date.clearTime().getTime();
+ var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
+ var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
+ var ddMatch = this.disabledDatesRE;
+ var ddText = this.disabledDatesText;
+ var ddays = this.disabledDays ? this.disabledDays.join("") : false;
+ var ddaysText = this.disabledDaysText;
+ var format = this.format;
+
+ var setCellClass = function(cal, cell){
+
+ //Roo.log('set Cell Class');
+ cell.title = "";
+ var t = d.getTime();
+
+ //Roo.log(d);
+
+
+ cell.dateValue = t;
+ if(t == today){
+ cell.className += " fc-today";
+ cell.className += " fc-state-highlight";
+ cell.title = cal.todayText;
+ }
+ if(t == sel){
+ // disable highlight in other month..
+ cell.className += " fc-state-highlight";
+
+ }
+ // disabling
+ if(t < min) {
+ //cell.className = " fc-state-disabled";
+ cell.title = cal.minText;
+ return;
+ }
+ if(t > max) {
+ //cell.className = " fc-state-disabled";
+ cell.title = cal.maxText;
+ return;
+ }
+ if(ddays){
+ if(ddays.indexOf(d.getDay()) != -1){
+ // cell.title = ddaysText;
+ // cell.className = " fc-state-disabled";
+ }
+ }
+ if(ddMatch && format){
+ var fvalue = d.dateFormat(format);
+ if(ddMatch.test(fvalue)){
+ cell.title = ddText.replace("%0", fvalue);
+ cell.className = " fc-state-disabled";
+ }
+ }
+
+ if (!cell.initialClassName) {
+ cell.initialClassName = cell.dom.className;
+ }
+
+ cell.dom.className = cell.initialClassName + ' ' + cell.className;
+ };
+
+ var i = 0;
+
+ for(; i < startingPos; i++) {
+ cells[i].dayName = (++prevStart);
+ Roo.log(textEls[i]);
+ d.setDate(d.getDate()+1);
+
+ //cells[i].className = "fc-past fc-other-month";
+ setCellClass(this, cells[i]);
+ }
+
+ var intDay = 0;
+
+ for(; i < days; i++){
+ intDay = i - startingPos + 1;
+ cells[i].dayName = (intDay);
+ d.setDate(d.getDate()+1);
+
+ cells[i].className = ''; // "x-date-active";
+ setCellClass(this, cells[i]);
+ }
+ var extraDays = 0;
+
+ for(; i < 42; i++) {
+ //textEls[i].innerHTML = (++extraDays);
+
+ d.setDate(d.getDate()+1);
+ cells[i].dayName = (++extraDays);
+ cells[i].className = "fc-future fc-other-month";
+ setCellClass(this, cells[i]);
+ }
+
+ //this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
+
+ var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
+
+ // this will cause all the cells to mis
+ var rows= [];
+ var i =0;
+ for (var r = 0;r < 6;r++) {
+ for (var c =0;c < 7;c++) {
+ this.ds.getAt(r).set('weekday' + c ,cells[i++].dayName );
+ }
+ }
+
+ this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
+ for(i=0;i<cells.length;i++) {
+
+ this.cells.elements[i].dayName = cells[i].dayName ;
+ this.cells.elements[i].className = cells[i].className;
+ this.cells.elements[i].initialClassName = cells[i].initialClassName ;
+ this.cells.elements[i].title = cells[i].title ;
+ this.cells.elements[i].dateValue = cells[i].dateValue ;
+ }
+
+
+
+
+ //this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
+ //this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
+
+ ////if(totalRows != 6){
+ //this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
+ // this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
+ // }
+
+ this.fireEvent('monthchange', this, date);
+
+
+ },
+ /**
+ * Returns the grid's SelectionModel.
+ * @return {SelectionModel}
+ */
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new Roo.grid.CellSelectionModel();
+ }
+ return this.selModel;
+ },
+
+ load: function() {
+ this.eventStore.load()
+
+
+
+ },
+
+ findCell : function(dt) {
+ dt = dt.clearTime().getTime();
+ var ret = false;
+ this.cells.each(function(c){
+ //Roo.log("check " +c.dateValue + '?=' + dt);
+ if(c.dateValue == dt){
+ ret = c;
+ return false;
+ }
+ return true;
+ });
+
+ return ret;
+ },
+
+ findCells : function(rec) {
+ var s = rec.data.start_dt.clone().clearTime().getTime();
+ // Roo.log(s);
+ var e= rec.data.end_dt.clone().clearTime().getTime();
+ // Roo.log(e);
+ var ret = [];
+ this.cells.each(function(c){
+ ////Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
+
+ if(c.dateValue > e){
+ return ;
+ }
+ if(c.dateValue < s){
+ return ;
+ }
+ ret.push(c);
+ });
+
+ return ret;
+ },
+
+ findBestRow: function(cells)
+ {
+ var ret = 0;
+
+ for (var i =0 ; i < cells.length;i++) {
+ ret = Math.max(cells[i].rows || 0,ret);
+ }
+ return ret;
+
+ },
+
+
+ addItem : function(rec)
+ {
+ // look for vertical location slot in
+ var cells = this.findCells(rec);
+
+ rec.row = this.findBestRow(cells);
+
+ // work out the location.
+
+ var crow = false;
+ var rows = [];
+ for(var i =0; i < cells.length; i++) {
+ if (!crow) {
+ crow = {
+ start : cells[i],
+ end : cells[i]
+ };
+ continue;
+ }
+ if (crow.start.getY() == cells[i].getY()) {
+ // on same row.
+ crow.end = cells[i];
+ continue;
+ }
+ // different row.
+ rows.push(crow);
+ crow = {
+ start: cells[i],
+ end : cells[i]
+ };
+
+ }
+
+ rows.push(crow);
+ rec.els = [];
+ rec.rows = rows;
+ rec.cells = cells;
+ for (var i = 0; i < cells.length;i++) {
+ cells[i].rows = Math.max(cells[i].rows || 0 , rec.row + 1 );
+
+ }
+
+
+ },
+
+ clearEvents: function() {
+
+ if (!this.eventStore.getCount()) {
+ return;
+ }
+ // reset number of rows in cells.
+ Roo.each(this.cells.elements, function(c){
+ c.rows = 0;
+ });
+
+ this.eventStore.each(function(e) {
+ this.clearEvent(e);
+ },this);
+
+ },
+
+ clearEvent : function(ev)
+ {
+ if (ev.els) {
+ Roo.each(ev.els, function(el) {
+ el.un('mouseenter' ,this.onEventEnter, this);
+ el.un('mouseleave' ,this.onEventLeave, this);
+ el.remove();
+ },this);
+ ev.els = [];
+ }
+ },
+
+
+ renderEvent : function(ev,ctr) {
+ if (!ctr) {
+ ctr = this.view.el.select('.fc-event-container',true).first();
+ }
+
+
+ this.clearEvent(ev);
+ //code
+
+
+
+ ev.els = [];
+ var cells = ev.cells;
+ var rows = ev.rows;
+ this.fireEvent('eventrender', this, ev);
+
+ for(var i =0; i < rows.length; i++) {
+
+ cls = '';
+ if (i == 0) {
+ cls += ' fc-event-start';
+ }
+ if ((i+1) == rows.length) {
+ cls += ' fc-event-end';
+ }
+
+ //Roo.log(ev.data);
+ // how many rows should it span..
+ var cg = this.eventTmpl.append(ctr,Roo.apply({
+ fccls : cls
+
+ }, ev.data) , true);
+
+
+ cg.on('mouseenter' ,this.onEventEnter, this, ev);
+ cg.on('mouseleave' ,this.onEventLeave, this, ev);
+ cg.on('click', this.onEventClick, this, ev);
+
+ ev.els.push(cg);
+
+ var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
+ var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
+ //Roo.log(cg);
+
+ cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);
+ cg.setWidth(ebox.right - sbox.x -2);
+ }
+ },
+
+ renderEvents: function()
+ {
+ // first make sure there is enough space..
+
+ if (!this.eventTmpl) {
+ this.eventTmpl = new Roo.Template(
+ '<div class="roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable {fccls} {cls}" style="position: absolute" unselectable="on">' +
+ '<div class="fc-event-inner">' +
+ '<span class="fc-event-time">{time}</span>' +
+ '<span class="fc-event-title" qtip="{qtip}">{title}</span>' +
+ '</div>' +
+ '<div class="ui-resizable-heandle ui-resizable-e"> </div>' +
+ '</div>'
+ );
+
+ }
+
+
+
+ this.cells.each(function(c) {
+ //Roo.log(c.select('.fc-day-content div',true).first());
+ c.select('.fc-day-content div',true).first().setHeight(Math.max(34, (c.rows || 1) * 20));
+ });
+
+ var ctr = this.view.el.select('.fc-event-container',true).first();
+
+ var cls;
+ this.eventStore.each(function(ev){
+
+ this.renderEvent(ev);
+
+
+ }, this);
+ this.view.layout();
+
+ },
+
+ onEventEnter: function (e, el,event,d) {
+ this.fireEvent('evententer', this, el, event);
+ },
+
+ onEventLeave: function (e, el,event,d) {
+ this.fireEvent('eventleave', this, el, event);
+ },
+
+ onEventClick: function (e, el,event,d) {
+ this.fireEvent('eventclick', this, el, event);
+ },
+
+ onMonthChange: function () {
+ this.store.load();
+ },
+
+ onLoad: function () {
+
+ //Roo.log('calendar onload');
+//
+ if(this.eventStore.getCount() > 0){
+
+
+
+ this.eventStore.each(function(d){
+
+
+ // FIXME..
+ var add = d.data;
+ if (typeof(add.end_dt) == 'undefined') {
+ Roo.log("Missing End time in calendar data: ");
+ Roo.log(d);
+ return;
+ }
+ if (typeof(add.start_dt) == 'undefined') {
+ Roo.log("Missing Start time in calendar data: ");
+ Roo.log(d);
+ return;
+ }
+ add.start_dt = typeof(add.start_dt) == 'string' ? Date.parseDate(add.start_dt,'Y-m-d H:i:s') : add.start_dt,
+ add.end_dt = typeof(add.end_dt) == 'string' ? Date.parseDate(add.end_dt,'Y-m-d H:i:s') : add.end_dt,
+ add.id = add.id || d.id;
+ add.title = add.title || '??';
+
+ this.addItem(d);
+
+
+ },this);
+ }
+
+ this.renderEvents();
+ }
+
+
+});
+/*
+ grid : {
+ xtype: 'Grid',
+ xns: Roo.grid,
+ listeners : {
+ render : function ()
+ {
+ _this.grid = this;
+
+ if (!this.view.el.hasClass('course-timesheet')) {
+ this.view.el.addClass('course-timesheet');
+ }
+ if (this.tsStyle) {
+ this.ds.load({});
+ return;
+ }
+ Roo.log('width');
+ Roo.log(_this.grid.view.el.getWidth());
+
+
+ this.tsStyle = Roo.util.CSS.createStyleSheet({
+ '.course-timesheet .x-grid-row' : {
+ height: '80px'
+ },
+ '.x-grid-row td' : {
+ 'vertical-align' : 0
+ },
+ '.course-edit-link' : {
+ 'color' : 'blue',
+ 'text-overflow' : 'ellipsis',
+ 'overflow' : 'hidden',
+ 'white-space' : 'nowrap',
+ 'cursor' : 'pointer'
+ },
+ '.sub-link' : {
+ 'color' : 'green'
+ },
+ '.de-act-sup-link' : {
+ 'color' : 'purple',
+ 'text-decoration' : 'line-through'
+ },
+ '.de-act-link' : {
+ 'color' : 'red',
+ 'text-decoration' : 'line-through'
+ },
+ '.course-timesheet .course-highlight' : {
+ 'border-top-style': 'dashed !important',
+ 'border-bottom-bottom': 'dashed !important'
+ },
+ '.course-timesheet .course-item' : {
+ 'font-family' : 'tahoma, arial, helvetica',
+ 'font-size' : '11px',
+ 'overflow' : 'hidden',
+ 'padding-left' : '10px',
+ 'padding-right' : '10px',
+ 'padding-top' : '10px'
+ }
+
+ }, Roo.id());
+ this.ds.load({});
+ }
+ },
+ autoWidth : true,
+ monitorWindowResize : false,
+ cellrenderer : function(v,x,r)
+ {
+ return v;
+ },
+ sm : {
+ xtype: 'CellSelectionModel',
+ xns: Roo.grid
+ },
+ dataSource : {
+ xtype: 'Store',
+ xns: Roo.data,
+ listeners : {
+ beforeload : function (_self, options)
+ {
+ options.params = options.params || {};
+ options.params._month = _this.monthField.getValue();
+ options.params.limit = 9999;
+ options.params['sort'] = 'when_dt';
+ options.params['dir'] = 'ASC';
+ this.proxy.loadResponse = this.loadResponse;
+ Roo.log("load?");
+ //this.addColumns();
+ },
+ load : function (_self, records, options)
+ {
+ _this.grid.view.el.select('.course-edit-link', true).on('click', function() {
+ // if you click on the translation.. you can edit it...
+ var el = Roo.get(this);
+ var id = el.dom.getAttribute('data-id');
+ var d = el.dom.getAttribute('data-date');
+ var t = el.dom.getAttribute('data-time');
+ //var id = this.child('span').dom.textContent;
+
+ //Roo.log(this);
+ Pman.Dialog.CourseCalendar.show({
+ id : id,
+ when_d : d,
+ when_t : t,
+ productitem_active : id ? 1 : 0
+ }, function() {
+ _this.grid.ds.load({});
+ });
+
+ });
+
+ _this.panel.fireEvent('resize', [ '', '' ]);
+ }
+ },
+ loadResponse : function(o, success, response){
+ // this is overridden on before load..
+
+ Roo.log("our code?");
+ //Roo.log(success);
+ //Roo.log(response)
+ delete this.activeRequest;
+ if(!success){
+ this.fireEvent("loadexception", this, o, response);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+ var result;
+ try {
+ result = o.reader.read(response);
+ }catch(e){
+ Roo.log("load exception?");
+ this.fireEvent("loadexception", this, o, response, e);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+ Roo.log("ready...");
+ // loop through result.records;
+ // and set this.tdate[date] = [] << array of records..
+ _this.tdata = {};
+ Roo.each(result.records, function(r){
+ //Roo.log(r.data);
+ if(typeof(_this.tdata[r.data.when_dt.format('j')]) == 'undefined'){
+ _this.tdata[r.data.when_dt.format('j')] = [];
+ }
+ _this.tdata[r.data.when_dt.format('j')].push(r.data);
+ });
+
+ //Roo.log(_this.tdata);
+
+ result.records = [];
+ result.totalRecords = 6;
+
+ // let's generate some duumy records for the rows.
+ //var st = _this.dateField.getValue();
+
+ // work out monday..
+ //st = st.add(Date.DAY, -1 * st.format('w'));
+
+ var date = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
+
+ var firstOfMonth = date.getFirstDayOfMonth();
+ var days = date.getDaysInMonth();
+ var d = 1;
+ var firstAdded = false;
+ for (var i = 0; i < result.totalRecords ; i++) {
+ //var d= st.add(Date.DAY, i);
+ var row = {};
+ var added = 0;
+ for(var w = 0 ; w < 7 ; w++){
+ if(!firstAdded && firstOfMonth != w){
+ continue;
+ }
+ if(d > days){
+ continue;
+ }
+ firstAdded = true;
+ var dd = (d > 0 && d < 10) ? "0"+d : d;
+ row['weekday'+w] = String.format(
+ '<span style="font-size: 16px;"><b>{0}</b></span>'+
+ '<span class="course-edit-link" style="color:blue;" data-id="0" data-date="{1}"> Add New</span>',
+ d,
+ date.format('Y-m-')+dd
+ );
+ added++;
+ if(typeof(_this.tdata[d]) != 'undefined'){
+ Roo.each(_this.tdata[d], function(r){
+ var is_sub = '';
+ var deactive = '';
+ var id = r.id;
+ var desc = (r.productitem_id_descrip) ? r.productitem_id_descrip : '';
+ if(r.parent_id*1>0){
+ is_sub = (r.productitem_id_visible*1 < 1) ? 'de-act-sup-link' :'sub-link';
+ id = r.parent_id;
+ }
+ if(r.productitem_id_visible*1 < 1 && r.parent_id*1 < 1){
+ deactive = 'de-act-link';
+ }
+
+ row['weekday'+w] += String.format(
+ '<br /><span class="course-edit-link {3} {4}" qtip="{5}" data-id="{0}">{2} - {1}</span>',
+ id, //0
+ r.product_id_name, //1
+ r.when_dt.format('h:ia'), //2
+ is_sub, //3
+ deactive, //4
+ desc // 5
+ );
+ });
+ }
+ d++;
+ }
+
+ // only do this if something added..
+ if(added > 0){
+ result.records.push(_this.grid.dataSource.reader.newRow(row));
+ }
+
+
+ // push it twice. (second one with an hour..
+
+ }
+ //Roo.log(result);
+ this.fireEvent("load", this, o, o.request.arg);
+ o.request.callback.call(o.request.scope, result, o.request.arg, true);
+ },
+ sortInfo : {field: 'when_dt', direction : 'ASC' },
+ proxy : {
+ xtype: 'HttpProxy',
+ xns: Roo.data,
+ method : 'GET',
+ url : baseURL + '/Roo/Shop_course.php'
+ },
+ reader : {
+ xtype: 'JsonReader',
+ xns: Roo.data,
+ id : 'id',
+ fields : [
+ {
+ 'name': 'id',
+ 'type': 'int'
+ },
+ {
+ 'name': 'when_dt',
+ 'type': 'string'
+ },
+ {
+ 'name': 'end_dt',
+ 'type': 'string'
+ },
+ {
+ 'name': 'parent_id',
+ 'type': 'int'
+ },
+ {
+ 'name': 'product_id',
+ 'type': 'int'
+ },
+ {
+ 'name': 'productitem_id',
+ 'type': 'int'
+ },
+ {
+ 'name': 'guid',
+ 'type': 'int'
+ }
+ ]
+ }
+ },
+ toolbar : {
+ xtype: 'Toolbar',
+ xns: Roo,
+ items : [
+ {
+ xtype: 'Button',
+ xns: Roo.Toolbar,
+ listeners : {
+ click : function (_self, e)
+ {
+ var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
+ sd.setMonth(sd.getMonth()-1);
+ _this.monthField.setValue(sd.format('Y-m-d'));
+ _this.grid.ds.load({});
+ }
+ },
+ text : "Back"
+ },
+ {
+ xtype: 'Separator',
+ xns: Roo.Toolbar
+ },
+ {
+ xtype: 'MonthField',
+ xns: Roo.form,
+ listeners : {
+ render : function (_self)
+ {
+ _this.monthField = _self;
+ // _this.monthField.set today
+ },
+ select : function (combo, date)
+ {
+ _this.grid.ds.load({});
+ }
+ },
+ value : (function() { return new Date(); })()
+ },
+ {
+ xtype: 'Separator',
+ xns: Roo.Toolbar
+ },
+ {
+ xtype: 'TextItem',
+ xns: Roo.Toolbar,
+ text : "Blue: in-active, green: in-active sup-event, red: de-active, purple: de-active sup-event"
+ },
+ {
+ xtype: 'Fill',
+ xns: Roo.Toolbar
+ },
+ {
+ xtype: 'Button',
+ xns: Roo.Toolbar,
+ listeners : {
+ click : function (_self, e)
+ {
+ var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
+ sd.setMonth(sd.getMonth()+1);
+ _this.monthField.setValue(sd.format('Y-m-d'));
+ _this.grid.ds.load({});
+ }
+ },
+ text : "Next"
+ }
+ ]
+ },
+
+ }
+ };
+
+ *//*
+ * 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">
+ */
+
+/**
+ * @class Roo.LoadMask
+ * A simple utility class for generically masking elements while loading data. If the element being masked has
+ * an underlying {@link Roo.data.Store}, the masking will be automatically synchronized with the store's loading
+ * process and the mask element will be cached for reuse. For all other elements, this mask will replace the
+ * element's UpdateManager load indicator and will be destroyed after the initial load.
+ * @constructor
+ * Create a new LoadMask
+ * @param {String/HTMLElement/Roo.Element} el The element or DOM node, or its id
+ * @param {Object} config The config object
+ */
+Roo.LoadMask = function(el, config){
+ this.el = Roo.get(el);
+ Roo.apply(this, config);
+ if(this.store){
+ this.store.on('beforeload', this.onBeforeLoad, this);
+ this.store.on('load', this.onLoad, this);
+ this.store.on('loadexception', this.onLoadException, this);
+ this.removeMask = false;
+ }else{
+ var um = this.el.getUpdateManager();
+ um.showLoadIndicator = false; // disable the default indicator
+ um.on('beforeupdate', this.onBeforeLoad, this);
+ um.on('update', this.onLoad, this);
+ um.on('failure', this.onLoad, this);
+ this.removeMask = true;
+ }
+};
+
+Roo.LoadMask.prototype = {
+ /**
+ * @cfg {Boolean} removeMask
+ * 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...')
+ */
+ msg : 'Loading...',
+ /**
+ * @cfg {String} msgCls
+ * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
+ */
+ msgCls : 'x-mask-loading',
+
+ /**
+ * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
+ * @type Boolean
+ */
+ disabled: false,
+
+ /**
+ * Disables the mask to prevent it from being displayed
+ */
+ disable : function(){
+ this.disabled = true;
+ },
+
+ /**
+ * Enables the mask so that it can be displayed
+ */
+ enable : function(){
+ this.disabled = false;
+ },
+
+ onLoadException : function()
+ {
+ Roo.log(arguments);
+
+ if (typeof(arguments[3]) != 'undefined') {
+ Roo.MessageBox.alert("Error loading",arguments[3]);
+ }
+ /*
+ try {
+ if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
+ Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
+ }
+ } catch(e) {
+
+ }
+ */
+
+ (function() { this.el.unmask(this.removeMask); }).defer(50, this);
+ },
+ // private
+ onLoad : function()
+ {
+ (function() { this.el.unmask(this.removeMask); }).defer(50, this);
+ },
+
+ // private
+ onBeforeLoad : function(){
+ if(!this.disabled){
+ (function() { this.el.mask(this.msg, this.msgCls); }).defer(50, this);
+ }
+ },
+
+ // private
+ destroy : function(){
+ if(this.store){
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('loadexception', this.onLoadException, this);
+ }else{
+ var um = this.el.getUpdateManager();
+ um.un('beforeupdate', this.onBeforeLoad, this);
+ um.un('update', this.onLoad, this);
+ um.un('failure', this.onLoad, this);
+ }
+ }
+};/*
+ * 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">
+ */
+
+
+/**
+ * @class Roo.XTemplate
+ * @extends Roo.Template
+ * Provides a template that can have nested templates for loops or conditionals. The syntax is:
+<pre><code>
+var t = new Roo.XTemplate(
+ '<select name="{name}">',
+ '<tpl for="options"><option value="{value:trim}">{text:ellipsis(10)}</option></tpl>',
+ '</select>'
+);
+
+// then append, applying the master template values
+ </code></pre>
+ *
+ * Supported features:
+ *
+ * Tags:
+
+<pre><code>
+ {a_variable} - output encoded.
+ {a_variable.format:("Y-m-d")} - call a method on the variable
+ {a_variable:raw} - unencoded output
+ {a_variable:toFixed(1,2)} - Roo.util.Format."toFixed"
+ {a_variable:this.method_on_template(...)} - call a method on the template object.
+
+</code></pre>
+ * The tpl tag:
+<pre><code>
+ <tpl for="a_variable or condition.."></tpl>
+ <tpl if="a_variable or condition"></tpl>
+ <tpl exec="some javascript"></tpl>
+ <tpl name="named_template"></tpl> (experimental)
+
+ <tpl for="."></tpl> - just iterate the property..
+ <tpl for=".."></tpl> - iterates with the parent (probably the template)
+</code></pre>
+ *
+ */
+Roo.XTemplate = function()
+{
+ Roo.XTemplate.superclass.constructor.apply(this, arguments);
+ if (this.html) {
+ this.compile();
+ }
+};
+
+
+Roo.extend(Roo.XTemplate, Roo.Template, {
+
+ /**
+ * The various sub templates
+ */
+ tpls : false,
+ /**
+ *
+ * basic tag replacing syntax
+ * WORD:WORD()
+ *
+ * // you can fake an object call by doing this
+ * x.t:(test,tesT)
+ *
+ */
+ re : /\{([\w-\.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
+
+ /**
+ * compile the template
+ *
+ * This is not recursive, so I'm not sure how nested templates are really going to be handled..
+ *
+ */
+ compile: function()
+ {
+ var s = this.html;
+
+ s = ['<tpl>', s, '</tpl>'].join('');
+
+ var re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
+ nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
+ ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
+ execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
+ namedRe = /^<tpl\b[^>]*?name="(\w+)"/, // named templates..
+ m,
+ id = 0,
+ tpls = [];
+
+ while(true == !!(m = s.match(re))){
+ var forMatch = m[0].match(nameRe),
+ ifMatch = m[0].match(ifRe),
+ execMatch = m[0].match(execRe),
+ namedMatch = m[0].match(namedRe),
+
+ exp = null,
+ fn = null,
+ exec = null,
+ name = forMatch && forMatch[1] ? forMatch[1] : '';
+
+ if (ifMatch) {
+ // if - puts fn into test..
+ exp = ifMatch && ifMatch[1] ? ifMatch[1] : null;
+ if(exp){
+ fn = new Function('values', 'parent', 'with(values){ return '+(Roo.util.Format.htmlDecode(exp))+'; }');
+ }
+ }
+
+ if (execMatch) {
+ // exec - calls a function... returns empty if true is returned.
+ exp = execMatch && execMatch[1] ? execMatch[1] : null;
+ if(exp){
+ exec = new Function('values', 'parent', 'with(values){ '+(Roo.util.Format.htmlDecode(exp))+'; }');
+ }
+ }
+
+
+ if (name) {
+ // for =
+ switch(name){
+ case '.': name = new Function('values', 'parent', 'with(values){ return values; }'); break;
+ case '..': name = new Function('values', 'parent', 'with(values){ return parent; }'); break;
+ default: name = new Function('values', 'parent', 'with(values){ return '+name+'; }');
+ }
+ }
+ var uid = namedMatch ? namedMatch[1] : id;
+
+
+ tpls.push({
+ id: namedMatch ? namedMatch[1] : id,
+ target: name,
+ exec: exec,
+ test: fn,
+ body: m[1] || ''
+ });
+ if (namedMatch) {
+ s = s.replace(m[0], '');
+ } else {
+ s = s.replace(m[0], '{xtpl'+ id + '}');
+ }
+ ++id;
+ }
+ this.tpls = [];
+ for(var i = tpls.length-1; i >= 0; --i){
+ this.compileTpl(tpls[i]);
+ this.tpls[tpls[i].id] = tpls[i];
+ }
+ this.master = tpls[tpls.length-1];
+ return this;
+ },
+ /**
+ * same as applyTemplate, except it's done to one of the subTemplates
+ * when using named templates, you can do:
+ *
+ * var str = pl.applySubTemplate('your-name', values);
+ *
+ *
+ * @param {Number} id of the template
+ * @param {Object} values to apply to template
+ * @param {Object} parent (normaly the instance of this object)
+ */
+ applySubTemplate : function(id, values, parent)
+ {
+
+
+ var t = this.tpls[id];
+
+
+ try {
+ if(t.test && !t.test.call(this, values, parent)){
+ return '';
+ }
+ } catch(e) {
+ Roo.log("Xtemplate.applySubTemplate 'test': Exception thrown");
+ Roo.log(e.toString());
+ Roo.log(t.test);
+ return ''
+ }
+ try {
+
+ if(t.exec && t.exec.call(this, values, parent)){
+ return '';
+ }
+ } catch(e) {
+ Roo.log("Xtemplate.applySubTemplate 'exec': Exception thrown");
+ Roo.log(e.toString());
+ Roo.log(t.exec);
+ return ''
+ }
+ try {
+ var vs = t.target ? t.target.call(this, values, parent) : values;
+ parent = t.target ? values : parent;
+ if(t.target && vs instanceof Array){
+ var buf = [];
+ for(var i = 0, len = vs.length; i < len; i++){
+ buf[buf.length] = t.compiled.call(this, vs[i], parent);
+ }
+ return buf.join('');
+ }
+ return t.compiled.call(this, vs, parent);
+ } catch (e) {
+ Roo.log("Xtemplate.applySubTemplate : Exception thrown");
+ Roo.log(e.toString());
+ Roo.log(t.compiled);
+ return '';
+ }
+ },
+
+ compileTpl : function(tpl)
+ {
+ var fm = Roo.util.Format;
+ var useF = this.disableFormats !== true;
+ var sep = Roo.isGecko ? "+" : ",";
+ var undef = function(str) {
+ Roo.log("Property not found :" + str);
+ return '';
+ };
+
+ var fn = function(m, name, format, args)
+ {
+ //Roo.log(arguments);
+ args = args ? args.replace(/\\'/g,"'") : args;
+ //["{TEST:(a,b,c)}", "TEST", "", "a,b,c", 0, "{TEST:(a,b,c)}"]
+ if (typeof(format) == 'undefined') {
+ format= 'htmlEncode';
+ }
+ if (format == 'raw' ) {
+ format = false;
+ }
+
+ if(name.substr(0, 4) == 'xtpl'){
+ return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent)'+sep+"'";
+ }
+
+ // build an array of options to determine if value is undefined..
+
+ // basically get 'xxxx.yyyy' then do
+ // (typeof(xxxx) == 'undefined' || typeof(xxx.yyyy) == 'undefined') ?
+ // (function () { Roo.log("Property not found"); return ''; })() :
+ // ......
+
+ var udef_ar = [];
+ var lookfor = '';
+ Roo.each(name.split('.'), function(st) {
+ lookfor += (lookfor.length ? '.': '') + st;
+ udef_ar.push( "(typeof(" + lookfor + ") == 'undefined')" );
+ });
+
+ var udef_st = '((' + udef_ar.join(" || ") +") ? undef('" + name + "') : "; // .. needs )
+
+
+ if(format && useF){
+
+ args = args ? ',' + args : "";
+
+ if(format.substr(0, 5) != "this."){
+ format = "fm." + format + '(';
+ }else{
+ format = 'this.call("'+ format.substr(5) + '", ';
+ args = ", values";
+ }
+
+ return "'"+ sep + udef_st + format + name + args + "))"+sep+"'";
+ }
+
+ if (args.length) {
+ // called with xxyx.yuu:(test,test)
+ // change to ()
+ return "'"+ sep + udef_st + name + '(' + args + "))"+sep+"'";
+ }
+ // raw.. - :raw modifier..
+ return "'"+ sep + udef_st + name + ")"+sep+"'";
+
+ };
+ var body;
+ // branched to use + in gecko and [].join() in others
+ if(Roo.isGecko){
+ body = "tpl.compiled = function(values, parent){ with(values) { return '" +
+ tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
+ "';};};";
+ }else{
+ body = ["tpl.compiled = function(values, parent){ with (values) { return ['"];
+ body.push(tpl.body.replace(/(\r\n|\n)/g,
+ '\\n').replace(/'/g, "\\'").replace(this.re, fn));
+ body.push("'].join('');};};");
+ body = body.join('');
+ }
+
+ Roo.debug && Roo.log(body.replace(/\\n/,'\n'));
+
+ /** eval:var:tpl eval:var:fm eval:var:useF eval:var:undef */
+ eval(body);
+
+ return this;
+ },
+
+ applyTemplate : function(values){
+ return this.master.compiled.call(this, values, {});
+ //var s = this.subs;
+ },
+
+ apply : function(){
+ return this.applyTemplate.apply(this, arguments);
+ }
+
+ });
+
+Roo.XTemplate.from = function(el){
+ el = Roo.getDom(el);
+ return new Roo.XTemplate(el.value || el.innerHTML);
+};Roo.dialog = {};
+/*
+* Licence: LGPL
+*/
+
+/**
+ * @class Roo.dialog.UploadCropbox
+ * @extends Roo.BoxComponent
+ * Dialog UploadCropbox class
+ * @cfg {String} emptyText show when image has been loaded
+ * @cfg {String} rotateNotify show when image too small to rotate
+ * @cfg {Number} errorTimeout default 3000
+ * @cfg {Number} minWidth default 300
+ * @cfg {Number} minHeight default 300
+ * @cfg {Number} outputMaxWidth default 1200
+ * @cfg {Number} windowSize default 300
+ * @cfg {Array} buttons default ['rotateLeft', 'pictureBtn', 'rotateRight']
+ * @cfg {Boolean} isDocument (true|false) default false
+ * @cfg {String} url action url
+ * @cfg {String} paramName default 'imageUpload'
+ * @cfg {String} method default POST
+ * @cfg {Boolean} loadMask (true|false) default true
+ * @cfg {Boolean} loadingText default 'Loading...'
+ *
+ * @constructor
+ * Create a new UploadCropbox
+ * @param {Object} config The config object
+ */
+
+ Roo.dialog.UploadCropbox = function(config){
+ Roo.dialog.UploadCropbox.superclass.constructor.call(this, config);
+
+ this.addEvents({
/**
- * @event cellclick
- * Fires when a cell is clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "cellclick" : true,
- /**
- * @event celldblclick
- * Fires when a cell is double clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "celldblclick" : true,
- /**
- * @event rowclick
- * Fires when a row is clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Roo.EventObject} e
- */
- "rowclick" : true,
- /**
- * @event rowdblclick
- * Fires when a row is double clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Roo.EventObject} e
- */
- "rowdblclick" : true,
- /**
- * @event headerclick
- * Fires when a header is clicked
- * @param {Grid} this
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "headerclick" : true,
- /**
- * @event headerdblclick
- * Fires when a header cell is double clicked
- * @param {Grid} this
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
- */
- "headerdblclick" : true,
- /**
- * @event rowcontextmenu
- * Fires when a row is right clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Roo.EventObject} e
+ * @event beforeselectfile
+ * Fire before select file
+ * @param {Roo.dialog.UploadCropbox} this
*/
- "rowcontextmenu" : true,
+ "beforeselectfile" : true,
/**
- * @event cellcontextmenu
- * Fires when a cell is right clicked
- * @param {Grid} this
- * @param {Number} rowIndex
- * @param {Number} cellIndex
- * @param {Roo.EventObject} e
+ * @event initial
+ * Fire after initEvent
+ * @param {Roo.dialog.UploadCropbox} this
*/
- "cellcontextmenu" : true,
+ "initial" : true,
/**
- * @event headercontextmenu
- * Fires when a header is right clicked
- * @param {Grid} this
- * @param {Number} columnIndex
- * @param {Roo.EventObject} e
+ * @event crop
+ * Fire after initEvent
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {String} data
*/
- "headercontextmenu" : true,
+ "crop" : true,
/**
- * @event bodyscroll
- * Fires when the body element is scrolled
- * @param {Number} scrollLeft
- * @param {Number} scrollTop
+ * @event prepare
+ * Fire when preparing the file data
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {Object} file
*/
- "bodyscroll" : true,
+ "prepare" : true,
/**
- * @event columnresize
- * Fires when the user resizes a column
- * @param {Number} columnIndex
- * @param {Number} newSize
+ * @event exception
+ * Fire when get exception
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {XMLHttpRequest} xhr
*/
- "columnresize" : true,
+ "exception" : true,
/**
- * @event columnmove
- * Fires when the user moves a column
- * @param {Number} oldIndex
- * @param {Number} newIndex
+ * @event beforeloadcanvas
+ * Fire before load the canvas
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {String} src
*/
- "columnmove" : true,
+ "beforeloadcanvas" : true,
/**
- * @event startdrag
- * Fires when row(s) start being dragged
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {event} e The raw browser event
+ * @event trash
+ * Fire when trash image
+ * @param {Roo.dialog.UploadCropbox} this
*/
- "startdrag" : true,
+ "trash" : true,
/**
- * @event enddrag
- * Fires when a drag operation is complete
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {event} e The raw browser event
+ * @event download
+ * Fire when download the image
+ * @param {Roo.dialog.UploadCropbox} this
*/
- "enddrag" : true,
+ "download" : true,
/**
- * @event dragdrop
- * Fires when dragged row(s) are dropped on a valid DD target
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
+ * @event footerbuttonclick
+ * Fire when footerbuttonclick
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {String} type
*/
- "dragdrop" : true,
+ "footerbuttonclick" : true,
/**
- * @event dragover
- * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
+ * @event resize
+ * Fire when resize
+ * @param {Roo.dialog.UploadCropbox} this
*/
- "dragover" : true,
+ "resize" : true,
/**
- * @event dragenter
- * Fires when the dragged row(s) first cross another DD target while being dragged
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
+ * @event rotate
+ * Fire when rotate the image
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {String} pos
*/
- "dragenter" : true,
+ "rotate" : true,
/**
- * @event dragout
- * Fires when the dragged row(s) leave another DD target while being dragged
- * @param {Grid} this
- * @param {Roo.GridDD} dd The drag drop object
- * @param {String} targetId The target drag drop object
- * @param {event} e The raw browser event
+ * @event inspect
+ * Fire when inspect the file
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {Object} file
*/
- "dragout" : true,
+ "inspect" : true,
/**
- * @event rowclass
- * Fires when a row is rendered, so you can change add a style to it.
- * @param {GridView} gridview The grid view
- * @param {Object} rowcfg contains record rowIndex and rowClass - set rowClass to add a style.
+ * @event upload
+ * Fire when xhr upload the file
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {Object} data
*/
- 'rowclass' : true,
-
+ "upload" : true,
/**
- * @event render
- * Fires when the grid is rendered
- * @param {Grid} grid
+ * @event arrange
+ * Fire when arrange the file data
+ * @param {Roo.dialog.UploadCropbox} this
+ * @param {Object} formData
*/
- 'render' : true,
- /**
- * @event select
- * Fires when a date is selected
- * @param {DatePicker} this
- * @param {Date} date The selected date
- */
- 'select': true,
- /**
- * @event monthchange
- * Fires when the displayed month changes
- * @param {DatePicker} this
- * @param {Date} date The selected month
- */
- 'monthchange': true,
- /**
- * @event evententer
- * Fires when mouse over an event
- * @param {Calendar} this
- * @param {event} Event
- */
- 'evententer': true,
+ "arrange" : true,
/**
- * @event eventleave
- * Fires when the mouse leaves an
- * @param {Calendar} this
- * @param {event}
- */
- 'eventleave': true,
- /**
- * @event eventclick
- * Fires when the mouse click an
- * @param {Calendar} this
- * @param {event}
- */
- 'eventclick': true,
- /**
- * @event eventrender
- * Fires before each cell is rendered, so you can modify the contents, like cls / title / qtip
- * @param {Calendar} this
- * @param {data} data to be modified
- */
- 'eventrender': true
-
+ * @event loadcanvas
+ * Fire after load the canvas
+ * @param {Roo.dialog.UploadCropbox}
+ * @param {Object} imgEl
+ */
+ "loadcanvas" : true
});
-
- Roo.grid.Grid.superclass.constructor.call(this);
- this.on('render', function() {
- this.view.el.addClass('x-grid-cal');
-
- (function() { this.setDate(new Date()); }).defer(100,this); //default today..
-
- },this);
-
- if (!Roo.grid.Calendar.style) {
- Roo.grid.Calendar.style = Roo.util.CSS.createStyleSheet({
-
-
- '.x-grid-cal .x-grid-col' : {
- height: 'auto !important',
- 'vertical-align': 'top'
- },
- '.x-grid-cal .fc-event-hori' : {
- height: '14px'
- }
-
-
- }, Roo.id());
- }
-
-
+ this.buttons = this.buttons || Roo.dialog.UploadCropbox.footer.STANDARD;
};
-Roo.extend(Roo.grid.Calendar, Roo.grid.Grid, {
- /**
- * @cfg {Store} eventStore The store that loads events.
- */
- eventStore : 25,
-
-
- activeDate : false,
- startDay : 0,
- autoWidth : true,
- monitorWindowResize : false,
-
-
- resizeColumns : function() {
- var col = (this.view.el.getWidth() / 7) - 3;
- // loop through cols, and setWidth
- for(var i =0 ; i < 7 ; i++){
- this.cm.setColumnWidth(i, col);
- }
- },
- setDate :function(date) {
-
- Roo.log('setDate?');
-
- this.resizeColumns();
- var vd = this.activeDate;
- this.activeDate = date;
-// if(vd && this.el){
-// var t = date.getTime();
-// if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
-// Roo.log('using add remove');
-//
-// this.fireEvent('monthchange', this, date);
-//
-// this.cells.removeClass("fc-state-highlight");
-// this.cells.each(function(c){
-// if(c.dateValue == t){
-// c.addClass("fc-state-highlight");
-// setTimeout(function(){
-// try{c.dom.firstChild.focus();}catch(e){}
-// }, 50);
-// return false;
-// }
-// return true;
-// });
-// return;
-// }
-// }
-
- var days = date.getDaysInMonth();
-
- var firstOfMonth = date.getFirstDateOfMonth();
- var startingPos = firstOfMonth.getDay()-this.startDay;
-
- if(startingPos < this.startDay){
- startingPos += 7;
- }
-
- var pm = date.add(Date.MONTH, -1);
- var prevStart = pm.getDaysInMonth()-startingPos;
-//
-
-
- this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
-
- this.textNodes = this.view.el.query('.x-grid-row .x-grid-col .x-grid-cell-text');
- //this.cells.addClassOnOver('fc-state-hover');
-
- var cells = this.cells.elements;
- var textEls = this.textNodes;
-
- //Roo.each(cells, function(cell){
- // cell.removeClass([ 'fc-past', 'fc-other-month', 'fc-future', 'fc-state-highlight', 'fc-state-disabled']);
- //});
-
- days += startingPos;
-
- // convert everything to numbers so it's fast
- var day = 86400000;
- var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
- //Roo.log(d);
- //Roo.log(pm);
- //Roo.log(prevStart);
-
- var today = new Date().clearTime().getTime();
- var sel = date.clearTime().getTime();
- var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
- var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
- var ddMatch = this.disabledDatesRE;
- var ddText = this.disabledDatesText;
- var ddays = this.disabledDays ? this.disabledDays.join("") : false;
- var ddaysText = this.disabledDaysText;
- var format = this.format;
-
- var setCellClass = function(cal, cell){
-
- //Roo.log('set Cell Class');
- cell.title = "";
- var t = d.getTime();
-
- //Roo.log(d);
-
-
- cell.dateValue = t;
- if(t == today){
- cell.className += " fc-today";
- cell.className += " fc-state-highlight";
- cell.title = cal.todayText;
- }
- if(t == sel){
- // disable highlight in other month..
- cell.className += " fc-state-highlight";
-
- }
- // disabling
- if(t < min) {
- //cell.className = " fc-state-disabled";
- cell.title = cal.minText;
- return;
- }
- if(t > max) {
- //cell.className = " fc-state-disabled";
- cell.title = cal.maxText;
- return;
- }
- if(ddays){
- if(ddays.indexOf(d.getDay()) != -1){
- // cell.title = ddaysText;
- // cell.className = " fc-state-disabled";
- }
- }
- if(ddMatch && format){
- var fvalue = d.dateFormat(format);
- if(ddMatch.test(fvalue)){
- cell.title = ddText.replace("%0", fvalue);
- cell.className = " fc-state-disabled";
- }
- }
-
- if (!cell.initialClassName) {
- cell.initialClassName = cell.dom.className;
- }
-
- cell.dom.className = cell.initialClassName + ' ' + cell.className;
- };
-
- var i = 0;
-
- for(; i < startingPos; i++) {
- cells[i].dayName = (++prevStart);
- Roo.log(textEls[i]);
- d.setDate(d.getDate()+1);
-
- //cells[i].className = "fc-past fc-other-month";
- setCellClass(this, cells[i]);
- }
-
- var intDay = 0;
-
- for(; i < days; i++){
- intDay = i - startingPos + 1;
- cells[i].dayName = (intDay);
- d.setDate(d.getDate()+1);
-
- cells[i].className = ''; // "x-date-active";
- setCellClass(this, cells[i]);
- }
- var extraDays = 0;
-
- for(; i < 42; i++) {
- //textEls[i].innerHTML = (++extraDays);
-
- d.setDate(d.getDate()+1);
- cells[i].dayName = (++extraDays);
- cells[i].className = "fc-future fc-other-month";
- setCellClass(this, cells[i]);
- }
-
- //this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
-
- var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
-
- // this will cause all the cells to mis
- var rows= [];
- var i =0;
- for (var r = 0;r < 6;r++) {
- for (var c =0;c < 7;c++) {
- this.ds.getAt(r).set('weekday' + c ,cells[i++].dayName );
- }
- }
-
- this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
- for(i=0;i<cells.length;i++) {
-
- this.cells.elements[i].dayName = cells[i].dayName ;
- this.cells.elements[i].className = cells[i].className;
- this.cells.elements[i].initialClassName = cells[i].initialClassName ;
- this.cells.elements[i].title = cells[i].title ;
- this.cells.elements[i].dateValue = cells[i].dateValue ;
- }
-
-
-
-
- //this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
- //this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
-
- ////if(totalRows != 6){
- //this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
- // this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
- // }
-
- this.fireEvent('monthchange', this, date);
-
-
- },
- /**
- * Returns the grid's SelectionModel.
- * @return {SelectionModel}
- */
- getSelectionModel : function(){
- if(!this.selModel){
- this.selModel = new Roo.grid.CellSelectionModel();
- }
- return this.selModel;
- },
- load: function() {
- this.eventStore.load()
-
-
-
- },
-
- findCell : function(dt) {
- dt = dt.clearTime().getTime();
- var ret = false;
- this.cells.each(function(c){
- //Roo.log("check " +c.dateValue + '?=' + dt);
- if(c.dateValue == dt){
- ret = c;
- return false;
- }
- return true;
- });
-
- return ret;
- },
-
- findCells : function(rec) {
- var s = rec.data.start_dt.clone().clearTime().getTime();
- // Roo.log(s);
- var e= rec.data.end_dt.clone().clearTime().getTime();
- // Roo.log(e);
- var ret = [];
- this.cells.each(function(c){
- ////Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
-
- if(c.dateValue > e){
- return ;
- }
- if(c.dateValue < s){
- return ;
- }
- ret.push(c);
- });
-
- return ret;
- },
-
- findBestRow: function(cells)
- {
- var ret = 0;
-
- for (var i =0 ; i < cells.length;i++) {
- ret = Math.max(cells[i].rows || 0,ret);
- }
- return ret;
-
- },
-
+Roo.extend(Roo.dialog.UploadCropbox, Roo.Component, {
+
+ emptyText : 'Click to upload image',
+ rotateNotify : 'Image is too small to rotate',
+ errorTimeout : 3000,
+ scale : 0,
+ baseScale : 1,
+ rotate : 0,
+ dragable : false,
+ pinching : false,
+ mouseX : 0,
+ mouseY : 0,
+ cropData : false,
+ minWidth : 300,
+ minHeight : 300,
+ outputMaxWidth : 1200,
+ windowSize : 300,
+ file : false,
+ exif : {},
+ baseRotate : 1,
+ cropType : 'image/jpeg',
+ buttons : false,
+ canvasLoaded : false,
+ isDocument : false,
+ method : 'POST',
+ paramName : 'imageUpload',
+ loadMask : true,
+ loadingText : 'Loading...',
+ maskEl : false,
- addItem : function(rec)
+ getAutoCreate : function()
{
- // look for vertical location slot in
- var cells = this.findCells(rec);
-
- rec.row = this.findBestRow(cells);
-
- // work out the location.
-
- var crow = false;
- var rows = [];
- for(var i =0; i < cells.length; i++) {
- if (!crow) {
- crow = {
- start : cells[i],
- end : cells[i]
- };
- continue;
- }
- if (crow.start.getY() == cells[i].getY()) {
- // on same row.
- crow.end = cells[i];
- continue;
- }
- // different row.
- rows.push(crow);
- crow = {
- start: cells[i],
- end : cells[i]
- };
-
- }
-
- rows.push(crow);
- rec.els = [];
- rec.rows = rows;
- rec.cells = cells;
- for (var i = 0; i < cells.length;i++) {
- cells[i].rows = Math.max(cells[i].rows || 0 , rec.row + 1 );
-
- }
-
-
- },
-
- clearEvents: function() {
-
- if (!this.eventStore.getCount()) {
- return;
- }
- // reset number of rows in cells.
- Roo.each(this.cells.elements, function(c){
- c.rows = 0;
- });
-
- this.eventStore.each(function(e) {
- this.clearEvent(e);
- },this);
+ var cfg = {
+ tag : 'div',
+ cls : 'roo-upload-cropbox',
+ cn : [
+ {
+ tag : 'input',
+ cls : 'roo-upload-cropbox-selector',
+ type : 'file'
+ },
+ {
+ tag : 'div',
+ cls : 'roo-upload-cropbox-body',
+ style : 'cursor:pointer',
+ cn : [
+ {
+ tag : 'div',
+ cls : 'roo-upload-cropbox-preview'
+ },
+ {
+ tag : 'div',
+ cls : 'roo-upload-cropbox-thumb'
+ },
+ {
+ tag : 'div',
+ cls : 'roo-upload-cropbox-empty-notify',
+ html : this.emptyText
+ },
+ {
+ tag : 'div',
+ cls : 'roo-upload-cropbox-error-notify alert alert-danger',
+ html : this.rotateNotify
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'roo-upload-cropbox-footer',
+ cn : {
+ tag : 'div',
+ cls : 'btn-group btn-group-justified roo-upload-cropbox-btn-group',
+ cn : []
+ }
+ }
+ ]
+ };
+ return cfg;
},
- clearEvent : function(ev)
+ onRender : function(ct, position)
{
- if (ev.els) {
- Roo.each(ev.els, function(el) {
- el.un('mouseenter' ,this.onEventEnter, this);
- el.un('mouseleave' ,this.onEventLeave, this);
- el.remove();
- },this);
- ev.els = [];
- }
- },
-
-
- renderEvent : function(ev,ctr) {
- if (!ctr) {
- ctr = this.view.el.select('.fc-event-container',true).first();
+ Roo.dialog.UploadCropbox.superclass.onRender.call(this, ct, position);
+
+ if(this.el){
+ if (this.el.attr('xtype')) {
+ this.el.attr('xtypex', this.el.attr('xtype'));
+ this.el.dom.removeAttribute('xtype');
+
+ this.initEvents();
+ }
}
+ else {
+ var cfg = Roo.apply({}, this.getAutoCreate());
-
- this.clearEvent(ev);
- //code
-
-
-
- ev.els = [];
- var cells = ev.cells;
- var rows = ev.rows;
- this.fireEvent('eventrender', this, ev);
-
- for(var i =0; i < rows.length; i++) {
+ cfg.id = this.id || Roo.id();
- cls = '';
- if (i == 0) {
- cls += ' fc-event-start';
- }
- if ((i+1) == rows.length) {
- cls += ' fc-event-end';
+ if (this.cls) {
+ cfg.cls = (typeof(cfg.cls) == 'undefined' ? this.cls : cfg.cls) + ' ' + this.cls;
}
- //Roo.log(ev.data);
- // how many rows should it span..
- var cg = this.eventTmpl.append(ctr,Roo.apply({
- fccls : cls
-
- }, ev.data) , true);
-
+ if (this.style) { // fixme needs to support more complex style data.
+ cfg.style = (typeof(cfg.style) == 'undefined' ? this.style : cfg.style) + '; ' + this.style;
+ }
- cg.on('mouseenter' ,this.onEventEnter, this, ev);
- cg.on('mouseleave' ,this.onEventLeave, this, ev);
- cg.on('click', this.onEventClick, this, ev);
+ this.el = ct.createChild(cfg, position);
- ev.els.push(cg);
+ this.initEvents();
+ }
+
+ if (this.buttons.length) {
- var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
- var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
- //Roo.log(cg);
-
- cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);
- cg.setWidth(ebox.right - sbox.x -2);
+ Roo.each(this.buttons, function(bb) {
+
+ var btn = this.el.select('.roo-upload-cropbox-footer div.roo-upload-cropbox-btn-group').first().createChild(bb);
+
+ btn.on('click', this.onFooterButtonClick.createDelegate(this, [bb.action], true));
+
+ }, this);
+ }
+
+ if(this.loadMask){
+ this.maskEl = this.el;
}
},
- renderEvents: function()
- {
- // first make sure there is enough space..
+ initEvents : function()
+ {
+ this.urlAPI = (window.createObjectURL && window) ||
+ (window.URL && URL.revokeObjectURL && URL) ||
+ (window.webkitURL && webkitURL);
+
+ this.bodyEl = this.el.select('.roo-upload-cropbox-body', true).first();
+ this.bodyEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
- if (!this.eventTmpl) {
- this.eventTmpl = new Roo.Template(
- '<div class="roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable {fccls} {cls}" style="position: absolute" unselectable="on">' +
- '<div class="fc-event-inner">' +
- '<span class="fc-event-time">{time}</span>' +
- '<span class="fc-event-title" qtip="{qtip}">{title}</span>' +
- '</div>' +
- '<div class="ui-resizable-heandle ui-resizable-e"> </div>' +
- '</div>'
- );
-
- }
-
+ this.selectorEl = this.el.select('.roo-upload-cropbox-selector', true).first();
+ this.selectorEl.hide();
+ this.previewEl = this.el.select('.roo-upload-cropbox-preview', true).first();
+ this.previewEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
- this.cells.each(function(c) {
- //Roo.log(c.select('.fc-day-content div',true).first());
- c.select('.fc-day-content div',true).first().setHeight(Math.max(34, (c.rows || 1) * 20));
- });
+ this.thumbEl = this.el.select('.roo-upload-cropbox-thumb', true).first();
+ this.thumbEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
+ this.thumbEl.hide();
- var ctr = this.view.el.select('.fc-event-container',true).first();
+ this.notifyEl = this.el.select('.roo-upload-cropbox-empty-notify', true).first();
+ this.notifyEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
- var cls;
- this.eventStore.each(function(ev){
-
- this.renderEvent(ev);
-
-
- }, this);
- this.view.layout();
+ this.errorEl = this.el.select('.roo-upload-cropbox-error-notify', true).first();
+ this.errorEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
+ this.errorEl.hide();
+ this.footerEl = this.el.select('.roo-upload-cropbox-footer', true).first();
+ this.footerEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
+ this.footerEl.hide();
+
+ this.setThumbBoxSize();
+
+ this.bind();
+
+ this.resize();
+
+ this.fireEvent('initial', this);
},
-
- onEventEnter: function (e, el,event,d) {
- this.fireEvent('evententer', this, el, event);
- },
-
- onEventLeave: function (e, el,event,d) {
- this.fireEvent('eventleave', this, el, event);
- },
-
- onEventClick: function (e, el,event,d) {
- this.fireEvent('eventclick', this, el, event);
+
+ bind : function()
+ {
+ var _this = this;
+
+ window.addEventListener("resize", function() { _this.resize(); } );
+
+ this.bodyEl.on('click', this.beforeSelectFile, this);
+
+ if(Roo.isTouch){
+ this.bodyEl.on('touchstart', this.onTouchStart, this);
+ this.bodyEl.on('touchmove', this.onTouchMove, this);
+ this.bodyEl.on('touchend', this.onTouchEnd, this);
+ }
+
+ if(!Roo.isTouch){
+ this.bodyEl.on('mousedown', this.onMouseDown, this);
+ this.bodyEl.on('mousemove', this.onMouseMove, this);
+ var mousewheel = (/Firefox/i.test(navigator.userAgent))? 'DOMMouseScroll' : 'mousewheel';
+ this.bodyEl.on(mousewheel, this.onMouseWheel, this);
+ Roo.get(document).on('mouseup', this.onMouseUp, this);
+ }
+
+ this.selectorEl.on('change', this.onFileSelected, this);
},
- onMonthChange: function () {
- this.store.load();
+ reset : function()
+ {
+ this.scale = 0;
+ this.baseScale = 1;
+ this.rotate = 0;
+ this.baseRotate = 1;
+ this.dragable = false;
+ this.pinching = false;
+ this.mouseX = 0;
+ this.mouseY = 0;
+ this.cropData = false;
+ this.notifyEl.dom.innerHTML = this.emptyText;
+
+ // this.selectorEl.dom.value = '';
+
},
- onLoad: function () {
-
- //Roo.log('calendar onload');
-//
- if(this.eventStore.getCount() > 0){
-
-
-
- this.eventStore.each(function(d){
-
-
- // FIXME..
- var add = d.data;
- if (typeof(add.end_dt) == 'undefined') {
- Roo.log("Missing End time in calendar data: ");
- Roo.log(d);
- return;
- }
- if (typeof(add.start_dt) == 'undefined') {
- Roo.log("Missing Start time in calendar data: ");
- Roo.log(d);
- return;
- }
- add.start_dt = typeof(add.start_dt) == 'string' ? Date.parseDate(add.start_dt,'Y-m-d H:i:s') : add.start_dt,
- add.end_dt = typeof(add.end_dt) == 'string' ? Date.parseDate(add.end_dt,'Y-m-d H:i:s') : add.end_dt,
- add.id = add.id || d.id;
- add.title = add.title || '??';
-
- this.addItem(d);
-
-
- },this);
+ resize : function()
+ {
+ if(this.fireEvent('resize', this) != false){
+ this.setThumbBoxPosition();
+ this.setCanvasPosition();
}
-
- this.renderEvents();
- }
+ },
-
-});
-/*
- grid : {
- xtype: 'Grid',
- xns: Roo.grid,
- listeners : {
- render : function ()
- {
- _this.grid = this;
-
- if (!this.view.el.hasClass('course-timesheet')) {
- this.view.el.addClass('course-timesheet');
- }
- if (this.tsStyle) {
- this.ds.load({});
- return;
- }
- Roo.log('width');
- Roo.log(_this.grid.view.el.getWidth());
-
-
- this.tsStyle = Roo.util.CSS.createStyleSheet({
- '.course-timesheet .x-grid-row' : {
- height: '80px'
- },
- '.x-grid-row td' : {
- 'vertical-align' : 0
- },
- '.course-edit-link' : {
- 'color' : 'blue',
- 'text-overflow' : 'ellipsis',
- 'overflow' : 'hidden',
- 'white-space' : 'nowrap',
- 'cursor' : 'pointer'
- },
- '.sub-link' : {
- 'color' : 'green'
- },
- '.de-act-sup-link' : {
- 'color' : 'purple',
- 'text-decoration' : 'line-through'
- },
- '.de-act-link' : {
- 'color' : 'red',
- 'text-decoration' : 'line-through'
- },
- '.course-timesheet .course-highlight' : {
- 'border-top-style': 'dashed !important',
- 'border-bottom-bottom': 'dashed !important'
- },
- '.course-timesheet .course-item' : {
- 'font-family' : 'tahoma, arial, helvetica',
- 'font-size' : '11px',
- 'overflow' : 'hidden',
- 'padding-left' : '10px',
- 'padding-right' : '10px',
- 'padding-top' : '10px'
- }
-
- }, Roo.id());
- this.ds.load({});
- }
- },
- autoWidth : true,
- monitorWindowResize : false,
- cellrenderer : function(v,x,r)
- {
- return v;
- },
- sm : {
- xtype: 'CellSelectionModel',
- xns: Roo.grid
- },
- dataSource : {
- xtype: 'Store',
- xns: Roo.data,
- listeners : {
- beforeload : function (_self, options)
- {
- options.params = options.params || {};
- options.params._month = _this.monthField.getValue();
- options.params.limit = 9999;
- options.params['sort'] = 'when_dt';
- options.params['dir'] = 'ASC';
- this.proxy.loadResponse = this.loadResponse;
- Roo.log("load?");
- //this.addColumns();
- },
- load : function (_self, records, options)
- {
- _this.grid.view.el.select('.course-edit-link', true).on('click', function() {
- // if you click on the translation.. you can edit it...
- var el = Roo.get(this);
- var id = el.dom.getAttribute('data-id');
- var d = el.dom.getAttribute('data-date');
- var t = el.dom.getAttribute('data-time');
- //var id = this.child('span').dom.textContent;
-
- //Roo.log(this);
- Pman.Dialog.CourseCalendar.show({
- id : id,
- when_d : d,
- when_t : t,
- productitem_active : id ? 1 : 0
- }, function() {
- _this.grid.ds.load({});
- });
-
- });
-
- _this.panel.fireEvent('resize', [ '', '' ]);
- }
- },
- loadResponse : function(o, success, response){
- // this is overridden on before load..
-
- Roo.log("our code?");
- //Roo.log(success);
- //Roo.log(response)
- delete this.activeRequest;
- if(!success){
- this.fireEvent("loadexception", this, o, response);
- o.request.callback.call(o.request.scope, null, o.request.arg, false);
- return;
- }
- var result;
- try {
- result = o.reader.read(response);
- }catch(e){
- Roo.log("load exception?");
- this.fireEvent("loadexception", this, o, response, e);
- o.request.callback.call(o.request.scope, null, o.request.arg, false);
- return;
- }
- Roo.log("ready...");
- // loop through result.records;
- // and set this.tdate[date] = [] << array of records..
- _this.tdata = {};
- Roo.each(result.records, function(r){
- //Roo.log(r.data);
- if(typeof(_this.tdata[r.data.when_dt.format('j')]) == 'undefined'){
- _this.tdata[r.data.when_dt.format('j')] = [];
- }
- _this.tdata[r.data.when_dt.format('j')].push(r.data);
- });
-
- //Roo.log(_this.tdata);
-
- result.records = [];
- result.totalRecords = 6;
-
- // let's generate some duumy records for the rows.
- //var st = _this.dateField.getValue();
-
- // work out monday..
- //st = st.add(Date.DAY, -1 * st.format('w'));
-
- var date = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
-
- var firstOfMonth = date.getFirstDayOfMonth();
- var days = date.getDaysInMonth();
- var d = 1;
- var firstAdded = false;
- for (var i = 0; i < result.totalRecords ; i++) {
- //var d= st.add(Date.DAY, i);
- var row = {};
- var added = 0;
- for(var w = 0 ; w < 7 ; w++){
- if(!firstAdded && firstOfMonth != w){
- continue;
- }
- if(d > days){
- continue;
- }
- firstAdded = true;
- var dd = (d > 0 && d < 10) ? "0"+d : d;
- row['weekday'+w] = String.format(
- '<span style="font-size: 16px;"><b>{0}</b></span>'+
- '<span class="course-edit-link" style="color:blue;" data-id="0" data-date="{1}"> Add New</span>',
- d,
- date.format('Y-m-')+dd
- );
- added++;
- if(typeof(_this.tdata[d]) != 'undefined'){
- Roo.each(_this.tdata[d], function(r){
- var is_sub = '';
- var deactive = '';
- var id = r.id;
- var desc = (r.productitem_id_descrip) ? r.productitem_id_descrip : '';
- if(r.parent_id*1>0){
- is_sub = (r.productitem_id_visible*1 < 1) ? 'de-act-sup-link' :'sub-link';
- id = r.parent_id;
- }
- if(r.productitem_id_visible*1 < 1 && r.parent_id*1 < 1){
- deactive = 'de-act-link';
- }
-
- row['weekday'+w] += String.format(
- '<br /><span class="course-edit-link {3} {4}" qtip="{5}" data-id="{0}">{2} - {1}</span>',
- id, //0
- r.product_id_name, //1
- r.when_dt.format('h:ia'), //2
- is_sub, //3
- deactive, //4
- desc // 5
- );
- });
- }
- d++;
- }
-
- // only do this if something added..
- if(added > 0){
- result.records.push(_this.grid.dataSource.reader.newRow(row));
- }
-
-
- // push it twice. (second one with an hour..
-
- }
- //Roo.log(result);
- this.fireEvent("load", this, o, o.request.arg);
- o.request.callback.call(o.request.scope, result, o.request.arg, true);
- },
- sortInfo : {field: 'when_dt', direction : 'ASC' },
- proxy : {
- xtype: 'HttpProxy',
- xns: Roo.data,
- method : 'GET',
- url : baseURL + '/Roo/Shop_course.php'
- },
- reader : {
- xtype: 'JsonReader',
- xns: Roo.data,
- id : 'id',
- fields : [
- {
- 'name': 'id',
- 'type': 'int'
- },
- {
- 'name': 'when_dt',
- 'type': 'string'
- },
- {
- 'name': 'end_dt',
- 'type': 'string'
- },
- {
- 'name': 'parent_id',
- 'type': 'int'
- },
- {
- 'name': 'product_id',
- 'type': 'int'
- },
- {
- 'name': 'productitem_id',
- 'type': 'int'
- },
- {
- 'name': 'guid',
- 'type': 'int'
- }
- ]
- }
- },
- toolbar : {
- xtype: 'Toolbar',
- xns: Roo,
- items : [
- {
- xtype: 'Button',
- xns: Roo.Toolbar,
- listeners : {
- click : function (_self, e)
- {
- var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
- sd.setMonth(sd.getMonth()-1);
- _this.monthField.setValue(sd.format('Y-m-d'));
- _this.grid.ds.load({});
- }
- },
- text : "Back"
- },
- {
- xtype: 'Separator',
- xns: Roo.Toolbar
- },
- {
- xtype: 'MonthField',
- xns: Roo.form,
- listeners : {
- render : function (_self)
- {
- _this.monthField = _self;
- // _this.monthField.set today
- },
- select : function (combo, date)
- {
- _this.grid.ds.load({});
- }
- },
- value : (function() { return new Date(); })()
- },
- {
- xtype: 'Separator',
- xns: Roo.Toolbar
- },
- {
- xtype: 'TextItem',
- xns: Roo.Toolbar,
- text : "Blue: in-active, green: in-active sup-event, red: de-active, purple: de-active sup-event"
- },
- {
- xtype: 'Fill',
- xns: Roo.Toolbar
- },
- {
- xtype: 'Button',
- xns: Roo.Toolbar,
- listeners : {
- click : function (_self, e)
- {
- var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
- sd.setMonth(sd.getMonth()+1);
- _this.monthField.setValue(sd.format('Y-m-d'));
- _this.grid.ds.load({});
- }
- },
- text : "Next"
- }
- ]
- },
-
+ onFooterButtonClick : function(e, el, o, type)
+ {
+ switch (type) {
+ case 'rotate-left' :
+ this.onRotateLeft(e);
+ break;
+ case 'rotate-right' :
+ this.onRotateRight(e);
+ break;
+ case 'picture' :
+ this.beforeSelectFile(e);
+ break;
+ case 'trash' :
+ this.trash(e);
+ break;
+ case 'crop' :
+ this.crop(e);
+ break;
+ case 'download' :
+ this.download(e);
+ break;
+ case 'center' :
+ this.center(e);
+ break;
+ default :
+ break;
+ }
+
+ this.fireEvent('footerbuttonclick', this, type);
+ },
+
+ beforeSelectFile : function(e)
+ {
+ e.preventDefault();
+
+ if(this.fireEvent('beforeselectfile', this) != false){
+ this.selectorEl.dom.click();
+ }
+ },
+
+ onFileSelected : function(e)
+ {
+ e.preventDefault();
+
+ if(typeof(this.selectorEl.dom.files) == 'undefined' || !this.selectorEl.dom.files.length){
+ return;
+ }
+
+ var file = this.selectorEl.dom.files[0];
+
+ if(this.fireEvent('inspect', this, file) != false){
+ this.prepare(file);
+ }
+
+ },
+
+ trash : function(e)
+ {
+ this.fireEvent('trash', this);
+ },
+
+ download : function(e)
+ {
+ this.fireEvent('download', this);
+ },
+
+ center : function(e)
+ {
+ this.setCanvasPosition();
+ },
+
+ loadCanvas : function(src)
+ {
+ if(this.fireEvent('beforeloadcanvas', this, src) != false){
+
+ this.reset();
+
+ this.imageEl = document.createElement('img');
+
+ var _this = this;
+
+ this.imageEl.addEventListener("load", function(){ _this.onLoadCanvas(); });
+
+ this.imageEl.src = src;
+ }
+ },
+
+ onLoadCanvas : function()
+ {
+ this.imageEl.OriginWidth = this.imageEl.naturalWidth || this.imageEl.width;
+ this.imageEl.OriginHeight = this.imageEl.naturalHeight || this.imageEl.height;
+
+ if(this.fireEvent('loadcanvas', this, this.imageEl) != false){
+
+ this.bodyEl.un('click', this.beforeSelectFile, this);
+
+ this.notifyEl.hide();
+ this.thumbEl.show();
+ this.footerEl.show();
+
+ this.baseRotateLevel();
+
+ if(this.isDocument){
+ this.setThumbBoxSize();
}
- };
+
+ this.setThumbBoxPosition();
+
+ this.baseScaleLevel();
+
+ this.draw();
+
+ this.resize();
+
+ this.canvasLoaded = true;
- *//*
- * 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">
- */
-
-/**
- * @class Roo.LoadMask
- * A simple utility class for generically masking elements while loading data. If the element being masked has
- * an underlying {@link Roo.data.Store}, the masking will be automatically synchronized with the store's loading
- * process and the mask element will be cached for reuse. For all other elements, this mask will replace the
- * element's UpdateManager load indicator and will be destroyed after the initial load.
- * @constructor
- * Create a new LoadMask
- * @param {String/HTMLElement/Roo.Element} el The element or DOM node, or its id
- * @param {Object} config The config object
- */
-Roo.LoadMask = function(el, config){
- this.el = Roo.get(el);
- Roo.apply(this, config);
- if(this.store){
- this.store.on('beforeload', this.onBeforeLoad, this);
- this.store.on('load', this.onLoad, this);
- this.store.on('loadexception', this.onLoadException, this);
- this.removeMask = false;
- }else{
- var um = this.el.getUpdateManager();
- um.showLoadIndicator = false; // disable the default indicator
- um.on('beforeupdate', this.onBeforeLoad, this);
- um.on('update', this.onLoad, this);
- um.on('failure', this.onLoad, this);
- this.removeMask = true;
- }
-};
+ }
+
+ if(this.loadMask){
+ this.maskEl.unmask();
+ }
+
+ },
+
+ setCanvasPosition : function(center = true)
+ {
+ if(!this.canvasEl){
+ return;
+ }
-Roo.LoadMask.prototype = {
- /**
- * @cfg {Boolean} removeMask
- * 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.
- */
- /**
- * @cfg {String} msg
- * The text to display in a centered loading message box (defaults to 'Loading...')
- */
- msg : 'Loading...',
- /**
- * @cfg {String} msgCls
- * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
- */
- msgCls : 'x-mask-loading',
+ var newCenterLeft = Math.ceil((this.bodyEl.getWidth() - this.canvasEl.width) / 2);
+ var newCenterTop = Math.ceil((this.bodyEl.getHeight() - this.canvasEl.height) / 2);
- /**
- * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
- * @type Boolean
- */
- disabled: false,
+ if(center) {
+ this.previewEl.setLeft(newCenterLeft);
+ this.previewEl.setTop(newCenterTop);
- /**
- * Disables the mask to prevent it from being displayed
- */
- disable : function(){
- this.disabled = true;
+ return;
+ }
+
+ var oldScaleLevel = this.baseScale * Math.pow(1.02, this.startScale);
+ var oldCanvasWidth = Math.floor(this.imageEl.OriginWidth * oldScaleLevel);
+ var oldCanvasHeight = Math.floor(this.imageEl.OriginHeight * oldScaleLevel);
+
+ var oldCenterLeft = Math.ceil((this.bodyEl.getWidth() - oldCanvasWidth) / 2);
+ var oldCenterTop = Math.ceil((this.bodyEl.getHeight() - oldCanvasHeight) / 2);
+
+ var leftDiff = newCenterLeft - oldCenterLeft;
+ var topDiff = newCenterTop - oldCenterTop;
+
+ var newPreviewLeft = this.previewEl.getLeft(true) + leftDiff;
+ var newPreviewTop = this.previewEl.getTop(true) + topDiff;
+
+ this.previewEl.setLeft(newPreviewLeft);
+ this.previewEl.setTop(newPreviewTop);
+
+ },
+
+ onMouseDown : function(e)
+ {
+ e.stopEvent();
+
+ this.dragable = true;
+ this.pinching = false;
+
+ if(this.isDocument && (this.canvasEl.width < this.thumbEl.getWidth() || this.canvasEl.height < this.thumbEl.getHeight())){
+ this.dragable = false;
+ return;
+ }
+
+ this.mouseX = Roo.isTouch ? e.browserEvent.touches[0].pageX : e.getPageX();
+ this.mouseY = Roo.isTouch ? e.browserEvent.touches[0].pageY : e.getPageY();
+
},
+
+ onMouseMove : function(e)
+ {
+ e.stopEvent();
+
+ if(!this.canvasLoaded){
+ return;
+ }
+
+ if (!this.dragable){
+ return;
+ }
- /**
- * Enables the mask so that it can be displayed
- */
- enable : function(){
- this.disabled = false;
+ var maxPaddingLeft = this.canvasEl.width / 0.9 * 0.05;
+ var maxPaddingTop = maxPaddingLeft * this.minHeight / this.minWidth;
+
+ if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight <= this.minWidth / this.minHeight)) {
+ maxPaddingLeft = (this.canvasEl.height * this.minWidth / this.minHeight - this.canvasEl.width) / 2 + maxPaddingLeft;
+ }
+
+ if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight >= this.minWidth / this.minHeight)) {
+ maxPaddingTop = (this.canvasEl.width * this.minHeight / this.minWidth - this.canvasEl.height) / 2 + maxPaddingTop;
+ }
+
+ var minX = Math.ceil(this.thumbEl.getLeft(true) + this.thumbEl.getWidth() - this.canvasEl.width - maxPaddingLeft);
+ var minY = Math.ceil(this.thumbEl.getTop(true) + this.thumbEl.getHeight() - this.canvasEl.height - maxPaddingTop);
+
+ var maxX = Math.ceil(this.thumbEl.getLeft(true) + maxPaddingLeft);
+ var maxY = Math.ceil(this.thumbEl.getTop(true) + maxPaddingTop);
+
+ if(minX > maxX) {
+ var tempX = minX;
+ minX = maxX;
+ maxX = tempX;
+ }
+
+ if(minY > maxY) {
+ var tempY = minY;
+ minY = maxY;
+ maxY = tempY;
+ }
+
+ var x = Roo.isTouch ? e.browserEvent.touches[0].pageX : e.getPageX();
+ var y = Roo.isTouch ? e.browserEvent.touches[0].pageY : e.getPageY();
+
+ x = x - this.mouseX;
+ y = y - this.mouseY;
+
+ var bgX = Math.ceil(x + this.previewEl.getLeft(true));
+ var bgY = Math.ceil(y + this.previewEl.getTop(true));
+
+ bgX = (bgX < minX) ? minX : ((bgX > maxX) ? maxX : bgX);
+ bgY = (bgY < minY) ? minY : ((bgY > maxY) ? maxY : bgY);
+
+ this.previewEl.setLeft(bgX);
+ this.previewEl.setTop(bgY);
+
+ this.mouseX = Roo.isTouch ? e.browserEvent.touches[0].pageX : e.getPageX();
+ this.mouseY = Roo.isTouch ? e.browserEvent.touches[0].pageY : e.getPageY();
},
- onLoadException : function()
+ onMouseUp : function(e)
+ {
+ e.stopEvent();
+
+ this.dragable = false;
+ },
+
+ onMouseWheel : function(e)
+ {
+ e.stopEvent();
+
+ this.startScale = this.scale;
+ this.scale = (e.getWheelDelta() > 0) ? (this.scale + 1) : (this.scale - 1);
+
+ if(!this.zoomable()){
+ this.scale = this.startScale;
+ return;
+ }
+
+
+ this.draw();
+
+ return;
+ },
+
+ zoomable : function()
{
- Roo.log(arguments);
+ var minScale = this.thumbEl.getWidth() / this.minWidth;
- if (typeof(arguments[3]) != 'undefined') {
- Roo.MessageBox.alert("Error loading",arguments[3]);
- }
- /*
- try {
- if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
- Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
- }
- } catch(e) {
+ if(this.minWidth < this.minHeight){
+ minScale = this.thumbEl.getHeight() / this.minHeight;
+ }
+
+ var width = Math.ceil(this.imageEl.OriginWidth * this.getScaleLevel() / minScale);
+ var height = Math.ceil(this.imageEl.OriginHeight * this.getScaleLevel() / minScale);
+
+ var maxWidth = this.imageEl.OriginWidth;
+ var maxHeight = this.imageEl.OriginHeight;
+
+
+ var newCanvasWidth = Math.floor(this.imageEl.OriginWidth * this.getScaleLevel());
+ var newCanvasHeight = Math.floor(this.imageEl.OriginHeight * this.getScaleLevel());
+
+ var oldCenterLeft = Math.ceil((this.bodyEl.getWidth() - this.canvasEl.width) / 2);
+ var oldCenterTop = Math.ceil((this.bodyEl.getHeight() - this.canvasEl.height) / 2);
+
+ var newCenterLeft = Math.ceil((this.bodyEl.getWidth() - newCanvasWidth) / 2);
+ var newCenterTop = Math.ceil((this.bodyEl.getHeight() - newCanvasHeight) / 2);
+
+ var leftDiff = newCenterLeft - oldCenterLeft;
+ var topDiff = newCenterTop - oldCenterTop;
+
+ var newPreviewLeft = this.previewEl.getLeft(true) + leftDiff;
+ var newPreviewTop = this.previewEl.getTop(true) + topDiff;
+
+ var paddingLeft = newPreviewLeft - this.thumbEl.getLeft(true);
+ var paddingTop = newPreviewTop - this.thumbEl.getTop(true);
+
+ var paddingRight = this.thumbEl.getLeft(true) + this.thumbEl.getWidth() - newCanvasWidth - newPreviewLeft;
+ var paddingBottom = this.thumbEl.getTop(true) + this.thumbEl.getHeight() - newCanvasHeight - newPreviewTop;
+
+ var maxPaddingLeft = newCanvasWidth / 0.9 * 0.05;
+ var maxPaddingTop = maxPaddingLeft * this.minHeight / this.minWidth;
+
+ if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight <= this.minWidth / this.minHeight)) {
+ maxPaddingLeft = (newCanvasHeight * this.minWidth / this.minHeight - newCanvasWidth) / 2 + maxPaddingLeft;
+ }
+
+ if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight >= this.minWidth / this.minHeight)) {
+ maxPaddingTop = (newCanvasWidth * this.minHeight / this.minWidth - newCanvasHeight) / 2 + maxPaddingTop;
+ }
+
+ if(
+ this.isDocument &&
+ (this.rotate == 0 || this.rotate == 180) &&
+ (
+ width > this.imageEl.OriginWidth ||
+ height > this.imageEl.OriginHeight ||
+ (width < this.minWidth && height < this.minHeight)
+ )
+ ){
+ return false;
+ }
+
+ if(
+ this.isDocument &&
+ (this.rotate == 90 || this.rotate == 270) &&
+ (
+ width > this.imageEl.OriginWidth ||
+ height > this.imageEl.OriginHeight ||
+ (width < this.minHeight && height < this.minWidth)
+ )
+ ){
+ return false;
+ }
+
+ if(
+ !this.isDocument &&
+ (this.rotate == 0 || this.rotate == 180) &&
+ (
+ // for zoom out
+ paddingLeft > maxPaddingLeft ||
+ paddingRight > maxPaddingLeft ||
+ paddingTop > maxPaddingTop ||
+ paddingBottom > maxPaddingTop ||
+ // for zoom in
+ width > maxWidth ||
+ height > maxHeight
+ )
+ ){
+ return false;
+ }
+
+ if(
+ !this.isDocument &&
+ (this.rotate == 90 || this.rotate == 270) &&
+ (
+ width < this.minHeight ||
+ width > this.imageEl.OriginWidth ||
+ height < this.minWidth ||
+ height > this.imageEl.OriginHeight
+ )
+ ){
+ return false;
+ }
+
+ return true;
+
+ },
+
+ onRotateLeft : function(e)
+ {
+ if(!this.isDocument && (this.canvasEl.height < this.thumbEl.getWidth() || this.canvasEl.width < this.thumbEl.getHeight())){
+
+ var minScale = this.thumbEl.getWidth() / this.minWidth;
+
+ var bw = Math.ceil(this.canvasEl.width / this.getScaleLevel());
+ var bh = Math.ceil(this.canvasEl.height / this.getScaleLevel());
+
+ this.startScale = this.scale;
+ while (this.getScaleLevel() < minScale){
+
+ this.scale = this.scale + 1;
+
+ if(!this.zoomable()){
+ break;
+ }
+
+ if(
+ Math.ceil(bw * this.getScaleLevel()) < this.thumbEl.getHeight() ||
+ Math.ceil(bh * this.getScaleLevel()) < this.thumbEl.getWidth()
+ ){
+ continue;
+ }
+
+ this.rotate = (this.rotate < 90) ? 270 : this.rotate - 90;
+
+ this.draw();
+
+ return;
+ }
+
+ this.scale = this.startScale;
+
+ this.onRotateFail();
+
+ return false;
}
- */
+
+ this.rotate = (this.rotate < 90) ? 270 : this.rotate - 90;
+
+ if(this.isDocument){
+ this.setThumbBoxSize();
+ this.setThumbBoxPosition();
+ this.setCanvasPosition();
+ }
+
+ this.draw();
+
+ this.fireEvent('rotate', this, 'left');
+
+ },
- (function() { this.el.unmask(this.removeMask); }).defer(50, this);
+ onRotateRight : function(e)
+ {
+ if(!this.isDocument && (this.canvasEl.height < this.thumbEl.getWidth() || this.canvasEl.width < this.thumbEl.getHeight())){
+
+ var minScale = this.thumbEl.getWidth() / this.minWidth;
+
+ var bw = Math.ceil(this.canvasEl.width / this.getScaleLevel());
+ var bh = Math.ceil(this.canvasEl.height / this.getScaleLevel());
+
+ this.startScale = this.scale;
+
+ while (this.getScaleLevel() < minScale){
+
+ this.scale = this.scale + 1;
+
+ if(!this.zoomable()){
+ break;
+ }
+
+ if(
+ Math.ceil(bw * this.getScaleLevel()) < this.thumbEl.getHeight() ||
+ Math.ceil(bh * this.getScaleLevel()) < this.thumbEl.getWidth()
+ ){
+ continue;
+ }
+
+ this.rotate = (this.rotate > 180) ? 0 : this.rotate + 90;
+
+ this.draw();
+
+ return;
+ }
+
+ this.scale = this.startScale;
+
+ this.onRotateFail();
+
+ return false;
+ }
+
+ this.rotate = (this.rotate > 180) ? 0 : this.rotate + 90;
+
+ if(this.isDocument){
+ this.setThumbBoxSize();
+ this.setThumbBoxPosition();
+ this.setCanvasPosition();
+ }
+
+ this.draw();
+
+ this.fireEvent('rotate', this, 'right');
},
- // private
- onLoad : function()
+
+ onRotateFail : function()
{
- (function() { this.el.unmask(this.removeMask); }).defer(50, this);
+ this.errorEl.show(true);
+
+ var _this = this;
+
+ (function() { _this.errorEl.hide(true); }).defer(this.errorTimeout);
},
+
+ draw : function()
+ {
+ this.previewEl.dom.innerHTML = '';
+
+ var canvasEl = document.createElement("canvas");
+
+ var contextEl = canvasEl.getContext("2d");
+
+ canvasEl.width = this.imageEl.OriginWidth * this.getScaleLevel();
+ canvasEl.height = this.imageEl.OriginWidth * this.getScaleLevel();
+ var center = this.imageEl.OriginWidth / 2;
+
+ if(this.imageEl.OriginWidth < this.imageEl.OriginHeight){
+ canvasEl.width = this.imageEl.OriginHeight * this.getScaleLevel();
+ canvasEl.height = this.imageEl.OriginHeight * this.getScaleLevel();
+ center = this.imageEl.OriginHeight / 2;
+ }
+
+ contextEl.scale(this.getScaleLevel(), this.getScaleLevel());
+
+ contextEl.translate(center, center);
+ contextEl.rotate(this.rotate * Math.PI / 180);
- // private
- onBeforeLoad : function(){
- if(!this.disabled){
- (function() { this.el.mask(this.msg, this.msgCls); }).defer(50, this);
+ contextEl.drawImage(this.imageEl, 0, 0, this.imageEl.OriginWidth, this.imageEl.OriginHeight, center * -1, center * -1, this.imageEl.OriginWidth, this.imageEl.OriginHeight);
+
+ this.canvasEl = document.createElement("canvas");
+
+ this.contextEl = this.canvasEl.getContext("2d");
+
+ switch (this.rotate) {
+ case 0 :
+
+ this.canvasEl.width = this.imageEl.OriginWidth * this.getScaleLevel();
+ this.canvasEl.height = this.imageEl.OriginHeight * this.getScaleLevel();
+
+ this.contextEl.drawImage(canvasEl, 0, 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+
+ break;
+ case 90 :
+
+ this.canvasEl.width = this.imageEl.OriginHeight * this.getScaleLevel();
+ this.canvasEl.height = this.imageEl.OriginWidth * this.getScaleLevel();
+
+ if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
+ this.contextEl.drawImage(canvasEl, Math.abs(this.canvasEl.width - this.canvasEl.height), 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+ break;
+ }
+
+ this.contextEl.drawImage(canvasEl, 0, 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+
+ break;
+ case 180 :
+
+ this.canvasEl.width = this.imageEl.OriginWidth * this.getScaleLevel();
+ this.canvasEl.height = this.imageEl.OriginHeight * this.getScaleLevel();
+
+ if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
+ this.contextEl.drawImage(canvasEl, 0, Math.abs(this.canvasEl.width - this.canvasEl.height), this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+ break;
+ }
+
+ this.contextEl.drawImage(canvasEl, Math.abs(this.canvasEl.width - this.canvasEl.height), 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+
+ break;
+ case 270 :
+
+ this.canvasEl.width = this.imageEl.OriginHeight * this.getScaleLevel();
+ this.canvasEl.height = this.imageEl.OriginWidth * this.getScaleLevel();
+
+ if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
+ this.contextEl.drawImage(canvasEl, 0, 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+ break;
+ }
+
+ this.contextEl.drawImage(canvasEl, 0, Math.abs(this.canvasEl.width - this.canvasEl.height), this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
+
+ break;
+ default :
+ break;
}
+
+ this.previewEl.appendChild(this.canvasEl);
+
+ this.setCanvasPosition(false);
},
-
- // private
- destroy : function(){
- if(this.store){
- this.store.un('beforeload', this.onBeforeLoad, this);
- this.store.un('load', this.onLoad, this);
- this.store.un('loadexception', this.onLoadException, this);
- }else{
- var um = this.el.getUpdateManager();
- um.un('beforeupdate', this.onBeforeLoad, this);
- um.un('update', this.onLoad, this);
- um.un('failure', this.onLoad, this);
+
+ crop : function()
+ {
+ if(!this.canvasLoaded){
+ return;
}
- }
-};/*
- * 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">
- */
+
+ var imageCanvas = document.createElement("canvas");
+
+ var imageContext = imageCanvas.getContext("2d");
+
+ imageCanvas.width = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? this.imageEl.OriginWidth : this.imageEl.OriginHeight;
+ imageCanvas.height = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? this.imageEl.OriginWidth : this.imageEl.OriginHeight;
+
+ var center = imageCanvas.width / 2;
+
+ imageContext.translate(center, center);
+
+ imageContext.rotate(this.rotate * Math.PI / 180);
+
+ imageContext.drawImage(this.imageEl, 0, 0, this.imageEl.OriginWidth, this.imageEl.OriginHeight, center * -1, center * -1, this.imageEl.OriginWidth, this.imageEl.OriginHeight);
+
+ var canvas = document.createElement("canvas");
+
+ var context = canvas.getContext("2d");
+ canvas.width = this.thumbEl.getWidth() / this.getScaleLevel();
+
+ canvas.height = this.thumbEl.getHeight() / this.getScaleLevel();
-/**
- * @class Roo.XTemplate
- * @extends Roo.Template
- * Provides a template that can have nested templates for loops or conditionals. The syntax is:
-<pre><code>
-var t = new Roo.XTemplate(
- '<select name="{name}">',
- '<tpl for="options"><option value="{value:trim}">{text:ellipsis(10)}</option></tpl>',
- '</select>'
-);
-
-// then append, applying the master template values
- </code></pre>
- *
- * Supported features:
- *
- * Tags:
+ switch (this.rotate) {
+ case 0 :
+
+ var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getWidth() / this.getScaleLevel());
+ var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getHeight() / this.getScaleLevel());
+
+ var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
+ var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
+
+ var sx = this.thumbEl.getLeft(true) - this.previewEl.getLeft(true);
+ var sy = this.thumbEl.getTop(true) - this.previewEl.getTop(true);
-<pre><code>
- {a_variable} - output encoded.
- {a_variable.format:("Y-m-d")} - call a method on the variable
- {a_variable:raw} - unencoded output
- {a_variable:toFixed(1,2)} - Roo.util.Format."toFixed"
- {a_variable:this.method_on_template(...)} - call a method on the template object.
-
-</code></pre>
- * The tpl tag:
-<pre><code>
- <tpl for="a_variable or condition.."></tpl>
- <tpl if="a_variable or condition"></tpl>
- <tpl exec="some javascript"></tpl>
- <tpl name="named_template"></tpl> (experimental)
-
- <tpl for="."></tpl> - just iterate the property..
- <tpl for=".."></tpl> - iterates with the parent (probably the template)
-</code></pre>
- *
- */
-Roo.XTemplate = function()
-{
- Roo.XTemplate.superclass.constructor.apply(this, arguments);
- if (this.html) {
- this.compile();
- }
-};
+ sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
+ sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
+ if(canvas.width > this.outputMaxWidth) {
+ var scale = this.outputMaxWidth / canvas.width;
+ canvas.width = canvas.width * scale;
+ canvas.height = canvas.height * scale;
+ context.scale(scale, scale);
+ }
-Roo.extend(Roo.XTemplate, Roo.Template, {
+ context.fillStyle = 'white';
+ context.fillRect(0, 0, this.thumbEl.getWidth() / this.getScaleLevel(), this.thumbEl.getHeight() / this.getScaleLevel());
- /**
- * The various sub templates
- */
- tpls : false,
- /**
- *
- * basic tag replacing syntax
- * WORD:WORD()
- *
- * // you can fake an object call by doing this
- * x.t:(test,tesT)
- *
- */
- re : /\{([\w-\.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
- /**
- * compile the template
- *
- * This is not recursive, so I'm not sure how nested templates are really going to be handled..
- *
- */
- compile: function()
- {
- var s = this.html;
-
- s = ['<tpl>', s, '</tpl>'].join('');
-
- var re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
- nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
- ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
- execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
- namedRe = /^<tpl\b[^>]*?name="(\w+)"/, // named templates..
- m,
- id = 0,
- tpls = [];
-
- while(true == !!(m = s.match(re))){
- var forMatch = m[0].match(nameRe),
- ifMatch = m[0].match(ifRe),
- execMatch = m[0].match(execRe),
- namedMatch = m[0].match(namedRe),
+ context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
+
+ break;
+ case 90 :
+
+ var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getWidth() / this.getScaleLevel());
+ var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getHeight() / this.getScaleLevel());
+
+ var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
+ var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
+
+ var targetWidth = this.minWidth - 2 * x;
+ var targetHeight = this.minHeight - 2 * y;
+
+ var scale = 1;
+
+ if((x == 0 && y == 0) || (x == 0 && y > 0)){
+ scale = targetWidth / width;
+ }
+
+ if(x > 0 && y == 0){
+ scale = targetHeight / height;
+ }
+
+ if(x > 0 && y > 0){
+ scale = targetWidth / width;
+
+ if(width < height){
+ scale = targetHeight / height;
+ }
+ }
+
+ context.scale(scale, scale);
+
+ var sx = Math.min(this.canvasEl.width - this.thumbEl.getWidth(), this.thumbEl.getLeft(true) - this.previewEl.getLeft(true));
+ var sy = Math.min(this.canvasEl.height - this.thumbEl.getHeight(), this.thumbEl.getTop(true) - this.previewEl.getTop(true));
+
+ sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
+ sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
+
+ sx += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight) : 0;
+
+ context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
+
+ break;
+ case 180 :
+
+ var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getWidth() / this.getScaleLevel());
+ var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getHeight() / this.getScaleLevel());
+
+ var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
+ var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
+
+ var targetWidth = this.minWidth - 2 * x;
+ var targetHeight = this.minHeight - 2 * y;
+
+ var scale = 1;
+
+ if((x == 0 && y == 0) || (x == 0 && y > 0)){
+ scale = targetWidth / width;
+ }
+
+ if(x > 0 && y == 0){
+ scale = targetHeight / height;
+ }
- exp = null,
- fn = null,
- exec = null,
- name = forMatch && forMatch[1] ? forMatch[1] : '';
+ if(x > 0 && y > 0){
+ scale = targetWidth / width;
+
+ if(width < height){
+ scale = targetHeight / height;
+ }
+ }
- if (ifMatch) {
- // if - puts fn into test..
- exp = ifMatch && ifMatch[1] ? ifMatch[1] : null;
- if(exp){
- fn = new Function('values', 'parent', 'with(values){ return '+(Roo.util.Format.htmlDecode(exp))+'; }');
+ context.scale(scale, scale);
+
+ var sx = Math.min(this.canvasEl.width - this.thumbEl.getWidth(), this.thumbEl.getLeft(true) - this.previewEl.getLeft(true));
+ var sy = Math.min(this.canvasEl.height - this.thumbEl.getHeight(), this.thumbEl.getTop(true) - this.previewEl.getTop(true));
+
+ sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
+ sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
+
+ sx += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? 0 : Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight);
+ sy += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight) : 0;
+
+ context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
+
+ break;
+ case 270 :
+
+ var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getWidth() / this.getScaleLevel());
+ var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getHeight() / this.getScaleLevel());
+
+ var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
+ var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
+
+ var targetWidth = this.minWidth - 2 * x;
+ var targetHeight = this.minHeight - 2 * y;
+
+ var scale = 1;
+
+ if((x == 0 && y == 0) || (x == 0 && y > 0)){
+ scale = targetWidth / width;
}
+
+ if(x > 0 && y == 0){
+ scale = targetHeight / height;
+ }
+
+ if(x > 0 && y > 0){
+ scale = targetWidth / width;
+
+ if(width < height){
+ scale = targetHeight / height;
+ }
+ }
+
+ context.scale(scale, scale);
+ var sx = Math.min(this.canvasEl.width - this.thumbEl.getWidth(), this.thumbEl.getLeft(true) - this.previewEl.getLeft(true));
+ var sy = Math.min(this.canvasEl.height - this.thumbEl.getHeight(), this.thumbEl.getTop(true) - this.previewEl.getTop(true));
+
+ sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
+ sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
+
+ sy += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? 0 : Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight);
+
+ context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
+
+ break;
+ default :
+ break;
+ }
+
+ this.cropData = canvas.toDataURL(this.cropType);
+
+ if(this.fireEvent('crop', this, this.cropData) !== false){
+ this.process(this.file, this.cropData);
+ }
+
+ return;
+
+ },
+
+ setThumbBoxSize : function()
+ {
+ var width, height;
+
+ if(this.isDocument && typeof(this.imageEl) != 'undefined'){
+ width = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.max(this.minWidth, this.minHeight) : Math.min(this.minWidth, this.minHeight);
+ height = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.min(this.minWidth, this.minHeight) : Math.max(this.minWidth, this.minHeight);
+
+ this.minWidth = width;
+ this.minHeight = height;
+
+ if(this.rotate == 90 || this.rotate == 270){
+ this.minWidth = height;
+ this.minHeight = width;
}
+ }
+
+ height = this.windowSize;
+ width = Math.ceil(this.minWidth * height / this.minHeight);
+
+ if(this.minWidth > this.minHeight){
+ width = this.windowSize;
+ height = Math.ceil(this.minHeight * width / this.minWidth);
+ }
+
+ this.thumbEl.setStyle({
+ width : width + 'px',
+ height : height + 'px'
+ });
+
+ return;
- if (execMatch) {
- // exec - calls a function... returns empty if true is returned.
- exp = execMatch && execMatch[1] ? execMatch[1] : null;
- if(exp){
- exec = new Function('values', 'parent', 'with(values){ '+(Roo.util.Format.htmlDecode(exp))+'; }');
+ },
+
+ setThumbBoxPosition : function()
+ {
+ var x = Math.ceil((this.bodyEl.getWidth() - this.thumbEl.getWidth()) / 2 );
+ var y = Math.ceil((this.bodyEl.getHeight() - this.thumbEl.getHeight()) / 2);
+
+ this.thumbEl.setLeft(x);
+ this.thumbEl.setTop(y);
+
+ },
+
+ baseRotateLevel : function()
+ {
+ this.baseRotate = 1;
+
+ if(
+ typeof(this.exif) != 'undefined' &&
+ typeof(this.exif[Roo.dialog.UploadCropbox['tags']['Orientation']]) != 'undefined' &&
+ [1, 3, 6, 8].indexOf(this.exif[Roo.dialog.UploadCropbox['tags']['Orientation']]) != -1
+ ){
+ this.baseRotate = this.exif[Roo.dialog.UploadCropbox['tags']['Orientation']];
+ }
+
+ this.rotate = Roo.dialog.UploadCropbox['Orientation'][this.baseRotate];
+
+ },
+
+ baseScaleLevel : function()
+ {
+ var width, height;
+
+ if(this.isDocument){
+
+ if(this.baseRotate == 6 || this.baseRotate == 8){
+
+ height = this.thumbEl.getHeight();
+ this.baseScale = height / this.imageEl.OriginWidth;
+
+ if(this.imageEl.OriginHeight * this.baseScale > this.thumbEl.getWidth()){
+ width = this.thumbEl.getWidth();
+ this.baseScale = width / this.imageEl.OriginHeight;
}
+
+ return;
+ }
+
+ height = this.thumbEl.getHeight();
+ this.baseScale = height / this.imageEl.OriginHeight;
+
+ if(this.imageEl.OriginWidth * this.baseScale > this.thumbEl.getWidth()){
+ width = this.thumbEl.getWidth();
+ this.baseScale = width / this.imageEl.OriginWidth;
}
+
+ return;
+ }
+
+ if(this.baseRotate == 6 || this.baseRotate == 8){
+ width = this.thumbEl.getHeight();
+ this.baseScale = width / this.imageEl.OriginHeight;
- if (name) {
- // for =
- switch(name){
- case '.': name = new Function('values', 'parent', 'with(values){ return values; }'); break;
- case '..': name = new Function('values', 'parent', 'with(values){ return parent; }'); break;
- default: name = new Function('values', 'parent', 'with(values){ return '+name+'; }');
+ if(this.imageEl.OriginHeight * this.baseScale < this.thumbEl.getWidth()){
+ height = this.thumbEl.getWidth();
+ this.baseScale = height / this.imageEl.OriginHeight;
+ }
+
+ if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
+ height = this.thumbEl.getWidth();
+ this.baseScale = height / this.imageEl.OriginHeight;
+
+ if(this.imageEl.OriginWidth * this.baseScale < this.thumbEl.getHeight()){
+ width = this.thumbEl.getHeight();
+ this.baseScale = width / this.imageEl.OriginWidth;
}
}
- var uid = namedMatch ? namedMatch[1] : id;
+ return;
+ }
+
+ width = this.thumbEl.getWidth();
+ this.baseScale = width / this.imageEl.OriginWidth;
+
+ if(this.imageEl.OriginHeight * this.baseScale < this.thumbEl.getHeight()){
+ height = this.thumbEl.getHeight();
+ this.baseScale = height / this.imageEl.OriginHeight;
+ }
+
+ if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
- tpls.push({
- id: namedMatch ? namedMatch[1] : id,
- target: name,
- exec: exec,
- test: fn,
- body: m[1] || ''
- });
- if (namedMatch) {
- s = s.replace(m[0], '');
- } else {
- s = s.replace(m[0], '{xtpl'+ id + '}');
+ height = this.thumbEl.getHeight();
+ this.baseScale = height / this.imageEl.OriginHeight;
+
+ if(this.imageEl.OriginWidth * this.baseScale < this.thumbEl.getWidth()){
+ width = this.thumbEl.getWidth();
+ this.baseScale = width / this.imageEl.OriginWidth;
}
- ++id;
+
}
- this.tpls = [];
- for(var i = tpls.length-1; i >= 0; --i){
- this.compileTpl(tpls[i]);
- this.tpls[tpls[i].id] = tpls[i];
+
+ if(this.imageEl.OriginWidth < this.minWidth || this.imageEl.OriginHeight < this.minHeight) {
+ this.baseScale = width / this.minWidth;
}
- this.master = tpls[tpls.length-1];
- return this;
+
+ return;
},
- /**
- * same as applyTemplate, except it's done to one of the subTemplates
- * when using named templates, you can do:
- *
- * var str = pl.applySubTemplate('your-name', values);
- *
- *
- * @param {Number} id of the template
- * @param {Object} values to apply to template
- * @param {Object} parent (normaly the instance of this object)
- */
- applySubTemplate : function(id, values, parent)
+
+ getScaleLevel : function()
{
+ return this.baseScale * Math.pow(1.02, this.scale);
+ },
+
+ onTouchStart : function(e)
+ {
+ if(!this.canvasLoaded){
+ this.beforeSelectFile(e);
+ return;
+ }
+ var touches = e.browserEvent.touches;
- var t = this.tpls[id];
+ if(!touches){
+ return;
+ }
+ if(touches.length == 1){
+ this.onMouseDown(e);
+ return;
+ }
- try {
- if(t.test && !t.test.call(this, values, parent)){
- return '';
- }
- } catch(e) {
- Roo.log("Xtemplate.applySubTemplate 'test': Exception thrown");
- Roo.log(e.toString());
- Roo.log(t.test);
- return ''
+ if(touches.length != 2){
+ return;
}
- try {
-
- if(t.exec && t.exec.call(this, values, parent)){
- return '';
- }
- } catch(e) {
- Roo.log("Xtemplate.applySubTemplate 'exec': Exception thrown");
- Roo.log(e.toString());
- Roo.log(t.exec);
- return ''
+
+ var coords = [];
+
+ for(var i = 0, finger; finger = touches[i]; i++){
+ coords.push(finger.pageX, finger.pageY);
}
- try {
- var vs = t.target ? t.target.call(this, values, parent) : values;
- parent = t.target ? values : parent;
- if(t.target && vs instanceof Array){
- var buf = [];
- for(var i = 0, len = vs.length; i < len; i++){
- buf[buf.length] = t.compiled.call(this, vs[i], parent);
- }
- return buf.join('');
- }
- return t.compiled.call(this, vs, parent);
- } catch (e) {
- Roo.log("Xtemplate.applySubTemplate : Exception thrown");
- Roo.log(e.toString());
- Roo.log(t.compiled);
- return '';
+
+ var x = Math.pow(coords[0] - coords[2], 2);
+ var y = Math.pow(coords[1] - coords[3], 2);
+
+ this.startDistance = Math.sqrt(x + y);
+
+ this.startScale = this.scale;
+
+ this.pinching = true;
+ this.dragable = false;
+
+ },
+
+ onTouchMove : function(e)
+ {
+ if(!this.pinching && !this.dragable){
+ return;
+ }
+
+ var touches = e.browserEvent.touches;
+
+ if(!touches){
+ return;
+ }
+
+ if(this.dragable){
+ this.onMouseMove(e);
+ return;
}
+
+ var coords = [];
+
+ for(var i = 0, finger; finger = touches[i]; i++){
+ coords.push(finger.pageX, finger.pageY);
+ }
+
+ var x = Math.pow(coords[0] - coords[2], 2);
+ var y = Math.pow(coords[1] - coords[3], 2);
+
+ this.endDistance = Math.sqrt(x + y);
+
+ this.scale = this.startScale + Math.floor(Math.log(this.endDistance / this.startDistance) / Math.log(1.1));
+
+ if(!this.zoomable()){
+ this.scale = this.startScale;
+ return;
+ }
+
+ this.draw();
+
},
-
- compileTpl : function(tpl)
+
+ onTouchEnd : function(e)
{
- var fm = Roo.util.Format;
- var useF = this.disableFormats !== true;
- var sep = Roo.isGecko ? "+" : ",";
- var undef = function(str) {
- Roo.log("Property not found :" + str);
- return '';
+ this.pinching = false;
+ this.dragable = false;
+
+ },
+
+ process : function(file, crop)
+ {
+ if(this.loadMask){
+ this.maskEl.mask(this.loadingText);
+ }
+
+ this.xhr = new XMLHttpRequest();
+
+ file.xhr = this.xhr;
+
+ this.xhr.open(this.method, this.url, true);
+
+ var headers = {
+ "Accept": "application/json",
+ "Cache-Control": "no-cache",
+ "X-Requested-With": "XMLHttpRequest"
};
- var fn = function(m, name, format, args)
- {
- //Roo.log(arguments);
- args = args ? args.replace(/\\'/g,"'") : args;
- //["{TEST:(a,b,c)}", "TEST", "", "a,b,c", 0, "{TEST:(a,b,c)}"]
- if (typeof(format) == 'undefined') {
- format= 'htmlEncode';
- }
- if (format == 'raw' ) {
- format = false;
+ for (var headerName in headers) {
+ var headerValue = headers[headerName];
+ if (headerValue) {
+ this.xhr.setRequestHeader(headerName, headerValue);
}
-
- if(name.substr(0, 4) == 'xtpl'){
- return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent)'+sep+"'";
+ }
+
+ var _this = this;
+
+ this.xhr.onload = function()
+ {
+ _this.xhrOnLoad(_this.xhr);
+ }
+
+ this.xhr.onerror = function()
+ {
+ _this.xhrOnError(_this.xhr);
+ }
+
+ var formData = new FormData();
+
+ formData.append('returnHTML', 'NO');
+
+ if(crop){
+ formData.append('crop', crop);
+ var blobBin = atob(crop.split(',')[1]);
+ var array = [];
+ for(var i = 0; i < blobBin.length; i++) {
+ array.push(blobBin.charCodeAt(i));
}
+ var croppedFile =new Blob([new Uint8Array(array)], {type: this.cropType});
+ formData.append(this.paramName, croppedFile, file.name);
+ }
+
+ if(typeof(file.filename) != 'undefined'){
+ formData.append('filename', file.filename);
+ }
+
+ if(typeof(file.mimetype) != 'undefined'){
+ formData.append('mimetype', file.mimetype);
+ }
+
+ if(this.fireEvent('arrange', this, formData) != false){
+ this.xhr.send(formData);
+ };
+ },
+
+ xhrOnLoad : function(xhr)
+ {
+ if(this.loadMask){
+ this.maskEl.unmask();
+ }
+
+ if (xhr.readyState !== 4) {
+ this.fireEvent('exception', this, xhr);
+ return;
+ }
+
+ var response = Roo.decode(xhr.responseText);
+
+ if(!response.success){
+ this.fireEvent('exception', this, xhr);
+ return;
+ }
+
+ var response = Roo.decode(xhr.responseText);
+
+ this.fireEvent('upload', this, response);
+
+ },
+
+ xhrOnError : function()
+ {
+ if(this.loadMask){
+ this.maskEl.unmask();
+ }
+
+ Roo.log('xhr on error');
+
+ var response = Roo.decode(xhr.responseText);
+
+ Roo.log(response);
+
+ },
+
+ prepare : function(file)
+ {
+ if(this.loadMask){
+ this.maskEl.mask(this.loadingText);
+ }
+
+ this.file = false;
+ this.exif = {};
+
+ if(typeof(file) === 'string'){
+ this.loadCanvas(file);
+ return;
+ }
+
+ if(!file || !this.urlAPI){
+ return;
+ }
+
+ this.file = file;
+ if(typeof(file.type) != 'undefined' && file.type.length != 0) {
+ this.cropType = file.type;
+ }
+
+ var _this = this;
+
+ if(this.fireEvent('prepare', this, this.file) != false){
- // build an array of options to determine if value is undefined..
-
- // basically get 'xxxx.yyyy' then do
- // (typeof(xxxx) == 'undefined' || typeof(xxx.yyyy) == 'undefined') ?
- // (function () { Roo.log("Property not found"); return ''; })() :
- // ......
-
- var udef_ar = [];
- var lookfor = '';
- Roo.each(name.split('.'), function(st) {
- lookfor += (lookfor.length ? '.': '') + st;
- udef_ar.push( "(typeof(" + lookfor + ") == 'undefined')" );
- });
-
- var udef_st = '((' + udef_ar.join(" || ") +") ? undef('" + name + "') : "; // .. needs )
-
+ var reader = new FileReader();
- if(format && useF){
+ reader.onload = function (e) {
+ if (e.target.error) {
+ Roo.log(e.target.error);
+ return;
+ }
- args = args ? ',' + args : "";
-
- if(format.substr(0, 5) != "this."){
- format = "fm." + format + '(';
- }else{
- format = 'this.call("'+ format.substr(5) + '", ';
- args = ", values";
+ var buffer = e.target.result,
+ dataView = new DataView(buffer),
+ offset = 2,
+ maxOffset = dataView.byteLength - 4,
+ markerBytes,
+ markerLength;
+
+ if (dataView.getUint16(0) === 0xffd8) {
+ while (offset < maxOffset) {
+ markerBytes = dataView.getUint16(offset);
+
+ if ((markerBytes >= 0xffe0 && markerBytes <= 0xffef) || markerBytes === 0xfffe) {
+ markerLength = dataView.getUint16(offset + 2) + 2;
+ if (offset + markerLength > dataView.byteLength) {
+ Roo.log('Invalid meta data: Invalid segment size.');
+ break;
+ }
+
+ if(markerBytes == 0xffe1){
+ _this.parseExifData(
+ dataView,
+ offset,
+ markerLength
+ );
+ }
+
+ offset += markerLength;
+
+ continue;
+ }
+
+ break;
+ }
+
}
- return "'"+ sep + udef_st + format + name + args + "))"+sep+"'";
- }
-
- if (args.length) {
- // called with xxyx.yuu:(test,test)
- // change to ()
- return "'"+ sep + udef_st + name + '(' + args + "))"+sep+"'";
+ var url = _this.urlAPI.createObjectURL(_this.file);
+
+ _this.loadCanvas(url);
+
+ return;
}
- // raw.. - :raw modifier..
- return "'"+ sep + udef_st + name + ")"+sep+"'";
- };
- var body;
- // branched to use + in gecko and [].join() in others
- if(Roo.isGecko){
- body = "tpl.compiled = function(values, parent){ with(values) { return '" +
- tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
- "';};};";
- }else{
- body = ["tpl.compiled = function(values, parent){ with (values) { return ['"];
- body.push(tpl.body.replace(/(\r\n|\n)/g,
- '\\n').replace(/'/g, "\\'").replace(this.re, fn));
- body.push("'].join('');};};");
- body = body.join('');
+ reader.readAsArrayBuffer(this.file);
+
}
- Roo.debug && Roo.log(body.replace(/\\n/,'\n'));
-
- /** eval:var:tpl eval:var:fm eval:var:useF eval:var:undef */
- eval(body);
+ },
+
+ parseExifData : function(dataView, offset, length)
+ {
+ var tiffOffset = offset + 10,
+ littleEndian,
+ dirOffset;
+
+ if (dataView.getUint32(offset + 4) !== 0x45786966) {
+ // No Exif data, might be XMP data instead
+ return;
+ }
- return this;
+ // Check for the ASCII code for "Exif" (0x45786966):
+ if (dataView.getUint32(offset + 4) !== 0x45786966) {
+ // No Exif data, might be XMP data instead
+ return;
+ }
+ if (tiffOffset + 8 > dataView.byteLength) {
+ Roo.log('Invalid Exif data: Invalid segment size.');
+ return;
+ }
+ // Check for the two null bytes:
+ if (dataView.getUint16(offset + 8) !== 0x0000) {
+ Roo.log('Invalid Exif data: Missing byte alignment offset.');
+ return;
+ }
+ // Check the byte alignment:
+ switch (dataView.getUint16(tiffOffset)) {
+ case 0x4949:
+ littleEndian = true;
+ break;
+ case 0x4D4D:
+ littleEndian = false;
+ break;
+ default:
+ Roo.log('Invalid Exif data: Invalid byte alignment marker.');
+ return;
+ }
+ // Check for the TIFF tag marker (0x002A):
+ if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) {
+ Roo.log('Invalid Exif data: Missing TIFF marker.');
+ return;
+ }
+ // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
+ dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
+
+ this.parseExifTags(
+ dataView,
+ tiffOffset,
+ tiffOffset + dirOffset,
+ littleEndian
+ );
},
-
- applyTemplate : function(values){
- return this.master.compiled.call(this, values, {});
- //var s = this.subs;
+
+ parseExifTags : function(dataView, tiffOffset, dirOffset, littleEndian)
+ {
+ var tagsNumber,
+ dirEndOffset,
+ i;
+ if (dirOffset + 6 > dataView.byteLength) {
+ Roo.log('Invalid Exif data: Invalid directory offset.');
+ return;
+ }
+ tagsNumber = dataView.getUint16(dirOffset, littleEndian);
+ dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
+ if (dirEndOffset + 4 > dataView.byteLength) {
+ Roo.log('Invalid Exif data: Invalid directory size.');
+ return;
+ }
+ for (i = 0; i < tagsNumber; i += 1) {
+ this.parseExifTag(
+ dataView,
+ tiffOffset,
+ dirOffset + 2 + 12 * i, // tag offset
+ littleEndian
+ );
+ }
+ // Return the offset to the next directory:
+ return dataView.getUint32(dirEndOffset, littleEndian);
},
-
- apply : function(){
- return this.applyTemplate.apply(this, arguments);
+
+ parseExifTag : function (dataView, tiffOffset, offset, littleEndian)
+ {
+ var tag = dataView.getUint16(offset, littleEndian);
+
+ this.exif[tag] = this.getExifValue(
+ dataView,
+ tiffOffset,
+ offset,
+ dataView.getUint16(offset + 2, littleEndian), // tag type
+ dataView.getUint32(offset + 4, littleEndian), // tag length
+ littleEndian
+ );
+ },
+
+ getExifValue : function (dataView, tiffOffset, offset, type, length, littleEndian)
+ {
+ var tagType = Roo.dialog.UploadCropbox.exifTagTypes[type],
+ tagSize,
+ dataOffset,
+ values,
+ i,
+ str,
+ c;
+
+ if (!tagType) {
+ Roo.log('Invalid Exif data: Invalid tag type.');
+ return;
+ }
+
+ tagSize = tagType.size * length;
+ // Determine if the value is contained in the dataOffset bytes,
+ // or if the value at the dataOffset is a pointer to the actual data:
+ dataOffset = tagSize > 4 ?
+ tiffOffset + dataView.getUint32(offset + 8, littleEndian) : (offset + 8);
+ if (dataOffset + tagSize > dataView.byteLength) {
+ Roo.log('Invalid Exif data: Invalid data offset.');
+ return;
+ }
+ if (length === 1) {
+ return tagType.getValue(dataView, dataOffset, littleEndian);
+ }
+ values = [];
+ for (i = 0; i < length; i += 1) {
+ values[i] = tagType.getValue(dataView, dataOffset + i * tagType.size, littleEndian);
+ }
+
+ if (tagType.ascii) {
+ str = '';
+ // Concatenate the chars:
+ for (i = 0; i < values.length; i += 1) {
+ c = values[i];
+ // Ignore the terminating NULL byte(s):
+ if (c === '\u0000') {
+ break;
+ }
+ str += c;
+ }
+ return str;
+ }
+ return values;
}
+
+});
- });
-
-Roo.XTemplate.from = function(el){
- el = Roo.getDom(el);
- return new Roo.XTemplate(el.value || el.innerHTML);
-};
\ No newline at end of file
+Roo.apply(Roo.dialog.UploadCropbox, {
+ tags : {
+ 'Orientation': 0x0112
+ },
+
+ Orientation: {
+ 1: 0, //'top-left',
+// 2: 'top-right',
+ 3: 180, //'bottom-right',
+// 4: 'bottom-left',
+// 5: 'left-top',
+ 6: 90, //'right-top',
+// 7: 'right-bottom',
+ 8: 270 //'left-bottom'
+ },
+
+ exifTagTypes : {
+ // byte, 8-bit unsigned int:
+ 1: {
+ getValue: function (dataView, dataOffset) {
+ return dataView.getUint8(dataOffset);
+ },
+ size: 1
+ },
+ // ascii, 8-bit byte:
+ 2: {
+ getValue: function (dataView, dataOffset) {
+ return String.fromCharCode(dataView.getUint8(dataOffset));
+ },
+ size: 1,
+ ascii: true
+ },
+ // short, 16 bit int:
+ 3: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint16(dataOffset, littleEndian);
+ },
+ size: 2
+ },
+ // long, 32 bit int:
+ 4: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint32(dataOffset, littleEndian);
+ },
+ size: 4
+ },
+ // rational = two long values, first is numerator, second is denominator:
+ 5: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getUint32(dataOffset, littleEndian) /
+ dataView.getUint32(dataOffset + 4, littleEndian);
+ },
+ size: 8
+ },
+ // slong, 32 bit signed int:
+ 9: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getInt32(dataOffset, littleEndian);
+ },
+ size: 4
+ },
+ // srational, two slongs, first is numerator, second is denominator:
+ 10: {
+ getValue: function (dataView, dataOffset, littleEndian) {
+ return dataView.getInt32(dataOffset, littleEndian) /
+ dataView.getInt32(dataOffset + 4, littleEndian);
+ },
+ size: 8
+ }
+ },
+
+ footer : {
+ STANDARD : [
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-rotate-left',
+ action : 'rotate-left',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-undo"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-picture',
+ action : 'picture',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-picture-o"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-rotate-right',
+ action : 'rotate-right',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-repeat"></i>'
+ }
+ ]
+ }
+ ],
+ DOCUMENT : [
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-rotate-left',
+ action : 'rotate-left',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-undo"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-download',
+ action : 'download',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-download"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-crop',
+ action : 'crop',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-crop"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-trash',
+ action : 'trash',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-trash"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-rotate-right',
+ action : 'rotate-right',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-repeat"></i>'
+ }
+ ]
+ }
+ ],
+ ROTATOR : [
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-rotate-left',
+ action : 'rotate-left',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-undo"></i>'
+ }
+ ]
+ },
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-rotate-right',
+ action : 'rotate-right',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : '<i class="fa fa-repeat"></i>'
+ }
+ ]
+ }
+ ],
+ CENTER : [
+ {
+ tag : 'div',
+ cls : 'btn-group roo-upload-cropbox-center',
+ action : 'center',
+ cn : [
+ {
+ tag : 'button',
+ cls : 'btn btn-default',
+ html : 'CENTER'
+ }
+ ]
+ }
+ ]
+ }
+});