mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 00:53:08 +00:00 
			
		
		
		
	Remove ajax_select from counters
This commit is contained in:
		
							
								
								
									
										184
									
								
								core/static/webpack/core/components/ajax-select-base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								core/static/webpack/core/components/ajax-select-base.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| import { inheritHtmlElement } from "#core:utils/web-components"; | ||||
| import TomSelect from "tom-select"; | ||||
| import type { | ||||
|   RecursivePartial, | ||||
|   TomLoadCallback, | ||||
|   TomOption, | ||||
|   TomSettings, | ||||
| } from "tom-select/dist/types/types"; | ||||
| import type { escape_html } from "tom-select/dist/types/utils"; | ||||
|  | ||||
| export class AutoCompleteSelectBase extends inheritHtmlElement("select") { | ||||
|   static observedAttributes = [ | ||||
|     "delay", | ||||
|     "placeholder", | ||||
|     "max", | ||||
|     "min-characters-for-search", | ||||
|   ]; | ||||
|   public widget: TomSelect; | ||||
|  | ||||
|   protected minCharNumberForSearch = 0; | ||||
|   protected delay: number | null = null; | ||||
|   protected placeholder = ""; | ||||
|   protected max: number | null = null; | ||||
|  | ||||
|   protected attributeChangedCallback( | ||||
|     name: string, | ||||
|     _oldValue?: string, | ||||
|     newValue?: string, | ||||
|   ) { | ||||
|     switch (name) { | ||||
|       case "delay": { | ||||
|         this.delay = Number.parseInt(newValue) ?? null; | ||||
|         break; | ||||
|       } | ||||
|       case "placeholder": { | ||||
|         this.placeholder = newValue ?? ""; | ||||
|         break; | ||||
|       } | ||||
|       case "max": { | ||||
|         this.max = Number.parseInt(newValue) ?? null; | ||||
|         break; | ||||
|       } | ||||
|       case "min-characters-for-search": { | ||||
|         this.minCharNumberForSearch = Number.parseInt(newValue) ?? 0; | ||||
|         break; | ||||
|       } | ||||
|       default: { | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   connectedCallback() { | ||||
|     super.connectedCallback(); | ||||
|     this.widget = new TomSelect(this.node, this.tomSelectSettings()); | ||||
|     this.attachBehaviors(); | ||||
|   } | ||||
|  | ||||
|   protected shouldLoad(query: string) { | ||||
|     return query.length >= this.minCharNumberForSearch; // Avoid launching search with less than setup number of characters | ||||
|   } | ||||
|  | ||||
|   protected tomSelectSettings(): RecursivePartial<TomSettings> { | ||||
|     return { | ||||
|       plugins: { | ||||
|         // biome-ignore lint/style/useNamingConvention: this is required by the api | ||||
|         remove_button: { | ||||
|           title: gettext("Remove"), | ||||
|         }, | ||||
|       }, | ||||
|       persist: false, | ||||
|       maxItems: this.node.multiple ? this.max : 1, | ||||
|       closeAfterSelect: true, | ||||
|       loadThrottle: this.delay, | ||||
|       placeholder: this.placeholder, | ||||
|       shouldLoad: (query: string) => this.shouldLoad(query), // wraps the method to avoid shadowing `this` by the one from tom-select | ||||
|       render: { | ||||
|         option: (item: TomOption, sanitize: typeof escape_html) => { | ||||
|           return `<div class="select-item"> | ||||
|             <span class="select-item-text">${sanitize(item.text)}</span> | ||||
|           </div>`; | ||||
|         }, | ||||
|         item: (item: TomOption, sanitize: typeof escape_html) => { | ||||
|           return `<span>${sanitize(item.text)}</span>`; | ||||
|         }, | ||||
|         // biome-ignore lint/style/useNamingConvention: that's how it's defined | ||||
|         not_loading: (data: TomOption, _sanitize: typeof escape_html) => { | ||||
|           return `<div class="no-results">${interpolate(gettext("You need to type %(number)s more characters"), { number: this.minCharNumberForSearch - data.input.length }, true)}</div>`; | ||||
|         }, | ||||
|         // biome-ignore lint/style/useNamingConvention: that's how it's defined | ||||
|         no_results: (_data: TomOption, _sanitize: typeof escape_html) => { | ||||
|           return `<div class="no-results">${gettext("No results found")}</div>`; | ||||
|         }, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   protected attachBehaviors() { | ||||
|     /* Called once the widget has been initialized */ | ||||
|   } | ||||
| } | ||||
|  | ||||
| export abstract class AjaxSelect extends AutoCompleteSelectBase { | ||||
|   protected filter?: (items: TomOption[]) => TomOption[] = null; | ||||
|   protected minCharNumberForSearch = 2; | ||||
|  | ||||
|   protected abstract valueField: string; | ||||
|   protected abstract labelField: string; | ||||
|   protected abstract searchField: string[]; | ||||
|  | ||||
|   protected abstract renderOption( | ||||
|     item: TomOption, | ||||
|     sanitize: typeof escape_html, | ||||
|   ): string; | ||||
|   protected abstract renderItem(item: TomOption, sanitize: typeof escape_html): string; | ||||
|   protected abstract search(query: string): Promise<TomOption[]>; | ||||
|  | ||||
|   private initialValues: TomOption[] = []; | ||||
|   public setFilter(filter?: (items: TomOption[]) => TomOption[]) { | ||||
|     this.filter = filter; | ||||
|   } | ||||
|  | ||||
|   protected shouldLoad(query: string) { | ||||
|     const resp = super.shouldLoad(query); | ||||
|     /* Force order sync with backend if no client side filtering is set */ | ||||
|     if (!resp && this.searchField.length === 0) { | ||||
|       this.widget.clearOptions(); | ||||
|     } | ||||
|     return resp; | ||||
|   } | ||||
|  | ||||
|   protected async loadFunction(query: string, callback: TomLoadCallback) { | ||||
|     /* Force order sync with backend if no client side filtering is set */ | ||||
|     if (this.searchField.length === 0) { | ||||
|       this.widget.clearOptions(); | ||||
|     } | ||||
|  | ||||
|     const resp = await this.search(query); | ||||
|  | ||||
|     if (this.filter) { | ||||
|       callback(this.filter(resp), []); | ||||
|     } else { | ||||
|       callback(resp, []); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected tomSelectSettings(): RecursivePartial<TomSettings> { | ||||
|     return { | ||||
|       ...super.tomSelectSettings(), | ||||
|       hideSelected: true, | ||||
|       diacritics: true, | ||||
|       duplicates: false, | ||||
|       valueField: this.valueField, | ||||
|       labelField: this.labelField, | ||||
|       searchField: this.searchField, | ||||
|       load: (query: string, callback: TomLoadCallback) => | ||||
|         this.loadFunction(query, callback), // wraps the method to avoid shadowing `this` by the one from tom-select | ||||
|       render: { | ||||
|         ...super.tomSelectSettings().render, | ||||
|         option: this.renderOption, | ||||
|         item: this.renderItem, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   connectedCallback() { | ||||
|     /* Capture initial values before they get moved to the inner node and overridden by tom-select */ | ||||
|     this.initialValues = Array.from(this.children) | ||||
|       .filter((child: Element) => child.tagName.toLowerCase() === "slot") | ||||
|       .map((slot) => JSON.parse(slot.innerHTML)); | ||||
|  | ||||
|     super.connectedCallback(); | ||||
|   } | ||||
|  | ||||
|   protected attachBehaviors() { | ||||
|     super.attachBehaviors(); | ||||
|  | ||||
|     // Gather selected options, they must be added with slots like `<slot>json</slot>` | ||||
|     for (const value of this.initialValues) { | ||||
|       this.widget.addOption(value, false); | ||||
|       this.widget.addItem(value[this.valueField]); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,12 +1,6 @@ | ||||
| import "tom-select/dist/css/tom-select.default.css"; | ||||
| import { inheritHtmlElement, registerComponent } from "#core:utils/web-components"; | ||||
| import TomSelect from "tom-select"; | ||||
| import type { | ||||
|   RecursivePartial, | ||||
|   TomLoadCallback, | ||||
|   TomOption, | ||||
|   TomSettings, | ||||
| } from "tom-select/dist/types/types"; | ||||
| import { registerComponent } from "#core:utils/web-components"; | ||||
| import type { TomOption } from "tom-select/dist/types/types"; | ||||
| import type { escape_html } from "tom-select/dist/types/utils"; | ||||
| import { | ||||
|   type GroupSchema, | ||||
| @@ -15,181 +9,13 @@ import { | ||||
|   userSearchUsers, | ||||
| } from "#openapi"; | ||||
|  | ||||
| import { | ||||
|   AjaxSelect, | ||||
|   AutoCompleteSelectBase, | ||||
| } from "#core:core/components/ajax-select-base"; | ||||
|  | ||||
| @registerComponent("autocomplete-select") | ||||
| class AutocompleteSelect extends inheritHtmlElement("select") { | ||||
|   static observedAttributes = [ | ||||
|     "delay", | ||||
|     "placeholder", | ||||
|     "max", | ||||
|     "min-characters-for-search", | ||||
|   ]; | ||||
|   public widget: TomSelect; | ||||
|  | ||||
|   protected minCharNumberForSearch = 0; | ||||
|   protected delay: number | null = null; | ||||
|   protected placeholder = ""; | ||||
|   protected max: number | null = null; | ||||
|  | ||||
|   protected attributeChangedCallback( | ||||
|     name: string, | ||||
|     _oldValue?: string, | ||||
|     newValue?: string, | ||||
|   ) { | ||||
|     switch (name) { | ||||
|       case "delay": { | ||||
|         this.delay = Number.parseInt(newValue) ?? null; | ||||
|         break; | ||||
|       } | ||||
|       case "placeholder": { | ||||
|         this.placeholder = newValue ?? ""; | ||||
|         break; | ||||
|       } | ||||
|       case "max": { | ||||
|         this.max = Number.parseInt(newValue) ?? null; | ||||
|         break; | ||||
|       } | ||||
|       case "min-characters-for-search": { | ||||
|         this.minCharNumberForSearch = Number.parseInt(newValue) ?? 0; | ||||
|         break; | ||||
|       } | ||||
|       default: { | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   connectedCallback() { | ||||
|     super.connectedCallback(); | ||||
|     this.widget = new TomSelect(this.node, this.tomSelectSettings()); | ||||
|     this.attachBehaviors(); | ||||
|   } | ||||
|  | ||||
|   protected shouldLoad(query: string) { | ||||
|     return query.length >= this.minCharNumberForSearch; // Avoid launching search with less than setup number of characters | ||||
|   } | ||||
|  | ||||
|   protected tomSelectSettings(): RecursivePartial<TomSettings> { | ||||
|     return { | ||||
|       plugins: { | ||||
|         // biome-ignore lint/style/useNamingConvention: this is required by the api | ||||
|         remove_button: { | ||||
|           title: gettext("Remove"), | ||||
|         }, | ||||
|       }, | ||||
|       persist: false, | ||||
|       maxItems: this.node.multiple ? this.max : 1, | ||||
|       closeAfterSelect: true, | ||||
|       loadThrottle: this.delay, | ||||
|       placeholder: this.placeholder, | ||||
|       shouldLoad: (query: string) => this.shouldLoad(query), // wraps the method to avoid shadowing `this` by the one from tom-select | ||||
|       render: { | ||||
|         option: (item: TomOption, sanitize: typeof escape_html) => { | ||||
|           return `<div class="select-item"> | ||||
|             <span class="select-item-text">${sanitize(item.text)}</span> | ||||
|           </div>`; | ||||
|         }, | ||||
|         item: (item: TomOption, sanitize: typeof escape_html) => { | ||||
|           return `<span>${sanitize(item.text)}</span>`; | ||||
|         }, | ||||
|         // biome-ignore lint/style/useNamingConvention: that's how it's defined | ||||
|         not_loading: (data: TomOption, _sanitize: typeof escape_html) => { | ||||
|           return `<div class="no-results">${interpolate(gettext("You need to type %(number)s more characters"), { number: this.minCharNumberForSearch - data.input.length }, true)}</div>`; | ||||
|         }, | ||||
|         // biome-ignore lint/style/useNamingConvention: that's how it's defined | ||||
|         no_results: (_data: TomOption, _sanitize: typeof escape_html) => { | ||||
|           return `<div class="no-results">${gettext("No results found")}</div>`; | ||||
|         }, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   protected attachBehaviors() { | ||||
|     /* Called once the widget has been initialized */ | ||||
|   } | ||||
| } | ||||
|  | ||||
| abstract class AjaxSelect extends AutocompleteSelect { | ||||
|   protected filter?: (items: TomOption[]) => TomOption[] = null; | ||||
|   protected minCharNumberForSearch = 2; | ||||
|  | ||||
|   protected abstract valueField: string; | ||||
|   protected abstract labelField: string; | ||||
|   protected abstract searchField: string[]; | ||||
|  | ||||
|   protected abstract renderOption( | ||||
|     item: TomOption, | ||||
|     sanitize: typeof escape_html, | ||||
|   ): string; | ||||
|   protected abstract renderItem(item: TomOption, sanitize: typeof escape_html): string; | ||||
|   protected abstract search(query: string): Promise<TomOption[]>; | ||||
|  | ||||
|   private initialValues: TomOption[] = []; | ||||
|   public setFilter(filter?: (items: TomOption[]) => TomOption[]) { | ||||
|     this.filter = filter; | ||||
|   } | ||||
|  | ||||
|   protected shouldLoad(query: string) { | ||||
|     const resp = super.shouldLoad(query); | ||||
|     /* Force order sync with backend if no client side filtering is set */ | ||||
|     if (!resp && this.searchField.length === 0) { | ||||
|       this.widget.clearOptions(); | ||||
|     } | ||||
|     return resp; | ||||
|   } | ||||
|  | ||||
|   protected async loadFunction(query: string, callback: TomLoadCallback) { | ||||
|     /* Force order sync with backend if no client side filtering is set */ | ||||
|     if (this.searchField.length === 0) { | ||||
|       this.widget.clearOptions(); | ||||
|     } | ||||
|  | ||||
|     const resp = await this.search(query); | ||||
|  | ||||
|     if (this.filter) { | ||||
|       callback(this.filter(resp), []); | ||||
|     } else { | ||||
|       callback(resp, []); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected tomSelectSettings(): RecursivePartial<TomSettings> { | ||||
|     return { | ||||
|       ...super.tomSelectSettings(), | ||||
|       hideSelected: true, | ||||
|       diacritics: true, | ||||
|       duplicates: false, | ||||
|       valueField: this.valueField, | ||||
|       labelField: this.labelField, | ||||
|       searchField: this.searchField, | ||||
|       load: (query: string, callback: TomLoadCallback) => | ||||
|         this.loadFunction(query, callback), // wraps the method to avoid shadowing `this` by the one from tom-select | ||||
|       render: { | ||||
|         ...super.tomSelectSettings().render, | ||||
|         option: this.renderOption, | ||||
|         item: this.renderItem, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   connectedCallback() { | ||||
|     /* Capture initial values before they get moved to the inner node and overridden by tom-select */ | ||||
|     this.initialValues = Array.from(this.children) | ||||
|       .filter((child) => child.tagName.toLowerCase() === "slot") | ||||
|       .map((slot) => JSON.parse(slot.innerHTML)); | ||||
|  | ||||
|     super.connectedCallback(); | ||||
|   } | ||||
|  | ||||
|   protected attachBehaviors() { | ||||
|     super.attachBehaviors(); | ||||
|  | ||||
|     // Gather selected options, they must be added with slots like `<slot>json</slot>` | ||||
|     for (const value of this.initialValues) { | ||||
|       this.widget.addOption(value, false); | ||||
|       this.widget.addItem(value[this.valueField]); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| export class AutoCompleteSelect extends AutoCompleteSelectBase {} | ||||
|  | ||||
| @registerComponent("user-ajax-select") | ||||
| export class UserAjaxSelect extends AjaxSelect { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user