问题的关键在与,整个js文件中更新数据参数只有一个,且不管是答完题之后,还是答题超时,这个参数值是实时更新的
我的项目中需要是实现的功能是:在一个题目界面,有一个10秒倒计时,10秒计时结束后,用户没有答出题,那么久请求下一题的数据渲染到页面中。如果用户没有答题超时,那么用户在点击选择答案的时候,我们向后请求下一题的数据,然后将数据渲染到页面。
我这里写的都是本地路径,产品上线时,就不能用本地路径了。
这其中就涉及到了题目的id值,无论是用户答题超时,还是用户选择了答案,题目的id是当前题目的id值加1。
(1)首先:在html页面,定义一个隐藏的标签来存储题目的id值
<input type="hidden" id="questionId" value={{$data["question"]->id}}/>
这里是用input设置类型为hidden,然后将题目的id值取出赋值给value值。
(2)定义一个10秒倒计时,在小于或等于0秒的时候,请求下一组数据值
其中questionId要定义为全局变量,10秒倒计时功能的实现主要是运用了setInterval函数,而将setInterval赋给timer,也必须定义为全局变量,因为在ajax请求的封装函数中,会用到清理定时器的情况。
//写在全局范围内
var timer = null;
var questionId;
questionId = $("#questionId").val();
//10秒倒计时,并在答题超时时,请求下一组数据
function RunTimer(num,questionId) {
$("#time").text(num);//将时间数值num渲染到页面
timer = setInterval(function (){ //定义匿名函数,
if(num <= 0){//首先判断num的值是不是<=0
clearInterval(timer);//如果num的值<=0,那就表示时间到了,就要将现在的倒计时清除,不清除倒计时,会是后面的定时器时间越来越快。
//下面是一个提示答题时间到了的遮罩层
$("#mask").html('<div class="model">答题时间<br/>已结束</div> ');
$("#mask").css("display","block");
//10秒结束,用一个定时器,在一秒之后请求下一组数据。
setTimeout(function(){
//在请求的时候,出现一个请等待的模态框,增强用户体验
$("#mask").html('<div class="loding"><img src="images/loding.gif"/></div>');
$("#mask").css("display","block");
//定义请求时需要的参数,其中studentId,answerId都被我写死了。实际中studentId是不写死的,然后规定用户答题超时时,给后台返回的answerId为-1
var studentId = 1;
var answerId = -1;
var params = {
questionId: questionId,
studentId: studentId,
answerId: answerId
}
//请求的路径,因为我是本地测试环境,所以url路径是同一个项目下的另一个文件。
var urlRes = "getAnswerScore";
//封装的ajax请求函数,这里定义一个传递的questionId的参数,是用来在ajax中重启定时器会用到的。
responseWithNoCallback(urlRes,params,questionId);
}, 1000);
}else{//此时是num>0的情况,也就是还在倒计时时的情况
num--; //函数每调用一次num减一
$("#time").text(num);
}
},1000); //每隔一秒执行一次该匿名函数
}
(3)用户点击之后,发送请求。
//给ul下面的li绑定click事件
$("#answerWrap li").on("click",function(event){
//用户一点击完,就要清除还在走的定时器
clearInterval(timer);
//这里是关键,取到存到隐藏的input中的题目的id值。
questionId = $("#questionId").val();
//获取用户点击的 event事件,保存点击的li的id值,用来后续的判断
event = window.event || event || e;
var target = event.target || window.event.srcElement;
var id = target.id;
//判断用户是否有选择
if(id != '' || id != null){//这里是如果用户有点击的情况
//给用户选择的li添加一个效果
var targetLi = document.getElementById(id);
targetLi.className = "active";
//事先将answerId的值已经用data-属性存入了每个li中。
//这里要注意data-属性的调用时,无论定义属性后缀有没有大写字母,获取值是都是小写字母
var answerId = targetLi.dataset.answerid;
//studentId 是写死的
var studentId = 1;
var urlRes = "getAnswerScore";
//这里就将隐藏在input中的题目的id值,给了参数questionId。
//这里要注意的是第一组题目的id是1,请求的链接是http://localhost/testenglevel4/branch/server/public/getAnswerScore?questionId=1&studentId=1&answerId=..
也就是说questionId=1获取了下一组数据。
var params = {
questionId: questionId,
studentId: studentId,
answerId: answerId
}
//请求之后,出现等待模态框
$("#mask").html('<div class="loding"><img src="images/loding.gif"/></div>');
$('#mask').css("display","block");
responseWithNoCallback(urlRes,params,questionId);
}
})
(4)现在,就是ajax 的封装装函数了
//ajax请求
function responseWithNoCallback(url, data, questionId) {
var xhr = $.ajax({
//提交数据的类型 POST GET
type: "get",
//提交的网址
url: url,
//提交的数据
data: data,
// 设置超时的时间20s
timeout:20000,
//返回数据的格式
datatype: "json", //"xml", "html", "script", "json", "jsonp", "text".
xhrFields: {
withCredentials: true
},
crossDomain: true,
//在请求之前调用的函数
beforeSend: function () {
},
//成功返回之后调用的函数
success: function (response) {
//在请求完成之后,li的选中的状态消失
$("#answerWrap li").each(function(index, el) {
if ($(this).hasClass('active')) {
$(this).removeClass('active');
}
});
//json数据解析的两种方法,建议用我用的这种,eval不安全
// var response = eval("(" + response + ")");解析json数据
var response = JSON.parse(response);
var data = response.data;
//这一块是数据渲染到页面,可以用不细究
$(".question_title").text(data.nextQuestion.title);
$("#audio").data("type",data.nextQuestion.type);
//将音频资源链接加载到文档中,
// $("player").attr("src",data.nextQuestion.audio);
$("#optionOne").text(data.nextQuestion.answers[0].answer);
$("#optionTwo").text(data.nextQuestion.answers[1].answer);
$("#optionThree").text(data.nextQuestion.answers[2].answer);
$("#optionFour").text(data.nextQuestion.answers[3].answer);
$("#optionOne").data("answerId",data.nextQuestion.answers[0].id);
$("#optionTwo").data("answerId",data.nextQuestion.answers[1].id);
$("#optionThree").data("answerId",data.nextQuestion.answers[2].id);
$("#optionFour").data("answerId",data.nextQuestion.answers[3].id);
//重点,这里是请求返回的数据中的question的Id值取出,并给到input的value中存储起来
questionId = data.nextQuestion.id;
$("#questionId").val(questionId);
//到这新的数据已经渲染到页面了,那么请等待的模态框就可以消失了
$("#mask").html('<div class="loding"><img src="images/loding.gif"/></div>');
$('#mask').css("display","none");
//在题目重新加载完的时候,重启倒计时,这是要把现在请求到哪题了,也就是这时题目的id值给到Runtime中请求时的questionId
RunTimer(3,questionId);
//有值的传递就要将值return出去
return questionId;
},
//调用执行后调用的函数
complete: function (XMLHttpRequest, textStatus) {
},
//调用出错执行的函数
error: function () {
//请求出错处理
console.log("error");
//这里是请求出错,也就是已经将最后一组数据答完了,后面已经没有数据,就在同一个页面渲染答完题的页面
$("#questionTest").css("display","none");
$("#userInfo").css("display","block");
$('#mask').css("display","none");
}
});
}
最重要是要明白,javascript是需要顺序执行的,其实看完了实现过程,会了解到,ajax请求中新添加的questionId传递给倒计时函数Runtime,是没必要的,还搞的很难理解,只需要在Runtime函数的请求之前,将input的value的值取到就可以了。
我为什么要保存这个步骤呢,是因为一开始我使用的传参的方法,去建立倒计时函数,onclick函数,ajax封装函数之间的联系,但是后来出现了bug.
之前onclick事件是这样写的,questionId的值一开始是1,这个对于一直都会进行选择的用户是没有问题的,但是对于有的题选择了,有的题答题超时的用户,就会出现questionId发生错乱的情况,因为,click事件中请求时传递的参数param中questionId值变化是1,2,3,4,5,6,7,8....是顺序变化的,而请求中的第三个参数questionId使用来传参给ajax请求中重启定时器的参数questionId,这就出现了参数变化不统一d的情况。
var questionId = 1;//定义为全局变量
//用户点击选项后,将用户选择的答案返回给后台。
$("#answerWrap li").on("click",function(event){
clearInterval(timer);
event = window.event || event || e;
var target = event.target || window.event.srcElement;
var id = target.id;
//questionId = $("#questionId").val();//这句是关键,让全局只有一个统一变化的questionId
if(id != '' || id != null){
var targetLi = document.getElementById(id);
targetLi.className = "active";
var answerId = targetLi.dataset.answerid;
var studentId = 1;
var urlRes = "getAnswerScore";
var params = {
questionId: questionId,
studentId: studentId,
answerId: answerId
}
responseWithNoCallback(urlRes,params,questionId);
questionId++;
}
})