blob: 0936e6cc8cfb9ba9bfc39ee4b146d9bece505b52 [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 Repp8e21cbe2021-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 Repp8e21cbe2021-08-18 16:37:52 +020034 this._el = document.createElement("ul");
35 this._el.style.outline = 0;
36 this._el.setAttribute('tabindex', 0);
37 this._el.classList.add('menu', 'container'); //container class allows for more stylesheet changes
38 this._el.addEventListener("mousedown", function (e) {
39 // see https://stackoverflow.com/questions/10652852/jquery-fire-click-before-blur-event
40 e.preventDefault();
41 // It used to be, that clicking on a item within the container (see container.js) would cause the container to gain focus
42 // thanks to mousedown default behaviour, which would mean the actual menu (ul menu roll containermenu hint) would not be in focus (I think? containermenu ul is its child
43 // afterall?). This would cause blur to be called, which (see hint/menu.js) would hide the current menu and its container, causing click to target a location
44 // the containeritem USED to be.
45 //https://w3c.github.io/uievents/#event-type-mousedown
46 //These default actions are thus not supported anymore.
47
48 }.bind(this));
49 this._el.addEventListener("click", function (e) {
50 this._menu.prefix("");
51 this._menu.hint().inputField().insert("").update();
52 this._menu.element().blur();
53 this._menu.hint().unshow(); //hide the containermenu, not with hide but with blur, because blur would usually happen in default mousedown behaviour
54 e.halt(); // Question: my impression is, that this click event handler is called after all the others and thus this should be absolutely no problem.
55 // Are we sure there are no things that do not happen now thanks to this?
Leo Reppd162b2e2021-06-30 13:51:07 +020056
Leo Repp8e21cbe2021-08-18 16:37:52 +020057 //by default, click focuses its target. Maybe that is why e.halt() is necessary? (https://w3c.github.io/uievents/#event-type-click)
58 }.bind(this));
Leo Reppd162b2e2021-06-30 13:51:07 +020059
60 this.items = new Array();
Leo Repp050a7342021-10-25 11:05:32 +020061 //items are stored in the order they are added in. This includes the prefix.
Leo Reppd162b2e2021-06-30 13:51:07 +020062 if (listOfContainerItems !== undefined) {
63 for (let item of listOfContainerItems) {
64 this.addItem(item);
65 }
66 }
67
Leo Repp050a7342021-10-25 11:05:32 +020068 this.position = undefined; //undefined = not in container,
69 // 0 to length-1 = in container
70
71 this._prefixPosition = undefined; //Required so that switching
72 // to prefix by default is supported
73
Leo Repp8e21cbe2021-08-18 16:37:52 +020074 this._menu = undefined // add later
75
Leo Reppd162b2e2021-06-30 13:51:07 +020076
77
78 //t._el.classList.add('visible'); //Done by containermenu
Leo Repp8e21cbe2021-08-18 16:37:52 +020079 },
Leo Reppd162b2e2021-06-30 13:51:07 +020080
Leo Repp8e21cbe2021-08-18 16:37:52 +020081 /**
82 * Adds a static item to this container by creating a standard containerItem as specified when this container was created,
83 * then upgrading it to the item passed to this function, and calling element() and content(). For a full list of supported functions see
84 * containeritem.js .
85 * Example:
86 *
87 * menu.container().addItem(
88 * {defaultTextValue : "dynamic", onClick : function (e) { ... }
89 * )
90 *
91 * For a full demo see containermenudemo.js.
92 *
93 * @param {Object} item An object with any number of functions like in containeritem.js or an attribute defaultTextValue,
94 * as well as any number of own properties.
95 * @returns the new use-ready containerItem
96 */
Leo Reppd162b2e2021-06-30 13:51:07 +020097 addItem : function (item) {
Leo Repp8e21cbe2021-08-18 16:37:52 +020098 //Call Order: First _containerItemClass is created and then upgraded To whatever object is passed to this function
99 //Then container calls first element() and then container()
Leo Reppd162b2e2021-06-30 13:51:07 +0200100 var cItem = this._containerItemClass.create().upgradeTo(item);
101 cItem._menu = this._menu; //if not set then undefined, but thats OK
102 this.items.push(cItem);
Leo Repp8e21cbe2021-08-18 16:37:52 +0200103 if (this._cItemPrefix !== undefined){ //this must be dynamic adding of CIs, move prefix to the back
104 this.items.splice(this.items.indexOf(this._cItemPrefix) , 1); //remove cItemPrefix
105 this.items.push(this._cItemPrefix); //and move it to the end;
106 };
Leo Reppd162b2e2021-06-30 13:51:07 +0200107 this._el.appendChild(cItem.element());
Leo Repp8e21cbe2021-08-18 16:37:52 +0200108 cItem.content(); // create its textNode
Leo Reppd162b2e2021-06-30 13:51:07 +0200109 return cItem;
110 },
111
112 addMenu : function (menu) {
113 this._menu = menu;
Leo Repp8e21cbe2021-08-18 16:37:52 +0200114 if (this._cItemPrefix !== undefined) {
115 this._menu._prefix = this._cItemPrefix; // better than going via classList or something
Leo Reppd162b2e2021-06-30 13:51:07 +0200116 };
117 for (let item of this.items) {
118 item._menu=menu;
119 }
120 },
121
122 addPrefix : function (prefix) {
123 prefix.isSelectable = function () {
124 return this.isSet(); //TODO check!
125 }
Leo Repp050a7342021-10-25 11:05:32 +0200126 this._prefixPosition = this.items.length;
Leo Repp8e21cbe2021-08-18 16:37:52 +0200127 prefix.content = function (t) {}; //Does not need a textNode Child!
Leo Reppd162b2e2021-06-30 13:51:07 +0200128 var prefItem = this.addItem(prefix);
Leo Repp8e21cbe2021-08-18 16:37:52 +0200129 this._cItemPrefix = prefItem;
130 prefItem._el["onclick"] = prefItem.onclick.bind(prefItem);
Leo Reppd162b2e2021-06-30 13:51:07 +0200131 if (this._menu !== undefined){
132 this._menu._prefix=prefItem;
133 }
134 },
Leo Reppc66268e2021-10-28 11:44:35 +0200135
Leo Repp8e21cbe2021-08-18 16:37:52 +0200136 //Taken from Branch 5133
Leo Reppc66268e2021-10-28 11:44:35 +0200137 /**
138 * Remove a containeritem from the container by identity. Should not be used with prefix.
139 * If the active item is removed, this calls menu.next().
140 * @param {containerItemClass} item The item to be removed.
141 */
142 removeItem : function (item) {
143 if (this.items.indexOf(item) === -1){ // This is returned if indexOf cannot find the item.
144 KorAP.log(0,"Invalid item in containers removeItemByIndex: This containerItem is not contained", "container.js");
145 return;
146 };
Leo Repp8e21cbe2021-08-18 16:37:52 +0200147 if (item === this._cItemPrefix) {//CHANGE TO _cItemPrefix later!!!
Leo Reppc66268e2021-10-28 11:44:35 +0200148 KorAP.log(0,"Tried to remove the prefix item by calling removeItem. Please cut all connections from the menu to prefix and then\
Leo Repp8e21cbe2021-08-18 16:37:52 +0200149 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 +0200150 return;
151 };
152 if (item.active()) {
153 this._menu.next();
154 };
155 item._menu=undefined;
156 this._el.removeChild(item.element());
157 this.items.splice(this.items.indexOf(item) , 1);
158 },
Leo Reppd162b2e2021-06-30 13:51:07 +0200159
160 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200161 * Remove a containeritem from the container by its index. Should not be used with prefix.
162 * CAUTION liveIndex, so what you see, is not the actual index within the containerItem list.
163 * This can be accessed with container.items . If the active item is removed, this calls menu.next().
164 * @param {Int} index The index of the item to be removed.
Leo Reppd162b2e2021-06-30 13:51:07 +0200165 */
Leo Reppc66268e2021-10-28 11:44:35 +0200166 removeItemByIndex : function (index) {
167 if (index < 0 || index >= this.length()){
168 KorAP.log(0,"Invalid index in containers removeItemByIndex: "+index, "container.js");
169 return;
170 };
171 this.removeItem(this.items[index]); //CAUTION liveIndex (what you see) != index within the list!
172 },
173
174 /**
175 * Exit the container unconditionally. Required so that active returns the
176 * correct result. Called when the prefix or similar resets the currently visual
177 * field.
178 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200179 exit : function () {
180 if (this.position !== undefined) {
181 this.item().active(false);
182 this.position = undefined;
183 };
184 },
185
186 element : function () {
187 return this._el;
188 },
189
190 destroy : function () {
191 for (let item of this.items){
192 delete item['_menu'];
193 }
194 },
195
196 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200197 * @returns whether an item within the container is active (by checking this.position)
198 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200199 active : function () {
200 return this.position !== undefined;
201 },
202
203 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200204 * Getter for items
205 * @param {Integer} index [optional] Index of to select item. If left blank this.position.
206 * @returns item at location index
207 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200208 item : function (index) {
209 if (index === undefined) return this.items[this.position];
210 return this.items[index];
211 },
212
213 /**
Leo Repp050a7342021-10-25 11:05:32 +0200214 *
215 * Make the container active without having called prev or next.
216 * Occurs whenever the prefix makes the
217 * menus list empty while we had it selected.
218 * This potentially requires adjusting this.position.
219 */
Leo Repp8e21cbe2021-08-18 16:37:52 +0200220 makeActive : function () {
Leo Repp050a7342021-10-25 11:05:32 +0200221 if (this.position === undefined) {
Leo Repp8e21cbe2021-08-18 16:37:52 +0200222 if (this._cItemPrefix.isSelectable()) {
Leo Repp050a7342021-10-25 11:05:32 +0200223 this.position = this._prefixPosition; //make prefix active if it exists
224 this.item().active(true);
225 } else if (this.liveLength() > 0) {
226 this.position = 0;
Leo Repp8e21cbe2021-08-18 16:37:52 +0200227 this._cItemPrefix.active(false); // usually the menu makes the prefix active anyway.
Leo Repp050a7342021-10-25 11:05:32 +0200228 this.item().active(true);
229 }
230 }
231 },
Leo Reppc66268e2021-10-28 11:44:35 +0200232
Leo Repp050a7342021-10-25 11:05:32 +0200233 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200234 *
235 * Move on to the next item in container. Returns true if we then leave the container, false otherwise.
236 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200237 next : function() {
238 if (this.position !== undefined){
239 this.item().active(false);
240 this.position++;
241 } else {
242 this.position = 0;
243 };
244 if (this.position >= this.length()) {
245 this.position=undefined;
246 return true;
247 };
248 while (!this.item().isSelectable()) {
249 this.position++;
250 if (this.position >= this.length()) {
251 this.position=undefined;
252 return true;
253 }
254 };
255 this.item().active(true);
256 return false;
257 },
258
259 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200260 * Move on to the previous item in container. Returns true if we then leave the container, false otherwise.
261 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200262 prev : function() {
263 if (this.position !== undefined){
264 this.item().active(false);
265 this.position = (this.position-1)
266 } else {
267 this.position = (this.items.length-1);
268 }
269 if (this.position<0) {
270 this.position=undefined;
271 return true;
272 }
273 while (!this.item().isSelectable()) {
274 this.position--;
275 if (this.position<0){
276 this.position=undefined;
277 return true;
278 };
279 };
280 this.item().active(true);
281 return false;
282 },
283
284 further : function () {
285 const item = this.item();
286 if (item["further"] !== undefined) {
287 item["further"].bind(item).apply();
288 };
289 },
290
291 enter : function (event) {
292 this.item().onclick(event);
293 },
294
295 chop : function () {
296 for (let item of this.items) {
297 item.chop();
298 }
299 },
300
301 add : function (letter) {
302 for (let item of this.items) {
303 item.add(letter);
304 }
305 },
306
307 length : function () {
308 return this.items.length;
309 },
310
311 /**
Leo Reppc66268e2021-10-28 11:44:35 +0200312 *
313 * @returns The number of items that are selectable. Is the actual length of the list.
314 */
Leo Reppd162b2e2021-06-30 13:51:07 +0200315 liveLength : function () {
316 var ll = 0;
317 for (let item of this.items){
318 if (item.isSelectable()){
319 ll++;
320 }
321 }
322 return ll;
323 }
324
325};
Akronb7a005a2021-09-21 17:43:02 +0200326});