/* * 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 useraction event is emitted when a widget gets modified by user interaction. * The event is emitted for the option value. * * @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 click * on the fader will move the handle to the pointed position. * @property {Boolean} [options.bind_dblclick=true] - If true, a dblclick * on the fader will reset the fader value to options.reset. * @property {Number} [options.reset=options.value] - The reset value, which is used by * the dblclick 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 toolkit-fader. */ 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 toolkit-track. */ this._track = TK.element("div", "toolkit-track"); this.element.appendChild(this._track); /** * @member {HTMLDivElement} TK.Fader#_handle - The handle of the fader. Has class toolkit-handle. */ 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 options.reset. * * @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);