view class doc
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 2023. All Rights Reserved.
/// * Trademark of Four Js Development Tools Europe Ltd
///   in the United States and elsewhere
///
/// This file can be modified by licensees according to the
/// product manual.
/// FOURJS_END_COPYRIGHT

'use strict';

modulum('DateEditWidgetBase', ['FieldWidgetBase', 'WidgetFactory'],
  function(context, cls) {

    /**
     * DateEdit widget Base class.
     * @class DateEditWidgetBase
     * @memberOf classes
     * @extends classes.FieldWidgetBase
     * @publicdoc Widgets
     */
    cls.DateEditWidgetBase = context.oo.Class(cls.FieldWidgetBase, function($super) {
      return /** @lends classes.DateEditWidgetBase.prototype */ {
        __name: 'DateEditWidgetBase',

        /**
         * @inheritDoc
         */
        __dataContentPlaceholderSelector: '.gbc_dataContentPlaceholder',

        /**
         * Format used to display dates of the calendar
         * @type {?string}
         */
        _displayFormat: null,

        /**
         * Get list of sorted days depending of first day of the week which is defined
         * @type {Array}
         */
        _sortedDays: null,

        /**
         * Callback function used to disable days of calendar (framework api)
         * @type {function}
         */
        _disableDayFn: null,

        /**
         * Chinese format for date
         * @type {boolean}
         */
        _useMingGuoYears: false,

        /**
         * Current date dayjs object of the widget
         * @type {Object}
         */
        _dateObj: null,

        /**
         * Last valid date
         * @type {?string}
         */
        _validValue: null,

        /**
         * Indicates if current date of the calendar has been validated by user
         * @type {boolean}
         */
        _mustValid: false,
        /**
         * Max length of the input field
         * @type {number}
         */
        _maxLength: -1,
        /**
         * To detect if we selected a date using keyboard or mouse
         * @type {boolean}
         */
        _keyPressed: false,
        /**
         * Save last clicked date value. Needed to detect double click
         * @type {?string}
         */
        _lastClickValue: null,
        /**
         * Picker icon used to display/hide calendar
         * @type {HTMLElement}
         */
        _pikerIcon: null,

        /** @type {string|null} */
        _defaultTTFColor: null,

        /**
         * @inheritDoc
         */
        _initLayout: function() {
          if (!this._ignoreLayout) {
            this._layoutInformation = new cls.LayoutInformation(this);
            this._layoutEngine = new cls.LeafLayoutEngine(this);
            this._layoutInformation.setSingleLineContentOnly(true);
            this._layoutInformation.setReservedDecorationSpace(2);
          }
        },

        /**
         * @inheritDoc
         */
        _initElement: function() {
          $super._initElement.call(this);
          if (this._displayFormat === null) {
            this._displayFormat = 'MM/DD/YYYY'; //default format
          }

          this._inputElement = this._element.getElementsByTagName('input')[0];
          this._pikerIcon = this._element.getElementsByTagName('i')[0];

          this._inputElement.on('input.DateEditWidgetBase', this._onInput.bind(this));
        },

        /**
         * @inheritDoc
         */
        destroy: function() {
          this._pikerIcon = null;
          if (this._inputElement) {
            this._inputElement.off('input.DateEditWidgetBase');
            this._inputElement.remove();
            this._inputElement = null;
          }

          $super.destroy.call(this);
        },

        /**
         * @inheritDoc
         */
        setTextAlign: function(align) {
          this.setStyle('input', {
            'text-align': align
          });
        },

        /**
         * @inheritDoc
         */
        getTextAlign: function() {
          return this.getStyle('input', 'text-align');
        },

        /**
         * Reset input field with last valid date
         */
        setLastValidValue: function() {
          if (this._inputElement) {
            this._inputElement.value = this._validValue;
          }
        },

        /**
         * @inheritDoc
         */
        setReadOnly: function(readonly) {
          $super.setReadOnly.call(this, readonly);
          this._setInputReadOnly(readonly);
        },

        /**
         * Set input readonly attribute if it doesn't have focus or is noentry.
         * @param {boolean} readonly - true to set the edit part as read-only, false otherwise
         */
        _setInputReadOnly: function(readonly) {
          if (readonly) {
            this._inputElement.setAttribute('readonly', 'readonly');
            if (window.browserInfo.isIE || window.browserInfo.isEdge) {
              this._inputElement.removeAttribute('contentEditable');
            }
          } else {
            this._inputElement.removeAttribute('readonly');
            if (window.browserInfo.isIE || window.browserInfo.isEdge) {
              // need to add this to be sure that user can enter text in the field
              // even if setCursorPosition is called before
              this._inputElement.setAttribute('contentEditable', 'true');
            }
          }
        },

        /**
         * Get the current dateedit value
         * @returns {string} the displayed value
         */
        getValue: function() {
          return this._inputElement.value;
        },

        /**
         * @inheritDoc
         */
        setValue: function(dateString, fromVM) {
          $super.setValue.call(this, dateString, fromVM);
          this.setDate(dateString);
        },

        /**
         * Return the current Date object
         * @returns {Object} returns current dayjs date object
         * @publicdoc
         */
        getDate: function() {
          return this._dateObj && this._dateObj.isValid() ? this._dateObj.format(this._displayFormat) : "Invalid date";
        },

        /**
         * Generate dayjs date object from a string and set it for both the calendar component and the input field
         * @param {string} date - date value in string format
         * @publicdoc
         */
        setDate: function(date) {
          // created date object based on received value using known format (for datepicker)
          if (this._useMingGuoYears) { // Convert Ming Guo year to 4 digit years for datepicker
            var str = cls.DateTimeHelper.mingGuoToGregorianYears(date);
            this._dateObj = context.dayjs(str, this._displayFormat, true);
          } else {
            this._dateObj = context.dayjs(date, this._displayFormat, true);
          }

          // set non formatted value to input (already formatted by VM depending)
          if (this.getValue() !== date) {
            this._inputElement.value = date;
          }

          this._elementState.backup(this._inputElement);
        },

        /**
         * Set a specified format of date. Default is MM/DD/YYYY
         * @param {string} format - date format used to display and send date to the VM.
         * @publicdoc
         */
        setFormat: function(format) {
          var years = format && format.match(/Y/g);
          if (years && years.length === 3) { // Ming Guo format
            this._useMingGuoYears = true;
            format = format.replace('YYY', 'YYYY');
          }
          if (this._displayFormat !== format) {
            this._displayFormat = format;
          }
        },

        /**
         * Return date format
         * @returns {string} the date format
         * @publicdoc
         */
        getFormat: function() {
          return this._displayFormat;
        },

        /**
         * Get cursors
         * @return {{start: number, end: number}} object with cursors
         * @publicdoc
         */
        getCursors: function() {
          var cursors = {
            start: 0,
            end: 0
          };
          if (this._inputElement && this._inputElement.value) {
            try {
              cursors.start = this._inputElement.selectionStart;
              cursors.end = this._inputElement.selectionEnd;
            } catch (ignore) {
              // Some input types don't allow cursor manipulation
            }
          }
          return cursors;
        },

        /** Place the cursor at the given position,
         * @param {number} cursor - first cursor position
         * @param {number=} cursor2 - second cursor position
         * @publicdoc
         */
        setCursors: function(cursor, cursor2) {
          if (!cursor2) {
            cursor2 = cursor;
          }
          if (cursor2 && cursor2 < 0) {
            cursor2 = this.getValue() && this.getValue().length || 0;
          }
          this._inputElement.setCursorPosition(cursor, cursor2);
        },

        /**
         * @inheritDoc
         */
        setTitle: function(title) {
          this._inputElement.setAttribute('title', title);
        },

        /**
         * @inheritDoc
         */
        getTitle: function() {
          return this._inputElement.getAttribute('title');
        },

        /**
         * @inheritDoc
         */
        setFocus: function(fromMouse) {
          $super.setFocus.call(this, fromMouse);
          this._inputElement.domFocus();
        },

        /**
         * @inheritDoc
         */
        setEnabled: function(enabled) {
          $super.setEnabled.call(this, enabled);
          this._setInputReadOnly(!enabled);
        },

        /**
         * Define the maximum number of characters allowed
         * @param {number} maxlength - maximum number of characters allowed in the field
         * @publicdoc
         */
        setMaxLength: function(maxlength) {
          if (maxlength) {
            this._maxLength = maxlength;
            this._setElementAttribute('maxlength', maxlength + 1, "_inputElement");
          }
        },

        /**
         * Get the maximum number of characters allowed
         * @returns {number} the maximum number of characters allowed in the field
         * @publicdoc
         */
        getMaxLength: function() {
          return this._maxLength;
        },

        /**
         * Set Default color (defined by DefaultTTF)
         * @param {string} color - rgb formatted or css name
         */
        setDefaultColor: function(color) {
          this._defaultTTFColor = color;

          this.setStyle(".zmdi", {
            'color': color
          });
        },

        /**
         * Defines a different image for the icon of the DateEdit
         * @param {string} icon - src of the image
         */
        setButtonIcon: function(icon) {
          if (icon) {
            var img = cls.WidgetFactory.createWidget('ImageWidget', this.getBuildParameters());
            var imgElem = img.getElement();

            if (this._defaultTTFColor) {
              img.setDefaultColor(this._defaultTTFColor);
            }

            img.setSrc(context.__wrapper.wrapResourcePath(icon), true);
            img.setTitle("Open picker");
            this._pikerIcon.classList.remove('zmdi', 'zmdi-calendar-blank', 'zmdi-calendar-clock');
            this.domAttributesMutator(function() {
              this._pikerIcon.innerHTML = imgElem.innerHTML;
              img.destroy();
            }.bind(this));
          }
        },

        /**
         * Verify the display width, the max length and the encoding according to the char/byte length semantics,
         * the width/max length of the widget and vm encoding
         * @param {Object} domEvent
         * @private
         */
        _verifyWidgetValue: function(domEvent) {
          //Manage display char (full/half) and length semantics constraints
          var widgetText = this._elementState.getValue();
          var inputValue = this._inputElement.value;

          if (widgetText.length >= inputValue.length) {
            this._elementState.backup(this._inputElement);
            //Less char in edit so no need to verify autonext
            return;
          } else {
            var newPart = this._elementState.newPart(inputValue);
            //If you need to make some char replacement do something like:
            //newPart = newPart.replace(....)
            var res = '';
            //On mobile isComposing=1 even for regular char
            if (!window.isMobile() && domEvent.isComposing) {
              //When composing we can use invalid char to create a valid one
              //No need to verify autonext
              return;
            } else {
              res = this._checkValue(widgetText, newPart.removeUnknownChar());
            }

            if (res !== newPart) {
              this._elementState.restore(this._inputElement, res);
              this.setValue(this._inputElement.value);
              //Set restored to true to not trigger the autonext
              this._elementState.setRestored(true);
              return;
            }
          }
        },

        /**
         * @inheritDoc
         */
        _onInput: function(event) {
          $super._onInput.call(this);

          //Keyboard events are managed by manageKeyUp method
          if (!this._processingKeyEvent) {
            this._verifyWidgetValue(event);
            //If value is restored no need to emit Key up to trigger the autonext
            if (!this._elementState.isRestored()) {
              this.emit(context.constants.widgetEvents.keyUp, event, true);
            }
          }
        },

        /**
         * Fix the newTextPart according to byte/char length and display width
         * @param  {string} text - widget valid value
         * @param {string} newTextPart - new text part
         * @return {string} a valid newTextPart
         * @private
         */
        _checkValue: function(text, newTextPart) {
          if (this._dialogType !== 'Input' && this._dialogType !== 'InputArray') {
            return newTextPart;
          }

          newTextPart = this.checkValueDisplayWidth(text, newTextPart);

          return newTextPart;
        },

        /**
         * @return {boolean} true if we can trigger autonext event
         */
        canAutoNext: function() {
          var text = this.getValue().toString();

          if (this._maxLength > 0) {
            return text.length >= this._maxLength;
          }

          return false;
        },

        /**
         * @inheritDoc
         */
        getClipboardValue: function(ignoreSelection) {
          if (ignoreSelection) {
            return this.getValue();
          }

          var txt = cls.ClipboardHelper.getSelection();

          return txt.length > 0 ? txt : null;
        },

        /**
         * @inheritDoc
         */
        getClipboardAuthorizedAction: function() {
          return {
            paste: false,
            copy: true,
            cut: false
          };
        }
      };
    });
  });