Allowing href links with hashes on them to bypass SammyJS routing - javascript

I have the following server-side URL mappings defined:
/main/item1
/main/item2
I've added SammyJS routing support so that I am able to do the following:
/main/item1#/ /* main view */
/main/item1#/topups /* topup view */
I've set up SammyJS like so:
s.app = Sammy(function() {
this.get('#/topups', function() {
console.log('Initializing topups view.');
});
this.get('#/', function() {
console.log('Initializing main view.');
});
});
The problem is, I have a summary section in my page that redirects to the topup view of a different "item". E.g., I am at the url /main/item1#/, and in this page, there exists a tag item 2's topups.
I expect to be redirected (page refresh) to the new URL, however, it seems like SammyJS is intercepting the /main/item2#/topups call and simply running the this.get('#/topups') route I've defined.
I expect that since the URL paths before the hash, /main/item1 and /main/item2 are different, the SammyJS routing won't be triggered.
Is there a way to prevent this behavior from happening in SammyJS?

I don't know much about Sammy but I can tell you from the way any router behaves, is that it catches the first match in the routing possibilities, and so, anything that ends with #/topups will be considered the same as long as it's after the hash sign.
so you better define the router this way:
this.get('#/topups/:item', function() {
console.log('Initializing topups view for item: '+ item);
})
and then call the pages with URLs like:
item 2's topups
I hope this is what you're looking for

I'm pretty sure using the full URL redirect you.
Item 2 top ups for the lazy coder
However, that will cause the page to reload. If you modify Labib's answer you can have an even better solution:
this.get('#/topups/:item', function () {
console.log('Doing some post processing on current item');
console.log('Now redirecting you to ' + this.params.item);
window.location.href = 'http://example.com/menu/# + this.params.item +#/topups';
});
Again this will cause the page to reload, but if you do not mind that, then just either method.
NOTE: Sammy will also check form submission for you. This trips me up EVERY time I use it. This post has the solution.

Related

Ember.js re-render links on locale change

