blob: 7ce74556029a62af787ab83b18fbd5bddb30fd45 [file] [log] [blame]
Nils Diewald0e6992a2015-04-14 20:13:52 +00001/*
2 * MenuItems may define:
3 *
4 * onclick: action happen on click and enter.
5 * further: action happen on right arrow
6 */
7
Akronacffc652017-12-18 21:21:25 +01008"use strict";
9
Nils Diewald0e6992a2015-04-14 20:13:52 +000010/**
11 * Item in the Dropdown menu
12 */
13define({
14 /**
15 * Create a new MenuItem object.
16 *
17 * @constructor
18 * @this {MenuItem}
19 * @param {Array.<string>} An array object of name, action and
20 * optionally a description
21 */
22 create : function (params) {
23 return Object.create(this)._init(params);
24 },
25
Akronc53cfc82020-10-19 11:00:58 +020026
Nils Diewald0e6992a2015-04-14 20:13:52 +000027 /**
28 * Upgrade this object to another object,
29 * while private data stays intact.
30 *
31 * @param {Object] An object with properties.
32 */
33 upgradeTo : function (props) {
Akronc53cfc82020-10-19 11:00:58 +020034 for (let prop in props) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000035 this[prop] = props[prop];
36 };
37 return this;
38 },
39
Nils Diewald7148c6f2015-05-04 15:07:53 +000040
41 /**
42 * Get or set the content of the meun item.
43 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000044 content : function (content) {
45 if (arguments.length === 1)
46 this._content = document.createTextNode(content);
47 return this._content;
48 },
49
Akronc53cfc82020-10-19 11:00:58 +020050
Nils Diewald7148c6f2015-05-04 15:07:53 +000051 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +000052 * Get or set the information for action of this item.
53 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000054 action : function (action) {
55 if (arguments.length === 1)
56 this._action = action;
57 return this._action;
58 },
Akron52ed22d2018-07-11 17:05:19 +020059
60
61 /**
62 * Get the lower cased field of the item
63 * (used for analyses).
64 */
65 lcField : function () {
66 return this._lcField;
67 },
68
Nils Diewald0e6992a2015-04-14 20:13:52 +000069
70 /**
71 * Check or set if the item is active
72 *
73 * @param {boolean|null} State of activity
74 */
75 active : function (bool) {
Akronc53cfc82020-10-19 11:00:58 +020076 const cl = this.element().classList;
Nils Diewald0e6992a2015-04-14 20:13:52 +000077 if (bool === undefined)
78 return cl.contains("active");
79 else if (bool)
80 cl.add("active");
81 else
82 cl.remove("active");
83 },
84
Akronc53cfc82020-10-19 11:00:58 +020085
Nils Diewald0e6992a2015-04-14 20:13:52 +000086 /**
87 * Check or set if the item is
88 * at the boundary of the menu
89 * list
90 *
91 * @param {boolean|null} State of activity
92 */
93 noMore : function (bool) {
Akronc53cfc82020-10-19 11:00:58 +020094 const cl = this.element().classList;
Nils Diewald0e6992a2015-04-14 20:13:52 +000095 if (bool === undefined)
96 return cl.contains("no-more");
97 else if (bool)
98 cl.add("no-more");
99 else
100 cl.remove("no-more");
101 },
102
103 /**
104 * Get the document element of the menu item
105 */
106 element : function () {
107 // already defined
108 if (this._element !== undefined)
109 return this._element;
110
111 // Create list item
Akronc53cfc82020-10-19 11:00:58 +0200112 const li = document.createElement("li");
Nils Diewald0e6992a2015-04-14 20:13:52 +0000113
114 // Connect action
115 if (this["onclick"] !== undefined) {
116 li["onclick"] = this.onclick.bind(this);
117 };
118
119 // Append template
120 li.appendChild(this.content());
121
122 return this._element = li;
123 },
124
125 /**
126 * Highlight parts of the item
127 *
128 * @param {string} Prefix string for highlights
129 */
130 highlight : function (prefix) {
Akron6ed13992016-05-23 18:06:05 +0200131
132 // The prefix already matches
133 if (this._prefix === prefix)
134 return;
135
136 // There is a prefix but it doesn't match
137 if (this._prefix !== null) {
138 this.lowlight();
139 }
140
Akronc53cfc82020-10-19 11:00:58 +0200141 const children = this.element().childNodes;
142 for (let i = children.length -1; i >= 0; i--) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000143 this._highlight(children[i], prefix);
144 };
Akron6ed13992016-05-23 18:06:05 +0200145
146 this._prefix = prefix;
147 },
148
Akronc53cfc82020-10-19 11:00:58 +0200149
Akron6ed13992016-05-23 18:06:05 +0200150 /**
151 * Remove highlight of the menu item
152 */
153 lowlight : function () {
154 if (this._prefix === null)
155 return;
156
Akronc53cfc82020-10-19 11:00:58 +0200157 const e = this.element();
Akron6ed13992016-05-23 18:06:05 +0200158
Akronc53cfc82020-10-19 11:00:58 +0200159 const marks = e.getElementsByTagName("mark");
160
161 for (let i = marks.length - 1; i >= 0; i--) {
162
Akron6ed13992016-05-23 18:06:05 +0200163 // Replace with content
164 marks[i].parentNode.replaceChild(
Akronc53cfc82020-10-19 11:00:58 +0200165 // Create text node clone
166 document.createTextNode(
167 marks[i].firstChild.nodeValue
168 ),
Akronacffc652017-12-18 21:21:25 +0100169 marks[i]
Akron6ed13992016-05-23 18:06:05 +0200170 );
171 };
172
173 // Remove consecutive textnodes
174 e.normalize();
175 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000176 },
177
Akron52ed22d2018-07-11 17:05:19 +0200178
Nils Diewald0e6992a2015-04-14 20:13:52 +0000179 // Highlight a certain substring of the menu item
Akronacffc652017-12-18 21:21:25 +0100180 _highlight : function (elem, prefixString) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000181 if (elem.nodeType === 3) {
182
Akronc53cfc82020-10-19 11:00:58 +0200183 const text = elem.nodeValue;
184 const textlc = text.toLowerCase();
Akronacffc652017-12-18 21:21:25 +0100185
186 // Split prefixes
187 if (prefixString) {
Akron359c7e12017-12-19 12:06:55 +0100188
189 // ND:
190 // Doing this in a single line can trigger
191 // a deep-recursion in Firefox 57.01, though I don't know why.
192 prefixString = prefixString.trim();
Akronacffc652017-12-18 21:21:25 +0100193
Akronc53cfc82020-10-19 11:00:58 +0200194 const prefixes = prefixString.split(" ");
195
196 let testPos,
197 pos = -1,
198 len = 0;
Akronacffc652017-12-18 21:21:25 +0100199
200 // Iterate over all prefixes and get the best one
Akronb50964a2020-10-12 11:44:37 +0200201 // for (var i = 0; i < prefixes.length; i++) {
202 prefixes.forEach(function(i) {
Akronacffc652017-12-18 21:21:25 +0100203
204 // Get first pos of a matching prefix
Akronb50964a2020-10-12 11:44:37 +0200205 testPos = textlc.indexOf(i);
Akronacffc652017-12-18 21:21:25 +0100206 if (testPos < 0)
Akronb50964a2020-10-12 11:44:37 +0200207 return;
Akronacffc652017-12-18 21:21:25 +0100208
209 if (pos === -1 || testPos < pos) {
210 pos = testPos;
Akronb50964a2020-10-12 11:44:37 +0200211 len = i.length;
Akronacffc652017-12-18 21:21:25 +0100212 }
Akronb50964a2020-10-12 11:44:37 +0200213 else if (testPos === pos && i.length > len) {
214 len = i.length;
Akronacffc652017-12-18 21:21:25 +0100215 };
Akronb50964a2020-10-12 11:44:37 +0200216 });
Akronacffc652017-12-18 21:21:25 +0100217
218 // Matches!
219 if (pos >= 0) {
220
221 // First element
222 if (pos > 0) {
223 elem.parentNode.insertBefore(
224 document.createTextNode(text.substr(0, pos)),
225 elem
226 );
227 };
228
229 // Second element
Akronc53cfc82020-10-19 11:00:58 +0200230 const hl = document.createElement("mark");
Akronacffc652017-12-18 21:21:25 +0100231 hl.appendChild(
232 document.createTextNode(text.substr(pos, len))
233 );
234 elem.parentNode.insertBefore(hl, elem);
235
236 // Third element
Akronc53cfc82020-10-19 11:00:58 +0200237 const third = text.substr(pos + len);
Akronacffc652017-12-18 21:21:25 +0100238
239 if (third.length > 0) {
Akronc53cfc82020-10-19 11:00:58 +0200240 const thirdE = document.createTextNode(third);
Akronacffc652017-12-18 21:21:25 +0100241 elem.parentNode.insertBefore(
242 thirdE,
243 elem
244 );
245 this._highlight(thirdE, prefixString);
246 };
247
Akronc53cfc82020-10-19 11:00:58 +0200248 elem.parentNode.removeChild(elem);
Akronacffc652017-12-18 21:21:25 +0100249 };
250 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000251 }
Akronc53cfc82020-10-19 11:00:58 +0200252
Nils Diewald0e6992a2015-04-14 20:13:52 +0000253 else {
Akronc53cfc82020-10-19 11:00:58 +0200254 const children = elem.childNodes;
255 for (let i = children.length -1; i >= 0; i--) {
Akronacffc652017-12-18 21:21:25 +0100256 this._highlight(children[i], prefixString);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000257 };
258 };
259 },
260
Nils Diewald0e6992a2015-04-14 20:13:52 +0000261 // Initialize menu item
262 _init : function (params) {
263
Akronc82513b2016-06-11 11:22:36 +0200264 if (params[0] === undefined) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000265 throw new Error("Missing parameters");
Akronc82513b2016-06-11 11:22:36 +0200266 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000267
Akronc53cfc82020-10-19 11:00:58 +0200268 const t = this;
269
270 t.content(params[0]);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000271
Akron52ed22d2018-07-11 17:05:19 +0200272 if (params.length > 1) {
Akronc53cfc82020-10-19 11:00:58 +0200273 t._action = params[1];
Nils Diewald0e6992a2015-04-14 20:13:52 +0000274
Akron52ed22d2018-07-11 17:05:19 +0200275 if (params.length > 2)
Akronc53cfc82020-10-19 11:00:58 +0200276 t._onclick = params[2];
Akron52ed22d2018-07-11 17:05:19 +0200277 };
278
Akronc53cfc82020-10-19 11:00:58 +0200279 t._lcField = ' ' + t.content().textContent.toLowerCase();
280 t._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000281
282 return this;
283 },
284
Akron52ed22d2018-07-11 17:05:19 +0200285
286 /**
287 * The click action of the menu item.
288 */
289 onclick : function (e) {
Akronc53cfc82020-10-19 11:00:58 +0200290 const m = this.menu();
Akron52ed22d2018-07-11 17:05:19 +0200291
292 // Reset prefix
293 m.prefix("");
294
295 if (this._onclick)
296 this._onclick.apply(this, e);
297
298 m.hide();
299 },
300
301
Nils Diewald0e6992a2015-04-14 20:13:52 +0000302 /**
303 * Return menu list.
304 */
305 menu : function () {
306 return this._menu;
307 }
308});