# Source: src/js/views/maps/LayerOpacityView.js

    'use strict';

    define(
      [
        'jquery',
        'underscore',
        'backbone',
        'models/maps/assets/MapAsset',
        'text!templates/maps/layer-opacity.html'
      ],
      function (
        $,
        _,
        Backbone,
        MapAsset,
        Template
      ) {

        /**
        * @class LayerOpacityView
        * @classdesc A number slider that shows and updates the opacity in a MapAsset model.
        * Changing the opacity of a layer will also make it visible, if it was not visible
        * before (i.e. this view also updates the MapAsset's visible attribute.)
        * @classcategory Views/Maps
        * @name LayerOpacityView
        * @extends Backbone.View
        * @screenshot views/maps/LayerOpacityView.png
        * @since 2.18.0
        * @constructs
        */
        var LayerOpacityView = Backbone.View.extend(
          /** @lends LayerOpacityView.prototype */{

            /**
            * The type of View this is
            * @type {string}
            */
            type: 'LayerOpacityView',

            /**
            * The HTML classes to use for this view's element
            * @type {string}
            */
            className: 'layer-opacity',

            /**
            * The model that this view uses
            * @type {MapAsset}
            */
            model: undefined,

            /**
             * The primary HTML template for this view
             * @type {Underscore.template}
             */
            template: _.template(Template),

            /**
             * CSS classes assigned to the HTML elements that make up this view
             * @type {Object}
             * @property {string} sliderContainer The element that will be converted by this
             * view into a number slider widget. An element with this class must exist in the
             * template.
             * @property {string} handle The class given to the element that acts as a
             * handle for the slider UI. The handle is created during render and the class is
             * set by the jquery slider widget.
             * @property {string} range The class given to the element that shades the
             * slider from 0 to the current opacity. The range is created during render and
             * the class is set by the jquery slider widget.
             * @property {string} label The element that displays the current opacity
             * value as a percentage. This element is created during render.
             */
            classes: {
              sliderContainer: 'layer-opacity__slider',
              handle: 'layer-opacity__handle',
              range: 'layer-opacity__range',
              label: 'layer-opacity__label'
            },

            /**
            * The events this view will listen to and the associated function to call.
            * @type {Object}
            */
            events: {
              // 'event selector': 'function',
            },

            /**
            * Executed when a new LayerOpacityView is created
            * @param {Object} [options] - A literal object with options to pass to the view
            */
            initialize: function (options) {

              try {
                // Get all the options and apply them to this view
                if (typeof options == 'object') {
                  for (const [key, value] of Object.entries(options)) {
                    this[key] = value;
                  }
                }
              } catch (e) {
                console.log('A LayerOpacityView failed to initialize. Error message: ' + e);
              }

            },

            /**
            * Renders this view
            * @return {LayerOpacityView} Returns the rendered view element
            */
            render: function () {

              try {

                // Save a reference to this view
                var view = this;

                // Ensure the view's main element has the given class name
                this.el.classList.add(this.className);

                // Insert the template into the view
                this.$el.html(this.template({}));

                var startOpacity = this.model ? this.model.get('opacity') || 1 : 1;

                // Find the element that will contain the slider
                view.sliderContainer = this.$el.find('.' + this.classes.sliderContainer).first()

                // The model opacity may be updated by this or other views or models. Make
                // sure that the UI reflects any of these changes.
                view.stopListening(view.model, 'change:opacity')
                view.listenTo(view.model, 'change:opacity', view.updateSlider)

                // Create the jQuery slider widget. See https://api.jqueryui.com/slider/
                view.sliderContainer.slider({
                  min: 0,
                  max: 1,
                  range: 'min',
                  value: startOpacity,
                  step: 0.01,
                  // classes to add to the slider elements
                  classes: {
                    'ui-slider': '',
                    'ui-slider-handle': view.classes.handle,
                    'ui-slider-range': view.classes.range
                  },
                  // event handling
                  slide: handleSliderEvent, // when the slider is moved by the user
                  change: handleSliderEvent // when the slider is changed programmatically
                })

                // What to do when the opacity slider is changed. The event handler needs the
                // view context to call other functions that update the model and the label.
                function handleSliderEvent (e, ui) {
                  const newOpacity = ui.value
                  const currentVisibility = view.model.get('visible')
                  // Update the model. This will trigger other UI updates in this view.
                  view.updateModel(newOpacity)
                  // If the opacity changes to anything but zero, then make sure the asset is
                  // also visible. (Why would a user change the opacity and not also want the
                  // layer visible?)
                  if (newOpacity > 0 && !currentVisibility) {
                    view.model.set('visible', true)
                  // If the opacity is changed to zero, also set visibility to false. This
                  // triggers the layer list to grey-out the layer item.
                  } else if (newOpacity === 0 && currentVisibility) {
                    view.model.set('visible', false)
                  }
                }

                // Create the element that will display the current opacity value as a
                // percentage. Insert it into the slider handle so that it can be easily
                // positioned just below the handle, even as the handle moves.
                this.opacityLabel = document.createElement('div')
                this.opacityLabel.className = view.classes.label
                view.sliderContainer.slider('instance').handle.append(this.opacityLabel)
                // Show the initial opacity value
                view.updateLabel(startOpacity)

                return this

              }
              catch (error) {
                console.log(
                  'There was an error rendering a LayerOpacityView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Get the new opacity value from the model and update the slider handle position
             * and label. This function is called whenever the model opacity is updated.
             */
            updateSlider: function () {
              try {
                const newOpacity = this.model.get('opacity')
                // Only update if the value has actually changed
                if (newOpacity !== this.displayedOpacity) {
                  this.updateLabel(newOpacity)
                  // If this function was triggered by any event other than a user sliding the
                  // handle, then the slider handle position will need to be updated
                  this.sliderContainer.slider('value', newOpacity)
                  this.displayedOpacity = newOpacity
                }
              }
              catch (error) {
                console.log(
                  'There was an error handling a slider event in a LayerOpacityView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Update the MapAsset model's opacity attribute with a new value.
             * @param {Number} newOpacity A number between 0 and 1 indicating the new opacity
             * value for the MapAsset model
             */
            updateModel: function (newOpacity) {
              try {
                if (!this.model || typeof newOpacity !== 'number') {
                  return
                }
                this.model.set('opacity', newOpacity)
              }
              catch (error) {
                console.log(
                  'There was an error updating the model in a LayerOpacityView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Update the label with the newOpacity displayed as a percentage
             * @param {Number} newOpacity A number between 0 and 1 indicating the new opacity
             * value for the MapAsset model
             */
            updateLabel: function (newOpacity) {

              try {
                if (!this.opacityLabel || (typeof newOpacity === 'undefined') || typeof newOpacity !== 'number') {
                  return
                }
                var opacityPercent = Math.round(newOpacity * 100);
                this.opacityLabel.innerText = opacityPercent + '%'
              }
              catch (error) {
                console.log(
                  'There was an error updating the opacity label in a LayerOpacityView' +
                  '. Error details: ' + error
                );
              }
            },

            /**
             * Perform clean-up functions when this view is about to be removed from the page
             * or navigated away from.
             */
            onClose: function () {
              try {
                this.stopListening(this.model, 'change:opacity')
              }
              catch (error) {
                console.log(
                  'There was an error performing clean up functions in a LayerOpacityView' +
                  '. Error details: ' + error
                );
              }
            }

          }
        );

        return LayerOpacityView;

      }
    );