My application uses the ember-i18n addon. For SEO purposes, I wanted to put a /[lang-code]/ in the URL.
When changing the language, history.pushState can change the URL and its alright, but the links on the page do not change for the new language, even though router.rootURL is changed.
So far I can just change the window.location.pathname, but thus a new network request is being made.
I tried overriding the href-to helper (from ember-href-to addon) and added this:
_recomputeOnLocaleChange: Ember.observer('i18n.locale', function() {
console.log("here?");
this.recompute();
})
It executes on locale change, but apparently recompute does nothing. Even with setTimeout (because perhaps rootURL didn't change yet). Links are all the same.
Can you suggest anything? Would be greatly appreciated! Thank you!!
In the didTransition hook of your router.js file, check if the locale has changed and set a property on a service to the new locale if it has. Then in your helper, you can check that property.
In router.js:
oldI18nLocale: null,
didTransition() {
this._super(...arguments);
// ...
let i18nLocale = this.get('locale.i18n');
if (i18nLocale !== this.get('oldI18nLocale')) {
this.set('someService.i18nlocale', this.get('locale.i18n');
}
this.set('oldI18nLocale', i18nLocale));
}
In your helper:
recomputeOnLocaleChange: Ember.observer('someService.i18nLocale', function() {
this.recompute();
})
Hope this works.

Redirect from within Angular controller if condition is met at start of page

I have an Angular app used to track hours worked by the user. When the user adds a new job, they are taken through a wizard, with each page/controller adding a new property to the job object. Once the wizard is complete, the user can start tracking by navigating to the jobs home page from the app main page.
It is, however, possible to exit the wizard before it is completed (via the back button) and then navigate to the home page of the job. What I need is for the controller for that home page to redirect to the appropriate wizard page for whichever job property is missing.
The job variable is retrieved from local storage at the start of the controller code.
var job = DatastoreService.objectJob();
job.initFromHash($routeParams.jobHash);
function checkJobProps(prop, route){
if(!job.data.hasOwnProperty(prop))
$location.path('/wizard/add-position/' + $routeParams.jobHash + '/' + route);
}
checkJobProps('taxSettings', 'tax');
checkJobProps('payrollSettings','payroll-opt');
checkJobProps('breaks', 'breaks');
checkJobProps('allowances', 'allowances');
checkJobProps('deductions', 'deductions');
checkJobProps('generalSettings', 'general-settings');
There is code below this on the controller that breaks if certain properties are not available. None of these function calls execute fast enough to prevent errors. They will redirect, but not elegantly and it will also always be the last one in the list that takes effect.
Do I do this with a promise? The navigation that is used from the home page of the app to the home page of each job is a directive so, I guess it may be possible to init the job from the $routeParams.jobhash and check these properties within the directive, but I would have to learn more about directives first.
Any help would be much appreciated.
$location.path() is asynchronous and will not prevent the code that follows it from executing. You will have to manually stop the execution with a return statement.
Note that the return statement must belong to the controller function block. You cannot put it inside another function since that will only stop the execution of that specific function.
Something along these lines should work:
var job = DatastoreService.objectJob();
job.initFromHash($routeParams.jobHash);
var redirectPath;
function checkJobProps(prop, route) {
if (redirectPath || job.data.hasOwnProperty(prop)) return;
redirectPath = '/wizard/add-position/' + $routeParams.jobHash + '/' + route;
}
checkJobProps('taxSettings', 'tax');
checkJobProps('payrollSettings', 'payroll-opt');
checkJobProps('breaks', 'breaks');
checkJobProps('allowances', 'allowances');
checkJobProps('deductions', 'deductions');
checkJobProps('generalSettings', 'general-settings');
if (redirectPath) return $location.path(redirectPath);
... rest of the code ...

What is the best practice for maintain a menu after a page reload? I have this custom code which feels wrong

I have a custom Menu which loads a new MVC View for each click as I want.
I load the new View by setting window.location.href. To make it work I have to set the baseURL (the name of the website) each time. To Store the state of the menu I use URL's querystring.
My concerns is in the use of:
'/WebConsole53/' // hardcode baseurl i have to apply each time manually
Setting window.location.href to load the new View from JavaScript // Is this the best way or should I use some URL/Html helpers instead?
I store the state of the selected menuItem in the querystring ("menu") // Is it more common to store that kind in Session/Cookie?
Any thoughts, corrections and suggestions would be much appriciated - thanks.
_Layout.cshtml
var controller = $self.data('webconsole-controller');
var action = $self.data('webconsole-action');
var menu = "?menu=" + $self.attr('id');
var relUrl = controller + "/" + action + menu;
var url = urlHelper.getUrl(relUrl);
window.location.href = url;
UrlHelper.js
var urlHelper = function () {
var getBaseUrl = '/WebConsole53/',
buildUrl = function(relUrl) {
return getBaseUrl + relUrl;
};
var getUrl = function(relUrl) { // relUrl format: 'controller/action'
return buildUrl(relUrl);
};
return {
getUrl: getUrl
};
}();
I Use MVC 5.
You can save this problem using Route. Through the route you know exactly where you are located in you application.
_Layout.cshtml is definetely not the place to have this javascript. Maybe you are missing some MVC concepts, I would recommend you to read a bit more about routes and routelinks
I hope this helps you a bit: RouteLinks in MVC
'/WebConsole53/' // hardcode baseurl I have to apply each time manually
sometimes you need to access your root from javascript where you don't have access to server-side code (eg #Html). While refactoring may be the best option you can get around this by storing the baseurl once, using server-side code, eg in _layout.cshtml:
<head>
<script type="text/javascript">
var basePath = '#Url.Content("~")'; // includes trailing /
</script>
... load other scripts after the above ...
</head>
you can then reference this everywhere and it will always be valid even if you move the base / migrate to test/live.
Setting window.location.href to load the new View from JavaScript // Is this the best way or should I use some URL/Html helpers instead?
Depends on your requirements - you could use $.ajax (or shortcuts $.get or $.load) to load PartialViews into specific areas on your page. There's plenty of examples on SO for this and the jquery api help.
Or just use <a> anchors or #Html.ActionLink as already suggested. Without needing menu= (see next) you don't need to control all your links.
I store the state of the selected menuItem in the querystring ("menu") // Is it more common to store that kind in Session/Cookie?
If you change the page, then you could query the current url to find which menu item points to it and highlight that one (ie set the menu dynamically rather than store it).
This would also cover the case where you user enters the url directly without the menu= part ... or where your forget to add this... not that that would happen :)
Additional: You can specify which layout to use in your view by specifying the Layout at the top of the view, eg:
#{
Layout = "~/Views/Shared/AltLayout.cshtml";
}
(which is also one of the options when you right click Views and Add View in visual studio)
Without this, MVC uses a configuration-by-convention and looks at Views/_ViewStart.cshtml which specifies the default _layout.cshtml.
If you don't want a layout at all, then just return PartialView(); instead

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.

