I am new to Javascript and backbone.js, so hopefully I am missing something simple here. I am experimenting with some sample code I found which is supposed to check for unsaved changes before allowing a user to navigate away to another page. I have created a JSfiddle here:
http://jsfiddle.net/U43T5/4/
The code subscribes to the hashchange event like this:
$(window).on("hashchange", router.hashChange);
And the router.hashChange function checks a "dirty" flag to determine whether or not to allow the navigation, like this:
hashChange: function (evt) {
if (this.cancelNavigate) { // cancel out if just reverting the URL
evt.stopImmediatePropagation();
this.cancelNavigate = false;
return;
}
if (this.view && this.view.dirty) {
var dialog = confirm("You have unsaved changes. To stay on the page, press cancel. To discard changes and leave the page, press OK");
if (dialog == true) return;
else {
evt.stopImmediatePropagation();
this.cancelNavigate = true;
window.location.href = evt.originalEvent.oldURL;
}
}
},
The problem is that the code is not working because this.view is undefined, so the 2nd if block is never entered.
I would like the sample program to always ask for confirmation before navigating away from the page (in my sample program, I have set this.view.dirty to always be true, which is why it should always ask for confirmation). Or if there is a better approach, I am open to alternatives.
The main issue is the this context in the methods , this corresponds to the Window Object and not the Router. So it always remains undefined as you are defining view on the router. Declare a initialize method which binds the context inside the methods to router.
initialize: function() {
_.bindAll(this, 'loadView', 'hashChange');
},
Check Fiddle
I spent a lot of time to make at least something decent.
I ended up writing a wrapper for Backbone function:
var ignore = false;
Backbone.history.checkUrl = function() {
if (ignore) {
ignore = false;
return;
}
app.dirtyModelHandler.confirm(this, function () {
Backbone.History.prototype.checkUrl.call(Backbone.history);
},
function() {
ignore = true;
window.history.forward();
});
};
app.dirtyModelHandler.confirm is a function which shows confirmation (Ok, Cancel) view and takes success and cancel functions as arguments.
Related
In my AngularJS/Ui-Router application I have a series of input box like this:
<input ng-model="vm.filter" ng-keyup="vm.onKeyUp($event)" type="text" class="form-control" id="filter">
Then I have a controller, in which I have put the onKeyUp function, that looks like this:
var vm = this;
vm.onKeyUp = function (e) {
var val = e.currentTarget.value;
$state.go('aState', { 'filter': val });
}
This will fire the state change (really just state parameter filter is changed) that will call a resolve behind the scene and at the end of the flow, my data set is filtered and the subset is showed. Amazing.
But there is a little problem: on the state changed the HTML input lost its focus, and I can't figure out how can set it on focus at the end of the flow.
This is necessary since the user start to write in the input box and then will be surprised when his focus get "misteriously" lost. Bad UI experience.
Ok for what can i read you just call the same state with different parameters on the url (:filter), what happens even if the state is the same is that the stateChange force a reload of the view and therefore the lost of the focused element.
And that is the behaviour unless you make a event.preventDefault() on $stateChangeStart but im pretty sure your data will not be updated.
So possible solutions are imho:
Rethink your flow so it doenst depend on state change. (No need to code here :P)
Find a way to save your active element and set it on reload, like.
//sessionStorage to prevent data stored after window is closed;
$scope.$on('$stateChangeStart', function(){
$window.sessionStorage.setItem('focusedId', '#' + document.activeElement.id);
});
$scope.$on('$viewContentLoaded', function(){
if((var id = $window.sessionStorage.getItem('focusedId'))) {
$DOM.focus(id);
//Use a service for DOM manipulation, you know manipulation in controllers is bad :(
}
});
`
the service could be like this:
.factory('$DOM', ['$window', '$timeout', function ($window, $timeout) {
return {
__constructor: function (selector, event) {
$timeout(function () {
var element = (typeof selector == 'object') ?
selector :
$window.document.querySelector(selector);
(!!element && !!element[event]) ? element[event]() : null;
});
},
focus: function (selector) {
this.__constructor(selector, 'focus');
},
blur: function (selector) {
this.__constructor(selector, 'blur');
}, //and so....
};
}])
comment if you find a better way, maybe you can refresh the data preventing the refresh of the view and my understanding of ui.router is wrong :)
Currently I have a text input attached to a model with a $scope.watch statement observing the model. This is all used to achieve a type of auto complete / typeahead feature.
<!-- HTML -->
<input type="text" ng-model="search.mySearchText">
// JS
var deregister = $scope.$watch('search.mySearchText', doSearch);
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
// do something with the data
});
}
This works fine. However, occasionally in my .then function I want to make changes to search.mySearchText. Obviously this would cause the watcher to be fired again, but I don't want this.
What I'm hoping to do is find a way to suppress the $watch from firing that next time. Maybe by somehow telling Angular that that particular watched model property is no longer dirty?
I tried removing the $watch by de/re- registering the watch at appropriate times, but that didn't work either.
function doSearch() {
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
deregister(); // shut off the watch
search.mySearchText = 'some new string'; // manipulate the model property that I don't want to cause a search
deregister = $scope.$watch('search.mySearchText', doSearch);
});
}
However, this didn't prevent the event firing like I expected, which is why I'm now looking for a way to suppress the event.
You could have a variable that determines whether doSearch exits early, like so:
var searchActive = true;
function doSearch() {
if (!searchActive) return;
mySearchService.executeSearch(search.mySearchText)
.then(function(res) {
searchActive = false;
// do manipulation of search.mySearchText
searchActive = true;
});
}
How can I return a parent function from a child function on click? Here's an example code:
function returnFunction() {
var output = false;
$('a').click(function() {
output = true;
});
return output;
}
var result = returnFunction();
The result will always be false since the click hasn't happened at the time the code is being run. How could I make this work though?
My intention is to call pop-up dialogs, and I'd like to have all the logic inside one function that can easily be loaded - including the click events of a Confirm dialog box.
Elsewhere in scripts I'd be calling it this way for example:
// Menu click triggers the dialog
$('a').click(function(e) {
// The function displays the dialog and returns the click events of the dialog
var result = returnFunction();
// If result was false, we'll prevent the menu access, for example
if (!result) {
e.preventDefault();
}
});
I'm aware of the jQuery UI dialog plugin. But I'd like to achieve this without it for now.
Thanks.
An over-simplification of it is:
Everything stops (including scrolling and clicking on hyperlinks) while executing javascript. This means you cannot "pause" the script until someone clicks on a link.
The typical way of solving this is a callback function:
function my_callback(some, arguments) {
// you can do whatever in here: an ajax load, set a global variable, change some of the page, ...
console.log(some, arguments);
alert(some + " " + arguments);
}
function returnFunction(callback, param) {
var output = false;
$('a').click(function() {
callback(param, "world");
});
}
returnFunction(my_callback, "hello");
demo at http://jsfiddle.net/UnBj5/
EDIT:
I did mention global variables because they are an option, but they are typically bad style. Try to use other means if possible.
If you want more help with it, provide more details of what you are trying to do.
Try using parameters instead. Send a parameter to a function that shows your alert boxes, show a different pop-up alert depending on the parameter, what you are trying to do won't work because its basically a chicken-egg problem.
I have the following solution for stopping the users from accidentally leaving my registration page:
var warning = true;
function CheckExit() {
if (warning) {
return "Changes done. Remember to save, blah blah.";
}
}
window.onbeforeunload = CheckExit;
addToPostBack = function (func) {
var old__doPostBack = __doPostBack;
if (typeof __doPostBack != 'function') {
__doPostBack = func;
} else {
__doPostBack = function (t, a) {
if (func(t, a)) old__doPostBack(t, a);
}
}
};
$(document).ready(function () {
$('#aspnetForm').submit(function () {
warning = false;
return true;
});
addToPostBack(function (t, a) {
warning = false;
return true;
});
});
Most of this code is from different questions here on StackOverflow, and works just fine on my local debugging page; all ASP.Net controls (linkbuttons, buttons) can be clicked without a warning, but as soon as the user tries to close the window or navigate away, the warning is displayed.
However, when deployed to my server, the warning pops up when I click ASP.Net controls too (and I really do not want to warn the user that his changes isn't saved when he clicks the "Save" button). Now, there is a difference between my local debugging page and the server: the server uses a different MasterPage with some ads in it, but after debugging with Firebug (see next paragraph), I can't see how that should make a difference.
I have tried running different parts of the Javascript code by using Firebug to confirm that everything is loaded correctly (my first guess was that the section containing $('#aspnetForm') wasn't being loaded, since this seems to be the difference), but there is no improvement at all. The warning still pops up on every click of a link or a button.
My main problem is, I think, that I do not fully understand what the addToPostBack function does, and as such, I cannot properly debug it.
Is there anybody here who has any idea what might be wrong?
It's a guess, but you don't have the conventional anti-conflict device which goes
(function ($) { code here })(jQuery);
so something might be conflicting with jQuery's definition of '$' which would stop the execution of the exception your code makes when someone is posting back.
I agree with James. Most of the time such type of conflict occurs. I will suggest with noConflict().
Happy Coding.
Is there a way to make it so Sammy.JS does not automatically call runRoute when you call app.run()?
My code currently initializes Sammy on the first load, but does not want it to actually call any sammy routes until the user actually clicks a link.
You could try passing in a non-operational route to the run method. It might ignore any route in the hash in that case.
Otherwise, you could set a listener on the document root to listen for clicks in the document and run the application then. But this solution seems "less clean."
(assuming jQuery)
$(function () {
var app = Sammy();
$("a").live(function () {
if (!app.isRunning()) {
app.run();
}
});
});
Question is old but I used the following solution that I find a little cleaner.
The page is loaded normally and Sammy doesn't call the current route when using .run().
http://sammyjs.org/docs/api/0.7.4/Sammy.Application.methods.before
var appIsRunning = false;
var app = Sammy(function() {
this.before('.*', function() {
if (this.path == document.location.pathname && appIsRunning == false) {
return false;
}
});
// The routes...
...
}
// start the application
app.run();
if (app.isRunning()) { appIsRunning = true; }