/* * Raterater 1.1.1 * License: MIT - http://www.opensource.org/licenses/mit-license.php * Author: Bain Mullins - http://bainweb.com */ ;(function( $ ) { var data = {}; var opts = {}; var MODE_INPUT = 'input'; var MODE_CALLBACK = 'callback'; var object; var elems = null; var inputCounter = 0; $.fn.raterater = function(options) { /* Default options */ $.fn.raterater.defaults = { submitFunction: '', // this function will be called when a rating is chosen allowChange: false, // allow the user to change their mind after they have submitted a rating starWidth: 20, // width of the stars in pixels spaceWidth: 5, // spacing between stars in pixels numStars: 5, isStatic: false, mode: MODE_CALLBACK, step: false, }; opts = $.extend( {}, $.fn.raterater.defaults, options ); opts.width = opts.numStars * ( opts.starWidth + opts.spaceWidth ); // total rating div width opts.starAspect = 0.9226; // aspect ratio of the font awesome stars /* Validate the step option is between 0 and 1 if present */ if(opts.step !== false){ opts.step = parseFloat(opts.step); if(opts.step <= 0 || opts.step > 1){ throw "Error: step must be between 0 and 1"; } } elems = this; /* First we create ze elements */ init(); /* Zen we position ze elements */ initializePositions(); return this; } function setValue (id,stars){ $('.raterater-input[data-id="'+ id + '"]').data('rating', stars).change(); } function init() { elems.each( function() { var $this = $( this ); if(opts.mode == MODE_INPUT && ($this.prop("tagName") == 'INPUT' || $this.prop("tagName") == 'SELECT') ){ var iId = 'input-' +(inputCounter++); var outer = $('
').attr('data-id', iId).attr('data-rating',$this.val()).data('input',$this); $this.attr('data-id', iId).attr('data-id', iId).attr('data-rating',$this.val()).data('input',$this).after(outer).hide(); object = $this = outer; } object = $this; var id = dataId( $this ); /* Uh oh... We really need a data-id or bad things happen */ if( !id ) { throw "Error: Each raterater element needs a unique data-id attribute."; } /* This is where we store our important data for each rating box */ data[id] = { state: 'inactive', // inactive, hover, or rated stars: null, }; /* Make our wrapper relative if it is static so we can position children absolutely */ if( $this.css( 'position' ) === 'static' ) $this.css( 'position', 'relative' ); /* Add class raterater-wrapper */ $this.addClass('raterater-wrapper'); /* Clear out anything inside so we can append the relevent children */ $this.html( '' ); /* We have 4 div children here as different star layers * Layer 1 contains the dull filled stars as a background * Layer 2 shows the bright filled stars that represent the current user's rating * Layer 3 shows the bright filled stars that represent the item's rating * Layer 4 shows the outline stars and is just for looks * Layer 5 covers the widget and mainly exists to keep event.offsetX from being ruined by child elements */ $.each( [ 'bg', 'hover', 'rating', 'outline', 'cover' ], function() { $this.append(' ' ); }); /* Fill the layers with stars */ for( var i = 0; i < opts.numStars; i++ ) { $this.children( '.raterater-bg-layer' ).first() .append( '' ); $this.children( '.raterater-outline-layer' ).first() .append( '' ); $this.children( '.raterater-hover-layer' ).first() .append( '' ); $this.children( '.raterater-rating-layer' ).first() .append( '' ); } /* Register mouse event callbacks */ if(!opts.isStatic){ $this.find( '.raterater-cover-layer' ).hover( mouseEnter, mouseLeave ); $this.find( '.raterater-cover-layer' ).mousemove( hiliteStarsHover ).addClass("needsclick"); $this.find( '.raterater-cover-layer' ).click( rate ); } }); } function initializePositions() { elems.each( function() { var $this; if(opts.mode == MODE_INPUT) $this = $(this).parent().find('.raterater-input[data-id="'+dataId(this)+'"]'); else $this = $( this ); var id = dataId( $this ); /* Set the width and height of the raterater wrapper and layers */ var width = opts.width + 'px'; var height = Math.floor( opts.starWidth / opts.starAspect ) + 'px'; $this.css( 'width', width ) .css( 'height', height ); $this.find( '.raterater-layer' ).each(function(){ $( this ).css( 'width', width ) .css( 'height', height ); }); /* Absolutely position the stars (necessary for partial stars) */ for( var i = 0; i < opts.numStars; i++ ) { $.each( [ 'bg', 'hover', 'rating', 'outline' ], function() { $this.children( '.raterater-' + this + '-layer' ).first().children( 'i' ).eq( i ) .css( 'left', i * ( opts.starWidth + opts.spaceWidth ) + 'px' ) .css( 'font-size', Math.floor( opts.starWidth / opts.starAspect ) + 'px'); }); } /* show the item's current rating on the raterater-rating-layer */ var rating = parseFloat( $this.attr( 'data-rating' ) ); var whole = Math.floor( rating ); var partial = rating - whole; hiliteStars ( $this.find( '.raterater-rating-layer' ).first(), whole, partial ); }); } function rate(e) { var $this = $( e.target ).parent(); var id = dataId( $this ); var stars = data[id].whole_stars_hover + data[id].partial_star_hover; /* Round stars to 2 decimals */ stars = Math.round( stars * 100 ) / 100; /* Set the state to 'rated' to disable functionality */ data[id].state = 'rated'; data[id].stars = stars; /* Add the 'rated' class to the hover layer for additional styling flexibility */ $this.find( '.raterater-hover-layer' ).addClass( 'rated' ); /* Call the user-defined callback function if it exists */ if(opts.mode != 'input' && window[opts.submitFunction] !== undefined && typeof window[opts.submitFunction] === 'function') window[opts.submitFunction]( id, stars ); else setValue(id, stars); } /* Calculate the number of stars from the x position of the mouse relative to the cover layer * (This is only compicated because of the spacing between stars) */ function calculateStars(x, id) { /* Whole star = floor( x / ( star_width + space_width ) ) */ var whole_stars = Math.floor( x / ( opts.starWidth + opts.spaceWidth ) ); /* Partial star = max( 1, ( x - whole_stars * ( star_width + space_width ) ) / star_width ) */ var partial_star = x - whole_stars * ( opts.starWidth + opts.spaceWidth ); if( partial_star > opts.starWidth ) partial_star = opts.starWidth; partial_star /= opts.starWidth; /* Round to the nearest step if a step is provided */ if(opts.step !== false){ var stepInt = 1 / opts.step partial_star = (Math.round(partial_star * stepInt) / stepInt); } /* Store our result in the data object */ data[id].whole_stars_hover = whole_stars; data[id].partial_star_hover = partial_star; } /* Given a layer object and rating data, highlight the stars */ function hiliteStars($layer, whole, partial) { var id = dataId( $layer.parent() ); /* highlight the 'whole' stars */ for( var i = 0; i < whole; i++ ) { $layer.find( 'i' ).eq( i ) .css( 'width', opts.starWidth + 'px' ); } /* highlight the partial star */ $layer.find( 'i' ).eq( whole ) .css( 'width', opts.starWidth * partial + 'px' ); /* clear the extra stars */ for( var i = whole+1; i < opts.numStars; i++) { $layer.find( 'i' ).eq( i ) .css( 'width', '0px' ); } } /* Highlight the hover layer stars * This is the callback for the mousemove event */ function hiliteStarsHover(e) { var id = dataId( $( e.target ).parent() ); /* Leave it alone, we aren't hovering */ if( data[id].state !== 'hover' ) { return; } /* Get the mouse offsetX */ var x = e.offsetX; /* Firefox requires a pageX hack */ if(x === undefined) { x = e.pageX - $( e.target ).offset().left; } data[id].stars = calculateStars( x, id ); /* Find the layer element */ var $layer = $( e.target ).parent().children( '.raterater-hover-layer' ).first(); /* Call the more generic highlighting function */ hiliteStars( $layer, data[id].whole_stars_hover, data[id].partial_star_hover ); } /* Active this rating box * This is the callback for the mouseenter event */ function mouseEnter(e) { var id = dataId( $( e.target ).parent() ); /* Leave it alone, we have already rated this item */ if( data[id].state === 'rated' && !opts.allowChange ) { return; } /* set the state to 'hover' */ data[id].state = 'hover'; /* show the hover layer and hide the rating layer */ $( e.target ).parent().children( '.raterater-rating-layer' ).first().css( 'display', 'none' ); $( e.target ).parent().children( '.raterater-hover-layer' ).first().css( 'display','block' ); } /* Deactivate this rating box * This is the callback for the mouseleave event */ function mouseLeave(e) { var $this = $( e.target ).parent(); var id = dataId( $this ); /* hide the hover layer and show the rating layer */ $( e.target ).parent().children( '.raterater-hover-layer' ).first().css( 'display', 'none' ); $( e.target ).parent().children( '.raterater-rating-layer' ).first().css( 'display','block' ); /* Revert to the last rating, since we have already rated this item */ if( data[id].state === 'rated' ) { var rating = parseFloat( data[id].stars ); var whole = Math.floor( rating ); var partial = rating - whole; hiliteStars ( $this.find( '.raterater-rating-layer' ).first(), whole, partial ); return; } /* set the state to 'inactive' */ data[id].state = 'inactive'; } /* Shorthand function to get the data-id of an element */ function dataId(e) { return $( e ).attr( 'data-id' ); } })( jQuery );