Measure actual activity time - javascript

Fiddle: http://jsfiddle.net/qfn04xzj/
var a, b;
$('input:not([type=password]), textarea, select').on('focus', function(event) {
a = performance.now();
});
$('input:not([type=password]), textarea, select').on('blur', function(event) {
b = performance.now();
$('#console').append( $(this).attr('name') + ' took ' + (b - a) + ' ms.' + "<br/>");
});
... works but it measures time between focus and blur.
Is there any way I could measure the actual time — the time it took to type or time between two value changes?

You should first define what actual time means. If you mean the time between the first and last keystroke, then the following code should work. It creates an object for each time the user focuses on the input and listens for every keystroke, keeping record of the first and last keystrokes. When the input is blurred, it computes the difference between the two.
Of course, the user can still type a couple of characters, go for a cup of coffee and then return and type the others, which will result in a very long time of activity. There is no way to know how much time the user has spent actually staring at the input. You could, however, define a "timeout" or period of inactivity after which you assume that the user is idle.
Fiddle
$('input:not([type=password]), textarea, select').on('focus', function() {
new EditingSession($(this));
});
/**
* Represents the period of time during which the user is focused on
* the input.
*/
function EditingSession(input){
var firstKeydownTime = null;
var lastKeydownTime = null;
var sessionId = makeRandomId();
input.on("keydown." + sessionId, function(){
// User typed something
var time = performance.now();
lastKeydownTime = time;
if(firstKeydownTime === null){
firstKeydownTime = time;
}
});
input.on("blur." + sessionId, function(){
// Editing session finished.
// Detach all handlers
input.off("." + sessionId);
// Print time between first and last keydown
var time;
if(firstKeydownTime === null){
time = 0;
} else {
time = lastKeydownTime - firstKeydownTime;
}
$('#console').append(input.attr('name') + ' took ' + time + ' ms.' + "<br/>");
});
}
// Borrowed from http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
function makeRandomId() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}

