/*
* 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
*/
/**
* The <code>useraction</code> event is emitted when a widget gets modified by user interaction.
* The event is emitted for the option <code>value</code>.
*
* @event TK.Fader#useraction
*
* @param {string} name - The name of the option which was changed due to the users action
* @param {mixed} value - The new value of the option
*/
"use strict";
(function(w, TK){
function vert(O) {
return O.layout === "left" || O.layout === "right";
}
function get_value(ev) {
var is_vertical = vert(this.options);
var pos, real, hsize, pad;
hsize = this._handle_size / 2;
pad = this._padding;
if (is_vertical) {
real = this.options.basis - (ev.offsetY - hsize) + pad.bottom;
} else {
real = ev.offsetX - hsize + pad.left;
}
return this.px2val(real);
}
function tooltip_by_position(ev, tt) {
if (this._handle.contains(ev.target)) {
tooltip_by_value.call(this, ev, tt);
return;
}
var val = this.snap(get_value.call(this, ev));
TK.set_text(tt, this.options.tooltip(val));
}
function tooltip_by_value(ev, tt) {
TK.set_text(tt, this.options.tooltip(this.options.value));
}
function mouseenter (ev) {
if (!this.options.tooltip) return;
TK.tooltip.add(1, this.tooltip_by_position);
}
function clicked(ev) {
var value;
if (this._handle.contains(ev.target)) return;
if (this.value && this.value.element.contains(ev.target)) return;
if (this.label && this.label.element.contains(ev.target)) return;
if (this.scale && this.scale.element.contains(ev.target)) return;
value = this.userset("value", get_value.call(this, ev));
if (this.options.tooltip && TK.tooltip._entry)
TK.set_text(TK.tooltip._entry, this.options.tooltip(this.options.value));
}
function mouseleave (ev) {
TK.tooltip.remove(1, this.tooltip_by_position);
}
function startdrag(ev) {
if (!this.options.tooltip) return;
TK.tooltip.add(0, this.tooltip_by_value);
}
function stopdrag(ev) {
TK.tooltip.remove(0, this.tooltip_by_value);
}
function scrolling(ev) {
if (!this.options.tooltip) return;
TK.set_text(TK.tooltip._entry, this.options.tooltip(this.options.value));
}
function dblclick(ev) {
this.userset("value", this.options.reset);
/**
* Is fired when the handle receives a double click.
*
* @event TK.Fader#doubleclick
*
* @param {number} value - The value of the {@link TK.Fader}.
*/
this.fire_event("doubleclick", this.options.value);
}
function activate_tooltip() {
if (!this.tooltip_by_position) {
this.tooltip_by_position = tooltip_by_position.bind(this);
this.tooltip_by_value = tooltip_by_value.bind(this);
this.__startdrag = startdrag.bind(this);
this.__stopdrag = stopdrag.bind(this);
this.__scrolling = scrolling.bind(this);
}
this.add_event("mouseenter", mouseenter);
this.add_event("mouseleave", mouseleave);
this.drag.add_event("startdrag", this.__startdrag);
this.drag.add_event("stopdrag", this.__stopdrag);
this.scroll.add_event("scrolling", this.__scrolling);
}
function deactivate_tooltip() {
if (!this.tooltip_by_position) return;
TK.tooltip.remove(0, this.tooltip_by_value);
TK.tooltip.remove(1, this.tooltip_by_position);
this.remove_event("mouseenter", mouseenter);
this.remove_event("mouseleave", mouseleave);
this.drag.remove_event("startdrag", this.__startdrag);
this.drag.remove_event("stopdrag", this.__stopdrag);
this.scroll.remove_event("scrolling", this.__scrolling);
}
/**
* TK.Fader is a slidable control with a {@link TK.Scale} next to it which
* can be both dragged and scrolled. TK.Fader implements {@link TK.Ranged},
* {@link TK.Warning} and {@link TK.GlobalCursor} and inherits their options.
* A {@link TK.Label} and a {@link TK.Value} are available optionally.
*
* @class TK.Fader
*
* @extends TK.Widget
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {Number} [options.value] - The faders position. This options is
* modified by user interaction.
* @property {Function} [options.tooltip=false] - An optional formatting function for
* the tooltip value. The tooltip will show the value the mouse cursor is
* currently hovering over. If this option is not set, no tooltip will be shown.
* @property {Boolean} [options.bind_click=false] - If true, a <code>click</code>
* on the fader will move the handle to the pointed position.
* @property {Boolean} [options.bind_dblclick=true] - If true, a <code>dblclick</code>
* on the fader will reset the fader value to <code>options.reset</code>.
* @property {Number} [options.reset=options.value] - The reset value, which is used by
* the <code>dblclick</code> event and the {@link TK.Fader#reset} method.
* @property {Boolean} [options.show_scale=true] - If true, a {@link TK.Scale} is added to the fader.
* @property {Boolean} [options.show_value=false] - If true, a {@link TK.Value} widget is added to the fader.
* @property {String|Boolean} [options.label=false] - Add a label to the fader. Set to `false` to remove the label from the DOM.
*/
TK.Fader = TK.class({
_class: "Fader",
Extends: TK.Widget,
Implements: [TK.Ranged, TK.Warning, TK.GlobalCursor],
_options: Object.assign(Object.create(TK.Widget.prototype._options),
TK.Ranged.prototype._options, TK.Scale.prototype._options, {
value: "number",
division: "number",
levels: "array",
gap_dots: "number",
gap_labels: "number",
show_labels: "boolean",
labels: "function",
tooltip: "function",
layout: "string",
direction: "int",
reset: "number",
bind_click: "boolean",
bind_dblclick: "boolean",
}),
options: {
value: 0,
division: 1,
levels: [1, 6, 12, 24],
gap_dots: 3,
gap_labels: 40,
show_labels: true,
labels: function (val) { return val.toFixed(2); },
tooltip: false,
layout: "left",
bind_click: false,
bind_dblclick: true,
label: false,
},
static_events: {
set_bind_click: function(value) {
if (value) this.add_event("click", clicked);
else this.remove_event("click", clicked);
},
set_bind_dblclick: function(value) {
if (value) this.add_event("dblclick", dblclick);
else this.remove_event("dblclick", dblclick);
},
set_tooltip: function(value) {
(value ? activate_tooltip : deactivate_tooltip).call(this);
},
set_layout: function(value) {
this.options.direction = vert(this.options) ? "vertical" : "horizontal";
this.drag.set("direction", this.options.direction);
this.scroll.set("direction", this.options.direction);
},
},
initialize: function (options) {
this.__tt = false;
TK.Widget.prototype.initialize.call(this, options);
var E, O = this.options;
/**
* @member {HTMLDivElement} TK.Fader#element - The main DIV container.
* Has class <code>toolkit-fader</code>.
*/
if (!(E = this.element)) this.element = E = TK.element("div");
TK.add_class(E, "toolkit-fader");
this.widgetize(E, true, true, true);
/**
* @member {HTMLDivElement} TK.Fader#_track - The track for the handle. Has class <code>toolkit-track</code>.
*/
this._track = TK.element("div", "toolkit-track");
this.element.appendChild(this._track);
/**
* @member {HTMLDivElement} TK.Fader#_handle - The handle of the fader. Has class <code>toolkit-handle</code>.
*/
this._handle = TK.element("div", "toolkit-handle");
this._handle_size = 0;
this._track.appendChild(this._handle);
if (O.reset === void(0))
O.reset = O.value;
if (O.direction === void(0))
O.direction = vert(O) ? "vertical" : "horizontal";
/**
* @member {TK.DragValue} TK.Fader#drag - Instance of {@link TK.DragValue} used for the handle
* interaction.
*/
this.drag = new TK.DragValue(this, {
node: this._handle,
classes: this.element,
direction: O.direction,
limit: true,
});
/**
* @member {TK.ScrollValue} TK.Fader#scroll - Instance of {@link TK.ScrollValue} used for the
* handle interaction.
*/
this.scroll = new TK.ScrollValue(this, {
node: this.element,
classes: this.element,
limit: true,
});
this.set("bind_click", O.bind_click);
this.set("bind_dblclick", O.bind_dblclick);
this.set("tooltip", O.tooltip);
},
redraw: function () {
TK.Widget.prototype.redraw.call(this);
var I = this.invalid;
var O = this.options;
var E = this.element;
var value;
var tmp;
if (I.layout) {
I.layout = false;
value = O.layout;
TK.remove_class(E, "toolkit-vertical", "toolkit-horizontal", "toolkit-left",
"toolkit-right", "toolkit-top", "toolkit-bottom");
TK.add_class(E, vert(O) ? "toolkit-vertical" : "toolkit-horizontal");
TK.add_class(E, "toolkit-"+value);
if (TK.supports_transform)
this._handle.style.transform = null;
else {
if (vert(O))
this._handle.style.left = null;
else
this._handle.style.bottom = null;
}
I.value = false;
}
if (I.validate.apply(I, Object.keys(TK.Ranged.prototype._options)) || I.value) {
I.value = false;
// TODO: value is snapped already in set(). This is not enough for values which are set during
// initialization.
tmp = this.val2px(this.snap(O.value)) + "px"
if (vert(O)) {
if (TK.supports_transform)
this._handle.style.transform = "translateY(-"+tmp+")";
else
this._handle.style.bottom = tmp;
} else {
if (TK.supports_transform)
this._handle.style.transform = "translateX("+tmp+")";
else
this._handle.style.left = tmp;
}
}
},
resize: function () {
var O = this.options;
var T = this._track, H = this._handle;
var basis;
TK.Widget.prototype.resize.call(this);
this._padding = TK.css_space(T, "padding", "border");
if (vert(O)) {
this._handle_size = TK.outer_height(H, true);
basis = TK.inner_height(T) - this._handle_size;
} else {
this._handle_size = TK.outer_width(H, true);
basis = TK.inner_width(T) - this._handle_size;
}
this.set("basis", basis);
},
destroy: function () {
this._handle.remove();
TK.Widget.prototype.destroy.call(this);
TK.tooltip.remove(0, this.tooltip_by_value);
TK.tooltip.remove(1, this.tooltip_by_position);
},
/**
* Resets the fader value to <code>options.reset</code>.
*
* @method TK.Fader#reset
*/
reset: function() {
this.set("value", this.options.reset);
},
// GETTER & SETTER
set: function (key, value) {
if (key === "value") {
if (value > this.options.max || value < this.options.min)
this.warning(this.element);
value = this.snap(value);
}
return TK.Widget.prototype.set.call(this, key, value);
},
userset: function (key, value) {
if (key == "value") {
if (value > this.options.max || value < this.options.min)
this.warning(this.element);
value = this.snap(value);
}
return TK.Widget.prototype.userset.call(this, key, value);
}
});
/**
* @member {TK.Scale} TK.Fader#scale - A {@link TK.Scale} to display a scale next to the fader.
*/
TK.ChildWidget(TK.Fader, "scale", {
create: TK.Scale,
show: true,
inherit_options: true,
toggle_class: true,
static_events: {
set: function(key, value) {
/**
* Is fired when the scale was changed.
*
* @event TK.Fader#scalechanged
*
* @param {string} key - The key of the option.
* @param {mixed} value - The value to which it was set.
*/
if (this.parent)
this.parent.fire_event("scalechanged", key, value);
},
},
});
/**
* @member {TK.Label} TK.Fader#label - A {@link TK.label} to display a title.
*/
TK.ChildWidget(TK.Fader, "label", {
create: TK.Label,
show: false,
toggle_class: true,
option: "label",
map_options: {
label: "label",
},
});
/**
* @member {TK.Label} TK.Fader#value - A {@link TK.Value} to display the current value, offering a way to enter a value via keyboard.
*/
TK.ChildWidget(TK.Fader, "value", {
create: TK.Value,
show: false,
static_events: {
"valueset" : function (v) { this.parent.set("value", v); }
},
map_options: {
value: "value",
format: "format",
},
toggle_class: true,
userset_delegate: true,
});
})(this, this.TK);