scripts/django_cradmin/widgets/AbstractSelectWidget.jsx | django_cradmin API Document

# scripts/django_cradmin/widgets/AbstractSelectWidget.jsx

    import React from "react";
    import ReactDOM from "react-dom";
    import AbstractWidget from "ievv_jsbase/widget/AbstractWidget";
    import HttpDjangoJsonRequest from 'ievv_jsbase/http/HttpDjangoJsonRequest';

    export default class AbstractSelectWidget extends AbstractWidget {
      constructor(element, widgetInstanceId) {
        super(element, widgetInstanceId);
        this.logger = new window.ievv_jsbase_core.LoggerSingleton().getLogger(
          'django_cradmin.widgets.AbstractSelectWidget');
        this.onSelectResultSignal = this.onSelectResultSignal.bind(this);
        this.onSearchRequestedSignal = this.onSearchRequestedSignal.bind(this);

        this._uniquePrefix = `django_cradmin.Select.${this.widgetInstanceId}`;
        this._searchRequestedSignalName = `${this._uniquePrefix}.SearchRequested`;
        this._searchCompletedSignalName = `${this._uniquePrefix}.SearchCompleted`;
        this._selectResultSignalName = `${this._uniquePrefix}.SelectResult`;

        this.initialValue = this._getInitialValue();
        this.selectedValue = this.initialValue;
        this.logger.debug(`initialValue: "${this.initialValue}"`);
        this._initializeSignalHandlers();
        if(this.initialValue != null) {
          this._loadPreviewForInitialValue();
        } else {
          this._updateUiForEmptyValue();
        }
      }

      getDefaultConfig() {
        return {
          valueAttribute: 'id',
          fetchEmptySearchOnLoad: false,
          toggleElementsOnValueChange: {
            loading: [],
            hasValue: [],
            noValue: []
          },
          updateInnerHtmlWithResult: {},
          clientsideSearch: {},
          searchApi: {
            url: null,
            staticData: {}
          },
          componentProps: {
            search: {},
            resultList: {},
            result: {}
          }
        }
      }

      _getInitialValue() {
        let initialValue = null;
        if(this.config.valueTargetInputId) {
          initialValue = document.getElementById(this.config.valueTargetInputId).value;
        } else if (this.config.initialValue) {
          initialValue = this.config.initialValue;
        }
        if(initialValue == undefined || initialValue == '') {
          initialValue = null;
        }
        return initialValue;
      }

      _initializeSignalHandlers() {
        new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
          this._searchRequestedSignalName,
          'django_cradmin.widgets.AbstractSelectWidget',
          this.onSearchRequestedSignal
        );
        new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
          this._selectResultSignalName,
          'django_cradmin.widgets.AbstractSelectWidget',
          this.onSelectResultSignal
        );
      }

      destroy() {
        this.element.removeEventListener('click', this._onClick);
        new window.ievv_jsbase_core.SignalHandlerSingleton().removeReceiver(
          this._searchRequestedSignalName,
          'django_cradmin.widgets.AbstractSelectWidget'
        );
        new window.ievv_jsbase_core.SignalHandlerSingleton().removeReceiver(
          this._selectResultSignalName,
          'django_cradmin.widgets.AbstractSelectWidget'
        );
        if(this._reactWrapperElement) {
          ReactDOM.unmountComponentAtNode(this._reactWrapperElement);
          this._reactWrapperElement.remove();
        }
      }

      _setValueTargetValue(value) {
        if(this.config.valueTargetInputId) {
          document.getElementById(this.config.valueTargetInputId).value = value;
        }
      }

      _hideElementById(domId) {
        const element = document.getElementById(domId);
        if(element) {
          element.setAttribute('style', 'display: none');
        }
      }

      _showElementById(domId) {
        const element = document.getElementById(domId);
        if(element) {
          element.setAttribute('style', 'display: block');
        }
      }

      _hideElementsById(domIdArray) {
        for(let domId of domIdArray) {
          this._hideElementById(domId);
        }
      }

      _showElementsById(domIdArray) {
        for(let domId of domIdArray) {
          this._showElementById(domId);
        }
      }

      _updatePreviews(resultObject) {
        for(let attribute of Object.keys(this.config.updateInnerHtmlWithResult)) {
          let domIds = this.config.updateInnerHtmlWithResult[attribute];
          let value = resultObject[attribute];
          if(value != undefined && value != null) {
            for(let domId of domIds) {
              const element = document.getElementById(domId);
              if(element) {
                element.innerHTML = value;
              }
            }
          }
        }
      }

      _getValueFromResultObject(resultObject) {
        return resultObject[this.config.valueAttribute];
      }

      _setLoading() {
        this._hideElementsById(this.config.toggleElementsOnValueChange.hasValue);
        this._hideElementsById(this.config.toggleElementsOnValueChange.noValue);
        this._showElementsById(this.config.toggleElementsOnValueChange.loading);
      }

      _updateUiForEmptyValue() {
        this.selectedValue = null;
        this._hideElementsById(this.config.toggleElementsOnValueChange.hasValue);
        this._hideElementsById(this.config.toggleElementsOnValueChange.loading);
        this._showElementsById(this.config.toggleElementsOnValueChange.noValue);
        this._setValueTargetValue('');
      }

      _updateUiFromResultObject(resultObject) {
        let value = this._getValueFromResultObject(resultObject);
        this.selectedValue = value;
        this._hideElementsById(this.config.toggleElementsOnValueChange.noValue);
        this._hideElementsById(this.config.toggleElementsOnValueChange.loading);
        this._showElementsById(this.config.toggleElementsOnValueChange.hasValue);
        this._setValueTargetValue(value);
        this._updatePreviews(resultObject);
      }

      onSelectResultSignal(receivedSignalInfo) {
        this.logger.debug(receivedSignalInfo.toString());
        let resultObject = receivedSignalInfo.data;
        if(resultObject == null) {
          this._updateUiForEmptyValue();
        } else {
          this._updateUiFromResultObject(resultObject);
        }
      }

      _useServerSideSearch() {
        return this.config.searchApi.url != undefined && this.config.searchApi.url != null;
      }

      onSearchRequestedSignal(receivedSignalInfo) {
        this.logger.debug(receivedSignalInfo.toString());
        const searchString = receivedSignalInfo.data;
        this.requestSearchResults(searchString)
          .then((results) => {
            this.sendSearchCompletedSignal(results);
          })
          .catch((error) => {
            throw error;
          });
      }

      sendSearchCompletedSignal(results) {
        this.logger.debug('Search complete. Result:', results);
        new window.ievv_jsbase_core.SignalHandlerSingleton().send(
          this._searchCompletedSignalName,
          results
        );
      }

      _isClientSideSearchMatch(searchString, resultObject) {
        for(let attribute of this.config.clientsideSearch.searchAttributes) {
          if(resultObject[attribute] != undefined && resultObject[attribute] != null) {
            if(resultObject[attribute].toLowerCase().indexOf(searchString) != -1) {
              return true;
            }
          }
        }
        return false;
      }

      _requestClientSideSearchResults(searchString) {
        return new Promise((resolve, reject) => {
          const resultObjectArray = [];
          searchString = searchString.toLowerCase();
          for (let resultObject of this.config.clientsideSearch.data) {
            if (this._isClientSideSearchMatch(searchString, resultObject)) {
              resultObjectArray.push(resultObject);
            }
          }
          resolve({
            count: resultObjectArray.size,
            results: resultObjectArray
          });
        });
      }

      _requestServerSideSearchResults(searchString) {
        return new Promise((resolve, reject) => {
          const request = new HttpDjangoJsonRequest(this.config.searchApi.url);
          for (let attribute of Object.keys(this.config.searchApi.staticData)) {
            request.urlParser.queryString.set(
              attribute, this.config.searchApi.staticData[attribute]);
          }
          request.urlParser.queryString.set('search', searchString);
          request.get()
            .then((response) => {
              resolve(response.bodydata);
            })
            .catch((error) => {
              reject(error);
            });
        });
      }

      requestSearchResults(searchString='') {
        if(this._useServerSideSearch()) {
          return this._requestServerSideSearchResults(searchString);
        } else {
          return this._requestClientSideSearchResults(searchString);
        }
      }

      _requestSingleResultServerSide(value) {
        return new Promise((resolve, reject) => {
          let url = this.config.searchApi.url;
          if(!this.config.searchApi.url.endsWith('/')) {
            url = `${url}/`;
          }
          url = `${url}${value}`;
          const request = new HttpDjangoJsonRequest(url);
          request.get()
            .then((response) => {
              resolve(response.bodydata);
            })
            .catch((error) => {
              reject(error);
            });
        });
      }

      _requestSingleResultClientSide(value) {
        return new Promise((resolve, reject) => {
          for(let resultObject of this.config.clientsideSearch.data) {
            if(this._getValueFromResultObject(resultObject) == value) {
              resolve(resultObject);
            }
          }
          reject(new Error(
            `config.clientsideSearch.data does not contain an ` +
            `object with ${this.config.valueAttribute} = "${value}"`
          ));
        });
      }

      _requestSingleResult(value) {
        if(this._useServerSideSearch()) {
          return this._requestSingleResultServerSide(value);
        } else {
          return this._requestSingleResultClientSide(value);
        }
      }

      _loadPreviewForInitialValue() {
        this._setLoading();
        this._requestSingleResult(this.initialValue)
          .then((resultObject) => {
            this.logger.debug(`Loaded data for initialValue ("${this.initialValue}"):`, resultObject);
            this._updateUiFromResultObject(resultObject);
          })
          .catch((error) => {
            this.logger.error(
              `Failed to load data for initialValue: "${this.initialValue}". ` +
              `Error details: ${error.toString()}`);
          });
      }

      addReactWrapperElementToDocument(reactWrapperElement) {
        this.element.parentNode.insertBefore(reactWrapperElement, this.element.nextSibling);
      }

      initializeReactComponent() {
        this._reactWrapperElement = document.createElement('div');
        this.addReactWrapperElementToDocument(this._reactWrapperElement);
        const reactElement = this.makeReactElement();
        ReactDOM.render(
          reactElement,
          this._reactWrapperElement
        );
        if(this.config.fetchEmptySearchOnLoad) {
        this.requestSearchResults()
          .then((results) => {
            this.sendSearchCompletedSignal(results);
          })
          .catch((error) => {
            throw error;
          });
        }
      }

      makeReactComponentProps() {
        return {
          closeCallback: this._onClose,
          selectResultSignalName: this._selectResultSignalName,
          searchCompletedSignalName: this._searchCompletedSignalName,
          searchRequestedSignalName: this._searchRequestedSignalName,
          valueAttribute: this.config.valueAttribute,
          searchComponentProps: this.config.componentProps.search,
          resultListComponentProps: this.config.componentProps.resultList,
          resultComponentProps: this.config.componentProps.result,
          selectedValue: this.selectedValue
        }
      }

      makeReactElement() {
        throw new Error('You must override makeReactElement()');
      }
    }