2017-01-30 20:32:47 +01:00
|
|
|
/* USAGE:
|
|
|
|
Toggle x = components.toggle({
|
|
|
|
selected: Integer selected_index,
|
|
|
|
values: Array<Object> [
|
2017-06-23 21:56:08 +02:00
|
|
|
{ label: i18n.t(String title) }
|
2017-01-30 20:32:47 +01:00
|
|
|
],
|
|
|
|
callback: function () {
|
|
|
|
// .. on value change.
|
|
|
|
},
|
|
|
|
}).get();
|
|
|
|
*/
|
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
exports.toggle = function (opts) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const component = (function render_component(opts) {
|
|
|
|
const _component = $("<div class='tab-switcher'></div>");
|
2018-04-14 16:28:58 +02:00
|
|
|
opts.values.forEach(function (value, i) {
|
|
|
|
// create a tab with a tab-id so they don't have to be referenced
|
|
|
|
// by text value which can be inconsistent.
|
2019-11-02 00:06:25 +01:00
|
|
|
const tab = $("<div class='ind-tab' data-tab-key='" + value.key + "' data-tab-id='" + i + "' tabindex='0'>" + value.label + "</div>");
|
2018-04-14 16:28:58 +02:00
|
|
|
|
|
|
|
// add proper classes for styling in CSS.
|
|
|
|
if (i === 0) {
|
|
|
|
// this should be default selected unless otherwise specified.
|
|
|
|
tab.addClass("first selected");
|
|
|
|
} else if (i === opts.values.length - 1) {
|
|
|
|
tab.addClass("last");
|
|
|
|
} else {
|
|
|
|
tab.addClass("middle");
|
2018-03-29 20:48:49 +02:00
|
|
|
}
|
2018-04-14 16:28:58 +02:00
|
|
|
_component.append(tab);
|
|
|
|
});
|
|
|
|
return _component;
|
|
|
|
}(opts));
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const meta = {
|
2018-04-14 16:28:58 +02:00
|
|
|
$ind_tab: component.find(".ind-tab"),
|
|
|
|
idx: -1,
|
|
|
|
};
|
2018-03-29 20:48:49 +02:00
|
|
|
|
2018-06-04 00:10:36 +02:00
|
|
|
function select_tab(idx) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = meta.$ind_tab.eq(idx);
|
2019-05-08 09:26:27 +02:00
|
|
|
if (elem.hasClass('disabled')) {
|
|
|
|
return;
|
|
|
|
}
|
2018-04-14 16:28:58 +02:00
|
|
|
meta.$ind_tab.removeClass("selected");
|
2018-03-29 20:48:49 +02:00
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
elem.addClass("selected");
|
|
|
|
|
toggler: Always call back to callback function.
In our toggler component (the thing that handles tabs in things
like our markdown/search help, settings/org, etc.), we have
a callback mechanism when you switch to the tab. We were
being tricky and only calling it when the tab changed.
It turns out it's better to just always call the callback,
since these things are often in modals that open and close,
and if you open a modal for the second time, you want to do
the callback task for whichever setting you're going to.
There was actually kind of a nasty bug with this, where the
keyboard handling in the keyboard-help modal worked fine the
first time you opened it, but then it didn't work the second
time (if you focused some other element in the interim), and
it was due to not re-setting the focus to the inner modal
because we weren't calling the callback.
Of course, there are pitfalls in calling the same callbacks
twice, but our callbacks should generally be idempotent
for other reasons.
2018-06-03 22:12:10 +02:00
|
|
|
meta.idx = idx;
|
|
|
|
if (opts.callback) {
|
|
|
|
opts.callback(
|
|
|
|
opts.values[idx].label,
|
2018-06-04 00:10:36 +02:00
|
|
|
opts.values[idx].key
|
toggler: Always call back to callback function.
In our toggler component (the thing that handles tabs in things
like our markdown/search help, settings/org, etc.), we have
a callback mechanism when you switch to the tab. We were
being tricky and only calling it when the tab changed.
It turns out it's better to just always call the callback,
since these things are often in modals that open and close,
and if you open a modal for the second time, you want to do
the callback task for whichever setting you're going to.
There was actually kind of a nasty bug with this, where the
keyboard handling in the keyboard-help modal worked fine the
first time you opened it, but then it didn't work the second
time (if you focused some other element in the interim), and
it was due to not re-setting the focus to the inner modal
because we weren't calling the callback.
Of course, there are pitfalls in calling the same callbacks
twice, but our callbacks should generally be idempotent
for other reasons.
2018-06-03 22:12:10 +02:00
|
|
|
);
|
2018-03-29 20:48:49 +02:00
|
|
|
}
|
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
if (!opts.child_wants_focus) {
|
|
|
|
elem.focus();
|
2018-03-29 20:48:49 +02:00
|
|
|
}
|
2018-04-14 16:28:58 +02:00
|
|
|
}
|
2018-03-29 20:48:49 +02:00
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
function maybe_go_left() {
|
|
|
|
if (meta.idx > 0) {
|
|
|
|
select_tab(meta.idx - 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2017-10-27 01:35:12 +02:00
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
function maybe_go_right() {
|
|
|
|
if (meta.idx < opts.values.length - 1) {
|
|
|
|
select_tab(meta.idx + 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
meta.$ind_tab.click(function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const idx = $(this).data("tab-id");
|
2018-04-14 16:28:58 +02:00
|
|
|
select_tab(idx);
|
|
|
|
});
|
|
|
|
|
|
|
|
keydown_util.handle({
|
|
|
|
elem: meta.$ind_tab,
|
|
|
|
handlers: {
|
|
|
|
left_arrow: maybe_go_left,
|
|
|
|
right_arrow: maybe_go_right,
|
|
|
|
},
|
|
|
|
});
|
2018-03-29 20:48:49 +02:00
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
// We should arguably default opts.selected to 0.
|
|
|
|
if (typeof opts.selected === "number") {
|
|
|
|
select_tab(opts.selected);
|
|
|
|
}
|
|
|
|
}());
|
2016-11-04 22:34:12 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const prototype = {
|
2018-04-14 16:28:58 +02:00
|
|
|
maybe_go_left: maybe_go_left,
|
|
|
|
maybe_go_right: maybe_go_right,
|
2018-04-04 17:01:34 +02:00
|
|
|
|
2019-05-08 09:26:27 +02:00
|
|
|
disable_tab: function (name) {
|
2020-02-08 05:31:13 +01:00
|
|
|
const value = opts.values.find(o => o.key === name);
|
2019-05-08 09:26:27 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const idx = opts.values.indexOf(value);
|
2019-05-08 09:26:27 +02:00
|
|
|
meta.$ind_tab.eq(idx).addClass('disabled');
|
|
|
|
},
|
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
value: function () {
|
|
|
|
if (meta.idx >= 0) {
|
|
|
|
return opts.values[meta.idx].label;
|
|
|
|
}
|
|
|
|
},
|
2018-03-29 20:48:49 +02:00
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
get: function () {
|
|
|
|
return component;
|
|
|
|
},
|
|
|
|
// go through the process of finding the correct tab for a given name,
|
|
|
|
// and when found, select that one and provide the proper callback.
|
2018-06-04 00:10:36 +02:00
|
|
|
goto: function (name) {
|
2020-02-08 05:31:13 +01:00
|
|
|
const value = opts.values.find(o => o.label === name || o.key === name);
|
2016-11-04 22:34:12 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const idx = opts.values.indexOf(value);
|
2016-11-04 22:34:12 +01:00
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
if (idx >= 0) {
|
2018-06-04 00:10:36 +02:00
|
|
|
select_tab(idx);
|
2018-04-14 16:28:58 +02:00
|
|
|
}
|
|
|
|
},
|
2016-11-04 22:34:12 +01:00
|
|
|
};
|
|
|
|
|
2018-04-14 16:28:58 +02:00
|
|
|
return prototype;
|
|
|
|
};
|
2016-11-04 22:34:12 +01:00
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.components = exports;
|