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
JavaScript
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() {