Skip to content

Instantly share code, notes, and snippets.

@sgammon
Created March 4, 2012 01:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sgammon/1969794 to your computer and use it in GitHub Desktop.
Save sgammon/1969794 to your computer and use it in GitHub Desktop.
bsd thermometer
(function($, window, document, undefined) {
// Feature tests shamelessly borrowed from Modernizr... unless Modernizr is available, in which case it is used
window._featureTests = {};
prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
omPrefixes = 'Webkit Moz O ms';
cssomPrefixes = omPrefixes.split(' ');
domPrefixes = omPrefixes.toLowerCase().split(' ');
ns = {'svg': 'http://www.w3.org/2000/svg'};
mod = 'modernizr';
docElement = document.documentElement;
modElem = document.createElement(mod);
mStyle = modElem.style;
function is( obj, type ) {
return typeof obj === type;
}
function testProps( props, prefixed ) {
for ( var i in props ) {
if ( mStyle[ props[i] ] !== undefined ) {
return prefixed == 'pfx' ? props[i] : true;
}
}
return false;
}
function setCss( str ) { mStyle.cssText = str; }
function setCssAll( str1, str2 ) { return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); }
function contains( str, substr ) { return !!~('' + str).indexOf(substr); }
function testDOMProps( props, obj, elem ) {
for ( var i in props ) {
var item = obj[props[i]];
if ( item !== undefined) {
// return the property name as a string
if (elem === false) return props[i];
// let's bind a function
if (is(item, 'function')){
// default to autobind unless override
return item.bind(elem || obj);
}
// return the unbound function or obj or value
return item;
}
}
return false;
}
function testPropsAll( prop, prefixed, elem ) { // test for css props
var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
// did they call .prefixed('boxSizing') or are we just testing a prop?
if(is(prefixed, "string") || is(prefixed, "undefined")) {
return testProps(props, prefixed);
// otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
} else {
props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
return testDOMProps(props, prefixed, elem);
}
}
if (typeof(window.Modernizr) == 'null' || typeof(window.Modernizr) == 'undefined' || window.Modernizr === null)
{
_featureTests.csstransitions = function() { return testPropsAll('transition'); };
_featureTests.cssanimations = function() { return testPropsAll('animationName'); };
_featureTests.borderradius = function() { return testPropsAll('borderRadius'); };
_featureTests.cssreflections = function() { return testPropsAll('boxReflect'); };
_featureTests.csstransforms = function() { return !!testPropsAll('transform'); };
_featureTests.boxshadow = function() { return testPropsAll('boxShadow'); };
_featureTests.cssgradients = function() {
/**
* For CSS Gradients syntax, please see:
* webkit.org/blog/175/introducing-css-gradients/
* developer.mozilla.org/en/CSS/-moz-linear-gradient
* developer.mozilla.org/en/CSS/-moz-radial-gradient
* dev.w3.org/csswg/css3-images/#gradients-
*/
var str1 = 'background-image:',
str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
str3 = 'linear-gradient(left top,#9f9, white);';
setCss(
(str1 + '-webkit- '.split(' ').join(str2 + str1) + prefixes.join(str3 + str1)).slice(0, -str1.length)
);
return contains(mStyle.backgroundImage, 'gradient');
};
_featureTests.csstransform3d = function() {
var ret = !!testPropsAll('perspective');
return ret;
};
} else {
_featureTests.csstransitions = Modernizr.csstransitions;
_featureTests.cssanimations = Modernizr.cssanimations;
_featureTests.cssgradients = Modernizr.cssgradients;
_featureTests.cssreflections = Modernizr.cssreflections;
_featureTests.csstransforms = Modernizr.csstransforms;
_featureTests.csstransform3d = Modernizr.csstransform3d;
_featureTests.borderradius = Modernizr.borderradius;
_featureTests.boxshadow = Modernizr.boxshadow;
}
})(jQuery, window, document);
(function($, window, document, undefined) {
var config,
defaults,
options,
parent,
id_prefix,
injected_id,
data,
element,
string,
falses = [false, null, NaN, 'false', 'null', 'NaN', 'None'],
methods = {
init : function(options) {
defaults = {
'id': null,
'el': {},
// Style
'style': {
'borderRadius': 7,
// Style settings for the progress part of the thermometer
'progress': {
'fill': '#115B8F', // fill is set if no gradient is set, or if gradients aren't supported
'animation_time': 300, // the default amount of milliseconds it takes to finish animating the progress bar
'gradient': [
// Gradient stops can be expressed as integers or strings
['#115B8F', 100],
['#1A7EB3', '70%']
]
},
'thumb': {
'fill': '#115B8F', // fill is set if no gradient is set, or if gradients aren't supported
'gradient': [
// Gradient stops can be expressed as integers or strings
['#115B8F', 100],
['#1A7EB3', '70%']
],
'label': {
'enable': true,
'type': 'percentage', // valid values: "percentage", "rawvalue", "custom"
'value': null, // use this for a custom label
'snap': 'progress', // which element to snap the label to - "progress" for inner, "thermometer" for outer
'align': 'right' // whether to align right, left, or middle (relative to the value of "snap")
}
},
// Style settings for the background part of the thermometer
'thermometer': {
'fill': '#020202',
'gradient': null
}
},
// Fancy features
'fancy': {
'animate': true, // uses CSS3 animations/transforms/transitions if supported, falls back to jQuery
'fillGradient': false, // uses CSS3 linear gradients if supported, no fallback available
'fillReflection': false, // uses CSS3 reflections if supported, no fallback available
'dropShadow': false, // uses CSS3 boxshadow if supported, no fallback available
'borderRadius': true // uses CSS3 borderRadius is supported, no fallback available
},
'stops': [], // [] or int
'status': {
'progress': 35, // can be expressed as progress to a goal, or an independent percentage of progres (without a 'goal')
'goal': null,
// values for display
'goal_val': null,
'progress_val': null
},
// Main config
'vertical': false,
'size': 400,
'thickness': 30,
'thumbHeight': 26,
// Debug/dev config
'logging': true
};
featuresDetected = {
'transitions': window._featureTests.csstransitions(),
'animations': window._featureTests.cssanimations(),
'gradients': window._featureTests.cssgradients(),
'reflections': window._featureTests.cssreflections(),
'transforms': window._featureTests.csstransforms(),
'transform3d': window._featureTests.csstransform3d(),
'borderradius': window._featureTests.borderradius(),
'boxshadow': window._featureTests.boxshadow()
};
config = $.extend({}, defaults, options);
config.features = featuresDetected;
return this.each(function() {
var $this = $(this);
// get parent element & inject thermometer
parent = (config.id !== null) ? $(config.id) : $this;
config.id = parent.attr('id');
id_prefix = config.id;
if (typeof id_prefix !== 'undefined' && id_prefix !== false)
{
injected_id = id_prefix+'-'+'thermometer';
}
else
{
injected_id = 'bsd-thermometer';
}
element = parent.append([
'<div id="'+injected_id+'" class="bsdthermometer" style="position: relative;">',
'<style id="'+injected_id+'-injected-style'+'"></style>',
'<div id="'+injected_id+'-fill" class="bsdthermometer-inner" style="opacity: 0">',
'<div id="'+injected_id+'-thumb" class="bsdthermometer-thumb" style="opacity: 1;position: absolute;left: 3%;">',
'<span id="'+injected_id+'-thumb-label'+'" class="bsdthermometer-thumb-label" style="opacity:1;">0%</span>',
'</div>',
'</div>',
'</div>'
].join("\n ")).children('.bsdthermometer');
config.el = '#' + element.attr('id');
data = element.data(config);
$this.data({
'prefix': id_prefix
});
});
},
close : function() {
return this.each(function() {
$(this).children().removeData();
$(this).children().remove();
});
},
show : function() {
return this.do_animate(this, {
'opacity': 1
}, {timing: 300, defer: false, trigger: false});
},
hide : function() {
return this.do_animate(this, {
'opacity': 0
}, {timing: 300, defer: false, trigger: false});
},
do_animate: function (selector, props, timing_or_config) {
var timing = 300,
config = {
defer: false,
trigger: true,
enable_css3: true,
force_css3: false
};
if (typeof timing_or_config == 'number')
{
timing = timing_or_config;
} else {
if (typeof timing_or_config.timing != 'undefined' && timing_or_config.timing !== null)
{
timing = timing_or_config.timing;
$.extend(config, timing_or_config);
}
}
if(false)
{
}
else
{
$(selector).animate(props, timing);
}
},
format_number: function (original) {
if (typeof original != 'undefined' && original !== null)
{
return original.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
}
},
generate_gradient: function (gradient_style) {
var element_block = null;
var gradientElements = [];
for (element_block in gradient_style)
{
// Pull each gradient stop
element_block = gradient_style[element_block];
// If the block is an array, they are specifying a color stop with an alpha value
if (Object.prototype.toString.call(element_block) == '[object Array]')
{
// If the alpha value is a string, pass it into CSS unchanged
if (typeof element_block[1] == 'string')
{
gradientElements.push(element_block.join(' '));
// If the alpha value is an int, stringify and add a percent sign, then pass into CSS
} else {
gradientElements.push([element_block[0], element_block[1].toString()+'%'].join(' '));
}
}
}
// Generate the gradient call content
progressGradient = '(top, ' + gradientElements.join(',')+')';
// Set the CSS with appropriate prefixes
progressBackground = [
'-moz-' + prefixTemp + progressGradient,
'-o-' + prefixTemp + progressGradient,
'-webkit-' + prefixTemp + progressGradient,
'-ms-' + prefixTemp + progressGradient,
prefixTemp + progressGradient
];
element_block = null;
for (element_block in progressBackground)
{
element_block = progressBackground[element_block];
$data.style.injected.inner_fill.styles.push(['background', element_block]);
}
},
update : function() {
var $this = $('#'+this.data('prefix')+'-thermometer'),
$data = $this.data(),
thumbGradient = null,
thermometerGradient = null,
thumbBackground = [],
thermometerBackground = [],
progressBackground = null,
progressGradient = null,
prefixTemp = '',
postfixTemp = '',
gradientElements = [],
element_block = null,
fill = null,
stop_margin = null,
labelValue = null,
labelOffset = null,
labelSnap = null,
injectedStyleBlocks = {};
/* ======== Init phase ======== */
// Prepare blocks
injectedStyleBlocks.outer_wrap = {'selector': '#'+this.data('prefix'), 'class': null, 'styles': []};
injectedStyleBlocks.outer_fill = {'selector': '#'+this.data('prefix')+'-thermometer', 'class': 'bsdthermometer', 'styles': []};
injectedStyleBlocks.inner_fill = {'selector': '#'+this.data('prefix')+'-thermometer-fill', 'class': 'bsdthermometer-inner', 'styles': []};
injectedStyleBlocks.outer_thumb = {'selector': '#'+this.data('prefix')+'-thermometer-thumb', 'class': 'bsdthermometer-thumb', 'styles': []};
injectedStyleBlocks.inner_thumb = {'selector': '#'+this.data('prefix')+'-thermometer-thumb-label', 'class': 'bsdthermometer-thumb-label', 'styles': []};
console.log('data for thermometer', $data, injectedStyleBlocks);
$data.style.injected = injectedStyleBlocks;
// Calculate current progress and fill
if ($data.status.goal === null || typeof $data.status.goal == 'undefined')
{
fill = ($data.status.progress) / 100 * $data.size;
$data.status.progress_percentage = $data.status.progress;
stop_margin = Math.floor($data.size/($data.stops + 1));
} else {
fill = ($data.status.progress/$data.status.goal) * $data.size;
$data.status.progress_percentage = Math.floor(($data.status.progress/$data.status.goal) * 100);
stop_margin = Math.floor($data.size/($data.stops + 1));
}
// Do any EL replaces that we need to do with the data
if (typeof $data.elreplace !== 'undefined' && typeof $.inArray($data.elreplace, falses) != -1)
{
console.log('element replace is enabled', $data.elreplace);
var element_replace = null;
for (element_replace in $data.elreplace)
{
element_block = $data.elreplace[element_replace];
console.log('replacing element...', element_replace, element_block);
switch(element_replace)
{
case 'progress':
if (typeof $data.status.progress_val != 'undefined' && typeof $data.status.progress_val !== null)
{
$(element_block).text(methods.format_number($data.status.progress_val));
} else {
$(element_block).text(methods.format_number($data.status.progress));
}
break;
case 'progress_percentage':
$(element_block).text($data.status.progress_percentage.toString() + '%');
break;
case 'remaining':
if (typeof $data.status.progress_val != 'undefined' && typeof $data.status.progress_val !== null)
{
$(element_block).text(methods.format_number($data.status.goal_val - $data.status.progress_val));
} else {
$(element_block).text(methods.format_number($data.status.goal - $data.status.progress));
}
break;
case 'goal':
if (typeof $data.status.goal_val != 'undefined' && typeof $data.status.goal_val !== null)
{
$(element_block).text(methods.format_number($data.status.goal_val));
} else {
$(element_block).text(methods.format_number($data.status.goal));
}
break;
}
}
element_block = null;
element_replace = null;
}
// Prepare the thumb label, if it's enabled
if (typeof $data.style.thumb !== 'undefined' && typeof $data.style.thumb.label !== 'undefined' && $.inArray($data.style.thumb.label, falses) === -1 && $data.style.thumb.label.enable === true)
{
console.log('label enabled');
$data.style.injected.inner_thumb.styles.push(['height', $data.thickness.toString() + 'px']);
$data.style.injected.inner_thumb.styles.push(['line-height', $data.thickness.toString() + 'px']);
switch($data.style.thumb.label.type)
{
case 'percentage':
labelValue = $data.status.progress_percentage.toString() + '%';
break;
case 'rawvalue':
labelValue = $data.status.progress;
break;
case 'goal':
labelValue = $data.status.goal;
break;
case 'custom':
labelValue = $data.style.thumb.label.value;
break;
}
// Replace the label value
$($data.style.injected.inner_thumb.selector).html(labelValue);
switch($data.style.thumb.label.snap)
{
case 'progress':
labelOffset = $data.size - (($data.status.progress) / 100 * $data.size);
switch($data.style.thumb.label.align)
{
case 'right':
$data.style.injected.inner_thumb.styles.push(['right', labelOffset + 5 + 'px']);
break;
case 'left':
$data.style.injected.inner_thumb.styles.push(['left', '3%']);
break;
case 'middle':
$data.style.injected.inner_thumb.styles.push(['width', fill]);
$data.style.injected.inner_thumb.styles.push(['text-align', 'center']);
break;
}
break;
case 'thermometer':
labelOffset = '3%';
switch($data.style.thumb.label.align)
{
case 'right':
$data.style.injected.inner_thumb.styles.push(['right', '3%']);
break;
case 'left':
$data.style.injected.inner_thumb.styles.push(['left', '3%']);
break;
case 'middle':
$data.style.injected.inner_thumb.styles.push(['width', '100%']);
$data.style.injected.inner_thumb.styles.push(['text-align', 'center']);
break;
}
break;
}
}
// Consider border radius
if (typeof $data.style.borderRadius !== 'undefined' && $.inArray($data.style.borderRadius, falses) === -1 && $data.features.borderradius)
{
if (fill == $data.size)
{
$($data.el+'-fill').css({
borderRadius: $data.style.borderRadius + "px"
});
}
else
{
$($data.el+'-fill').css({
'border-bottom-left-radius': $data.style.borderRadius + "px",
'border-top-left-radius': $data.style.borderRadius + "px"
});
}
$(this).css({
borderRadius: $data.style.borderRadius + "px"
});
$($data.el).css({
borderRadius: $data.style.borderRadius + "px"
});
}
// Consider gradients
if (typeof $data.fancy.fillGradient !== 'undefined' && $.inArray($data.fancy.fillGradient, falses) === -1 && $data.features.gradients)
{
// Generate CSS3 gradients, according to config
console.log('gradient supported and enabled', $data.style);
prefixTemp = 'linear-gradient';
// Process the thumb's gradient settings
if (typeof $data.style.progress.gradient !== 'undefined' && $.inArray($data.style.progress.gradient, falses) === -1)
{
console.log('progress has a gradient...');
for (element_block in $data.style.progress.gradient)
{
// Pull each gradient stop
element_block = $data.style.progress.gradient[element_block];
// If the block is an array, they are specifying a color stop with an alpha value
if (Object.prototype.toString.call(element_block) == '[object Array]')
{
// If the alpha value is a string, pass it into CSS unchanged
if (typeof element_block[1] == 'string')
{
gradientElements.push(element_block.join(' '));
// If the alpha value is an int, stringify and add a percent sign, then pass into CSS
} else {
gradientElements.push([element_block[0], element_block[1].toString()+'%'].join(' '));
}
}
}
// Generate the gradient call content
progressGradient = '(top, ' + gradientElements.join(',')+')';
// Set the CSS with appropriate prefixes
progressBackground = [
'-moz-' + prefixTemp + progressGradient,
'-o-' + prefixTemp + progressGradient,
'-webkit-' + prefixTemp + progressGradient,
prefixTemp + progressGradient
];
element_block = null;
for (element_block in progressBackground)
{
element_block = progressBackground[element_block];
$data.style.injected.inner_fill.styles.push(['background', element_block]);
}
} else {
// if there's no gradient defined in the settings, default to a simple background fill
progressBackground = $data.style.progress.fill;
$data.style.injected.inner_fill.progress.push(['background', progressBackground]);
}
// Process the thermometer's gradient settings
if (typeof $data.style.thermometer.gradient !== 'undefined' && $.inArray($data.style.thermometer.gradient, falses) === -1)
{
console.log('thermometer has a gradient...');
gradientElements = [];
element_block = null;
for (element_block in $data.style.thermometer.gradient)
{
// Pull each gradient stop
element_block = $data.style.thermometer.gradient[element_block];
// If the block is an array, they are specifying a color stop with an alpha value
if (Object.prototype.toString.call(element_block) == '[object Array]')
{
// If the alpha value is a string, pass it into CSS unchanged
if (typeof element_block[1] == 'string')
{
gradientElements.push(element_block.join(' '));
} else {
gradientElements.push([element_block[0], element_block[1].toString()+'%'].join(' '));
}
}
}
// Generate the gradient call content
thermometerGradient = '(top, ' + gradientElements.join(',')+')';
// Set the CSS with appropriate prefixes
thermometerBackground = [
'-moz-' + prefixTemp + thermometerGradient,
'-o-' + prefixTemp + thermometerGradient,
'-webkit-' + prefixTemp + thermometerGradient,
'-ms-' + prefixTemp + thermometerGradient,
prefixTemp + thermometerGradient
];
element_block = null;
for (element_block in thermometerBackground)
{
element_block = thermometerBackground[element_block];
$data.style.injected.outer_fill.styles.push(['background', element_block]);
}
} else {
// if there's no gradient defined in the settings, default to a simple background fill
thermometerBackground = $data.style.thermometer.fill;
$data.style.injected.outer_fill.styles.push(['background', thermometerBackground]);
}
} else {
// if there's no support for CSS3 gradients at all, default to a simple background fill
thumbBackground = $data.style.thumb.fill;
thermometerBackground = $data.style.thumb.fill;
$data.style.injected.inner_fill.styles.push(['background', thumbBackground]);
$data.style.injected.outer_fill.styles.push(['background', thermometerBackground]);
}
if (typeof $data.fancy.fillReflection !== 'undefined' && $.inArray($data.fancy.fillReflection, falses) === -1 && $data.features.reflections)
{
// Reflection
console.log('reflection supported and enabled');
}
if (typeof $data.fancy.dropShadow !== 'undefined' && $.inArray($data.fancy.dropShadow, falses) === -1 && $data.features.boxshadow)
{
// Drop shadow
console.log('dropshadow supported and enabled');
}
if ($data.stops >= 1)
{
// Add the stops
}
/* ======== Render phase ======== */
// Set injected CSS
$($data.el+'-injected-style').text(function (){
console.log('setting injected style', this);
// inject styles for each block
var element_block = null;
var injected_style_stanzas = [];
for (element_block in $data.style.injected)
{
element_block = $data.style.injected[element_block];
if (typeof element_block['class'] != 'undefined' && element_block['class'] !== null)
{
// reduce computed style properties to the proper format ("<prop_name>: <prop_value>;")
var computed_styles = [];
var computed_style = null;
for (computed_style in element_block.styles)
{
computed_style = element_block.styles[computed_style];
computed_styles.push(computed_style[0]+': '+computed_style[1]+';');
}
injected_style_stanzas.push([
// i think this is as close as i can get to python in javascript.
// why, god, why aren't there generator
['.'+element_block['class']+' { ',
"\t "+computed_styles.join("\n\t "),
'}'
].join("\n ")
].join("\n "));
}
}
$(this).text(injected_style_stanzas.join("\n "));
});
if ( $data.vertical !== true ) // Horizontal
{
// Initial styles
$(this).css({
'width': $data.size
});
$($data.el+'-fill').css({
'width': '0px',
'height': $data.thickness.toString() + 'px',
'opacity': 0
});
// Animate?
if ($data.fancy.animate === true)
{
methods.do_animate($($data.el+'-fill'), {'width': fill, 'opacity': 1}, $data.style.progress.animation_time);
methods.do_animate($($data.el+'-thumb'), {'opacity': 1}, $data.style.progress.animation_time);
}
else
{
$($data.el+'-fill').css({
'width': fill,
'opacity': 1
});
$($data.el+'-thumb').css({'opacity': 1});
}
}
else if ($data.vertical === true) // Vertical
{
$(this).css({
'height': $data.size
});
// Initial styles
$($data.el+'-fill').css({
'height': '0px',
'width': $data.thickness.toString()+'px',
'opacity': 0,
'position': 'absolute',
'bottom': -$data.size
});
// Animate?
if ($data.fancy.animate === true)
{
methods.do_animate($($data.el+'-fill'), {'height': fill}, $data.style.progress.animation_time);
methods.do_animate($($data.el+'-thumb'), {'opacity': 1}, $data.style.progress.animation_time);
}
else
{
$($data.el+'-fill').css({
'height': fill,
'opacity': 1
});
$($data.el+'-thumb').css({'opacity': 1});
}
}
},
edit : function(changes) {
// edit
}
};
$.fn.thermometer = function(method) {
if ( methods[method] ) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on the current object.');
}
};
})(jQuery, window, document);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment