i'm looking around for javascript routing libraries and i come to Sammy, so i'm learning it.
All examples that i've seen so far show hot to proceed routing based from a domain as a base url, like www.mydomain.com/# and then all routes goes on
but i'm doing some trials within a nested dir within my localhost dir, say /wwwroot/play/sammy/ so my base url would be
http://localhost/~rockdeveloper/play/sammy/#
and then all routes must go on, like:
http://localhost/~rockdeveloper/play/sammy/#/products
http://localhost/~rockdeveloper/play/sammy/#/clients
http://localhost/~rockdeveloper/play/sammy/#/search
is there any way to set this base url so i can proceed to config sammy routes like this ?
get('#/products')
get('#/clients')
get('#/search')
by now i have to concatenate the main string to the route, and i wish it would be more smart than this...
baseurl='/~rockdeveloper/play/sammy/#/search';
get(baseurl + '#/products');
thanks.
Sammy is a frontend framework, you don't need to provide a baseurl, since all calls are made at clientside on the base of url, the rest of the path after the pound sign are there just for sammy use.
Ex:
http://localhost/~rockdeveloper/play/sammy/#/products
http://localhost/~rockdeveloper/play/sammy/#/products/iphone
http://localhost/~rockdeveloper/play/sammy/#/products/iphone/cases/all
Your base path IS
http://localhost/~rockdeveloper/play/sammy/
In all of them. The rest are just sammy routes, (think of them as GET params)
Also your routes must be defined in a way that can be called seamlessly from your anchors. Here is a portion of my routes file:
app = $.sammy('body', function() {
//define events
this.bind('addNew',function(e,data){
//data.name = data.name.split(' ').join('_');
for(x in mapa[data.element]){
if(mapa[data.element][x].name.toLowerCase() == data.name.toLowerCase()){
return alert('There is already an element with the same name');
break;
}
}
ap('Adding: '+data.element+' with the name: '+data.name);
mapa[data.element].push({name:data.name});
this.trigger('sections',{action:data.element});
});
// define routes
this.get('#/', function() {
$('#menuright').html('');
$('.customMenu').remove();
$('#holder').html('').attr({style:''});
});
this.get('#/someroute/:variable', function() {
/*
...
...
...
*/
});
this.before(function(){
if(typeof(app.historial)=='undefined'){
app.historial = [];
}
app.historial.unshift(this.path.split('#')[1]);
if(app.historial.length>2) app.historial.length = 2;
do_something();
});
});
And from your views you can just simply use something like:
<i class="icon-bookmark"></i> {{name}}
//le me here using mustache :{
Related
How can I escape client side routing when wanting to navigate outside the set list of routes setup in the router?
I created a project using Durandal.js and have created SPA's inside different Areas. The problem I ran into is that when I want to navigate outside the current SPA and into another or say back to the home page of the entire application which is not an SPA at all but simply a cshtml page.
What I have tried to do is use Durandal's mapUnknownRoutes handler to intercept and then use window.location.href to navigate out. This works, but when I want to go the home page of the application ("/"), the router matches the "root" of the SPA and doesn't navigate out to the home page but instead the SPA's root route.
The area route in this example is "/spaHome" whose MVC route looks like:
context.MapRoute(
"spaHome_default",
"spaHome/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here's what I've done to set up the Durandal router:
var routes = [
{ route: ['spaHome', ''], moduleId: 'spaHome', title: "spaHome", hash: "#spaHome"},
{ route: 'one/anotherPage', moduleId: 'one/anotherPage', title: "one/anotherPage", hash: "#one/anotherPage"}
];
router.makeRelative({ moduleId: 'viewmodels' });
router.map(routes)
.mapUnknownRoutes(function (instruction) {
utils.navigateToPath(instruction.fragment);
return false;
})
.activate({ pushState: true, root: "/spaHome" });
Any pointers or leads into the right direction for this would be much appreciated!
After some trial and error I was able to come up with a solution to what I was trying accomplish. It looks like a solid solution, so hopefully it doesn't cause any issues down the road.
I changed the routes array and removed the default route of '' from the first route. This allowed me to have a unknown route to go off of when wanting to hit the normal MVC homepage. I also had to remove the "root" property from the activate function of the router. Which in turn meant I had to explicitly declare the routes in the route array with the extra area portion or the URL ("spaHome/").
Then in my mapUnknownRoutes handler I checked the route for the area portion of the URL, and if that existed, I used the SPA router to show a notfound page for that SPA. Else I assumed that the route exists outside the area and I need to hard navigate to the URL using window.location.href.
var routes = [
{ route: ['spaHome'], moduleId: 'spaHome', title: "spaHome", hash: "#spaHome"},
{ route: 'spaHome/one/anotherPage', moduleId: 'one/anotherPage', title: "one/anotherPage", hash: "#spaHome/one/anotherPage"}
];
router.makeRelative({ moduleId: 'viewmodels' });
router.map(routes)
.mapUnknownRoutes(function (instruction) {
if (instruction.fragment.toLowerCase().indexOf("spaHome/", 0) === -1) {
utils.navigateToPath(instruction.fragment);
} else {
var notfoundRoute = "notfound";
instruction.config.moduleId = notfoundRoute;
history.navigate(notfoundRoute, { trigger: false, replace: true });
}
})
.activate({ pushState: true });
If anyone has a better solution please let me know!
EDIT
Ok, I ran into an issue with the history while doing this. I had to add a line to the Durandal router.js file to stop the previous route from being added to the history queue.
if (!instruction.cancelQueue) {
router.trigger('router:route:before-config', instruction.config, router);
router.trigger('router:route:after-config', instruction.config, router);
queueInstruction(instruction);
}
Edit 2
I also ran into an issue with this method where the navigation doesn't work quite right for IE9 and below.
I have a grails app backing an angularjs front-end. They are deployed as a single WAR. I've removed the context path from the app so that it runs on http://localhost:8080.
I have a list of articles and I have the $routeProvider setup to redirect / to /articles at which point the controller takes over and pulls the list via $http. Pretty standard stuff.
Initially, I was using the default location provider config in that hashes (#) are used in the URL. I've changed it via
$locationProvider.html5Mode(true);
and everything still works. However, if I change the URL directly in the address bar and hit enter, or if I just refresh the browser when it is at /articles, the server side takes over and I just get my list of articles as json. No angular. I understand why this happens and for now what I've done is detected a non-ajax request on the server and am issuing a redirect to / which will allow angular to kick into gear.
I'm wondering if this is the right thing. Or is there something else I can do that is a better practice.
Redirecting is the right solution.
I was able to make it work using url mapping. So far it works :-)
I started with something like this:
"/**" (controller: 'app', action: 'index')
with app/index being the angular app page. But this will also match everything else (e.g. /$controller/$action). I had to explicitly map each $controller/$action to the correct controller. Not so good... ;-)
To solve this problem I'm prefixing all uris with /client for angular routes and /server for grails uris. This makes url mapping easy and it helps to distinguish angular routes from template uris etc.
My final url mapping looks like this:
class UrlMappings {
static excludes = [
"/lib/**",
"/css/**",
"/js/**"
]
static mappings = {
// - all client uris (routes) will start with '/client/',
// - all server uris (load) will start with '/server/'
// redirect /$appName/ to /$appName/client
"/" (controller: 'redirect', action: 'redirectTo') {
to = '/client/'
permanent = true
}
// redirect any angular route
"/client/**" (controller: 'app', action: 'index')
// standard controller/action mapping
"/server/$controller/$action/$id?" {
constraints {
}
}
}
}
You can't redirect directly in the url mapping, so I use a simple controller:
class RedirectController {
def redirectTo () {
redirect (uri: params.to, permanent: params.permanent)
}
}
The routing entries look like this:
$routeProvider.when ('/client/login', {templateUrl: './server/security/login'});
just answered on this question here
angularjs html5mode refresh page get 404
be sure that you rewrite rules on server work correctly
I'm planning to build a SPA with asp.net MVC4 but I'm not quite sure how I have to Setup my Project because of the Routing. Most SPA's work with hashrouting like this mypage/#/News/today for instance.
What would happen if the browses directly to mypage/News/today if I haven't specified a Controller named News with an action today?
The App should handle both types of Routing, how can I achieve this?
Do I have to build my App in a classic way, like Adding several Controllers with appropriate Actions and views and also build a clientside MVC structure with knockout, jquery etc?
You'll have to let all routes to "pages" fall through to let your SPA handle them (including essentially fake 404s if it's not to a real page in your SPA), but at the same time, need to make sure that you get the correct responses for API calls and/or file requests.
Below is the setup I have (I am using Vue as the js framework but that doesn't matter much for this, and not at all for the server-side piece).
First, add this to your Startup.cs, in addition to your default route setup:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.Use(async (context, next) =>
{
await next();
var path = context.Request.Path.Value;
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page
if (context.Response.StatusCode == (int)HttpStatusCode.NotFound && !Path.HasExtension(path) && !path.StartsWith("/api"))
{
context.Request.Path = "/Home/SpaRedirect"; // attempts to redirect to the URL within the SPA
context.Response.StatusCode = (int)HttpStatusCode.OK; // Make sure we update the status code, otherwise it returns 404
await next();
}
});
...
}
So the newly added SpaRedirect to HomeController looks like this, and just stores the requested URL in ViewData...
public IActionResult SpaRedirect()
{
ViewData["RequestUrl"] = HttpContext.Request.Path;
return View("Index");
}
Then in Index.cshtml, just capture that requested url in session storage so we have it available on the client-side:
<script src="~/dist/main.js" asp-append-version="true">
sessionStorage.setItem("redirectAttempt", #ViewData["RequestUrl"]);
</script>
Then in your boot script file (the entry-point for your SPA), add something like:
let redirectAttemptUrl = sessionStorage.getItem("redirectAttempt");
if (redirectAttemptUrl) {
router.push(redirectAttemptUrl);
sessionStorage.removeItem("redirectAttempt");
}
Which just checks for the presence of a requested url, and then the SPA's router attempts to navigate to it (in the example above it is a vue-router), then removes it from storage.
So this way, if a user attempts to navigate directly to a URL by entering it in the url bar (or via a bookmark) the app will load and take them to the right place, IF it exists... which takes us to the last piece...
Finally, you have to handle "404s" within your SPA, which is done by adding a catch-all route to your routes defs that takes user to a 404 component page you set up, which for Vue would look like this:
// adding an explicit 404 path as well for programmatically handling when something is not found within the app, i.e. return this.$router.push('/404')
{ path: '/404', component: NotFound, name: '404', alias: '*' }, // remove alias to not show the actual url that resulted in our little 404 here
{ path: '*', redirect: '/404' }, // this is the catch-all path to take us to our 404 page
Caveat: I'm no expert so could be missing something, would love additional comments on how to improve this. One thing that this doesn't handle is if the user is ALREADY in the SPA and for some reason edits the URL directly to navigate to someplace else, it would still trigger a server call and full reload, which ideally wouldn't be the case, but this is a pretty trivial issue I'd say.
I have my Rails and Backbone.js application deployed to a suburi. What is the best way to prepend all request with the suburi?
Example: application is deployed to www.example.com/app. I have a resource users and I'd like backbone to call www.example.com/app/users instead of the default www.example.com/users.
I'm setting a ROOT_URI variable on the server side and I'm going to use it in the backbone app. The simple way is to add it to all urls in models and collections, but it's tedious and error prone. What should I do? Override Backbone.sync?
you can pass URL on your Fetch call
Example
model.fetch({
url: yourServiceURL,
success: function (response, xhr)
{
//console.log("Successfully Fetched...");
},
error: function()
{
//console.log("Error Occurred...");
}
});
in this way you don't have to define url in your model and collections
EDIT
what i understand from your comment. you can do something like this
yourModel = Backbone.Model.extend({
url:function() {
return yourGlobalVarForRootURI+"/staticLogicalPathForEachModel";
},
parse: function (response) {
return response;
}
});
in this way you can give yourGlobalVarForRootURI variable in all of your models/collections and you can change this global variable so it will be changed in all models/collection. I hope it will solve your problem
Ok. I have a url setup to log a user out. On the server, there is no html. The session on the server simply gets destroyed, and then the user is redirected to an address.
This works fine with plain html, but with Angular i am having issues. I've been routing all main routes using $routeProvider.when('/foo', {templateUrl: '/foo.html', controller: 'Ctrl'}) and that works fine for normal templated routes.. however, if there is no template it will not work.
So, how do i support the route /logout in the same fashion as above, when there is no html template?
A workaround is to use template instead of templateUrl. From the Angular docs:
template – {string=} – html template as a string that should be used
by ngView or ngInclude directives. this property takes precedence over
templateUrl.
This can be used as follows:
$routeProvider.when("/foo", {template: " ", controller: "Ctrl"});
Note: You must use " " instead of an empty string "" because Angular uses an if (template) check before firing the controller, and an empty string evaluates to false.
-- EDIT --
A better way to do it is to use the resolve map. See the Angular Docs:
resolve - {Object.=} - An optional map of
dependencies which should be injected into the controller.
This can be used like this:
$routeProvider.when('/foo', {resolve: {redirect: 'RedirectService'}});
Note: I've changed it from "Ctrl" to "RedirectService", because what you're describing in the question isn't really a "controller" in the Angular sense. It doesn't set up scope for a view. Instead, it's more like a service, which ends up redirecting.
I am writing the solution based on the already accepted answer and the github issue mentioned in it's comments.
The approach I am using is a resolve parameter in the $routeProvider. In my case I was trying to create a nice solution to logout in my application, when user goes to /logout.
Example code of $routeProvider:
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
...
when('/logout', {
resolve: {
logout: ['logoutService', function (logoutService) {
logoutService();
}]
},
}).
...
}]);
In the resolve part you specify a service (factory) by name and later on you have to call it. Still it is the nicest solution around.
To make the example complete I present my logoutService:
angular.module('xxx').factory('logoutService', function ($location, Auth) {
return function () {
Auth.setUser(undefined);
$location.path('/');
}
});
Works great!