blob: f0c531f0c4cba0def39851bd751796bdbac90d97 [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
26 /**
27 * Upgrade this object to another object,
28 * while private data stays intact.
29 *
30 * @param {Object] An object with properties.
31 */
32 upgradeTo : function (props) {
33 for (var prop in props) {
34 this[prop] = props[prop];
35 };
36 return this;
37 },
38
Nils Diewald7148c6f2015-05-04 15:07:53 +000039
40 /**
41 * Get or set the content of the meun item.
42 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000043 content : function (content) {
44 if (arguments.length === 1)
45 this._content = document.createTextNode(content);
46 return this._content;
47 },
48
Nils Diewald7148c6f2015-05-04 15:07:53 +000049 /**
Nils Diewald7148c6f2015-05-04 15:07:53 +000050 * Get or set the information for action of this item.
51 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000052 action : function (action) {
53 if (arguments.length === 1)
54 this._action = action;
55 return this._action;
56 },
Akron52ed22d2018-07-11 17:05:19 +020057
58
59 /**
60 * Get the lower cased field of the item
61 * (used for analyses).
62 */
63 lcField : function () {
64 return this._lcField;
65 },
66
Nils Diewald0e6992a2015-04-14 20:13:52 +000067
68 /**
69 * Check or set if the item is active
70 *
71 * @param {boolean|null} State of activity
72 */
73 active : function (bool) {
74 var cl = this.element().classList;
75 if (bool === undefined)
76 return cl.contains("active");
77 else if (bool)
78 cl.add("active");
79 else
80 cl.remove("active");
81 },
82
83 /**
84 * Check or set if the item is
85 * at the boundary of the menu
86 * list
87 *
88 * @param {boolean|null} State of activity
89 */
90 noMore : function (bool) {
91 var cl = this.element().classList;
92 if (bool === undefined)
93 return cl.contains("no-more");
94 else if (bool)
95 cl.add("no-more");
96 else
97 cl.remove("no-more");
98 },
99
100 /**
101 * Get the document element of the menu item
102 */
103 element : function () {
104 // already defined
105 if (this._element !== undefined)
106 return this._element;
107
108 // Create list item
109 var li = document.createElement("li");
110
111 // Connect action
112 if (this["onclick"] !== undefined) {
113 li["onclick"] = this.onclick.bind(this);
114 };
115
116 // Append template
117 li.appendChild(this.content());
118
119 return this._element = li;
120 },
121
122 /**
123 * Highlight parts of the item
124 *
125 * @param {string} Prefix string for highlights
126 */
127 highlight : function (prefix) {
Akron6ed13992016-05-23 18:06:05 +0200128
129 // The prefix already matches
130 if (this._prefix === prefix)
131 return;
132
133 // There is a prefix but it doesn't match
134 if (this._prefix !== null) {
135 this.lowlight();
136 }
137
Nils Diewald0e6992a2015-04-14 20:13:52 +0000138 var children = this.element().childNodes;
139 for (var i = children.length -1; i >= 0; i--) {
140 this._highlight(children[i], prefix);
141 };
Akron6ed13992016-05-23 18:06:05 +0200142
143 this._prefix = prefix;
144 },
145
146 /**
147 * Remove highlight of the menu item
148 */
149 lowlight : function () {
150 if (this._prefix === null)
151 return;
152
153 var e = this.element();
154
155 var marks = e.getElementsByTagName("mark");
156 for (var i = marks.length - 1; i >= 0; i--) {
157 // Create text node clone
158 var x = document.createTextNode(
Akronacffc652017-12-18 21:21:25 +0100159 marks[i].firstChild.nodeValue
Akron6ed13992016-05-23 18:06:05 +0200160 );
161
162 // Replace with content
163 marks[i].parentNode.replaceChild(
Akronacffc652017-12-18 21:21:25 +0100164 x,
165 marks[i]
Akron6ed13992016-05-23 18:06:05 +0200166 );
167 };
168
169 // Remove consecutive textnodes
170 e.normalize();
171 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000172 },
173
Akron52ed22d2018-07-11 17:05:19 +0200174
Nils Diewald0e6992a2015-04-14 20:13:52 +0000175 // Highlight a certain substring of the menu item
Akronacffc652017-12-18 21:21:25 +0100176 _highlight : function (elem, prefixString) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000177 if (elem.nodeType === 3) {
178
179 var text = elem.nodeValue;
180 var textlc = text.toLowerCase();
Akronacffc652017-12-18 21:21:25 +0100181
182 // Split prefixes
183 if (prefixString) {
Akron359c7e12017-12-19 12:06:55 +0100184
185 // ND:
186 // Doing this in a single line can trigger
187 // a deep-recursion in Firefox 57.01, though I don't know why.
188 prefixString = prefixString.trim();
189 var prefixes = prefixString.split(" ");
Akronacffc652017-12-18 21:21:25 +0100190
191 var prefix;
192 var testPos;
193 var pos = -1;
194 var len = 0;
195
196 // Iterate over all prefixes and get the best one
Akronb50964a2020-10-12 11:44:37 +0200197 // for (var i = 0; i < prefixes.length; i++) {
198 prefixes.forEach(function(i) {
Akronacffc652017-12-18 21:21:25 +0100199
200 // Get first pos of a matching prefix
Akronb50964a2020-10-12 11:44:37 +0200201 testPos = textlc.indexOf(i);
Akronacffc652017-12-18 21:21:25 +0100202 if (testPos < 0)
Akronb50964a2020-10-12 11:44:37 +0200203 return;
Akronacffc652017-12-18 21:21:25 +0100204
205 if (pos === -1 || testPos < pos) {
206 pos = testPos;
Akronb50964a2020-10-12 11:44:37 +0200207 len = i.length;
Akronacffc652017-12-18 21:21:25 +0100208 }
Akronb50964a2020-10-12 11:44:37 +0200209 else if (testPos === pos && i.length > len) {
210 len = i.length;
Akronacffc652017-12-18 21:21:25 +0100211 };
Akronb50964a2020-10-12 11:44:37 +0200212 });
Akronacffc652017-12-18 21:21:25 +0100213
214 // Matches!
215 if (pos >= 0) {
216
217 // First element
218 if (pos > 0) {
219 elem.parentNode.insertBefore(
220 document.createTextNode(text.substr(0, pos)),
221 elem
222 );
223 };
224
225 // Second element
226 var hl = document.createElement("mark");
227 hl.appendChild(
228 document.createTextNode(text.substr(pos, len))
229 );
230 elem.parentNode.insertBefore(hl, elem);
231
232 // Third element
233 var third = text.substr(pos + len);
234
235 if (third.length > 0) {
236 var thirdE = document.createTextNode(third);
237 elem.parentNode.insertBefore(
238 thirdE,
239 elem
240 );
241 this._highlight(thirdE, prefixString);
242 };
243
244 var p = elem.parentNode;
245 p.removeChild(elem);
246 };
247 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000248 }
249 else {
250 var children = elem.childNodes;
251 for (var i = children.length -1; i >= 0; i--) {
Akronacffc652017-12-18 21:21:25 +0100252 this._highlight(children[i], prefixString);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000253 };
254 };
255 },
256
Nils Diewald0e6992a2015-04-14 20:13:52 +0000257 // Initialize menu item
258 _init : function (params) {
259
Akronc82513b2016-06-11 11:22:36 +0200260 if (params[0] === undefined) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000261 throw new Error("Missing parameters");
Akronc82513b2016-06-11 11:22:36 +0200262 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000263
264 this.content(params[0]);
265
Akron52ed22d2018-07-11 17:05:19 +0200266 if (params.length > 1) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000267 this._action = params[1];
268
Akron52ed22d2018-07-11 17:05:19 +0200269 if (params.length > 2)
270 this._onclick = params[2];
271 };
272
Nils Diewald0e6992a2015-04-14 20:13:52 +0000273 this._lcField = ' ' + this.content().textContent.toLowerCase();
Akron7524be12016-06-01 17:31:33 +0200274 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000275
276 return this;
277 },
278
Akron52ed22d2018-07-11 17:05:19 +0200279
280 /**
281 * The click action of the menu item.
282 */
283 onclick : function (e) {
284 var m = this.menu();
285
286 // Reset prefix
287 m.prefix("");
288
289 if (this._onclick)
290 this._onclick.apply(this, e);
291
292 m.hide();
293 },
294
295
Nils Diewald0e6992a2015-04-14 20:13:52 +0000296 /**
297 * Return menu list.
298 */
299 menu : function () {
300 return this._menu;
301 }
302});