dojo.provide("dojox.layout.GridContainer"); dojo.experimental("dojox.layout.GridContainer"); dojo.require("dijit._base.focus"); dojo.require("dijit._Templated"); dojo.require("dijit._Container"); dojo.require("dijit._Contained"); dojo.require("dojo.dnd.move"); dojo.require("dojox.layout.dnd.PlottedDnd"); dojo.requireLocalization("dojox.layout", "GridContainer"); dojo.declare("dojox.layout.GridContainer", [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained], { // summary: // The Grid Container is a container of child elements that are placed in a kind of grid. // // description: // It displays the child elements by column (ie: the childs widths are fixed by the column width of the grid but the childs heights are free). // Each child is movable by drag and drop inside the Grid Container. // The position of other children is automatically calculated when a child is moved // templatePath: dojo.moduleUrl("dojox.layout", "resources/GridContainer.html"), isContainer: true, // i18n: Object // Contain i18n ressources. i18n: null, //isAutoOrganized: Boolean: // Define auto organisation of children into the grid container. isAutoOrganized : true, //isRightFixed: Boolean // Define if the right border has a fixed size. isRightFixed:false, //isLeftFixed: Boolean // Define if the left border has a fixed size. isLeftFixed:false, // hasResizableColumns: Boolean // Allow or not resizing of columns by a grip handle. hasResizableColumns:true, // nbZones: Integer // The number of dropped zones. nbZones:1, //opacity: Integer // Define the opacity of the DnD Avatar. opacity:1, // minColWidth: Integer // Minimum column width in percentage. minColWidth: 20, // minChildWidth: Integer // Minimun children with in pixel (only used for IE6 that doesn't handle min-width css property */ minChildWidth : 150, // acceptTypes: Array // The gridcontainer will only accept the children that fit to the types. // In order to do that, the child must have a widgetType or a dndType attribute corresponding to the accepted type.*/ acceptTypes: [], //mode: String // location to add columns, must be set to left or right(default) mode: "right", //allowAutoScroll: Boolean // auto-scrolling enable inside the GridContainer allowAutoScroll: false, //timeDisplayPopup: Integer // display time of popup in miliseconds timeDisplayPopup: 1500, //isOffset: Boolean // if true : Let the mouse to its original location when moving (allow to specify it proper offset) // if false : Current behavior, mouse in the upper left corner of the widget isOffset: false, //offsetDrag: Object // Allow to specify its own offset (x and y) onl when Parameter isOffset is true offsetDrag : {}, // //withHandles: Boolean // Specify if there is a specific drag handle on widgets withHandles: false, // handleClasses: Array // Array of classes of nodes that will act as drag handles handleClasses : [], //Contains the DnD widget _draggedWidget: null, //_isResized: Boolean // Determine if user can resizing the widget with the mouse. _isResized: false, //Contains the node grip to resize widget. _activeGrip: null, //_oldwidth: Integer // Save the old width size. _oldwidth: 0, //_oldheight: Integer // Save the old height size. _oldheight: 0, // a11y with keyboard is On/Off _a11yOn : false, // can display a popup _canDisplayPopup : true, constructor: function(props, node){ // FIXME: does this need a "scopeName" this.acceptTypes = props["acceptTypes"] || ["dijit.layout.ContentPane"]; this.dragOffset = props["dragOffset"] || { x:0, y:0 }; }, postMixInProperties: function(){ this.i18n = dojo.i18n.getLocalization("dojox.layout", "GridContainer"); }, _createCells: function() { if(this.nbZones === 0){ this.nbZones = 1; } var wCol = 100 / this.nbZones; if(dojo.isIE && dojo.marginBox(this.gridNode).height){ var space = document.createTextNode(" "); this.gridNode.appendChild(space); } var grid = []; this.cell = []; var i = 0; while(i < this.nbZones){ var node = dojo.doc.createElement("td"); dojo.addClass(node, "gridContainerZone"); node.id = this.id + "_dz" + i; node.style.width = wCol + "%"; var zone = this.gridNode.appendChild(node); this.cell[i] = zone; i++; } }, startup:function(){ this.inherited(arguments); this._createCells(); if(this.usepref !== true){ this[(this.isAutoOrganized ? "_organizeServices" : "_organizeServicesManually")](); }else{ //console.info("GridContainer organised by UserPref"); return; } this.init(); dojo.forEach(this.getChildren(), function(child){ !child.started && !child._started && child.startup(); }); }, init: function(){ // summary: Initialization of the GridContainer widget this.grid = this._createGrid(); this.connect(dojo.global, "onresize", "onResized"); this.connect(this, "onDndDrop", "_placeGrips"); this.dropHandler= dojo.subscribe("/dnd/drop", this, "_placeGrips"); this._oldwidth = this.domNode.offsetWidth; if(this.hasResizableColumns){ this._initPlaceGrips(); this._placeGrips(); } }, destroy: function(){ // summary: destroy GridContainer Component. for(var i = 0; i < this.handleDndStart; i++){ dojo.disconnect(this.handleDndStart[i]); } dojo.unsubscribe(this.dropHandler); this.inherited(arguments); }, /* FIXME: implement resize / BorderContainer support resize: function(){ console.log('resize',arguments) }, */ onResized: function(){ // summary: Callback method to resize the GridContainer widget and columns if(this.hasResizableColumns){ this._placeGrips(); this._oldwidth = this.domNode.offsetWidth; this._oldheight = this.domNode.offsetHeight; } }, /***********services methods******************/ _organizeServices : function(){ //summary: List all zones and insert service into columns. var nbz = this.nbZones; var nbs = this.getChildren().length; var res = Math.floor(nbs / nbz); var mod = nbs % nbz; var i = 0; for(var z = 0; z < nbz; z++){ for(var r = 0; r < res; r++){ this._insertService(z, i++, 0, true); } if(mod>0){ try { this._insertService(z, i++, 0, true); } catch (e) { console.error("Unable to insert service in grid container", e, this.getChildren()); } mod--; }else if(res === 0){ break; } } }, _organizeServicesManually : function (){ //summary: Organize Services by column property of widget. var children = this.getChildren(); for(var i = 0; i < children.length; i++){ try{ this._insertService(children[i].column - 1, i, 0, true); }catch(e){ console.error("Unable to insert service in grid container", e, children[i]); } } }, _insertService : function(/*Integer*/z, /*Integer*/p, /*Integer*/i, /*Boolean*/first){ //summary: Insert a service in a specific column of the GridContainer widget. var zone = this.cell[z]; var kidsZone = zone.childNodes.length; var service = this.getChildren()[(i ? i : 0)]; if(typeof(p)=="undefined" || p > kidsZone){ p = kidsZone; } var toto = dojo.place(service.domNode,zone, p); service.domNode.setAttribute("tabIndex", 0); if(!service.dragRestriction) dojo.addClass(service.domNode,"dojoDndItem"); if (!service.domNode.getAttribute("dndType")) service.domNode.setAttribute("dndType",service.declaredClass); dojox.layout.dnd._setGcDndHandle(service,this.withHandles,this.handleClasses, first); if(this.hasResizableColumns){ if(service.onLoad){ this.connect(service, "onLoad", "_placeGrips"); } if(service.onExecError){ this.connect(service, "onExecError", "_placeGrips"); } if(service.onUnLoad){ this.connect(service, "onUnLoad", "_placeGrips"); } } this._placeGrips(); return service.id; // String }, // FIXME: API change, rename to addChild addService : function(/*Object*/service, /*Integer*/z, /*Integer*/p){ // summary: Add a service (child widget) in a specific column of the GridContainer widget. // service: // widget to insert // z: // zone number (column) // p: // place in the zone (first = 0) service.domNode.id = service.id; this.addChild(service); if(p <= 0){ p = 0; } var result = this._insertService(z,p); this.grid[z].setItem(service.id,{data: service.domNode, type: [service.domNode.getAttribute("dndType")]}); return result; //Object }, /***********grid methods******************/ _createGrid : function(){ //summary: Create all grid (zones and grip) var grid = []; var i = 0; this.tabDZ = []; while(i < this.nbZones){ var zone = this.cell[i]; this.tabDZ[i] = this._createZone(zone); if(this.hasResizableColumns && i != (this.nbZones-1)) { this._createGrip(this.tabDZ[i]); } grid.push(this.tabDZ[i]); i++; } if(this.hasResizableColumns){ this.handleDndStart = []; for (var j = 0; j < this.tabDZ.length; j++) { var dz = this.tabDZ[j]; var self = this; this.handleDndStart.push(dojo.connect(dz, "onDndStart", dz, function(source){ if(source==this){ self.handleDndInsertNodes = []; for (i = 0; i < self.tabDZ.length; i++) { self.handleDndInsertNodes.push(dojo.connect(self.tabDZ[i], "insertNodes", self, function(){ self._disconnectDnd(); })); } self.handleDndInsertNodes.push(dojo.connect(dz,"onDndCancel", self, self._disconnectDnd)); self.onResized(); } })); } } return grid; // Object }, _disconnectDnd: function(){ //summary: disconnect all events on insertNodes dojo.forEach(this.handleDndInsertNodes, dojo.disconnect); setTimeout(dojo.hitch(this, "onResized"), 0); }, _createZone: function(/*Object*/zone){ //summary: Create a DnD column. var dz = null; dz = new dojox.layout.dnd.PlottedDnd(zone.id, { accept:this.acceptTypes, withHandles:this.withHandles, handleClasses: this.handleClasses, singular: true, hideSource:true, opacity: this.opacity, dom: this.domNode, allowAutoScroll: this.allowAutoScroll, isOffset:this.isOffset, offsetDrag : this.offsetDrag }); this.connect(dz, "insertDashedZone", "_placeGrips"); this.connect(dz, "deleteDashedZone", "_placeGrips"); return dz; //plottedDnd Object }, /************ grips methods***************/ _createGrip: function(/*Object*/dz){ // summary: Create a grip for a specific zone var grip = document.createElement("div"); grip.className = "gridContainerGrip"; grip.setAttribute("tabIndex", "0"); var _this= this; this.onMouseOver = this.connect(grip, "onmouseover", function(e){ var gridContainerGripShow = false; for(var i = 0; i < _this.grid.length - 1; i++){ if(dojo.hasClass(_this.grid[i].grip, "gridContainerGripShow")){ gridContainerGripShow = true; break; } } if(!gridContainerGripShow){ dojo.removeClass(e.target, "gridContainerGrip"); dojo.addClass(e.target, "gridContainerGripShow"); } } ); this.connect(grip,"onmouseout",function(e){ if(!_this._isResized){ dojo.removeClass(e.target, "gridContainerGripShow"); dojo.addClass(e.target, "gridContainerGrip"); } } ); this.connect(grip,"onmousedown",function(e){ _this._a11yOn = false; _this._activeGrip = e.target; _this.resizeColumnOn(e); }); this.domNode.appendChild(grip); dz.grip = grip; }, _initPlaceGrips: function(){ //summary: Initialize the position of a grip which will not change (top) var dcs = dojo.getComputedStyle(this.domNode); var gcs = dojo.getComputedStyle(this.gridContainerTable); this._x = parseInt(dcs.paddingLeft); this._topGrip = parseInt(dcs.paddingTop); if(dojo.isIE || gcs.borderCollapse != "collapse"){ var ex = dojo._getBorderExtents(this.gridContainerTable); this._x += ex.l; this._topGrip += ex.t } this._topGrip += "px"; dojo.forEach(this.grid, function(zone){ if(zone.grip){ var grip = zone.grip; if (!dojo.isIE){ zone.pad = dojo._getPadBorderExtents(zone.node).w; } grip.style.top = this._topGrip; } }, this); }, _placeGrips: function(){ //summary: Define the position of a grip and place it on page. var height; if (this.allowAutoScroll){ height = this.gridNode.scrollHeight; }else{ height = dojo.contentBox(this.gridNode).h; } var size = this._x; dojo.forEach(this.grid, function(zone){ if (zone.grip){ var grip = zone.grip; // Bug margin : IE size += dojo[(dojo.isIE ? "marginBox" : "contentBox")](zone.node).w + (dojo.isIE ? 0 : zone.pad); dojo.style(grip,{ left: size + "px", height: height + "px" }); } }, this); }, _getZoneByIndex : function(/*Integer*/n){ //summary: Return a DOM node containing a zone by given a index. return this.grid[(n >= 0 && n < this.grid.length ? n : 0 )]; //number }, getIndexZone : function(/*Node*/zone){ //summary: Return an integer by given a zone for(var z = 0; z < this.grid.length; z++){ if(this.grid[z].domNode == zone){ return z; // number } } return -1; // number }, /***********miscellaneous methods******************/ resizeColumnOn : function(/*Event*/e){ // summary: Connect events to listen the resize action. // Change the type of width columns (% to px) // Calculate the minwidth according to the children var k = dojo.keys; if(this._a11yOn && e.keyCode != k.LEFT_ARROW && e.keyCode != k.RIGHT_ARROW){ return; } e.preventDefault(); dojo.body().style.cursor = "ew-resize"; this._isResized = true; this.initX = e.pageX; var tabSize = []; for(var i = 0; i < this.grid.length; i++){ tabSize[i] = dojo.contentBox(this.grid[i].node).w; } this.oldTabSize = tabSize; for(var i = 0; i< this.grid.length; i++){ if(this._activeGrip == this.grid[i].grip) { this.currentColumn = this.grid[i].node; this.currentColumnWidth = tabSize[i]; this.nextColumn = this.currentColumn.nextSibling; this.nextColumnWidth = tabSize[i+1]; } this.grid[i].node.style.width = tabSize[i] + "px"; } // calculate the minWidh of all children for current and next column var calculateChildMinWidth = function(childNodes, minChild){ var width = 0; var childMinWidth = 0; dojo.forEach(childNodes, function(child){ if(child.nodeType == 1){ var objectStyle = dojo.getComputedStyle(child); var minWidth = (dojo.isIE ? minChild : parseInt(objectStyle.minWidth)); childMinWidth = minWidth + parseInt(objectStyle.marginLeft)+ parseInt(objectStyle.marginRight); if(width < childMinWidth){ width = childMinWidth; } } }); return width; }; var currentColumnMinWidth = calculateChildMinWidth(this.currentColumn.childNodes, this.minChildWidth); var nextColumnMinWidth = calculateChildMinWidth(this.nextColumn.childNodes, this.minChildWidth); var minPix = Math.round((dojo.marginBox(this.gridContainerTable).w * this.minColWidth) / 100); this.currentMinCol = currentColumnMinWidth; this.nextMinCol = nextColumnMinWidth; if(minPix > this.currentMinCol){ this.currentMinCol = minPix; } if(minPix > this.nextMinCol){ this.nextMinCol = minPix; } if(this._a11yOn){ this.connectResizeColumnMove = this.connect(dojo.doc, "onkeypress", "resizeColumnMove"); }else{ this.connectResizeColumnMove = this.connect(dojo.doc, "onmousemove", "resizeColumnMove"); this.connectResizeColumnOff = this.connect(document, "onmouseup", "resizeColumnOff"); } }, resizeColumnMove: function(/*Event*/e){ //summary: Change columns size. var d = 0; if(this._a11yOn){ var k = dojo.keys; switch (e.keyCode){ case k.LEFT_ARROW: d = -10; break; case k.RIGHT_ARROW: d = 10; break; } }else{ e.preventDefault(); d = e.pageX - this.initX; } if(d == 0){ return; } if(!(this.currentColumnWidth + d < this.currentMinCol || this.nextColumnWidth - d < this.nextMinCol)) { this.currentColumnWidth += d; this.nextColumnWidth -= d; this.initX = e.pageX; this.currentColumn.style["width"] = this.currentColumnWidth + "px"; this.nextColumn.style["width"] = this.nextColumnWidth + "px"; this._activeGrip.style.left = parseInt(this._activeGrip.style.left) + d + "px"; this._placeGrips(); } if(this._a11yOn){ this.resizeColumnOff(e); } }, resizeColumnOff : function(/*Event*/e){ //summary: Disconnect resize events. // Change the type of width columns (px to %) dojo.body().style.cursor = "default"; if(this._a11yOn){ this.disconnect(this.connectResizeColumnMove); this._a11yOn = false; }else{ this.disconnect(this.connectResizeColumnMove); this.disconnect(this.connectResizeColumnOff); } var tabSize = []; var testSize = []; var tabWidth = this.gridContainerTable.clientWidth; for(var i = 0; i < this.grid.length; i++){ var _cb = dojo.contentBox(this.grid[i].node); if(dojo.isIE){ tabSize[i] = dojo.marginBox(this.grid[i].node).w; testSize[i] = _cb.w; }else{ tabSize[i] = _cb.w; testSize = tabSize; } } var update = false; for(var i = 0; i < testSize.length; i++){ if(testSize[i] != this.oldTabSize[i]){ update = true; break; } } if(update){ var mul = dojo.isIE ? 100 : 10000; for(var i = 0; i < this.grid.length; i++){ this.grid[i].node.style.width = Math.round((100 * mul * tabSize[i]) / tabWidth) / mul + "%"; } this._placeGrips(); } if (this._activeGrip){ dojo.removeClass(this._activeGrip, "gridContainerGripShow"); dojo.addClass(this._activeGrip, "gridContainerGrip"); } this._isResized= false; }, setColumns : function(/*Integer*/nbColumns){ // summary: Set the number of columns if(nbColumns > 0){ var delta = this.grid.length-nbColumns; if(delta > 0){ var count = []; var zone, start, end; /*Check if right or left columns are fixed*/ /*Columns are not taken in account and can't be deleted*/ if(this.mode == "right"){ end = (this.isLeftFixed && this.grid.length > 0) ? 1 : 0; start = this.grid.length - (this.isRightFixed ? 2 : 1); for(var z = start; z >= end; z--){ var nbChildren = 0; var zone = this.grid[z].node; for(var j = 0;j < zone.childNodes.length; j++){ if(zone.childNodes[j].nodeType==1 && !(zone.childNodes[j].id == "")){ //1 = dojo.html.ELEMENT_NODE nbChildren++; break; } } if(nbChildren == 0){ count[count.length] = z; } if(count.length>=delta){ this._deleteColumn(count); break; } } if(count.length < delta){ //Not enough empty columns console.error(this.i18n.err_onSetNbColsRightMode); } }else{ // mode="left" if(this.isLeftFixed&&this.grid.length>0){ start=1; }else{ start=0; } if(this.isRightFixed){ end=this.grid.length-1; }else{ end=this.grid.length; } for(var z=start;z=delta){ this._deleteColumn(count); break; } } if (count.length= 0; i--){ zone = this.gridNode.childNodes[i].lastChild; var found = false; while(!found){ if(zone != null){ if(zone.style.display !== "none"){ dijit.focus(zone); dojo.stopEvent(event); found = true; }else{ zone = zone[pos]; } }else{ break; } } if(found){ break; } } break; } }else{ if(focusNode.parentNode.parentNode == this.gridNode){ switch(e){ case k.UP_ARROW: case k.DOWN_ARROW: dojo.stopEvent(event); var nbDisplayChild = 0; dojo.forEach(focusNode.parentNode.childNodes, function(child){ if (child.style.display !== "none") nbDisplayChild++; }); if (nbDisplayChild == 1) return; var found = false; zone = focusNode[pos]; while(!found){ if(zone == null){ zone = focusNode.parentNode[child]; if(zone.style.display !== "none") found = true; else zone = zone[pos]; }else{ if(zone.style.display !== "none"){ found = true; }else{ zone = zone[pos]; } } } if(event.shiftKey){ if (dijit.byNode(focusNode).dragRestriction) return; var _dndType = focusNode.getAttribute("dndtype"); var accept = false; for(var i = 0; i < this.acceptTypes.length; i++){ if (_dndType == this.acceptTypes[i]){ var accept = true; break; } } if(accept){ var parent = focusNode.parentNode; var firstChild = parent.firstChild; var lastChild = parent.lastChild; while(firstChild.style.display == "none" || lastChild.style.display == "none"){ if(firstChild.style.display == "none"){ firstChild = firstChild.nextSibling; } if(lastChild.style.display == "none"){ lastChild = lastChild.previousSibling; } } if(e == k.UP_ARROW){ var r = parent.removeChild(focusNode); if(r == firstChild){ parent.appendChild(r); }else{ parent.insertBefore(r, zone); } r.setAttribute("tabIndex", "0"); dijit.focus(r); }else{ if(focusNode == lastChild){ var r = parent.removeChild(focusNode); parent.insertBefore(r, zone); r.setAttribute("tabIndex", "0"); dijit.focus(r); }else{ var r = parent.removeChild(zone); parent.insertBefore(r, focusNode); focusNode.setAttribute("tabIndex", "0"); dijit.focus(focusNode); } } }else{ this._displayPopup(); } }else{ dijit.focus(zone); } break; case k.RIGHT_ARROW: case k.LEFT_ARROW: dojo.stopEvent(event); if(event.shiftKey){ if(dijit.byNode(focusNode).dragRestriction){ return; } var z = 0; if(focusNode.parentNode[pos] == null){ if (e == k.LEFT_ARROW){ var z = this.gridNode.childNodes.length - 1; } }else if(focusNode.parentNode[pos].nodeType == 3){ z = this.gridNode.childNodes.length - 2; }else{ for(var i = 0; i < this.gridNode.childNodes.length; i++){ if(focusNode.parentNode[pos] == this.gridNode.childNodes[i]){ break; } z++; } } var _dndType = focusNode.getAttribute("dndtype"); var accept = false; for(var i = 0; i < this.acceptTypes.length; i++){ if(_dndType == this.acceptTypes[i]){ accept = true; break; } } if(accept){ var parentSource = focusNode.parentNode; var widget = dijit.byNode(focusNode); var r = parentSource.removeChild(focusNode); var place = (e == k.RIGHT_ARROW ? 0 : this.gridNode.childNodes[z].length); this.addService(widget, z, place); r.setAttribute("tabIndex", "0"); dijit.focus(r); this._placeGrips(); }else{ this._displayPopup(); } }else{ var node = focusNode.parentNode; while(zone === null){ if(node[pos] !== null && node[pos].nodeType !== 3){ node = node[pos]; }else{ if(pos === "previousSibling"){ node = node.parentNode.childNodes[node.parentNode.childNodes.length - 1]; }else{ node = node.parentNode.childNodes[0]; } } var found = false; var tempZone = node[child]; while(!found){ if(tempZone != null){ if(tempZone.style.display !== "none") { zone = tempZone; found = true; }else{ tempZone = tempZone[pos]; } }else{ break; } } } dijit.focus(zone); } break; } }else{ // focus on a grip ! if(dojo.hasClass(focusNode,"gridContainerGrip") || dojo.hasClass(focusNode,"gridContainerGripShow")){ this._activeGrip = event.target; this._a11yOn = true; this.resizeColumnOn(event); } } } }, _displayPopup: function(){ //summary: display a popup when a widget type can not move if(this._canDisplayPopup){ var popup = dojo.doc.createElement("div"); dojo.addClass(popup, "gridContainerPopup"); popup.innerHTML = this.i18n.alertPopup; var attachPopup = this.containerNode.appendChild(popup); this._canDisplayPopup = false; setTimeout(dojo.hitch(this, function(){ this.containerNode.removeChild(attachPopup); dojo.destroy(attachPopup); this._canDisplayPopup = true; }), this.timeDisplayPopup); } } }); dojo.extend(dijit._Widget, { // dragRestriction: Boolean // To remove the drag capability. dragRestriction : false, // column: String // Column of the grid to place the widget. column : "1", // group: String // Defines a group belonging. group : "" });