view class doc
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394/// FOURJS_START_COPYRIGHT(D,2015)
/// Property of Four Js*
/// (c) Copyright Four Js 2015, 2024. 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('SliderWidget', ['FieldWidgetBase', 'WidgetFactory'],
  function(context, cls) {

    /**
     * Slider widget.
     * @class SliderWidget
     * @memberOf classes
     * @extends classes.FieldWidgetBase
     * @publicdoc Widgets
     */
    cls.SliderWidget = context.oo.Class(cls.FieldWidgetBase, function($super) {
      return /** @lends classes.SliderWidget.prototype */ {
        __name: 'SliderWidget',
        /**
         * Redefine where the data is located
         * @type {string|Object}
         */
        __dataContentPlaceholderSelector: cls.WidgetBase.selfDataContent,
        /**
         * Slider orientation. Default is horizontal
         * @type {?string}
         */
        _orientation: null,
        /**
         * Flag to indicate if we updated orientation before or after first widget layout
         */
        _afterLayoutFlag: null,

        /**
         * @constructs
         * @param {Object} opts - Options passed to the constructor
         * @publicdoc
         */
        constructor: function(opts) {
          $super.constructor.call(this, opts);
          this.setFocusable(true);
        },

        /**
         * @inheritDoc
         */
        _initLayout: function() {
          if (!this._ignoreLayout) {
            this._layoutInformation = new cls.LayoutInformation(this);
            this._layoutEngine = new cls.SliderLayoutEngine(this);
            this._layoutInformation.getSizePolicyConfig().initial = cls.SizePolicy.Dynamic();
            this._layoutInformation.getSizePolicyConfig().fixed = cls.SizePolicy.Dynamic();
            this._layoutInformation._forceFixedWidthMeasure = true;
          }
        },

        /**
         * @inheritDoc
         */
        _initElement: function() {
          $super._initElement.call(this);
          this._inputElement = this._element.getElementsByTagName('input')[0];
          var onSlideEnd = this._onSlide.bind(this);

          this._inputElement.on('touchend.SliderWidget', onSlideEnd);
          this._inputElement.on('mouseup.SliderWidget', onSlideEnd);

          this._inputElement.on('touchstart.SliderWidget', function() {
            this._preventContainerScrolling(true);
          }.bind(this));
        },

        /**
         * @inheritDoc
         */
        destroy: function() {
          this._inputElement.off('touchend.SliderWidget');
          this._inputElement.off('mouseup.SliderWidget');
          this._inputElement.off('touchstart.SliderWidget');
          $super.destroy.call(this);
        },

        /**
         * @inheritDoc
         */
        manageMouseClick: function(domEvent) {
          this._onRequestFocus(domEvent); // request focus
          return true;
        },

        /**
         * @inheritDoc
         */
        manageKeyDown: function(keyString, domKeyEvent, repeat) {
          var keyProcessed = false;

          if (this.isEnabled() && !this.isReadOnly()) {

            keyProcessed = true;
            switch (keyString) {
              case "down":
              case "pagedown":
              case this.getStart():
                this.setEditing(true);
                this._decrease();
                this.emit(context.constants.widgetEvents.change, false);
                break;
              case "up":
              case "pageup":
              case this.getEnd():
                this.setEditing(true);
                this._increase();
                this.emit(context.constants.widgetEvents.change, false);
                break;
              default:
                keyProcessed = false;
            }
          }

          if (keyProcessed) {
            return true;
          } else {
            return $super.manageKeyDown.call(this, keyString, domKeyEvent, repeat);
          }
        },

        /**
         * Handler executed on mouse click. We update the value depending of pointer location click on the slider
         * and send it to VM
         * @param {Object} evt - DOM event
         */
        _onSlide: function(evt) {
          if (!this.hasFocus()) {
            this._onRequestFocus(evt); // request focus
          }
          // need to emit change because on some browsers, change event is not raised when slider has not yet the focus
          if (this.isEnabled() && !this.isReadOnly()) {
            var total;
            // Vertical sliders are rotated with CSS. evt.offsetX takes this into account. getBoundingClientRect doesn't
            var clickPos = 0;
            var inputRect = this._inputElement.getBoundingClientRect();
            if (evt.offsetX) {
              clickPos = evt.offsetX;
              // on mobile offsetX doesn't exist. We need to calculate the relative click position using global pageX and input position
            } else if (window.isTouchDevice() && evt.changedTouches[0]) {
              if (this.getOrientation() === 'horizontal') {
                clickPos = evt.changedTouches[0].pageX - inputRect.left;
              } else {
                clickPos = inputRect.bottom - evt.changedTouches[0].pageY;
              }
            }
            if (this.getOrientation() === 'horizontal') {
              total = inputRect.width;
            } else {
              total = inputRect.height;
            }
            var expectedTotal = this.getMax() - this.getMin();
            var expectedVal = expectedTotal * (clickPos / total);
            var step = this.getStep();
            var value = this.getMin() + Math.floor(expectedVal / step) * step;
            if ((expectedVal % step) > (step / 2)) {
              value += step;
            }
            this.setEditing(true);
            this._inputElement.value = value;
          }
          this._preventContainerScrolling(false);
          this.emit(context.constants.widgetEvents.change, false);
        },

        /**
         * Increase the displayed value
         */
        _increase: function() {
          this.setValue((this.getValue() || 0) + this.getStep());
        },

        /**
         * Decrease the displayed value
         */
        _decrease: function() {
          this.setValue((this.getValue() || 0) - this.getStep());
        },

        /**
         * @inheritDoc
         */
        getValue: function() {
          return parseInt(this._inputElement.value, 10);
        },

        /**
         * @inheritDoc
         */
        setValue: function(value, fromVM) {
          $super.setValue.call(this, value, fromVM);
          value = +value;
          this._inputElement.value = Object.isNumber(value) && !Number.isNaN(value) ? value : 0;
          this.setAriaAttribute("valuenow", value);
        },

        /**
         * Get minimum possible value of the slider
         * @returns {number} the minimum value
         * @publicdoc
         */
        getMin: function() {
          return this._inputElement.getIntAttribute('min');
        },

        /**
         * Define the minimum possible value of the slider
         * @param {number} value - the minimum value
         * @publicdoc
         */
        setMin: function(value) {
          if (Object.isNumber(value)) {
            this._inputElement.setAttribute('min', value);
          } else {
            this._inputElement.removeAttribute('min');
          }
          this.setAriaAttribute("valuemin", value);
        },

        /**
         * Get maximum possible value of the slider
         * @returns {number} the maximum value
         * @publicdoc
         */
        getMax: function() {
          return this._inputElement.getIntAttribute('max');
        },

        /**
         * Define the maximum possible value of the slider
         * @param {number} value - the maximum value
         * @publicdoc
         */
        setMax: function(value) {
          if (Object.isNumber(value)) {
            this._inputElement.setAttribute('max', value);
          } else {
            this._inputElement.removeAttribute('max');
          }
          this.setAriaAttribute("valuemax", value);
        },

        /**
         * Get slider step when increasing or decreasing value
         * @returns {number} the step value
         * @publicdoc
         */
        getStep: function() {
          return this._inputElement.getIntAttribute('step');
        },

        /**
         * Define the slider step when increasing or decreasing value
         * @param {number} step - the step value
         * @publicdoc
         */
        setStep: function(step) {
          this._inputElement.setAttribute('step', Object.isNumber(step) && step > 0 ? step : 1);
        },

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

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

        /**
         * Set the orientation of the slider widget
         * @param {string} orientation can be 'horizontal' or 'vertical'
         * @param {boolean} afterLayout internal
         * @publicdoc
         */
        setOrientation: function(orientation, afterLayout) {
          if (this._orientation !== orientation || this._afterLayoutFlag !== afterLayout) {
            this._orientation = orientation;
            this._afterLayoutFlag = afterLayout;
            var newStyle = {};

            if (orientation === 'vertical' && afterLayout) {
              // Rotate only after layout
              this.setStyle({
                'transform': 'rotate(-90deg)'
              });
            } else {
              newStyle = {
                '-webkit-appearance': null,
                'writing-mode': null
              };
              if (this._inputElement) {
                this._inputElement.removeAttribute('orient');
              }
            }
            this.setStyle('>input[type=range]', newStyle);
            this.setAriaAttribute("orientation", orientation);
          }
        },

        /**
         * Get the current slider orientation. Default is horizontal.
         * @returns {string} current css orientation
         * @publicdoc
         */
        getOrientation: function() {
          return this._orientation ? this._orientation : 'horizontal';
        },

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

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

        /**
         * Set if input element is readonly
         * @param {boolean} readonly
         * @private
         */
        _setInputReadOnly: function(readonly) {
          if (readonly) {
            this._inputElement.setAttribute('readonly', 'readonly');
          } else {
            this._inputElement.removeAttribute('readonly');
          }
        },

        /**
         * Prevent the container to scroll while doing touch to scroll the tab-titles
         * @param {Boolean} prevent - true to prevent it, false otherwise
         * @private
         */
        _preventContainerScrolling: function(prevent) {
          var form = this.getFormWidget();
          if (form) {
            form.getContainerElement().toggleClass("prevent-touch-scroll", prevent);
          }
        },

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

          return null;
        },

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

      };
    });
    cls.WidgetFactory.registerBuilder('Slider', cls.SliderWidget);
  });