diff --git a/examples/filters.html b/examples/filters.html index da9c4235..8340fa2b 100644 --- a/examples/filters.html +++ b/examples/filters.html @@ -26,6 +26,9 @@

Filter Builders

Setting allowGroups to false disallows nested logical filters.

+ +
+

Setting allowCQL to true provides to option to specify the filter as CQL.

The above filter builder allows group conditions.

diff --git a/examples/filters.js b/examples/filters.js index 71d0c3a2..f7e2ab94 100644 --- a/examples/filters.js +++ b/examples/filters.js @@ -72,6 +72,29 @@ Ext.onReady(function() { }] }); + var panelcql = new Ext.Panel({ + title: "Filter Builder (no groups, but with CQL option)", + renderTo: "panelcql", + width: 320, + items: [{ + xtype: "gxp_filterbuilder", + style: "padding: .3em .3em .1em .1em", + allowGroups: false, + allowCQL: true, + filter: filter.clone(), + attributes: new GeoExt.data.AttributeStore({ + url: "data/describe_feature_type.xml", + ignore: {name: "the_geom"} + }) + }], + bbar: ["->", { + text: "View Filter Encoding", + handler: function() { + showFE(panel1); + } + }] + }); + var panel2 = new Ext.Panel({ title: "Filter Builder (with groups)", renderTo: "panel2", diff --git a/examples/symbolizer-grid.html b/examples/symbolizer-grid.html index da86cd8e..47ea91b5 100644 --- a/examples/symbolizer-grid.html +++ b/examples/symbolizer-grid.html @@ -5,18 +5,17 @@ - + - - - - - - - + + + + + + diff --git a/examples/symbolizer-grid.js b/examples/symbolizer-grid.js index 137d6654..9598eeaf 100644 --- a/examples/symbolizer-grid.js +++ b/examples/symbolizer-grid.js @@ -6,10 +6,6 @@ symbolizers.push(new OpenLayers.Symbolizer.Point({ strokeColor: "red", strokeWidth: 1 })); -symbolizers.push(new OpenLayers.Symbolizer.Line({ - strokeColor: "#669900", - strokeWidth: 3 -})); symbolizers.push(new OpenLayers.Symbolizer.Polygon({ fillColor: "olive", fillOpacity: 0.25, @@ -64,6 +60,7 @@ var showSLD = function() { Ext.onReady(function() { grid = new gxp.grid.SymbolizerGrid({ symbolizers: symbolizers, + symbolType: "Polygon", height: 375, width: 400, renderTo: "grid", diff --git a/examples/ux/treegrid/TreeGrid.js b/examples/ux/treegrid/TreeGrid.js deleted file mode 100644 index 0e9ad6fb..00000000 --- a/examples/ux/treegrid/TreeGrid.js +++ /dev/null @@ -1,410 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -/** - * @class Ext.ux.tree.TreeGrid - * @extends Ext.tree.TreePanel - * - * @xtype treegrid - */ -Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, { - rootVisible : false, - useArrows : true, - lines : false, - borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell - cls : 'x-treegrid', - - columnResize : true, - enableSort : true, - reserveScrollOffset : true, - enableHdMenu : true, - - columnsText : 'Columns', - - initComponent : function() { - if(!this.root) { - this.root = new Ext.tree.AsyncTreeNode({text: 'Root'}); - } - - // initialize the loader - var l = this.loader; - if(!l){ - l = new Ext.ux.tree.TreeGridLoader({ - dataUrl: this.dataUrl, - requestMethod: this.requestMethod, - store: this.store - }); - }else if(Ext.isObject(l) && !l.load){ - l = new Ext.ux.tree.TreeGridLoader(l); - } - this.loader = l; - - Ext.ux.tree.TreeGrid.superclass.initComponent.call(this); - - this.initColumns(); - - if(this.enableSort) { - this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort); - } - - if(this.columnResize){ - this.colResizer = new Ext.tree.ColumnResizer(this.columnResize); - this.colResizer.init(this); - } - - var c = this.columns; - if(!this.internalTpl){ - this.internalTpl = new Ext.XTemplate( - '
', - '
', - '
', - '', - '', - '', - '', - '', - '
', - '
', - this.enableHdMenu ? '' : '', - '{header}', - '
', - '
', - '
', - '
', - '
', - '
', - '
' - ); - } - - if(!this.colgroupTpl) { - this.colgroupTpl = new Ext.XTemplate( - '' - ); - } - }, - - initColumns : function() { - var cs = this.columns, - len = cs.length, - columns = [], - i, c; - - for(i = 0; i < len; i++){ - c = cs[i]; - if(!c.isColumn) { - c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn'; - c = Ext.create(c); - } - c.init(this); - columns.push(c); - - if(this.enableSort !== false && c.sortable !== false) { - c.sortable = true; - this.enableSort = true; - } - } - - this.columns = columns; - }, - - onRender : function(){ - Ext.tree.TreePanel.superclass.onRender.apply(this, arguments); - - this.el.addClass('x-treegrid'); - - this.outerCt = this.body.createChild({ - cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines') - }); - - this.internalTpl.overwrite(this.outerCt, {columns: this.columns}); - - this.mainHd = Ext.get(this.outerCt.dom.firstChild); - this.innerHd = Ext.get(this.mainHd.dom.firstChild); - this.innerBody = Ext.get(this.outerCt.dom.lastChild); - this.innerCt = Ext.get(this.innerBody.dom.firstChild); - - this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns}); - - if(this.hideHeaders){ - this.el.child('.x-grid3-header').setDisplayed('none'); - } - else if(this.enableHdMenu !== false){ - this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'}); - if(this.enableColumnHide !== false){ - this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'}); - this.colMenu.on({ - scope: this, - beforeshow: this.beforeColMenuShow, - itemclick: this.handleHdMenuClick - }); - this.hmenu.add({ - itemId:'columns', - hideOnClick: false, - text: this.columnsText, - menu: this.colMenu, - iconCls: 'x-cols-icon' - }); - } - this.hmenu.on('itemclick', this.handleHdMenuClick, this); - } - }, - - setRootNode : function(node){ - node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI; - node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node); - if(this.innerCt) { - this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns}); - } - return node; - }, - - clearInnerCt : function(){ - if(Ext.isIE){ - var dom = this.innerCt.dom; - while(dom.firstChild){ - dom.removeChild(dom.firstChild); - } - }else{ - Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this); - } - }, - - initEvents : function() { - Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments); - - this.mon(this.innerBody, 'scroll', this.syncScroll, this); - this.mon(this.innerHd, 'click', this.handleHdDown, this); - this.mon(this.mainHd, { - scope: this, - mouseover: this.handleHdOver, - mouseout: this.handleHdOut - }); - }, - - onResize : function(w, h) { - Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments); - - var bd = this.innerBody.dom; - var hd = this.innerHd.dom; - - if(!bd){ - return; - } - - if(Ext.isNumber(h)){ - bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px'; - } - - if(Ext.isNumber(w)){ - var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth()); - if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){ - this.setScrollOffset(sw); - }else{ - var me = this; - setTimeout(function(){ - me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0); - }, 10); - } - } - }, - - updateColumnWidths : function() { - var cols = this.columns, - colCount = cols.length, - groups = this.outerCt.query('colgroup'), - groupCount = groups.length, - c, g, i, j; - - for(i = 0; i 0 && this.columns[index]) { - this.setColumnVisible(index, !item.checked); - } - } - - return true; - }, - - setColumnVisible : function(index, visible) { - this.columns[index].hidden = !visible; - this.updateColumnWidths(); - }, - - /** - * Scrolls the grid to the top - */ - scrollToTop : function(){ - this.innerBody.dom.scrollTop = 0; - this.innerBody.dom.scrollLeft = 0; - }, - - // private - syncScroll : function(){ - this.syncHeaderScroll(); - var mb = this.innerBody.dom; - this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop); - }, - - // private - syncHeaderScroll : function(){ - var mb = this.innerBody.dom; - this.innerHd.dom.scrollLeft = mb.scrollLeft; - this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore) - }, - - registerNode : function(n) { - Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n); - if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) { - n.ui = new Ext.ux.tree.TreeGridNodeUI(n); - } - } -}); - -Ext.reg('treegrid', Ext.ux.tree.TreeGrid); \ No newline at end of file diff --git a/examples/ux/treegrid/TreeGridColumnResizer.js b/examples/ux/treegrid/TreeGridColumnResizer.js deleted file mode 100644 index 1fdc9cbe..00000000 --- a/examples/ux/treegrid/TreeGridColumnResizer.js +++ /dev/null @@ -1,119 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -/** - * @class Ext.tree.ColumnResizer - * @extends Ext.util.Observable - */ -Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, { - /** - * @cfg {Number} minWidth The minimum width the column can be dragged to. - * Defaults to 14. - */ - minWidth: 14, - - constructor: function(config){ - Ext.apply(this, config); - Ext.tree.ColumnResizer.superclass.constructor.call(this); - }, - - init : function(tree){ - this.tree = tree; - tree.on('render', this.initEvents, this); - }, - - initEvents : function(tree){ - tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this); - this.tracker = new Ext.dd.DragTracker({ - onBeforeStart: this.onBeforeStart.createDelegate(this), - onStart: this.onStart.createDelegate(this), - onDrag: this.onDrag.createDelegate(this), - onEnd: this.onEnd.createDelegate(this), - tolerance: 3, - autoStart: 300 - }); - this.tracker.initEl(tree.innerHd); - tree.on('beforedestroy', this.tracker.destroy, this.tracker); - }, - - handleHdMove : function(e, t){ - var hw = 5, - x = e.getPageX(), - hd = e.getTarget('.x-treegrid-hd', 3, true); - - if(hd){ - var r = hd.getRegion(), - ss = hd.dom.style, - pn = hd.dom.parentNode; - - if(x - r.left <= hw && hd.dom !== pn.firstChild) { - var ps = hd.dom.previousSibling; - while(ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) { - ps = ps.previousSibling; - } - if(ps) { - this.activeHd = Ext.get(ps); - ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize'; - } - } else if(r.right - x <= hw) { - var ns = hd.dom; - while(ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) { - ns = ns.previousSibling; - } - if(ns) { - this.activeHd = Ext.get(ns); - ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize'; - } - } else{ - delete this.activeHd; - ss.cursor = ''; - } - } - }, - - onBeforeStart : function(e){ - this.dragHd = this.activeHd; - return !!this.dragHd; - }, - - onStart : function(e){ - this.dragHeadersDisabled = this.tree.headersDisabled; - this.tree.headersDisabled = true; - this.proxy = this.tree.body.createChild({cls:'x-treegrid-resizer'}); - this.proxy.setHeight(this.tree.body.getHeight()); - - var x = this.tracker.getXY()[0]; - - this.hdX = this.dragHd.getX(); - this.hdIndex = this.tree.findHeaderIndex(this.dragHd); - - this.proxy.setX(this.hdX); - this.proxy.setWidth(x-this.hdX); - - this.maxWidth = this.tree.outerCt.getWidth() - this.tree.innerBody.translatePoints(this.hdX).left; - }, - - onDrag : function(e){ - var cursorX = this.tracker.getXY()[0]; - this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth)); - }, - - onEnd : function(e){ - var nw = this.proxy.getWidth(), - tree = this.tree, - disabled = this.dragHeadersDisabled; - - this.proxy.remove(); - delete this.dragHd; - - tree.columns[this.hdIndex].width = nw; - tree.updateColumnWidths(); - - setTimeout(function(){ - tree.headersDisabled = disabled; - }, 100); - } -}); \ No newline at end of file diff --git a/examples/ux/treegrid/TreeGridColumns.js b/examples/ux/treegrid/TreeGridColumns.js deleted file mode 100644 index a532bdc6..00000000 --- a/examples/ux/treegrid/TreeGridColumns.js +++ /dev/null @@ -1,39 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -(function() { - Ext.override(Ext.list.Column, { - init : function() { - var types = Ext.data.Types, - st = this.sortType; - - if(this.type){ - if(Ext.isString(this.type)){ - this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO; - } - }else{ - this.type = types.AUTO; - } - - // named sortTypes are supported, here we look them up - if(Ext.isString(st)){ - this.sortType = Ext.data.SortTypes[st]; - }else if(Ext.isEmpty(st)){ - this.sortType = this.type.sortType; - } - } - }); - - Ext.tree.Column = Ext.extend(Ext.list.Column, {}); - Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {}); - Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {}); - Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {}); - - Ext.reg('tgcolumn', Ext.tree.Column); - Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn); - Ext.reg('tgdatecolumn', Ext.tree.DateColumn); - Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn); -})(); diff --git a/examples/ux/treegrid/TreeGridLoader.js b/examples/ux/treegrid/TreeGridLoader.js deleted file mode 100644 index 62eb417c..00000000 --- a/examples/ux/treegrid/TreeGridLoader.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -/** - * @class Ext.ux.tree.TreeGridLoader - * @extends Ext.tree.TreeLoader - */ -Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, { - createNode : function(attr) { - if (!attr.uiProvider) { - attr.uiProvider = Ext.ux.tree.TreeGridNodeUI; - } - return Ext.tree.TreeLoader.prototype.createNode.call(this, attr); - } -}); \ No newline at end of file diff --git a/examples/ux/treegrid/TreeGridNodeUI.js b/examples/ux/treegrid/TreeGridNodeUI.js deleted file mode 100644 index e12bf950..00000000 --- a/examples/ux/treegrid/TreeGridNodeUI.js +++ /dev/null @@ -1,107 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -/** - * @class Ext.ux.tree.TreeGridNodeUI - * @extends Ext.tree.TreeNodeUI - */ -Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { - isTreeGridNodeUI: true, - - renderElements : function(n, a, targetNode, bulkRender){ - var t = n.getOwnerTree(), - cols = t.columns, - c = cols[0], - i, buf, len; - - this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : ''; - - buf = [ - '', - '', - '', - '', this.indentMarkup, "", - '', - '', - '', - '', (c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text), '', - '' - ]; - - for(i = 1, len = cols.length; i < len; i++){ - c = cols[i]; - buf.push( - '', - '
', - (c.tpl ? c.tpl.apply(a) : a[c.dataIndex]), - '
', - '' - ); - } - - buf.push( - '', - '' - ); - for(i = 0, len = cols.length; i'); - } - buf.push(''); - - if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){ - this.wrap = Ext.DomHelper.insertHtml("beforeBegin", n.nextSibling.ui.getEl(), buf.join('')); - }else{ - this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join('')); - } - - this.elNode = this.wrap.childNodes[0]; - this.ctNode = this.wrap.childNodes[1].firstChild.firstChild; - var cs = this.elNode.firstChild.childNodes; - this.indentNode = cs[0]; - this.ecNode = cs[1]; - this.iconNode = cs[2]; - this.anchor = cs[3]; - this.textNode = cs[3].firstChild; - }, - - // private - animExpand : function(cb){ - this.ctNode.style.display = ""; - Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb); - } -}); - -Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, { - isTreeGridNodeUI: true, - - // private - render : function(){ - if(!this.rendered){ - this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom; - this.node.expanded = true; - } - - if(Ext.isWebKit) { - // weird table-layout: fixed issue in webkit - var ct = this.ctNode; - ct.style.tableLayout = null; - (function() { - ct.style.tableLayout = 'fixed'; - }).defer(1); - } - }, - - destroy : function(){ - if(this.elNode){ - Ext.dd.Registry.unregister(this.elNode.id); - } - delete this.node; - }, - - collapse : Ext.emptyFn, - expand : Ext.emptyFn -}); \ No newline at end of file diff --git a/examples/ux/treegrid/TreeGridSorter.js b/examples/ux/treegrid/TreeGridSorter.js deleted file mode 100644 index 294a7eb5..00000000 --- a/examples/ux/treegrid/TreeGridSorter.js +++ /dev/null @@ -1,137 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -Ext.ns('Ext.ux.tree'); - -/** - * @class Ext.ux.tree.TreeGridSorter - * @extends Ext.tree.TreeSorter - * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}. The TreeGridSorter automatically monitors events on the - * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange). - * Example usage:
- *

