Added html5slider to support range inputs in Firefox
authorJoar Wandborg <git@wandborg.com>
Wed, 18 Apr 2012 10:06:10 +0000 (12:06 +0200)
committerJoar Wandborg <git@wandborg.com>
Wed, 18 Apr 2012 10:06:10 +0000 (12:06 +0200)
extlib/html5slider/html5slider.js [new file with mode: 0644]
mediagoblin/static/js/audio.js
mediagoblin/static/js/extlib/html5slider.js [new symlink]
mediagoblin/templates/mediagoblin/media_displays/audio.html

diff --git a/extlib/html5slider/html5slider.js b/extlib/html5slider/html5slider.js
new file mode 100644 (file)
index 0000000..e69c3d2
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+html5slider - a JS implementation of <input type=range> for Firefox 4 and up
+https://github.com/fryn/html5slider
+
+Copyright (c) 2010-2011 Frank Yan, <http://frankyan.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+(function() {
+
+// test for native support
+var test = document.createElement('input');
+try {
+  test.type = 'range';
+  if (test.type == 'range')
+    return;
+} catch (e) {
+  return;
+}
+
+// test for required property support
+if (!document.mozSetImageElement || !('MozAppearance' in test.style))
+  return;
+
+var scale;
+var isMac = navigator.platform == 'MacIntel';
+var thumb = {
+  radius: isMac ? 9 : 6,
+  width: isMac ? 22 : 12,
+  height: isMac ? 16 : 20
+};
+var track = '-moz-linear-gradient(top, transparent ' + (isMac ?
+  '6px, #999 6px, #999 7px, #ccc 9px, #bbb 11px, #bbb 12px, transparent 12px' :
+  '9px, #999 9px, #bbb 10px, #fff 11px, transparent 11px') +
+  ', transparent)';
+var styles = {
+  'min-width': thumb.width + 'px',
+  'min-height': thumb.height + 'px',
+  'max-height': thumb.height + 'px',
+  padding: 0,
+  border: 0,
+  'border-radius': 0,
+  cursor: 'default',
+  'text-indent': '-999999px' // -moz-user-select: none; breaks mouse capture
+};
+var onChange = document.createEvent('HTMLEvents');
+onChange.initEvent('change', true, false);
+
+if (document.readyState == 'loading')
+  document.addEventListener('DOMContentLoaded', initialize, true);
+else
+  initialize();
+
+function initialize() {
+  // create initial sliders
+  Array.forEach(document.querySelectorAll('input[type=range]'), transform);
+  // create sliders on-the-fly
+  document.addEventListener('DOMNodeInserted', onNodeInserted, true);
+}
+
+function onNodeInserted(e) {
+  check(e.target);
+  if (e.target.querySelectorAll)
+    Array.forEach(e.target.querySelectorAll('input'), check);
+}
+
+function check(input, async) {
+  if (input.localName != 'input' || input.type == 'range');
+  else if (input.getAttribute('type') == 'range')
+    transform(input);
+  else if (!async)
+    setTimeout(check, 0, input, true);
+}
+
+function transform(slider) {
+
+  var isValueSet, areAttrsSet, isChanged, isClick, prevValue, rawValue, prevX;
+  var min, max, step, range, value = slider.value;
+
+  // lazily create shared slider affordance
+  if (!scale) {
+    scale = document.body.appendChild(document.createElement('hr'));
+    style(scale, {
+      '-moz-appearance': isMac ? 'scale-horizontal' : 'scalethumb-horizontal',
+      display: 'block',
+      visibility: 'visible',
+      opacity: 1,
+      position: 'fixed',
+      top: '-999999px'
+    });
+    document.mozSetImageElement('__sliderthumb__', scale);
+  }
+
+  // reimplement value and type properties
+  var getValue = function() { return '' + value; };
+  var setValue = function setValue(val) {
+    value = '' + val;
+    isValueSet = true;
+    draw();
+    delete slider.value;
+    slider.value = value;
+    slider.__defineGetter__('value', getValue);
+    slider.__defineSetter__('value', setValue);
+  };
+  slider.__defineGetter__('value', getValue);
+  slider.__defineSetter__('value', setValue);
+  slider.__defineGetter__('type', function() { return 'range'; });
+
+  // sync properties with attributes
+  ['min', 'max', 'step'].forEach(function(prop) {
+    if (slider.hasAttribute(prop))
+      areAttrsSet = true;
+    slider.__defineGetter__(prop, function() {
+      return this.hasAttribute(prop) ? this.getAttribute(prop) : '';
+    });
+    slider.__defineSetter__(prop, function(val) {
+      val === null ? this.removeAttribute(prop) : this.setAttribute(prop, val);
+    });
+  });
+
+  // initialize slider
+  slider.readOnly = true;
+  style(slider, styles);
+  update();
+
+  slider.addEventListener('DOMAttrModified', function(e) {
+    // note that value attribute only sets initial value
+    if (e.attrName == 'value' && !isValueSet) {
+      value = e.newValue;
+      draw();
+    }
+    else if (~['min', 'max', 'step'].indexOf(e.attrName)) {
+      update();
+      areAttrsSet = true;
+    }
+  }, true);
+
+  slider.addEventListener('mousedown', onDragStart, true);
+  slider.addEventListener('keydown', onKeyDown, true);
+  slider.addEventListener('focus', onFocus, true);
+  slider.addEventListener('blur', onBlur, true);
+
+  function onDragStart(e) {
+    isClick = true;
+    setTimeout(function() { isClick = false; }, 0);
+    if (e.button || !range)
+      return;
+    var width = parseFloat(getComputedStyle(this, 0).width);
+    var multiplier = (width - thumb.width) / range;
+    if (!multiplier)
+      return;
+    // distance between click and center of thumb
+    var dev = e.clientX - this.getBoundingClientRect().left - thumb.width / 2 -
+              (value - min) * multiplier;
+    // if click was not on thumb, move thumb to click location
+    if (Math.abs(dev) > thumb.radius) {
+      isChanged = true;
+      this.value -= -dev / multiplier;
+    }
+    rawValue = value;
+    prevX = e.clientX;
+    this.addEventListener('mousemove', onDrag, true);
+    this.addEventListener('mouseup', onDragEnd, true);
+  }
+
+  function onDrag(e) {
+    var width = parseFloat(getComputedStyle(this, 0).width);
+    var multiplier = (width - thumb.width) / range;
+    if (!multiplier)
+      return;
+    rawValue += (e.clientX - prevX) / multiplier;
+    prevX = e.clientX;
+    isChanged = true;
+    this.value = rawValue;
+  }
+
+  function onDragEnd() {
+    this.removeEventListener('mousemove', onDrag, true);
+    this.removeEventListener('mouseup', onDragEnd, true);
+  }
+
+  function onKeyDown(e) {
+    if (e.keyCode > 36 && e.keyCode < 41) { // 37-40: left, up, right, down
+      onFocus.call(this);
+      isChanged = true;
+      this.value = value + (e.keyCode == 38 || e.keyCode == 39 ? step : -step);
+    }
+  }
+
+  function onFocus() {
+    if (!isClick)
+      this.style.boxShadow = !isMac ? '0 0 0 2px #fb0' :
+        '0 0 2px 1px -moz-mac-focusring, inset 0 0 1px -moz-mac-focusring';
+  }
+
+  function onBlur() {
+    this.style.boxShadow = '';
+  }
+
+  // determines whether value is valid number in attribute form
+  function isAttrNum(value) {
+    return !isNaN(value) && +value == parseFloat(value);
+  }
+
+  // validates min, max, and step attributes and redraws
+  function update() {
+    min = isAttrNum(slider.min) ? +slider.min : 0;
+    max = isAttrNum(slider.max) ? +slider.max : 100;
+    if (max < min)
+      max = min > 100 ? min : 100;
+    step = isAttrNum(slider.step) && slider.step > 0 ? +slider.step : 1;
+    range = max - min;
+    draw(true);
+  }
+
+  // recalculates value property
+  function calc() {
+    if (!isValueSet && !areAttrsSet)
+      value = slider.getAttribute('value');
+    if (!isAttrNum(value))
+      value = (min + max) / 2;;
+    // snap to step intervals (WebKit sometimes does not - bug?)
+    value = Math.round((value - min) / step) * step + min;
+    if (value < min)
+      value = min;
+    else if (value > max)
+      value = min + ~~(range / step) * step;
+  }
+
+  // renders slider using CSS background ;)
+  function draw(attrsModified) {
+    calc();
+    if (isChanged && value != prevValue)
+      slider.dispatchEvent(onChange);
+    isChanged = false;
+    if (!attrsModified && value == prevValue)
+      return;
+    prevValue = value;
+    var position = range ? (value - min) / range * 100 : 0;
+    var bg = '-moz-element(#__sliderthumb__) ' + position + '% no-repeat, ';
+    style(slider, { background: bg + track });
+  }
+
+}
+
+function style(element, styles) {
+  for (var prop in styles)
+    element.style.setProperty(prop, styles[prop], 'important');
+}
+
+})();
index 217a21603c4175f8873fe458a7661b3b2135a336..50d58cd9b066263152062f88b15ded4893f6d2f0 100644 (file)
@@ -210,14 +210,8 @@ var audioPlayer = new Object();
         $('<div class="seekbar"></div>').appendTo(im.parent());
         $('<div class="audio-control-play-pause paused">▶</div>').appendTo(im.parent());
         $('<div class="audio-currentTime">00:00</div>').appendTo(im.parent());
