Open lega911 opened 8 years ago
https://jsfiddle.net/fend25/e1LLu72c/ better and preferabe variant now,
guys, feel free to comment)
Haha, thought yesterday how great it would be having some views bound to routes. :D
A good basic router implementation would be this: http://krasimirtsonev.com/blog/article/A-modern-JavaScript-router-in-100-lines-history-api-pushState-hash-url
My approach would use regexes as parameter of a directive:
<div al-router-view="/main/?">
<a href="/user/1">User 1</a>
</div>
<div al-router-view="/user/:id/?">
<a href="/main">Back to overview</a>
</div>
This would allow to define the urls directly in the template, no additional code would be needed for basic features. But there should definitivly be an API to do redirects, handle url errors etc.
Oleg's reference implementation (ancient one) http://plnkr.co/edit/QYb53NUnnhIezI5n0JxB?p=preview
Ok, i just build an absolute simple, quick n dirty views implementation: https://jsfiddle.net/r9t3ropc/
Some explanation: the al-router-view directive could be extended to watch the expression. So one could define the url structure in javascript or the url of a view could be changed:
<div al-router-view="myVar.main">Main</div>
<div al-router-view="myVar.user">User</div>
scope.myVar = {
main: '/main',
user: '/user'
}
Comments are welcome.
I think it should be separated into two different API's:
The first should have unified interface to change current location and update/rewrite state. Also it should be presented in root scope and should broadcast events. Events could be stopped to prevent page reloading when there is unsaved changes or so.
View should allow to use functions as route value. This functions return async operations to load template and resource specified by route. This is needed to render state dependent routes. By default view listening scope's $routeChanged
event. But changes source could be configurable to use exact router instance.
<al-app>
<al-view al-view.routes = "routes"/>
</al-app>
alight.bootstrap('al-app', {
routes : {
// Simple template route
'/': 'index.html',
// State dependent route
'/home': function(route, scope){
if (scope.user) {
return {
scope: {
user: user
},
template: '/users/home.html'
}
} else {
return '/sign-in.html'
}
},
// Async route
'/users/:id' : route => {
var user = users.get(route.id);
var promise;
if (! user) {
promise = loadUser(route.id).then(user => {
if (! user) {
// User not found, show 404 error page
return '/errors/404.html';
} else {
// User found, use user page template
return {
scope: {
user
},
template: '/users/item.html'
}
}
});
} else {
promise = Promise.resolve(user);
}
return promise;
}
}
});
Also view could has loading
view with spinner to indicate intermediate state while resolving promise.
@rumkin Can I use common header and footer for "/users/*" in your approach. In this example by @fend25 I can do this: https://jsfiddle.net/fend25/e1LLu72c/
Events could be stopped to prevent page reloading when there is unsaved changes or so.
It's @onLoseFocus="showPreventWindow()"
in the example.
@onSwitchTo="onSwtichToUser()"
could be something like this:
scope.onSwtichToUser = route => {
var user = users.get(route.id).then
var promise;
if (! user) {
promise = loadUser(route.id).then(user => {
if (! user) {
// User not found, show 404 error page
return '/errors/404.html';
} else {
// User found, use user page template
return {
scope: {
user
},
template: '/users/item.html'
}
}
});
} else {
promise = Promise.resolve(user);
}
return promise;
}
What do you think? "Configuring" in html can be easier.
@lega911 Yep, it could be done with several view elements. Like this:
<al-view al-view-routes="routes.head">
<al-view al-view-routes="routes.body">
<al-view al-view-routes="routes.footer">
It's not so elegant as HTML configuration but it simply to manage from code. I think configuring in HTML is the most proper way to do this for MVVM app. But it should be also configurable and controllable from code.
@
is not valid HTML attribute name character. Is this some kind of future DSL?
I think it's better to configure router with html and implement fully controllable router and view interfaces with focus on usability.
@ is not valid HTML attribute name character. Is this some kind of future DSL?
Yes, but we can make it like this (with the event directive):
al-on:on-lose-focus="showPreventWindow()"
or alias
@on-lose-focus="showPreventWindow()"
and "on-lose-focus" is an event (new CustomEvent), which you can catch even with jQuery. https://github.com/lega911/angular-light/issues/123
Maybe it's better to avoid on
duplication and bind events to target component to avoid mess of events in the future:
al-router:on-reload-requested
al-input:on-lost-focus
Maybe, but custom events can be more flexible.
May it possible to implement url handling like express.js? Another point is, that using Views which use "templates" would not be useful, because in this case i could redirect the user to a page which shows the content of this "template". Views should be used inline, to easily switch between views by a changing url. I also think it is not useful to bind a view to a scope variable.
My approach uses a global routes object, where regexes are defined. Using al-route or by adding routes to the object, new routes can be defined. Also a custom route can be defined in this object, which will be referenced by a given element selector.
<div id="home"></div>
<div id="players"></div>
<div al-route="/users/:user"></div>
alight.routes = {
'/home': (scope) ->
return '#home'
'/players/:id': (scope, id) ->
scope.id = id
return '#players'
}
alight.d.al.route = (scope, exp, element, env) ->
alight.routes[exp] = ->
return element
What do you think?
My approach uses a global routes object,
I agree
div id="home"></div> <div id="players"></div> <div al-route="/users/:user"></div>```
should it work like al-if with dependency on url?
What about this one?
<div>
<div al-route:group @switch-to="onSwtichToAuth()">
<div al-route="/login" al-ctrl="App"/>
<div al-route="/register" al-ctrl="App"/>
</div>
<div al-route:group @switch-to="onSwtichToApp()" @lose-focus="showPreventWindow()">
<Header/>
<div al-route="/posts" al-ctrl="Posts"/>
<div al-route="/feed" al-ctrl="Feed"/>
<div al-route="/user">
<div al-ctrl="userList"></div>
<div al-route="/user/:userId" al-ctrl="userItem" @lose-focus="checkIfUserSaved()">
<div al-if="!user">Loading...</div>
<div al-if="user" al-include="/templates/userItem.html"></div>
</div>
</div>
<div al-route="*">
No page 404
</div>
<Footer/>
</div>
</div>
when url = "/user/:userId", then controller al-ctrl="userItem"
is activated, there you can load a user,
a template can be loaded by al-include when a user is loaded <div al-if="user" al-include="/templates/userItem.html"></div>
,
and you can see this before user is loaded <div al-if="!user">Loading...</div>
so, visible html for /user/:userid would be
<div>
<div al-route:group @switch-to="onSwtichToApp()" @lose-focus="showPreventWindow()">
<Header/>
<div al-route="/user">
<div al-ctrl="userList"></div>
<div al-route="/user/:userId" al-ctrl="userItem" @lose-focus="checkIfUserSaved()">
<div al-if="!user">Loading...</div>
<div al-if="user" al-include="/templates/userItem.html"></div>
</div>
</div>
<Footer/>
</div>
</div>
visible html for /user would be
<div>
<div al-route:group @switch-to="onSwtichToApp()" @lose-focus="showPreventWindow()">
<Header/>
<div al-route="/user">
<div al-ctrl="userList"></div>
</div>
<Footer/>
</div>
</div>
visible html for /login would be
<div>
<div al-route:group @switch-to="onSwtichToAuth()">
<div al-route="/login" al-ctrl="App"/>
</div>
</div>
I agree. It looks exciting. And it should to allow to load subroutes with al-include for complex components.
@rumkin al-include is deprecated, look at http://angular-light.readthedocs.org/en/latest/directive/html.html , it's more flexible :)
@lega911 I will. But you're using al-include in your example.
Let's focus on modular structure, easy of use and flexibility. And thus it should to use al-html to load subroutes.
should it work like al-if with dependency on url?
Yes, you have a pretty good understanding of what i mean. :-)
al-route:group
I do not understand how this works. I understand that it groups some requests. But how is defined that a specific request should be used? Which request is the first if you are visiting url "/"?
What i also thought about are subroutes:
<div al-route="/user">
<div al-route="/:id"></div>
<div al-route="/details"></div>
</div>
Which would be matched when accessing: /user /user/:id /user/details
Maybe it would be great to also handle redirects in the global route object:
alight.routes = {
'/home': '/'
'/players/:id': (scope, id) ->
scope.id = id
return '#players'
}
which would redirect "/home" to "/".
al-route:group I do not understand how this works.
It's easy, one rule - if there is a route that maches to url, then it should be visible, then all parent DOM should be visible, other route-elements should be hidden. I changed your example in this style:
<div al-route>
<header />
<div al-route="/profile"></div>
<div al-route="/user/:id/view"></div>
<div al-route="/user/details"></div>
<footer />
</div>
for url "/user/details" we will have:
<div al-route>
<header />
<div al-route="/user/details"></div>
<footer />
</div>
In your approach we have to copy-past header and footer for "/profile". On the other side, your approach works well for loaded html (al-html).
Hm. This is a bit confusing for me. The grouping attribute is there to render a specific header an a footer, right? The content for a route is inside of
<div al-route="/profile">
<h1>My user profile</h1>
</div>
So when accessing /profile i will see this:
<header />
<div al-route="/profile">
<h1>My user profile</h1>
</div>
<footer/>
Am i right?
So when accessing /profile i will see this: Am i right?
Yes. Actually for example above we don't need a "group" route:
<div>
<header />
<div al-route="/profile"></div>
<div al-route="/user/:id/view"></div>
<div al-route="/user/details"></div>
<footer />
</div>
In a simple, "group route" just holds all child routes, like al-route="/profile; /user/:id/view; /user/details"
, but it takes them automatically.
The grouping attribute is there to render a specific header an a footer, right?
Not exactly, it renders all DOM, except routes that don't match to URL
Not exactly, it renders all DOM, except routes that don't match to URL
I like the approach. :-)
Prototype: http://plnkr.co/edit/lZkXWXg5xk1J147nb1jX embeded: http://run.plnkr.co/plunks/lZkXWXg5xk1J147nb1jX/
with no regex yet.
There is an regex router with params and tail capturing: https://jsfiddle.net/rumkin/y6xLu1sm/
Here an regex router with params and tail capturing:
I took it for my example
You can get arguments using event al-on.route-to
<div al-route="/user/:name/view" @route-to="username=$event.value.name">
One more example: http://plnkr.co/edit/pQBycumCKE1DJb3oVaPE?p=preview
al-route="/user/details"
in the example) <div al-route="*">
404 - Not found
</div>
You can get arguments using event
al-on.route-to
Why not providing these variables automatically in a scope variable? E.g.
scope.$route.name
Parsing from URL shouldn't be so difficult:
"/user/:id/name/:name/view/:action".match(/:\w+/g)
Why not providing these variables automatically in a scope variable?
It can give you collisions for routes "/users/:name", "/users/root" they both will be visible on url="/users/root"
@dev-rke It could be done manually with hooks. And sometimes it could cause type mismatch.
I prefer to assign route params to scope.$route
.
It can give you collisions for routes "/users/:name", "/users/root" they both will be visible on url="/users/root"
No, it should not. When you provide for each route a own scope, e.g.
<div>
<header />
<div al-route="/profile/:profileID">
{{$route.profileID}}
</div>
<div al-route="/user/:id/view">
{{$route.id}}
</div>
<div al-route="/user/details/booking/:id">
{{$route.id}}
</div>
<footer />
</div>
Then i see no problems. :-)
No, it should not. When you provide for each route a own scope, e.g.
Ok, I don't see normal cases for collision, so $route should be ok.
But I don't want al-route makes a new scope, you can use al-ctrl if you want a new scope, also in this case you don't need @route-to
because your controller be called when route is active.
$route is ready http://plnkr.co/edit/pQBycumCKE1DJb3oVaPE?p=preview
http://plnkr.co/edit/pQBycumCKE1DJb3oVaPE?p=preview embedded: http://run.plnkr.co/plunks/pQBycumCKE1DJb3oVaPE/
what works: al-route - define route (can be empty) al-route:default - it's activated on undefined url (for 404) al-route-out="onOut()" - return true if you want prevent url be changed al-on.route-to - event when route is activated, you can use al-ctrl instead of it. scope.$route - contains arguments from url
what else?
what about "href" ?
<a al-link="/login">Login</a>
<a al-router-link="/login">Login</a>
<a al-router-href="/login">Login</a>
Also we have js API:
service.location = {
go(link) // move to link
subscribe(fn, [flag]) // subscribe on changes url, on 404 page, out-event
unsubscribe(fn, [flag])
getCurrentUrl()
isDefault() // returns true if it's 404
}
What about routes that should not be routed and need page reloading? If I wish to serve /blog
with another lib or have sub applications?
<ul al-router>
<li al-route="/blog/**" al-route.reload></li>
</ul>
need page reloading
you can use usual <a href="">
to reload the page.
If I wish to serve /blog with another lib or have sub applications?
al-route="/blog/**"
Your example works, but router should be only one. Your sub applications can use API: location.subscribe()
you can use usual
<a href="">
to reload the page.
ok
what about "href" ?
al-href?
al-href?
Why not, this name is not busy.
al-href
will collide with :href
, at least in mind.
AngularUI uses ui-sref attribute.
I vote for al-router-link
. Although al-router-href
is ok too.
vuejs and A2 seems to use name "link", v-link, router-link
Hm, your examples do not work anymore, maybe due to the last release?
It doesn't work in FF because of wrong transpiler, FF doesn't support sync xhr
I tried it in Chrome, it works fine. I like the defined behaviour.
What do you think about prefixing normal href attributes using "#" instead of introducing something like al-link? When a href attribute is prefixed with "#" the attribute gets handled by alight. If html5 push states are used, the prefix get removed before applying the url.
Do you mean?
<a href="#/user/profile">Profile</a>
Interesting, what about buttons and other?
<button href="#/user/profile">Profile</button>
<div href="#/user/profile">Profile</div>
<li href="#/user/profile">Profile</li>
Shebang is using to navigate across webview. This behaviour will be broken. I think al-href
is good enough. It short and clean. It refers to default href which it actually is.
What do you think about prefixing normal href attributes using "#" instead of introducing something like al-link?
it will bring some strange logic to the service.go
processor.
But the most important thing is that it will collide and brake specs about hashes in url. And what if I want to use hashes?
I think, special directive is the best way because it won't collide with specs nor with other things in mind. In appearance al-href
is very similar to :html
. And it hasn't any allusion to router. If nobody wants see al-router-href
, maybe al-rhref
?
New idea: make new namespace for router: r
, or rt
or something else?
Interesting, what about buttons and other?
<button href="#/user/profile">Profile</button>
Why? it's totally confusing. And all workaround (like twitter bootstrap) is prepared for <li><a></a></li>
structure.
@lega911
Interesting, what about buttons and other?
href is meant as compatibility mode. So for using buttons normally one would use a link element and style it like a button (compare twitter bootstrap). Also a button can be used with a form, so the form action also should be handled.
I also think it would not be necessary to prefix links using a hash. As we have a global routing object, we can check each click event of a link in the current scope, if the url matches to a defined route. E.g.
<a href="/user">Will show /user route element</a>
<a href="/profile">There is no matching route, so page /profile will load</a>
<div al-route="/user"></div>
There should be no problems and it is easy to implement. Also it would be good for SEO purposes, because one can use prerender.io and all links should be accessible.
@rumkin
Shebang is using to navigate across webview.
Can you please give an example? I did not understand what the problem is. Maybe its obsolete when we check a clicked link against our routes (see above).
@fend25
But the most important thing is that it will collide and brake specs about hashes in url. And what if I want to use hashes?
Why would it break? Using views requires to use hashes or html5 push states, because they are bound to the url. When someone uses a link outside of defined routes, the link would not be handled by alight. The great benefit would be, that someone can define "hard" links (e.g. for SEO purposes) which are handled by javascript, if the client supports javascript and fallback to normal links, if no javascript is used.
if the client supports javascript and fallback to normal links, if no javascript is used.
What if I need a normal link, but I use javascript.
<a href="/profile">There is no matching route, so page /profile will load</a>
You can do this with al-route:default
https://gist.github.com/fend25/3619d6d730039c30a34d