jQuery插件-jRating评分插件源码分析及使用方法


Posted in Javascript onDecember 28, 2012

该插件被广泛应用于各种需要评分的页面当中,今天作为学习,把源码拿出来分析一下,顺便学习其使用方法。
一、插件使用一览

<div> 
<div>第一个例子</div> 
<div id="16_1" class="myRating"></div> 
</div>

<link href="Script/jRating/jRating.jquery.css" rel="stylesheet" type="text/css" /> 
<script src="Script/jquery-1.7.min.js" type="text/javascript"></script> 
<script src="Script/jRating/jRating.jquery.js" type="text/javascript"></script> 
<script type="text/javascript"> 
$(function () { 
$(".myRating").jRating({ 
length:10 
}); 
}); 
</script>

执行效果
jQuery插件-jRating评分插件源码分析及使用方法
可以看到,上面的例子中,有10颗星,是参数length的作用。其中,默认总分是20分,就是10颗星都选择。这里我们着重注意<div>的id16_1,其中16被用来初始化评分插件默认选择的比例,16/20 * 10。所以我们上面有8颗星是黄色的。

当我们把鼠标放到插件上时,小星星会随着鼠标移动而增加或减少(红色会覆盖黄色或白色),表示评分的从0至20,但点击鼠标时,评分结束,插件不能再编辑了,同时,通过Ajax向指定的路径POST数据,用后台数据将评分数据持久化。

在分析源代码之前,我们先看一下使用该插件时有哪些可选参数:
jQuery插件-jRating评分插件源码分析及使用方法 
二、插件源码分析

按照jQuery插件开发的推荐方法,为了避免快捷符号“$”与其他JavaScript插件产生冲突,源码开头采用了下面技术:

(function($) { 
$.fn.jRating = function(op) { 
//这里为插件代码 
} 
})(jQurery)

接下来,我们分析的所有代码都将出现在上面绿色区域部分,首先设置默认参数。
var defaults = { 
/** String vars **/ 
bigStarsPath : 'icons/stars.png', // 设置大星星(默认显示)的相对路径 
smallStarsPath : 'icons/small.png', // 小星星 
phpPath : 'php/jRating.php', // 点击鼠标,评分确定后,将POST数据的地址,接下来我们会采用ASP.Net技术进行处理 
type : 'big', // 可以看出,默认是使用大星星 
/** Boolean vars **/ 
step:false, // 如果设置为True,则星星要么全变色,要么不全变,当然这也适和选择分数是同步的。 
isDisabled:false, //如果设置为True,则插件不能编辑,当点击鼠标过后,默认是True的状态 
showRateInfo: true, //当鼠标放到星星上时,是否在鼠标下方显示选择比例信息,例如16/20 
/** Integer vars **/ 
length:5, // 星星的个数 
decimalLength : 0, // 选择的数字其后的小数位,最多为3位,如果设置为1,可能出现的情况为16.3/20 
rateMax : 20, // 比例中的分母,整数0-9999 
rateInfosX : -45, // 信息提示框相对于鼠标位置的横坐标位置 
rateInfosY : 5, // 同上,纵坐标位置 
/** Functions **/ 
onSuccess : null, //成功后的回调函数 
onError : null //出错处理函数 
};

