/**
* @fileoverview
* Implementation of popup dialogs.
* @author Partridge Jiang
*/
/*
* requires /lan/classes.js
* requires /utils/kekule.utils.js
* requires /utils/kekule.domUtils.js
* requires /xbrowsers/kekule.x.js
* requires /widgets/kekule.widget.base.js
* requires /localization
*/
(function(){
"use strict";
var DU = Kekule.DomUtils;
var EU = Kekule.HtmlElementUtils;
var SU = Kekule.StyleUtils;
var CNS = Kekule.Widget.HtmlClassNames;
/** @ignore */
Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, {
DIALOG: 'K-Dialog',
DIALOG_INSIDE: 'K-Dialog-Inside', // dialog smaller than current view port size
DIALOG_OVERFLOW: 'K-Dialog-Overflow', // dialog larger than current view port size
DIALOG_CLIENT: 'K-Dialog-Client',
DIALOG_CAPTION: 'K-Dialog-Caption',
DIALOG_BTN_PANEL: 'K-Dialog-Button-Panel'
});
/**
* Enumeration of predefined dialog buttons.
* @class
*/
Kekule.Widget.DialogButtons = {
OK: 'ok',
CANCEL: 'cancel',
YES: 'yes',
NO: 'no',
/**
* Whether button is a positive one (e.g. Ok, Yes).
* @param {String} btn
* @returns {Bool}
*/
isPositive: function(btn)
{
var DB = Kekule.Widget.DialogButtons;
return ([DB.OK, DB.YES].indexOf(btn) >= 0);
},
/**
* Whether button is a negative one (e.g. Cancel, No).
* @param {String} btn
* @returns {Bool}
*/
isNegative: function(btn)
{
var DB = Kekule.Widget.DialogButtons;
return ([DB.CANCEL, DB.NO].indexOf(btn) >= 0);
}
};
/**
* Enumeration of location of widget.
* @class
*/
Kekule.Widget.Location = {
/** Show widget as is, no special position handling will be done. */
DEFAULT: 1,
/** Show widget at center of browser window visible area. */
CENTER: 2,
/** Widget will fill all area of browser window visible area. */
FULLFILL: 3,
/**
* Widget will be shown at center of window if its initial size smaller than window,
* or fullfill the whole visible area if its intial size larger than window.
*/
CENTER_OR_FULLFILL: 4
};
/**
* A popup dialog widget.
* @class
* @augments Kekule.Widget.BaseWidget
*
* @property {String} caption Caption of dialog. If no caption is set, the caption bar will be automatically hidden.
* @property {Array} buttons Array of predefined button names that should be shown in dialog.
* @property {String} result The name of button that close this dialog.
* @property {Int} location Value from {@link Kekule.Widget.Location}, determine the position when dialog is popped up.
*/
Kekule.Widget.Dialog = Class.create(Kekule.Widget.BaseWidget,
/** @lends Kekule.Widget.Dialog# */
{
/** @private */
CLASS_NAME: 'Kekule.Widget.Dialog',
/** @private */
BINDABLE_TAG_NAMES: ['div', 'span'],
/** @private */
BTN_NAME_FIELD: '__$btnName__',
/** @constructs */
initialize: function($super, parentOrElementOrDocument, caption, buttons)
{
this._dialogCallback = null;
this._modalInfo = null;
this._childButtons = [];
this.setPropStoreFieldValue('location', Kekule.Widget.Location.CENTER);
$super(parentOrElementOrDocument);
this._dialogOpened = false; // used internally
this.setUseCornerDecoration(true);
if (caption)
this.setCaption(caption);
if (buttons)
this.setButtons(buttons);
this.setDisplayed(false);
},
/** @private */
doFinalize: function($super)
{
//this.unprepareModal(); // if finalize during dialog show, modal preparation should always be unprepared
if (this.getModalInfo())
{
this.getGlobalManager().unprepareModalWidget(this);
}
$super();
},
/** @private */
initProperties: function()
{
this.defineProp('caption', {'dataType': DataType.STRING,
'getter': function() { return DU.getElementText(this.getCaptionElem()); },
'setter': function(value)
{
DU.setElementText(this.getCaptionElem(), value);
SU.setDisplay(this.getCaptionElem(), !!value);
}
});
this.defineProp('buttons', {'dataType': DataType.ARRAY,
'setter': function(value)
{
this.setPropStoreFieldValue('buttons', value);
if (this.getBtnPanelElem())
SU.setDisplay(this.getBtnPanelElem(), value && value.length);
this.buttonsChanged();
}
});
this.defineProp('result', {'dataType': DataType.STRING, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('location', {'dataType': DataType.INT});
// private properties
this.defineProp('clientElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('captionElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
this.defineProp('btnPanelElem', {'dataType': DataType.OBJECT, 'serializable': false, 'setter': null, 'scope': Class.PropertyScope.PUBLIC});
},
/** @ignore */
initPropValues: function($super)
{
$super();
if (this.setMovable)
{
this.setMovable(true);
}
},
/** @ignore */
getDefaultMovingGripper: function()
{
return this.getCaptionElem();
},
/** @ignore */
getCoreElement: function()
{
return this.getClientElem();
},
/** @ignore */
doGetWidgetClassName: function()
{
return CNS.DIALOG;
},
/** @ignore */
doCreateRootElement: function(doc)
{
var result = doc.createElement('div');
return result;
},
/** @ignore */
doCreateSubElements: function(doc, rootElem)
{
var result = [];
// caption element
var elem = doc.createElement('div');
elem.className = CNS.DIALOG_CAPTION;
rootElem.appendChild(elem);
this.setPropStoreFieldValue('captionElem', elem);
result.push(elem);
// click on caption to move
if (this.setMovingGripper)
this.setMovingGripper(elem);
// client element
var elem = doc.createElement('div');
elem.className = CNS.DIALOG_CLIENT;
rootElem.appendChild(elem);
this.setPropStoreFieldValue('clientElem', elem);
this.doCreateClientContents(elem);
result.push(elem);
// button panel element
var elem = doc.createElement('div');
elem.className = CNS.DIALOG_BTN_PANEL;
rootElem.appendChild(elem);
this.setPropStoreFieldValue('btnPanelElem', elem);
result.push(elem);
this.buttonsChanged(); // create buttons if essential
return result;
},
/**
* Create essential child widgets (and other elements) in client area.
* Descendants may override this method.
* @param {HTMLElement} clientElem
* @private
*/
doCreateClientContents: function(clientElem)
{
// do nothing here
},
/**
* Called when buttons property is changed.
* @private
*/
buttonsChanged: function()
{
// clear old buttons
for (var i = this._childButtons.length - 1; i >= 0; --i)
{
var btn = this._childButtons[i];
btn.finalize();
}
var btns = this.getButtons() || [];
for (var i = 0, l = btns.length; i < l; ++i)
{
this.createDialogButton(btns[i]);
}
},
/** @private */
createDialogButton: function(btnName, btnResName, doc, btnPanel)
{
var btnInfo = this._getPredefinedButtonInfo(btnName);
if (!btnInfo) // info can not get, a custom button
{
btnInfo = {'text': btnName};
}
if (btnInfo)
{
var btn = new Kekule.Widget.Button(this, btnInfo.text);
btn[this.BTN_NAME_FIELD] = btnName;
btn.addEventListener('execute', this._reactDialogBtnExec, this);
btn.appendToElem(this.getBtnPanelElem());
btn.__$name__ = btnName;
var resName = btnResName || this._getDialogButtonResName(btnName);
if (resName)
{
btn.linkStyleResource(resName);
}
this._childButtons.push(btn);
return btn;
}
else
return null;
},
/** @private */
_getDialogButtonResName: function(btnName)
{
var DB = Kekule.Widget.DialogButtons;
if ([DB.OK, DB.YES].indexOf(btnName) >= 0)
{
return Kekule.Widget.StyleResourceNames.BUTTON_YES_OK;
}
else if ([DB.CANCEL, DB.NO].indexOf(btnName) >= 0)
{
return Kekule.Widget.StyleResourceNames.BUTTON_NO_CANCEL;
}
else
return null;
},
/** @private */
_reactDialogBtnExec: function(e)
{
var DB = Kekule.Widget.DialogButtons;
var closeButtons = [DB.OK, DB.YES, DB.CANCEL, DB.NO];
var btn = e.target;
if (btn)
{
var btnName = btn.__$name__;
if (closeButtons.indexOf(btnName) >= 0)
{
this.setResult(btnName);
this.close(btnName);
}
}
},
/** @private */
_getPredefinedButtonInfo: function(btnName)
{
var DB = Kekule.Widget.DialogButtons;
//var WT = Kekule.WidgetTexts;
var btnNames = [DB.OK, DB.CANCEL, DB.YES, DB.NO];
var btnTexts = [
//WT.CAPTION_OK, WT.CAPTION_CANCEL, WT.CAPTION_YES, WT.CAPTION_NO
Kekule.$L('WidgetTexts.CAPTION_OK'),
Kekule.$L('WidgetTexts.CAPTION_CANCEL'),
Kekule.$L('WidgetTexts.CAPTION_YES'),
Kekule.$L('WidgetTexts.CAPTION_NO')
];
var index = btnNames.indexOf(btnName);
return (index >= 0)? {'text': btnTexts[index]}: null;
},
/**
* Return a button object corresponding to btnName in dialog.
* @param {String} btnName
* @returns {Kekule.Widget.Button}
*/
getDialogButton: function(btnName)
{
for (var i = this._childButtons.length - 1; i >= 0; --i)
{
var btn = this._childButtons[i];
if (btn[this.BTN_NAME_FIELD] === btnName)
return btn;
}
return null;
},
/** @private */
needAdjustPosition: function()
{
var showHideType = this.getShowHideType();
var ST = Kekule.Widget.ShowHideType;
var result = (showHideType !== ST.DROPDOWN);
//console.log('need adjust pos', result, this.getShowHideType());
return result;
},
/** @private */
_storePositionInfo: function()
{
if (!this.needAdjustPosition())
return;
var elem = this.getElement();
var style = elem.style;
this._posInfo = {
'left': style.left,
'top': style.top,
'right': style.right,
'bottom': style.bottom,
'width': style.width,
'height': style.height,
'position': style.position
}
},
/** @private */
_restorePositionInfo: function()
{
if (!this.needAdjustPosition())
return;
var elem = this.getElement();
var style = elem.style;
var info = this._posInfo;
if (info)
{
style.left = info.left;
style.top = info.top;
style.right = info.right;
style.bottom = info.bottom;
style.width = info.width;
style.height = info.height;
style.position = info.position;
}
},
/**
* Adjust the size and position of dialog before pop up.
* @private
*/
adjustLocation: function()
{
if (!this.needAdjustPosition())
return;
this._storePositionInfo();
var L = Kekule.Widget.Location;
var location = this.getLocation() || L.DEFAULT;
var overflow = false;
if (location !== L.DEFAULT)
{
var l, t, w, h, r, b;
// set display first, otherwise the size may not be set properly
var oldDisplayed = this.getDisplayed();
var oldVisible = this.getVisible();
try
{
this.setDisplayed(true, true); // bypass widgetShowStateChange handle, or recursion
this.setVisible(true, true);
/*
var selfWidth = this.getOffsetWidth();
var selfHeight = this.getOffsetHeight();
*/
//var viewPortDim = Kekule.DocumentUtils.getClientDimension(this.getDocument());
var viewPortDim = Kekule.DocumentUtils.getInnerClientDimension(this.getDocument());
//var selfBoundingRect = Kekule.HtmlElementUtils.getElemBoundingClientRect(this.getElement());
var selfBoundingRect = Kekule.HtmlElementUtils.getElemPageRect(this.getElement(), true);
var selfWidth = selfBoundingRect.right - selfBoundingRect.left;
var selfHeight = selfBoundingRect.bottom - selfBoundingRect.top;
var parent = this.getOffsetParent();
//var parentBoundingRect = parent? Kekule.HtmlElementUtils.getElemBoundingClientRect(parent):
var parentBoundingRect = parent? Kekule.HtmlElementUtils.getElemPageRect(parent, true):
{'left': 0, 'top': 0, 'width': 0, 'height': 0};
overflow = (selfWidth >= viewPortDim.width) || (selfHeight >= viewPortDim.height);
}
finally
{
this.setVisible(oldVisible, true);
this.setDisplayed(oldDisplayed, true);
}
if (location === L.CENTER_OR_FULLFILL)
{
if (overflow)
{
var viewPortBox = Kekule.DocumentUtils.getClientVisibleBox(this.getDocument());
//location = L.FULLFILL;
if (selfWidth >= viewPortDim.width)
{
l = 0;
r = 0;
}
else // width not exceed, center the dialog in horizontal direction
{
l = (viewPortDim.width - selfWidth) / 2;
}
if (selfHeight >= viewPortDim.height)
{
t = 0;
b = 0;
}
else // height not exceed, center in vertical direction
{
t = (viewPortDim.height - selfHeight) / 2;
}
}
else
location = L.CENTER;
}
if (location === L.FULLFILL)
{
/*
w = viewPortDim.width;
h = viewPortDim.height;
*/
l = 0; //-parentBoundingRect.left;
t = 0; //-parentBoundingRect.top;
r = 0;
b = 0;
}
else if (location === L.CENTER)
{
/*
l = (viewPortDim.width - selfWidth) / 2 - parentBoundingRect.left;
t = (viewPortDim.height - selfHeight) / 2 - parentBoundingRect.top;
*/
l = (viewPortDim.width - selfWidth) / 2;
t = (viewPortDim.height - selfHeight) / 2;
//console.log('center', l, t);
}
//overflow = true; // debug
if (overflow) // use absolute position
{
this.removeClassName(CNS.DIALOG_INSIDE);
this.addClassName(CNS.DIALOG_OVERFLOW);
var viewPortScrollPos = Kekule.DocumentUtils.getScrollPosition(this.getDocument());
l += viewPortScrollPos.left;
t += viewPortScrollPos.top;
// ensure left/top are not out of page region
if (l < 0)
l = 0;
if (t < 0)
t = 0;
}
else // use fixed position
{
this.removeClassName(CNS.DIALOG_OVERFLOW);
this.addClassName(CNS.DIALOG_INSIDE);
}
var style = this.getElement().style;
var notUnset = Kekule.ObjUtils.notUnset;
if (notUnset(l))
style.left = l + 'px';
if (notUnset(t))
style.top = t + 'px';
if (notUnset(r))
style.right = r + 'px';
if (notUnset(b))
style.bottom = r + 'px';
if (notUnset(w))
style.width = w + 'px';
if (notUnset(h))
style.height = h + 'px';
//style.position = 'fixed';
this.adjustClientSize(w, h, overflow);
}
this._posAdjusted = true;
},
/** @private */
adjustClientSize: function(dialogWidth, dialogHeight, overflow)
{
// TODO: do nothing here
},
/** @private */
prepareShow: function(callback)
{
// if this dialog has no element parent, just append it to body
var elem = this.getElement();
if (!elem.parentNode)
this.getDocument().body.appendChild(elem);
var self = this;
// defer the function, make sure it be called when elem really in DOM tree
setTimeout(function()
{
self._dialogCallback = callback;
},
0);
/*
if (!this.isShown())
this.adjustLocation();
*/
},
/**
* Show a modal simulation dialog. When the dialog is closed,
* callback(modalResult) will be called.
* @param {Func} callback
* @param {Kekule.Widget.BaseWidget} caller Who calls the show method and make this dialog visible. Can be null.
*/
openModal: function(callback, caller)
{
//this.prepareModal();
this.getGlobalManager().prepareModalWidget(this);
// important, must called before prepareShow, or DOM tree change will cause doWidgetShowStateChanged
// and make callback called even before dialog showing
/*
this.prepareShow(callback);
this.show(caller, null, Kekule.Widget.ShowHideType.DIALOG);
*/
return this.open(callback, caller);
},
/**
* Show a popup dialog. When the dialog is closed,
* callback(modalResult) will be called.
* @param {Func} callback
* @param {Kekule.Widget.BaseWidget} caller Who calls the show method and make this dialog visible. Can be null.
*/
openPopup: function(callback, caller)
{
/*
this.prepareShow(callback);
this.show(caller, null, Kekule.Widget.ShowHideType.POPUP);
*/
return this.open(callback, caller, Kekule.Widget.ShowHideType.POPUP);
},
/**
* Show a modeless dialog. When the dialog is closed,
* callback(dialogResult) will be called.
* @param {Func} callback
* @param {Kekule.Widget.BaseWidget} caller Who calls the show method and make this dialog visible. Can be null.
* @param {Int} showType
*/
open: function(callback, caller, showType)
{
this.prepareShow(callback);
this.show(caller, null, showType || Kekule.Widget.ShowHideType.DIALOG);
this._dialogOpened = true;
return this;
},
/**
* Close the dialog.
* @param {String} result What result should this dialog return when closed.
*/
close: function(result)
{
//var self = this;
this.setResult(result);
this.hide();
},
/**
* Returns whether the dialog result is a positive one (like Ok, Yes).
* @param {String} result Dialog result, if not set, current dialog result will be used.
* @returns {Bool}
*/
isPositiveResult: function(result)
{
return Kekule.Widget.DialogButtons.isPositive(result || this.getResult());
},
/**
* Returns whether the dialog result is a negative one (like Cancel, No).
* @param {String} result Dialog result, if not set, current dialog result will be used.
* @returns {Bool}
*/
isNegativeResult: function(result)
{
return Kekule.Widget.DialogButtons.isNegative(result || this.getResult());
},
/** @ignore */
widgetShowStateBeforeChanging: function($super, isShown)
{
$super(isShown);
if (isShown /*&& (!this.isShown())*/) // show
{
this.adjustLocation();
this.setResult(null);
}
else
{
// IMPORTANT, can not unprepare modal here, otherwise the modification of DOM tree
// affects the disappear transition
/*
if (this._modalInfo)
this.unprepareModal();
*/
}
},
/** @ignore */
doWidgetShowStateChanged: function($super, isShown)
{
$super(isShown);
if (!isShown) // hide
{
if (this._dialogCallback)
{
//console.log('hide and call callback');
this._dialogCallback(this.getResult());
this._dialogCallback = null; // avoid call twice
}
}
},
/** @ignore */
widgetShowStateDone: function($super, isShown)
{
$super(isShown);
if (!isShown && this._dialogOpened) // hide after dialog open
{
if (this.getModalInfo())
{
this.getGlobalManager().unprepareModalWidget(this);
}
if (!this.isShown())
this._restorePositionInfo();
this._dialogOpened = false;
}
}
});
})();