-        if (navigator && /Firefox/.test(navigator.userAgent)) {
-            $('<p class="message_warning">Sorry, Firefox does not support the '
-                    + 'range input type, you won\'t be able to change the volume</p>')
-                .appendTo(im.parent().parent());
-        } else {
-            $('<input type="range" class="audio-volume"'
-                    +'value="1" min="0" max="1" step="0.001" />').appendTo(im.parent());
-        }
+        $('<input type="range" class="audio-volume"'
+                +'value="1" min="0" max="1" step="0.001" />').appendTo(im.parent());
         $('.audio-spectrogram').trigger('attachedControls');
     };
 })(audioPlayer);
diff --git a/mediagoblin/static/js/extlib/html5slider.js b/mediagoblin/static/js/extlib/html5slider.js
new file mode 120000 (symlink)
index 0000000..feae2cb
--- /dev/null
@@ -0,0 +1 @@
+../../../../extlib/html5slider/html5slider.js
\ No newline at end of file
index 3efd80e1c3919434b24d8f762404b7af8ad9b4d1..06d4f88269cc5b1409fc330652461d21730aa35c 100644 (file)
@@ -21,6 +21,8 @@
 {% block mediagoblin_head %}
     {{ super() }}
     <link rel="stylesheet" type="text/css" href="{{ request.staticdirect('/css/audio.css') }}" />
+    <script type="text/javascript" src="{{ request.staticdirect(
+        '/js/extlib/html5slider.js') }}"></script>
     <script type="text/javascript" src="{{ request.staticdirect(
         '/js/audio.js') }}"></script>
 {% endblock %}