diff --git a/README.md b/README.md index 1b9e41b..bbb02ac 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,72 @@ Each module has options. Some of options are common between modules and some are +### Number input + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionData TypeDefault
pressIntervalSpecify the interval which will control how fast the value is changing while the buttons are pressed.number150
disabledClassSpecify class which will be added to arrow buttons when maximum or minimum number is reachedstring"jcf-disabled"
+ +### Range input + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionData TypeDefault
orientationSpecify range input orientation: "horizontal" or "vertical"stringhorizontal
dragHandleCenterEnable this option to make the cursor stick to the center of the input handlebooleantrue
snapToMarksSnap input handle to HTML5 datalist marksbooleantrue
snapRadiusSpecify snapping radius in pixelsnumber5
+ ### File diff --git a/css/theme-minimal/jcf.css b/css/theme-minimal/jcf.css index a3f57e8..915bb2c 100755 --- a/css/theme-minimal/jcf.css +++ b/css/theme-minimal/jcf.css @@ -378,6 +378,143 @@ body > .jcf-select-drop.jcf-drop-flipped { content: ''; } +/* number input */ +.jcf-number { + display: inline-block; + position: relative; + height: 32px; +} +.jcf-number input {-moz-appearance: textfield;} +.jcf-number input::-webkit-inner-spin-button, +.jcf-number input::-webkit-outer-spin-button {-webkit-appearance: none;} +.jcf-number input { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid #777; + padding: 3px 27px 3px 7px; + margin: 0; + height: 100%; +} +.jcf-number .jcf-btn-dec, +.jcf-number .jcf-btn-inc { + position: absolute; + background: #aaa; + width: 20px; + height: 15px; + right: 1px; + top: 1px; +} +.jcf-number .jcf-btn-dec { + top: auto; + bottom: 1px; +} +.jcf-number .jcf-btn-dec:hover, +.jcf-number .jcf-btn-inc:hover { + background: #e6e6e6; +} +.jcf-number.jcf-disabled .jcf-btn-dec:hover, +.jcf-number.jcf-disabled .jcf-btn-inc:hover { + background: #aaa; +} +.jcf-number .jcf-btn-dec:before, +.jcf-number .jcf-btn-inc:before { + position: absolute; + content: ''; + width: 0; + height: 0; + top: 50%; + left: 50%; + margin: -6px 0 0 -4px; + border: 4px solid #aaa; + border-color: transparent transparent #000 transparent; +} +.jcf-number .jcf-btn-dec:before { + margin: -1px 0 0 -4px; + border-color: #000 transparent transparent transparent; +} +.jcf-number.jcf-disabled .jcf-btn-dec:before, +.jcf-number.jcf-disabled .jcf-btn-inc:before, +.jcf-number .jcf-btn-dec.jcf-disabled:before, +.jcf-number .jcf-btn-inc.jcf-disabled:before { + opacity: 0.3; +} +.jcf-number.jcf-disabled input { + background: #ddd; +} + +/* range input */ +.jcf-range { + display: inline-block; + min-width: 200px; + margin: 0 10px; + width: 130px; +} +.jcf-range .jcf-range-track { + margin: 0 20px 0 0; + position: relative; + display: block; +} +.jcf-range .jcf-range-wrapper { + background: #e5e5e5; + border-radius: 5px; + display: block; + margin: 5px 0; + height: 10px; +} +.jcf-range.jcf-vertical { + width: auto; +} +.jcf-range.jcf-vertical .jcf-range-wrapper { + margin: 0; + width: 10px; + height: auto; + padding: 20px 0 0; +} +.jcf-range.jcf-vertical .jcf-range-track { + height: 180px; + width: 10px; +} +.jcf-range.jcf-vertical .jcf-range-handle { + left: -5px; + top: auto; +} +.jcf-range .jcf-range-handle { + position: absolute; + background: #aaa; + border-radius: 19px; + width: 19px; + height: 19px; + margin: -4px 0 0; + z-index: 1; + top: 0; + left: 0; +} +.jcf-range .jcf-range-mark { + position: absolute; + overflow: hidden; + background: #000; + width: 1px; + height: 3px; + top: -7px; + margin: 0 0 0 9px; +} +.jcf-range.jcf-vertical .jcf-range-mark { + margin: 0 0 9px; + left: 14px; + top: auto; + width: 3px; + height: 1px; +} +.jcf-range.jcf-focus .jcf-range-handle { + border: 1px solid #f00; + margin: -5px 0 0 -1px; +} +.jcf-range.jcf-disabled { + background: none !important; + opacity: 0.3; +} + /* common styles */ .jcf-disabled {background: #ddd !important;} .jcf-focus, .jcf-focus * {border-color: #f00 !important;} \ No newline at end of file diff --git a/index.html b/index.html index eccd676..3050520 100755 --- a/index.html +++ b/index.html @@ -11,7 +11,9 @@ + + @@ -33,7 +35,52 @@

