Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/widget/src/address/ |
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/widget/src/address/address.js |
import {Type, Event} from 'main.core'; import {EventEmitter} from 'main.core.events'; import { Address as AddressEntity, ControlMode, Format, AddressStringConverter, LocationRepository, ErrorPublisher, FormatTemplateType, AddressType, Storage } from 'location.core'; import State from '../state'; import BaseFeature from './features/basefeature'; import {FeatureEvent} from './featurevent'; /** * Props for the address widget constructor */ export type AddressConstructorProps = { /** @see ControlMode */ mode: string, addressFormat: Format, address?: AddressEntity, needWarmBackendAfterAddressChanged?: boolean, locationRepository?: LocationRepository, }; /** * Props for the address widget render method */ export type AddressRenderProps = { /** Input control witch will be used by user to enter the address */ inputNode: Element, /** Control wrapper witch could be used for mouseover event etc. */ controlWrapper: Element, /** If map feature is used it could be used to bind map popup */ mapBindElement: ?Element, /** If autocomplete feature is used it could be used to bind menu node */ autocompleteMenuElement: ?Element }; /** * Address widget */ export default class Address extends EventEmitter { /* If address was changed by user */ static onAddressChangedEvent = 'onAddressChanged'; /* If state of the widget was changed */ static onStateChangedEvent = 'onStateChanged'; /* Any feature-related events */ static onFeatureEvent = 'onFeatureEvent'; #mode; #state; #address; #addressFormat; #languageId; #features = []; #inputNode; #controlWrapper; #destroyed = false; #isAddressChangedByFeature = false; #isInputNodeValueUpdated = false; #needWarmBackendAfterAddressChanged = true; #locationRepository; /** * Constructor * @param {AddressConstructorProps} props */ constructor(props: AddressConstructorProps) { super(); this.setEventNamespace('BX.Location.Widget.Address'); if (!(props.addressFormat instanceof Format)) { BX.debug('addressFormat must be instance of Format'); } this.#addressFormat = props.addressFormat; if (props.address && !(props.address instanceof AddressEntity)) { BX.debug('address must be instance of Address'); } this.#address = props.address || null; if (!(ControlMode.isValid(props.mode))) { BX.debug('mode must be valid ControlMode'); } this.#mode = props.mode; if (!Type.isString(props.languageId)) { throw new TypeError('props.languageId must be type of string'); } this.#languageId = props.languageId; if (props.features) { if (!Type.isArray(props.features)) { throw new TypeError('features must be an array'); } props.features.forEach((feature: BaseFeature) => { this.#addFeature(feature); }); } if (Type.isBoolean(props.needWarmBackendAfterAddressChanged)) { this.#needWarmBackendAfterAddressChanged = props.needWarmBackendAfterAddressChanged; } if (props.locationRepository instanceof LocationRepository) { this.#locationRepository = props.locationRepository; } else if (this.#needWarmBackendAfterAddressChanged) { this.#locationRepository = new LocationRepository(); } this.#state = State.INITIAL; } /** * @param {AddressEntity} address * @param {BaseFeature} sourceFeature * @param {Array} excludeFeatures * @param {Object} options * @internal */ setAddressByFeature( address: AddressEntity, sourceFeature: BaseFeature, excludeFeatures: Array = [], options: Object = {} ): void { const addressId = this.#address ? this.#address.id : 0; if ( address && !address.getFieldValue(AddressType.ADDRESS_LINE_1) && this.#addressFormat.isTemplateExists(FormatTemplateType.ADDRESS_LINE_1) ) { address.setFieldValue( AddressType.ADDRESS_LINE_1, AddressStringConverter.convertAddressToStringTemplate( address, this.#addressFormat.getTemplate(FormatTemplateType.ADDRESS_LINE_1), AddressStringConverter.CONTENT_TYPE_TEXT, null, this.#addressFormat ) ); } this.#address = address; const storeAsLastAddress = options.hasOwnProperty('storeAsLastAddress') ? options.storeAsLastAddress : true; if (storeAsLastAddress) { this.#storeAsLastAddress(); } if (addressId > 0) { this.#address.id = addressId; } this.#isAddressChangedByFeature = true; this.#setInputValue(address); this.#executeFeatureMethod( 'setAddress', [address], sourceFeature, excludeFeatures ); if (this.#state !== State.DATA_INPUTTING) { this.#emitOnAddressChanged(); } } emitFeatureEvent(featureEvent: FeatureEvent) { this.emit( Address.onFeatureEvent, featureEvent ); } /** * Add feature to the widget * @param {BaseFeature} feature */ #addFeature(feature: BaseFeature) { if (!(feature instanceof BaseFeature)) { BX.debug('feature must be instance of BaseFeature'); } feature.setAddressWidget(this); this.#features.push(feature); } get features() { return this.#features; } #executeFeatureMethod(method, params = [], sourceFeature = null, excludeFeatures = []) { let result; for(const feature of this.#features) { let isExcluded = false; for(const excludeFeature of excludeFeatures) { if (feature instanceof excludeFeature) { isExcluded = true; break; } } if (!isExcluded && feature !== sourceFeature) { result = feature[method].apply(feature, params); } } return result; } #emitOnAddressChanged() { this.emit( Address.onAddressChangedEvent, {address: this.#address} ); if (this.#address && this.#needWarmBackendAfterAddressChanged) { this.#warmBackendAfterAddressChanged(this.#address); } } #warmBackendAfterAddressChanged(address: AddressEntity): void { if (address.location !== null && address.location.id <= 0) { this.#locationRepository.findParents(address.location); } } // eslint-disable-next-line no-unused-vars #onInputFocus(e: KeyboardEvent) { const value = this.#inputNode.value; if (value.length > 0) { BX.setCaretPosition(this.#inputNode, value.length); } } #convertAddressToString( address: ?Address, templateType: string ): string { let result = ''; if (address) { if (!this.#addressFormat.isTemplateExists(templateType)) { console.error(`Address format "${this.#addressFormat.code}" does not have a template "${templateType}"`); return ''; } result = AddressStringConverter.convertAddressToStringTemplate( address, this.#addressFormat.getTemplate(templateType), AddressStringConverter.CONTENT_TYPE_TEXT, ', ', this.#addressFormat ); } return result; } #setInputValue(address: ?Address) { if (this.#inputNode) { const shortAddressString = this.#convertAddressToString(address, FormatTemplateType.AUTOCOMPLETE); const fullAddressString = this.#convertAddressToString(address, FormatTemplateType.DEFAULT); this.#inputNode.value = shortAddressString.trim() !== '' ? shortAddressString : fullAddressString; this.#inputNode.title = fullAddressString; const selectionStart = this.#inputNode.selectionStart; const selectionEnd = shortAddressString.length; this.#inputNode.setSelectionRange(selectionStart, selectionEnd); } } // eslint-disable-next-line no-unused-vars #onInputFocusOut(e: Event) { // Seems that we don't have any autocompleter feature if (this.#isInputNodeValueUpdated && !this.#isAddressChangedByFeature) { const value = this.#inputNode.value.trim(); const address = new AddressEntity({languageId: this.#languageId}); address.setFieldValue(this.#addressFormat.fieldForUnRecognized, value); this.address = address; this.#emitOnAddressChanged(); } this.#isInputNodeValueUpdated = false; this.#isAddressChangedByFeature = false; } onInputKeyup(e: KeyboardEvent) { switch (e.code) { case 'Tab': case 'Esc': case 'Enter': case 'NumpadEnter': this.resetView(); break; } } onInputInput(e) { this.#isInputNodeValueUpdated = true; } resetView(): void { this.#executeFeatureMethod('resetView'); } /** * Render Widget * @param {AddressRenderProps} props */ render(props: AddressRenderProps): void { if (!Type.isDomNode(props.controlWrapper)) { BX.debug('props.controlWrapper must be instance of Element'); } this.#controlWrapper = props.controlWrapper; if (this.#mode === ControlMode.edit) { if (!Type.isDomNode(props.inputNode)) { BX.debug('props.inputNode must be instance of Element'); } this.#inputNode = props.inputNode; this.#setInputValue(this.#address); } this.#executeFeatureMethod('render', [props]); // We can prevent these events in features if need if (this.#mode === ControlMode.edit) { Event.bind(this.#inputNode, 'focus', this.#onInputFocus.bind(this)); Event.bind(this.#inputNode, 'focusout', this.#onInputFocusOut.bind(this)); Event.bind(this.#inputNode, 'keyup', this.onInputKeyup.bind(this)); Event.bind(this.#inputNode, 'input', this.onInputInput.bind(this)); } } get controlWrapper() { return this.#controlWrapper; } get inputNode() { return this.#inputNode; } get address(): ?AddressEntity { return this.#address; } set address(address: ?AddressEntity): void { if (address && !(address instanceof AddressEntity)) { BX.debug('address must be instance of Address'); } this.#address = address; this.#storeAsLastAddress(); this.#executeFeatureMethod('setAddress', [address]); this.#isInputNodeValueUpdated = false; this.#isAddressChangedByFeature = false; this.#setInputValue(address); } get mode() { return this.#mode; } set mode(mode: string): void { if (!(ControlMode.isValid(mode))) { BX.debug('mode must be valid ControlMode'); } this.#mode = mode; this.#executeFeatureMethod('setMode', [mode]); } get state(): string { return this.#state; } get addressFormat(): Format { return this.#addressFormat; } setStateByFeature(state: string) { this.#state = state; this.emit( Address.onStateChangedEvent, {state: state} ); } subscribeOnStateChangedEvent(listener: Function): void { this.subscribe(Address.onStateChangedEvent, listener); } subscribeOnAddressChangedEvent(listener: Function): void { this.subscribe(Address.onAddressChangedEvent, listener); } subscribeOnFeatureEvent(listener: Function): void { this.subscribe(Address.onFeatureEvent, listener); } subscribeOnErrorEvent(listener: Function): void { ErrorPublisher.getInstance().subscribe(listener); } #storeAsLastAddress() { if ( this.#address && this.#address.fieldCollection && this.#address.fieldCollection.isFieldExists(AddressType.LOCALITY) ) { Storage.getInstance().lastAddress = this.#address; } } destroy() { if (this.#destroyed) { return; } Event.unbindAll(this); Event.unbind(this.#inputNode, 'focus', this.#onInputFocus); Event.unbind(this.#inputNode, 'focusout', this.#onInputFocusOut); Event.unbind(this.#inputNode, 'keyup', this.onInputKeyup); Event.unbind(this.#inputNode, 'input', this.onInputInput); this.#executeFeatureMethod('destroy'); this.#destroyFeatures(); this.#destroyed = true; } #destroyFeatures() { this.#features.splice(0, this.#features.length); } isDestroyed() { return this.#destroyed; } }