2
\$\begingroup\$

I've made myself a JavaScript slider constructor. You can create new sliders and tell it what elements it should refer to.

var SliderControl = function (container, area, fill, action, actionWhenActive, actionWhenSet) {
    'use strict';
    var self = this;
    this.action = action;
    this.container = container;
    this.area = area;
    this.fill = fill;
    this.element = container.find(area);
    this.value = 0;
    this.active = false;
    this.actionWhenActive = actionWhenActive;
    this.actionWhenSet = actionWhenSet;
    $(document).on('mousemove', function (event) {
        self.move(event, self);
    });
    $(document).on('mouseup', function (event) {
        self.drop(event, self);
    });
    this.area.on('mousedown', function (event) {
        self.grab(event, self);
    });
};

SliderControl.prototype = {
    action: this.action,
    width: function () {
        'use strict';
        var calcWidth = ((this.value * 100) + '%');
        this.fill.width(calcWidth);
    },
    update: function (event, self) {
        'use strict';
        if (this.actionWhenActive === true) {
            this.action();
        }
        var direction, percent, container, area;
        direction = event.pageX - this.element.offset().left;
        percent = Math.min(Math.max(direction / this.element.width(), 0), 1.0);
        this.value = percent;
        this.width();
    },
    move: function (event, self) {
        'use strict';
        if (this.active === true) {
            this.update(event);
        }
    },
    grab: function (event, self) {
        'use strict';
        this.active = true;
        self.update(event);
        event.preventDefault();
    },
    drop: function (event, self) {
        'use strict';
        if (this.active === true) {
            this.active = false;
            this.action();
        }
    },
    setValue: function (value) {
        if (this.active === false) {
            this.value = value;
            this.width();
            if (this.actionWhenSet === true) {
                this.action();
            }
        }
    }
};

If you want to try it out, go here.

I think there's a better way to set a custom function it should run when you drop the slider (or if actionWhenActive is set to true, while you're dragging it) but I'm not sure what would be the most DRY way to do it.

\$\endgroup\$
1
\$\begingroup\$

Really your question boils down to whether or not there is a better way to set a custom function. That's hard to say because your approach will work fine. Really any kind of answer your going to get is largely subjective.

For me I tend to avoid the prototype method for objects unless I'm overriding or extending some other function or object. You started on the right track with your declaration of the SliderControl object; you can incorporate your prototype into that method declaration as well to greatly simplify and encapsulate your code.

It could use some cleanup I'm sure but here's my quick take.

<!DOCTYPE>
<html>
<head>
  <title>Slider</title>
  <meta charset="utf-8" />
  <style type="text/css" media="screen, print">
    #container {
      width: 400px;
      background: white;
      padding: 30px;
    }

    .area {
      width: 100%;
      height: 10px;
      padding-top: 10px;
      cursor: pointer;
    }

    .rail {
      width: 100%;
      height: 2px;
      background: lightgray;
    }

    .element {
      height: 2px;
      background: black;
      width: 40%;
    }

    .knob {
      width: 10px;
      height: 10px;
      background: black;
      float: right;
      border-radius: 50px;
      margin-top: -4px;
    }
  </style>
  <script>
    var SliderControl = function (container, action, actionWhenActive, actionWhenSet) {
        var self = this;
        this.action = action;
        this.container = container;
          var area = document.createElement("div");
          this.container.appendChild(area);
          area.className = "area";
            var railDiv = document.createElement("div");
            area.appendChild(railDiv);
            railDiv.className = "rail";
              this.fill = document.createElement('div');
              railDiv.appendChild(this.fill);
              this.fill.className = "element";
              this.fill.innerHTML = "<div class='knob'></div>";
        this.element = area;
        this.value = 0;
        this.active = false;
        this.actionWhenActive = actionWhenActive;
        this.actionWhenSet = actionWhenSet;

        this.noEvent = false;
        this.clearNoEvent = function(){
          self.noEvent = false;
        }; 

        document.addEventListener("mousemove", function (event) {
            if(self.noEvent){
                return;
            }
            self.noEvent = true;
            setTimeout(self.clearNoEvent,10);  //debounce so the handler isn't called excessively
            self.move(event);
        },false);

        document.addEventListener("mouseup", function (event) {
            self.drop(event, self);
        },false);

        this.element.addEventListener("mousedown", function (event) {
            self.grab(event, self);
        },false);

        this.width = function(){
            var calcWidth = ((this.value * 100) + '%');
            this.fill.style.width = calcWidth;
        };

        this.update = function(event){
            if (this.actionWhenActive === true) {
                self.action(this.value);
            }
            var direction, percent, container, area;
            direction = event.pageX - this.element.offsetLeft;
            percent = Math.min(Math.max(direction / this.element.offsetWidth, 0), 1.0);
            this.value = percent;
            self.width();
        };

        this.move = function(event){
            if (this.active === true) {
                self.update(event);
            }
        };

        this.grab = function(event){
            event.preventDefault();
            this.active = true;
            self.update(event);
        };

        this.drop = function(event){
            if (this.active === true) {
                this.active = false;
                self.action(this.value);
            }
        };

        this.setValue = function(value){
            if (this.active !== false) {
                return;
            }
            this.value = value;
            self.width();
            if (this.actionWhenSet === true) {
                self.action(value);
            }
        };
    };

    window.onload = function(){
        var sContainer = document.getElementById('container');
        var sValue = document.getElementById('value');

        // create the slider control
        var mySlider = new SliderControl(sContainer, function setValue(value) {
            sValue.innerHTML = value;
          }, true);
    };
  </script>
</head>
<body>
  <div id='container'></div>
  <div>The value is <span id='value'>0.4</span></div>
</body>
</html>

Now above and beyond your initial question...

To make your slider more portable (and practical) you should also create the dom structure in the object so that all you have to pass in during initialization is the element you'd like to put it in.

You'll also want to add the ability to set a default value, in case you're loading and editing preset data.

I would also suggest removing 'actionWhenActive' and 'actionWhenSet' and replacing them with your existing 'action' and a new 'actionSet' function. You can check if one or the other is null or undefined to give you the same effect as the booleans. This will also give your slider the ability to perform both operations (ie displaying a value, and then saving it when done).

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.