Backbone router not triggering view change - javascript

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);
};

Related

Hiding an element by ID when in local storage once clicked not working

I am using this script to refresh the page after a button has been clicked and to hide that button once the ID (the shortcode) has been stored locally and to show a second element underneath:
function myVote(shortcode) {
$(document).on('submit', 'form', function() {
// Set product in local storage
localStorage.setItem(shortcode, "true");
// Refresh page after 3000 milliseconds
setTimeout(function() { location.reload(true); }, 3000);
});
};
</script>
<script>
for(let i=0; i<localStorage.length; i++) {
document.getElementById(localStorage.key(i)).style.display = "none";
}
</script>
The button has the following value: onClick="myVote('shortcode');"
The "shortcode" values are dynamic, each element has it's own.
I have built an upvoting system and the above means that once a vote has been placed they won't be able to vote for it again (unless they use another device or clear storage), the vote button is hidden and the "voted" button is now displayed. But for some reason, it's not working as it should and I'm struggling to figure it out. Check it out at https://www.carryr.com/vote.
At first, I thought I didn't configure the z-index for both of the element but they seem fine to me; the element underneath has a z-index of 11 and the one on top 100.
The SetTimeout may be the reason as after clicking on the said button, tahe page gets reload and localstorage value become null. Hence, the page throws document.getElementById(...) is null exception.
Please remove Timeout and retry the same.
You are storing many things in local storage which are not element ids.
So there will be an error when you try to access style property on an element that does not exist. Hence this code doesn't work.
Suggestion:
1.
const elm = document.getElementById(localstorage.key(i));
if (elm) {
elm.style.display="none"
}
2.It's better if you store the voted ids in a separate object and store it in storage. and read that object on load and style based on that.
// setting to storage
let voted = localStorage.getItem('voted');
if (!voted) {
voted = {};
} else {
voted = JSON.parse(voted);
}
voted.shortCode = true;
localStorage.setItem('voted',JSON.stringify(voted));
// reading from storage
voted = localStorage.getItem('voted');
if (!voted) {
voted = {}
} else {
voted = JSON.parse(voted);
}
Object.keys(voted).forEach(key => document.getElementById(key).style.display = 'none';
The above methods only store data in localstorage. so if user clears cache, opens a incognito window, opens in another system he won't be able to see the votes. better persist the state in a DB if possible

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.

Ask confirmation BEFORE Redirect from page

I have application.
Client side is - knockout.
so I have page with form, and i want to ask user confirametion before he will try go to another page (in case if he changed something (this pard is ready))
So routing on all website - its Sammy.js.
I tried :
Sammy JS - before
function ViewModel()
{
Sammy.before(/.*/, function () {
if (window.confirm('Really go to another page?')){
}
else{
//DO NOTHING AND STAY IN THE SAME PAGE
//OR SOMETHING ELSE THAT YOU WANT
return false;
}
});
}
its work , but its still working for all website - and its bad.
I'm not found way to disable it, so maybe I can do it without sammy???
Thank you guys!
Update: This website is SPA
You can use before, but you must specify options to show the confirmation dialog only when necessary. See the docs. You're specifying to run it for all the routes, you must specify in which routes you want it executed. Before option only gives you information on the next route. If it depends on the current route, you can get it examining the current url:
function extractSammyUrlFrom (context)
{
// get the hash fragment (curernt route)
"#" + context.path.split("#")[1] ;
}
If it depends on any other thing in your page, just check it before showing the dialog.

Can't figure out History JS

I am trying to implement a navigation to my ajax controlled site, and I am encountering some strange errors.
I am using History JS, the HTML5 only version.
This is how I initialize it:
function initializeHistory() {
var History = window.History;
if ( !History.enabled ) {
console.log("Not enabled!");
return false;
}
// Changing the main page state to -1.
History.replaceState({id:-1}, baseTitle, baseUrl);
// Bind to StateChange Event
History.Adapter.bind(window,'statechange',function(){
var State = History.getState();
console.log(History.savedStates);
if (historyData.manualStateChange)
{
if (State.data.id=='-1') alert('History start');
historyData.allowHistoryPushPop=false;
var gotoState=historyData.data[State.data.id];
var currentState=historyData.currentNavigation;
/* Some code here to revert to the previous state */
historyData.allowHistoryPushPop=true;
}
History.log(State.data, State.title, State.url);
});
};
I am using a global object, named historyData, in which I store the following things:
var historyData={
data: new Array(), //an array which contains objects that refer to the data that needs to be shown, when back or forward button is pushed
manualStateChange: true, //using this to figure out if the back or forward button was pressed in the browser, or if I am adding or removing a state from History programmatically
allowHistoryPushPop: true, //using this to prevent history from being changed in certain situations
currentNavigation: {
page: 'dashboard',
pageid: null,
module: null,
moduleid: null
}, // stores the current object from the historyData.data array
status: true // boolean that enables or disables History on my page
}
Whenever I click on a link in my page, a function, called navigation fires, which changes the History's state, and eventually runs a chain of functions to display the page which was asked for by the user. The relevant parts from the navigation function are as follows:
function navigation(gotofunc, navData) {
if ((historyData.allowHistoryPushPop) && (historyData.status))
{
if (navData['urlData']==null) // if this is null, then the title and the url for the asked page, will be returned by the server, after an ajax call, so we set it to the current url and current title
{
var curState=History.getState();
urlData={
title: curState.title,
url: curState.url
};
} else {
urlData=navData['urlData'];
if (!urlData['title']) urlData['title']=curState.title;
if (!urlData['url']) urlData['url']=curState.url;
}
navData['parameters']=new Array();
if (arguments.length>2) for (i=2;i<arguments.length ;i++) navData['parameters'].push(arguments[i]);
historyData.manualStateChange=false; // we are making a programmatic change, so we don't want the binded 'statechange' to fire
historyData.data.push(navData); // store the history data in our own array
History.pushState({id:historyData.data.length-1}, urlData['title'], urlData['url']); // push the History state, and set it's id to point to our newly added array element
historyData.manualStateChange=true; // re-enable the manual state change
}
}
If I don't know up front what the URL will be after I fetch the data via ajax, my Ajax call, replaces the current state with the correct data, this way:
if ((dataArray['navDat']) && (historyData.status))
{
historyData.manualStateChange=false;
var State = History.getState();
History.replaceState(State.data, dataArray['navDat']['title'], dataArray['navDat']['url']);
historyData.manualStateChange=true;
}
This works fine for the most part. If I navigate forward a few pages, and then go backwards, everything works greatly, if I then go forward once again(all by using the browsers back and forward button), it works greatly. There is only one exception: if I load the page, load a subpage, then try to click on the back button, this line never fires:
if (State.data.id=='-1') alert('History start');
It basicaly won't figure out that I arrived back at the front page, when I only navigate one page forward.
The other strange thing is the following(perhaps this is what's causing my original problem also): I tried fetching the savedStates of the History object, to see what is going on, and strangely, when I use the replaceState event, it adds a new state in savedStates. One of the objects that is added, is with the correct data id, the other one is with the previous data id.
What could be causing the problem, is there an error in my script somewhere? Or is this completly normal? (the part of adding multiple objects to History.savedStates after replaceState)
Thanks for the help in advance!
If I replace the url, or the title of the page with the first replaceState, the program realizes when I return to the front page, I dunno why, but till then this is the best solution I can figure.

How to track user steps

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"?

Categories