/*
* 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 toggle(e) {
var self = this.parent;
e.preventDefault();
e.stopPropagation();
return collapse.call(self, !self.options.expanded);
}
function collapse(state) {
this.userset("expanded", state);
return false;
}
function visible_when_expanded(widget) {
var v = widget.options._expanded;
return v !== false;
}
function visible_when_collapsed(widget) {
var v = widget.options._collapsed;
return v === true;
}
function is_visible(widget) {
var value = this.options.always_expanded || this.options.expanded;
if (value) {
return visible_when_expanded(widget);
} else {
return visible_when_collapsed(widget);
}
}
function changed_expanded(value) {
var group = this.options.group;
var other_expander;
var grp;
if (group) {
grp = expander_groups[group];
if (value) {
other_expander = grp.active;
grp.active = this;
if (other_expander && other_expander !== this)
other_expander.set("expanded", false);
} else if (grp.active === this) {
grp.active = false;
if (grp.default) grp.default.set("expanded", true);
}
}
update_visibility.call(this);
}
function add_to_group(group) {
var grp;
var O = this.options;
if (!(grp = expander_groups[group]))
expander_groups[group] = grp = { active: false, default: false };
if (O.group_default) {
grp.default = this;
if (!grp.active) {
this.set("expanded", true);
return;
}
}
if (O.expanded)
changed_expanded.call(this, O.expanded);
}
function remove_from_group(group) {
var grp = expander_groups[group];
if (grp.default === this) grp.default = false;
if (grp.active === this) {
grp.active = false;
if (grp.default) grp.default.set("expanded", true);
}
}
function remove_group_default(group) {
if (!group) return;
var grp = expander_groups[group];
grp.default = false;
}
function update_visibility() {
var C = this.children;
var value = this.options.always_expanded || this.options.expanded;
if (C) {
var test = value ? visible_when_expanded : visible_when_collapsed;
for (var i = 0; i < C.length; i++) {
if (test(C[i]))
this.show_child(i);
else
this.hide_child(i);
}
}
if (value) {
this.fire_event("expand");
/**
* Is fired when the expander expands.
*
* @event TK.Expander#expand
*/
} else {
/**
* Is fired when the expander collapses.
*
* @event TK.Expander#collapse
*/
this.fire_event("collapse");
}
}
var expander_groups = { };
w.eg = expander_groups;
TK.Expander = TK.class({
/**
* TK.Expander is a container which can be toggled between two different states,
* expanded and collapsed. It can be used to implement overlay popups, but it is
* not limited to that application.
* In expanded mode the container has the class <code>toolkit-expanded</code>.
* Child widgets are shown or hidden depending on the state of the two pseudo
* options <code>_expanded</code> and <code>_collapsed</code>. If a child widget
* of the expander has <code>_expanded</code> set to true it will be shown in
* expanded state. If a child widget has <code>_collapsed</code> set to false, it
* will be shown in collapsed state. This feature can be used to make interfaces
* more reactive.
*
* @class TK.Expander
*
* @extends TK.Container
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {Boolean} [options.expanded=false] - The state of the widget.
* @property {Boolean} [options.always_expanded=false] - This essentially overwrites
* the <code>expanded</code> option. This can be used to switch this widget to be
* always expanded, e.g. when the screen size is big enough.
* @property {String} [options.group=""] - If set, this expander is grouped together with
* all other expanders of the same group name. At most one expander of the same group
* can be open at one time.
* @property {Boolean} [options.group_default=false] - If set, this expander is expanded
* if all other group members are collapsed.
* @property {String} [options.icon=""] - Icon of the {@link TK.Button} which toggles expanded state.
* @property {String} [options.label=""] - Label of the {@link TK.Button} which toggles expanded state.
* @property {Boolean} [options.show_button=true] - Set to `false` to hide the {@link TK.Button} toggling expanded state.
*/
_class: "Expander",
_options: Object.assign(Object.create(TK.Container.prototype._options), {
expanded: "boolean",
always_expanded: "boolean",
group: "string",
group_default: "boolean",
label: "string",
icon: "string",
}),
options: {
expanded: false,
always_expanded: false,
group_default: false,
label: "",
icon: "",
},
static_events: {
set_expanded: changed_expanded,
set_always_expanded: update_visibility,
set_group: function(value) {
if (value) add_to_group.call(this, value);
}
},
Extends: TK.Container,
/**
* Toggles the collapsed state of the widget.
*
* @method TK.Expander#toggle
*/
toggle: function() {
toggle.call(this);
},
redraw: function() {
var I = this.invalid;
var O = this.options;
TK.Container.prototype.redraw.call(this);
if (I.always_expanded) {
this[O.always_expanded ? "add_class" : "remove_class"]("toolkit-always-expanded");
}
if (I.expanded || I.always_expanded) {
I.always_expanded = I.expanded = false;
var v = O.always_expanded || O.expanded;
this[v ? "add_class" : "remove_class"]("toolkit-expanded");
this.trigger_resize();
}
},
initialize: function (options) {
TK.Container.prototype.initialize.call(this, options);
/**
* @member {HTMLDivElement} TK.Expander#element - The main DIV container.
* Has class <code>toolkit-expander</code>.
*/
TK.add_class(this.element, "toolkit-expander");
this._update_visibility = update_visibility.bind(this);
if (this.options.group) add_to_group.call(this, this.options.group);
this.set("expanded", this.options.expanded);
this.set("always_expanded", this.options.always_expanded);
},
add_child: function(child) {
TK.Container.prototype.add_child.call(this, child);
if (!is_visible.call(this, child)) this.hide_child(child);
child.add_event("set__expanded", this._update_visibility);
child.add_event("set__collapsed", this._update_visibility);
},
remove_child: function(child) {
TK.Container.prototype.remove_child.call(this, child);
child.remove_event("set__expanded", this._update_visibility);
child.remove_event("set__collapsed", this._update_visibility);
},
set: function(key, value) {
var group;
if (key === "group") {
group = this.options.group;
// this is reached from init, where this element was never added
// to the group.
if (group && value !== group) remove_from_group.call(this, group);
} else if (key === "group_default") {
if (!value && this.options.group_default)
remove_group_default.call(this, this.options.group);
}
return TK.Container.prototype.set.call(this, key, value);
},
});
/**
* @member {TK.Button} TK.Expander#button - The button for toggling the state of the expander.
*/
TK.ChildWidget(TK.Expander, "button", {
create: TK.Button,
show: true,
map_options: {
label: "label",
icon: "icon",
},
default_options: {
_expanded: true,
_collapsed: true,
class: "toolkit-toggle-expand"
},
static_events: {
click: toggle,
},
});
})(this, this.TK);