JavaScript Custom Forms

This library allows crossbrowser form elements customization using CSS.

Supported elements are following: select, checkbox, radio, file input, textarea and scrollbars (and more coming)

- + + +
+

Number inputs

+ + + + +
+ +
+

Range inputs

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+ + + + + + + + + + +
diff --git a/js/jcf.button.js b/js/jcf.button.js index 898ee10..d39e641 100644 --- a/js/jcf.button.js +++ b/js/jcf.button.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict'; diff --git a/js/jcf.checkbox.js b/js/jcf.checkbox.js index 19ce94d..89b5fde 100755 --- a/js/jcf.checkbox.js +++ b/js/jcf.checkbox.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict'; diff --git a/js/jcf.file.js b/js/jcf.file.js index 3c92465..42868a7 100644 --- a/js/jcf.file.js +++ b/js/jcf.file.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict'; diff --git a/js/jcf.js b/js/jcf.js index e2f3ed4..fc46366 100755 --- a/js/jcf.js +++ b/js/jcf.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function (root, factory) { if (typeof define === 'function' && define.amd) { diff --git a/js/jcf.number.js b/js/jcf.number.js new file mode 100644 index 0000000..b9e1d0a --- /dev/null +++ b/js/jcf.number.js @@ -0,0 +1,154 @@ +/*! + * JavaScript Custom Forms : Number Module + * + * Copyright 2014 PSD2HTML (http://psd2html.com) + * Released under the MIT license (LICENSE.txt) + * + * Version: 1.0.3 + */ +;(function($, window) { + 'use strict'; + + jcf.addModule({ + name: 'Number', + selector: 'input[type="number"]', + options: { + realElementClass: 'jcf-real-element', + fakeStructure: '', + btnIncSelector: '.jcf-btn-inc', + btnDecSelector: '.jcf-btn-dec', + pressInterval: 150 + }, + matchElement: function(element) { + return element.is(this.selector); + }, + init: function(options) { + this.initStructure(); + this.attachEvents(); + this.refresh(); + }, + initStructure: function() { + this.page = $('html'); + this.realElement = $(this.options.element).addClass(this.options.realElementClass); + this.fakeElement = $(this.options.fakeStructure).insertBefore(this.realElement).prepend(this.realElement); + this.btnDec = this.fakeElement.find(this.options.btnDecSelector); + this.btnInc = this.fakeElement.find(this.options.btnIncSelector); + + // set initial values + this.initialValue = parseFloat(this.realElement.val()) || 0; + this.minValue = parseFloat(this.realElement.attr('min')); + this.maxValue = parseFloat(this.realElement.attr('max')); + this.stepValue = parseFloat(this.realElement.attr('step')) || 1; + + // check attribute values + this.minValue = isNaN(this.minValue) ? -Infinity : this.minValue; + this.maxValue = isNaN(this.maxValue) ? Infinity : this.maxValue; + + // handle range + if(isFinite(this.maxValue)) { + this.maxValue -= (this.maxValue - this.minValue) % this.stepValue; + } + }, + attachEvents: function() { + this.realElement.on({ + 'focus': this.onFocus + }); + this.btnDec.add(this.btnInc).on('jcf-pointerdown', this.onBtnPress); + }, + onBtnPress: function(e) { + var self = this, + increment; + + if(!this.realElement.is(':disabled')) { + increment = this.btnInc.is(e.currentTarget); + + self.step(increment); + clearInterval(this.stepTimer); + this.stepTimer = setInterval(function() { + self.step(increment); + }, this.options.pressInterval); + + this.page.on('jcf-pointerup', this.onBtnRelease); + } + }, + onBtnRelease: function() { + clearInterval(this.stepTimer); + this.page.off('jcf-pointerup', this.onBtnRelease); + }, + onFocus: function() { + this.fakeElement.addClass(this.options.focusClass); + this.realElement.on({ + 'blur': this.onBlur, + 'keydown': this.onKeyPress + }); + }, + onBlur: function() { + this.fakeElement.removeClass(this.options.focusClass); + this.realElement.off({ + 'blur': this.onBlur, + 'keydown': this.onKeyPress + }); + }, + onKeyPress: function(e) { + if(e.which === 38 || e.which === 40) { + e.preventDefault(); + this.step(e.which === 38); + } + }, + step: function(increment) { + var originalValue = parseFloat(this.realElement.val()), + newValue = originalValue || 0, + addValue = this.stepValue * (increment ? 1 : -1), + edgeNumber = isFinite(this.minValue) ? this.minValue : this.initialValue - Math.abs(newValue * this.stepValue), + diff = Math.abs(edgeNumber - newValue) % this.stepValue; + + // handle step diff + if(diff) { + if(increment) { + newValue += addValue - diff; + } else { + newValue -= diff; + } + } else { + newValue += addValue; + } + + // handle min/max limits + if(newValue < this.minValue) { + newValue = this.minValue; + } else if(newValue > this.maxValue) { + newValue = this.maxValue; + } + + // update value in real input if its changed + if(newValue !== originalValue) { + this.realElement.val(newValue).trigger('change'); + this.refresh(); + } + }, + refresh: function() { + // handle disabled state + var isDisabled = this.realElement.is(':disabled'); + this.fakeElement.toggleClass(this.options.disabledClass, isDisabled); + + // refresh button classes + this.btnDec.toggleClass(this.options.disabledClass, this.realElement.val() == this.minValue); + this.btnInc.toggleClass(this.options.disabledClass, this.realElement.val() == this.maxValue); + }, + destroy: function() { + // restore original structure + this.realElement.removeClass(this.options.realElementClass).insertBefore(this.fakeElement); + this.fakeElement.remove(); + clearInterval(this.stepTimer); + + // remove event handlers + this.page.off('jcf-pointerup', this.onBtnRelease); + this.realElement.off({ + 'keydown': this.onKeyPress, + 'focus': this.onFocus, + 'blur': this.onBlur + }); + } + }); + +}(jQuery, this)); \ No newline at end of file diff --git a/js/jcf.radio.js b/js/jcf.radio.js index 060be21..d6efb05 100755 --- a/js/jcf.radio.js +++ b/js/jcf.radio.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict'; diff --git a/js/jcf.range.js b/js/jcf.range.js new file mode 100644 index 0000000..fedfe1b --- /dev/null +++ b/js/jcf.range.js @@ -0,0 +1,265 @@ +/*! + * JavaScript Custom Forms : Range Module + * + * Copyright 2014 PSD2HTML (http://psd2html.com) + * Released under the MIT license (LICENSE.txt) + * + * Version: 1.0.3 + */ +;(function($, window) { + 'use strict'; + + jcf.addModule({ + name: 'Range', + selector: 'input[type="range"]', + options: { + realElementClass: 'jcf-real-element', + fakeStructure: '', + dataListMark: '', + dragValueDisplay: '', + handleSelector: '.jcf-range-handle', + trackSelector: '.jcf-range-track', + verticalClass: 'jcf-vertical', + orientation: 'horizontal', + dragHandleCenter: true, + snapToMarks: true, + snapRadius: 5 + }, + matchElement: function(element) { + return element.is(this.selector); + }, + init: function(options) { + this.initStructure(); + this.attachEvents(); + this.refresh(); + }, + initStructure: function() { + this.page = $('html'); + this.realElement = $(this.options.element).addClass(this.options.hiddenClass); + this.fakeElement = $(this.options.fakeStructure).insertBefore(this.realElement).prepend(this.realElement); + this.track = this.fakeElement.find(this.options.trackSelector); + this.handle = this.fakeElement.find(this.options.handleSelector); + + // handle orientation + this.isVertical = (this.options.orientation === 'vertical'); + this.directionProperty = this.isVertical ? 'top' : 'left'; + this.offsetProperty = this.isVertical ? 'bottom' : 'left'; + this.eventProperty = this.isVertical ? 'pageY' : 'pageX'; + this.sizeMethod = this.isVertical ? 'innerHeight' : 'innerWidth'; + if(this.isVertical) { + this.fakeElement.addClass(this.options.verticalClass); + } + + // set initial values + this.minValue = parseFloat(this.realElement.attr('min')); + this.maxValue = parseFloat(this.realElement.attr('max')); + this.stepValue = parseFloat(this.realElement.attr('step')) || 1; + + // check attribute values + this.minValue = isNaN(this.minValue) ? 0 : this.minValue; + this.maxValue = isNaN(this.maxValue) ? 100 : this.maxValue; + + // handle range + if(this.stepValue !== 1) { + this.maxValue -= (this.maxValue - this.minValue) % this.stepValue; + } + this.stepsCount = (this.maxValue - this.minValue) / this.stepValue + 1; + this.createDataList(); + }, + attachEvents: function() { + this.realElement.on({ + 'focus': this.onFocus + }); + this.handle.on('jcf-pointerdown', this.onPress); + }, + createDataList: function() { + var self = this, + dataValues = [], + dataListId = this.realElement.attr('list'); + + if(dataListId) { + $('#' + dataListId).find('option').each(function() { + var itemValue = parseFloat(this.value || this.innerHTML), + mark, markOffset; + + if(!isNaN(itemValue)) { + markOffset = self.valueToOffset(itemValue); + dataValues.push({ + value: itemValue, + offset: markOffset + }); + mark = $(self.options.dataListMark).text(itemValue).attr({ + 'data-mark-value': itemValue + }).css(self.offsetProperty, markOffset + '%').appendTo(self.track); + } + }); + if(dataValues.length) { + self.dataValues = dataValues; + } + } + }, + onPress: function(e) { + var trackSize, trackOffset, innerOffset; + + e.preventDefault(); + if(!this.realElement.is(':disabled')) { + trackSize = this.track[this.sizeMethod](); + trackOffset = this.track.offset()[this.directionProperty]; + innerOffset = this.options.dragHandleCenter ? this.handle[this.sizeMethod]() / 2 : e[this.eventProperty] - this.handle.offset()[this.directionProperty]; + + this.dragData = { + trackSize: trackSize, + innerOffset: innerOffset, + trackOffset: trackOffset, + min: trackOffset, + max: trackOffset + trackSize + }; + this.page.on({ + 'jcf-pointermove': this.onMove, + 'jcf-pointerup': this.onRelease + }); + + if(e.pointerType === 'mouse') { + this.realElement.focus(); + } + } + }, + onMove: function(e) { + var self = this, + newOffset, dragPercent, stepIndex, valuePercent; + + // calculate offset + if(this.isVertical) { + newOffset = this.dragData.max + (this.dragData.min - e[this.eventProperty]) - this.dragData.innerOffset; + } else { + newOffset = e[this.eventProperty] - this.dragData.innerOffset; + } + + // fit in range + if(newOffset < this.dragData.min) { + newOffset = this.dragData.min; + } else if(newOffset > this.dragData.max) { + newOffset = this.dragData.max; + } + + e.preventDefault(); + if(this.options.snapToMarks && this.dataValues) { + // snap handle to marks + var dragOffset = newOffset - this.dragData.trackOffset; + dragPercent = (newOffset - this.dragData.trackOffset) / this.dragData.trackSize * 100; + + $.each(this.dataValues, function(index, item) { + var markOffset = item.offset / 100 * self.dragData.trackSize, + markMin = markOffset - self.options.snapRadius, + markMax = markOffset + self.options.snapRadius; + + if(dragOffset >= markMin && dragOffset <= markMax) { + dragPercent = item.offset; + return false; + } + }); + } else { + // snap handle to steps + dragPercent = (newOffset - this.dragData.trackOffset) / this.dragData.trackSize * 100; + } + stepIndex = Math.round(dragPercent * this.stepsCount / 100); + valuePercent = stepIndex * (100 / this.stepsCount); + + if(this.dragData.stepIndex !== stepIndex) { + this.dragData.stepIndex = stepIndex; + this.dragData.offset = valuePercent; + this.handle.css(this.offsetProperty, this.dragData.offset + '%'); + } + }, + onRelease: function() { + var newValue; + if(typeof this.dragData.offset === 'number') { + newValue = this.stepIndexToValue(this.dragData.stepIndex); + this.realElement.val(newValue).trigger('change'); + } + + this.page.off({ + 'jcf-pointermove': this.onMove, + 'jcf-pointerup': this.onRelease + }); + delete this.dragData; + }, + onFocus: function() { + this.fakeElement.addClass(this.options.focusClass); + this.realElement.on({ + 'blur': this.onBlur, + 'keydown': this.onKeyPress + }); + }, + onBlur: function() { + this.fakeElement.removeClass(this.options.focusClass); + this.realElement.off({ + 'blur': this.onBlur, + 'keydown': this.onKeyPress + }); + }, + onKeyPress: function(e) { + var incValue = (e.which === 38 || e.which === 39), + decValue = (e.which === 37 || e.which === 40); + + if(decValue || incValue) { + e.preventDefault(); + this.step(incValue ? this.stepValue : -this.stepValue); + } + }, + step: function(changeValue) { + var originalValue = parseFloat(this.realElement.val()), + newValue = originalValue; + + if(isNaN(originalValue)) { + newValue = 0; + } + + newValue += changeValue; + + if(newValue > this.maxValue) { + newValue = this.maxValue; + } else if(newValue < this.minValue) { + newValue = this.minValue; + } + + if(newValue !== originalValue) { + this.realElement.val(newValue).trigger('change'); + this.setSliderValue(newValue); + } + }, + stepIndexToValue: function(stepIndex) { + return this.minValue + this.stepValue * stepIndex; + }, + valueToOffset: function(value) { + var range = this.maxValue - this.minValue, + percent = (value - this.minValue) / range; + + return percent * 100; + }, + setSliderValue: function(value) { + // set handle position accordion according to value + this.handle.css(this.offsetProperty, this.valueToOffset(value) + '%'); + }, + refresh: function() { + // handle disabled state + var isDisabled = this.realElement.is(':disabled'); + this.fakeElement.toggleClass(this.options.disabledClass, isDisabled); + + // refresh handle position according to current value + var realValue = parseFloat(this.realElement.val()) || 0; + this.setSliderValue(realValue); + }, + destroy: function() { + this.realElement.removeClass(this.options.hiddenClass).insertBefore(this.fakeElement); + this.fakeElement.remove(); + + this.realElement.off({ + 'keydown': this.onKeyPress, + 'focus': this.onFocus, + 'blur': this.onBlur + }); + } + }); + +}(jQuery, this)); \ No newline at end of file diff --git a/js/jcf.scrollable.js b/js/jcf.scrollable.js index 3d54782..4ef6246 100755 --- a/js/jcf.scrollable.js +++ b/js/jcf.scrollable.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict'; diff --git a/js/jcf.select.js b/js/jcf.select.js index 68d18b6..295bd91 100755 --- a/js/jcf.select.js +++ b/js/jcf.select.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict'; @@ -286,7 +286,7 @@ var selectOffset = this.fakeElement.offset(), selectWidth = this.fakeElement.outerWidth(), selectHeight = this.fakeElement.outerHeight(), - dropHeight = this.dropdown.outerHeight(), + dropHeight = this.dropdown.css('width', selectWidth).outerHeight(), winScrollTop = this.win.scrollTop(), winHeight = this.win.height(), calcTop, calcLeft, bodyOffset, needFlipDrop = false; diff --git a/js/jcf.textarea.js b/js/jcf.textarea.js index b8eb5e2..caaf970 100644 --- a/js/jcf.textarea.js +++ b/js/jcf.textarea.js @@ -4,7 +4,7 @@ * Copyright 2014 PSD2HTML (http://psd2html.com) * Released under the MIT license (LICENSE.txt) * - * Version: 1.0.2 + * Version: 1.0.3 */ ;(function($, window) { 'use strict';