Instead of getting the time difference between keydown and keyup (which is silly :)) you need to simply get one single performance.now() timestamp and store it into a b variable for a later "input" event time-difference comparison.
var $fields = $('input:not([type=password]), textarea, select'),
$console = $("#console"),
$total = $("#totTime"),
tot=0, a=0, b=0;
$fields.on('input', function() {
a = performance.now();
var diff = (a - b)|0; // |0 prevents floated results (happens in FF)
$console.append( this.name +" took "+ diff +" ms"+ this.value +"<br>");
$total.text("Total time: "+ (tot+=diff) +" ms");
b = a; // Store a into b for later use.
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="totTime"></div>
<input type="text" name="" id="">
<div id="console"></div>
Performance-wise (since you're dealing with hopefully time accuracy) cache your Elements selectors into variables, that way you'll prevent parsing again the DOM in the search for your elements on every input event.
Also "input" as event is quite enough to handle all you need from copy-paste, change etc...

This will give you total time typing: Fiddle
<input type="text" name="" id="">
<div id="total"></div>
<div id="console"></div>
JS
$('input:not([type=password]), textarea, select').on('input change keyup', function (event) {
if (event.type == 'input') {
$('#console').append("input: " + $(this).val() + "<br/>");
}
if (event.type == 'change') {
$('#console').append("change: " + $(this).val() + "<br/>");
}
});
var a, b, total = 0;
$('input:not([type=password]), textarea, select').on('keydown', function (event) {
a = performance.now();
});
$('input:not([type=password]), textarea, select').on('keyup', function (event) {
b = performance.now();
var net = (b - a);
$('#console').append($(this).attr('name') + ' took ' + net + ' ms.' + "<br/>");
var final = total += net;
$('#total').text('Total time typing: ' + final + ' ms.');
});

Related

Pure Javascript - get beginning position of current line in a multiline textarea

I'm looking for a pure javascript answer as that reflects my projects scope. jQuery answers will not be marked correct, but are welcomed for future question seekers. Side note: I am also not interested in third party libraries.
I'm trying to get the first position (0) of the current line (current is based of selectionStart the chance the user selects more than 1 line) in a multiline textarea, but translate that to caret index.
What have I tried? Nothing pretty:
for ( i = 0; i < this.selectionStart; i++ ) {
if (this.value.substr(i,1).match(/\r?\n|\r/)) {
lineStartIndx = i + 1
}
}
This is proving to be costly when iterating textareas with huge amounts of lines. My personal usage of this will not be executed every keydown, however, I just used it as an example. Are there any better methods, built in or otherwise to emulate this outcome?
My full example:
var input = document.getElementById("ta");
var output = document.getElementById("output");
let pattern = /\r?\n|\r/;
var lineNum, lineStartIndx;
input.addEventListener("keydown", function(e) {
taHandler(this);
})
input.addEventListener("click", function(e) {
taHandler(this);
})
function taHandler(elem) {
lineNum = getLineNumForSelection(elem);
let caretPos = elem.selectionStart;
lineStartIndx = 0;
for ( i = 0; i < caretPos; i++ ) {
if (elem.value.substr(i,1).match(pattern)) {
lineStartIndx = i + 1
}
}
output.innerHTML = "Selection Start: " + caretPos + " Selection End: " + elem.selectionEnd +
" <br> Line Number: " + lineNum.start +
"<br>Line Start Position: " + lineStartIndx;
}
function getLineNumForSelection(sel) {
return {
'start' : sel.value.substr(0, sel.selectionStart).split(pattern).length,
'end' : sel.value.substr(0,sel.selectionEnd).split(pattern).length
};
}
<textarea id="ta" rows="5" cols="50">
Line one
Line two
Line three
Line four
</textarea>
<hr>
<div id="output"></div>
The method in my copy of the snippet splits the content into lines and uses the .length property instead of a loop so it looks "prettier" but according to time complexity of javascript's .length may not be any faster since there is nothing in the spec that prevents slow browser implementation.
Two side notes about the code, I'd use lineStartIndex, not lineStartIndx without the e. Since lineNum is an array, I'd use lineNumArray or selLineNums or something that is more obvious since variables ending in num are generally integers.
var input = document.getElementById("ta");
var output = document.getElementById("output");
let pattern = /\r?\n|\r/;
var lineNum, lineStartIndx;
input.addEventListener("keydown", function(e) {
taHandler(this);
})
input.addEventListener("click", function(e) {
taHandler(this);
})
function taHandler(elem) {
lineNum = getLineNumForSelection(elem);
let caretPos = elem.selectionStart;
lineStartIndx = 0;
// begin modified code
let lines = elem.value.split(pattern),
lineIndex = 0;
while ( (lineIndex + 1 ) < lineNum.start ) {
lineStartIndx += parseInt( lines[lineIndex].length ) + 1;
lineIndex++;
}
// end modified code
// begin replaced code
for ( i = 0; i < caretPos; i++ ) {
if (elem.value.substr(i,1).match(pattern)) {
lineStartIndx = i + 1
}
}
// end replaced code
output.innerHTML = "Selection Start: " + caretPos + " Selection End: " + elem.selectionEnd +
" <br> Line Number: " + lineNum.start +
"<br>Line Start Position: " + lineStartIndx;
}
function getLineNumForSelection(sel) {
return {
'start' : sel.value.substr(0, sel.selectionStart).split(pattern).length,
'end' : sel.value.substr(0,sel.selectionEnd).split(pattern).length
};
}
<textarea id="ta" rows="5" cols="50">
Line one
Line two
Line three
Line four
</textarea>
<hr>
<div id="output"></div>

How to add an "exception" or "condition" in this code (javascript, jquery)?

I would like to add an exception or condition in this code.
var a = 0;
$(document).ready(function() {
$(document).on("change", "#txtInput" ,function(){
$("#contenido").append("<tr><td>"+$("#txtInput").val()+"</td></tr>");
a += 1;
var str = 'Total Bianuales: '
$('p').html(str + a);
}
)
});
This is how it works: I get a value with the ID txtInput. I got some condition about this ID up in my code but I want to apply it when it add the value in a table that it's created. How do I make a condition that only append the value I get from txtInput when it's major or equal than 9?
Thanks for your help anyway!
Remember that change fires when input looses focus so you will need to click out of the control to cause it to fire. If you wanted it to fire on each keystroke then you want the input event.
var a = 0;
$(document).on("change", "#txtInput",function(){
var inputLength = this.value.length;
if (inputLength <= 8) { return; }
$("#contenido").append("<tr><td>" + this.value + "</td></tr>");
$("p").html('Total Bianuales: ' + (++a));
});
<input id="txtInput" />
<table id="contenido"></table>
<p></p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
var a = 0;
$(document).ready(function() {
$(document).on("change", "#txtInput" ,function(){
var textinput = parseInt($(this).val());
if (textinput > 8) {
$("#contenido").append("<tr><td>"+$(this).val()+"</td></tr>");
a += 1;
var str = 'Total Bianuales: '
$('p').html(str + a);
}}
);
});

Real time agenda with javascript and json

I have a div on a website that needs to be a real time agenda for a list of events. I have the events loaded through a json file and am using javascript to populate the div with the data from the file.
The problem is that the data displays stacked item by item in one column within a div, I need to now split the div into three separate columns/divs. One for events happening now, next, and coming soon. Ex) one event is at 7am, next is 7:30, and coming soon is 8am.
But I am not able to select each item and move it using css since the code just populates by one item upon page load and I cannot see an index for each item (video side content item) over and over to display what events are necessary.
This would be a lot easier if I could just format the items being populated into three separate columns through css, but I can't figure out how to do this.
By the way this site is written using FlexBox, hence why there is a "row" in one of the divs.
Also can someone please point in the right direction as to how to get this to be real time? Or any other helpful solution that could achieve this?
Thanks in advance for any help.
picture of what I'm trying to do
Function to populate the data
function agendaRealTimeUpdate() {
if ($('.real-time-agenda').length !== 0) {
videoSideContentType = 'agenda';
$.getJSON("ac.json", function(data) {
var sessions = data.session;
var contentString = '';
var currentSessionIndex;
var currentSession;
var currentTime;
var currentDay;
var title;
var time;
var room;
var description;
var d;
var i = 0;
d = new Date();
//gets the current time and returns it in a string with 3-4 digits EX: "1000" = 10 am
currentTime = parseInt(d.getHours().toString() + ((d.getMinutes() < 10 ? '0' : '') + d.getMinutes()).toString());
currentDay = d.getDate();
// this loop runs as long as we haven't figured out which session matches the time
while (currentSessionIndex === undefined && i < sessions.length) {
//this takes the current time and compares it to the sessions start and end times
if ((currentTime >= sessions[i].startTime && currentTime <= sessions[i].endTime) &&
currentDay === sessions[i].day &&
sessions[i].track === "none")
{
currentSessionIndex = i;
}
i++;
}
if (currentSessionIndex === undefined) {
currentSessionIndex = 0;
}
// This function finds the sessions that come after the identified current session
function findNextSessions() {
var sessionsCopy = sessions.slice(); //make a copy of the sessions array so we aren't altering it when we remove indexes
for (var z = 0; z < 2; z++) {
var index = currentSessionIndex + z;
// breaks out of the loop if the next session is undefined
if (sessionsCopy[index] === undefined) {
z = 2;
}
currentSession = sessionsCopy[index];
// loops through the sessions and if the session has a track it is removed from the sessionsCopy array
while (currentSession.track !== "none") {
console.log('has a track: ' + currentSession.track);
sessionsCopy.splice(index, 1);
currentSession = sessionsCopy[index];
}
time = currentSession.timeString !== undefined ? "<div class='video-side-content__time'><b>Time:</b> " + currentSession.timeString + "</div>" : '';
room = currentSession.room !== undefined ? "<div class='video-side-content__room'><b>Room:</b> " + currentSession.room + "</div>" : '';
title = currentSession.title !== undefined ? "<div class='video-side-content__secondary-title'>" + currentSession.title + "</div>" : '';
description = currentSession.content !== undefined ? "<div class='video-side-content__content'>" + currentSession.content + "</div>" : '';
contentString += "<div class='video-side-content__item'>" + time + room + title + description + "</div>";
}
}
findNextSessions();
$('.real-time-agenda').html(contentString);
});
}
}
Div I'm working with
<div class="row__item">
<h2 class="video-side-content__title"><img src="img/agenda-icon.png"> Thursday. Sept. 22</h2>
<div class="row__flex-container">
<div class="row__item video-side-content__strip video-side-content__strip-left"> </div>
<div class="row__item video-side-content__row">
<div class="video-side-content">
<div class="video-side-content__items">
<div class="video-side-content__item">
<h2 class="count-down__sub-header"><br>
SHOWING NOW</h2><br>
<div class="real-time-agenda">
<!--data populates upon page load from the json file
It lays the data out as: Time, Title, Room, Description-->
</div>
<br>
</div>
</div>
</div>
</div>
</div>
</div>

Javascript name generator

I'm trying to make JS output a full name based on input from both radio buttons and text. I'm having an issue as I want it to output the person's choice, yet whenever I click male or female, it's always displaying as Male (plus the text). The code I've got at the moment for the output is:
window.onload = initAll;
function initAll() {
document.getElementById("sillySubmit").onclick = function() {
document.getElementById("msgField").innerHTML = getSillyName();
return false;
}
}
function getSillyName() {
var x = $('input[id]').click(function() {
($('input[id]:checked').val());
});
var lastName1 = ["lots of names"];
var lastName2 = ["lots more names" ];
var lastNm = document.getElementById("lName").value.toUpperCase();
var validName = true;
if (lastNm == "") {
validName = false;
}
else {
var lastNum1 = lastNm.charCodeAt(0) - 65;
var lastNum2 = lastNm.charCodeAt((lastNm.length-1)) - 65;
if (lastNum1 < 0 || lastNum1 > 25 || lastNum2 < 0 || lastNum2 > 25) {
validName = false;
}
}
if (!validName) {
document.getElementById("lName").focus();
document.getElementById("lName").select();
return "I say. Something gone a bit squiffy here. Try again...";
}
return "Your name is " + x + ' ' + lastName1[lastNum1] + ' '
+ lastName2[lastNum2]; //add in comparisons later on
}
where 'x' is representing male and 'y' is female. What can I add/change so that it displays the correct preference?
I can't see your html, so I have to guess a lot. I assume you have a lot of input elements, all with an id-attribute, and your female/male buttons are two of them. Those buttons have a value being either 'on' or 'off', and in some browsers its always 'on'. So don't look for value. Further radio buttons have a property checked being either true or false.
You don't need to attach a click-handler to get the state.
So the first lines of your getSillyName() could look like:
function getSillyName() {
var x;
if (document.getElementById(/* female id here */).checked) {
x = 'Mrs.' // change string to whatever you want to see
} else if (document.getElementById(/* male id here */).checked) {
x = 'Mr.' // as above
} else x = 'cat'; // possibly both are unchecked
/* the very important next line in your code should be: */
console.log(x);
/* this important line move step by step further down your code */
console.log(/* and put in here what you want to check */);
/* rest of code here */
}
After that your element #msgField should show something.
What can I add/change so that it displays the correct preference?
If I understood correctly, the answer will be: parentheses.
The code will become:
"Your name is " + (x || y) + ' ' + lastName1[lastNum1] + ' ' + lastName2[lastNum2];
Try defining the first name before returning the value.
if(gender == 'male')
firstname = ...
else
firstName = ...
return 'Your name is ' + firstname + ' ' + lastName1[lastNum1] + ' ' + lastName2[lastNum2];
I hope that helps.

How to update counter and replace text with button click

I have a form where people can add more input with a button.
The javascript function clones the "origin-item", but I can't seem to get the function to correctly update the value in #id_nested-TOTAL_FORMS and __prefix__ does not get replaced with a counter-number, It just copies and adds __prefix__ instead of 1 or 2 and so on.
Anyone know what is wrong with the function?
The script was found here: https://github.com/rotaris/dynamicformdjango/blob/master/todo/templates/edit_items_in_list.html
<input id="id_nested-TOTAL_FORMS" name="nested-TOTAL_FORMS" type="hidden" value="1">
<input id="id_nested-INITIAL_FORMS" name="nested-INITIAL_FORMS" type="hidden" value="0">
<input id="id_nested-MAX_NUM_FORMS" name="nested-MAX_NUM_FORMS" type="hidden" value="1000">
<div id="origin-item" class="hide item">
<div id="div_id_nested-__prefix__-name">
<label for="id_nested-__prefix__-name">Name</label>
<div class="controls col-lg-10">
<input id="id_nested-__prefix__-name" name="nested-__prefix__-name" type="text" />
</div>
</div>
<p><a id="add-new-item" href="#">Add new person</a></p>
<script type="text/javascript">
var prefix = 'nested';
var MAX_FORMS = '1000';
var MIN_FORMS = 1;
/*
* Perform any enhancements you'd like to a given item here
* e.g. add datepicker widgets, popovers etc.
*/
function enhanceItem(item) {
$('.btn.delete', item).popover({
offset: 10
});
}
function enhanceItems() {
$('.item').each(function() {
enhanceItem(this);
});
}
/*
* The opposite of enhanceItem()
*/
function diminishItem(item) {
$('.btn.delete', item).unbind();
}
function diminishItems() {
$('.item').each(function() {
diminishItem(this);
});
}
$(document).ready(function() {
enhanceItems();
});
function getFormCount() {
return parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
}
function updateFormCount(newValue) {
$('#id_' + prefix + '-TOTAL_FORMS').val(newValue);
}
/*
* ====================================================
* General Notes: 'Form' and 'Item' are used somewhat interchangeably.
* ====================================================
*/
$.fn.clearForm = function() {
return this.each(function() {
var type = this.type, tag = this.tagName.toLowerCase();
if (tag == 'form')
return $(':input',this).clearForm();
if (type == 'text' || type == 'password' || tag == 'textarea')
this.value = '';
else if (type == 'checkbox' || type == 'radio')
this.checked = false;
else if (tag == 'select')
this.selectedIndex = -1;
});
};
/*
* Given an element, this function replaces a given string/regex
* with another specified replacement (new_value) within the element
*/
function updateElement(el, target_regex, replacement) {
var id_regex = target_regex;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
// Update the value of the hidden ID
// This hidden ID represents the ID of the model instance
var hidden_ID_patt = new RegExp('id_(' + prefix + '-\\d+)-id');
// Only update if an ID exists (i.e. if a corresponding model instance exists)
if (hidden_ID_patt.test(el.id)) {
$(el).val(new_value + 1);
}
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
}
/*
* Given an element, this function replaces (the first?) occurence of a number
* that follows a specific prefix (e.g. 'exampleprefix-n')
* with another specified number (new_value) within the element
* where n is a number
*/
function updateElementIndex(el, prefix, new_value) {
var id_regex = new RegExp('(' + prefix + '-\\d+-)');
var replacement = prefix + '-' + new_value + '-';
updateElement(this, id_regex, replacement);
}
function reapplyEnhancements() {
// Apply some fresh enhancements
diminishItems();
enhanceItems();
}
/*
* btn = the button (or link or some source object) that instigated this action
* prefix = the prefix used in the formset (?)
*/
function addItem(btn, prefix) {
var formCount = getFormCount();
// You might like/want to do some validation before proceeding in this function
// i.e. before adding an item
// In this case, I'm limiting it to max number of forms
if (formCount < MAX_FORMS) {
// Clone a item (without event handlers) from the first item
//var item = $(".item:first").clone(false).get(0);
// Clone the origin item
var item = $("#origin-item").clone(false).get(0);
$(item).removeAttr("id");
$(item).removeClass("hide");
// Clear its values
$(':input', item).clearForm();
// Insert it after the last item
$(item).removeAttr('id').hide().insertAfter("form .item:last").slideDown(300);
$(':input, label', item).each(function() {
// Relabel/rename all the relevant bits
// '__prefix__' comes from #origin-item
// see 'empty_form': https://docs.djangoproject.com/en/1.4/topics/forms/formsets/#empty-form
var target_regex = new RegExp('(' + prefix + '-__prefix__-)');
var replacement = prefix + '-' + formCount + '-';
updateElement(this, target_regex, replacement);
// Remove error classes
$(this).removeClass("error");
});
reapplyEnhancements();
// Update the total form count (in the management form)
updateFormCount(formCount + 1);
}
else {
// Feel free to notify the user using some other technique instead of an JS alert
alert("Sorry, you can only have a maximum of " + MAX_FORMS + " goals.");
}
}
/*
* Relabel all items
*/
function relabelItems() {
var forms = $('.item'); // Get all the forms
// Update the total number of forms (likely 1 less than before)
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
var i = 0;
// Go through the forms and set their indices, names and IDs
for (formCount = forms.length; i < formCount; i++) {
$(":input, label", forms.get(i)).each(function() {
updateElementIndex(this, prefix, i);
});
}
}
/*
* Removes an item from a list of items
*/
function removeItem(btn, prefix) {
var formCount = getFormCount();
// Do some validation before proceeding
// In this case, just make sure there is at least one item
if (formCount > MIN_FORMS) {
var item = $(btn).parents('.item');
// Delete the item
$("*", item).fadeOut();
$(item).animate({'backgroundColor':'#fb6c6c'}, 300).slideUp('slow', function() {
$(item).remove();
relabelItems();
});
// Apply enhancements
enhanceItems();
}
else {
// Feel free to notify the user using some other technique instead of an JS alert
alert("Come on now, you need to have at least a minimum of " + MIN_FORMS + " item(s).");
}
}
// JavaScript to create a new items/entries
$(document).ready(function() {
$('#add-new-item').click(function(e) {
addItem(this, prefix);
return false;
});
$('a.delete').live("click", function(e) {
removeItem(this, prefix);
return false;
});
});
</script>
The bugs in this code seem to be caused by the variable named prefix being used for 2 different tasks.
Here it is used like this
$('#id_' + prefix + '-TOTAL_FORMS').val(newValue);
where I assume prefix is assigned '_prefix_', but later it is used like this
var target_regex = new RegExp('(' + prefix + '-__prefix__-)');
where I assume prefix is assigned 'id'
If you where to sort out the variable prefix to only contain one type of piece of information, by have 2 variables with different names.
I'm pretty sure everything would fall into place.

Categories

Resources