blob: ea46287864804bdb6eb77ed056eda5961a0782b5 [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 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +000028 * Get or set the content of the meun item.
29 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000030 content : function (content) {
31 if (arguments.length === 1)
32 this._content = document.createTextNode(content);
33 return this._content;
34 },
35
Akronc53cfc82020-10-19 11:00:58 +020036
Nils Diewald7148c6f2015-05-04 15:07:53 +000037 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +000038 * Get or set the information for action of this item.
39 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000040 action : function (action) {
41 if (arguments.length === 1)
42 this._action = action;
43 return this._action;
44 },
Akron52ed22d2018-07-11 17:05:19 +020045
46
47 /**
48 * Get the lower cased field of the item
49 * (used for analyses).
50 */
51 lcField : function () {
52 return this._lcField;
53 },
54
Nils Diewald0e6992a2015-04-14 20:13:52 +000055
56 /**
57 * Check or set if the item is active
58 *
59 * @param {boolean|null} State of activity
60 */
61 active : function (bool) {
Akronc53cfc82020-10-19 11:00:58 +020062 const cl = this.element().classList;
Nils Diewald0e6992a2015-04-14 20:13:52 +000063 if (bool === undefined)
64 return cl.contains("active");
65 else if (bool)
66 cl.add("active");
67 else
68 cl.remove("active");
69 },
70
Akronc53cfc82020-10-19 11:00:58 +020071
Nils Diewald0e6992a2015-04-14 20:13:52 +000072 /**
73 * Check or set if the item is
74 * at the boundary of the menu
75 * list
76 *
77 * @param {boolean|null} State of activity
78 */
79 noMore : function (bool) {
Akronc53cfc82020-10-19 11:00:58 +020080 const cl = this.element().classList;
Nils Diewald0e6992a2015-04-14 20:13:52 +000081 if (bool === undefined)
82 return cl.contains("no-more");
83 else if (bool)
84 cl.add("no-more");
85 else
86 cl.remove("no-more");
87 },
88
89 /**
90 * Get the document element of the menu item
91 */
92 element : function () {
93 // already defined
Akron24aa0052020-11-10 11:00:34 +010094 if (this._el !== undefined)
95 return this._el;
Nils Diewald0e6992a2015-04-14 20:13:52 +000096
97 // Create list item
Akronc53cfc82020-10-19 11:00:58 +020098 const li = document.createElement("li");
Nils Diewald0e6992a2015-04-14 20:13:52 +000099
100 // Connect action
101 if (this["onclick"] !== undefined) {
102 li["onclick"] = this.onclick.bind(this);
103 };
104
105 // Append template
106 li.appendChild(this.content());
107
Akron24aa0052020-11-10 11:00:34 +0100108 return this._el = li;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000109 },
110
111 /**
112 * Highlight parts of the item
113 *
114 * @param {string} Prefix string for highlights
115 */
116 highlight : function (prefix) {
Akron6ed13992016-05-23 18:06:05 +0200117
118 // The prefix already matches
119 if (this._prefix === prefix)
120 return;
121
122 // There is a prefix but it doesn't match
123 if (this._prefix !== null) {
124 this.lowlight();
125 }
126
Akronc53cfc82020-10-19 11:00:58 +0200127 const children = this.element().childNodes;
128 for (let i = children.length -1; i >= 0; i--) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000129 this._highlight(children[i], prefix);
130 };
Akron6ed13992016-05-23 18:06:05 +0200131
132 this._prefix = prefix;
133 },
134
Akronc53cfc82020-10-19 11:00:58 +0200135
Akron6ed13992016-05-23 18:06:05 +0200136 /**
137 * Remove highlight of the menu item
138 */
139 lowlight : function () {
140 if (this._prefix === null)
141 return;
142
Akronc53cfc82020-10-19 11:00:58 +0200143 const e = this.element();
Akron6ed13992016-05-23 18:06:05 +0200144
Akronc53cfc82020-10-19 11:00:58 +0200145 const marks = e.getElementsByTagName("mark");
146
147 for (let i = marks.length - 1; i >= 0; i--) {
148
Akron6ed13992016-05-23 18:06:05 +0200149 // Replace with content
150 marks[i].parentNode.replaceChild(
Akronc53cfc82020-10-19 11:00:58 +0200151 // Create text node clone
152 document.createTextNode(
153 marks[i].firstChild.nodeValue
154 ),
Akronacffc652017-12-18 21:21:25 +0100155 marks[i]
Akron6ed13992016-05-23 18:06:05 +0200156 );
157 };
158
159 // Remove consecutive textnodes
160 e.normalize();
161 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000162 },
163
Akron52ed22d2018-07-11 17:05:19 +0200164
Nils Diewald0e6992a2015-04-14 20:13:52 +0000165 // Highlight a certain substring of the menu item
Akronacffc652017-12-18 21:21:25 +0100166 _highlight : function (elem, prefixString) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000167 if (elem.nodeType === 3) {
168
Akronc53cfc82020-10-19 11:00:58 +0200169 const text = elem.nodeValue;
170 const textlc = text.toLowerCase();
Akronacffc652017-12-18 21:21:25 +0100171
172 // Split prefixes
173 if (prefixString) {
Akron359c7e12017-12-19 12:06:55 +0100174
175 // ND:
176 // Doing this in a single line can trigger
177 // a deep-recursion in Firefox 57.01, though I don't know why.
178 prefixString = prefixString.trim();
Akronacffc652017-12-18 21:21:25 +0100179
Akronc53cfc82020-10-19 11:00:58 +0200180 const prefixes = prefixString.split(" ");
181
182 let testPos,
183 pos = -1,
184 len = 0;
Akronacffc652017-12-18 21:21:25 +0100185
186 // Iterate over all prefixes and get the best one
Akronb50964a2020-10-12 11:44:37 +0200187 // for (var i = 0; i < prefixes.length; i++) {
188 prefixes.forEach(function(i) {
Akronacffc652017-12-18 21:21:25 +0100189
190 // Get first pos of a matching prefix
Akronb50964a2020-10-12 11:44:37 +0200191 testPos = textlc.indexOf(i);
Akronacffc652017-12-18 21:21:25 +0100192 if (testPos < 0)
Akronb50964a2020-10-12 11:44:37 +0200193 return;
Akronacffc652017-12-18 21:21:25 +0100194
195 if (pos === -1 || testPos < pos) {
196 pos = testPos;
Akronb50964a2020-10-12 11:44:37 +0200197 len = i.length;
Akronacffc652017-12-18 21:21:25 +0100198 }
Akronb50964a2020-10-12 11:44:37 +0200199 else if (testPos === pos && i.length > len) {
200 len = i.length;
Akronacffc652017-12-18 21:21:25 +0100201 };
Akronb50964a2020-10-12 11:44:37 +0200202 });
Akronacffc652017-12-18 21:21:25 +0100203
204 // Matches!
205 if (pos >= 0) {
206
207 // First element
208 if (pos > 0) {
209 elem.parentNode.insertBefore(
210 document.createTextNode(text.substr(0, pos)),
211 elem
212 );
213 };
214
215 // Second element
Akronc53cfc82020-10-19 11:00:58 +0200216 const hl = document.createElement("mark");
Akronacffc652017-12-18 21:21:25 +0100217 hl.appendChild(
218 document.createTextNode(text.substr(pos, len))
219 );
220 elem.parentNode.insertBefore(hl, elem);
221
222 // Third element
Akronc53cfc82020-10-19 11:00:58 +0200223 const third = text.substr(pos + len);
Akronacffc652017-12-18 21:21:25 +0100224
225 if (third.length > 0) {
Akronc53cfc82020-10-19 11:00:58 +0200226 const thirdE = document.createTextNode(third);
Akronacffc652017-12-18 21:21:25 +0100227 elem.parentNode.insertBefore(
228 thirdE,
229 elem
230 );
231 this._highlight(thirdE, prefixString);
232 };
233
Akronc53cfc82020-10-19 11:00:58 +0200234 elem.parentNode.removeChild(elem);
Akronacffc652017-12-18 21:21:25 +0100235 };
236 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000237 }
Akronc53cfc82020-10-19 11:00:58 +0200238
Nils Diewald0e6992a2015-04-14 20:13:52 +0000239 else {
Akronc53cfc82020-10-19 11:00:58 +0200240 const children = elem.childNodes;
241 for (let i = children.length -1; i >= 0; i--) {
Akronacffc652017-12-18 21:21:25 +0100242 this._highlight(children[i], prefixString);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000243 };
244 };
245 },
246
Nils Diewald0e6992a2015-04-14 20:13:52 +0000247 // Initialize menu item
248 _init : function (params) {
249
Akronc82513b2016-06-11 11:22:36 +0200250 if (params[0] === undefined) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000251 throw new Error("Missing parameters");
Akronc82513b2016-06-11 11:22:36 +0200252 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000253
Akronc53cfc82020-10-19 11:00:58 +0200254 const t = this;
255
256 t.content(params[0]);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000257
Akron52ed22d2018-07-11 17:05:19 +0200258 if (params.length > 1) {
Akronc53cfc82020-10-19 11:00:58 +0200259 t._action = params[1];
Nils Diewald0e6992a2015-04-14 20:13:52 +0000260
Akron52ed22d2018-07-11 17:05:19 +0200261 if (params.length > 2)
Akronc53cfc82020-10-19 11:00:58 +0200262 t._onclick = params[2];
Akron52ed22d2018-07-11 17:05:19 +0200263 };
264
Akronc53cfc82020-10-19 11:00:58 +0200265 t._lcField = ' ' + t.content().textContent.toLowerCase();
266 t._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000267
268 return this;
269 },
270
Akron52ed22d2018-07-11 17:05:19 +0200271
272 /**
273 * The click action of the menu item.
274 */
275 onclick : function (e) {
Akronc53cfc82020-10-19 11:00:58 +0200276 const m = this.menu();
Akron52ed22d2018-07-11 17:05:19 +0200277
278 // Reset prefix
279 m.prefix("");
280
281 if (this._onclick)
282 this._onclick.apply(this, e);
283
284 m.hide();
285 },
286
287
Nils Diewald0e6992a2015-04-14 20:13:52 +0000288 /**
289 * Return menu list.
290 */
291 menu : function () {
292 return this._menu;
293 }
294});