通过上面绿色部分的解释,我们可以看到所有参数的默认值,同时,我们可以在插件使用中,根据需求确定适合的配置,插件的使用不就是这些参数的搭配组合吗?
接下来我们再看一个函数作用域:
if(this.length>0) 
return this.each(function() { //接下来出现的代码,都将在此处!!!}

这段代码很简单,我们要在选中的集合上执行jRating()函数,而上面的代码首先判断该集合是否长度大于0,如果为1或者更多,则在该集合上执行each()函数,对集合中的每一个元素(div)进行单独处理。
该插件的核心代码其实都在上面的each()函数中,我们首先看几个函数,这几个函数都定义在each()函数中,并被其他语句调用。
function findRealLeft(obj) { 
if( !obj ) return 0; 
return obj.offsetLeft + findRealLeft( obj.offsetParent ); 
};

首先关注findRealLeft()函数,该函数接收名为obj的对象参数,最后返回该元素对象相对于浏览器左边界的距离。注:offsetParent是指元素最近的定位(relative,absolute)祖先元素,如果没有祖先元素是定位的话,会指向body元素。offsetLeft返回相对于offsetParent的位置。
function getNote(relativeX) { 
var noteBrut = parseFloat((relativeX*100/widthRatingContainer)*opts.rateMax/100); //两个100是否可以去掉,表示选择的比例,如16 或 16.1 
switch(opts.decimalLength) { //根据参数确定要输去比例需要的小数位,例如16.1 16.12 16.123 
case 1 : 
var note = Math.round(noteBrut*10)/10; 
break; 
case 2 : 
var note = Math.round(noteBrut*100)/100; 
break; 
case 3 : 
var note = Math.round(noteBrut*1000)/1000; 
break; 
default : 
var note = Math.round(noteBrut*1)/1; 
} 
return note; 
};

接着关注getNote函数,首先我们看以下relativeX是一个什么东西:
var realOffsetLeft = findRealLeft(this); 
var relativeX = e.pageX - realOffsetLeft;

上面两行代码是调用getNote函数前,定义relativeX变量用的,我们可以分析出relativeX的作用。这里的this是我们应用jRating()函数的某个div,首先获得其相对于浏览器的左边距,因为上面两行代码是出现在鼠标移动处理函数mouseenter中(稍后我们会看到),因此这里的e.pageX表示鼠标相对于浏览器的横向距离。因此,这里的relativeX表示的是鼠标相对于<div>左边界的横向距离。
我们再次关注getNote函数,由widthRatingContainer = starWidth*opts.length可以看出,widthRatingContainer是左右星星图片加起来的宽度。因此,var noteBrut = parseFloat((relativeX*100/widthRatingContainer)*opts.rateMax/100);可以把分母与分子上的两个100去掉,即(relativeX/widthRatingContainer)*opts.rateMax),noteBrut变量最后存储的是鼠标选择的比例,如果rateMax设为20,则noteBrut的范围可以通过鼠标来确定(0—20)。
switch函数,是通过decimalLength参数(用来设定显示比例的小数位),最终确定(比例)显示的位数。读到这里,我们可以发现,getNote函数就是通过relativX来返回鼠标选择的比例,这个比例是什么,见下图用笔刷框起来的部分:
jQuery插件-jRating评分插件源码分析及使用方法 
接下来,我们再关注一个函数
function getStarWidth(){ 
switch(opts.type) { 
case 'small' : 
starWidth = 12; // small.png小星星图片的宽度 
starHeight = 10; // 高度 
bgPath = opts.smallStarsPath; //图片相对地址 
break; 
default : 
starWidth = 23; // 大星星的宽度,可以看到,这是默认值 
starHeight = 20; // 高度 
bgPath = opts.bigStarsPath; //星图片相对地址 
} 
};

这个是一个比较简单的用于初始化变量的函数,根据type属性,初始化三个变量,分别是starWidth、starHeight、bgPath,绿色的注释信息已能够说明一切,不再赘述!
each()中定义的函数看完了,接下来,我们还在each()函数中进行游荡,按照从上到下的顺序,先截取了几行代码如下:
var opts = $.extend(defaults, op), //利用extend()函数将默认参数与输入参数进行合并,最后存储在opts变量中。 
newWidth = 0, //定义变量,该变量用于存储relativeX,但会根据step属性进行相应调整 
starWidth = 0, //定义变量,星星的宽度 
starHeight = 0, //高度 
bgPath = ''; //星星图片地址 
if($(this).hasClass('jDisabled') || opts.isDisabled) //确定jDisabled变量,表示是否能对div进行操作 
var jDisabled = true; 
else 
var jDisabled = false; 
getStarWidth(); //这个函数不赘述,上面分析过 
$(this).height(starHeight); //根据星星的高度,确定此div的高度。

接着往下看
var average = parseFloat($(this).attr('id').split('_')[0]), //通过<div>的id(例如16_2),获取下划线前面的数字,把该数字作为默认的选择比例 
idBox = parseInt($(this).attr('id').split('_')[1]), // 下划线后面的部分,作为辨别评分插件的id 
widthRatingContainer = starWidth*opts.length, // 星星图片宽度总和,并作为外围容器的宽度 
widthColor = average/opts.rateMax*widthRatingContainer, // 颜色块占用的宽度

接下来,我们将看到新建的三个<div>,并插入到主div中
quotient = 
$('<div>', 
{ 
'class' : 'jRatingColor', 
css:{ 
width:widthColor 
} 
}).appendTo($(this)), 
average = 
$('<div>', 
{ 
'class' : 'jRatingAverage', 
css:{ 
width:0, 
top:- starHeight 
} 
}).appendTo($(this)), 
jstar = 
$('<div>', 
{ 
'class' : 'jStar', 
css:{ 
width:widthRatingContainer, 
height:starHeight, 
top:- (starHeight*2), 
background: 'url('+bgPath+') repeat-x' 
} 
}).appendTo($(this));

首先我们分析第一个<div>,它的类名为jRatingColor,它表示默认比例,用黄色表示,它的长度为withColor,这里主要看一下它的样式表:
.jRatingColor { 
background-color:#f4c239; /* bgcolor of the stars*/ 
position:relative; //相对定位 
top:0; 
left:0; 
z-index:2; //这里需注意,该div的祖先即我们each函数中的this 的z-index是1,下面我们将马上看到。 
height:100%; 
}

第二个<div>样式表如下:
.jRatingAverage { 
background-color:#f62929; //红色 
position:relative; 
top:0; 
left:0; 
z-index:2; 
height:100%; 
}

但在上面的程序中,初始化时,把宽度设为0(因为鼠标还没选嘛),同时改变了top值:- 星高度,这样它就和上面添加的div在纵方向上重合了。
接下来看第三个<div>,主要用来放小星星。
/** Div containing the stars **/ 
.jStar { 
position:relative; 
left:0; 
z-index:3; 
}

这个样式表比较简单,我们着重看一下JS中动态添加的几个属性值:
width:widthRatingContainer, //设置宽度 
height:starHeight, //高度 
top:- (starHeight*2), //改变纵方向的值,和上面两个<div>重合 
background: 'url('+bgPath+') repeat-x' //设置背景为小星星

属性的值设置了,但也许有人会问,问什么只看到小星星颜色是彩色的,而上面添加的前两个<div>不是具有高度的长方形颜色条吗?下面我们看一下小星星的图片就明白为什么了!
jQuery插件-jRating评分插件源码分析及使用方法
不用多说,旁边用不透明的背景,中间小星星是透明的,下面的颜色自然就显示出来了!!
接下来的语句很简单,就是设置一下最外层div容器的样式,注意z-Index属性:
$(this).css({width: widthRatingContainer,overflow:'hidden',zIndex:1,position:'relative'});

接下来会进入相对复杂的部分,我们将关注鼠标动作及其响应效果,首先关注一个小逻辑:
if(!jDisabled)
//接下来的代码
可以看出,前面我们设置的jDisable变量在这里用上了,如果jDisabled为true,就表示插件禁用了,那么接下来的鼠标操作将不会被执行。
接下来看鼠标操作是如何添加到插件中的:
$(this).unbind().bind({//鼠标事件处理代码,下面将分别进行讨论。
});
首先看以一下鼠标进入事件处理代码
mouseenter : function(e){ 
var realOffsetLeft = findRealLeft(this); 
var relativeX = e.pageX - realOffsetLeft; //首先计算出relativeX,它表示的是鼠标相对于外层<div>左边界的横向距离 
if (opts.showRateInfo) 
var tooltip = 
$('<p>',{ 
'class' : 'jRatingInfos', 
html : getNote(relativeX)+' <span class="maxRate">/ '+opts.rateMax+'</span>', //注意这里用了getNote方法,前面已讲了它的用途。 
css : { 
top: (e.pageY + opts.rateInfosY), 
left: (e.pageX + opts.rateInfosX) 
} 
}).appendTo('body').show(); 
},

relativeX变量不多解释,这里的注释和前面都有提到,接下来,判断showRateInfo参数是否为true,如果为true,表示要显示比例信息(例如鼠标下面显示16/20),tooltip变量就是这个信息框,最后通过appendTo方法添加到body中。代码逻辑很简单,这个函数主要用来显示提示框<p>,我们在这里可以重点关注一下<p>节点的样式,它是绝对定位的,并利用代码改变了top和Left值,看一下相关的样式表:
p.jRatingInfos { 
position: absolute; 
z-index:9999; 
background: transparent url('http://www.cnblogs.com/icons/bg_jRatingInfos.png') no-repeat; 
color: #FFF; 
display: none; 
width: 91px; 
height: 29px; 
font-size:16px; 
text-align:center; 
padding-top:5px; 
} 
p.jRatingInfos span.maxRate { 
color:#c9c9c9; 
font-size:14px; 
}

接下来我们看一下鼠标进来后的mousemove事件的处理函数:
mousemove : function(e){ 
var realOffsetLeft = findRealLeft(this); 
var relativeX = e.pageX - realOffsetLeft; 
if(opts.step) newWidth = Math.floor(relativeX/starWidth)*starWidth + starWidth; 
else newWidth = relativeX; 
average.width(newWidth); 
if (opts.showRateInfo) 
$("p.jRatingInfos") 
.css({ 
left: (e.pageX + opts.rateInfosX) 
}) 
.html(getNote(newWidth) +' <span class="maxRate">/ '+opts.rateMax+'</span>'); 
},

这个函数主要用来确定鼠标选择的比例,当然这个比例是通过getNote(newWidth)来得到的,那么,确定合适的newWidth值就成了这个函数的核心,如果opts.step为true,即比例只能是整数个星星(不能为15.3等等),那么我们看一下这个逻辑:Math.floor(relativeX/starWidth),starWidth是星星图片的宽度,Math.floor(-0.1)=-1,Math.floor(0.1) = 0,Math.floor(2.6)=2,知道这些,上面加红的代码就很容易理解了。
OK,Let's go on,看一下三个简单的处理函数
mouseover : function(e){ 
$(this).css('cursor','pointer'); 
}, 
mouseout : function(){ 
$(this).css('cursor','default'); 
average.width(0); 
}, 
mouseleave: function () { 
$("p.jRatingInfos").remove(); 
},

mouseover函数确保鼠标进入插件后的显示样式,mouseout也是同样,但它将类名为average的div(红色的)宽度变成0,mouseleave函数让提示信息框消失。
最后一个函数,也是整个源码的结尾,当然也是最重要最复杂的——鼠标点击函数:
click : function(e){ 
//接下来的代码都在此处。 
}

我们分部来,先看第一部分:
$(this).unbind().css('cursor','default').addClass('jDisabled');

为什么这里只列出一条语句,因为它很重要,但也很简单,我们这里一定要关注unbind()函数,它非常非常重要,当点击鼠标后,首先把其他所有绑定到外围<div>的事件都去掉了,这样就鼠标点击的瞬间,该插件的外观就固定显示在浏览器中,不再随着鼠标事件而出现变化。当然,最后给<div>添加jDisabled属性。
我们接着往后走:
if (opts.showRateInfo) $("p.jRatingInfos").fadeOut('fast',function(){$(this).remove();}); 
e.preventDefault(); 
var rate = getNote(newWidth); //关注rate变量,后面要用到。 
average.width(newWidth);

第一句不难理解,删除提示信息框,第二句取消鼠标点击的默认操作,后面两句很简单,不再赘述,要知道newWidth在前面已提到,表示鼠标选择的宽度。
最后一条语句,把选择的比例发送到服务器端进行持久化操作:
$.post( 
opts.phpPath, //利用Ajax技术,向服务端发送数据的地址 
{ //Post过去的数据 
idBox : idBox, 
rate : rate, 
action : 'rating' 
}, 
function(data) { //回调函数,主要向插件自定义函数传递参数并执行。 
if(!data.error) 
{ 
if(opts.onSuccess) opts.onSuccess( element, rate ); 
} 
else 
{ 
if(opts.onError) opts.onError( element, rate ); 
} 
}, 
'json' //确定如何理解返回的数据,它采用json. 
);

利用jQuery做Ajax确实很简单,代码中做了必要注释,这里不再赘述,这个插件的源码就分析完了,比较粗,但整个逻辑也许体现了一些,希望该学习笔记对大家能有帮助。下面我们进入实战阶段。
三、实战jRating插件
为了更加逼近真实应用,我们先利用sql server建立一张数据库表,它是一个文章类型表,有id、标题、文章内容、评分四个字段,截图如下:
jQuery插件-jRating评分插件源码分析及使用方法 
评分字段默认为-1,表示该文章还没有被评分。当然,现在有人会说,这个表设计的很不合理,因为一篇文章不会只评分一次吧,应该每个用户都能进行评论,是的,我们在这里只是为了演示jRating插件利用Ajax进行持久化操作,因为是演示,所以一切从俭。
新建一个Web页面,用来显示第一篇文章(id为1)的标题、内容及评分插件,见前台代码:
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head runat="server"> 
<title></title> 
<link href="Script/jRating/jRating.jquery.css" rel="stylesheet" type="text/css" /> 
<script src="Script/jquery-1.7.min.js" type="text/javascript"></script> 
<script src="Script/jRating/jRating.jquery.js" type="text/javascript"></script> 
<script type="text/javascript"> 
$(function () { 
$(".theRating").jRating({ 
length: 20, 
phpPath: 'tempAjax.aspx/UpdateComment' //地址变成了一个aspx类型的WEB页面下的一个静态函数,稍后我们会看到! 
}); 
}); 
</script> 
</head> 
<body> 
<form id="form1" runat="server"> 
<div> 
<asp:Label ID="Label1" runat="server" Text="标题:"></asp:Label>   
<asp:Label ID="page1_title" runat="server" Text=""></asp:Label><br /> 
<asp:Label ID="page1_body" runat="server" Text=""></asp:Label><br /> 
<div id="16_1" class="theRating"></div> 
</div> 
</form> 
</body> 
</html>

后台CS代码如下:
protected void Page_Load(object sender, EventArgs e) 
{ 
if (!Page.IsPostBack) 
{ 
tempEntities cbx = new tempEntities(); //用了实体框架获取数据表 
var page1 = cbx.jRatingArticles.Where(m => m.id == 1).SingleOrDefault(); 
page1_title.Text = page1.title; 
page1_body.Text = page1.body; 
} 
}

为了减少数据库连接代码,我用了实体框架,只映射了一张表(jRatingArticle),就是上面我们看到的。获取id为1的文章对象,并把相应属性赋值到Label控件的Text属性中。
页面效果如下
jQuery插件-jRating评分插件源码分析及使用方法 
我们可以看到上面前台页面的JS代码中,有这样一条语句:
phpPath: 'tempAjax.aspx/UpdateComment'
它指明了,当鼠标点击插件后,要通过Ajax发送数据的地址,这里我们用.net页面技术来处理这个异步请求。tempAjax.aspx的后台cs代码如下:
[WebMethod()] 
public static void UpdateComment(int idBox, int rate) 
{ 
tempEntities cbx = new tempEntities(); 
var page1 = cbx.jRatingArticles.Where(m => m.id == 1).SingleOrDefault(); 
page1.is_comment = rate; 
cbx.SaveChanges(); 
}

此时,我们还需修改jRating插件的原文件,把鼠标单击(click)处理函数中的$.post函数替换如下:
$.ajax({ 
type: "POST", 
url: opts.phpPath, 
data: '{"idBox":"' + idBox + '","rate":"' + rate + '"}', 
contentType: "application/json; charset=utf-8", 
dataType: "json" 
});

为什么要改变源文件,因为我想改变Ajax请求的contentType属性,利用json格式发送请求数据,默认是application/x-www-form-urlencoded
OK,万事俱备,看一下执行效果(选择比例为16,16颗红星嘛):
jQuery插件-jRating评分插件源码分析及使用方法 
看看数据库的变化
jQuery插件-jRating评分插件源码分析及使用方法 
试验成功!今天学习就到这里,希望此篇学习笔记对大家能有所帮助!
Javascript 相关文章推荐
贴一个在Mozilla中常用的Javascript代码
Jan 09 Javascript
js实现拖拽 闭包函数详细介绍
Nov 25 Javascript
JS实现时间格式化的方式汇总
Oct 16 Javascript
学习使用grunt来打包JavaScript和CSS程序的教程
Jan 04 Javascript
jQuery获取当前点击的对象元素(实现代码)
May 19 Javascript
JavaScript地理位置信息API
Jun 11 Javascript
使用do...while的方法输入一个月中所有的周日(实例代码)
Jul 22 Javascript
AngularJS 前台分页实现的示例代码
Jun 07 Javascript
详解Vue 动态组件与全局事件绑定总结
Nov 11 Javascript
JavaScript中构造函数与原型链之间的关系详解
Feb 25 Javascript
vue2.x 通过后端接口代理,获取qq音乐api的数据示例
Oct 30 Javascript
Vue中rem与postcss-pxtorem的应用详解
Nov 20 Javascript
JS声明变量背后的编译原理剖析
Dec 28 #Javascript
动态的改变IFrame的高度实现IFrame自动伸展适应高度
Dec 28 #Javascript
2012年开发人员的16款新鲜的jquery插件体验分享
Dec 28 #Javascript
web性能优化之javascript性能调优
Dec 28 #Javascript
javascript的字符串按引用复制和传递,按值来比较介绍与应用
Dec 28 #Javascript
javascript 利用Image对象实现的埋点(某处的点击数)统计
Dec 28 #Javascript
Javascript 加载和执行-性能提高篇
Dec 28 #Javascript
You might like
PHP跨平台获取服务器IP地址自定义函数分享
2014/12/29 PHP
php PDO判断连接是否可用的实现方法
2017/04/03 PHP
基于jquery的跨域调用文件
2010/11/19 Javascript
jquery 插件开发 extjs中的extend用法小结
2013/01/04 Javascript
jquery win 7透明弹出层效果的简单代码
2013/08/06 Javascript
jQuery判断数组是否包含了指定的元素
2015/03/10 Javascript
jQuery EasyUI 布局之动态添加tabs标签页
2015/11/18 Javascript
jQuery Chart图表制作组件Highcharts用法详解
2016/06/01 Javascript
深入理解JS正则表达式---分组
2016/07/18 Javascript
微信小程序 简单DEMO布局,逻辑,样式的练习
2016/11/30 Javascript
JavaScript数据结构之二叉树的遍历算法示例
2017/04/13 Javascript
vue-quill-editor实现图片上传功能
2017/08/08 Javascript
js 获取json数组里面数组的长度实例
2017/10/31 Javascript
JavaScript实现的超简单计算器功能示例
2017/12/23 Javascript
360doc网站不登录就无法复制内容的解决方法
2018/01/27 Javascript
详解angular如何调用HTML字符串的方法
2018/06/30 Javascript
使用vue-cli webpack 快速搭建项目的代码
2018/11/21 Javascript
js消除图片小游戏代码
2019/12/11 Javascript
JavaScript构造函数原理及实现流程解析
2020/11/19 Javascript
原生js实现自定义滚动条
2021/01/20 Javascript
python使用clear方法清除字典内全部数据实例
2015/07/11 Python
Python利用Beautiful Soup模块创建对象详解
2017/03/27 Python
numpy使用fromstring创建矩阵的实例
2018/06/15 Python
Python实现识别图片内容的方法分析
2018/07/11 Python
QML实现钟表效果
2020/06/02 Python
html5本地存储之localstorage 、本地数据库、sessionStorage简单使用示例
2014/05/08 HTML / CSS
世界上最大的乐器零售商:Guitar Center
2017/11/07 全球购物
Notino罗马尼亚网站:购买香水和化妆品
2019/07/20 全球购物
澳洲的UGG雪地靴超级市场:Uggs.com.au
2020/04/06 全球购物
大学运动会入场词
2014/02/22 职场文书
乡镇交通安全实施方案
2014/03/29 职场文书
信仰心得体会
2014/09/05 职场文书
班子查摆四风个人对照检查材料思想汇报
2014/10/04 职场文书
小学中队活动总结
2015/05/11 职场文书
《法国号》教学反思
2016/02/22 职场文书
nginx 反向代理之 proxy_pass的实现
2021/03/31 Servers