412 lines
14 KiB
JavaScript
412 lines
14 KiB
JavaScript
|
/*
|
||
|
* 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);
|