PDA

View Full Version : trying a multitracker Slider



Rodolfo
30 Aug 2009, 6:16 PM
Hi, this a simple copy of the Ext.Slider with the particularity that you can define n numbers of "trackers"...

Im using this widget specificly for get and set alpha values of multiple layers in OpenLayers (Map Framework), but Im sure could be useful for some body else in a complete different context.

example


var tracks = [
{name: 'track_1', value: 100},
{name: 'track_2', value: 50},
{name: 'track_3', value: 60},
{name: 'track_4', value: 10}
]

/* tip plugin */
var tip = new Ext.ux.SliderTip();

var slider = new Ext.ux.Slider({
renderTo: 'horizontal-slider',
width: 400,
increment: 10,
minValue: 0,
maxValue: 200,
tracks: tracks,
overlap: false,
plugins: tip
});


http://github.com/warfares/Ext.ux.Slider

TODO: get tracker focus
TODO: move trackers with keys

testing on: Safari 4.0, FireFox 3.5, Chrome 2.0, IE 8.0
PD: take care with IE, the sample code use the console for testing events.


Grettings.
Rodolfo B.
rbarriga.blogspot.com

Ext.ux.Slider


/**
* multiple track slider
*
* @author Rodolfo Barriga S.
* @class Ext.ux.Slider
* @extends Ext.BoxComponent
*
*/

