/*
* Copyright (c) 2014-2015, Wanadev <http://www.wanadev.fr/>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Wanadev nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Authored by: Fabien LOISON <http://flozz.fr/>
*/
/**
* PhotonUI - Javascript Web User Interface.
*
* @module PhotonUI
* @submodule Container
* @namespace photonui
*/
var Stone = require("stonejs");
var Helpers = require("../helpers.js");
var Widget = require("../widget.js");
var BaseWindow = require("./basewindow.js");
var _windowList = [];
/**
* Window.
*
* wEvents:
*
* * close-button-clicked:
* - description: called when the close button was clicked.
* - callback: function(widget)
*
* @class Window
* @constructor
* @extends photonui.BaseWindow
*/
var Window = BaseWindow.$extend({ // jshint ignore:line
// Constructor
__init__: function (params) {
this._registerWEvents(["close-button-clicked"]);
this.$super(params);
// Bind js events
this._bindEvent("move.dragstart", this.__html.windowTitle, "mousedown", this.__moveDragStart.bind(this));
this._bindEvent("move.touchstart", this.__html.windowTitle, "touchstart", this.__moveTouchStart.bind(this));
this._bindEvent("closeButton.click", this.__html.windowTitleCloseButton, "click",
this.__closeButtonClicked.bind(this));
this._bindEvent("totop", this.__html.window, "mousedown", this.moveToFront.bind(this));
this._bindEvent("totop-touch", this.__html.window, "touchstart", this.moveToFront.bind(this));
// Update Properties
this.moveToFront();
},
//////////////////////////////////////////
// Properties and Accessors //
//////////////////////////////////////////
// ====== Public properties ======
/**
* The window title.
*
* @property title
* @type String
* @default "Window"
*/
_title: "Window",
getTitle: function () {
"@photonui-update";
return this._title;
},
setTitle: function (title) {
this._title = title;
Helpers.cleanNode(this.__html.windowTitleText);
this.__html.windowTitleText.appendChild(document.createTextNode(title));
},
/**
* Determine if the window can be moved (drag & drop) by the user.
*
* @property movable
* @type Boolean
* @default true
*/
_movable: true,
isMovable: function () {
return this._movable && (!this._fullscreen);
},
setMovable: function (movable) {
this._movable = movable;
},
/**
* Fullscreen Window
*
* @property fullscreen
* @type Boolean
* @default false
*/
_fullscreen: false,
isFullscreen: function () {
return this._fullscreen;
},
setFullscreen: function (fullscreen) {
this._fullscreen = Boolean(fullscreen);
if (this._fullscreen) {
this.addClass("photonui-window-fullscreen");
} else {
this.removeClass("photonui-window-fullscreen");
}
},
/**
* Determine if the close button in the title bar is displayed or not.
*
* @property closeButtonVisible
* @type Boolean
* default: true
*/
_closeButtonVisible: true,
getCloseButtonVisible: function () {
"@photonui-update";
return this._closeButtonVisible;
},
setCloseButtonVisible: function (closeButtonVisible) {
this._closeButtonVisible = closeButtonVisible;
if (closeButtonVisible) {
this.addClass("photonui-window-have-button");
this.__html.windowTitleCloseButton.style.display = "block";
} else {
this.removeClass("photonui-window-have-button");
this.__html.windowTitleCloseButton.style.display = "none";
}
},
/**
* Modal window?
*
* @property modal
* @type Boolean
* @default false
*/
_modal: false,
isModal: function () {
return this._modal;
},
setModal: function (modal) {
this._modal = modal;
if (modal && !this.__html.modalBox) {
this.__html.modalBox = document.createElement("div");
this.__html.modalBox.className = "photonui-window-modalbox";
var parentNode = Widget.e_parent || document.getElementsByTagName("body")[0];
parentNode.appendChild(this.__html.modalBox);
this.visible = this.visible; // Force update
} else if (!modal && this.__html.modalBox) {
this.__html.modalBox.parentNode.removeChild(this.__html.modalBox);
delete this.__html.modalBox;
}
},
/**
* HTML Element that contain the child widget HTML.
*
* @property containerNode
* @type HTMLElement
* @readOnly
*/
getContainerNode: function () {
return this.__html.windowContent;
},
setVisible: function (visible) {
this.$super(visible);
if (this.visible) {
this.moveToFront();
if (this.modal) {
this.__html.modalBox.style.display = "block";
}
} else {
this.moveToBack();
if (this.modal) {
this.__html.modalBox.style.display = "none";
}
}
},
//////////////////////////////////////////
// Methods //
//////////////////////////////////////////
// ====== Public methods ======
/**
* Bring the window to front.
*
* @method moveToFront
*/
moveToFront: function () {
var index = _windowList.indexOf(this);
if (index >= 0) {
_windowList.splice(index, 1);
}
_windowList.unshift(this);
this._updateWindowList();
},
/**
* Bring the window to the back.
*
* @method moveToBack
*/
moveToBack: function () {
var index = _windowList.indexOf(this);
if (index >= 0) {
_windowList.splice(index, 1);
}
_windowList.push(this);
this._updateWindowList();
},
/**
* Destroy the widget.
*
* @method destroy
*/
destroy: function () {
this.modal = false;
var index = _windowList.indexOf(this);
if (index >= 0) {
_windowList.splice(index, 1);
}
this.$super();
},
// ====== Private methods ======
/**
* Build the widget HTML.
*
* @method _buildHtml
* @private
*/
_buildHtml: function () {
var _ = Stone.lazyGettext;
this.$super();
this.__html.window.className += " photonui-window";
this.__html.windowTitle = document.createElement("div");
this.__html.windowTitle.className = "photonui-window-title";
this.__html.window.appendChild(this.__html.windowTitle);
this.__html.windowTitleCloseButton = document.createElement("button");
this.__html.windowTitleCloseButton.className = "photonui-window-title-close-button fa fa-times";
this.__html.windowTitleCloseButton.title = _("Close");
this.__html.windowTitle.appendChild(this.__html.windowTitleCloseButton);
this.__html.windowTitleText = document.createElement("span");
this.__html.windowTitleText.className = "photonui-window-title-text";
this.__html.windowTitle.appendChild(this.__html.windowTitleText);
this.__html.windowContent = document.createElement("div");
this.__html.windowContent.className = "photonui-container photonui-window-content";
this.__html.window.appendChild(this.__html.windowContent);
},
/**
* Update all the windows.
*
* @method _updateWindowList
* @private
*/
_updateWindowList: function () {
for (var i = _windowList.length - 1, z = 0 ; i >= 0 ; i--, z++) { // jshint ignore:line
if (i === 0) {
_windowList[i].html.style.zIndex = 2001;
_windowList[i].addClass("photonui-active");
} else {
_windowList[i].html.style.zIndex = 1000 + z;
_windowList[i].removeClass("photonui-active");
}
if (_windowList[i].modal) {
_windowList[i].html.style.zIndex = 3001;
}
}
},
//////////////////////////////////////////
// Internal Events Callbacks //
//////////////////////////////////////////
/**
* Start moving the window.
*
* @method _moveDragStart
* @private
* @param {Object} event
*/
__moveDragStart: function (event) {
if (!this.movable || event.button > 0 || event.target === this.__html.windowTitleCloseButton) {
return;
}
var offsetX = (event.offsetX !== undefined) ? event.offsetX : event.layerX;
var offsetY = (event.offsetY !== undefined) ? event.offsetY : event.layerY;
this.__html.windowTitle.style.cursor = "move";
this._bindEvent("move.dragging", document, "mousemove", this.__moveDragging.bind(this, offsetX, offsetY));
this._bindEvent("move.dragend", document, "mouseup", this.__moveDragEnd.bind(this));
},
/**
* Move the window.
*
* @method _moveDragging
* @private
* @param {Number} offsetX
* @param {Number} offsetY
* @param {Object} event
*/
__moveDragging: function (offsetX, offsetY, event) {
this.__internalDragging(offsetX, offsetY, event.pageX, event.pageY);
},
/**
* Stop moving the window.
*
* @method _moveDragEnd
* @private
* @param {Object} event
*/
__moveDragEnd: function (event) {
this.__html.windowTitle.style.cursor = "default";
this._unbindEvent("move.dragging");
this._unbindEvent("move.dragend");
},
/**
* Move the window.
*
* @method __internalDragging
* @private
* @param {Number} offsetX
* @param {Number} offsetY
* @param {Number} pageX
* @param {Number} pageY
*/
__internalDragging: function (offsetX, offsetY, pageX, pageY) {
var e_body = document.getElementsByTagName("body")[0];
var x = Math.min(Math.max(pageX - offsetX, 40 - this.offsetWidth), e_body.offsetWidth - 40);
var y = Math.max(pageY - offsetY, 0);
if (e_body.offsetHeight > 0) {
y = Math.min(y, e_body.offsetHeight - this.__html.windowTitle.offsetHeight);
}
this.setPosition(x, y);
},
/**
* Start moving the window.
*
* @method _moveTouchStart
* @private
* @param {Object} event
*/
__moveTouchStart: function (event) {
if (!this.movable) {
return;
}
if (event.target !== this.__html.windowTitle) {
return;
}
var touchEvent = this.__getTouchEvent(event);
this.__html.windowTitle.style.cursor = "move";
this._bindEvent("move.touchmove", document, "touchmove",
this.__moveTouchMove.bind(this, touchEvent.offsetX, touchEvent.offsetY));
this._bindEvent("move.touchend", document, "touchend", this.__moveTouchEnd.bind(this));
this._bindEvent("move.touchcancel", document, "touchcancel", this.__moveTouchEnd.bind(this));
},
/**
* Move the window.
*
* @method _moveTouchMove
* @private
* @param {Number} offsetX
* @param {Number} offsetY
* @param {Object} event
*/
__moveTouchMove: function (offsetX, offsetY, event) {
var touchEvent = this.__getTouchEvent(event);
this.__internalDragging(offsetX, offsetY, touchEvent.pageX, touchEvent.pageY);
},
/**
* Stop moving the window.
*
* @method _moveTouchEnd
* @private
* @param {Object} event
*/
__moveTouchEnd: function (event) {
this.__html.windowTitle.style.cursor = "default";
this._unbindEvent("move.touchmove");
this._unbindEvent("move.touchend");
this._unbindEvent("move.touchcancel");
},
/**
* Gets the first touch event and normalizes pageX/Y and offsetX/Y properties.
*
* @method _moveTouchEnd
* @private
* @param {Object} event
*/
__getTouchEvent: function (event) {
if (event.touches && event.touches.length) {
event.preventDefault();
var evt = event.touches[0];
evt.pageX = evt.pageX || evt.clientX;
evt.pageY = evt.pageX || evt.clientY;
var position = Helpers.getAbsolutePosition(event.target);
evt.offsetX = evt.offsetX || evt.pageX - position.x;
evt.offsetY = evt.offsetY || evt.pageY - position.y;
return evt;
}
return event;
},
/**
* Close button clicked.
*
* @method _closeButtonClicked
* @private
* @param {Object} event
*/
__closeButtonClicked: function (event) {
this._callCallbacks("close-button-clicked");
},
/**
* Called when the locale is changed.
*
* @method __onLocaleChanged
* @private
*/
__onLocaleChanged: function () {
this.$super();
this.__html.windowTitleCloseButton.title = Stone.lazyGettext("Close");
}
});
module.exports = Window;