How to track user steps - javascript

We have a intermittent bug that it's been hard to track, the bug consists in randomly (so we think) when redeeming a code, the entire code value disappears upon making the order...
Doing our self the same process manually, it does not, never ever, happen!
So I thought recording the user actions, every action:
click links
submit inputs
page views
ended up making up this set of rules, and appending to the end of the master page:
$(function() {
// log this page view
log2Loggly('Page View', '');
// for each click
$("a").click(function() {
var ref = $(this).attr("onclick").length > 0 ? $(this).attr("onclick") : $(this).attr("href");
log2Loggly('Link clicked', ref);
});
// forms
$("form").submit(function() {
var dt = $(this).serialize();
log2Loggly('Form submited', dt);
});
});
and using Loggly I can send each action, as they get the hold of the date and IP, it's easier to match a user IP upon our system.
I keep seeing this services like CrazyEgg that record all user actions, but we can't match the user, it's anonymous data!
What I really liked was to search for IP and get the entire user tree as kind'a of an organigram of what the user did... maybe I'm able to pull this off with the data I have, but I would like to ask 2 things prior to adventure on this idea
Is there any kind of service for this outthere that you might come across before?
what more should I track to make it "almost perfect"?

Related

jquery ajax call getting old data when re-used

I have an ajax call that builds a small graph in a popup window. The html for the link is re-used in many different links for different devices on the page. What happens, is that when you click a graph for the first device, you get that device. You click a button for the second device, you get that device, however, if you keep clicking away, after the third click or so, you suddenly start getting only the first device, over and over. I think my variables are being cached in some odd way, and I don't understand:
the HTML:
<a class="bluebtn graphbutton ingraph" href="http://wasat/cgi-bin/rrdjson.cgi?res=3600&start=-24h&end=now-1h&uid=28.7B2562040000" data-uid="28.7B2562040000" data-name="Laundry Room Freezer"></a>
<a class="bluebtn graphbutton ingraph" href="http://wasat/cgi-bin/rrdjson.cgi?res=3600&start=-24h&end=now-1h&uid=28.F7A962040000" data-uid="28.F7A962040000" data-name="Garage Temp"></a>
The code in question:
$(document).ready(function() {
$('.graphbutton').click(function(e) {
var formURL = $(this).attr("href");
var uid = $(this).data("uid");
var name = $(this).data("name");
e.preventDefault();
$.ajax({
url: formURL,
dataType: "json",
cache: false,
context: this,
success: function(data){
console.log("calling mkgraph with uid "+uid+" name " +name);
make_graph(data.data, uid, name);
},
error: function(ts) {
console.log(ts.responseText); }
});
}); /* clickfunc */
}); /*docready */
What happens:
Click freezer:
"calling mkgraph with uid 28.7B2562040000 name Laundry Room Freezer"
Click Garage:
"calling mkgraph with uid 28.F7A962040000 name Garage Temp"
Click Garage again:
"calling mkgraph with uid 28.7B2562040000 name Laundry Room Freezer"
Some of these links are being manufactured by the make_graph() function. I'm a bit worried that this is the issue, and somehow the ajax thing needs to be re-initialized after doing this?
By request, the relevant code in make_graph() that I think is causing my issue here. Basically, I'm editing the buttons in the css popup on the fly, and I think this is creating a wierd situation where the ajax binding is bound to the old href, and not being updated, even though the link is correct in the produced html. This is consistent with the effect where the binding only gets mangled on the third attempt.
$(".ingraph").each(function() {
this.href = $(this).attr("href").replace(/uid=.*/g, 'uid=' + uid);
this.setAttribute('data-uid' ,uid);
if (devname.length > 0) {
this.setAttribute('data-name', devname);
}
});
EDIT: adding a long answer:
I have multiple buttons on the main page. Each one specifies a "uid" that gets fed to rrdjson.cgi, which takes the uid and finds the data for that device, and returns it as json. When make_graph() recieves this json data, it populates a css popup, with the graph, and edits 5 buttons so they reference that UID. Those 5 buttons change the timescale of the graph by re-requesting the data from rrdjson.cgi.
What I am worried is happening, is that I click on the frige, it changes the uid's of the buttons inside the popup to reference the frige. Then I close that, click on the garage, it also changes the uid's and correctly shows the garage data. Then I click on one of the buttons inside the popup for the garage, and poof, I get the refrigerator again. I suspect that ajax "remembers" the old values for $(this).attr("href") etc and passes those values to the code, rather than re-reading the contents of the HTML. (perhaps instead of HTML, I meant DOM there, I'm a little vauge on the difference, but I suspect I meant DOM)
Maybe the answer is to somehow un-register the ajax binding to those buttons and re-register it every time make_graph() changes them? How would I do the un-register? .off() ? .unbind() ?
After much gnashing of teeth, and google, I have answered my own question.
https://forum.jquery.com/topic/jquery-data-caching-of-data-attributes
Turns out, jquery caches "data" types, but not attr types. So when I do:
uid = $(this).data("uid");
vs
uid = $(this).attr("data-uid");
I get wildly different results. I guess the moral of the story is that .data is super evil.. :)
If you add a random value to your url like
var formURL = $(this).attr("href")+"?rv="+Math.random();
you'll force the ajax call to reload the URL. You can use the cache property (set it to false) JQuery will load the data again, but any proxy may send a cached version.
(Please check that there are no other attributes set in the url, otherwise set "&rv="+Math.random(); (& instead of ?) use
var formURL = $(this).attr("href");
formURL + (formURL.indexOf("?") > 0 ? "&rv=" : "?rv=" )+ Math.random();
Your problem should not have something to do with make_graph() as uid and name depend on $('.graphbutton')
(if not make_graph(), or some other function, changes the attributes of your buttons)

Ionic Angular - Resetting scope variables

I have a flow of few pages/views starting from first page to last page. This is pretty much based on the Ionic tutorial.
I update a factory "Info" with some data as I proceed with the flow. In the final page, after displaying the "summary info" to the user, I use $state.go('firstPage') to navigate back to the first page.
When I make a different selection in the first page this time, it doesn't seem to take effect in the view.
I tried the suggestion from here but that didn't help me. I tried resetting the variables again, but that doesn't help either.
angular.module('my.services', [])
.factory("Info", function(){
// All user data
var infoData = {
type: "",
level: 0
};
var originalInfoData = angular.copy(infoData);
// Reset data
infoData.resetUserDetails = function() {
userData = angular.copy(originalInfoData);
};
Final Page Controller
$scope.finish = function() {
UseInfo.resetUserDetails();
$state.go('firstPage');
}
This takes me back to first page but even though I select something different this time, the pages seem to remember what I did in my first run.
The question is - how do I clear things up so the user can do something else after getting back to the first page without remembering previous selections.

Dynamically loading a database based on user text input

I have an autocomplete widget which needs to return options from a database of objects.
On doing so, once the user selects an item the widget will populate other hidden textfields with values from the particular object they chose. - All of this works and has been used on previous projects
However this particular database is far too big (44k+ objects, filesize is several mb and has taken far too long to load in practice) so we've tried various ways of splitting it up. So far the best has been by first letter of the object label.
As a result I'm trying to create a function which tracks the users input into a textfield and returns the first letter. This is then used to AJAX a file of that name (e.g. a.js).
That said I've never had much luck trying to track user input at this level and normally find that it takes a couple keystrokes for everything to get working when I'm trying to get it done on the first keystroke. Does anyone have any advice on a better way of going about this objective? Or why the process doesn't work straight away?
Here is my current non-working code to track the user input - it's used on page load:
function startupp(){
console.log("starting");
$("#_Q0_Q0_Q0").on("keyup", function(){
console.log("further starting!");
if($("#_Q0_Q0_Q0").val().length == 1){
console.log("more starting");
countryChange(($("#_Q0_Q0_Q0").val()[0]).toUpperCase());
}
else{
console.log("over or under");
}
});
}
And an example of the data (dummy values):
tags=[
{
label:"label",
code:"1",
refnum:"555555",
la:"888",
DCSF:"4444",
type:"Not applicable",
status:"Open",
UR:"1",
gRegion:"North West"
},
....
];
edit: fixes applied:
Changed startupp from .change(function) to .on("keyup", function) - keydown could also be used, this is personal preference for me.
Changed the autocomplete settings to have minLength: 4, - as the data starts loading from the first letter this gives it the few extra split ms to load the data before offering options and also cuts down how much data needs to be shown (helps for a couple of specific instances).
Changed how the source is gathered by changing the autocomplete setting to the following:
source: function(request, response) {
var results = $.ui.autocomplete.filter(tags, request.term);
response(results.slice(0, 20));
},
where tags is the array with the data.
all seems to be working now.
You should bind to keydown event:
function startupp(){
console.log("starting");
$("#_Q0_Q0_Q0").keydown(function(){
console.log("further starting!");
if($(this).length() == 1){
console.log("more starting");
countryChange(($(this).val()[0]).toUpperCase());
}
else{
console.log("over or under");
}
});
}

ExtJs 4.2 ref-selector vs Ext.getcmp() vs Ext.ComponentQuery.query()

I was asked to develop a tab panel with 6 tabs, each having 30 to 40 elements. Each tab is acting as a form in accumulating the details of a person and the last tab is a Summary page which displays all the values entered in the first five tabs. I was asked to provide summary as a tab because, the user can navigate to summary tab at any instance and look at the details entered by him/ or glace the summary. i am following ExtJs MVC pattern. Payload is coming from / going to Spring MVC Application. (JSON)
Using tab change event in controller and if the newtab is summary I am rendering the page with show hide functionality.
Method 1 :In controller I have used Ext.getCmp('id of each element inside the tabs') and show hide the components in summary tab based on the value entered by the user. This killed my app in IE8 popping a message saying that the "script is slow and blah blah..." i had to click on NO for 5 to 6 times for the summary tab to render and display the page.
Method 2 :In controller I used ref and selectos to acccess all the items in tabs. I have used itemId for each and every field in summary tab. like this.getXyz().show(). I thought it would be fast. Yes it was in Google chrome. but my app in IE8 is slow compared to goolge chrome/firefox
Any suggestions regarding this and plan to decrease page render time. The summary page has more than 1000 fields. Please feel free to shed ur thoughts or throw some ideas on this.
thank you!!
I've got a few suggestions you can try. First, to answer your title, I think the fastest simple way to lookup components in javascript is to build a hash map. Something like this:
var map = {};
Ext.each(targetComponents, function(item) {
map[item.itemId] = item;
});
// Fastest way to retrieve a component
var myField = map[componentId];
For the rendering time, be sure that the layout/DOM is not updated each time you call hide or show on a child component. Use suspendLayouts to do that:
summaryTabCt.suspendLayouts();
// intensive hide-and-seek business
// just one layout calculation and subsequent DOM manipulation
summaryTabCt.resumeLayouts(true);
Finally, if despite your best efforts you can't cut on the processing time, do damage control. That is, avoid freezing the UI the whole time, and having the browser telling the user your app is dead.
You can use setTimeout to limit the time your script will be holding the execution thread at once. The interval will let the browser some time to process UI events, and prevent it from thinking your script is lost into an infinite loop.
Here's an example:
var itemsToProcess = [...],
// The smaller the chunks, the more the UI will be responsive,
// but the whole processing will take longer...
chunkSize = 50,
i = 0,
slice;
function next() {
slice = itemsToProcess.slice(i, i+chunkSize);
i += chunkSize;
if (slice.length) {
Ext.each(slice, function(item) {
// costly business with item
});
// defer processing to give time
setTimeout(next, 50);
} else {
// post-processing
}
}
// pre-processing (eg. disabling the form submit button)
next(); // start the loop
up().down().action....did the magic. I have replaced each and every usage of Ext.getCmp('id'). Booooha... it's fast and NO issues.
this.up('tabpanel').down('tabName #itemIdOfField').actions.
actions= hide(), show(), setValues().
Try to check deferredRender is true. This should only render the active tab.
You also can try a different hideMode. Especially hideMode:'offsets ' sounds promising
Quote from the sencha API:
hideMode: 'offsets' is often the solution to layout issues in IE specifically when hiding/showing things
As I wrote in the comment, go through this performance guide: http://docs.sencha.com/extjs/4.2.2/#!/guide/performance
In your case, this will be very interesting for you:
{
Ext.suspendLayouts();
// batch of updates
// show() / hide() elements
Ext.resumeLayouts(true);
}

Backbone router not triggering view change

I've been trying to solve a pretty irritating for the last couple of days now, and I'm finally admitting defeat, and appealing to SO.
First I'll give an overview of what I'm trying to do, then I'll give you specifics of where I'm running into issues.
The Goal:
A user is filling out a form, and if any element is changed on the form and the user tries to leave the form without saving the changes, a modal should display with the following message:
You have made changes to this record. Do you want to save the changes?
The user gets a Yes/No/Cancel option.
If the user selects:
Yes: The record should save, then navigate to the originally intended route.
No: The record should not save, and the user should be navigated to the intended route.
Cancel: The modal should close and the user will remain on the current page with no route change.
I am using the backbone.routefilter library to detect route changes before the route change happens.
The Problem:
To solve the problem, I put a route change listener within my initialize method in the view that contains the form. Below is that code:
// If the user tries go to a different section but the record has been changed, confirm the user action
app.circulationRouter.before = function( route, params ) {
app.fn.clearNotifications();
if(app.recordChanged){
var confirmView = new app.views.confirm({
header:"Patron Record Changed",
bodyText:"You have made changes to this record. Do you want to save the changes?",
trueLabel:"Yes",
falseLabel:"No",
cancelLabel:"Cancel",
hasTrue:true,
hasFalse:true,
hasCancel:true,
trueCallback:function(){
// Ignore the patron record change
_this.saveRecord();
// Render the patron section AFTER the save
_this.model.on({
'saved' : function(){
// Render the new seciton
app.circulationRouter.renderPatronSection(_this.model, params[1]);
app.currentPatronSection = params[1];
// Set the record changed to false
app.recordChanged = false;
// Remove the 'before' listener from the circulationRouter
app.circulationRouter.before = function(){};
if(con)console.log('in saved');
// Remove the listener
_this.model.off('saved');
},
'notsaved' : function(){
// Stay on the patron record page, keep the url at record
app.circulationRouter.navigate("patrons/"+_this.model.get('PatronID')+"/record",false);
_this.model.off('notsaved');
}
}, _this);
},
falseCallback:function(){
if(con)console.log(params);
if(params.length){
// Ignore the patron record change
app.circulationRouter.renderPatronSection(_this.model, params[1]);
app.currentPatronSection = params[1];
}else{
if(con)console.log('blah');
setTimeout(function(){
if(con)console.log('should navigate');
app.circulationRouter.navigate("", true);
}, 5000);
}
app.recordChanged = false;
app.circulationRouter.before = function(){};
},
cancelCallback:function(){
// Stay on the patron record page, keep the url at record
app.circulationRouter.navigate("patrons/"+_this.model.get('PatronID')+"/record",false);
}
});
app.el.append(confirmView.render().el);
return false;
}
};
Each of the three options has a callback that gets called within the initialize function that will control the outcome. The Cancel button always behaves correctly. The issue I'm running into is when the user wants to navigate to the home page, ie when this line is called: app.circulationRouter.navigate("", true);. Everything else works correctly if there is a new, defined route to navigate to.
Here is the sequence of events that creates the issue:
1) Modify a record
2) Try to navigate to the home page
3) Route is changed home page route, but record is still in view
4) Modal is automatically displayed with three options
5) Select the No button
6) falseCallback is triggered
7) Modal is closed and view remains on record page, but route displayed in browser is for home page
The expected behavior for #7 was to display the view for home page, but only the url reflects that.
You can see in the falseCallback I even tried delaying the trigger for the redirection to make sure it wasn't a DOM issue, but that didn't work.
Does anyone know what may be happening?
Here's the problem. Once the route is changed, if you re-navigate to the same route, even if you set the param {trigger:true}, the page won't reload. There is a long discussion about it here.
Based off of the discussion listed in github, I am using this solution and adding the refresh option. If the refresh option is set to true, then the view will reload, even if the url doesn't change. Otherwise, the functionality remains the same.
_.extend(Backbone.History.prototype, {
refresh: function() {
this.loadUrl(this.fragment);
}
});
var routeStripper = /^[#\/]/;
var origNavigate = Backbone.History.prototype.navigate;
Backbone.History.prototype.navigate = function (fragment, options) {
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag && options.refresh)
this.refresh();
else
origNavigate.call(this, fragment, options);
};

Categories