Ext.ux.Slider = Ext.extend(Ext.BoxComponent, {

/**
* @cfg {Array} array with track object
*/
tracks: [],
/**
* @cfg {Boolean} overlap prevent overlap of the sliders, Defaults to true
*/
overlap: true,
/**
* @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
*/
vertical: false,
/**
* @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
*/
minValue: 0,
/**
* @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
*/
maxValue: 100,
/**
* @cfg {Number/Boolean} decimalPrecision.
* <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
* <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
*/
decimalPrecision: 0,
/**
* @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead.
*/
keyIncrement: 1,
/**
* @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
*/
increment: 0,
//private
clickRange: [5, 15],
/**
* @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
*/
clickToChange: true,
/**
* @cfg {Boolean} animate Turn on or off animation. Defaults to true
*/
animate: true,
/**
* True while the thumb is in a drag operation
* @type boolean
*/
dragging: false,

// private override
initComponent: function() {

Ext.ux.Slider.superclass.initComponent.call(this);
this.keyIncrement = Math.max(this.increment, this.keyIncrement);

this.addEvents(
'beforechange',
'change',
'changecomplete',
'dragstart',
'drag',
'dragend'
);

if (this.vertical) {
Ext.apply(this, Ext.ux.Slider.Vertical);
}
},

// private override
onRender: function() {
this.autoEl = {
cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
cn: {
cls: 'x-slider-end',
cn: { tag: 'div', cls: 'x-slider-inner' }
}
};

Ext.ux.Slider.superclass.onRender.apply(this, arguments);

this.endEl = this.el.first();
this.innerEl = this.endEl.first();

//default value
if (this.tracks.length === 0)
this.tracks.push({name:'',value:this.minValue});

this.length = this.tracks.length;

this.thumbs = [];
for (var i = 0; i < this.length; i++) {
var thumb = this.innerEl.createChild({ tag: 'div', cls: 'x-slider-thumb' });
this.thumbs.push(thumb);
};

var thumb = this.thumbs[0];
this.halfThumb = (this.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
this.initEvents();
},
// private override
initEvents: function() {
this.mon(this.el, {
scope: this,
mousedown: this.onMouseDown,
keydown: this.onKeyDown
});

for (var i = 0; i < this.length; i++) {
var thumb = this.thumbs[i];
thumb.addClassOnOver('x-slider-thumb-over');

var tracker = new Ext.dd.DragTracker({
onBeforeStart: this.onBeforeDragStart.createDelegate(this),
onStart: this.onDragStart.createDelegate(this, i, true),
onDrag: this.onDrag.createDelegate(this, i, true),
onEnd: this.onDragEnd.createDelegate(this, i, true),
tolerance: 3,
autoStart: 300
});
tracker.initEl(thumb);
};
//TODO destroy trackers
//this.on('beforedestroy', this.thumbs.destroy, this.thumbs);
},
// private override
onMouseDown: function(e) {
if (this.disabled) { return; }
for (var i = 0; i < this.length; i++) {
var thumb = this.thumbs[i];
if (e.target == thumb.dom)
return
};
if (this.clickToChange) {
var local = this.innerEl.translatePoints(e.getXY());
this.onClickChange(local);
}
this.focus();
}
,
// private
onClickChange: function(local) {
if (local.top > this.clickRange[0] && local.top < this.clickRange[1]) {
var v = Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision);
this.setValue(this.getIndexByMinDist(v), v, undefined, true);
}
},
//private
getIndexByMinDist: function(v) {
var dist = Math.abs(this.tracks[0].value - v);
var index = 0;

for (var i = 1; i < this.length; i++) {
var tr = this.tracks[i];
if (dist > Math.abs(tr.value - v)) {
var dist = Math.abs(tr.value - v);
index = i;
}
};
return index;
},
//private
getIndexArraySortByValue: function() {
var indexs = [];
for (var i = 0; i < this.length; i++) {
var tr = this.tracks[i];
indexs.push({ index: i, value: tr.value });
};

var fc = function(a, b) { return a.value - b.value; }
return indexs.sort(fc);
},
//private
getIndexNeighborhood: function(index) {
var indexs = this.getIndexArraySortByValue();

if (this.length > 1) {
var max = this.length - 1;

if (indexs[0].index === index) {
var tIndex = indexs[1].index;
return { dIndex: null, tIndex:tIndex};
}

if (indexs[max].index === index) {
var dIndex = indexs[(max - 1)].index;
return { dIndex:dIndex, tIndex: null };
}

for (var i = 1; i < max; i++) {
if (indexs[i].index === index) {
var dIndex = indexs[i - 1].index;
var tIndex = indexs[i + 1].index;
return { dIndex:dIndex, tIndex:tIndex};
}
};
};
},
//private
checkOverlap: function(index, v) {
var nb = this.getIndexNeighborhood(index);
var tValue = nb.tIndex !== null ? this.getValue(nb.tIndex) : this.maxValue + 1;
var dValue = nb.dIndex !== null ? this.getValue(nb.dIndex) : this.minValue - 1;
if (dValue >= v || v >= tValue)
return false;

return true;
}
,
// private //TODO
onKeyDown: function(e) {
if (this.disabled) { e.preventDefault(); return; }
var k = e.getKey();
switch (k) {
case e.UP:
case e.RIGHT:
break;
case e.DOWN:
case e.LEFT:
break;
default:
e.preventDefault();
}
},
// private
doSnap: function(value) {
if (!this.increment || this.increment == 1 || !value) {
return value;
}
var newValue = value, inc = this.increment;
var m = value % inc;
if (m != 0) {
newValue -= m;
if (m * 2 > inc) {
newValue += inc;
} else if (m * 2 < -inc) {
newValue -= inc;
}
}
return newValue.constrain(this.minValue, this.maxValue);
},
// private override
afterRender: function() {
Ext.ux.Slider.superclass.afterRender.apply(this, arguments);
for (var i = 0; i < this.length; i++) {
var tr = this.tracks[i];
var value = tr.value;
if (value !== undefined) {
var v = this.normalizeValue(value);
if (v !== value) {
this.setValue(i, v, false);
} else {
this.moveThumb(i, this.translateValue(v), false);
}
}
}
},
// private
getRatio: function() {
var w = this.innerEl.getWidth();
var v = this.maxValue - this.minValue;
return v == 0 ? w : (w / v);
},

// private
normalizeValue: function(v) {
v = this.doSnap(v);
v = Ext.util.Format.round(v, this.decimalPrecision);
v = v.constrain(this.minValue, this.maxValue);
return v;
},

setValue: function(index, v, animate, changeComplete) {
v = this.normalizeValue(v);

if (this.length > 1 && !this.overlap && !this.checkOverlap(index, v))
return;

var tr = this.tracks[index];
if (v !== tr.value && this.fireEvent('beforechange', this, index) !== false) {
tr.value = v;
this.moveThumb(index, this.translateValue(v), animate !== false);
this.fireEvent('change', this, index);
if (changeComplete) {
this.fireEvent('changecomplete', this, index);
}
}
},
// private
translateValue: function(v) {
var ratio = this.getRatio();
return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
},
// private
reverseValue: function(pos) {
var ratio = this.getRatio();
return (pos + this.halfThumb + (this.minValue * ratio)) / ratio;
},

// private
moveThumb: function(index, v, animate) {
var thumb = this.thumbs[index];
if (!animate || this.animate === false) {
thumb.setLeft(v);
} else {
thumb.shift({ left: v, stopFx: true, duration: .35 });
}
},
// private //TODO
focus: function() {
//this.focusEl.focus(10);
},
// private
onBeforeDragStart: function(e) {
return !this.disabled;
},
// private
onDragStart: function(e, i) {
var tr = this.tracks[i];
var thumb = this.thumbs[i];
thumb.addClass('x-slider-thumb-drag');
this.dragging = true;
this.dragStartValue = tr.value;
this.fireEvent('dragstart', this, e, i);
},

// private
onDrag: function(e, i) {
var pos = this.innerEl.translatePoints(e.getXY());
this.setValue(i, Ext.util.Format.round(this.reverseValue(pos.left), this.decimalPrecision), false);
this.fireEvent('drag', this, e, i);
},

// private
onDragEnd: function(e, i) {
var tr = this.tracks[i];
var thumb = this.thumbs[i];
thumb.removeClass('x-slider-thumb-drag');

this.dragging = false;
this.fireEvent('dragend', this, e, i);
if (this.dragStartValue != tr.value) {
this.fireEvent('changecomplete', this, i);
}
},

onResize: function(w, h) {
this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
this.syncThumb();
},

//private
onDisable: function() {
Ext.ux.Slider.superclass.onDisable.call(this);
for (var i = 0; i < this.length; i++) {
var thumb = this.thumbs[i];
thumb.addClass(this.disabledClass);

if (Ext.isIE) {
//IE breaks when using overflow visible and opacity other than 1.
//Create a place holder for the thumb and display it.
var xy = thumb.getXY();
thumb.hide();
this.innerEl.addClass(this.disabledClass).dom.disabled = true;
if (!this.thumbHolder) {
this.thumbHolder = this.endEl.createChild({ cls: 'x-slider-thumb ' + this.disabledClass });
}
this.thumbHolder.show().setXY(xy);
}
}
},
//private
onEnable: function() {
Ext.Slider.superclass.onEnable.call(this);
for (var i = 0; i < this.length; i++) {
var thumb = this.thumbs[i];
thumb.removeClass(this.disabledClass);
if (Ext.isIE) {
this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
if (this.thumbHolder) {
this.thumbHolder.hide();
}
thumb.show();
this.syncThumb();
}
}
},
syncThumb: function() {
if (this.rendered) {
for (var i = 0; i < this.length; i++) {
var tr = this.tracks[i];
this.moveThumb(i, this.translateValue(tr.value), true);
}
}
},
getValue: function(i) {
var tr = this.tracks[i];
return tr.value;

},
getName: function(i) {
var tr = this.tracks[i];
return tr.name;
},
getTrack: function(i) {
return this.tracks[i];
}
});
Ext.reg('ux.silder', Ext.ux.Slider);


