blob: 4d48c78e5da3258c8e402a2d3c8c227f22c3a5c6 [file] [log] [blame]
Leo Reppd162b2e2021-06-30 13:51:07 +02001/**
2 * Container for several containerItem style items. Functions like a mini menu with next, prev, add etc propagation,
3 * but no event handling or slider or lengthField. Supposed to be subelement to a (container)menu class item.
4 *
5 * @author Leo Repp
6 */
7
8"use strict";
9define([
Leo Repp57997402021-08-18 16:37:52 +020010 'container/containeritem'
Leo Reppd162b2e2021-06-30 13:51:07 +020011], function (
12 defaultContainerItemClass
13) {
14
15 return {
16 /**
Leo Reppc66268e2021-10-28 11:44:35 +020017 *
18 * @param {Array<object>} listOfContainerItems List of items that will be placed within the container and that realise some of the functions supplied in containeritem.js
19 * @param {object} params May contain attribute containerItemClass for a base class all containerItems build upon
20 * @returns The container object
21 */
Leo Reppd162b2e2021-06-30 13:51:07 +020022 create : function (listOfContainerItems, params) {
23 var obj = Object.create(this);
24 obj._init(listOfContainerItems, params);
25 return obj;
26 },
27
28 _init : function (listOfContainerItems, params){
29 if (params !== undefined && params["containerItemClass"] !== undefined){
30 this._containerItemClass = params["containerItemClass"];
31 } else {
32 this._containerItemClass = defaultContainerItemClass;
33 };
Leo Repp84539162021-10-25 12:06:07 +020034 var el = document.createElement("ul");
35 el.style.outline = 0;
36 el.setAttribute('tabindex', 0);
37 el.classList.add('menu', 'container'); //container class allows for more stylesheet changes
38
39 this._el = el;
40 this._cItemPrefix = undefined; //required for re-setting the menus pointer correctly
41 // after having upgraded a new item scss style to the prefix object.
Leo Reppd162b2e2021-06-30 13:51:07 +020042
43 this.items = new Array();
Leo Repp050a7342021-10-25 11:05:32 +020044 //items are stored in the order they are added in. This includes the prefix.
Leo Reppd162b2e2021-06-30 13:51:07 +020045 if (listOfContainerItems !== undefined) {
46 for (let item of listOfContainerItems) {
47 this.addItem(item);
48 }
49 }
50
Leo Repp050a7342021-10-25 11:05:32 +020051 this.position = undefined; //undefined = not in container,
52 // 0 to length-1 = in container
53
54 this._prefixPosition = undefined; //Required so that switching
55 // to prefix by default is supported
56
Leo Repp57997402021-08-18 16:37:52 +020057 this._menu = undefined // add later
58
Leo Reppd162b2e2021-06-30 13:51:07 +020059
60
61 //t._el.classList.add('visible'); //Done by containermenu
Leo Repp57997402021-08-18 16:37:52 +020062 },
Leo Reppd162b2e2021-06-30 13:51:07 +020063
Leo Repp84539162021-10-25 12:06:07 +020064
Leo Repp57997402021-08-18 16:37:52 +020065 /**
Leo Repp84539162021-10-25 12:06:07 +020066 * Adds a static item to this container by creating a standard containerItem as specified when this container was created,
Leo Repp5bb710b2021-12-01 13:35:13 +010067 * then upgrading it to the item passed to this function, and calling element() and content(). The item is always added to the
68 * bottom of the list. It gets slightly more complicated if you want to add it in between. For a full list of supported functions see
Leo Repp84539162021-10-25 12:06:07 +020069 * containeritem.js .
70 * Example:
71 *
72 * menu.container().addItem(
73 * {defaultTextValue : "dynamic", onClick : function (e) { ... }
74 * )
75 *
76 * For a full demo see containermenudemo.js.
77 *
78 * @param {Object} item An object with any number of functions like in containeritem.js or an attribute defaultTextValue,
79 * as well as any number of own properties.
80 * @returns the new use-ready containerItem
81 */
Leo Reppd162b2e2021-06-30 13:51:07 +020082 addItem : function (item) {
Leo Repp57997402021-08-18 16:37:52 +020083 //Call Order: First _containerItemClass is created and then upgraded To whatever object is passed to this function
84 //Then container calls first element() and then container()
Leo Reppd162b2e2021-06-30 13:51:07 +020085 var cItem = this._containerItemClass.create().upgradeTo(item);
86 cItem._menu = this._menu; //if not set then undefined, but thats OK
87 this.items.push(cItem);
Leo Repp84539162021-10-25 12:06:07 +020088 this.element().appendChild(cItem.element());
89 cItem.initContent(); // create its textNode
Leo Repp57997402021-08-18 16:37:52 +020090 if (this._cItemPrefix !== undefined){ //this must be dynamic adding of CIs, move prefix to the back
Leo Repp5bb710b2021-12-01 13:35:13 +010091
92 //If an item gets added and the prefix was active, we need to adjust this.position, otherwise
93 //the new item is now active and not the prefix
94 if (this.item() === this._cItemPrefix) {
95 this.position++; //Do not use menu.next(), as ONLY adjustments of this.position are necessary. isSelectable has already been taken care of,
96 //otherwise we would not have this item be active
97 }//^^This must happen before we change the "items" list to not contain prefix at the active position, i.e. before removing prefix, but
98 //it can happen after adding the new item as it gets added to the back
99
Leo Repp84539162021-10-25 12:06:07 +0200100 //adjust the prefix' position within .items to be in the back
Leo Repp57997402021-08-18 16:37:52 +0200101 this.items.splice(this.items.indexOf(this._cItemPrefix) , 1); //remove cItemPrefix
102 this.items.push(this._cItemPrefix); //and move it to the end;
Leo Repp84539162021-10-25 12:06:07 +0200103 //adjust the prefix' HTML elements position
104 this.element().removeChild(this._cItemPrefix.element());
105 this.element().appendChild(this._cItemPrefix.element());
106 //adjust the prefix' stored position
Leo Repp5bb710b2021-12-01 13:35:13 +0100107 this._prefixPosition = this.items.length-1; // otherwise after list due to 0 index
Leo Repp57997402021-08-18 16:37:52 +0200108 };
Leo Repp5bb710b2021-12-01 13:35:13 +0100109 //If an item was active and we add another we do not need to adjust this.position, because
110 //the new item ALWAYS gets added to the bottom of the list
Leo Reppd162b2e2021-06-30 13:51:07 +0200111 return cItem;
112 },
113
114 addMenu : function (menu) {
115 this._menu = menu;
Leo Repp57997402021-08-18 16:37:52 +0200116 if (this._cItemPrefix !== undefined) {
117 this._menu._prefix = this._cItemPrefix; // better than going via classList or something
Leo Reppd162b2e2021-06-30 13:51:07 +0200118 };
119 for (let item of this.items) {
120 item._menu=menu;
Leo Repp84539162021-10-25 12:06:07 +0200121 };
Leo Reppd162b2e2021-06-30 13:51:07 +0200122 },
123
124 addPrefix : function (prefix) {
125 prefix.isSelectable = function () {
126 return this.isSet(); //TODO check!
Leo Repp84539162021-10-25 12:06:07 +0200127 };
Leo Repp5bb710b2021-12-01 13:35:13 +0100128 this._prefixPosition = this.items.length; // not -1 because prefix will still get added
Leo Repp84539162021-10-25 12:06:07 +0200129 prefix.initContent = function () {}; //Does not need a textNode Child!
Leo Reppd162b2e2021-06-30 13:51:07 +0200130 var prefItem = this.addItem(prefix);
Leo Repp57997402021-08-18 16:37:52 +0200131 this._cItemPrefix = prefItem;
132 prefItem._el["onclick"] = prefItem.onclick.bind(prefItem);
Leo Reppd162b2e2021-06-30 13:51:07 +0200133 if (this._menu !== undefined){
134 this._menu._prefix=prefItem;
Leo Repp84539162021-10-25 12:06:07 +0200135 };
Leo Reppd162b2e2021-06-30 13:51:07 +0200136 },
Leo Reppc66268e2021-10-28 11:44:35 +0200137
Leo Repp57997402021-08-18 16:37:52 +0200138 //Taken from Branch 5133
Leo Reppc66268e2021-10-28 11:44:35 +0200139 /**
140 * Remove a containeritem from the container by identity. Should not be used with prefix.
141 * If the active item is removed, this calls menu.next().
142 * @param {containerItemClass} item The item to be removed.
143 */
144 removeItem : function (item) {
145 if (this.items.indexOf(item) === -1){ // This is returned if indexOf cannot find the item.
Leo Repp5bb710b2021-12-01 13:35:13 +0100146 KorAP.log(0,"Invalid item in containers removeItem: This containerItem is not contained", "container.js");
Leo Reppc66268e2021-10-28 11:44:35 +0200147 return;
148 };
Leo Repp57997402021-08-18 16:37:52 +0200149 if (item === this._cItemPrefix) {
150 KorAP.log(0,"Tried to remove the prefix item. Illegal.");
151 console.log("Tried to remove the prefix item by calling removeItem. Please cut all connections from the menu to prefix and then\
152 the connection container._cItemPrefix before calling this function if you really want to remove the prefix.","container.js");
Leo Reppc66268e2021-10-28 11:44:35 +0200153 return;
154 };
Leo Repp5bb710b2021-12-01 13:35:13 +0100155 if (this.items.indexOf(item) < this.position) {
156 this.position--; // otherwise might go over length of list. If this.position != 0, then there must
157 // be at least one item left, thus this would not create an invalid position. If position == 0 then
158 // either a) we remove an item after the active entry or b) we are removing the active entry. a) no problem
159 // b) see below
160 //Do not use menu.prev(), as ONLY adjustments of this.position are necessary. isSelectable has already been taken care of,
161 //otherwise we would not have this item be active
162 } else if (item.active()) { // === position
Leo Reppc66268e2021-10-28 11:44:35 +0200163 this._menu.next();
Leo Repp5bb710b2021-12-01 13:35:13 +0100164 //Now we still need to reduce the position, as next was still working with a longer items list. And now we shorten it straight afterwards
165 this.position--;
Leo Reppc66268e2021-10-28 11:44:35 +0200166 };
167 item._menu=undefined;
168 this._el.removeChild(item.element());
169 this.items.splice(this.items.indexOf(item) , 1);
Leo Repp5bb710b2021-12-01 13:35:13 +0100170 this._prefixPosition=this.length()-1;
Leo Reppc66268e2021-10-28 11:44:35 +0200171 },
Leo Reppd162b2e2021-06-30 13:51:07 +0200172
Leo Repp84539162021-10-25 12:06:07 +0200173
Leo Reppd162b2e2021-06-30 13:51:07 +0200174 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200175 * Remove a containeritem from the container by its index. Should not be used with prefix.
176 * CAUTION liveIndex, so what you see, is not the actual index within the containerItem list.
Leo Repp5bb710b2021-12-01 13:35:13 +0100177 * This can be accessed with container.items.indexOf(item) . If the active item is removed, this calls menu.next().
Leo Reppc66268e2021-10-28 11:44:35 +0200178 * @param {Int} index The index of the item to be removed.
Leo Reppd162b2e2021-06-30 13:51:07 +0200179 */
Leo Reppc66268e2021-10-28 11:44:35 +0200180 removeItemByIndex : function (index) {
181 if (index < 0 || index >= this.length()){
182 KorAP.log(0,"Invalid index in containers removeItemByIndex: "+index, "container.js");
183 return;
184 };
185 this.removeItem(this.items[index]); //CAUTION liveIndex (what you see) != index within the list!
186 },
187
188 /**
189 * Exit the container unconditionally. Required so that active returns the
190 * correct result. Called when the prefix or similar resets the currently visual
191 * field.
192 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200193 exit : function () {
194 if (this.position !== undefined) {
195 this.item().active(false);
196 this.position = undefined;
197 };
198 },
199
200 element : function () {
201 return this._el;
202 },
203
204 destroy : function () {
205 for (let item of this.items){
206 delete item['_menu'];
207 }
208 },
209
210 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200211 * @returns whether an item within the container is active (by checking this.position)
212 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200213 active : function () {
214 return this.position !== undefined;
215 },
216
217 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200218 * Getter for items
219 * @param {Integer} index [optional] Index of to select item. If left blank this.position.
220 * @returns item at location index
221 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200222 item : function (index) {
223 if (index === undefined) return this.items[this.position];
224 return this.items[index];
225 },
226
227 /**
Leo Repp050a7342021-10-25 11:05:32 +0200228 *
229 * Make the container active without having called prev or next.
230 * Occurs whenever the prefix makes the
231 * menus list empty while we had it selected.
232 * This potentially requires adjusting this.position.
233 */
Leo Repp57997402021-08-18 16:37:52 +0200234 makeActive : function () {
Leo Repp050a7342021-10-25 11:05:32 +0200235 if (this.position === undefined) {
Leo Repp57997402021-08-18 16:37:52 +0200236 if (this._cItemPrefix.isSelectable()) {
Leo Repp050a7342021-10-25 11:05:32 +0200237 this.position = this._prefixPosition; //make prefix active if it exists
238 this.item().active(true);
239 } else if (this.liveLength() > 0) {
240 this.position = 0;
Leo Repp57997402021-08-18 16:37:52 +0200241 this._cItemPrefix.active(false); // usually the menu makes the prefix active anyway.
Leo Repp050a7342021-10-25 11:05:32 +0200242 this.item().active(true);
Leo Repp84539162021-10-25 12:06:07 +0200243 } else {
244 console.log("It appears that both containermenu and its container contain no selectable items.\
245 Let us hope there is no problem. - container.js");
246 };
247 };
Leo Repp050a7342021-10-25 11:05:32 +0200248 },
Leo Reppc66268e2021-10-28 11:44:35 +0200249
Leo Repp050a7342021-10-25 11:05:32 +0200250 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200251 *
252 * Move on to the next item in container. Returns true if we then leave the container, false otherwise.
253 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200254 next : function() {
255 if (this.position !== undefined){
256 this.item().active(false);
257 this.position++;
258 } else {
259 this.position = 0;
260 };
261 if (this.position >= this.length()) {
262 this.position=undefined;
263 return true;
264 };
265 while (!this.item().isSelectable()) {
266 this.position++;
267 if (this.position >= this.length()) {
268 this.position=undefined;
269 return true;
270 }
271 };
272 this.item().active(true);
273 return false;
274 },
275
276 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200277 * Move on to the previous item in container. Returns true if we then leave the container, false otherwise.
278 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200279 prev : function() {
280 if (this.position !== undefined){
281 this.item().active(false);
282 this.position = (this.position-1)
283 } else {
284 this.position = (this.items.length-1);
285 }
286 if (this.position<0) {
287 this.position=undefined;
288 return true;
289 }
290 while (!this.item().isSelectable()) {
291 this.position--;
292 if (this.position<0){
293 this.position=undefined;
294 return true;
295 };
296 };
297 this.item().active(true);
298 return false;
299 },
300
301 further : function () {
302 const item = this.item();
303 if (item["further"] !== undefined) {
304 item["further"].bind(item).apply();
305 };
306 },
307
308 enter : function (event) {
309 this.item().onclick(event);
310 },
311
312 chop : function () {
313 for (let item of this.items) {
314 item.chop();
315 }
316 },
317
318 add : function (letter) {
319 for (let item of this.items) {
320 item.add(letter);
321 }
322 },
323
324 length : function () {
325 return this.items.length;
326 },
327
328 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200329 *
330 * @returns The number of items that are selectable. Is the actual length of the list.
331 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200332 liveLength : function () {
333 var ll = 0;
334 for (let item of this.items){
335 if (item.isSelectable()){
336 ll++;
337 }
338 }
339 return ll;
340 }
341
342};
Akronb7a005a2021-09-21 17:43:02 +0200343});