UNPKG

test-isc

Version:

An Ionic component similar to Ionic Select, that allows to search items, including async search, group, add, edit, delete items, and much more.

1,253 lines (1,252 loc) 138 kB
import { Component, Prop, h, Host, Element, Event, Watch, Method, State, } from '@stencil/core'; import { getMode, modalController, } from '@ionic/core'; import { hostContext, addRippleEffectElement, findItem, findItemLabel, renderHiddenInput } from '../../utils/utils'; import { IonicSelectableInfiniteScrolledEvent, IonicSelectableSearchingEvent, IonicSelectableSearchSuccessedEvent, IonicSelectableSearchFailedEvent, IonicSelectableSelectedEvent, IonicSelectableChangedEvent, IonicSelectableItemAddingEvent, IonicSelectableClearedEvent, IonicSelectableOpenedEvent, IonicSelectableClosedEvent, } from './ionic-selectable.interfaces.component'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. * * @part placeholder - The text displayed in the select when there is no value. * @part text - The displayed value of the select. * @part icon - The select icon container. * @part icon-inner - The select icon. */ export class IonicSelectableComponent { constructor() { this.id = this.element.id ? this.element.id : `ionic-selectable-${nextId++}`; this.isInited = false; this.isRendered = false; this.isChangeInternal = false; this.groups = []; this.filteredGroups = []; this.hasFilteredItems = false; this.hasObjects = false; this.hasGroups = false; this.footerButtonsCount = 0; this.isSearching = false; this.isAddItemTemplateVisible = false; this.isFooterVisible = true; this.itemToAdd = null; this.selectedItems = []; this.valueItems = []; this.itemsToConfirm = []; /** * Determines whether Modal is opened. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#isopened). * * @default false * @readonly * @memberof IonicSelectableComponent */ this.isOpened = false; /** * Determines whether the component is disabled. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#isdisabled). * * @default false * @memberof IonicSelectableComponent */ this.isDisabled = false; /** * Close button text. * The field is only applicable to **iOS** platform, on **Android** only Cross icon is displayed. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#closebuttontext). * * @default 'Cancel' * @memberof IonicSelectableComponent */ this.closeButtonText = 'Cancel'; /** * Close button slot. [Ionic slots](https://ionicframework.com/docs/api/buttons) are supported. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#closebuttonslot). * * @default 'start' * @memberof IonicSelectableComponent */ this.closeButtonSlot = 'start'; /** * Item icon slot. [Ionic slots](https://ionicframework.com/docs/api/item) are supported. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#itemiconslot). * * @default 'start' * @memberof IonicSelectableComponent */ this.itemIconSlot = 'start'; /** * Confirm button text. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#confirmbuttontext). * * @default 'OK' * @memberof IonicSelectableComponent */ this.confirmButtonText = 'OK'; /** * Clear button text. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#clearbuttontext). * * @default 'Clear' * @memberof IonicSelectableComponent */ this.clearButtonText = 'Clear'; /** * Add button text. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#addbuttontext). * * @default 'Add' * @memberof IonicSelectableComponent */ this.addButtonText = 'Add'; /** * The name of the control, which is submitted with the form data. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#name). * * @default null * @memberof IonicSelectableComponent */ this.name = this.id; /** * Determines whether multiple items can be selected. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#ismultiple). * * @default false * @memberof IonicSelectableComponent */ this.isMultiple = false; /** * The value of the component. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#value). * * @default null * @memberof IonicSelectableComponent */ this.value = null; /** * Is set to true, the value will be extracted from the itemValueField of the objects. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#shouldStoreItemValue). * * @default false * @memberof IonicSelectableComponent */ this.shouldStoreItemValue = false; /** * A list of items. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#items). * * @default [] * @memberof IonicSelectableComponent */ this.items = []; /** * A list of items to disable. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#disableditems). * * @default [] * @memberof IonicSelectableComponent */ this.disabledItems = []; /** * Item property to use as a unique identifier, e.g, `'id'`. * **Note**: `items` should be an object array. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#itemvaluefield). * * @default null * @memberof IonicSelectableComponent */ this.itemValueField = null; /** * Item property to display, e.g, `'name'`. * **Note**: `items` should be an object array. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#itemtextfield). * * @default null * @memberof IonicSelectableComponent */ this.itemTextField = null; /** * Modal CSS class. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#modalcssclass). * * @default null * @memberof IonicSelectableComponent */ this.modalCssClass = null; /** * Modal enter animation. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#modalenteranimation). * * @default null * @memberof IonicSelectableComponent */ this.modalEnterAnimation = null; /** * Modal leave animation. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#modalleaveanimation). * * @default null * @memberof IonicSelectableComponent */ this.modalLeaveAnimation = null; /** * Text of [Ionic Label](https://ionicframework.com/docs/api/label). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#label). * * @readonly * @default null * @memberof IonicSelectableComponent */ this.titleText = null; /** * * Group property to use as a unique identifier to group items, e.g. `'country.id'`. * **Note**: `items` should be an object array. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#groupvaluefield). * * @default null * @memberof IonicSelectableComponent */ this.groupValueField = null; /** * Group property to display, e.g. `'country.name'`. * **Note**: `items` should be an object array. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#grouptextfield). * * @default null * @memberof IonicSelectableComponent */ this.groupTextField = null; /** * Determines whether Ionic [InfiniteScroll](https://ionicframework.com/docs/api/infinite-scroll) is enabled. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hasinfinitescroll). * * @default false * @memberof IonicSelectableComponent */ this.hasInfiniteScroll = false; /** * The threshold distance from the bottom of the content to call the infinite output event when scrolled. * Use the value 100px when the scroll is within 100 pixels from the bottom of the page. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#infinite-scroll). * * @default '100px' * @memberof IonicSelectableComponent */ this.infiniteScrollThreshold = '100px'; /** * Determines whether Ionic [VirtualScroll](https://ionicframework.com/docs/api/virtual-scroll) is enabled. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hasvirtualscroll). * * @default false * @memberof IonicSelectableComponent */ this.hasVirtualScroll = false; /** * See Ionic VirtualScroll [approxHeaderHeight](https://ionicframework.com/docs/api/virtual-scroll). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#virtualscrollheaderfn). * * @default 30 * @memberof IonicSelectableComponent */ this.virtualScrollApproxHeaderHeight = 30; /** * See Ionic VirtualScroll [approxItemHeight](https://ionicframework.com/docs/api/virtual-scroll). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#virtualscrollheaderfn). * * @default 45 * @memberof IonicSelectableComponent */ this.virtualScrollApproxItemHeight = 45; /** * Determines whether Confirm button is visible for single selection. * By default Confirm button is visible only for multiple selection. * **Note**: It is always true for multiple selection and cannot be changed. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hasconfirmbutton). * * @default false * @memberof IonicSelectableComponent */ this.hasConfirmButton = false; /** * Determines whether to allow adding items. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#canadditem). * * @default false * @memberof IonicSelectableComponent */ this.canAddItem = false; /** * Determines whether to show Clear button. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#canclear). * @default false * @memberof IonicSelectableComponent */ // Pending - @HostBinding('class.ionic-selectable-can-clear') this.canClear = false; /** * Determines whether to show [Searchbar](https://ionicframework.com/docs/api/searchbar). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#cansearch). * * @default false * @memberof IonicSelectableComponent */ this.canSearch = false; /** * Determines the search is delegate to event, and not handled internally. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#cansearch). * * @default false * @memberof IonicSelectableComponent */ this.shouldDelegateSearchToEvent = false; /** * How long, in milliseconds, to wait to filter items or to trigger `onSearch` event after each keystroke. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#searchdebounce). * * @default 250 * @memberof IonicSelectableComponent */ this.searchDebounce = 250; /** * A placeholder for [Searchbar](https://ionicframework.com/docs/api/searchbar). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#searchplaceholder). * * @default 'Search' * @memberof IonicSelectableComponent */ this.searchPlaceholder = 'Search'; /** * Text in [Searchbar](https://ionicframework.com/docs/api/searchbar). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#searchtext). * * @default '' * @memberof IonicSelectableComponent */ this.searchText = ''; /** * Text to display when no items have been found during search. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#searchfailtext). * * @default 'No items found.' * @memberof IonicSelectableComponent */ this.searchFailText = 'No items found.'; /** * Determines whether Searchbar should receive focus when Modal is opened. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#shouldfocussearchbar). * * @default false * @memberof IonicSelectableComponent */ this.shouldFocusSearchbar = false; /** * Set the cancel button icon of the [Searchbar](https://ionicframework.com/docs/api/searchbar). * Only applies to md mode. Defaults to "arrow-back-sharp". * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hassearchtext). * * @default 'arrow-back-sharp' * @memberof IonicSelectableComponent */ this.searchCancelButtonIcon = 'arrow-back-sharp'; /** * Set the the cancel button text of the [Searchbar](https://ionicframework.com/docs/api/searchbar). * Only applies to ios mode. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hassearchtext). * * @default 'Cancel' * @memberof IonicSelectableComponent */ this.searchCancelButtonText = 'Cancel'; /** * Set the clear icon of the [Searchbar](https://ionicframework.com/docs/api/searchbar). * Defaults to "close-circle" for ios and "close-sharp" for md. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hassearchtext). * * @memberof IonicSelectableComponent */ this.searchClearIcon = getMode() === 'ios' ? 'close-circle' : 'close-sharp'; /** * A hint to the browser for which keyboard to display. * Possible values: "none", "text", "tel", "url", "email", "numeric", "decimal", and "search". * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hassearchtext). * @default 'none' * @memberof IonicSelectableComponent */ this.searchInputmode = 'none'; /** * The icon to use as the search icon in the [Searchbar](https://ionicframework.com/docs/api/searchbar). * Defaults to "search-outline" in ios mode and "search-sharp" in md mode. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hassearchtext). * @default 'none' * @memberof IonicSelectableComponent */ this.searchIcon = getMode() === 'ios' ? 'search-outline' : 'search-sharp'; /** * Sets the behavior for the cancel button of the [Searchbar](https://ionicframework.com/docs/api/searchbar). * Defaults to "never". * Setting to "focus" shows the cancel button on focus. * Setting to "never" hides the cancel button. * Setting to "always" shows the cancel button regardless of focus state. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hassearchtext). * @default 'none' * @memberof IonicSelectableComponent */ this.searchShowCancelButton = 'never'; /** * Determines whether Confirm button is enabled. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#isconfirmbuttonenabled). * * @default true * @memberof IonicSelectableComponent */ this.isConfirmButtonEnabled = true; /** * Header color. [Ionic colors](https://ionicframework.com/docs/theming/advanced#colors) are supported. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#headercolor). * * @default null * @memberof IonicSelectableComponent */ this.headerColor = null; /** * Group color. [Ionic colors](https://ionicframework.com/docs/theming/advanced#colors) are supported. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#groupcolor). * * @default null * @memberof IonicSelectableComponent */ this.groupColor = null; /** * See Ionic VirtualScroll [headerFn](https://ionicframework.com/docs/api/virtual-scroll). * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#virtualscrollheaderfn). * * @memberof IonicSelectableComponent */ this.virtualScrollHeaderFn = () => null; this.onClick = async () => { this.setFocus(); this.open(); }; this.onFocus = () => { this.focused.emit(); }; this.onBlur = () => { this.blurred.emit(); }; } onShouldStoreItemValueChanged(value) { if (!value && !this.hasObjects) { throw new Error(`If items contains primitive elements, shouldStoreItemValue must be null or true: ${this.element.id}`); } } onItemValueFieldChanged(value) { if (this.hasObjects && this.isNullOrWhiteSpace(value)) { throw new Error(`If items contains object elements, itemValueField must be non null or non whitespace : ${this.element.id}`); } else if (!this.hasObjects && !this.isNullOrWhiteSpace(value)) { throw new Error(`If items contains primitive elements, itemValueField must be null: ${this.element.id}`); } } onItemTextFieldChanged(value) { if (this.hasObjects && this.isNullOrWhiteSpace(value)) { throw new Error(`If items contains object elements, itemTextField must be non null or non whitespace : ${this.element.id}`); } else if (!this.hasObjects && !this.isNullOrWhiteSpace(value)) { throw new Error(`If items contains primitive elements, itemTextField must be null: ${this.element.id}`); } } onItemsChanged(value) { this.setItems(value); } onDisabledChanged() { this.emitStyle(); } onValueChanged(newValue) { if (!this.isChangeInternal) { this.emitStyle(); if (this.isInited) { this.setValue(newValue, false); } } this.isChangeInternal = false; } onSearchTextChanged(newValue) { if (!this.isChangeInternal) { if (this.isOpened) { this.startSearch(); this.filterItems(newValue, false); this.endSearch(); } } this.isChangeInternal = false; } onIsMultipleChanged() { this.countFooterButtons(); } onDisabledItemsChanged() { var _a; (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } async connectedCallback() { this.emitStyle(); } componentWillLoad() { this.setItems(this.items); this.setValue(this.value); this.countFooterButtons(); this.isInited = true; } /** * Determines whether any item has been selected. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hasvalue). * * @returns A boolean determining whether any item has been selected. * @memberof IonicSelectableComponent */ async hasValue() { return this.parseValue() !== ''; } /** * Opens Modal. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#open). * * @returns Promise that resolves when Modal has been opened. * @memberof IonicSelectableComponent */ async open() { if (this.isDisabled || this.isOpened) { return Promise.reject(`IonicSelectable is disabled or already opened: ${this.element.id}`); } const label = findItemLabel(this.element); if (label && !this.titleText) { this.titleText = label.textContent; } const modalOptions = { component: 'ionic-selectable-modal', componentProps: { selectableComponent: this }, backdropDismiss: this.shouldBackdropClose, }; if (this.modalCssClass) { modalOptions.cssClass = this.modalCssClass; } if (this.modalEnterAnimation) { modalOptions.enterAnimation = this.modalEnterAnimation; } if (this.modalLeaveAnimation) { modalOptions.leaveAnimation = this.modalLeaveAnimation; } this.filterItems(this.searchText); this.modalElement = await modalController.create(modalOptions); await this.modalElement.present(); this.isOpened = true; this.setFocus(); this.whatchModalEvents(); this.emitOpened(); return Promise.resolve(); } /** * Closes Modal. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#close). * * @returns Promise that resolves when Modal has been closed. * @memberof IonicSelectableComponent */ async close() { if (this.isDisabled || !this.isOpened) { return Promise.reject(`IonicSelectable is disabled or already closed: ${this.element.id}`); } await this.modalElement.dismiss(); this.itemToAdd = null; this.hideAddItemTemplate(); if (!this.shouldDelegateSearchToEvent) { this.setHasSearchText(''); } this.emitClosed(); return Promise.resolve(); } /** * Return a list of items that are selected and awaiting confirmation by user, when he has clicked Confirm button. * After the user has clicked Confirm button items to confirm are cleared. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#itemstoconfirm). * * @returns a promise whit de list of items that are selected and awaiting confirmation by user * @memberof IonicSelectableComponent */ async getItemsToConfirm() { return this.itemsToConfirm; } /** * Confirms selected items by updating value. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#confirm). * * @memberof IonicSelectableComponent */ async confirm() { if (this.isMultiple) { this.setValue(this.selectedItems); } else if (this.hasConfirmButton || (this.hasTemplateRender && this.hasTemplateRender('footer'))) { this.setValue(this.selectedItems[0] || null); } } /** * Clears value. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#clear). * * @memberof IonicSelectableComponent */ async clear() { this.clearItems(); } /** * Enables infinite scroll. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#enableinfinitescroll). * * @memberof IonicSelectableComponent */ async enableInfiniteScroll() { if (!this.hasInfiniteScroll) { return; } this.selectableModalComponent.infiniteScrollElement.disabled = false; } /** * Disables infinite scroll. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#disableinfinitescroll). * * @memberof IonicSelectableComponent */ async disableInfiniteScroll() { if (!this.hasInfiniteScroll) { return; } this.selectableModalComponent.infiniteScrollElement.disabled = true; } /** * Ends infinite scroll. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#endinfinitescroll). * * @memberof IonicSelectableComponent */ async endInfiniteScroll() { if (!this.hasInfiniteScroll) { return; } this.selectableModalComponent.infiniteScrollElement.complete(); this.setItems(this.items); } /** * Scrolls to the top of Modal content. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#scrolltotop). * * @returns Promise that resolves when scroll has been completed. * @memberof IonicSelectableComponent */ async scrollToTop() { if (!this.isOpened) { return Promise.reject(`IonicSelectable content cannot be scrolled: ${this.element.id}`); } await this.selectableModalComponent.contentElement.scrollToTop(); return Promise.resolve(); } /** * Scrolls to the bottom of Modal content. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#scrolltobottom). * * @returns Promise that resolves when scroll has been completed. * @memberof IonicSelectableComponent */ async scrollToBottom() { if (!this.isOpened) { return Promise.reject(`IonicSelectable content cannot be scrolled: ${this.element.id}`); } await this.selectableModalComponent.contentElement.scrollToBottom(); return Promise.resolve(); } /** * Starts search process by showing Loading spinner. * Use it together with `onSearch` event to indicate search start. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#startsearch). * * @memberof IonicSelectableComponent */ async startSearch() { if (this.isDisabled) { return; } this.showLoading(); } /** * Ends search process by hiding Loading spinner and refreshing items. * Use it together with `onSearch` event to indicate search end. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#endsearch). * * @memberof IonicSelectableComponent */ async endSearch() { if (this.isDisabled) { return; } this.hideLoading(); // Refresh items manually. // Pending - this.setItems(this.items); this.emitOnSearchSuccessedOrFailed(this.hasFilteredItems); } /** * Shows Loading spinner. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#showloading). * * @memberof IonicSelectableComponent */ async showLoading() { var _a; if (this.isDisabled) { return; } this.isSearching = true; (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } /** * Hides Loading spinner. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hideloading). * * @memberof IonicSelectableComponent */ async hideLoading() { var _a; if (this.isDisabled) { return; } this.isSearching = false; (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } /** * Adds item. * **Note**: If you want an item to be added to the original array as well use two-way data binding syntax on `[(items)]` field. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#additem). * * @param item Item to add. * @returns Promise that resolves when item has been added. * @memberof IonicSelectableComponent */ async addItem(item) { // Adding item triggers onItemsChange. // Return a promise that resolves when onItemsChange finishes. // We need a promise or user could do something after item has been added, // e.g. use search() method to find the added item. this.items.push(item); this.setItems(this.items); return Promise.resolve(); } /** * Deletes item. * **Note**: If you want an item to be deleted from the original array as well use two-way data binding syntax on `[(items)]` field. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#deleteitem). * * @param item Item to delete. * @returns Promise that resolves when item has been deleted. * @memberof IonicSelectableComponent */ async deleteItem(item) { let hasValueChanged = false; // Remove deleted item from selected items. if (this.selectedItems) { this.selectedItems = this.selectedItems.filter((_item) => this.getItemValue(item) !== this.getStoredItemValue(_item)); } // Remove deleted item from value. if (this.value) { if (this.isMultiple) { const values = this.value.filter((value) => { return value.id !== item.id; }); if (values.length !== this.value.length) { this.value = values; hasValueChanged = true; } } else { if (item === this.value) { this.value = null; hasValueChanged = true; } } } if (hasValueChanged) { this.emitChanged(); } // Remove deleted item from list. const items = this.items.filter((_item) => { return _item.id !== item.id; }); // Refresh items on parent component. // Pending - this.itemsChange.emit(items); // Refresh list. this.setItems(items); return Promise.resolve(); } /** * Selects or deselects all or specific items. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#toggleitems). * * @param isSelect Determines whether to select or deselect items. * @param [items] Items to toggle. If items are not set all items will be toggled. * @memberof IonicSelectableComponent */ async toggleItems(isSelect, items) { var _a; if (isSelect) { const hasItems = items && items.length; let itemsToToggle = this.groups.reduce((allItems, group) => { return allItems.concat(group.items); }, []); // Don't allow to select all items in single mode. if (!this.isMultiple && !hasItems) { itemsToToggle = []; } // Toggle specific items. if (hasItems) { itemsToToggle = itemsToToggle.filter((itemToToggle) => { return (items.find((item) => { return this.getItemValue(itemToToggle) === this.getItemValue(item); }) !== undefined); }); // Take the first item for single mode. if (!this.isMultiple) { itemsToToggle.splice(0, 1); } } itemsToToggle.forEach((item) => { this.addSelectedItem(item); }); } else { const hasItems = items && items.length; if (hasItems) { items.forEach((item) => { this.deleteSelectedItem(item); }); } else { this.selectedItems = []; } (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } this.itemsToConfirm = [...this.selectedItems]; } /** * Shows `ionicSelectableAddItemTemplate`. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#showadditemtemplate). * * @memberof IonicSelectableComponent */ async showAddItemTemplate() { this.toggleAddItemTemplate(true); } /** * Hides `ionicSelectableAddItemTemplate`. * See more on [GitHub](https://github.com/eakoriakin/ionic-selectable/wiki/Documentation#hideadditemtemplate). * * @memberof IonicSelectableComponent */ async hideAddItemTemplate() { // Clean item to add as it's no longer needed once Add Item Modal has been closed. this.itemToAdd = null; this.toggleAddItemTemplate(false); } clearItems() { var _a; this.setValue(null); (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); this.emitCleared(); } closeModal() { this.close(); } addItemClick() { if (this.hasTemplateRender && this.hasTemplateRender('addItem')) { this.showAddItemTemplate(); } else { this.emitItemAdding(); } } onSearchbarValueChanged(event) { this.startSearch(); this.filterItems(event.detail.value); this.endSearch(); } isItemSelected(item) { return (this.selectedItems.find((selectedItem) => { return this.getItemValue(item) === this.getStoredItemValue(selectedItem); }) !== undefined); } isItemDisabled(item) { if (!this.disabledItems) { return; } return this.disabledItems.some((_item) => { return this.getItemValue(_item) === this.getItemValue(item); }); } selectItem(item) { const isItemSelected = this.isItemSelected(item); if (this.isMultiple) { if (isItemSelected) { this.deleteSelectedItem(item); } else { this.addSelectedItem(item); } this.itemsToConfirm = [...this.selectedItems]; // Emit onSelect event after setting items to confirm so they could be used inside the event. this.emitSelected(item, !isItemSelected); } else { if (this.hasConfirmButton /* || this.footerTemplate*/) { // Don't close Modal and keep track on items to confirm. // When footer template is used it's up to developer to close Modal. this.selectedItems = []; if (isItemSelected) { this.deleteSelectedItem(item); } else { this.addSelectedItem(item); } this.itemsToConfirm = [...this.selectedItems]; // Emit onSelect event after setting items to confirm so they could be used inside the event. this.emitSelected(item, !isItemSelected); } else { if (!isItemSelected) { this.selectedItems = []; this.addSelectedItem(item); // Emit onSelect before onChange. this.emitSelected(item, true); this.setValue(item); } this.close(); } } } confirmSelection() { this.confirm(); this.close(); } getMoreItems() { this.emitIonInfiniteScrolled(); } setValue(value, isChangeInternal = true) { var _a; this.isChangeInternal = isChangeInternal; if (value) { // If type is string convert to object value = typeof value === 'string' ? JSON.parse(value.replace(/\'/gi, '"')) : value; const isArray = Array.isArray(value); if (!isArray) { value = [value]; } if (this.isMultiple && !isArray) { throw new Error(`If isMultiple is set to true, value must be array: ${this.element.id}`); } if (!this.isMultiple && isArray) { throw new Error(`If isMultiple is set to false, value must be object: ${this.element.id}`); } this.valueItems = []; value.forEach((_item) => { if (this.shouldStoreItemValue && typeof _item === 'object') { throw new Error(`If shouldStoreItemValue is set to true, value must be primitive: ${this.element.id}`); } else if (!this.shouldStoreItemValue && typeof _item !== 'object') { throw new Error(`If shouldStoreItemValue is set to false, value must be object: ${this.element.id}`); } const itemFind = this.items.find((item) => this.getItemValue(item) === this.getStoredItemValue(_item)); if (itemFind) { this.valueItems.push(this.getItem(itemFind)); } }); if (!this.isMultiple) { this.valueItems = this.valueItems.pop(); this.selectedItems = [this.valueItems]; } else { this.selectedItems = [...this.valueItems]; } if (this.isChangeInternal) { this.value = this.valueItems; } } else { this.valueItems = []; this.selectedItems = []; if (this.isChangeInternal) { this.value = this.isMultiple ? [] : null; } } this.itemsToConfirm = []; if (this.isOpened) { (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } if (this.isInited) { this.emitChanged(); } } setItems(items) { var _a, _b; if (!Array.isArray(items)) { throw new Error(`items must be array: ${this.element.id}`); } this.items.forEach((item) => { if (typeof item === 'object') { this.hasObjects = true; } }); // If items contains primitive elements, isValuePrimitive is set to true if (!this.hasObjects) { this.shouldStoreItemValue = true; } this.onItemValueFieldChanged(this.itemValueField); this.onItemTextFieldChanged(this.itemTextField); this.onShouldStoreItemValueChanged(this.shouldStoreItemValue); // Grouping is supported for objects only. // Ionic VirtualScroll has it's own implementation of grouping. this.hasGroups = Boolean(this.hasObjects && (this.groupValueField || this.groupTextField) && !this.hasVirtualScroll); /* It's important to have an empty starting group with empty items (groups[0].items), * because we bind to it when using VirtualScroll. * See https://github.com/eakoriakin/ionic-selectable/issues/70. */ let groups = [ { items: items || [], }, ]; if (items && items.length) { if (this.hasGroups) { groups = []; items.forEach((item) => { const groupValue = this.getPropertyValue(item, this.groupValueField || this.groupTextField); const group = groups.find((_group) => _group.value === groupValue); if (group) { group.items.push(item); } else { groups.push({ value: groupValue, text: this.getPropertyValue(item, this.groupTextField), items: [item], }); } }); } } this.groups = groups; this.filteredGroups = this.groups; this.hasFilteredItems = !this.areGroupsEmpty(this.filteredGroups); if (this.hasVirtualScroll) { // Rerender Virtual Scroll List After Adding New Data (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.virtualScrollElement.checkEnd(); } (_b = this.selectableModalComponent) === null || _b === void 0 ? void 0 : _b.update(); if (this.isInited) { this.emitItemsChanged(); } } filterItems(searchText, isChangeInternal = true) { var _a; this.isChangeInternal = isChangeInternal; this.setHasSearchText(searchText); if (this.shouldDelegateSearchToEvent) { // Delegate filtering to the event. this.emitSearching(); } else { // Default filtering. let groups = []; if (this.searchText === '') { groups = this.groups; } else { this.groups.forEach((group) => { const items = group.items.filter((item) => { const itemText = (this.itemTextField ? item[this.itemTextField] : item).toString().toLowerCase(); return itemText.indexOf(this.searchText.trim().toLowerCase()) !== -1; }); if (items.length) { groups.push({ value: group.value, text: group.text, items: items, }); } }); // No items found. if (!groups.length) { groups.push({ items: [], }); } } this.filteredGroups = groups; this.hasFilteredItems = !this.areGroupsEmpty(groups); this.emitOnSearchSuccessedOrFailed(this.hasFilteredItems); (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } } addSelectedItem(item) { var _a; const exist = this.selectedItems.find((_item) => this.getItemValue(item) === this.getStoredItemValue(_item)); if (!exist) { this.selectedItems.push(this.getItem(item)); } (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } deleteSelectedItem(item) { var _a; let itemToDeleteIndex; this.selectedItems.forEach((selectedItem, itemIndex) => { if (this.getItemValue(item) === this.getStoredItemValue(selectedItem)) { itemToDeleteIndex = itemIndex; } }); this.selectedItems.splice(itemToDeleteIndex, 1); (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } getItem(item) { if (!this.hasObjects) { return item; } return this.shouldStoreItemValue ? item[this.itemValueField] : item; } getItemValue(item) { if (!this.hasObjects) { return item; } return item[this.itemValueField]; } getStoredItemValue(item) { if (!this.hasObjects) { return item; } return this.shouldStoreItemValue ? item : item[this.itemValueField]; } toggleAddItemTemplate(isVisible) { var _a; // It should be possible to show/hide the template regardless // - canAddItem or canSaveItem parameters, so we could implement some // - custom behavior. E.g. adding item when search fails using onSearchFail event. if (this.hasTemplateRender && this.hasTemplateRender('addItem')) { // To make SaveItemTemplate visible we just position it over list using CSS. // We don't hide list with *ngIf or [hidden] to prevent its scroll position. this.isAddItemTemplateVisible = isVisible; this.isFooterVisible = !isVisible; (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } } emitSelected(item, isSelected) { this.selected.emit(new IonicSelectableSelectedEvent(item, isSelected, this.element)); } emitChanged() { this.changed.emit(new IonicSelectableChangedEvent(this.valueItems, this.element)); } emitOpened() { this.opened.emit(new IonicSelectableOpenedEvent(this.valueItems, this.element)); } emitClosed() { this.closed.emit(new IonicSelectableClosedEvent(this.valueItems, this.element)); } emitCleared() { this.cleared.emit(new IonicSelectableClearedEvent(this.selectedItems, this.element)); } emitItemAdding() { this.itemAdding.emit(new IonicSelectableItemAddingEvent(this.valueItems, this.element)); } emitItemsChanged() { this.itemsChanged.emit({ component: this.element, value: this.items }); } emitSearching() { this.searching.emit(new IonicSelectableSearchingEvent(this.searchText, this.element)); } emitIonInfiniteScrolled() { this.infiniteScrolled.emit(new IonicSelectableInfiniteScrolledEvent(this.searchText, this.element)); } emitOnSearchSuccessedOrFailed(isSuccess) { if (isSuccess) { this.searchSuccessed.emit(new IonicSelectableSearchSuccessedEvent(this.searchText, this.element)); } else { this.searchFailed.emit(new IonicSelectableSearchFailedEvent(this.searchText, this.element)); } } isNullOrWhiteSpace(value) { if (value === null || value === undefined) { return true; } // Convert value to string in case if it's not. return value.toString().replace(/\s/g, '').length < 1; } setHasSearchText(searchText) { this.hasSearchText = !this.isNullOrWhiteSpace(searchText); if (this.hasSearchText) { this.searchText = searchText.trim(); } else { this.searchText = ''; } } countFooterButtons() { var _a; let footerButtonsCount = 0; if (this.canClear) { footerButtonsCount++; } if (this.isMultiple || this.hasConfirmButton) { footerButtonsCount++; } if (this.canAddItem) { footerButtonsCount++; } this.footerButtonsCount = footerButtonsCount; (_a = this.selectableModalComponent) === null || _a === void 0 ? void 0 : _a.update(); } areGroupsEmpty(groups) { return (groups.length === 0 || groups.every((group) => { return !group.items || group.items.length === 0; })); } getItemText(item) { if (!this.hasObjects) { return item !== null && item !== void 0 ? item : ''; } return this.getPropertyValue(item, this.itemTextField); } getPropertyValue(object, property) { if (!property) { return ''; } return property.split('.').reduce((_object, _property) => { return _object ? _object[_property] : null; }, object); } parseValue() { return JSON.stringify(this.valueItems); } generateText() { if (Array.isArray(this.valueItems)) { return this.valueItems .map((_item) => { const itemFind = this.items.find((item) => this.getItemValue(item) === this.getStoredItemValue(_item)); return itemFind ? this.getItemText(itemFind) : ''; }) .filter((opt) => opt !== null) .join(', '); } else { const itemFind = this.items.find((item) => this.getItemValue(item) === this.getStoredItemValue(this.valueItems)); return itemFind ? this.getItemText(itemFind) : ''; } } getText() { const selectedText = this.selectedText; if (selectedText != null && selectedText !== '') { return selectedText; } return this.generateText(); } async emitStyle() {