// private class to support vertical sliders
Ext.ux.Slider.Vertical = {
onResize: function(w, h) {
this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
this.syncThumb();
},
getRatio: function() {
var h = this.innerEl.getHeight();
var v = this.maxValue - this.minValue;
return h / v;
},
moveThumb: function(index, v, animate) {
var thumb = this.thumbs[index];
if (!animate || this.animate === false) {
thumb.setBottom(v);
} else {
thumb.shift({ bottom: v, stopFx: true, duration: .35 });
}
},
onDrag: function(e,index) {
var pos = this.innerEl.translatePoints(e.getXY());
var bottom = this.innerEl.getHeight() - pos.top;
this.setValue(index, this.minValue + Ext.util.Format.round(bottom / this.getRatio(), this.decimalPrecision), false);
this.fireEvent('drag', this, e,index);
},
onClickChange: function(local) {
if (local.left > this.clickRange[0] && local.left < this.clickRange[1]) {
var bottom = this.innerEl.getHeight() - local.top;
var v = this.minValue + Math.round(bottom / this.getRatio());
this.setValue(this.getIndexByMinDist(v), v, undefined, true);
}
}
};



Ext.ux.SliderTip


/**
* Ext.ux.SliderTip Plugin for tips
* @author Rodolfo Barriga
* @class Ext.ux.SliderTip
* @extends Ext.Tip
*
*/

