// vertical slider extension
// add data-vertical="true" to the input to enable verticality
// data-height lets you set height in px
$.widget("mobile.slider", $.mobile.slider, {
options: {
vertical: false,
height: 250
},
_create: function() {
if (this.options.vertical) {
// TODO: Each of these should have comments explain what they're for
var self = this,
control = this.element,
trackTheme = this.options.trackTheme || $.mobile.getAttribute(control[ 0 ], "theme"),
trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
cornerClass = (this.options.corners || control.jqmData("corners")) ? " ui-corner-all" : "",
miniClass = (this.options.mini || control.jqmData("mini")) ? " ui-mini" : "",
cType = control[ 0 ].nodeName.toLowerCase(),
isToggleSwitch = (cType === "select"),
isRangeslider = control.parent().is(":jqmData(role='rangeslider')"),
selectClass = (isToggleSwitch) ? "ui-slider-switch" : "",
controlID = control.attr("id"),
$label = $("[for='" + controlID + "']"),
labelID = $label.attr("id") || controlID + "-label",
trueMin = !isToggleSwitch ? parseFloat(control.attr("min")) : 0,
trueMax = !isToggleSwitch ? parseFloat(control.attr("max")) : control.find("option").length-1,
min = trueMax * -1,
max = trueMin * -1,
step = window.parseFloat(control.attr("step") || 1),
domHandle = document.createElement("a"),
handle = $(domHandle),
domSlider = document.createElement("div"),
slider = $(domSlider),
valuebg = this.options.highlight && !isToggleSwitch ? (function() {
var bg = document.createElement("div");
bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
return $(bg).prependTo(slider);
})() : false,
options,
wrapper,
j, length,
i, optionsCount, origTabIndex,
side, activeClass, sliderImg;
$label.attr("id", labelID);
this.isToggleSwitch = isToggleSwitch;
domHandle.setAttribute("href", "#");
domSlider.setAttribute("role", "application");
domSlider.className = [ this.isToggleSwitch ? "ui-slider ui-slider-track ui-shadow-inset " : "ui-slider-track ui-shadow-inset ", selectClass, trackThemeClass, cornerClass, miniClass ].join("");
domHandle.className = "ui-slider-handle";
domSlider.appendChild(domHandle);
handle.attr({
"role": "slider",
"aria-valuemin": min,
"aria-valuemax": max,
"aria-valuenow": this._value(),
"aria-valuetext": this._value(),
"title": this._value(),
"aria-labelledby": labelID
});
$.extend(this, {
slider: slider,
handle: handle,
control: control,
type: cType,
step: step,
max: max,
min: min,
valuebg: valuebg,
isRangeslider: isRangeslider,
dragging: false,
beforeStart: null,
userModified: false,
mouseMoved: false
});
if (isToggleSwitch) {
// TODO: restore original tabindex (if any) in a destroy method
origTabIndex = control.attr("tabindex");
if (origTabIndex) {
handle.attr("tabindex", origTabIndex);
}
control.attr("tabindex", "-1").focus(function() {
$(this).blur();
handle.focus();
});
wrapper = document.createElement("div");
wrapper.className = "ui-slider-inneroffset";
for (j = 0, length = domSlider.childNodes.length; j < length; j++) {
wrapper.appendChild(domSlider.childNodes[j]);
}
domSlider.appendChild(wrapper);
// slider.wrapInner("<div class='ui-slider-inneroffset'></div>");
// make the handle move with a smooth transition
handle.addClass("ui-slider-handle-snapping");
options = control.find("option");
for (i = 0, optionsCount = options.length; i < optionsCount; i++) {
side = !i ? "b" : "a";
activeClass = !i ? "" : " " + $.mobile.activeBtnClass;
sliderImg = document.createElement("span");
sliderImg.className = [ "ui-slider-label ui-slider-label-", side, activeClass ].join("");
sliderImg.setAttribute("role", "img");
sliderImg.appendChild(document.createTextNode(options[i].innerHTML));
$(sliderImg).prependTo(slider);
}
self._labels = $(".ui-slider-label", slider);
}
// monitor the input for updated values
control.addClass(isToggleSwitch ? "ui-slider-switch" : "ui-slider-input");
this._on(control, {
"change": "_controlChange",
"keyup": "_controlKeyup",
"blur": "_controlBlur",
"vmouseup": "_controlVMouseUp"
});
slider.bind("vmousedown", $.proxy(this._sliderVMouseDown, this))
.bind("vclick", false);
// We have to instantiate a new function object for the unbind to work properly
// since the method itself is defined in the prototype (causing it to unbind everything)
this._on(document, { "vmousemove": "_preventDocumentDrag" });
this._on(slider.add(document), { "vmouseup": "_sliderVMouseUp" });
slider.insertAfter(control);
// wrap in a div for styling purposes
if (!isToggleSwitch && !isRangeslider) {
wrapper = this.options.mini ? "<div class='ui-slider ui-mini'>" : "<div class='ui-slider'>";
control.add(slider).wrapAll(wrapper);
}
// bind the handle event callbacks and set the context to the widget instance
this._on(this.handle, {
"vmousedown": "_handleVMouseDown",
"keydown": "_handleKeydown",
"keyup": "_handleKeyup"
});
this.handle.bind("vclick", false);
this._handleFormReset();
this.refresh(undefined, undefined, true);
this.slider.attr("style", "width:20px !important; margin: 0 0 20px 14px !important; height:"+this.options.height+"px !important;")
$(this.control).detach()
$(this.slider).parent().append(this.control)
$(this.slider).parent().css("margin-bottom", (this.options.height + 30) + "px")
} else {
this._super()
}
},
_value: function() {
if (!this.options.vertical) {
this._super()
} else {
return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat(this.element.val() * -1);
}
},
refresh: function(val, isfromControl, preventInputUpdate) {
if (!this.options.vertical) {
this._super(val, isfromControl, preventInputUpdate)
} else {
var self = this,
parentTheme = $.mobile.getAttribute(this.element[ 0 ], "theme"),
theme = this.options.theme || parentTheme,
themeClass = theme ? " ui-btn-" + theme : "",
trackTheme = this.options.trackTheme || parentTheme,
trackThemeClass = trackTheme ? " ui-bar-" + trackTheme : " ui-bar-inherit",
cornerClass = this.options.corners ? " ui-corner-all" : "",
miniClass = this.options.mini ? " ui-mini" : "",
top, height, data, tol,
pyStep, percent,
control, isInput, optionElements, min, max, step,
newval, valModStep, alignValue, percentPerStep,
handlePercent, aPercent, bPercent,
valueChanged;
self.slider[0].className = [ this.isToggleSwitch ? "ui-slider ui-slider-switch ui-slider-track ui-shadow-inset" : "ui-slider-track ui-shadow-inset", trackThemeClass, cornerClass, miniClass ].join("");
if (this.options.disabled || this.element.prop("disabled")) {
this.disable();
}
// set the stored value for comparison later
this.value = this._value();
if (this.options.highlight && !this.isToggleSwitch && this.slider.find(".ui-slider-bg").length === 0) {
this.valuebg = (function() {
var bg = document.createElement("div");
bg.className = "ui-slider-bg " + $.mobile.activeBtnClass;
return $(bg).prependTo(self.slider);
})();
}
this.handle.addClass("ui-btn" + themeClass + " ui-shadow");
control = this.element;
isInput = !this.isToggleSwitch;
optionElements = isInput ? [] : control.find("option");
// invert min and max
trueMin = isInput ? parseFloat(control.attr("min")) : 0
trueMax = isInput ? parseFloat(control.attr("max")) : optionElements.length - 1;
min = trueMax * -1
max = trueMin * -1
/* original
min = isInput ? parseFloat(control.attr("min")) : 0
max = isInput ? parseFloat(control.attr("max")) : optionElements.length - 1;*/
step = (isInput && parseFloat(control.attr("step")) > 0) ? parseFloat(control.attr("step")) : 1;
if (typeof val === "object") {
data = val;
// a slight tolerance helped get to the ends of the slider
tol = 8;
top = this.slider.offset().top;
height = this.slider.height();
pyStep = height/((max-min)/step);
if (!this.dragging ||
data.pageY < top - tol ||
data.pageY > top + height + tol) {
return;
}
if (pyStep > 1) {
percent = ((data.pageY - top)/height) * 100;
} else {
percent = Math.round(((data.pageY - top)/height) * 100);
}
} else {
if (val == null) {
val = isInput ? parseFloat(control.val() * -1 || 0) : control[0].selectedIndex;
}
percent = (parseFloat(val) - min)/(max - min) * 100;
}
if (isNaN(percent)) {
return;
}
newval = (percent/100) * (max - min) + min;
//from jQuery UI slider, the following source will round to the nearest step
valModStep = (newval - min) % step;
alignValue = newval - valModStep;
if (Math.abs(valModStep) * 2 >= step) {
alignValue += (valModStep > 0) ? step : (-step);
}
percentPerStep = 100/((max-min)/step);
// Since JavaScript has problems with large floats, round
// the final value to 5 digits after the decimal point (see jQueryUI: #4124)
newval = parseFloat(alignValue.toFixed(5));
if (typeof pyStep === "undefined") {
pyStep = height/((max-min)/step);
}
if (pyStep > 1 && isInput) {
percent = (newval - min) * percentPerStep * (1/step);
}
if (percent < 0) {
percent = 0;
}
if (percent > 100) {
percent = 100;
}
if (newval < min) {
newval = min;
}
if (newval > max) {
newval = max;
}
newval *= -1;
this.handle.css("top", percent + "%");
this.handle.css("margin-left", "-5px");
this.handle[0].setAttribute("aria-valuenow", isInput ? newval : optionElements.eq(newval).attr("value"));
this.handle[0].setAttribute("aria-valuetext", isInput ? newval : optionElements.eq(newval).getEncodedText());
this.handle[0].setAttribute("title", isInput ? newval : optionElements.eq(newval).getEncodedText());
if (this.valuebg) {
this.valuebg.css("height", percent + "%");
}
// drag the label heights
if (this._labels) {
handlePercent = this.handle.height()/this.slider.height() * 100;
aPercent = percent && handlePercent + (100 - handlePercent) * percent/100;
bPercent = percent === 100 ? 0 : Math.min(handlePercent + 100 - aPercent, 100);
this._labels.each(function() {
var ab = $(this).hasClass("ui-slider-label-a");
$(this).height((ab ? aPercent : bPercent ) + "%");
});
}
if (!preventInputUpdate) {
valueChanged = false;
// update control"s value
if (isInput) {
valueChanged = control.val() !== newval;
control.val(newval);
} else {
valueChanged = control[ 0 ].selectedIndex !== newval;
control[ 0 ].selectedIndex = newval;
}
if (this._trigger("beforechange", val) === false) {
return false;
}
if (!isfromControl && valueChanged) {
control.trigger("change");
}
}
}
}
})
<table>
<tr>
<td>
<label for="vert">Vertical Slider:</label>
<input type="range" name="vert" id="vert" data-vertical="true" value="0" min="-20" max="20"/>
</td>
<td>
<input type="range" name="vert2" id="vert2" data-vertical="true" value="0" min="0" max="20"/>
</td>
<td>
<input type="range" name="vert3" id="vert3" data-vertical="true" value="0" min="-20" max="0"/>
</td>
</tr>
</table>
<hr>
<label for="horiz">Horizontal Slider:</label>
<input type="range" name="horiz" id="horiz" value="0" min="-20" max="20">
Вы смотрели на [это] (http://www.elmundio.net/projects/verticalSlider/)? – glomad
@itchy Да, я сделал. Но он нуждается в капитальном ремонте, даже его пример испорчен. И id rater не испортит текущую реализацию JQM. – Rayf
Вы когда-нибудь находили решение? Мне нужно что-то подобное. –