I have a simple quiz form with several inputs and selects, and I need to measure the time it took the contestant to write/choose an answer.
This is what I'm trying, but it reports incorrect time:
$('input, select').on('focus', function(event) {
el = $(this);
name = el.attr('name'); // console.log(name);
a = performance.now();
a_value = el.val();
console.log(name + ' focused.');
$(el).on('input select cut copy paste', function(event) {
console.log('el: ' + el);
b_value = el.val();
if (a_value != b_value) {
b = performance.now();
if (name in times) {
console.log('exists');
times[name] = times[name] + (b - a);
} else {
times[name] = b - a;
}
}
});
$(el).on('blur', function(event) {
alert(times);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<input type="text" name="" id="">
<select name="" id="">
<option value="">option1</option>
<option value="">option2</option>
</select>
</body>
</html>
After talking with you (the OP), I've made several adjustments to your base code.
First, the .on('input ...') was being called every time the form element had focus so event handlers were stacking up. A corresponding .off('input ...') is called in the blur handler to handle this.
Next, to make an associative array in JavaScript we normally use objects, so I made times = {}.
Next, times[name] = times[name] + (b - a); keeps using the initial time value from a when the element was first focused, so aggregated time stacks up quickly. We can compensate for this by setting a = b; afterwards.
Finally, to keep track of when a select changes much like when the input changes, we can update the internal selected value like a_value = b_value; when the selection has changed.
I hope this is what you are looking for.
var times = {};
$('input, select').on('focus', function(event) {
var el = $(this);
// This will get the name of the input or select. Is that right?
// OP: yes, this becomes the key in the array
var name = el.attr('name');
var a = performance.now();
var a_value = el.val();
// This will attach an event handler over and over unless we
// unattach it. Please see "blur" below
el.on('input select cut copy paste', function(event) {
var b_value = el.val();
// Initial values are updated as inputs change
// so the times don't stack up
if (a_value !== b_value) {
b = performance.now();
if (times.hasOwnProperty(name)) {
console.log('exists');
times[name] = times[name] + (b - a);
a = b;
} else {
console.log('adding ' + name);
times[name] = b - a;
}
a_value = b_value;
}
});
el.one('blur', function(event) {
console.dir(times);
// Update the times display
displayTimes();
// Unattach the event handler added in on("focus")
el.off('input select cut copy paste');
});
// For the demo
function displayTimes() {
// Output results
var str = "";
$.each(times, function(key, value) {
str += key + " total time: " + value + "<br>";
});
$("#results").html(str);
}
// Periodically update the times just for the demo
setInterval(displayTimes, 200);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" name="input" id="">
<select name="select" id="">
<option value="option1">option1</option>
<option value="option2">option2</option>
</select>
<div id="results"></div>
Try this ..
<script>
var Stopwatch = (function() {
var s;
return {
settings: {
stop: 0,
sw: document.querySelectorAll(".stopwatch")[0],
results: document.querySelectorAll(".results")[0],
mills: 0,
secs: 0,
mins: 0,
i: 1,
times: ["00:00:00"],
clearButton: "Clear"
},
init: function() {
s = this.settings;
setInterval(this.timer, 1);
},
clear: function() {
s.i = 1,
s.times = ["00:00:00"],
s.results.innerHTML = s.clearButton;
},
lap: function() {
if (s.i === 1) {
s.results.innerHTML = s.clearButton;
}
s.times.push(("0" + s.mins).slice(-2) + ":"
+ ("0" + s.secs).slice(-2) + ":"
+ ("0" + s.mills).slice(-2));
var diffTime = ("0" + Math.floor(s.times[s.i].split(":")[0]
- s.times[s.i-1].split(":")[0])).slice(-2)
+ ":"
+ ("0" + Math.floor(s.times[s.i].split(":")[1]
- s.times[s.i-1].split(":")[1])).slice(-2)
+ ":"
+ ("0" + (s.times[s.i].split(":")[2]
- s.times[s.i-1].split(":")[2])).slice(-2);
s.results.innerHTML = s.results.innerHTML + "<tr><td>"
+ s.times[s.i] + "</td><td>"
+ diffTime + "</td></tr>";
s.i++;
},
restart: function() {
s.mills = 0,
s.secs = 0,
s.mins = 0;
this.start();
},
start: function() {
s.stop = 0;
},
stop: function() {
s.stop = 1;
},
timer: function() {
if (s.stop === 0) {
if (s.mills === 100) {
s.secs++;
s.mills = 0;
}
if (s.secs === 60) {
s.mins++;
s.secs = 0;
}
s.sw.innerHTML = ("0" + s.mins).slice(-2) + ":"
+ ("0" + s.secs).slice(-2) + ":"
+ ("0" + s.mills).slice(-2);
s.mills++;
}
}
};
})();
$('.textbox,.selectbox').focusin(function(event) {
Stopwatch.init();
Stopwatch.restart();
});
$('.textbox,.selectbox').on('blur', function(event) {
Stopwatch.stop();
});
Working Fiddle Here
References
I've made simple jquery plugin for this. It is able to tell you what is the total edit time (only when edit was actually in use), first edit time and last edit time for any input element. You can also get all edit times.
(function () {
var getTime = function () { return performance.now(); };
function MeasureTime () {
this.editTimes = [];
this.currentEdit = null;
this.lastEdit = {start:0, last: 0};
this.firstEdit = 0;
}
MeasureTime.prototype = {
setFirst: function () {
this.firstEdit = getTime();
this.setFirst = new Function();
},
startEdit: function (val) {
this.setFirst();
if(this.currentEdit == null) {
this.currentEdit = {start: getTime(), last: getTime(), value: val};
this.editTimes.push(0);
} else {
this.edit(val);
}
},
edit: function (val) {
if(this.currentEdit == null)
this.startEdit(val);
else {
var current = this.currentEdit;
if(current.value == val)
return;
current.last = getTime();
this.editTimes.pop();
this.editTimes.push(current.last - current.start);
}
},
stopEdit: function () {
if(this.currentEdit != null) {
this.lastEdit = this.currentEdit;
this.currentEdit = null;
}
},
getEvent: function () {
return new TimeMeasuredEvent(this.editTimes, this.currentEdit || this.lastEdit, this.firstEdit);
}
};
function TimeMeasuredEvent (all, current, first) {
this.all = all.slice(0);
this.start = current.start;
this.last = current.last;
this.first = first;
}
TimeMeasuredEvent.prototype = {
current: function () {
return this.all[this.all.length-1];
},
total: function () {
var sum = 0, a = this.all, l = a.length, i = -1;
while(++i<l)
sum+=a[i];
return sum;
}
};
function EnsureMeasureTime () {
if (typeof(this.measureTimeData) === "undefined") {
var mtd = this.measureTimeData = new MeasureTime();
$(this).on('focus', function () {
mtd.startEdit(this.value);
$(this).on('input.measuretime select.measuretime cut.measuretime copy.measuretime paste.measuretime', function () {
mtd.edit(this.value);
$(this).trigger('timeMeasured', [mtd.getEvent()]);
});
$(this).on('blur', function () {
mtd.stopEdit();
$(this).trigger('timeMeasured', [mtd.getEvent()]);
$(this).off('measuretime');
});
});
}
}
$.fn.measureTime = function () {
$(this).each(EnsureMeasureTime);
return this;
};
})();
Sample usage (fiddle):
var inputs = $('input, select');
inputs.measureTime();
var all = {};
inputs.on('timeMeasured', function (ev, data) {
console.log(ev, data);
all[ev.target.name] = data.total();
console.log("First edit time: " + data.first);
console.log("Last edit time: " + data.last);
console.log("All edits durations: " + data.all.join(", "));
console.log("Current edit duration: " + data.current());
console.log("Total edit duration: " + data.total());
var s = "";
for(var n in all) {
s+= n + ": " + all[n]+"\n";
}
$("#times").text(s);
});
You can also access raw MeasureTime object by element.measureTimeData to get edit times.
You can find what I tried in this fiddle.
The main point to me is that you were adding up times over and over again, since the initial time was not changing and you were adding the answering time more than once.
if (name in times) {
console.log('exists');
times[name] = times[name] + (b - a);
//here you already had added (b1 - a) with b1 < b
//either you reset 'a' here or you store the diff in a variable and sum it up at the end
} else {
times[name] = b - a;
}
I stored the answering time in a variable and added to the array on blur and tried to be as consistent as possible with your original approach.
There are still a couple of things I miss, though. Which is mainly related with cheating. As far as I got you want to count only the real changing time (from focus to the last input, say, not counting the time from the last input to blur and definitely not the time looking at the page and maybe writing the answer in a wordpad environment).
In a fair and safe system you should, IMHO, consider the time someone can look at the quiz more than the time s/he is actually writing the answer. But this obviously depends on what the quiz is made for!
Related
I need to develop an IP camera viewer and image capture website.
For that I have downloaded the WebSdk from Hikvision and run it without publish this website into any server at that time I can view live preview and capture the images from live preview too.
But when I publish this website into the IIS it stops capturing images.
I am calling "clickDeviceCapturePic" method all the time.
I am stuck at issue where I am not able to capture image from Hikvision camera.
It is not giving error and there is less documentation about anything.
If you have experience developing it .
Please give me advice .
Below is an code that I have tried.
// Initialize the plugin
// Save the currently selected window globally
var g_iWndIndex = 0; //You don’t need to set this variable. In the interface with window parameters, you don’t need to pass values. The development kit will use the current selection window by default.
var szIP = [];
var szPort = [];
var szUsername = [];
var szPassword = [];
var DocumentPath = "";
var DocumentName = "";
$(function () {
// var urlParams = new URLSearchParams(window.location.search);
DocumentName = $.urlParam("DocumentName");
DocumentPath = $.urlParam("DocumentPath");
// ReadTheJson
$.getJSON("../IPCameraCfg.json", function (data) {
// console.log(data);
szIP = data.IPCameras;
szPort = data.Ports;
szUsername = data.UserNames;
szPassword = data.Passwords;
}).fail(function () {
console.log("An error has occurred.");
});
// Check if the plugin has been installed
// console.log("installed ? ", WebVideoCtrl.I_CheckPluginInstall());
if (-1 == WebVideoCtrl.I_CheckPluginInstall()) {
alert(
"You have not installed the plugin yet, download and install WebComponents.exe!"
);
return;
}
/// Initialize plug-in parameters and insert plug-ins
WebVideoCtrl.I_InitPlugin(1350, 800, {
iWndowType: 3,
cbSelWnd: function (xmlDoc) {
g_iWndIndex = $(xmlDoc).find("SelectWnd").eq(0).text();
var szInfo = "Currently selected window number:" + g_iWndIndex;
// showCBInfo(szInfo);
},
});
WebVideoCtrl.I_InsertOBJECTPlugin("divPlugin");
// Check if the plugin is up to date
if (-1 == WebVideoCtrl.I_CheckPluginVersion()) {
alert("New plug-in version detected, please update WebComponents.exe!");
return;
}
/// Window event binding
$(window).bind({
resize: function () {
var $Restart = $("#restartDiv");
if ($Restart.length > 0) {
var oSize = getWindowSize();
$Restart.css({
width: oSize.width + "px",
height: oSize.height + "px",
});
}
},
});
// //initialization date and time
var szCurTime = dateFormat(new Date(), "yyyy-MM-dd");
$("#starttime").val(szCurTime + " 00:00:00");
$("#endtime").val(szCurTime + " 23:59:59");
//The login and preview methods are called here with setTimeout. If called directly, the window will not open because it takes time to load
clickSetLocalCfg();
setTimeout(function () {
clickLogin();
}, 3000);
setTimeout(function () {
clickStartRealPlay();
}, 4000);
});
function clickLogin() {
// var szPort = "80";
//var szUsername = "admin";
//var szPassword = "5E12345#";
console.log("Test", szIP[i], szPort[i], szUsername[i], szPassword[i]);
for (var i = 0; i < szIP.length; i++) {
var iRet = WebVideoCtrl.I_Login(
szIP[i],
1,
szPort[i],
szUsername[i],
szPassword[i],
{}
);
}
}
function clickStartRealPlay() {
for (var i = 0; i < szIP.length; i++) {
iWndIndex = i;
var iRet = WebVideoCtrl.I_StartRealPlay(szIP[i], {
iWndIndex: iWndIndex,
});
}
}
// device capturing
function clickDeviceCapturePic() {
//var szInfo = "";
for (var i = 0; i < szIP.length; i++) {
// console.log("loop", i);
var szDeviceIdentify = szIP[i]; // $("#ip").val();
// var bZeroChannel =
// $("#channels option")
// .eq($("#channels").get(0).selectedIndex)
// .attr("bZero") == "true"
// ? true
// : false;
var iChannelID = i; //parseInt($("#channels").val(), 10);
var iResolutionWidth = parseInt(200, 10);
var iResolutionHeight = parseInt(200, 10);
// if (null == szDeviceIdentify) {
// return;
// }
// if (bZeroChannel) {
// // zero channel do not support device capturing
// return;
// }
var szPicName = DocumentName + "_" + i;
//szDeviceIdentify + "_" + iChannelID + "_" + new Date().getTime();
var iRet = WebVideoCtrl.I_DeviceCapturePic(
szDeviceIdentify,
iChannelID,
szPicName,
{
bDateDir: false, //generate the date file or not
iResolutionWidth: iResolutionWidth,
iResolutionHeight: iResolutionHeight,
}
);
if (0 == iRet) {
console.log(szPicName, "device capturing succeed!");
} else {
console.log(szPicName, "device capturing failed!");
}
}
// showOPInfo(szDeviceIdentify + " " + szInfo);
}
// time format
function dateFormat(oDate, fmt) {
var o = {
"M+": oDate.getMonth() + 1, //month
"d+": oDate.getDate(), //day
"h+": oDate.getHours(), //hour
"m+": oDate.getMinutes(), //minute
"s+": oDate.getSeconds(), //second
"q+": Math.floor((oDate.getMonth() + 3) / 3), //quarter
S: oDate.getMilliseconds(), //millisecond
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(oDate.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return fmt;
}
// set local parameters
function clickSetLocalCfg() {
var arrXml = [],
szInfo = "";
arrXml.push("<LocalConfigInfo>");
arrXml.push("<PackgeSize>" + $("#packSize").val() + "</PackgeSize>");
arrXml.push("<PlayWndType>" + $("#wndSize").val() + "</PlayWndType>");
arrXml.push(
"<BuffNumberType>" + $("#netsPreach").val() + "</BuffNumberType>"
);
arrXml.push("<RecordPath>" + $("#recordPath").val() + "</RecordPath>");
arrXml.push("<CapturePath>" + $("#previewPicPath").val() + "</CapturePath>");
arrXml.push(
"<PlaybackFilePath>" + $("#playbackFilePath").val() + "</PlaybackFilePath>"
);
arrXml.push(
"<PlaybackPicPath>" + $("#playbackPicPath").val() + "</PlaybackPicPath>"
);
arrXml.push("<DeviceCapturePath>" + "C:\\Temp" + "</DeviceCapturePath>");
arrXml.push("<DownloadPath>" + $("#downloadPath").val() + "</DownloadPath>");
arrXml.push("<IVSMode>" + $("#rulesInfo").val() + "</IVSMode>");
arrXml.push(
"<CaptureFileFormat>" +
$("#captureFileFormat").val() +
"</CaptureFileFormat>"
);
arrXml.push("<ProtocolType>" + $("#protocolType").val() + "</ProtocolType>");
arrXml.push("</LocalConfigInfo>");
let K = WebVideoCtrl.I_SetLocalCfg(arrXml.join(""));
console.log(K, "Config set");
}
function clickGetLocalCfg() {
console.dirxml(WebVideoCtrl.I_GetLocalCfg(), "Local Cfg");
}
function StopStreaming() {
//console.log("Stop Streaming",({}));
for (var i = 0; i < szIP.length; i++) {
iWndIndex = i;
var iRet = WebVideoCtrl.I_Stop({
iWndIndex: iWndIndex,
});
}
}
$.urlParam = function (name) {
var results = new RegExp("[?&]" + name + "=([^&#]*)").exec(
window.location.href
);
if (results == null) {
return null;
} else {
return decodeURI(results[1]) || 0;
}
};
My code is as follows:
var page = new WebPage(), testindex = 0, loadInProgress = false;
var fs = require('fs');
var sheet = fs.read('courtcalllist.csv').split("\n").map(function(row){
return row.split(",");});
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.onLoadStarted = function() {
loadInProgress = true;
console.log("load started");
};
page.onLoadFinished = function() {
loadInProgress = false;
console.log("load finished");
};
for (var i = 1; i < sheet.length - 1; i ++ ){
year = sheet[i][8];
district = sheet[i][9];
casenumber = sheet[i][10];
var steps = [
function () {//first function to open page
page.open("http://www.cookcountyclerkofcourt.org/CourtCaseSearch/CourtCallSearch.aspx");
}, //first function bracket
function () {// second function calls page to evaulate page input
page.evaluate(function(year, district, casenumber) {
console.log("this is my test " + year, district, casenumber);
document.getElementById('ctl00_MainContent_txtCaseYear').value = year; //'2018';
document.getElementById('ctl00_MainContent_txtCaseCode').value = district;//'M3';
document.getElementById('ctl00_MainContent_txtCaseNumber').value = casenumber;//'005338';
return;
},year, district, casenumber);
}, //2nd function bracket
function () { // 3rd function calls evaluate click
//click
page.evaluate(function() {
document.getElementById('ctl00_MainContent_btnSearch').click();
});
}, //3rd function bracket
function () { // 4th function calls the 2nd page after data
// Output content of page to stdout after form has been submitted
page.evaluate(function() {
for (i = 1; i < 11; i++ ){
console.log(document.querySelectorAll('td')[i].innerHTML);
}
});
} //4thbracket
];
interval = setInterval(function() {
if (!loadInProgress && typeof steps[testindex] == "function") {
console.log("step " + (testindex + 1));
steps[testindex]();
testindex++;
}
if (typeof steps[testindex] != "function") {
console.log("test complete!");
phantom.exit();
}
}, 50);
}
For some reason, the console.log WILL print the variables i pass as such:
this is my test 2018 M3 005338
Which is correct, that's what i need it to be, but when they're evaluated i get:
TypeError: null is not an object (evaluating 'document.getElementById('ctl00_MainContent_txtCaseYear').value = year')
undefined:3
The code above works if i hard code my values in it Which is what is commented out, but it won't take the variables i'm giving it.
Is my variable being turned from a string to an object therefore it can't be read? I'm at a loss here, i have even tried
document.getElementById('ctl00_MainContent_txtCaseYear').value = year.toString;
document.getElementById('ctl00_MainContent_txtCaseCode').value = district.toString;
document.getElementById('ctl00_MainContent_txtCaseNumber').value = caenumber.toString;
And it just gives me back the same error.
function () {
page.evaluate(function(string1, string2, string3) {
console.log("this is my test " + String(string1) + String(string2) + String(string3));//, district, casenumber);
document.getElementById('ctl00_MainContent_txtCaseYear').value = String(string1);//'2002';
// console.log("this is my test " + String(string2));
document.getElementById('ctl00_MainContent_txtCaseCode').value = String(string2);//'M1';//
// console.log("this is my test " + String(string3));
document.getElementById('ctl00_MainContent_txtCaseNumber').value = String(string3);//'144115'; //
return;
},year, district, casenumber);
Is how you pass variables. Notice the arguments go at the end, and they must be written as something else within the function()
Here is my current code, it's a loop to type out some text automatically when my site is loaded. The issues is it is very touch and go, it only works sometimes (generally first load, not when refreshed etc.) Can someone point out the issue?
var i = 0;
var line_1 = " Understand their core goal.. Act upon the emotion..";
var line_2 = " Then..";
var line_3 = " Create your own luck!";
var all = line_1 + "{" + line_2 + "{" + line_3 + "{{";
var has = "";
var time = 100;
var hit = 0;
function myLoop () {
setTimeout(function () {
if(all.charAt(i) == "{") {
//has +1"<br>";
time = 2000;
hit++;
if(hit == 3){
document.getElementsByName('cbar')[0].placeholder = 'Enter your email address to learn more';
}
}else{
has += (all.charAt(i));
time = 100;
}
if(hit == 4){
document.getElementById('cbar').value = "";
}else{
document.getElementById('cbar').value = has;
}
if(all.charAt(i) == "{" || hit == 3){
has = "";
}
i++;
if (i < all.length) {
myLoop();
}
}, time)
}
myLoop();
Try putting your code inside window.onload = function() {//Your code here...}; It should be enough just to wrap the following:
window.onload = function() {
var i = 0;
var line_1 = " Understand their core goal.. Act upon the emotion..";
var line_2 = " Then..";
var line_3 = " Create your own luck!";
var all = line_1 + "{" + line_2 + "{" + line_3 + "{{";
var has = "";
var time = 100;
var hit = 0;
myLoop();
}
The myLoop function definition should be outside the block.
Try this:
HTML:
<body onload="myFunction()">
Javascript:
function myFunction(){
//your code here
}
Try this:
$( document ).ready(function() {
// Your code here
});
PS: This is JQuery though.
I have a little piece of code that reads some ajax (this bit works) from a server.
var self = this;
var serverItems = new Array();
var playersOnlineElement = $("#playersOnline");
function DataPair(k, v) {
this.key = k;
console.log("new datapair: " + k + ", " + v);
this.value = v;
}
DataPair.prototype.getKey = function() {
return this.key;
}
DataPair.prototype.getValue = function() {
return this.value;
}
$.getJSON("http://127.0.0.1", function(data) {
$.each(data, function(key, val) {
var pair = new DataPair(key, val);
self.serverItems.push(pair);
});
});
console.log(serverItems.length); //Problem is here
for (var i = 0; i < serverItems.length; i = i + 1) {
var dpair = serverItems[i];
if (dpair.getKey() === "playersOnline") {
self.playersOnlineElement.text("Players Online: " + dpair.getValue());
}
}
The datapair and the JSON get loaded but when they are pushed to the array it doesn't seem to work. I tried with self.serverItems and just serverItems because netbeans showed me the scope of the variables being good if I used just serverItems but I am a bit confused as to why this doesn't work. Can anyone help me?
I put in comments where the error is. serverItems.length is 0 even though when debugging in a browser in the DOM tree it has an array serverItems with all the data inside.
Assumingly this serverItems is in another scope and not the one I am calling when I want to get the length?
add this code into the success part, since its asynchronous...
for (var i = 0; i < serverItems.length; i = i + 1) {
var dpair = serverItems[i];
if (dpair.getKey() === "playersOnline") {
self.playersOnlineElement.text("Players Online: " + dpair.getValue());
}
to...
$.getJSON("http://127.0.0.1", function(data) {
$.each(data, function(key, val) {
var pair = new DataPair(key, val);
self.serverItems.push(pair);
for (var i = 0; i < serverItems.length; i = i + 1) {
var dpair = serverItems[i];
if (dpair.getKey() === "playersOnline") {
self.playersOnlineElement.text("Players Online: " + dpair.getValue());
}
});
});
I Have an odd issue with some JavaScript code (again, I hate debugging JS code). I am working on a regular table - which I fill up from a JSON call, and have added in support for some paging (sort of 2x paging I guess you could call it), sorting and some selecting of rows. Everything is working nicely - BUT when a row is DESELECTED (and deselected only) my add_navigate event gets fired twice, which results in a bit of reloading of data that is not needed - and an indication of loading that is even more not needed.
First here's my JS code:
var customerType;
var selYear;
var selMonth;
var sdir;
var sort;
var page;
var noteId;
var hasDoneCall;
var customerId;
var customerIdChanged = false;
function initValues() {
customerType = "Publisher";
selYear = new Date().getFullYear();
selMonth = new Date().getMonth()+1;
sdir = false;
sort = "CustomerName";
page = 1;
noteId = false;
customerId = 0;
hasDoneCall = location.href.indexOf('#') > 0;
}
function flash(elm, color, duration) {
var current = elm.css('backgroundColor');
elm.animate({ backgroundColor: 'rgb(' + color + ')' }, duration / 2).animate({ backgroundColor: current }, duration / 2);
}
function createNotes(elm) {
var btn = jQuery(elm);
btn.attr('disabled', 'disabled');
bulkCreditOption('true', '', function(changeSet) {
var i = 0;
while (i < changeSet.length) {
var selector = "input[type=checkbox][value=" + changeSet[i] + "].check:checked";
var row = jQuery(selector).parent().parent();
var cell = row.find("td:nth-child(2)");
cell.html("" + cell.html() + "");
flash(row, '60, 130, 200', 500);
i++;
}
btn.removeAttr('disabled');
});
}
function deleteNotes(elm) {
var btn = jQuery(elm);
btn.attr('disabled', 'disabled');
bulkCreditOption('', 'true', function(changeSet) {
var i = 0;
while (i < changeSet.length) {
var selector = "input[type=checkbox][value=" + changeSet[i] + "].check:checked";
var row = jQuery(selector).parent().parent();
var cell = row.find("td:nth-child(2)");
cell.html(cell.text());
flash(row, '60, 130, 200', 500);
i++;
}
btn.removeAttr('disabled');
});
}
function bulkCreditOption(createNotes, deleteNotes, callback) {
var path = "/BulkCredit";
var data = "";
var checked = jQuery("input[type=checkbox].check:checked");
checked.each(function(chk) {
data += "&ids=" + urlencode(jQuery(this).val());
});
jQuery.ajax({
type: 'POST',
url: path,
dataType: 'json',
data: "createNotes=" + urlencode(createNotes) + data + "&deleteNotes=" + urlencode(deleteNotes),
success: function(msg) {
callback(msg);
}
});
}
initValues();
Sys.Application.add_init(function() {
Sys.Application.add_navigate(function(sender, e) {
var reinstate = e.get_state();
if (typeof (reinstate) != 'undefined' && typeof (reinstate.customerType) != 'undefined') {
customerType = reinstate.customerType;
selYear = reinstate.selYear;
selMonth = reinstate.selMonth;
sdir = reinstate.sdir;
sort = reinstate.sort;
page = reinstate.page;
noteId = reinstate.noteId;
customerId = reinstate.customerId;
} else {
initValues();
}
if (!customerIdChanged) {
jQuery("#customerTypeChanger").val(customerType);
jQuery("#customerFilter").val(customerId);
jQuery("#monthPicker").empty();
makeMonthPicker();
if (noteId != false && noteId != 'false') {
doShowNotes();
} else {
jQuery("#notesContent").hide();
jQuery("#tableContent").show();
doAjaxCall();
}
} else {
//logic to fetch customer specific stuff here, TODO
customerIdChanged = false;
}
});
Sys.Application.set_enableHistory(true);
jQuery(document).ready(function() {
origColor = jQuery("#dataTable > thead > tr > th").css('backgroundColor');
makeMonthPicker();
jQuery("#customerTypeChanger").val(customerType);
jQuery("#customerTypeChanger").change(function() {
customerType = jQuery(this).val();
iqSetHistory();
});
jQuery("#customerFilter").change(function() {
customerId = jQuery(this).val();
var tableBody = jQuery("#dataTable > tbody");
tableBody.find("tr").removeClass("selected");
tableBody.find("tr[rel=" + customerId + "]").addClass("selected");
customerIdChanged = true;
iqSetHistory();
});
jQuery(".checkAll").click(function() {
var elm = jQuery(this);
if (elm.is(':checked')) {
jQuery(".check").attr('checked', 'checked');
} else {
jQuery(".check").removeAttr('checked');
}
});
if (!hasDoneCall) {
if (noteId == false) {
doAjaxCall();
} else {
doShowNotes();
}
}
});
});
function makeMonthPicker() {
var selDate = new Date();
selDate.setFullYear(selYear);
selDate.setMonth(selMonth-1);
jQuery("#monthPicker").monthPicker(function(year, month) {
selYear = year;
selMonth = month;
iqSetHistory();
}, selDate);
}
var origColor;
var notesPath = "/ShowNotes";
function fadeOut(elm) {
elm.animate({ backgroundColor: 'rgb(180, 180, 180)' }, 250);
}
function fadeIn(elm) {
elm.animate({ backgroundColor: origColor }, 250);
}
function iqSetHistory() {
var state = { 'customerType': customerType, 'selYear': selYear, 'selMonth': selMonth, 'sdir': sdir, 'sort': sort, 'page': page, 'noteId': noteId, 'customerId':customerId };
Sys.Application.addHistoryPoint(state);
}
var ajaxPath = "/GetCreditListMonth";
function doAjaxCall() {
fadeOut(jQuery("#dataTable > thead > tr > th"));
jQuery.ajax({
type: "POST",
url: ajaxPath,
dataType: "json",
data: "month=" + selMonth + "&year=" + selYear + "&custType=" + customerType + "&sort=" + sort + "&sdir=" + sdir + "&page=" + page + "&asCsv=false",
success: function(msg) {
var table = jQuery("#dataTable");
var tableBody = table.find("tbody");
tableBody.empty();
var i = 0;
while (i < msg.Rows.length) {
var data = msg.Rows[i];
var row = jQuery("<tr rel=\"" + data.CustomerId + "\"></tr>");
if (data.CustomerId == customerId) {
row.addClass("selected");
}
if (i % 2 == 1) {
row.addClass("alternatetablerow");
}
var custName = data.CustomerName;
if (data.PaymentCreated) {
custName = "" + custName + "";
}
row.append("<td><input type=\"checkbox\" class=\"check\" name=\"ids\" value=\"" + getCreditId(data.CustomerId) + "\" /></td>");
row.append("<td>" + custName + "</td>");
row.append("<td>" + data.AmountExcludingTaxes + "</td>");
row.append("<td>" + data.BonusAmount + "</td>");
row.append("<td>" + data.Amount + "</td>");
row.appendTo(tableBody);
i++;
}
tableBody.find("input, a").click(function(event){ //Stop clicks from falling through to the table row event
event.stopPropagation();
return true;
});
tableBody.find("tr").click(function(event){
var row = jQuery(this);
if (row.hasClass("selected")) { //Deselect
jQuery("#customerFilter").val(0);
} else {
jQuery("#customerFilter").val(jQuery(this).attr('rel'));
}
jQuery("#customerFilter").triggerHandler("change");
});
createPager(msg.Pages, jQuery("#pager"));
jQuery(".checkAll").triggerHandler('click');
fadeIn(table.find('thead > tr > th'));
}
});
}
function downloadListAsCsv() {
window.location.href = ajaxPath + "?month=" + selMonth + "&year=" + selYear + "&custType=" + customerType + "&sort=" + sort + "&sdir=" + sdir + "&page=0&asCsv=true";
}
function doShowNotes(){
jQuery.ajax({
type: "GET",
url: notesPath + "/" + noteId,
success: function(msg) {
jQuery("#tableContent").hide();
jQuery("#notesContent").html(msg).show();
}
});
}
function showNotes(id) {
noteId = id;
iqSetHistory();
}
function showTable() {
noteId = false;
iqSetHistory();
}
function getCreditId(custId) {
return selYear + "-" + selMonth + "-" + custId;
}
function sortDataTable(col) {
if (col == sort) {
sdir = !sdir;
} else {
sdir = false;
}
page = 1
sort = col;
iqSetHistory();
}
function createPager(totalPages, elm) {
elm.empty();
if (totalPages > 1)
{
var builder = "";
var numDirections = 2;
if (page > 1)
{
if (page - numDirections - 1 > 0)
{
builder += CreatePageLinkStatic(1, "«");
builder += " ";
}
builder += CreatePageLinkStatic(page - 1, "<");
builder += " ";
}
var n = page - numDirections;
while (n < page)
{
if (n > 0)
{
builder += CreatePageLinkStatic(n, n);
builder += " ";
}
n++;
}
builder += page;
builder += " ";
n = page + 1;
while (n <= page + numDirections && n <= totalPages)
{
builder += CreatePageLinkStatic(n, n);
builder +=" ";
n++;
}
if (page < totalPages)
{
builder += CreatePageLinkStatic(page + 1, ">");
builder += " ";
if (page + numDirections < totalPages)
{
builder += CreatePageLinkStatic(totalPages, "»");
}
}
builder;
elm.append(builder);
}
}
function CreatePageLinkStatic(page, str){
return "" + str + "";
}
function pageDataTable(newPage){
page = newPage;
iqSetHistory();
}
And the markup:
<div id="tableContent">
<select id="customerTypeChanger">
<option selected="selected" value="Publisher">Publisher</option>
<option value="Advertiser">Advertiser</option>
</select>
<select id="customerFilter"><option value="0">Choose Customer</option><option value="1">Customer 1</option><option value="1">Customer 2</option>...</select>
<div id="monthPicker"></div>
<div>DownloadAsCSV</div>
<table id="dataTable" class="grid">
<thead>
<tr>
<th style="text-align: left"><input type="checkbox" name="toggleCheckBox" class="checkAll" value="dummy" /></th>
<th>Customer name</th>
<th><a href="javascript:sortDataTable('AmountExcludingTaxes')">Amount</th>
<th>Bonus amount</th>
<th>Amount including VAT</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="pagination" id="pager"></div>
<div>With the selected rows</div>
<input id="createNotes" type="button" value="Create notes" onclick="javascript:createNotes(this)" /> <input id="deleteNotes" value="Delete notes" type="submit" onclick="javascript:deleteNotes(this)" />
</div>
<div id="notesContent"></div>
If needed as well, here's the code I did for the monthpicker (it is a very basic datepicker thing that just lets you flick back and forth between months and gives an output like
< April 2009 May 2009 June 2009 >
(where bold is clickable links taking you to see just that month period, and italic is the already selected one, obviously actual html markup differs)
It utilizes the datepicker from jQuery UI to get the localized names of the months
(function($) {
var selDate;
$.fn.monthPicker = function(callback, selectedDate) {
selDate = selectedDate;
var elm = this;
this.html("<span class=\"prevMonthButton\"><</span><span class=\"prevMonth\"></span><span class=\"curMonth\"></span><span class=\"nextMonth\"></span><span class=\"nextMonthButton\">></span>");
populateDates(this);
var prevMonthFunc = function() {
var month = selDate.getMonth() - 1;
if (month < 0) {
month = 11;
selDate.setFullYear(selDate.getFullYear() - 1);
}
selDate.setMonth(month);
populateDates(elm);
callback(selDate.getFullYear(), selDate.getMonth() + 1);
return false;
}
var nextMonthFunc = function() {
var month = selDate.getMonth() + 1;
if (month > 11) {
month = 0;
selDate.setFullYear(selDate.getFullYear() + 1);
}
selDate.setMonth(month);
populateDates(elm);
callback(selDate.getFullYear(), selDate.getMonth() + 1);
return false;
};
this.find(".prevMonth > a").click(prevMonthFunc);
this.find(".prevMonthButton > a").click(prevMonthFunc);
this.find(".nextMonth > a").click(nextMonthFunc);
this.find(".nextMonthButton > a").click(nextMonthFunc);
}
function populateDates(elm) {
var months = jQuery.datepicker._defaults.monthNames;
var selYear = selDate.getFullYear();
var selMonth = selDate.getMonth();
elm.find(".curMonth").text(months[selMonth] + " " + selYear);
var prevMonth = selMonth - 1;
var prevYear = selYear;
if (prevMonth < 0) {
prevMonth = 11;
prevYear = prevYear - 1;
}
elm.find(".prevMonth > a").text(months[prevMonth] + " " + prevYear);
var nextMonth = selMonth + 1;
var nextYear = selYear;
if (nextMonth > 11) {
nextMonth = 0;
nextYear = nextYear + 1;
}
elm.find(".nextMonth > a").text(months[nextMonth] + " " + nextYear);
}
})(jQuery);
I know most of this JavaScript code sucks - but for the main part it seems to do the job quite nicely, but as I said click a row to select it, then click it to deselect and boom, double call to add_navigate which results in an extra call to my JSON service and a visual flicker on the client side - and I cannot work out why it happens (and even more strange, why it just happens when it is deselected and not on a selected one as well).
I would try doing
.unbind('click').click(function()
instead of
.click(function()
Just to make sure click events are not getting bound twice.
I think the issue is with double binding of events to the same element unconsiously.
This is usually the scenario,
(function($){
var MyDocument = new Object({
prepareBody : function(){
//addClick Event
$('div#updatedElement').click(MyDocument.ajaxCall());
//adjusting the height of an updatedElement to an-otherElement
$('div#updatedElement').css('height', $('div#otherElement').height());
},
ajaxCall : function(){
//do your ajax Call
$.getJSON('index.php',{param:1, param:2},function(response){
//do something with your response
$('div#updatedElement').html(response)
//say if after your call you decide to update the body again
MyDocument.prepareBody();
//what that does is you will double bind click to the updateElement div.
//The next time that it is click, the AjaxCall function will run twice
//The next time it is clicked the MyDocument.ajaxCall function will be run four times
//8 - 16 - 32 and by now, firefox would have crashed!
},'json');
}
});
$(document).ready(function(){
MyDocument.prepareBody()
});
})(jQuery);
So as advised by KClough, unbind Events before binding them so that they run only once! Sad jQuery does not overwrite them as do other frameworks!
Hope this helps some one else
Oh Sorry I had completely forgotten about this question.
I restructured some of my code a little while after this and the issue was then gone. I am not exactly sure what caused this, seeing as it still surprised me how I only ever, seemingly, got the double loading half the time.
Anyhoow, the issue is solved, only I don't know how or why, which still slightly bothers me, but on the other hand - it works.