Ext.ux.SliderTip = Ext.extend(Ext.Tip, {
minWidth: 10,
offsets: [0, -10],
init: function(slider) {
slider.on('dragstart', this.onSlide, this);
slider.on('drag', this.onSlide, this);
slider.on('dragend', this.hide, this);
slider.on('destroy', this.destroy, this);
},
onSlide: function(slider, e, i) {
this.show();
this.body.update(this.getText(slider, i));
this.doAutoWidth();

var thumb = slider.thumbs[i];
this.el.alignTo(thumb, 'b-t?', this.offsets);
},
getText: function(slider, i) {
return slider.getValue(i) + '';
}
});



Sample code


<html>
<head>
<title>Sample</title>
<!-- ExtJS Style -->
<link rel="stylesheet" type="text/css" href="ext-3.0.0/resources/css/ext-all.css"/>

<!-- ExtJS 3.0.0 -->
<script type="text/javascript" src="ext-3.0.0/adapter/ext/ext-base.js" ></script>

<!-- ExtJS 3.0.0 Build -->
<script type="text/javascript" src="ext-3.0.0/ext-all.js" ></script>

<!-- Slider -->
<script type="text/javascript" src="Ext.ux.Slider.js" ></script>
<script type="text/javascript" src="Ext.ux.SliderTip.js" ></script>

<!-- Custom Slider Style -->
<link href="ext-3.0.0/examples/slider/slider.css" rel="stylesheet" type="text/css" />

<!-- Common Styles for the examples -->
<link href="ext-3.0.0/examples/shared/examples.css" rel="stylesheet" type="text/css" />
</head>

<body>

<script>

Ext.BLANK_IMAGE_URL = 'ext-3.0.0/resources/images/default/s.gif';

