blob: 873d81b3726321e2bfceace99b4a80dc44f78d5b [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
8/**
9 * Item in the Dropdown menu
10 */
11define({
12 /**
13 * Create a new MenuItem object.
14 *
15 * @constructor
16 * @this {MenuItem}
17 * @param {Array.<string>} An array object of name, action and
18 * optionally a description
19 */
20 create : function (params) {
21 return Object.create(this)._init(params);
22 },
23
24 /**
25 * Upgrade this object to another object,
26 * while private data stays intact.
27 *
28 * @param {Object] An object with properties.
29 */
30 upgradeTo : function (props) {
31 for (var prop in props) {
32 this[prop] = props[prop];
33 };
34 return this;
35 },
36
Nils Diewald7148c6f2015-05-04 15:07:53 +000037
38 /**
39 * Get or set the content of the meun item.
40 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000041 content : function (content) {
42 if (arguments.length === 1)
43 this._content = document.createTextNode(content);
44 return this._content;
45 },
46
Nils Diewald7148c6f2015-05-04 15:07:53 +000047 /**
48 * Get the lower cased field of the item
49 * (used for analyses).
50 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000051 lcField : function () {
52 return this._lcField;
53 },
54
Nils Diewald7148c6f2015-05-04 15:07:53 +000055
56 /**
57 * Get or set the information for action of this item.
58 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000059 action : function (action) {
60 if (arguments.length === 1)
61 this._action = action;
62 return this._action;
63 },
64
65 /**
66 * Check or set if the item is active
67 *
68 * @param {boolean|null} State of activity
69 */
70 active : function (bool) {
71 var cl = this.element().classList;
72 if (bool === undefined)
73 return cl.contains("active");
74 else if (bool)
75 cl.add("active");
76 else
77 cl.remove("active");
78 },
79
80 /**
81 * Check or set if the item is
82 * at the boundary of the menu
83 * list
84 *
85 * @param {boolean|null} State of activity
86 */
87 noMore : function (bool) {
88 var cl = this.element().classList;
89 if (bool === undefined)
90 return cl.contains("no-more");
91 else if (bool)
92 cl.add("no-more");
93 else
94 cl.remove("no-more");
95 },
96
97 /**
98 * Get the document element of the menu item
99 */
100 element : function () {
101 // already defined
102 if (this._element !== undefined)
103 return this._element;
104
105 // Create list item
106 var li = document.createElement("li");
107
108 // Connect action
109 if (this["onclick"] !== undefined) {
110 li["onclick"] = this.onclick.bind(this);
111 };
112
113 // Append template
114 li.appendChild(this.content());
115
116 return this._element = li;
117 },
118
119 /**
120 * Highlight parts of the item
121 *
122 * @param {string} Prefix string for highlights
123 */
124 highlight : function (prefix) {
Akron6ed13992016-05-23 18:06:05 +0200125
126 // The prefix already matches
127 if (this._prefix === prefix)
128 return;
129
130 // There is a prefix but it doesn't match
131 if (this._prefix !== null) {
132 this.lowlight();
133 }
134
Nils Diewald0e6992a2015-04-14 20:13:52 +0000135 var children = this.element().childNodes;
136 for (var i = children.length -1; i >= 0; i--) {
137 this._highlight(children[i], prefix);
138 };
Akron6ed13992016-05-23 18:06:05 +0200139
140 this._prefix = prefix;
141 },
142
143 /**
144 * Remove highlight of the menu item
145 */
146 lowlight : function () {
147 if (this._prefix === null)
148 return;
149
150 var e = this.element();
151
152 var marks = e.getElementsByTagName("mark");
153 for (var i = marks.length - 1; i >= 0; i--) {
154 // Create text node clone
155 var x = document.createTextNode(
156 marks[i].firstChild.nodeValue
157 );
158
159 // Replace with content
160 marks[i].parentNode.replaceChild(
161 x,
162 marks[i]
163 );
164 };
165
166 // Remove consecutive textnodes
167 e.normalize();
168 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000169 },
170
171 // Highlight a certain substring of the menu item
172 _highlight : function (elem, prefix) {
Nils Diewald0e6992a2015-04-14 20:13:52 +0000173 if (elem.nodeType === 3) {
174
175 var text = elem.nodeValue;
176 var textlc = text.toLowerCase();
177 var pos = textlc.indexOf(prefix);
178 if (pos >= 0) {
179
180 // First element
181 if (pos > 0) {
182 elem.parentNode.insertBefore(
183 document.createTextNode(text.substr(0, pos)),
184 elem
185 );
186 };
187
188 // Second element
189 var hl = document.createElement("mark");
190 hl.appendChild(
191 document.createTextNode(text.substr(pos, prefix.length))
192 );
193 elem.parentNode.insertBefore(hl, elem);
194
195 // Third element
196 var third = text.substr(pos + prefix.length);
197 if (third.length > 0) {
198 var thirdE = document.createTextNode(third);
199 elem.parentNode.insertBefore(
200 thirdE,
201 elem
202 );
203 this._highlight(thirdE, prefix);
204 };
205
206 var p = elem.parentNode;
207 p.removeChild(elem);
208 };
209 }
210 else {
211 var children = elem.childNodes;
212 for (var i = children.length -1; i >= 0; i--) {
213 this._highlight(children[i], prefix);
214 };
215 };
216 },
217
Nils Diewald0e6992a2015-04-14 20:13:52 +0000218 // Initialize menu item
219 _init : function (params) {
220
221 if (params[0] === undefined)
222 throw new Error("Missing parameters");
223
224 this.content(params[0]);
225
226 if (params.length === 2)
227 this._action = params[1];
228
229 this._lcField = ' ' + this.content().textContent.toLowerCase();
Akron7524be12016-06-01 17:31:33 +0200230 this._prefix = null;
Nils Diewald0e6992a2015-04-14 20:13:52 +0000231
232 return this;
233 },
234
235 /**
236 * Return menu list.
237 */
238 menu : function () {
239 return this._menu;
240 }
241});