restructuring for PR for friendica main repo
[friendica.git/.git] / view / theme / frio / frameworks / jRange / jquery.range.js
1 /*jshint multistr:true, curly: false */
2 /*global jQuery:false, define: false */
3 /**
4  * jRange - Awesome range control
5  *
6  * Written by
7  * ----------
8  * Nitin Hayaran (nitinhayaran@gmail.com)
9  *
10  * Licensed under the MIT (MIT-LICENSE.txt).
11  *
12  * @author Nitin Hayaran
13  * @version 0.1-RELEASE
14  *
15  * Dependencies
16  * ------------
17  * jQuery (http://jquery.com)
18  *
19  **/
20 ;
21 (function($, window, document, undefined) {
22         'use strict';
23
24         var jRange = function() {
25                 return this.init.apply(this, arguments);
26         };
27         jRange.prototype = {
28                 defaults: {
29                         onstatechange: function() {},
30       ondragend: function() {},
31       onbarclicked: function() {},
32                         isRange: false,
33                         showLabels: true,
34                         showScale: true,
35                         step: 1,
36                         format: '%s',
37                         theme: 'theme-green',
38                         width: 300,
39                         disable: false,
40                         snap: false
41                 },
42                 template: '<div class="slider-container">\
43                         <div class="back-bar">\
44                 <div class="selected-bar"></div>\
45                 <div class="pointer low"></div><div class="pointer-label low">123456</div>\
46                 <div class="pointer high"></div><div class="pointer-label high">456789</div>\
47                 <div class="clickable-dummy"></div>\
48             </div>\
49             <div class="scale"></div>\
50                 </div>',
51                 init: function(node, options) {
52                         this.options       = $.extend({}, this.defaults, options);
53                         this.inputNode     = $(node);
54                         this.options.value = this.inputNode.val() || (this.options.isRange ? this.options.from + ',' + this.options.from : this.options.from);
55                         this.domNode       = $(this.template);
56                         this.domNode.addClass(this.options.theme);
57                         this.inputNode.after(this.domNode);
58                         this.domNode.on('change', this.onChange);
59                         this.pointers      = $('.pointer', this.domNode);
60                         this.lowPointer    = this.pointers.first();
61                         this.highPointer   = this.pointers.last();
62                         this.labels        = $('.pointer-label', this.domNode);
63                         this.lowLabel      = this.labels.first();
64                         this.highLabel     = this.labels.last();
65                         this.scale         = $('.scale', this.domNode);
66                         this.bar           = $('.selected-bar', this.domNode);
67                         this.clickableBar  = this.domNode.find('.clickable-dummy');
68                         this.interval      = this.options.to - this.options.from;
69                         this.render();
70                 },
71                 render: function() {
72                         // Check if inputNode is visible, and have some width, so that we can set slider width accordingly.
73                         if (this.inputNode.width() === 0 && !this.options.width) {
74                                 console.log('jRange : no width found, returning');
75                                 return;
76                         } else {
77                                 this.options.width = this.options.width || this.inputNode.width();
78                                 this.domNode.width(this.options.width);
79                                 this.inputNode.hide();
80                         }
81
82                         if (this.isSingle()) {
83                                 this.lowPointer.hide();
84                                 this.lowLabel.hide();
85                         }
86                         if (!this.options.showLabels) {
87                                 this.labels.hide();
88                         }
89                         this.attachEvents();
90                         if (this.options.showScale) {
91                                 this.renderScale();
92                         }
93                         this.setValue(this.options.value);
94                 },
95                 isSingle: function() {
96                         if (typeof(this.options.value) === 'number') {
97                                 return true;
98                         }
99                         return (this.options.value.indexOf(',') !== -1 || this.options.isRange) ?
100                                 false : true;
101                 },
102                 attachEvents: function() {
103                         this.clickableBar.click($.proxy(this.barClicked, this));
104                         this.pointers.on('mousedown touchstart', $.proxy(this.onDragStart, this));
105                         this.pointers.bind('dragstart', function(event) {
106                                 event.preventDefault();
107                         });
108                 },
109                 onDragStart: function(e) {
110                         if ( this.options.disable || (e.type === 'mousedown' && e.which !== 1)) {
111                                 return;
112                         }
113                         e.stopPropagation();
114                         e.preventDefault();
115                         var pointer = $(e.target);
116                         this.pointers.removeClass('last-active');
117                         pointer.addClass('focused last-active');
118                         this[(pointer.hasClass('low') ? 'low' : 'high') + 'Label'].addClass('focused');
119                         $(document).on('mousemove.slider touchmove.slider', $.proxy(this.onDrag, this, pointer));
120                         $(document).on('mouseup.slider touchend.slider touchcancel.slider', $.proxy(this.onDragEnd, this));
121                 },
122                 onDrag: function(pointer, e) {
123                         e.stopPropagation();
124                         e.preventDefault();
125
126                         if (e.originalEvent.touches && e.originalEvent.touches.length) {
127                                 e = e.originalEvent.touches[0];
128                         } else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
129                                 e = e.originalEvent.changedTouches[0];
130                         }
131
132                         var position = e.clientX - this.domNode.offset().left;
133                         this.domNode.trigger('change', [this, pointer, position]);
134                 },
135                 onDragEnd: function(e) {
136                         this.pointers.removeClass('focused')
137                                 .trigger('rangeslideend');
138                         this.labels.removeClass('focused');
139                         $(document).off('.slider');
140                   this.options.ondragend.call(this, this.options.value);
141                 },
142                 barClicked: function(e) {
143                         if(this.options.disable) return;
144                         var x = e.pageX - this.clickableBar.offset().left;
145                         if (this.isSingle())
146                                 this.setPosition(this.pointers.last(), x, true, true);
147                         else {
148                                 var firstLeft           = Math.abs(parseFloat(this.pointers.first().css('left'), 10)),
149                                                 firstHalfWidth  = this.pointers.first().width() / 2,
150                                                 lastLeft                                = Math.abs(parseFloat(this.pointers.last().css('left'), 10)),
151                                                 lastHalfWidth   = this.pointers.first().width() / 2,
152                                                 leftSide        = Math.abs(firstLeft - x + firstHalfWidth),
153                                                 rightSide       = Math.abs(lastLeft - x + lastHalfWidth),
154                                                 pointer;
155
156                                 if(leftSide == rightSide) {
157                                         pointer = x < firstLeft ? this.pointers.first() : this.pointers.last();
158                                 } else {
159                                         pointer = leftSide < rightSide ? this.pointers.first() : this.pointers.last();
160                                 }
161                                 this.setPosition(pointer, x, true, true);
162                         }
163                         this.options.onbarclicked.call(this, this.options.value);
164                 },
165                 onChange: function(e, self, pointer, position) {
166                         var min, max;
167                         min = 0;
168                         max = self.domNode.width();
169
170                         if (!self.isSingle()) {
171                                 min = pointer.hasClass('high') ? parseFloat(self.lowPointer.css("left")) + (self.lowPointer.width() / 2) : 0;
172                                 max = pointer.hasClass('low') ? parseFloat(self.highPointer.css("left")) + (self.highPointer.width() / 2) : self.domNode.width();
173                         }
174
175                         var value = Math.min(Math.max(position, min), max);
176                         self.setPosition(pointer, value, true);
177                 },
178                 setPosition: function(pointer, position, isPx, animate) {
179                         var leftPos, rightPos,
180                                 lowPos = parseFloat(this.lowPointer.css("left")),
181                                 highPos = parseFloat(this.highPointer.css("left")) || 0,
182                                 circleWidth = this.highPointer.width() / 2;
183                         if (!isPx) {
184                                 position = this.prcToPx(position);
185                         }
186                         if(this.options.snap){
187                                 var expPos = this.correctPositionForSnap(position);
188                                 if(expPos === -1){
189                                         return;
190                                 }else{
191                                         position = expPos;
192                                 }
193                         }
194                         if (pointer[0] === this.highPointer[0]) {
195                                 highPos = Math.round(position - circleWidth);
196                         } else {
197                                 lowPos = Math.round(position - circleWidth);
198                         }
199                         pointer[animate ? 'animate' : 'css']({
200                                 'left': Math.round(position - circleWidth)
201                         });
202                         if (this.isSingle()) {
203                                 leftPos = 0;
204                         } else {
205                                 leftPos = lowPos + circleWidth;
206                                 rightPos = highPos + circleWidth;
207                         }
208                         var w = Math.round(highPos + circleWidth - leftPos);
209                         this.bar[animate ? 'animate' : 'css']({
210                                 'width': Math.abs(w),
211                                 'left': (w>0) ? leftPos : leftPos + w
212                         });
213                         this.showPointerValue(pointer, position, animate);
214                         this.isReadonly();
215                 },
216                 correctPositionForSnap: function(position){
217                         var currentValue = this.positionToValue(position) - this.options.from;
218                         var diff = this.options.width / (this.interval / this.options.step),
219                                 expectedPosition = (currentValue / this.options.step) * diff;
220                         if( position <= expectedPosition + diff / 2 && position >= expectedPosition - diff / 2){
221                                 return expectedPosition;
222                         }else{
223                                 return -1;
224                         }
225                 },
226                 // will be called from outside
227                 setValue: function(value) {
228                         var values = value.toString().split(',');
229                         values[0] = Math.min(Math.max(values[0], this.options.from), this.options.to) + '';
230                         if (values.length > 1){
231                                 values[1] = Math.min(Math.max(values[1], this.options.from), this.options.to) + '';
232                         }
233                         this.options.value = value;
234                         var prc = this.valuesToPrc(values.length === 2 ? values : [0, values[0]]);
235                         if (this.isSingle()) {
236                                 this.setPosition(this.highPointer, prc[1]);
237                         } else {
238                                 this.setPosition(this.lowPointer, prc[0]);
239                                 this.setPosition(this.highPointer, prc[1]);
240                         }
241                 },
242                 renderScale: function() {
243                         var s = this.options.scale || [this.options.from, this.options.to];
244                         var prc = Math.round((100 / (s.length - 1)) * 10) / 10;
245                         var str = '';
246                         for (var i = 0; i < s.length; i++) {
247                                 str += '<span style="left: ' + i * prc + '%">' + (s[i] != '|' ? '<ins>' + s[i] + '</ins>' : '') + '</span>';
248                         }
249                         this.scale.html(str);
250
251                         $('ins', this.scale).each(function() {
252                                 $(this).css({
253                                         marginLeft: -$(this).outerWidth() / 2
254                                 });
255                         });
256                 },
257                 getBarWidth: function() {
258                         var values = this.options.value.split(',');
259                         if (values.length > 1) {
260                                 return parseFloat(values[1]) - parseFloat(values[0]);
261                         } else {
262                                 return parseFloat(values[0]);
263                         }
264                 },
265                 showPointerValue: function(pointer, position, animate) {
266                         var label = $('.pointer-label', this.domNode)[pointer.hasClass('low') ? 'first' : 'last']();
267                         var text;
268                         var value = this.positionToValue(position);
269                         // Is it higer or lower than it should be?
270
271                         if ($.isFunction(this.options.format)) {
272                                 var type = this.isSingle() ? undefined : (pointer.hasClass('low') ? 'low' : 'high');
273                                 text = this.options.format(value, type);
274                         } else {
275                                 text = this.options.format.replace('%s', value);
276                         }
277
278                         var width = label.html(text).width(),
279                                 left = position - width / 2;
280                         left = Math.min(Math.max(left, 0), this.options.width - width);
281                         label[animate ? 'animate' : 'css']({
282                                 left: left
283                         });
284                         this.setInputValue(pointer, value);
285                 },
286                 valuesToPrc: function(values) {
287                         var lowPrc = ((parseFloat(values[0]) - parseFloat(this.options.from)) * 100 / this.interval),
288                                 highPrc = ((parseFloat(values[1]) - parseFloat(this.options.from)) * 100 / this.interval);
289                         return [lowPrc, highPrc];
290                 },
291                 prcToPx: function(prc) {
292                         return (this.domNode.width() * prc) / 100;
293                 },
294                 isDecimal: function() {
295                         return ((this.options.value + this.options.from + this.options.to).indexOf(".")===-1) ? false : true;
296                 },
297                 positionToValue: function(pos) {
298                         var value = (pos / this.domNode.width()) * this.interval;
299                         value = parseFloat(value, 10) + parseFloat(this.options.from, 10);
300                         if (this.isDecimal()) {
301                                 var final = Math.round(Math.round(value / this.options.step) * this.options.step *100)/100;
302                                 if (final!==0.0) {
303                                         final = '' + final;
304                                         if (final.indexOf(".")===-1) {
305                                                 final = final + ".";
306                                         }
307                                         while (final.length - final.indexOf('.')<3) {
308                                                 final = final + "0";
309                                         }
310                                 } else {
311                                         final = "0.00";
312                                 }
313                                 return final;
314                         } else {
315                                 return Math.round(value / this.options.step) * this.options.step;
316                         }
317                 },
318                 setInputValue: function(pointer, v) {
319                         // if(!isChanged) return;
320                         if (this.isSingle()) {
321                                 this.options.value = v.toString();
322                         } else {
323                                 var values = this.options.value.split(',');
324                                 if (pointer.hasClass('low')) {
325                                         this.options.value = v + ',' + values[1];
326                                 } else {
327                                         this.options.value = values[0] + ',' + v;
328                                 }
329                         }
330                         if (this.inputNode.val() !== this.options.value) {
331                                 this.inputNode.val(this.options.value)
332                                         .trigger('change');
333                                 this.options.onstatechange.call(this, this.options.value);
334                         }
335                 },
336                 getValue: function() {
337                         return this.options.value;
338                 },
339                 getOptions: function() {
340                         return this.options;
341                 },
342                 getRange: function() {
343                         return this.options.from + "," + this.options.to;
344                 },
345                 isReadonly: function(){
346                         this.domNode.toggleClass('slider-readonly', this.options.disable);
347                 },
348                 disable: function(){
349                         this.options.disable = true;
350                         this.isReadonly();
351                 },
352                 enable: function(){
353                         this.options.disable = false;
354                         this.isReadonly();
355                 },
356                 toggleDisable: function(){
357                         this.options.disable = !this.options.disable;
358                         this.isReadonly();
359                 },
360                 updateRange: function(range, value) {
361                         var values = range.toString().split(',');
362                         this.interval = parseInt(values[1]) - parseInt(values[0]);
363                         if(value){
364                                 this.setValue(value);
365                         }else{
366                                 this.setValue(this.getValue());
367                         }
368                 }
369         };
370
371         var pluginName = 'jRange';
372         // A really lightweight plugin wrapper around the constructor,
373         // preventing against multiple instantiations
374         $.fn[pluginName] = function(option) {
375                 var args = arguments,
376                         result;
377
378                 this.each(function() {
379                         var $this = $(this),
380                                 data = $.data(this, 'plugin_' + pluginName),
381                                 options = typeof option === 'object' && option;
382                         if (!data) {
383                                 $this.data('plugin_' + pluginName, (data = new jRange(this, options)));
384                                 $(window).resize(function() {
385                                         data.setValue(data.getValue());
386                                 }); // Update slider position when window is resized to keep it in sync with scale
387                         }
388                         // if first argument is a string, call silimarly named function
389                         // this gives flexibility to call functions of the plugin e.g.
390                         //   - $('.dial').plugin('destroy');
391                         //   - $('.dial').plugin('render', $('.new-child'));
392                         if (typeof option === 'string') {
393                                 result = data[option].apply(data, Array.prototype.slice.call(args, 1));
394                         }
395                 });
396
397                 // To enable plugin returns values
398                 return result || this;
399         };
400
401 })(jQuery, window, document);