var ZINDEX_BASE=200; var ZINDEX_INTERVAL=200; var ZINDEX_SHIFT_MODALLAYER=1; var ZINDEX_SHIFT_RESIZER=50; var PopupExitMethod={}; PopupExitMethod.DEFAULT=1; PopupExitMethod.ESCAPE=2; PopupExitMethod.SIDE_CLICK=3; PopupExitMethod.CLOSE_CLICK=4; var PopupContainer = Class.create({ nActivePopup: -1, initialize: function() { this.popups = []; Event.observe(window, 'resize', this.OnResize.bind(this)); Event.observe(window, 'mousedown', this.OnMouseDown.bind(this)); // Event.observe(document, 'keydown', this.OnKeyDown.bind(this)); this.modalLayerOnClick = this.OnModalLayerClick.bind(this); this.modalLayerOnKeyPress = this.OnModalLayerKeyPress.bind(this); }, AddPopup: function(popup) { if(!this.initialized_ && document.body) { Event.observe(document.body, 'keydown', this.OnKeyDown.bind(this)); this.initialized_=true; } var i = this.popups.length++; this.popups[i] = {window: popup}; var z = popup.SetZIndex( this.ZIndexOf(i) ); if (popup.modal) { var layer=$(document.createElement('div')); this.popups[i].modalLayer=layer; layer.addClassName('PopupModalLayer'); document.body.appendChild(layer); layer.setStyle({ zIndex: z-ZINDEX_SHIFT_MODALLAYER }); if (popup.modalLayerStyle) { layer.setStyle(popup.modalLayerStyle); } layer.observe('click', this.modalLayerOnClick); BindKeyDown(document, this.modalLayerOnKeyPress); } }, ZIndexOf: function(i) { return ZINDEX_BASE + i*ZINDEX_INTERVAL; }, RemoveModalLayer: function() { for(var i=this.popups.length-1; i>=0; --i) { if(this.popups[i].modalLayer) { var layer=this.popups[i].modalLayer; // unbind handlers first layer.stopObserving('click', this.modalLayerOnClick); UnbindKeyDown(document, this.modalLayerOnKeyPress); // then delete layer document.body.removeChild(layer); delete this.popups[i].modalLayer; return; } } }, OnModalLayerKeyPress: function(event) { var evt = Event.extend(event || window.event); var key=evt.which || evt.keyCode; if(key!==27) return; // escape StopEvent(evt); var popup = this.popups.last().window; popup.exitMethod=PopupExitMethod.ESCAPE; if (popup.fCloseCallback) popup.fCloseCallback(popup); }, OnModalLayerClick: function(event) { var evt = Event.extend(event || window.event); StopEvent(evt); var popup = this.popups.last().window; popup.exitMethod=PopupExitMethod.SIDE_CLICK; if (popup.fCloseCallback) popup.fCloseCallback(popup); }, RemovePopup: function(popup, bForce) { for (var i = 0; i < this.popups.length; ++i) { if (this.popups[i].window == popup) break; } if (i == this.popups.length) return; if (popup.canClose === false && !bForce) { // immortal popup //LogL("Immortal popup $$"+popup.nodeID+"$$ made hidden. "); //popup.Hide(); return; } if (popup.modal) this.RemoveModalLayer(); this.popups = this.popups.slice(0, i).concat(this.popups.slice(i + 1)); if (popup.div) document.body.removeChild(popup.div); if(!this.popups.length) { Event.stopObserving(document.body, 'keydown'); delete this.initialized_; } }, RemoveAllPopups: function(sLayer) { if (typeof(sLayer) == 'undefined') sLayer = "MainLayer"; var newPopups = []; for (var i = 0; i < this.popups.length; ++i) { var item=this.popups[i]; var popup = this.popups[i].window; if (popup.sLayer != sLayer) { newPopups.push(item); } else if (popup.canClose === false) { // immortal popup //popup.Hide(); newPopups.push(item); } else { // TODO: FIXME popup must unlink itself from given container (e.g. document.body) if (popup.div) document.body.removeChild(popup.div); } } this.popups = newPopups; if(!this.popups.length) { Event.stopObserving(document.body, 'keydown'); delete this.initialized_; } }, Activate: function(popup) { for (var i = 0; i < this.popups.length; ++i) { if (this.popups[i].window == popup) break; } if (i == this.popups.length) return; var p=this.popups[i]; this.popups = this.popups.slice(0, i).concat(this.popups.slice(i + 1)); this.popups.push(p); for (var j = i; j < this.popups.length; ++j) { // it's better to reverse this loop if (this.popups[i]) this.popups[j].window.SetZIndex( this.ZIndexOf(j) ); } }, OnResize: function(evt) { for (var i = 0; i < this.popups.length; ++i) { if (this.popups[i]) this.popups[i].window.SetPosition(); } for (var i = 0; i < this.popups.length; ++i) { if (this.popups[i]) this.popups[i].window.SetPosition(); } }, ActivateNext: function() { var ai=this.nActivePopup; ai++; if (ai>=this.popups.length) ai=-1; // switch to main window if (ai>=0) { this.Activate(this.popups[ai].window); } else { GWindowManager.Focus(); } this.nActivePopup=ai; }, OnMouseDown: function(event) { var evt=Event.extend(event || window.event); var ele=Event.element(evt); //LogE("id="+ele.innerHTML); var win=AControl.GetParentWindow(ele); if (!win) return; // strange element, ignore it // since Collector windows grow from AWindow, not from APopupWindow, we ought to use special flag if (win.IsPopup) { //LogE("OnClick in PopupContainer -- POPUP!"); GPopupContainer.Activate(win.Popup()); if (typeof(win.Focus)=='function') win.Focus(); } else { //LogE("OnClick in PopupContainer -- WINDOWMANAGER!"); GWindowManager.Focus(); } }, OnKeyDown: function(event) { // Handling window activation/focusing var eKD=GetKeyCode(event); var evt=Event.extend(event || window.event); //LogL("keyCode="+eKD.keyCode+" charCode="+eKD.charCode); if (eKD.charCode==27) { //var ai=this.nActivePopup; //LogE("ESC is pressed"); var popup=(this.popups.length ? this.popups.last().window : null); if (popup) { popup.exitMethod=PopupExitMethod.CLOSE_CLICK; if (popup.fCloseCallback) popup.fCloseCallback(popup); } } if (!(eKD.keyCode==9 || eKD.charCode==9)) { // not a TAB evt.root=evt.findElement('div[class=popup]'); return false; } if (evt.ctrlKey) { this.ActivateNext(); evt.stop(); return false; } } }); var GPopupContainer = new PopupContainer(); /** * Simple Popup Opener **/ var PopupOpener = Class.create({ initialize: function(args) { this.args = args; this.args.oOpener = this; this.ID_=args.nodeID; this.root=$(this.ID_); this.LocalID_ = this.root.readAttribute('localid'); if (!this.root) ThrowError("PopupOpener: nodeID is not defined! "); this.root.JSControl=this; this.root.observe('click', this.OnClick.bind(this)); // BindKeyDown(this.root, this.OnKeyDown.bind(this)); this.root.observe('keydown', this.OnKeyDown.bind(this)); this.bOpen = false; }, Open: function() { if (this.bOpen) return; // if popup already open, do not open another this.bOpen = true; return new APopup(this.args); }, // Popups are simple: this function receives control // whenever user wants popup to open. See also APopupWindow's SendOpenSignal // mvel@ SendOpenSignal: function() { this.Open(); }, Close: function() { this.bOpen = false; }, OnClick: function(event) { if (this.bOpen) return; // if popup already open, do not open another var evt=Event.extend(event || window.event); StopEvent(evt); this.SendOpenSignal(); }, SetTabOrder: function(nTabBase) { this.root.tabIndex=nTabBase; return ++nTabBase; }, OnKeyDown: function(event) { var eKD = GetKeyCode(event); var evt = Event.extend(event || window.event); var key = eKD.keyCode; if (key) return; if (typeof(eKD.charCode) == 'undefined') { if (key == 13 || key == 32) { StopEvent(evt); this.SendOpenSignal(); return false; } return; } // All other browsers, simple keys key = eKD.charCode; if (key == 13 || key == 32) { StopEvent(evt); this.SendOpenSignal(); return false; } }, ID: function() { return this.ID_; }, LocalID: function() { return this.LocalID_; } }); /** * Popup Window Opener **/ var PopupWindowOpener = Class.create(PopupOpener, { initialize: function($super, args) { $super(args); this.initializeEvents(); }, initializeEvents: function() { this.winID=AControl.GetParentWindowInstanceID(this.ID()); SubscribeOnEvent("WM_POPUP", this.winID, this.OnWMPopup, this); }, Open: function() { if (this.bOpen) return; // if popup already open, do not open another this.bOpen = true; return new APopupWindow(this.args); }, // Usual window is written so that CLICK action emits WM_POPUP event // that we should catch with OnWMPopup handler. This gives us a chance // to obtain arguments and 'src' attribute for popup window we intend to open // mvel@ SendOpenSignal: function() { if (this.args.fBeforeCreateCallback && !this.args.fBeforeCreateCallback()) return; new AEvent('CLICK', null, this); }, OnClick: function(event) { if (this.bOpen) return; // if popup already open, do not open another var evt=Event.extend(event || window.event); StopEvent(evt); this.SendOpenSignal(); }, OnWMPopup: function(oEvent) { var oData = oEvent.Data(); if (typeof(oData['id']) == 'undefined') return; // not our case if (oData['id'] != this.LocalID()) return; // this is an event for another control this.args.sParent = AControl.GetParentWindowInstanceID(document.getElementById(this.ID())); if (typeof(oData['src']) != 'undefined') this.args.sURL=oData['src']; // please do not mix up this.args (popup creation arguments) // and event arguments that contain control values and should be passed to window if (typeof(oData['args']) != 'undefined') this.args.sParams=URLSerializeVS(oData['args']); oEvent.Stop(); this.Open(); }, SetAttribute: function(sName, sValue) { if(sName=='src') this.args.sURL=sValue; }, Value: function() { return null; }, SetValue: function() {} }); /** * Generic popup object that can popup, align to control, move, and close * @param args.nodeID: when using PopupOpener or PopupWindowOpener, popup raises by click on element with that ID. If omitted or empty, nodeID will be autogenerated. Anyway, popup components' IDs will be generated from nodeID by appending specific suffixes. * @param popupFrame: a reference to custom popup frame node * @param args.modal modality flag (boolean, false by default) * @param args.alignID alignment element (optional) * @param args.shiftX, shiftY: relative shift from align element (valid when args.alignID is set) * @param args.fCloseCallback(oPopup) callback function that receives control when smb clicks on 'X' button (or clicks aside from popup). If conditions are good enough, we can call oPopup.Close() within this callback to close popup. Otherwise popup closing will be cancelled. * @param args.fCreateCallback(oPopup) callback function that receives control when popup is created * @param args.fAfterResizeCallback(oPopup) callback function that receives control when popup has been resized * @param args.showState: initial show state (can be 'hidden' or 'visible') * @param args.showHeader: if true (default), popup header will be shown (with title and close box) * @param args.canResize: if true (default), popup will be user-resizeable by LR corner. * @param args.maxHeight: if set, popup contents height will be limited to specified value (in pixels) * @param args.initialWidth, args.initialHeight: set initial height and width after construction * @param args.initialTotalWidth, args.initialTotalHeight: set initial total height and width after construction * @param args.initialX, initialY: set initial position after construction * @param args.autoSize: if true, popup will ignore initialHeight/initialWidth settings. Default is false. * @param args.bFitToScreen: if true, popup's initial position will be corrected so that it fits to screen. **/ var APopup = Class.create({ initialize: function(args) { this.container = GPopupContainer; if (!args.nodeID) args.nodeID = "PN_"+Math.uuid(); if (args.popupFrame) this.popupFrame=args.popupFrame; this.nodeID = args.nodeID+'_popup'; this.modal = (typeof(args.modal) != 'undefined') ? args.modal: false; this.alignID = args.alignID; this.shiftX=(typeof(args.shiftX)=='number') ? args.shiftX : null; this.shiftY=(typeof(args.shiftY)=='number') ? args.shiftY : null; if (args.oOpener) this.oOpener = args.oOpener; this.showState = (args.showState) ? args.showState : 'visible'; this.showHeader = (typeof(args.showHeader) != 'undefined') ? args.showHeader: true; this.canResize = (typeof(args.canResize) != 'undefined') ? args.canResize: true; this.canClose = (typeof(args.canClose) != 'undefined') ? args.canClose: true; this.maxHeight = (typeof(args.maxHeight) != 'undefined') ? args.maxHeight: null; this.initialWidth = (typeof(args.initialWidth) != 'undefined') ? args.initialWidth : null; this.initialHeight = (typeof(args.initialHeight) != 'undefined') ? args.initialHeight : null; this.bTotalWidthHeight = (typeof(args.bTotalWidthHeight) != 'undefined') ? args.bTotalWidthHeight: false; this.initialX = (typeof(args.initialX) != 'undefined') ? args.initialX : null; this.initialY = (typeof(args.initialY) != 'undefined') ? args.initialY : null; this.autoSize = (typeof(args.autoSize) != 'undefined') ? args.autoSize : false; this.bFitToScreen = (typeof(args.bFitToScreen) != 'undefined') ? args.bFitToScreen : false; this.sLayer = (typeof(args.sLayer) != 'undefined') ? args.sLayer : "MainLayer"; this.auxClassName = (typeof(args.auxClassName) != 'undefined') ? args.auxClassName : ""; this.exitMethod=PopupExitMethod.DEFAULT; // set popup control callbacks this.fCreateCallback = args.fCreateCallback; this.fCloseCallback = args.fCloseCallback; this.fAfterResizeCallback = args.fAfterResizeCallback; this.initWindowFrame(); this.stopEventCallback=(function(evt){evt.stop();}); this.container.AddPopup(this); this.Align(); if (this.initialWidth && this.initialHeight && !this.autoSize) { this.AdjustSize(this.initialWidth, this.initialHeight, {bTotal: this.bTotalWidthHeight}); } if (this.fCreateCallback) this.fCreateCallback(this); // call OnCreate handler }, SetZIndex: function(i) { this.zIndex = i; this.div.setStyle({ zIndex: this.zIndex }); return this.zIndex; }, initWindowFrame: function() { if (!this.popupFrame) { var pDoc=new AXML('ai:popup', AIURI); var pDOM=pDoc.XML(); var xRoot=pDOM.documentElement; if (this.showHeader) xRoot.setAttribute('showHeader', 'yes'); if (this.canResize) xRoot.setAttribute('canResize', 'yes'); if (this.auxClassName) xRoot.setAttribute('auxClassName', this.auxClassName); xRoot.setAttribute('id', this.nodeID); var oTransformDoc; try { oTransformDoc=GWindowManager.Transform(pDOM, true); } catch (exc) { alert("APopup: XSLT Transform failed: "+exc.message); } var elPopup=new Element('div'); document.body.appendChild(elPopup); ApplyTreeOrString(elPopup, oTransformDoc); document.body.appendChild(elPopup.firstChild); document.body.removeChild(elPopup); } else { // frame template is given to us document.body.appendChild(this.popupFrame); } this.div = $(this.nodeID+'_div'); this.header = $(this.nodeID+'_header'); this.contents = $(this.nodeID+'_contents'); if (this.canResize) { this.resizer = $(this.nodeID+'_resizer'); // calculate sizes of resizer this.resizerWidth = this.resizer.offsetWidth; this.resizerHeight = this.resizer.offsetHeight; } if (this.showHeader) { this.title = $(this.nodeID+'_title'); var closeIcon = $(this.nodeID+'_close'); closeIcon.observe('mousedown', function(evt) { evt.stop(); // see bug #576 // mvel@ return false; }); closeIcon.observe('click', this.OnClose.bind(this)); } this.zIndex = ZINDEX_BASE; this.div.setStyle({ zIndex: this.zIndex, visibility: this.showState }); if (this.maxHeight) { this.contents.setStyle({ maxHeight: this.maxHeight+'px', overflowY: 'auto' }); } this.initResizer(); if (this.showHeader) this.header.observe('mousedown', this.OnMouseDown.bind(this, 'drag')); if (this.canResize) this.resizer.observe('mousedown', this.OnMouseDown.bind(this, 'resize')); }, initResizer: function() { // Popup resizer this.resizeDiv = $('PopupResizer'); if (!this.resizeDiv) { this.resizeDiv = new Element('div', {"class": 'popupResizeDiv', "id": 'PopupResizer'}); this.resizeTable = new Element('table'); this.resizeTable.insertRow(0).insertCell(0).innerHTML=" "; this.resizeDiv.appendChild(this.resizeTable); document.body.appendChild(this.resizeDiv); } else { // just for IE (strange behavior) this.resizeTable = this.resizeDiv.firstDescendant(); } this.resizeDiv.setStyle({ zIndex: ((this.zIndex) ? this.zIndex+ZINDEX_SHIFT_RESIZER : ZINDEX_BASE+ZINDEX_SHIFT_RESIZER), visibility: 'hidden' }); }, offsetTop: function() { return ((this.showHeader) ? this.header.offsetTop : this.contents.offsetTop ); }, offsetLeft: function() { return this.contents.offsetLeft; }, offsetHeight: function() { return ((this.showHeader) ? this.header.offsetHeight + this.contents.offsetHeight : this.contents.offsetHeight ); }, offsetWidth: function() { return this.contents.offsetWidth; }, // same width as header HeaderHeight: function() { return (this.showHeader) ? this.header.offsetHeight : 0; }, headerLowerBound: function() { return (this.showHeader) ? (this.header.offsetTop + this.header.offsetHeight) : 0 }, Align: function() { var off = null; var a = null; if (this.alignID) { // calculate alignment from element a=$(this.alignID); if (a) off=a.cumulativeOffset(); if (this.shiftX !== null) off.left+=this.shiftX; if (this.shiftY !== null) off.top+=this.shiftY; } else if (this.initialX !== null && this.initialY !== null) { // get alignment from given coordinates off = { left: this.initialX, top: this.initialY }; a = { offsetHeight: 0 }; } else if(this.initialHeight || this.initialWidth) { off={ left: (document.body.offsetWidth-this.initialWidth)/2, top: (document.documentElement.clientHeight-this.initialHeight)/2}; a = { offsetHeight: 0 }; } // do coordinates correction var top = (off ? off.top + a.offsetHeight - this.offsetTop() / 2 : document.documentElement.clientHeight / 3) var left = (off ? off.left : document.body.offsetWidth / 3); this.SetPosition(left, top); }, OnMouseDown: function(sMode, evt) { if (!evt.isLeftClick()) return; this.container.Activate(this); if (sMode == 'drag') { if (!this.dragStarted) { this.mouseUpHandler = this.OnMouseUp.bind(this); this.mouseMoveHandler = this.OnMouseMove.bind(this); $(document).observe('mouseup', this.mouseUpHandler) $(document).observe('mousemove', this.mouseMoveHandler); this.div.addClassName('popupDrag'); this.dragX = evt.pointerX(); this.dragY = evt.pointerY(); this.startX = this.div.offsetLeft; this.startY = this.div.offsetTop; this.screenW=document.body.offsetWidth; this.screenH=document.body.offsetHeight; this.dragStarted = true; } } else if (sMode == 'resize') { if (!this.resizeStarted) { this.mouseUpHandler = this.OnMouseUp.bind(this); this.mouseMoveHandler = this.OnMouseMove.bind(this); $(document).observe('selectstart', this.stopEventCallback); $(document).observe('dragstart', this.stopEventCallback); $(document).observe('mouseup', this.mouseUpHandler) $(document).observe('mousemove', this.mouseMoveHandler); this.dragX = evt.pointerX(); this.dragY = evt.pointerY(); this.startWidth = this.contents.offsetWidth; this.startHeight = this.contents.offsetHeight; this.newWidth = this.startWidth; this.newHeight = this.startHeight; this.resizeDiv.setStyle({visibility: 'visible'}); this.AdjustResizer(); this.resizeStarted = true; } } evt.stop(); // see bug #576 return false; }, AdjustResizer: function() { this.resizeTable.setStyle({ width: this.newWidth+'px', height: ( this.newHeight+this.HeaderHeight() )+'px' }); this.resizeDiv.setStyle({ left: this.posX+'px', top: this.posY+'px' }); }, OnMouseUp: function(evt) { if (!evt.isLeftClick()) return; if (this.dragStarted) { this.dragStarted = false; this.div.removeClassName('popupDrag'); this.AdjustSize(); } else if (this.resizeStarted) { this.resizeDiv.setStyle({visibility: 'hidden'}); this.AdjustSize(this.newWidth, this.newHeight); this.resizeStarted = false; if (this.fAfterResizeCallback) this.fAfterResizeCallback(this); } $(document).stopObserving('dragstart', this.stopEventCallback); $(document).stopObserving('selectstart', this.stopEventCallback); $(document).stopObserving('mousemove', this.mouseMoveHandler); $(document).stopObserving('mouseup', this.mouseUpHandler); this.mouseMoveHandler = null; this.mouseUpHandler = null; }, OnClose: function(event) { var evt = Event.extend(event || window.event); StopEvent(evt); this.exitMethod=PopupExitMethod.CLOSE_CLICK; if (this.fCloseCallback) this.fCloseCallback(this); // and nothing else: somebody _must_ say APopup::Close }, Close: function() { this.container.RemovePopup(this, true); // if popup has opener, inform it if (this.oOpener) this.oOpener.Close(); // TODO: fix this bad implementation. Focus should be moved on the previous popup GWindowManager.Focus(); var ae=GWindowManager.ActiveElement(); if (ae && ae.tabIndex >= 0 && ae.style.display != 'none') { if (typeof(ae.activate)=='function') ae.activate(); else if (ae.focus) { try { ae.focus(); } catch (exc) {} } } }, OnMouseMove: function(evt) { var newX = evt.pointerX(); var newY = evt.pointerY(); if (this.dragStarted) { var newLeft = this.startX + (newX - this.dragX); var newTop = this.startY + (newY - this.dragY); this.SetPosition(newLeft, newTop, {bCacheScreenSize: true}); this.AdjustSize(undefined, undefined, {bCacheScreenSize: true}); } else if (this.resizeStarted) { // no flow control here if this.canResize is false var maxX = document.body.offsetWidth-this.resizerWidth; var maxY = document.body.offsetHeight-this.resizerHeight; if (newX > maxX) newX = maxX; if (newY > maxY) newY = maxY; var newW=this.startWidth + (newX - this.dragX); if (newW<0) newW=0; this.newWidth=newW; var newH=this.startHeight + (newY - this.dragY); if (newH<0) newH=0; this.newHeight=newH; this.AdjustResizer(); // Show popup contents while dragging //this.AdjustSize(this.newWidth, this.newHeight); } }, SetPosition: function(newLeft, newTop, options) { var screenW; var screenH; if (options && options.bCacheScreenSize) { screenW=this.screenW; screenH=this.screenH; } else { screenW=document.body.offsetWidth; screenH=document.body.offsetHeight; } var cW=this.div.offsetWidth; // current width and height var cH=this.div.offsetHeight; if (newLeft===undefined) newLeft=this.div.offsetLeft; if (newTop===undefined) newTop=this.div.offsetTop; var minW=cW; minW=(minW > 50 ? 50: minW); if (newLeft+cW <= minW) { newLeft=minW-cW; } else if (newLeft+minW > screenW) { newLeft=screenW-minW; } var minY=-this.offsetTop(); var headerH=this.HeaderHeight(); if (newTopscreenH) { newTop=screenH-headerH; } var contentsW=this.contents.offsetWidth; var contentsH=this.contents.offsetHeight; var newRight=newLeft+contentsW; var newBot=newTop+contentsH; if (newBot > screenH) { if (this.bFitToScreen) { var shiftY=newBot-screenH; newBot-=shiftY; newTop-=shiftY; //LogL("SetPosition(): FitToScreen: shifted Y by "+shiftY); } else { newBot=screenH; } } if (newRight > screenW) { if (this.bFitToScreen) { var shiftX=newRight-screenW; newRight-=shiftX; newLeft-=shiftX; //LogL("SetPosition(): FitToScreen: shifted X by "+shiftX); } else { newRight=screenW; } } var cropW=screenW-newRight; var cropH=screenH-(newBot); /*LogL("SetPosition:"+ " newLeft: "+newLeft+ " newTop: "+newTop+ " newRight: "+newRight+ " newBot: "+newBot+ " screenH: "+screenH+ " screenW: "+screenW+ " cropW: "+cropW+ " cropH: "+cropH+ " w/h: "+(newRight-newLeft)+"/"+(newBot-newTop));*/ this.div.setStyle({ left: newLeft+'px', top: newTop+'px'}); // set dimensions only if popup can resize var newW=newRight-newLeft; var newH=newBot-newTop; this.div.setStyle({ clip: 'rect(-6px,'+(newW+cropW)+'px,'+(newH+cropH)+'px,-6px)' }); this.posX=this.div.offsetLeft; this.posY=this.div.offsetTop; }, AdjustSize: function(newWidth, newHeight, options) { var w=((typeof(newWidth)=='number') ? newWidth : this.contents.scrollWidth); var h=((typeof(newHeight)=='number') ? newHeight : this.contents.scrollHeight); if (options && options.bTotal) { h-=(this.HeaderHeight()+4); // divider height (2px) + borders w-=4; // borders + 1px padding } // apply size constraint if (this.maxHeight && h > this.maxHeight) h=this.maxHeight; this.contents.setStyle({ width: w+'px', height: h+'px' }); //LogL("AdjustSize: Setting w:"+w+", h:"+h+", setting position"); this.SetPosition(undefined, undefined, options); }, Redraw: function() { this.UpdateTitle(); this.AdjustSize(); }, Show: function() { this.div.setStyle({visibility: 'visible'}); }, Hide: function() { this.div.setStyle({visibility: 'hidden'}); }, SetTitle: function(sTitle) { this.sTitle_ = sTitle; }, UpdateTitle: function() { if (typeof(this.title) != 'undefined') { var sT = (this.sTitle_) ? this.sTitle_ : "Window title is not defined!"; this.title.innerHTML = AXML.EscapeEntities(sT); } }, Append: function(eChild) { this.contents.appendChild(eChild); }, NodeID: function() { return this.nodeID; }, Width: function() { return this.div.scrollWidth; }, Height: function() { return this.div.scrollHeight; }, ContentsID: function() { return this.nodeID+'_contents'; }, // used only for APopupWindow Contents: function() { return this.contents; }, SetFitToScreen: function(value) { this.bFitToScreen = value; } }); /** * Popup window grows from generic AWindow object. Uses APopup to add popup style to window **/ var APopupWindow = Class.create(AWindow, { IsPopup: true, initialize: function($super, args) { //args.fCreateCallback = this.OnCreate.bind(this); // not used args.fCloseCallback = this.OnClose.bind(this); if (args.fAfterLoadCallback) this.fAfterLoadCallback = args.fAfterLoadCallback; if (typeof(args.canResize) == 'undefined') args.canResize = true; if (typeof(args.initialWidth) == 'undefined') args.initialWidth = document.body.scrollWidth/2; if (typeof(args.initialHeight) == 'undefined') args.initialHeight = document.body.scrollHeight/2; args.fAfterResizeCallback = this.OnPopupResize.bind(this); this.oPopup = new APopup(args); this.rets = args.rets; args.sControlID = this.oPopup.ContentsID(); if (args.fBeforeLoadCallback) { // allows adding parameters at window creation stage args.fBeforeLoadCallback(args); } this.Subscribe("WM_POPUP_RETURN", this.OnReturn, this); // BeforeLoadCallback can set fOnReturnCallback if(args.fOnReturnCallback) this.Subscribe("WM_POPUP_RETURN", args.fOnReturnCallback); /* default handler commented out, see the reason in comments below. if(typeof(args.fOnWMBackCallback) == 'undefined') { this.Subscribe("WM_BACK", this.OnWMBack, this); } else */if(args.fOnWMBackCallback) this.Subscribe("WM_BACK", args.fOnWMBackCallback, null); $super(args); // constructs AWindow }, // called on parameters return (WM_POPUP_RETURN custom event) // this is a generic handler that passes data to parent window (if any rets specified) OnReturn: function(oEvent) { if (this.rets && oEvent.Data().args) { // if there are any return values and they were set this.oParentWin.UpdateControls(this.rets, oEvent.Data().args); } this.oPopup.Close(); // emulate 'close' event oEvent.Stop(); this.Die(); }, /* Commented out to fix the following problem: when WM_BACK event fires, all popup windows are closed. * For example, this affects Extremist project. * When one needs to close popups on WM_BACK, he should use fOnWMBackCallback popup creation parameter * and write his own handler. OnWMBack: function(oEvent){ oEvent.Stop(); this.oPopup.Close(); this.Die(); }, */ Load: function($super) { $super(); var popup=this.oPopup; popup.SetTitle(this.sTitle_); popup.UpdateTitle(); if (popup.initialWidth && popup.initialHeight && !popup.autoSize) { popup.AdjustSize(popup.initialWidth, popup.initialHeight, {bTotal: popup.bTotalWidthHeight}); popup.AdjustSize(); // this is for proper border height (and width) recalculation } popup.Align(); if (this.fAfterLoadCallback) this.fAfterLoadCallback(this); }, // will be called automatically when popup is signaled to close OnClose: function(oPopup) { new AEvent("WM_POPUP_RETURN", { exitMethod: oPopup.exitMethod }, this); }, Popup: function() { return this.oPopup; }, OnPopupResize: function(oPopup) { // TODO: very BAD behavior! Popups resizing must not affect entire WindowManager. // There must be implemented descending resizing scheme when parents notify children about resizing. GWindowManager.OnResize(); }, Focus: function() { if (this.Focused()) { //LogL("PopupWindow is already focused. "); return; } GWindowManager.ClearTabOrder(FS_NOTFOCUSED); this.SetTabOrder(0); var r=this.focusFirst(this.HTML, SKIP_MYSELF); //LogL("Popup window $$"+this.ID()+"$$ focused with result: "+r); this.bFocused=true; }, Focused: function() { return this.bFocused; } }); /////////////////////////////////////// Popup selector ////////////////////////////////////////// var ClearButton = Class.create(SimpleButton, { initialize: function($super, elControl, sRets) { $super(elControl); this.elementsForClear=[]; var parentWin=AControl.GetParentWindow(elControl); var aRets = sRets.split(' '); for (var i = 0; i < aRets.length; ++i) { var x = aRets[i].indexOf('='); var rName = (x >= 0 ? aRets[i].substr(0, x) : aRets[i]); rName=rName.replace(' ', ''); if (!rName) continue; rName=parentWin.ControlHTMLID(rName); this.elementsForClear.push(rName); } }, OnClick: function() { var aChanged=[]; for(var i=0; i < this.elementsForClear.length; ++i) { var eControl = $(this.elementsForClear[i]); if (eControl && eControl.JSControl && (typeof(eControl.JSControl.SetValue) == 'function')) { eControl.JSControl.SetValue(new Optional()); aChanged.push(eControl.JSControl); } } for(var i=0;i < aChanged.length; ++i) { new AEvent("CHANGE", {}, aChanged[i]); } }, Value: function() { return null; }, SetValue: function() {} }); var SelectorPopupWindowOpener = Class.create(PopupWindowOpener, { initialize: function($super, args) { $super(args); this.initializeEvents(); if(args.clearButton) { this.clearButton = $(args.clearButton); } }, SetTabOrder: function(nTabBase) { var win=GWindowManager.GetWindow(this.winID); var aRets=this.args.rets.split(' '); for (var i = 0; i < aRets.length; ++i) { var x = aRets[i].indexOf('='); var rName = (x >= 0 ? aRets[i].substr(0, x) : aRets[i]); if(!win.Control(rName)) continue; var control=win.Control(rName).JSControl; if(!control) continue; if(control.visibility && control.visibility=="yes") { return nTabBase; } } this.root.tabIndex=nTabBase; if(this.clearButton) { return $(this.clearButton).SetTabOrder(++nTabBase); } else { return ++nTabBase; } }, SetAttribute: function(sName, sValue) { if(sName=='visibility') { if (sValue == "yes") { this.root.setStyle({"display": ""}); this.clearButton.elControl.setStyle({"display": ""}); } else { this.root.setStyle({"display": "none"}); this.clearButton.elControl.setStyle({"display": "none"}); } } } }); ////////////////////////////////////// Context menu /////////////////////////////////////////// var PopupMenu = Class.create({ modal: true, modalLayerStyle: { background: 'transparent' }, initialize: function(params) { this.htmlid = params.htmlid; this.items = new Object(); $(this.htmlid).hide(); this.fCloseCallback = this.OnClick.bind(this); }, Show: function(callback) { var m = $(this.htmlid); GPopupContainer.AddPopup(this); m.show(); this.callback = callback; }, AddItem: function(htmlid, value) { this.items[htmlid] = value; $(htmlid).observe('click', this.OnClick.bind(this, htmlid)); }, OnClick: function(htmlid) { GPopupContainer.RemovePopup(this); $(this.htmlid).hide(); if (this.callback) this.callback(htmlid ? this.items[htmlid] : undefined); }, SetPosition: function() {}, SetZIndex: function(i) { $(this.htmlid).setStyle({ zIndex: i }); } });