// application main entry point
Ext.onReady(function() {
Ext.QuickTips.init();

/* horizontal demo */
var tracks = [
{name: 'track_1', value: 100},
{name: 'track_2', value: 50},
{name: 'track_3', value: 60},
{name: 'track_4', value: 10}
]

/* tip plugin */
var tip = new Ext.ux.SliderTip();

var slider = new Ext.ux.Slider({
renderTo: 'horizontal-slider',
width: 400,
increment: 10,
minValue: 0,
maxValue: 200,
tracks: tracks,
overlap: false,
plugins: tip
});

slider.on('change', function(s, i) { console.log('changed[' + slider.getName(i) + ']: ' + slider.getValue(i)); });


/* vertical demo */
var tracks_1 = [
{ name: 'track_1', value: 0},
{ name: 'track_2', value: 100},
{ name: 'track_3', value: 200}
]

/* custom tip plugin*/
var tip_1 = new Ext.ux.SliderTip({
getText: function(slider, i) {
return String.format('<b>{1} <br/> value : {0}</b>', slider.getValue(i), slider.getName(i));
}
});

var sliderVertical = new Ext.ux.Slider({
renderTo: 'vertical-slider',
vertical: true,
increment: 10,
minValue: 0,
maxValue: 200,
height: 200,
tracks: tracks_1,
plugins: tip_1
});

sliderVertical.on('change', function(slider, i) { console.log('changed[' + slider.getName(i) + ']: ' + slider.getValue(i)); });

/* custom style demo */
var tracks_2 = [
{ name: 'A', value: 10},
{ name: 'B', value: 90}
]

var sliderCustomCss = new Ext.ux.Slider({
renderTo: 'custom-slider',
width: 214,
increment: 10,
minValue: 0,
maxValue: 100,
tracks: tracks_2,
overlap:false
});

});
// eo function onReady
</script>

<h1>Ext ux Slider Example</h1>
<p>Sliders support multiple trackers</p>

<h3>Horizontal Slider</h3>
<div id="horizontal-slider"></div>
<br/>

<h3>Vertical Slider</h3>
<div id="vertical-slider"></div>
<br/>

<h3>CSS Customized Slider</h3>
<div id="custom-slider"></div>
</html>

Animal
31 Aug 2009, 12:22 AM
Nice work.

I think that this should be integrated into the main Ext Slider. A bit like the outstanding Feature Request suggestion for TriggerField to be able to have any number of Triggers, but defaulting to 1.

Ext.Slider should be able to handle multiple trackers.

Rodolfo
31 Aug 2009, 10:18 PM
Thanks :)

- update: added overlap property: for prevent overlap of the trackers. (default : true).

steffenk
1 Sep 2009, 4:12 AM
Nice extension! Really useful for coloring things.

hankin
15 Sep 2009, 10:46 AM
I think I may have found a potential defect. It could also be my code. Is anybody else experiencing this functionality. I am testing in IE 7 and I am seeing both track points on top of each other no matter what values I put in. Any help would be greatly appreciated.

var tracks = [
{name: 'track_1', value: 10},
{name: 'track_2', value: 100}
]

/* tip plugin */
var tip = new Ext.ux.SliderTip();

var slider = new Ext.ux.Slider({
renderTo: 'custom-slider',
width: 400,
increment: 10,
minValue: 0,
maxValue: 200,
tracks: tracks,
overlap: false,
plugins: tip
});

aw1zard2
15 Sep 2009, 11:08 AM
I think I may have found a potential defect. It could also be my code. Is anybody else experiencing this functionality. I am testing in IE 7 and I am seeing both track points on top of each other no matter what values I put in. Any help would be greatly appreciated.


var tracks = [
{name: 'track_1', value: 10},
{name: 'track_2', value: 100}
]

/* tip plugin */
var tip = new Ext.ux.SliderTip();

var slider = new Ext.ux.Slider({
renderTo: 'custom-slider',
width: 400,
increment: 10,
minValue: 0,
maxValue: 200,
tracks: tracks,
overlap: false,
plugins: tip
});

Are you using the sample code above or just the Example code. Cause the sample code with the EXTJS includes does a lot more then just the example code. If you are using the sample code you need to paste a bit more then just that little code piece. Since the custom-slider uses the css file. And to be helpful use the CODE tags around code.

:)

hankin
15 Sep 2009, 1:05 PM
Sorry, I had a mis-linked path that was causing my issues. The code worked fine once i got it linked properly.

t800t8
14 Oct 2009, 6:07 PM
Very nice plugin. I'm looking for it. Thanks :-)

NikNik77771
9 Jan 2010, 2:22 AM
Very useful plugin. I am looking for same in ext-gwt? Does somebody know this stuff?