storing a variable in localStorage is too slow

I have two jQuery mobile pages (#list and #show). There are several items on the #list page with different IDs. If I click on item no.5, the ID no5 will be stored in localStorage and I will be redirected to page #show
Now the problem:
Storing the ID in localStorage works, but the next page shows me not the item no.5, but it shows me an old item, that was in the localStorage before.
script from page #list
localStorage.setItem("garageID", $(this).attr('id'));
window.location.replace("#show");
I encountered this problem too (and not on a mobile : on Chromium/linux).
As there doesn't seem to be a callback based API, I "fixed" it with a timeout which "prevents" the page to be closed before the setItem action is done :
localStorage.setItem(name, value);
setTimeout(function(){
// change location
}, 50);
A timeout of 0 might be enough but as I didn't find any specification (it's probably in the realm of bugs) and the problem isn't consistently reproduced I didn't take any chance. If you want you might test in a loop :
function setLocalStorageAndLeave(name, value, newLocation){
value = value.toString(); // to prevent infinite loops
localStorage.setItem(name, value);
(function one(){
if (localStorage.getItem(name) === value) {
window.location = newLocation;
} else {
setTimeout(one, 30);
}
})();
}
But I don't see how the fact that localStorage.getItem returns the right value would guarantee it's really written in a permanent way as there's no specification of the interruptable behavior, I don't know if the following part of the spec can be legitimately interpreted as meaning the browser is allowed to forget about dumping on disk when it leaves the page :
This specification does not require that the above methods wait until
the data has been physically written to disk. Only consistency in what
different scripts accessing the same underlying list of key/value
pairs see is required.
In your precise case, a solution might be to simply scroll to the element with that given name to avoid changing page.
Note on the presumed bug :
I didn't find nor fill any bug report as I find it hard to reproduce. In the cases I observed on Chromium/linux it happened with the delete operation.
Disclaimer: This solution isn't official and only tested for demo, not for production.
You can pass data between pages using $.mobile.changePage("target", { data: "anything" });. However, it only works when target is a URL (aka single page model).
Nevertheless, you still can pass data between pages - even if you're using Multi-page model - but you need to retrieve it manually.
When page is changed, it goes through several stages, one of them is pagebeforechange. That event carries two objects event and data. The latter object holds all details related to the page you're moving from and the page you're going to.
Since $.mobile.changePage() would ignore passed parameters on Multi-page model, you need to push your own property into data.options object through $.mobile.changePage("#", { options }) and then retrieve it when pagebeforechange is triggered. This way you won't need localstorage nor will you need callbacks or setTimeout.
Step one:
Pass data upon changing page. Use a unique property in order not to conflict with jQM ones. I have used stuff.
/* jQM <= v1.3.2 */
$.mobile.changePage("#page", { stuff: "id-123" });
/* jQM >= v1.4.0 */
$.mobile.pageContainer.pagecontainer("change", "#page", { stuff: "id-123" });
Step two:
Retrieve data when pagebeforechange is triggered on the page you're moving to, in your case #show.
$(document).on("pagebeforechange", function (event, data) {
/* check if page to be shown is #show */
if (data.toPage[0].id == "show") {
/* retrieve .stuff from data.options object */
var stuff = data.options.stuff;
/* returns id-123 */
console.log(stuff);
}
});
Demo

Categories