/*
* This file is part of Toolkit.
*
* Toolkit is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Toolkit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
"use strict";
(function(w, TK){
function after_hiding() {
this.__hide_id = false;
if (this.options.display_state === "hiding")
this.set("display_state", "hide");
}
function after_showing() {
this.__hide_id = false;
if (this.options.display_state === "showing")
this.set("display_state", "show");
}
function enable_draw_self() {
if (this._drawn) return;
this._drawn = true;
if (this.needs_redraw) {
TK.S.add(this._redraw, 1);
}
/**
* Is fired when the container is shown.
*
* @event TK.Container#show
*/
this.fire_event("show");
}
function enable_draw_children() {
var C = this.children;
var H = this.hidden_children;
if (C) for (var i = 0; i < C.length; i++) if (!H[i]) C[i].enable_draw();
}
function disable_draw_self() {
if (!this._drawn) return;
this._drawn = false;
if (this.needs_redraw) {
TK.S.remove(this._redraw, 1);
TK.S.remove_next(this._redraw, 1);
}
/**
* Is fired when the container is hidden.
*
* @event TK.Container#hide
*/
this.fire_event("hide");
}
function disable_draw_children() {
var C = this.children;
var H = this.hidden_children;
if (C) for (var i = 0; i < C.length; i++) if (!H[i]) C[i].disable_draw();
}
TK.Container = TK.class({
/**
* TK.Container represents a <code><DIV></code> element contining various
* other widgets or DOMNodes.
*
* Containers have four different display states: <code>show</code>, <code>hide</code>,
* <code>showing</code> and <code>hiding</code>. Each of these states has a corresponding
* CSS class called <code>toolkit-show</code>, <code>toolkit-hide</code>, <code>toolkit-showing</code>
* and <code>toolkit-hiding</code>, respectively. The display state can be controlled using
* the methods {@link TK.Container#show}, {@link TK.Container#hide} and {@link TK.Widget#toggle_hidden}.
*
* A container can keep track of the display states of its child widgets.
* The display state of a child can be changed using {@link TK.Container#hide_child},
* {@link TK.Container#show_child} and {@link TK.Container#toggle_child}.
*
* @class TK.Container
*
* @extends TK.Widget
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {String|HTMLElement} [options.content] - The content of the container. It can either be
* a string which is interpreted as Text or a DOM node. Note that this option will remove all
* child nodes from the container element including those added via append_child.
* @property {Number} [options.hiding_duration] - The duration in ms of the hiding CSS
* transition/animation of this container. If this option is not set, the transition duration
* will be determined by the computed style, which can be rather
* expensive. Setting this option explicitly can therefore be an optimization.
* @property {Number} [options.showing_duration] - The duration in ms of the showing CSS
* transition/animation of this container.
* @property {String} [options.display_state="show"] - The current display state of this container.
* Do not modify, manually.
* @property {Array<TK.Widget>} [options.children=[]] - Add child widgets on init. Will not be maintained on runtime! Just for convenience purposes on init.
*/
_class: "Container",
Extends: TK.Widget,
_options: Object.assign(Object.create(TK.Widget.prototype._options), {
content: "string|DOMNode",
display_state: "string",
hiding_duration: "number",
showing_duration: "number",
children: "array",
}),
options: {
display_state : "show",
children: [],
},
initialize: function (options) {
var E;
TK.Widget.prototype.initialize.call(this, options);
this.hidden_children = [];
/**
* @member {HTMLDivElement} TK.Container#element - The main DIV element. Has class <code>toolkit-container</code>
*/
if (!(E = this.element)) this.element = E = TK.element("div");
TK.add_class(E, "toolkit-container");
this.widgetize(E, true, true, true);
this.__after_hiding = after_hiding.bind(this);
this.__after_showing = after_showing.bind(this);
this.__hide_id = false;
TK.add_class(E, "toolkit-show");
if (this.options.children.length)
this.append_children(this.options.children);
},
/**
* Calls {@link TK.Container#append_child} for an array of widgets.
*
* @method TK.Container#append_children
*
* @param {Array.<TK.Widget>} children - The child widgets to append.
*/
append_children : function (a) {
a.map(this.append_child, this);
},
/**
* Appends <code>child.element</code> to the container element and
* registers <code>child</code> as a child widget.
*
* @method TK.Container#append_child
*
* @param {TK.Widget} child - The child widget to append.
*/
append_child : function(child) {
child.set("container", this.element);
this.add_child(child);
},
set_parent : function(parent) {
if (parent && !(parent instanceof TK.Container)) {
TK.warn("Container %o should not be child of non-container %o", this, parent);
}
TK.Widget.prototype.set_parent.call(this, parent);
},
add_child : function(child) {
TK.Widget.prototype.add_child.call(this, child);
var H = this.hidden_children;
if (!H) this.hidden_children = H = [];
H.push(false);
},
remove_child : function(child) {
if (!child) return;
child.disable_draw();
child.parent = null;
var C = this.children;
if (C === null) return;
var H = this.hidden_children;
var i = C.indexOf(child);
if (i !== -1) {
C.splice(i, 1);
H.splice(i, 1);
}
},
enable_draw: function () {
if (this._drawn) return;
enable_draw_self.call(this);
enable_draw_children.call(this);
},
disable_draw: function () {
if (!this._drawn) return;
disable_draw_self.call(this);
disable_draw_children.call(this);
},
/**
* Starts the transition of the <code>display_state</code> to <code>hide</code>.
*
* @method TK.Container#hide
*
*/
hide: function () {
var O = this.options;
if (O.display_state === "hide") return;
disable_draw_children.call(this);
enable_draw_self.call(this);
if (O.display_state === "hiding") return;
this.set("display_state", "hiding");
},
/**
* Immediately switches the display state of this container to <code>hide</code>.
* Unlike {@link TK.Container#hide} this method does not perform the hiding transition
* and immediately modifies the DOM by setting the <code>toolkit-hide</code> class.
*
* @method TK.Container#force_hide
*
*/
force_hide: function () {
var O = this.options;
if (O.display_state === "hide") return;
this.disable_draw();
var E = this.element;
O.display_state = "hide";
TK.add_class(E, "toolkit-hide");
TK.remove_class(E, "toolkit-hiding", "toolkit-showing", "toolkit-show");
},
/**
* Starts the transition of the <code>display_state</code> to <code>show</code>.
*
* @method TK.Container#show
*
*/
show: function() {
var O = this.options;
enable_draw_self.call(this);
if (O.display_state === "show" || O.display_state === "showing") return;
this.set("display_state", "showing");
},
/**
* Immediately switches the display state of this container to <code>show</code>.
* Unlike {@link TK.Container#hide} this method does not perform the hiding transition
* and immediately modifies the DOM by setting the <code>toolkit-show</code> class.
*
* @method TK.Container#force_show
*
*/
force_show: function() {
var O = this.options;
if (O.display_state === "show") return;
this.enable_draw();
var E = this.element;
O.display_state = "show";
TK.add_class(E, "toolkit-show");
TK.remove_class(E, "toolkit-hiding", "toolkit-showing", "toolkit-hide");
},
show_nodraw: function() {
var O = this.options;
if (O.display_state === "show") return;
this.set("display_state", "show");
var C = this.children;
var H = this.hidden_children;
if (C) for (var i = 0; i < C.length; i++) if (!H[i]) C[i].show_nodraw();
},
hide_nodraw: function() {
var O = this.options;
if (O.display_state === "hide") return;
this.set("display_state", "hide");
var C = this.children;
var H = this.hidden_children;
if (C) for (i = 0; i < C.length; i++) if (!H[i]) C[i].hide_nodraw();
},
/**
* Switches the hidden state of a child to <code>hidden</code>.
* The argument is either the child index or the child itself.
*
* @method TK.Container#hide_child
* @param {Object|integer} child - Child or its index.
*
*/
hide_child: function(i) {
var C = this.children;
var H = this.hidden_children;
if (typeof i !== "number") {
i = C.indexOf(i);
if (i === -1) throw("Cannot find child.");
}
H[i] = true;
C[i].hide();
},
/**
* Switches the hidden state of a child to <code>shown</code>.
* The argument is either the child index or the child itself.
*
* @method TK.Container#show_child
* @param {Object|integer} child - Child or its index.
*
*/
show_child: function(i) {
var C = this.children;
var H = this.hidden_children;
if (typeof i !== "number") {
i = C.indexOf(i);
if (i === -1) throw("Cannot find child.");
}
if (H[i]) {
H[i] = false;
if (this.is_drawn()) C[i].show();
else C[i].show_nodraw();
}
},
/**
* Toggles the hidden state of a child.
* The argument is either the child index or the child itself.
*
* @method TK.Container#toggle_child
* @param {Object|integer} child - Child or its index.
*
*/
toggle_child: function(i) {
var C = this.children;
var H = this.hidden_children;
if (typeof i !== "number") {
i = C.indexOf(i);
if (i === -1) throw("Cannot find child.");
}
if (H[i]) this.show_child(i);
else this.hide_child(i);
},
visible_children: function(a) {
if (!a) a = [];
var C = this.children;
var H = this.hidden_children;
if (C) for (var i = 0; i < C.length; i++) {
if (H[i]) continue;
a.push(C[i]);
C[i].visible_children(a);
}
return a;
},
hidden: function() {
var state = this.options.display_state;
return TK.Widget.prototype.hidden.call(this) || state === "hiding" || state === "hide";
},
redraw: function() {
var O = this.options;
var I = this.invalid;
var E = this.element;
TK.Widget.prototype.redraw.call(this);
if (I.display_state) {
I.display_state = false;
var time;
TK.remove_class(E, "toolkit-hiding", "toolkit-hide", "toolkit-showing", "toolkit-show");
if (this.__hide_id) {
window.clearTimeout(this.__hide_id);
this.__hide_id = false;
}
switch (O.display_state) {
case "hiding":
TK.add_class(E, "toolkit-hiding");
time = O.hiding_duration || TK.get_duration(E);
if (time > 0) {
this.__hide_id = window.setTimeout(this.__after_hiding, time);
break;
}
this.set("display_state", "hide");
TK.remove_class(E, "toolkit-hiding");
/* FALL THROUGH */
case "hide":
TK.add_class(E, "toolkit-hide");
disable_draw_self.call(this);
break;
case "showing":
TK.add_class(E, "toolkit-showing");
time = O.showing_duration || TK.get_duration(E);
if (time > 0) {
this.__hide_id = window.setTimeout(this.__after_showing, time);
enable_draw_children.call(this);
break;
}
this.set("display_state", "show");
TK.remove_class(E, "toolkit-showing");
/* FALL THROUGH */
case "show":
TK.add_class(E, "toolkit-show");
enable_draw_children.call(this);
break;
}
}
if (I.content) {
I.content = false;
TK.empty(E);
if (typeof O.content === "string") TK.set_content(E, O.content);
else E.appendChild(O.content);
}
},
});
})(this, this.TK);