- new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
-     folderSort: true,
-     dir: "desc",
-     sortType: function(node) {
-         // sort by a custom, typed attribute:
-         return parseInt(node.id, 10);
-     }
- });
- 
- * @constructor - * @param {TreeGrid} tree - * @param {Object} config - */ -Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, { - /** - * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to ['sort-asc', 'sort-desc']) - */ - sortClasses : ['sort-asc', 'sort-desc'], - /** - * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to 'Sort Ascending') - */ - sortAscText : 'Sort Ascending', - /** - * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to 'Sort Descending') - */ - sortDescText : 'Sort Descending', - - constructor : function(tree, config) { - if(!Ext.isObject(config)) { - config = { - property: tree.columns[0].dataIndex || 'text', - folderSort: true - } - } - - Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments); - - this.tree = tree; - tree.on('headerclick', this.onHeaderClick, this); - tree.ddAppendOnly = true; - - var me = this; - this.defaultSortFn = function(n1, n2){ - - var desc = me.dir && me.dir.toLowerCase() == 'desc', - prop = me.property || 'text', - sortType = me.sortType, - caseSensitive = me.caseSensitive === true, - leafAttr = me.leafAttr || 'leaf', - attr1 = n1.attributes, - attr2 = n2.attributes; - - if(me.folderSort){ - if(attr1[leafAttr] && !attr2[leafAttr]){ - return 1; - } - if(!attr1[leafAttr] && attr2[leafAttr]){ - return -1; - } - } - var prop1 = attr1[prop], - prop2 = attr2[prop], - v1 = sortType ? sortType(prop1) : (caseSensitive ? prop1 : prop1.toUpperCase()); - v2 = sortType ? sortType(prop2) : (caseSensitive ? prop2 : prop2.toUpperCase()); - - if(v1 < v2){ - return desc ? +1 : -1; - }else if(v1 > v2){ - return desc ? -1 : +1; - }else{ - return 0; - } - }; - - tree.on('afterrender', this.onAfterTreeRender, this, {single: true}); - tree.on('headermenuclick', this.onHeaderMenuClick, this); - }, - - onAfterTreeRender : function() { - if(this.tree.hmenu){ - this.tree.hmenu.insert(0, - {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'}, - {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'} - ); - } - this.updateSortIcon(0, 'asc'); - }, - - onHeaderMenuClick : function(c, id, index) { - if(id === 'asc' || id === 'desc') { - this.onHeaderClick(c, null, index); - return false; - } - }, - - onHeaderClick : function(c, el, i) { - if(c && !this.tree.headersDisabled){ - var me = this; - - me.property = c.dataIndex; - me.dir = c.dir = (c.dir === 'desc' ? 'asc' : 'desc'); - me.sortType = c.sortType; - me.caseSensitive === Ext.isBoolean(c.caseSensitive) ? c.caseSensitive : this.caseSensitive; - me.sortFn = c.sortFn || this.defaultSortFn; - - this.tree.root.cascade(function(n) { - if(!n.isLeaf()) { - me.updateSort(me.tree, n); - } - }); - - this.updateSortIcon(i, c.dir); - } - }, - - // private - updateSortIcon : function(col, dir){ - var sc = this.sortClasses, - hds = this.tree.innerHd.select('td').removeClass(sc); - hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]); - } -}); \ No newline at end of file diff --git a/examples/ux/treegrid/treegrid.css b/examples/ux/treegrid/treegrid.css deleted file mode 100644 index 0f946844..00000000 --- a/examples/ux/treegrid/treegrid.css +++ /dev/null @@ -1,57 +0,0 @@ -/*! - * Ext JS Library 3.4.0 - * Copyright(c) 2006-2011 Sencha Inc. - * licensing@sencha.com - * http://www.sencha.com/license - */ -.x-treegrid-root-table { - border-right: 1px solid; -} - -.x-treegrid-root-node { - overflow: auto; -} - -.x-treegrid-hd-hidden { - visibility: hidden; - border: 0; - width: 0; -} - -.x-treegrid-col { - border-bottom: 1px solid; - height: 20px; - overflow: hidden; - vertical-align: top; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; - white-space: nowrap; -} - -.x-treegrid-text { - padding-left: 4px; - -moz-user-select: none; - -khtml-user-select: none; -} - -.x-treegrid-resizer { - border-left:1px solid; - border-right:1px solid; - position:absolute; - left:0; - top:0; -} - -.x-treegrid-header-inner { - overflow: hidden; -} - -.x-treegrid-root-table, -.x-treegrid-col { - border-color: #ededed; -} - -.x-treegrid-resizer { - border-left-color:#555; - border-right-color:#555; -} \ No newline at end of file diff --git a/examples/viewer-layermanager.html b/examples/viewer-layermanager.html index d18744c1..2ab8f5a1 100644 --- a/examples/viewer-layermanager.html +++ b/examples/viewer-layermanager.html @@ -5,12 +5,22 @@ + + + + + + + + + + diff --git a/examples/viewer-layermanager.js b/examples/viewer-layermanager.js index 478ee15f..b363d554 100644 --- a/examples/viewer-layermanager.js +++ b/examples/viewer-layermanager.js @@ -63,6 +63,10 @@ Ext.onReady(function() { ptype: "gxp_layerproperties", outputTarget: "west", actionTarget: ["tree.tbar", "tree.contextMenu"] + }, { + ptype: "gxp_styler", + outputTarget: "west", + actionTarget: ["tree.tbar", "tree.contextMenu"] }, { ptype: "gxp_zoomtoextent", actionTarget: "map.tbar" diff --git a/examples/viewer.html b/examples/viewer.html index 217b0c43..d5303556 100644 --- a/examples/viewer.html +++ b/examples/viewer.html @@ -5,12 +5,21 @@ + + + + + + + + + diff --git a/examples/viewer.js b/examples/viewer.js index c7c1ec64..c3b84316 100644 --- a/examples/viewer.js +++ b/examples/viewer.js @@ -54,6 +54,9 @@ Ext.onReady(function() { }, { ptype: "gxp_layerproperties", actionTarget: ["tree.tbar", "tree.contextMenu"] + }, { + ptype: "gxp_styler", + actionTarget: ["tree.tbar", "tree.contextMenu"] }, { ptype: "gxp_zoomtoextent", actionTarget: "map.tbar" diff --git a/src/script/plugins/AddLayers.js b/src/script/plugins/AddLayers.js index 288a68f9..030aa0d5 100644 --- a/src/script/plugins/AddLayers.js +++ b/src/script/plugins/AddLayers.js @@ -642,7 +642,7 @@ gxp.plugins.AddLayers = Ext.extend(gxp.plugins.Tool, { rec = store.getAt(index), source = rec && this.target.layerSources[rec.get("id")]; if (source) { - if (source.title !== rec.get("title")) { + if (source.title !== rec.get("title") && !Ext.isEmpty(source.title)) { rec.set("title", source.title); sourceComboBox.setValue(rec.get(valueField)); } diff --git a/src/script/plugins/CSWCatalogueSource.js b/src/script/plugins/CSWCatalogueSource.js index 03967a72..ab4c47e7 100644 --- a/src/script/plugins/CSWCatalogueSource.js +++ b/src/script/plugins/CSWCatalogueSource.js @@ -10,7 +10,7 @@ * @requires plugins/CatalogueSource.js * @requires GeoExt/data/CSWRecordsReader.js * @requires GeoExt/data/ProtocolProxy.js - * @requires OpenLayers.Protocol.CSW/v2_0_2.js + * @requires OpenLayers/Protocol/CSW/v2_0_2.js */ /** api: (define) diff --git a/src/script/plugins/WMSSource.js b/src/script/plugins/WMSSource.js index df918e83..e144184c 100644 --- a/src/script/plugins/WMSSource.js +++ b/src/script/plugins/WMSSource.js @@ -201,7 +201,7 @@ gxp.plugins.WMSSource = Ext.extend(gxp.plugins.LayerSource, { * Reload the store when the authorization changes. */ onAuthorizationChange: function() { - if (this.store && this.store.url.charAt(0) === "/") { + if (this.store && this.url.charAt(0) === "/") { this.store.reload(); } }, @@ -558,7 +558,7 @@ gxp.plugins.WMSSource = Ext.extend(gxp.plugins.LayerSource, { record.json = config; } else { - if (window.console && this.store.getCount() > 0) { + if (window.console && this.store.getCount() > 0 && config.name !== undefined) { console.warn("Could not create layer record for layer '" + config.name + "'. Check if the layer is found in the WMS GetCapabilities response."); } } diff --git a/src/script/util.js b/src/script/util.js index b20ee338..9735a5e2 100644 --- a/src/script/util.js +++ b/src/script/util.js @@ -123,6 +123,55 @@ gxp.util = { return a.href; }, + /** api: function[throttle] + * :arg func: ``Function`` + * :arg interval: ``Integer`` + * :arg scope: ``Object`` + * :return: ``Function`` + * + * Returns a function, that, when invoked, will only be triggered at + * most once during a given window of time. + */ + throttle: (function() { + // taken from ExtJS 4.1 + // TODO remove when we upgrade to ExtJS 4.1 or higher. + /** + * Creates a throttled version of the passed function which, when called repeatedly and + * rapidly, invokes the passed function only after a certain interval has elapsed since the + * previous invocation. + * + * This is useful for wrapping functions which may be called repeatedly, such as + * a handler of a mouse move event when the processing is expensive. + * + * @param {Function} fn The function to execute at a regular time interval. + * @param {Number} interval The interval **in milliseconds** on which the passed function is executed. + * @param {Object} scope (optional) The scope (`this` reference) in which + * the passed function is executed. If omitted, defaults to the scope specified by the caller. + * @returns {Function} A function which invokes the passed function at the specified interval. + */ + var createThrottled = function(fn, interval, scope) { + var lastCallTime, elapsed, lastArgs, timer, execute = function() { + fn.apply(scope || this, lastArgs); + lastCallTime = new Date().getTime(); + }; + + return function() { + elapsed = new Date().getTime() - lastCallTime; + lastArgs = arguments; + + clearTimeout(timer); + if (!lastCallTime || (elapsed >= interval)) { + execute(); + } else { + timer = setTimeout(execute, interval - elapsed); + } + }; + }; + return function(func, interval, scope) { + return createThrottled(func, interval, scope); + }; + })(), + /** api: function[md5] * :arg data: ``String`` * :returns: ``String`` md5 hash diff --git a/src/script/widgets/CatalogueSearchPanel.js b/src/script/widgets/CatalogueSearchPanel.js index 3213176b..70ecaf7c 100644 --- a/src/script/widgets/CatalogueSearchPanel.js +++ b/src/script/widgets/CatalogueSearchPanel.js @@ -88,7 +88,7 @@ gxp.CatalogueSearchPanel = Ext.extend(Ext.Panel, { for (var key in this.sources) { sourceComboData.push([key, this.sources[key].title]); } - if (sourceComboData.length === 1) { + if (sourceComboData.length >= 1) { this.selectedSource = sourceComboData[0][0]; } var filterOptions = [['datatype', 'data type'], ['extent', 'spatial extent'], ['category', 'category']]; @@ -109,6 +109,14 @@ gxp.CatalogueSearchPanel = Ext.extend(Ext.Panel, { emptyText: this.searchFieldEmptyText, ref: "../../search", name: "search", + listeners: { + specialkey: function(field, e) { + if (e.getKey() == e.ENTER) { + this.performQuery(); + } + }, + scope: this + }, width: 300 }, { xtype: "button", diff --git a/src/script/widgets/FillSymbolizer.js b/src/script/widgets/FillSymbolizer.js index ff205430..c9f7cf8c 100644 --- a/src/script/widgets/FillSymbolizer.js +++ b/src/script/widgets/FillSymbolizer.js @@ -70,7 +70,7 @@ gxp.FillSymbolizer = Ext.extend(Ext.FormPanel, { border: false, /** i18n */ - fillText: "Fill", + titleText: "Fill", colorText: "Color", opacityText: "Opacity", @@ -86,8 +86,8 @@ gxp.FillSymbolizer = Ext.extend(Ext.FormPanel, { } var sliderValue = 100; - if (this.opacityProperty in this.symbolizer) { - sliderValue = this.symbolizer[this.opacityProperty]; + if (this.opacityProperty in this.symbolizer && this.symbolizer[this.opacityProperty] !== undefined) { + sliderValue = this.symbolizer[this.opacityProperty]*100; } else if (OpenLayers.Renderer.defaultSymbolizer[this.opacityProperty]) { sliderValue = OpenLayers.Renderer.defaultSymbolizer[this.opacityProperty]*100; @@ -95,7 +95,7 @@ gxp.FillSymbolizer = Ext.extend(Ext.FormPanel, { this.items = [{ xtype: "fieldset", - title: this.fillText, + title: this.titleText, autoHeight: true, checkboxToggle: this.checkboxToggle, collapsed: this.checkboxToggle === true && diff --git a/src/script/widgets/FilterBuilder.js b/src/script/widgets/FilterBuilder.js index 1b48a2dc..6240a4e9 100644 --- a/src/script/widgets/FilterBuilder.js +++ b/src/script/widgets/FilterBuilder.js @@ -87,6 +87,15 @@ gxp.FilterBuilder = Ext.extend(Ext.Container, { addConditionText: "add condition", addGroupText: "add group", removeConditionText: "remove condition", + switchToFilterBuilderText: "Switch back to filter builder", + cqlPrefixText: "or ", + cqlText: "use CQL filter instead", + + /** api: config[allowCQL] + * ``Boolean`` If true, provide an option to create a filter by + * providing CQL. Defaults to false + */ + allowCQL: false, /** api: config[allowGroups] * ``Boolean`` @@ -97,6 +106,9 @@ gxp.FilterBuilder = Ext.extend(Ext.Container, { allowGroups: true, initComponent: function() { + if (this.allowCQL) { + this.cqlFormat = new OpenLayers.Format.CQL(); + } var defConfig = { defaultBuilderType: gxp.FilterBuilder.ANY_OF }; @@ -109,6 +121,26 @@ gxp.FilterBuilder = Ext.extend(Ext.Container, { this.builderType = this.getBuilderType(); this.items = [{ + xtype: "container", + layout: "form", + hidden: true, + border: false, + hideLabels: true, + style: "padding-left: 2px", + ref: "cqlForm", + items: [{ + xtype: "textarea", + anchor: '99%', + ref: "../cqlField", + width: '100%', + growMax: 100 + }], + buttons: [{ + text: this.switchToFilterBuilderText, + handler: this.switchToFilterBuilder, + scope: this + }] + }, { xtype: "container", layout: "form", ref: "form", @@ -169,9 +201,48 @@ gxp.FilterBuilder = Ext.extend(Ext.Container, { scope: this }); } + if(this.allowCQL) { + bar.push(this.cqlPrefixText); + bar.push({ + text: this.cqlText, + handler: this.switchToCQL, + scope: this + }); + } return bar; }, - + + /** private: method[switchToCQL] + * Switch from filter builder to CQL. + */ + switchToCQL: function() { + var filter = this.getFilter(); + var CQL = ""; + if (filter !== false) { + CQL = this.cqlFormat.write(filter); + } + this.form.hide(); + this.cqlField.setValue(CQL); + this.cqlForm.show(); + }, + + /** private: method[switchToFilterBuilder] + * Switch from CQL field to filter builder. + */ + switchToFilterBuilder: function() { + var filter = null; + // when parsing fails, we keep the previous filter in the filter builder + try { + filter = this.cqlFormat.read(this.cqlField.getValue()); + } catch(e) { + } + this.cqlForm.hide(); + this.form.show(); + if (filter !== null) { + this.setFilter(filter); + } + }, + /** api: method[getFilter] * :return: ``OpenLayers.Filter`` * diff --git a/src/script/widgets/LayerUploadPanel.js b/src/script/widgets/LayerUploadPanel.js index 0aedc751..c697884c 100644 --- a/src/script/widgets/LayerUploadPanel.js +++ b/src/script/widgets/LayerUploadPanel.js @@ -361,13 +361,18 @@ gxp.LayerUploadPanel = Ext.extend(Ext.FormPanel, { records, tasks, task, msg, i, success = true; if (obj) { - tasks = obj.tasks || [obj.task]; - for (i=tasks.length-1; i>=0; --i) { - task = tasks[i]; - if (task.state !== "READY") { - success = false; - msg = "Source " + task.source.file + " is " + task.state; - break; + if (typeof obj === "string") { + success = false; + msg = obj; + } else { + tasks = obj.tasks || [obj.task]; + for (i=tasks.length-1; i>=0; --i) { + task = tasks[i]; + if (task.state !== "READY") { + success = false; + msg = "Source " + task.source.file + " is " + task.state; + break; + } } } } diff --git a/src/script/widgets/PointSymbolizer.js b/src/script/widgets/PointSymbolizer.js index d103b685..817f684a 100644 --- a/src/script/widgets/PointSymbolizer.js +++ b/src/script/widgets/PointSymbolizer.js @@ -46,6 +46,13 @@ gxp.PointSymbolizer = Ext.extend(Ext.Panel, { symbolText: "Symbol", sizeText: "Size", rotationText: "Rotation", + + /** api: config[filter] + * ``Integer`` One of gxp.PointSymbolizer.GRAPHIC or + * gxp.PointSymbolizer.MARK. If specified, this dialog will only show + * those elements relevant to the filter. + */ + filter: null, /** api: config[pointGraphics] * ``Array`` @@ -99,12 +106,14 @@ gxp.PointSymbolizer = Ext.extend(Ext.Panel, { {display: this.graphicTriangleText, value: "triangle", mark: true}, {display: this.graphicStarText, value: "star", mark: true}, {display: this.graphicCrossText, value: "cross", mark: true}, - {display: this.graphicXText, value: "x", mark: true}, - {display: this.graphicExternalText} + {display: this.graphicXText, value: "x", mark: true} ]; + if (this.filter !== gxp.PointSymbolizer.MARK) { + this.pointGraphics.push({display: this.graphicExternalText}); + } } - this.external = !!this.symbolizer["externalGraphic"]; + this.external = !!this.symbolizer["externalGraphic"] || this.filter === gxp.PointSymbolizer.GRAPHIC; this.markPanel = new Ext.Panel({ border: false, @@ -141,7 +150,7 @@ gxp.PointSymbolizer = Ext.extend(Ext.Panel, { name: "url", fieldLabel: this.urlText, value: this.symbolizer["externalGraphic"], - hidden: true, + hidden: (this.filter !== gxp.PointSymbolizer.GRAPHIC), listeners: { change: function(field, value) { this.symbolizer["externalGraphic"] = value; @@ -183,6 +192,7 @@ gxp.PointSymbolizer = Ext.extend(Ext.Panel, { this.items = [{ xtype: "combo", name: "mark", + hidden: (this.filter === gxp.PointSymbolizer.GRAPHIC), fieldLabel: this.symbolText, store: new Ext.data.JsonStore({ data: {root: this.pointGraphics}, @@ -213,11 +223,11 @@ gxp.PointSymbolizer = Ext.extend(Ext.Panel, { if(value) { this.urlField.hide(); // this to hide the container - otherwise the label remains - this.urlField.getEl().up('.x-form-item').setDisplayed(false); + //this.urlField.getEl().up('.x-form-item').setDisplayed(false); this.symbolizer["externalGraphic"] = value; } else { this.urlField.show(); - this.urlField.getEl().up('.x-form-item').setDisplayed(true); + //this.urlField.getEl().up('.x-form-item').setDisplayed(true); } if(!this.external) { this.external = true; @@ -295,5 +305,8 @@ gxp.PointSymbolizer = Ext.extend(Ext.Panel, { }); +gxp.PointSymbolizer.GRAPHIC = 0; +gxp.PointSymbolizer.MARK = 1; + /** api: xtype = gxp_pointsymbolizer */ Ext.reg('gxp_pointsymbolizer', gxp.PointSymbolizer); diff --git a/src/script/widgets/PolygonSymbolizer.js b/src/script/widgets/PolygonSymbolizer.js index 035e6256..c23b082d 100644 --- a/src/script/widgets/PolygonSymbolizer.js +++ b/src/script/widgets/PolygonSymbolizer.js @@ -74,5 +74,5 @@ gxp.PolygonSymbolizer = Ext.extend(Ext.Panel, { }); -/** api: xtype = gxp_linesymbolizer */ +/** api: xtype = gxp_polygonsymbolizer */ Ext.reg('gxp_polygonsymbolizer', gxp.PolygonSymbolizer); diff --git a/src/script/widgets/RulePanel.js b/src/script/widgets/RulePanel.js index a602089e..8083e4c5 100644 --- a/src/script/widgets/RulePanel.js +++ b/src/script/widgets/RulePanel.js @@ -13,6 +13,7 @@ * @include widgets/LineSymbolizer.js * @include widgets/PointSymbolizer.js * @include widgets/FilterBuilder.js + * @include widgets/grid/SymbolizerGrid.js */ /** api: (define) @@ -28,7 +29,7 @@ Ext.namespace("gxp"); * Create a panel for assembling SLD rules. */ gxp.RulePanel = Ext.extend(Ext.TabPanel, { - + /** api: property[fonts] * ``Array(String)`` List of fonts for the font combo. If not set, * defaults to the list provided by the . @@ -127,14 +128,14 @@ gxp.RulePanel = Ext.extend(Ext.TabPanel, { modifyScaleTipContext: Ext.emptyFn, /** i18n */ - labelFeaturesText: "Label Features", - labelsText: "Labels", - basicText: "Basic", - advancedText: "Advanced", + ruleText: "Rule", + symbologyText: "Symbology", limitByScaleText: "Limit by scale", - limitByConditionText: "Limit by condition", - symbolText: "Symbol", - nameText: "Name", + limitByConditionText: "Limit with filters", + symbolText: "Preview", + nameText: "Label", + legendPropertiesText: "Legend properties", + propertiesSuffix: "Properties", /** private */ initComponent: function() { @@ -157,18 +158,6 @@ gxp.RulePanel = Ext.extend(Ext.TabPanel, { this.activeTab = 0; - this.textSymbolizer = new gxp.TextSymbolizer({ - symbolizer: this.getTextSymbolizer(), - attributes: this.attributes, - fonts: this.fonts, - listeners: { - change: function(symbolizer) { - this.fireEvent("change", this, this.rule); - }, - scope: this - } - }); - /** * The interpretation here is that scale values of zero are equivalent to * no scale value. If someone thinks that a scale value of zero should have @@ -196,6 +185,7 @@ gxp.RulePanel = Ext.extend(Ext.TabPanel, { this.filterBuilder = new gxp.FilterBuilder({ allowGroups: this.nestedFilters, + allowCQL: true, filter: this.rule && this.rule.filter && this.rule.filter.clone(), attributes: this.attributes, listeners: { @@ -208,108 +198,12 @@ gxp.RulePanel = Ext.extend(Ext.TabPanel, { } }); - this.items = [{ - title: this.labelsText, - autoScroll: true, - bodyStyle: {"padding": "10px"}, - items: [{ - xtype: "fieldset", - title: this.labelFeaturesText, - autoHeight: true, - checkboxToggle: true, - collapsed: !this.hasTextSymbolizer(), - items: [ - this.textSymbolizer - ], - listeners: { - collapse: function() { - OpenLayers.Util.removeItem(this.rule.symbolizers, this.getTextSymbolizer()); - this.fireEvent("change", this, this.rule); - }, - expand: function() { - this.setTextSymbolizer(this.textSymbolizer.symbolizer); - this.fireEvent("change", this, this.rule); - }, - scope: this - } - }] - }]; if (this.getSymbolTypeFromRule(this.rule) || this.symbolType) { - this.items = [{ - title: this.basicText, - autoScroll: true, - items: [this.createHeaderPanel(), this.createSymbolizerPanel()] - }, this.items[0], { - title: this.advancedText, - defaults: { - style: { - margin: "7px" - } - }, - autoScroll: true, - items: [{ - xtype: "fieldset", - title: this.limitByScaleText, - checkboxToggle: true, - collapsed: !(this.rule && (this.rule.minScaleDenominator || this.rule.maxScaleDenominator)), - autoHeight: true, - items: [this.scaleLimitPanel], - listeners: { - collapse: function() { - delete this.rule.minScaleDenominator; - delete this.rule.maxScaleDenominator; - this.fireEvent("change", this, this.rule); - }, - expand: function() { - /** - * Start workaround for - * http://projects.opengeo.org/suite/ticket/676 - */ - var tab = this.getActiveTab(); - this.activeTab = null; - this.setActiveTab(tab); - /** - * End workaround for - * http://projects.opengeo.org/suite/ticket/676 - */ - var changed = false; - if (this.scaleLimitPanel.limitMinScaleDenominator) { - this.rule.minScaleDenominator = this.scaleLimitPanel.minScaleDenominator; - changed = true; - } - if (this.scaleLimitPanel.limitMaxScaleDenominator) { - this.rule.maxScaleDenominator = this.scaleLimitPanel.maxScaleDenominator; - changed = true; - } - if (changed) { - this.fireEvent("change", this, this.rule); - } - }, - scope: this - } - }, { - xtype: "fieldset", - title: this.limitByConditionText, - checkboxToggle: true, - collapsed: !(this.rule && this.rule.filter), - autoHeight: true, - items: [this.filterBuilder], - listeners: { - collapse: function(){ - delete this.rule.filter; - this.fireEvent("change", this, this.rule); - }, - expand: function(){ - var changed = false; - this.rule.filter = this.filterBuilder.getFilter(); - this.fireEvent("change", this, this.rule); - }, - scope: this - } - }] - }]; + this.items = [ + this.createRulePanel(), + this.createSymbolizerPanel() + ]; } - this.items[0].autoHeight = true; this.addEvents( /** api: events[change] @@ -332,51 +226,6 @@ gxp.RulePanel = Ext.extend(Ext.TabPanel, { gxp.RulePanel.superclass.initComponent.call(this); }, - /** private: method[hasTextSymbolizer] - */ - hasTextSymbolizer: function() { - var candidate, symbolizer; - for (var i=0, ii=this.rule.symbolizers.length; i", { - text: this.cancelText, + text: this.revertText, iconCls: "cancel", handler: function() { this.saveRule(ruleDlg.rulePanel, origRule); ruleDlg.destroy(); }, scope: this - }, { - text: this.saveText, - iconCls: "save", - handler: function() { ruleDlg.destroy(); } }] }); this.showDlg(ruleDlg); diff --git a/src/script/widgets/grid/SymbolizerGrid.js b/src/script/widgets/grid/SymbolizerGrid.js index 0ecbcd9a..be22b20e 100644 --- a/src/script/widgets/grid/SymbolizerGrid.js +++ b/src/script/widgets/grid/SymbolizerGrid.js @@ -30,7 +30,19 @@ gxp.grid.SymbolizerGrid = Ext.ux && Ext.ux.tree && Ext.ux.tree.TreeGrid && Ext.e */ symbolizers: null, + /** api: config[swatchSize] + * ``Array`` Width and height of the swatches / feature renderers. + * Defaults to 21 pixels. + */ + swatchSize: [21, 21], + + /** api: config[symbolType] + * ``String`` Main symbol type to use, e.g. Point or Polygon. + */ + symbolType: null, + /** private overrides */ + enableDD: true, enableHdMenu: false, enableSort: false, useArrows: false, @@ -45,19 +57,43 @@ gxp.grid.SymbolizerGrid = Ext.ux && Ext.ux.tree && Ext.ux.tree.TreeGrid && Ext.e * Initializes the SymbolizerGrid. */ initComponent: function() { + this.dropConfig = Ext.apply(this.dropConfig || {}, { + isValidDropPoint : function(n, pt, dd, e, data){ + return (pt !== 'append') && (n.node.parentNode === data.node.parentNode); + } + }); this.on('checkchange', this.onCheckChange, this); + this.on('movenode', this.onMoveNode, this); this.loader = new gxp.tree.SymbolizerLoader({ - symbolizers: this.symbolizers + symbolizers: this.symbolizers, + symbolType: this.symbolType, + listeners: { + 'nodeadded': function(loader, node) { + this.createSwatches(node); + }, + scope: this + } }); this.columns = [{ header: this.typeTitle, - dataIndex: 'type', + dataIndex: 'text', width: 200 }, { header: this.previewTitle, - width: 100, + width: 85, dataIndex: 'preview' }]; + this.addEvents( + /** + * Event: change + * Fires when the filter changes. + * + * Listener arguments: + * grid - {gxp.grid.SymbolizerGrid} This symbolizer grid. Call + * ``getSymbolizers`` to get the updated state. + */ + "change" + ); gxp.grid.SymbolizerGrid.superclass.initComponent.call(this); }, @@ -68,23 +104,61 @@ gxp.grid.SymbolizerGrid = Ext.ux && Ext.ux.tree && Ext.ux.tree.TreeGrid && Ext.e * have any visible children will be filtered out. */ getSymbolizers: function() { - var symbolizers = []; + var symbolizers = {}; + var keys = []; this.root.eachChild(function(n){ - var childVisible = false; + var type = n.attributes.type, + i, ii, j, + result = []; + keys.push(type); n.eachChild(function(c) { - var type = c.attributes.type.toLowerCase(); - if (type !== "label") { - n.attributes.originalSymbolizer[type] = c.attributes.checked; - } - if (c.attributes.checked === true) { - childVisible = true; + if (c.getUI().isChecked() === true) { + var subType = c.attributes.type; + var emptyFound = false, obj; + for (i=0, ii=result.length; i=0; --i) { + var key = keys[i]; + if (symbolizers[key].length > 0) { + for (j=symbolizers[key].length-1; j>=0; --j) { + var s; + if (key === "Point") { + // every subType should create its own symbolizer + var tmp = []; + for (s in symbolizers[key][j]) { + tmp.push(new OpenLayers.Symbolizer[key](symbolizers[key][j][s])); + } + result = result.concat(tmp.reverse()); + } else { + var config = {}; + for (s in symbolizers[key][j]) { + if (s !== "Label") { + config[s.toLowerCase()] = true; + } + config = Ext.applyIf(config, symbolizers[key][j][s]); + } + result.push(new OpenLayers.Symbolizer[key](config)); + } + } + } + } + return result; }, /** private: method[beforeDestroy] @@ -100,6 +174,61 @@ gxp.grid.SymbolizerGrid = Ext.ux && Ext.ux.tree && Ext.ux.tree.TreeGrid && Ext.e gxp.grid.SymbolizerGrid.superclass.onDestroy.call(this); }, + /** api: method[updateSwatch] + * :arg node: ``Ext.data.Node`` + * :arg newSymbolizer: ``Object`` + */ + updateSwatch: function(node, newSymbolizer) { + node.attributes.featureRenderer.drawFeature(); + if (node.parentNode.attributes.featureRenderer) { + node.parentNode.attributes.featureRenderer.drawFeature(); + } else { + node.cascade(function(c) { + c.attributes.featureRenderer.drawFeature(); + }); + } + if (newSymbolizer) { + this.fireEvent("change", this); + } + }, + + /** private: method[getNodeIndex] + * :arg node: ``Ext.data.Node`` + * :returns: ``Integer`` The index + * + * Get the index of the node in the tree, ignore unchecked nodes. + */ + getNodeIndex: function(node) { + var p = node.parentNode; + var idx = 0; + p.eachChild(function(c) { + if (c.getUI().isChecked() === true) { + if (node === c) { + return false; + } + idx++; + } + }); + return idx; + }, + + /** private: method[onMoveNode] + * + * Handle the movenode event for drag and drop. Update the symbolizers + * and their swatches. + */ + onMoveNode: function(tree, node, oldParent, newParent, index) { + var p = node.parentNode; + if (p !== this.getRootNode()) { + OpenLayers.Util.removeItem(p.attributes.symbolizer, node.attributes.symbolizer); + // we cannot use index directly since it takes into account unchecked nodes + var idx = this.getNodeIndex(node); + p.attributes.symbolizer.splice(idx, 0, node.attributes.symbolizer); + this.updateSwatch(node); + } + this.fireEvent("change", this); + }, + /** private: method[onCheckChange] * :arg node: ``Ext.data.Node`` * :arg checked: ``Boolean`` @@ -108,43 +237,17 @@ gxp.grid.SymbolizerGrid = Ext.ux && Ext.ux.tree && Ext.ux.tree.TreeGrid && Ext.e * swatches. */ onCheckChange: function(node, checked) { - var a = node.attributes; - var r = a.featureRenderer; - var type = a.type.toLowerCase(); - var symbolizer = a.symbolizer; - var fullSymbolizer = node.parentNode.attributes.symbolizer; - if (type !== "label") { - // special handling for graphic, can only be turned on if label is on - if (type === 'graphic') { - var label = node.parentNode.findChild('type', 'Label'); - if (label !== null) { - var labelChecked = label.attributes.checked; - if ((labelChecked && checked) || !checked) { - fullSymbolizer[type] = symbolizer[type] = checked; - } else { - node.getUI().toggleCheck(false); - } - } - } else { - fullSymbolizer[type] = symbolizer[type] = checked; - } + var p = node.parentNode; + if (checked === false) { + node.attributes.featureRenderer.hide(); + OpenLayers.Util.removeItem(p.attributes.symbolizer, node.attributes.symbolizer); } else { - if (!checked) { - symbolizer[type] = fullSymbolizer[type] = ""; - var graphic = node.parentNode.findChild('type', 'Graphic'); - if (graphic !== null) { - graphic.getUI().toggleCheck(false); - } - } else { - symbolizer[type] = fullSymbolizer[type] = "Ab"; - } - } - if (node.parentNode.attributes.featureRenderer) { - node.parentNode.attributes.featureRenderer.update({ - symbolizers: [fullSymbolizer] - }); + node.attributes.featureRenderer.show(); + var idx = this.getNodeIndex(node); + p.attributes.symbolizer.splice(idx, 0, node.attributes.symbolizer); } - r.update({symbolizers: [symbolizer]}); + this.updateSwatch(node); + this.fireEvent("change", this); }, /** private: method[afterRender] @@ -152,19 +255,32 @@ gxp.grid.SymbolizerGrid = Ext.ux && Ext.ux.tree && Ext.ux.tree.TreeGrid && Ext.e */ afterRender: function() { gxp.grid.SymbolizerGrid.superclass.afterRender.call(this); - this.root.cascade(function(node) { - if (node.attributes.rendererId) { - var ct = Ext.get(node.attributes.rendererId); + this.createSwatches(this.root); + }, + + /** private: method[createSwatches] + * :arg pNode: ``Ext.data.Node`` + * + * Create the FeatureRenderer instances on a node and all its subnodes. + */ + createSwatches: function(pNode) { + pNode.cascade(function(node) { + var attr = node.attributes; + if (attr.rendererId) { + var ct = Ext.get(attr.rendererId); if (ct) { - node.attributes.featureRenderer = new GeoExt.FeatureRenderer({ - symbolizers: [node.attributes.symbolizer], - renderTo: ct, - width:21, - height: 21 + attr.featureRenderer = new GeoExt.FeatureRenderer({ + labelText: "Ab", + hidden: !node.getUI().isChecked(), + symbolizers: Ext.isArray(attr.symbolizer) ? attr.symbolizer : [attr.symbolizer], + symbolType: attr.symbolType, + renderTo: ct, + width: this.swatchSize[0], + height: this.swatchSize[1] }); } } - }); + }, this); } }); diff --git a/src/script/widgets/tree/SymbolizerLoader.js b/src/script/widgets/tree/SymbolizerLoader.js index 69010d7b..0a68eaf8 100644 --- a/src/script/widgets/tree/SymbolizerLoader.js +++ b/src/script/widgets/tree/SymbolizerLoader.js @@ -19,6 +19,31 @@ Ext.ns("gxp.tree"); gxp.tree.SymbolizerLoader = function(config) { Ext.apply(this, config); + this.addEvents( + /** + * @event beforeload + * Fires before loading the tree nodes. + * @param {Object} This TreeLoader object. + * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded. + * @param {Object} callback The callback function specified in the {@link #load} call. + */ + "beforeload", + /** + * @event load + * Fires when the node has been successfuly loaded. + * @param {Object} This TreeLoader object. + * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded. + * @param {Object} response The response object containing the data from the server. + */ + "load", + /** + * @event nodeadded + * Fires when a new node has been added after load. + * @param {Object} This TreeLoader object. + * @param {Object} node The {@link Ext.tree.TreeNode} object being added. + */ + "nodeadded" + ); gxp.tree.SymbolizerLoader.superclass.constructor.call(this); }; @@ -34,6 +59,16 @@ Ext.extend(gxp.tree.SymbolizerLoader, Ext.util.Observable, { */ symbolizers: null, + /** private: config[divTpl] + * ``Ext.Template`` The template used for the swatches. + */ + divTpl: new Ext.Template('
'), + + /** private: config[emptyIconCls] + * ``String`` The css class to use on nodes without an icon in front. + */ + emptyIconCls: "gxp-icon-symbolgrid-none", + /** private: method[load] * :param node: ``Ext.tree.TreeNode`` The node to add children to. * :param callback: ``Function`` @@ -43,85 +78,133 @@ Ext.extend(gxp.tree.SymbolizerLoader, Ext.util.Observable, { while (node.firstChild) { node.removeChild(node.firstChild); } - var divTpl = new Ext.Template('
'); - for (var i=0, ii=this.symbolizers.length;i=0; --i) { var symbolizer = this.symbolizers[i]; - var key = symbolizer.CLASS_NAME.substring(symbolizer.CLASS_NAME.lastIndexOf(".")+1); - var fullSymbolizer = symbolizer.clone(); - if (key === 'Text') { - fullSymbolizer.label = "Ab"; - if (fullSymbolizer.fillColor || fullSymbolizer.graphicName) { - fullSymbolizer.graphic = true; + var className = symbolizer.CLASS_NAME; + var type = className.substr(className.lastIndexOf(".")+1); + if (OpenLayers.Util.indexOf(typeSeq, type) === -1) { + typeSeq.splice(0, 0, type); + } + split = this.splitSymbolizer(symbolizer); + for (s in split) { + symbolizers[type][s].push(split[s]); + } + } + var types = { + Polygon: ['Polygon', 'Line', 'Point', 'Text'], + Line: ['Line', 'Text'], + Point: ['Point', 'Text'], + Text: ['Text'] + }; + // check if we are missing symbolizers, if yes, create one + var typesNeeded = types[this.symbolType]; + for (i=0, ii=typesNeeded.length; i=0; --i) { + var key = typeSeq[i]; + if (symbolizers[key].empty === false) { + var id = Ext.id(); + var text = key; + if (key !== 'Text') { + text += 's'; // plural + } + var child = this.createNode({ + type: key, + text: text, + symbolizer: [], + symbolType: key, + expanded: true, rendererId: id, - preview: divTpl.applyTemplate({id: id}) - })); + iconCls: 'gxp-icon-symbolgrid-' + key.toLowerCase(), + preview: this.divTpl.applyTemplate({id: id}) + }); + for (var subKey in symbolizers[key]) { + for (var j=0, jj=symbolizers[key][subKey].length; j - - - - - - - + + + + + + @@ -27,17 +26,17 @@ strokeColor: "red", strokeWidth: 1 })); - symbolizers.push(new OpenLayers.Symbolizer.Line({ - strokeColor: "#669900", - strokeWidth: 3 - })); symbolizers.push(new OpenLayers.Symbolizer.Polygon({ fillColor: "olive", - fillOpacity: 0.25, strokeColor: "#666666", strokeWidth: 2, strokeDashstyle: "dot" })); + symbolizers.push(new OpenLayers.Symbolizer.Polygon({ + fillColor: "blue", + strokeColor: "black", + strokeWidth: 2 + })); symbolizers.push(new OpenLayers.Symbolizer.Text({ label: "${name}", labelAlign: "cm", @@ -52,15 +51,15 @@ function test_SymbolizerGrid(t) { t.plan(1); - var instance = new gxp.grid.SymbolizerGrid({symbolizers: createSymbolizers()}); + var instance = new gxp.grid.SymbolizerGrid({symbolizers: createSymbolizers(), symbolType: 'Polygon'}); t.ok(instance instanceof gxp.grid.SymbolizerGrid, "Instance created successfully"); instance.destroy(); } function test_getSymbolizers(t) { - t.plan(2); - var instance = new gxp.grid.SymbolizerGrid({renderTo: Ext.getBody(), symbolizers: createSymbolizers()}); + t.plan(5); + var instance = new gxp.grid.SymbolizerGrid({renderTo: Ext.getBody(), symbolizers: createSymbolizers(), symbolType: 'Polygon'}); instance.render(); t.eq(instance.getSymbolizers().length, 4, "All symbolizers returned"); instance.root.eachChild(function(node) { @@ -72,6 +71,11 @@ }); }); t.eq(instance.getSymbolizers().length, 3, "Point symbolizer excluded since all children are unchecked"); + var poly = instance.root.findChild('type', 'Polygon'); + t.eq(poly.childNodes.length, 6, "The two PolygonSymbolizers are combined in one tree node, and an empty symbolizer was added as well"); + t.eq(poly.attributes.symbolizer[0].fillColor, "olive", "The olive fill is on top"); + poly.findChild('checked', false).getUI().toggleCheck(true); + t.eq(poly.childNodes.length, 7, "Extra node inserted if last unchecked node gets checked"); instance.destroy(); } diff --git a/tests/script/widgets/tree/SymbolizerLoader.html b/tests/script/widgets/tree/SymbolizerLoader.html index 6052a00e..609c221a 100644 --- a/tests/script/widgets/tree/SymbolizerLoader.html +++ b/tests/script/widgets/tree/SymbolizerLoader.html @@ -6,13 +6,12 @@ - - - - - - - + + + + + + @@ -27,10 +26,6 @@ strokeColor: "red", strokeWidth: 1 })); - symbolizers.push(new OpenLayers.Symbolizer.Line({ - strokeColor: "#669900", - strokeWidth: 3 - })); symbolizers.push(new OpenLayers.Symbolizer.Polygon({ fillColor: "olive", fillOpacity: 0.25, @@ -59,8 +54,8 @@ } function test_load(t) { - t.plan(15); - var loader = new gxp.tree.SymbolizerLoader({symbolizers: createSymbolizers()}); + t.plan(19); + var loader = new gxp.tree.SymbolizerLoader({symbolType: "Polygon", symbolizers: createSymbolizers()}); var root = new Ext.tree.TreeNode(); var log = []; loader.load(root, function() { @@ -69,21 +64,25 @@ log.push(node); } }); - t.eq(log.length, 11, "We expect 11 nodes"); - t.eq(log[0].attributes.type, "Point", "Correct type"); - t.eq(log[1].attributes.type, "Stroke", "Correct subType created"); - t.eq(log[2].attributes.type, "Fill", "Correct subType created"); - t.eq(log[1].attributes.symbolizer.fill, false, "fill set to false for the stroke part of the the point symbolizer"); - t.eq(log[2].attributes.symbolizer.stroke, false, "stroke set to false for the fill part of the the point symbolizer"); - t.eq(log[3].attributes.type, "Line", "Correct type"); + t.eq(log.length, 14, "We expect 14 nodes"); + t.eq(log[0].attributes.type, "Text", "Correct type"); + t.eq(log[1].text, "${name}", "Fieldname used in node text"); + t.eq(log[1].attributes.type, "Label", "Correct subType created"); + t.ok(log[2].attributes.type === "Label" && log[2].getUI().isChecked() === false, "Correct subType created, but unchecked"); + t.eq(log[3].attributes.type, "Polygon", "Correct type"); + t.eq(log[4].attributes.symbolizer.fill, false, "For Stroke, fill is false"); t.eq(log[4].attributes.type, "Stroke", "Correct subType"); - t.eq(log[5].attributes.type, "Polygon", "Correct type"); - t.eq(log[8].attributes.type, "Text", "Correct type"); - t.eq(log[9].attributes.type, "Label", "Correct subType"); - t.eq(log[9].attributes.symbolizer.graphic, false, "Graphic set to false"); - t.eq(log[9].attributes.symbolizer.label, "Ab", "Label is correct"); - t.eq(log[10].attributes.type, "Graphic", "Correct subType"); - t.eq(log[10].attributes.symbolizer.label, "", "Label is emptied for graphic subType"); + t.ok(log[5].attributes.type === "Stroke" && log[5].getUI().isChecked() === false, "Correct subType created, but unchecked"); + t.eq(log[6].attributes.type, "Fill", "Correct subType"); + t.eq(log[6].attributes.symbolizer.stroke, false, "For Fill, stroke is false"); + t.ok(log[7].attributes.type === "Fill" && log[7].getUI().isChecked() === false, "Correct subType created, but unchecked"); + t.eq(log[8].attributes.type, "Point", "Correct type"); + t.eq(log[9].attributes.type, "Graphic", "Correct subType"); + t.eq(log[10].attributes.type, "Mark", "Correct subType"); + t.ok(log[11].attributes.type === "Mark" && log[11].getUI().isChecked() === false, "Correct subType created, but unchecked"); + t.eq(log[12].attributes.type, "Line", "Correct type"); + t.eq(log[13].attributes.type, "Stroke", "Correct subType"); + t.ok(log[13].getUI().isChecked() === false, "Node is unchecked"); }); }