blob: 835f69f1ec7547b48a6d639f4a79a32af4a421e7 [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 /**
50 * Get the lower cased field of the item
51 * (used for analyses).
52 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000053 lcField : function () {
54 return this._lcField;
55 },
56
Nils Diewald7148c6f2015-05-04 15:07:53 +000057
58 /**
59 * Get or set the information for action of this item.
60 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000061 action : function (action) {
62 if (arguments.length === 1)
63 this._action = action;
64 return this._action;
65 },
66
67 /**
68 * Check or set if the item is active
69 *
70 * @param {boolean|null} State of activity
71 */
72 active : function (bool) {
73 var cl = this.element().classList;
74 if (bool === undefined)
75 return cl.contains("active");
76 else if (bool)
77 cl.add("active");
78 else
79 cl.remove("active");
80 },
81
82 /**
83 * Check or set if the item is
84 * at the boundary of the menu
85 * list
86 *
87 * @param {boolean|null} State of activity
88 */
89 noMore : function (bool) {
90 var cl = this.element().classList;
91 if (bool === undefined)
92 return cl.contains("no-more");
93 else if (bool)
94 cl.add("no-more");
95 else
96 cl.remove("no-more");
97 },
98
99 /**
100 * Get the document element of the menu item
101 */
102 element : function () {
103 // already defined
104 if (this._element !== undefined)
105 return this._element;
106
107 // Create list item
108 var li = document.createElement("li");
109
110 // Connect action
111 if (this["onclick"] !== undefined) {
112 li["onclick"] = this.onclick.bind(this);
113 };
114
115 // Append template
116 li.appendChild(this.content());
117
118 return this._element = li;
119 },
120
121 /**
122 * Highlight parts of the item
123 *
124 * @param {string} Prefix string for highlights
125 */
126 highlight : function (prefix) {
Akron6ed13992016-05-23 18:06:05 +0200127
128 // The prefix already matches
129 if (this._prefix === prefix)
130 return;
131
132 // There is a prefix but it doesn't match
133 if (this._prefix !== null) {
134 this.lowlight();
135 }
136
Nils Diewald0e6992a2015-04-14 20:13:52 +0000137 var children = this.element().childNodes;
138 for (var i = children.length -1; i >= 0; i--) {
139 this._highlight(children[i], prefix);
140 };
Akron6ed13992016-05-23 18:06:05 +0200141
142 this._prefix = prefix;
143 },
144
145 /**
146 * Remove highlight of the menu item
147 */
148 lowlight : function () {
149 if (this._prefix === null)
150 return;
151
152 var e = this.element();
153
154 var marks = e.getElementsByTagName("mark");
155 for (var i = marks.length - 1; i >= 0; i--) {
156 // Create text node clone
157 var x = document.createTextNode(
Akronacffc652017-12-18 21:21:25 +0100158 marks[i].firstChild.nodeValue
Akron6ed13992016-05-23 18:06:05 +0200159 );
160
161 // Replace with content
162 marks[i].parentNode.replaceChild(
Akronacffc652017-12-18 21:21:25 +0100163 x,
164 marks[i]
Akron6ed13992016-05-23 18:06:05 +0200165 );
166 };
167
168 // Remove consecutive textnodes
169 e.normalize();
170 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000171 },
172
173 // Highlight a certain substring of the menu item
Akronacffc652017-12-18 21:21:25 +0100174 _highlight : function (elem, prefixString) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000175 if (elem.nodeType === 3) {
176
177 var text = elem.nodeValue;
178 var textlc = text.toLowerCase();
Akronacffc652017-12-18 21:21:25 +0100179
180 // Split prefixes
181 if (prefixString) {
182 var prefixes = prefixString.trim().split(" ");
183
184 var prefix;
185 var testPos;
186 var pos = -1;
187 var len = 0;
188
189 // Iterate over all prefixes and get the best one
190 for (var i = 0; i < prefixes.length; i++) {
191
192 // Get first pos of a matching prefix
193 testPos = textlc.indexOf(prefixes[i]);
194 if (testPos < 0)
195 continue;
196
197 if (pos === -1 || testPos < pos) {
198 pos = testPos;
199 len = prefixes[i].length;
200 }
201 else if (testPos === pos && prefixes[i].length > len) {
202 len = prefixes[i].length;
203 };
204 };
205
206 // Matches!
207 if (pos >= 0) {
208
209 // First element
210 if (pos > 0) {
211 elem.parentNode.insertBefore(
212 document.createTextNode(text.substr(0, pos)),
213 elem
214 );
215 };
216
217 // Second element
218 var hl = document.createElement("mark");
219 hl.appendChild(
220 document.createTextNode(text.substr(pos, len))
221 );
222 elem.parentNode.insertBefore(hl, elem);
223
224 // Third element
225 var third = text.substr(pos + len);
226
227 if (third.length > 0) {
228 var thirdE = document.createTextNode(third);
229 elem.parentNode.insertBefore(
230 thirdE,
231 elem
232 );
233 this._highlight(thirdE, prefixString);
234 };
235
236 var p = elem.parentNode;
237 p.removeChild(elem);
238 };
239 };
240
241 /*
242 pos = textlc.indexOf(prefix);
243
244 // Matches!
Nils Diewald0e6992a2015-04-14 20:13:52 +0000245 if (pos >= 0) {
246
Akronacffc652017-12-18 21:21:25 +0100247 // First element
248 if (pos > 0) {
249 elem.parentNode.insertBefore(
250 document.createTextNode(text.substr(0, pos)),
251 elem
252 );
253 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000254
Akronacffc652017-12-18 21:21:25 +0100255 // Second element
256 var hl = document.createElement("mark");
257 hl.appendChild(
258 document.createTextNode(text.substr(pos, prefix.length))
259 );
260 elem.parentNode.insertBefore(hl, elem);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000261
Akronacffc652017-12-18 21:21:25 +0100262 // Third element
263 var third = text.substr(pos + prefix.length);
264 if (third.length > 0) {
265 var thirdE = document.createTextNode(third);
266 elem.parentNode.insertBefore(
267 thirdE,
268 elem
269 );
270 this._highlight(thirdE, prefix);
271 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000272
Akronacffc652017-12-18 21:21:25 +0100273 var p = elem.parentNode;
274 p.removeChild(elem);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000275 };
Akronacffc652017-12-18 21:21:25 +0100276 */
Nils Diewald0e6992a2015-04-14 20:13:52 +0000277 }
278 else {
279 var children = elem.childNodes;
280 for (var i = children.length -1; i >= 0; i--) {
Akronacffc652017-12-18 21:21:25 +0100281 this._highlight(children[i], prefixString);
Nils Diewald0e6992a2015-04-14 20:13:52 +0000282 };
283 };
284 },
285
Nils Diewald0e6992a2015-04-14 20:13:52 +0000286 // Initialize menu item
287 _init : function (params) {
288
Akronc82513b2016-06-11 11:22:36 +0200289 if (params[0] === undefined) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000290 throw new Error("Missing parameters");
Akronc82513b2016-06-11 11:22:36 +0200291 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000292
293 this.content(params[0]);
294
295 if (params.length === 2)
296 this._action = params[1];
297
298 this._lcField = ' ' + this.content().textContent.toLowerCase();
Akron7524be12016-06-01 17:31:33 +0200299 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000300
301 return this;
302 },
303
304 /**
305 * Return menu list.
306 */
307 menu : function () {
308 return this._menu;
309 }
310});