alexgalays / abyssa-js Goto Github PK
View Code? Open in Web Editor NEWFramework agnostic hierarchical router for single page applications
License: MIT License
Framework agnostic hierarchical router for single page applications
License: MIT License
Line 185 in 9a0f50f
initState
specified by a state name should be able to have params, too (e.g. some id).
Please, see https://github.com/sompylasar/abyssa-js/blob/real-project-usage/src/Router.js#L156
Hi,
When I was reading your Readme, this note raise on me a little question.
Note: With the emergence of VDOM approaches, using abyssa as a stateful router has less of an impact, as VDOM diffing/patching will usually take care of good enough performances. Also, a component based view library can handle hierarchical data loading and caching.
Is this saying that using Abyssa with Vdom solution is pretty useless ?
I am considering to use Abyssa in a Snabbdom + MostJS project as Router with state capabilities.
But more in the way to freeze the current state of the route and bring back later when the route would be active once again.
Thank you for your answer.
Cheers,
Hi there,
I know that Abyssa can automatically detect the hash part of the url even if there are multiple hash in it, but still, would it be possible to customize the value of the hashbang which is the fake one?
Why do I need that? Because stupid Disqus only works correctly out of the box with a true hashbang like #!
. It is a pain in the ass, but that's it or doing DOM manipulation to fully kill Disqus in a SPA and then reload it.
Thanks !
// Customize with a hashbang '#!'
Router.configure({
hashPrefix: '!'
});
The issue is fixed by this commit: sompylasar@f772f85
A test is added to check the behavior.
Example from the test:
var router = Router({
blog: State('blog', {
// ...
articles: State('articles/:id', {
// ...
})
})
}).init('/blog/articles/33');
// ...
// Only the `id` param changes:
router.state('/blog/articles/44');
Is there any plan to support relative navigation?
It would be super useful when a parent need to reroute between several children but you don't want to rely on the full name, just the fact that it's child A or child B. The other obvious use case is to go to the parent.
I'm pretty sure we can already do something close using Router.currentState()
and names, but it would be cool to have it natively in Abyssa.
Inspiration : https://github.com/angular-ui/ui-router/wiki/Quick-Reference#examples-diagram
// Currently, we can do:
// (assuming you have Lodash, otherwise, write your own merge)
Router.siblingState = function siblingState(childName, params) {
var currentState = Router.currentState();
var names = currentState.fullName.split('.');
names.pop();
names.push(childName);
Router.state(names.join('.'), _.merge(params || {}, currentState.params));
};
// Go to 'articles.all' -> /articles
Router.state('articles.all');
// Go to 'articles.detail', {id: 1} -> /articles/1
Router.siblingState('detail', {id: 1});
// Would be could to support the syntax:
Router.state('^.detail', {id: 1});
It would be nice to have a way to test where I am currently inside all my states. Let's say I have the following hierarchy:
root
|- child1
\_ child2
If I'm on child2
, I would like a way to test if I'm exactly there, like
Router.is("root.child2")
But all also a way to test if I'm inside of my parent
Router.inside("root")
If #7 is implemented, those methods could also be present in the returned state.
Check this jsBin:
http://jsbin.com/zomimotiyu/1/
var State = Abyssa.State,
Router = Abyssa.Router,
router;
router = Router({
index: State('/', function() { console.log( "enter index" ); }),
books: State('/books', {
enter: function() {
console.log( "enter books index" );
},
exit: function(){
console.log( "exit books index" );
},
detail: State(':id', {
enter: function(params) {
console.log( "enter books detail", params );
},
exit: function(params){
console.log( "exit books detail", params );
}
})
})
})
.configure({
enableLogs: true,
urlSync: true
})
.init('index');
Going on "/books" it doesn't match books state.
I tried to use an internal state booksIndex url at "" and it works!
I have to make books State parent of bookDetail and it must enter when url is "/books", do some stuff and when I click for a book detail the url must be "/books/2" and it must not exit the books State.
How can I do this?
Sometime, you just cannot publish your website to the root url (thing about GitHub pages with no CNAME). It would be cool to handle that by having a way to configure the base url to prefix all url. Even better, it would be awesome if Abyssa actually retrieve it from the <base>
HTML tag. Thanks!
Similar to Crossroads, I would like to match multiple parameters in the route definition. For instance, new State('app/:params') would match*
app/param1 AND
app/param1/param2 AND
app/param1/param2/param3
Is this functionality supported in abyssa?
Crossroads Reference
http://millermedeiros.github.io/crossroads.js/#crossroads-add_route
It also allows "rest" segments (ending with ) which can match multiple segments. Rest segments can be optional and/or required and don't need to be the last segment of the pattern. The pattern "{foo}/:bar:" will match news "news/123", "news/123/bar", "news/123/lorem/ipsum".
It's a personal opinion, but I don't really like mixing classic stuff with magic stuff. In the Router initialization, it looks like if a key is named "notFound", it will catch all unknow urls to redirect to the corresponding state. Meaning all keys match only one url except this magic one.
I would rather create a new method on the Router. Moving from:
Router({
home: State(),
contact: State(),
notFound: State()
})
to:
Router({
home: State(),
contact: State()
}).otherwise( State() )
As we can read here: https://github.com/AlexGalays/abyssa-js#statewithparams
StateWithParams is the merge between a State object (created and added to the router) and params (both path and query params, extracted from the URL when transitions occur)
So for me, the result should be an object with all the property of a State, augmented with the params, an extended State. But currently debugging, this is not the case at all...
As we can see, the Router.currentState
is an object with the params, some other stuff, and a state
key pointing to the real state. Is that what we really want? Or are the docs correct and the implementation wrong?
If the implementation is right, this is not really DRY... There are several duplication between the currentState
returned object and its state
attribute, like data
, name
and fullName
. Would be better to have them only at one place.
Also, since I'm talking about the StateWithParams API, it would be cool to also have the following (not sure if it should be methods or attributes):
// currently: /#/articles/1/comments/2?search=test&page=2#toto
var currentState = Router.currentState();
// Return only the path, without the query string
// /articles/1/comments/2
currentState.path();
// Return only the query string
// search=test&page=2
currentState.queryString();
// Return only the params of the query string
// {search: 'test', page: 2}
currentState.queryParams();
// Return only the hash
// 'toto'
currentState.hash();
// Return only the params of the path
// {idArticle: 1, idComment: 2}
currentState.pathParams();
Hey,
So there is a way to specify dynamic params in the url and in the query string, like:
/contact/:who/:how?subject&from
Next, if I understand correctly, inside enterPrereqs
and enter
methods, you will have a params
object looking like:
{
who: ...
how: ...
subject: ...
from: ...
}
So far, so good. But what if my url is:
/contact/you/mail?subject=Hey&from=me&unknow=trololo
Is there a way to access the unkown
key of the query string? Even worse, what if my url is:
/contact/you/mail?subject=Hey&from=me&who=trololo&how=busted
Maybe having only one object is nice but this might create some naming conflict...
IMO, following functions:
Router#addState (name: String, state: State): void
State#addState (name: String, state: State): void
State#data (key: String, value: Any): void | Any
should return the corresponding router or state to allow chaining.
Shouldn't be that hard, right?
I have a strange behaviour using Abyssa but not sure if that's a bug or normal or my code totally wrong... I'm trying to update some query params, staying on the same state. Something like:
router.updateCurrentParams = function (newParams) {
const state = router.currentState();
if (!Utils.is.equal(newParams, state.params)) {
// Triple state!! (this is not a bug)
router.state(state.state.fullName, newParams);
}
};
Then I have a state tree like that, root has a query param named bis
defined on it.
root
── home (@ /)
── main
── base (@ /base)
Then I try to move from /base
to /base?bis=default
. I would expect the transition to only go through all the update
methods since I am staying on the same state, only updating a param (even if it's a param from a parent state). Here is what happen from Abyssa logs
Changing state to root.main.base
Starting transition from root.main.base:{} to root.main.base:{"bis":"default"}
Exit root.main.base
Exit root.main
Exit root
Enter root
Enter root.main
Enter root.main.base
Updating URL: /base?bis=default
Transition from root.main.base:{} to root.main.base:{"bis":"default"} completed
It clearly says that it will transition to the same state root.main.base
but it exits everything up to the root parent and then enter everything again.
What do you think?
Can you tag your 4.2.0 release to be in sync with npm?
Remove grunt, qunit, babel, Q
Use npm scripts, jest (or mocha?), typescript, Promise
Hey,
Regarding this line : https://github.com/AlexGalays/abyssa-js/blob/master/src/State.js#L157
Is there a specific reason to prevent overriding a data key? I would like to assign a new value each time I enter the State, fetching data in ajax.
I cannot use "enterPrereqs" since I want the State to be entered immediately, using a spinner, whatever the promise complete or not.
I think an important feature would be able to add/remove the Router states in runtime after the router has been initialized. That would allow the self-contained components to construct States on the fly when they become available to the user (e.g. when they are lazy-loaded).
A real application example is a lazy-loading wizard (a dialog with steps). The application controller may not know all the wizard's States during the Router initialization because the wizard is not yet loaded.
Is there a way to support redirection? Let's say I had the following state:
contact: State("/contact/:how?subject")
But my boss wants to change all url of the site to:
newContact: State("/something/contact/:how/more/stuff?subject")
It would be awesome to no display a 404 on the old url but rather redirect to the new one, keeping all params and query string and hash of couse.
It would also be useful for "notFound" state, if you want to redirect to home page rather than display a 404 page.
Hey,
Is there a way to access the current state from JS and all its "configuration"? Something like "Router.currentState()" returning a useful object, something like:
{
name: String: the name off the current state
fullName: String: the concatenation of the current name with all ancestor names separated by a "."
parent: Object: the same object but for its parent if one
params: Hash: all dynamic params in the url
query: Hash: all the query string
... all the components or the url ...
}
For exploding the url to several components, you could use URI.js (see how they decompose an url here : http://medialize.github.io/URI.js/about-uris.html ). The benefit would be to abstract modern browsers using History API to old browsers using an hashbang or whatever in a common API inside your router.
Short story
For the next major version of Abyssa, I'm considering getting rid of exitPrereqs
completely and fix enterPrereqs
by always have the router change the url synchronously.
Long story
Currently Abyssa waits for the whole transition to complete before updating the url. Transitions without enterPrereqs
/exitPrereqs
will be almost synchronous (They're still wrapped in a promise that will only take one frame to resolve). On the other hand, transitions with enterPrereqs
/exitPrereqs
can take any amount of time.
enterPrereqs
exitPrereqs
enterPrereqs
. The problem is exactly the same as for enterPrereqs
except I believe there is no rescue plan for it. Since the browser history navigation cannot be blocked, we have to let the url change itself synchronously with the popState event. Now imagine an exitPrereqs
asking the user if she really wants to leave this state because of unsaved changes. She hits the "backward" button, see a warning popup, the browser already changed the url to the previous state. She hits "cancel" to remain on the current state: State and url are now out of sync, even though no error occured.So why is exitPrereqs
so much worse than enterPrereqs
? It can leave the router's state and the current URL out of sync during normal operations. Here's an example:
The user comes from state A and is currently in state B
The user makes some changes in state B
The user hits her back button to go back to A, either on purpose or not
A warning dialog is plugged into exitPrereqs and appears to try and prevent any work loss
The user decides to stay on the current state B and finish her work so hit cancel => the exitPrereqs promise is rejected
The router is now out of sync with the URL: The state is B with the URL of state A
A router that cannot maintain its current state and the url in sync seems a bit stupid to me considering it was its only job in the first place! That's why I'd rather not have features pretending the navigation can be blocked, since the browsers don't allow it.
For information, Ember.js is often said to have the best router of all the full-stack js frameworks; It offers a functionality similar to enterPrereqs
(http://emberjs.com/guides/routing/asynchronous-routing/) and has all the issues described above: The url sometimes changes before a transition, sometimes after, and it can easily get out of sync.
So what should be done for Abyssa?
One possibility is to leave things mostly as they are and document enterPrereqs
and exitPrereqs
as being unsafe under certain conditions (mostly when using urlSync: true
). I'm not fond of this option or having "sometimes working functionalities" in general, as it's confusing and if one is using Abyssa, it probably means urlSync is enabled for some devices (Desktops at least), where as we've seen, enterPrereqs
/ exitPrereqs
is broken.
What I suggest instead is getting rid of exitPrereqs
. It had few uses in the first place, though in an ideal world, it would be a great feature to have around.
As for enterPrereqs
, I suggest "fixing" it by having the url update immediately upon state changes; this way we have the exact same behavior when navigating normally or when popping a state. There will be an inconsistency window while the transition is running. we have guaranteed eventual consistency if the transition succeeds. Should the transition fails, the developer can take action with the router.transition.failed signal, display a message, etc. This respects the browsers' choice of never having blocking history changes except in a few strategic places (window.onbeforeunload). This would also simplify the router's logic.
Any thoughts? Did I miss anything?
I'm having troubles to understand if what I see is a bug or not... I have an application that do standard REST url like: /resources/:id/subResource
, and you can stop at any point of the url of course. Since Abyssa doesn't allow to stop outside of a leaf, I tried to create an empty state for stopping midway if the abstract parent has already do all the logic. But when using the same empty state in several states... all is going crazy.
Here is a sample: http://jsbin.com/vajovire/2/edit?js,output
Am I missing something?
UPDATE
So one solution was to create factory for common states returning a new state each time: http://jsbin.com/vajovire/4/edit?js,output
I'm starting to think it might be related to the fact that one state cannot have several parents... which seems fair enough :-)
When you are on a state, the update method will be called if any params of the full url has changed. So if I switch from /articles?page=1
to /articles?page=2
, only the update method will be called. Fair enough.
But if I change my url from /articles/1
to /articles/2
, it will still be only the update... that seems a bit less obvious. I mean, I change my path, I am on a fully different resource / page. I might have expected to have the exit and enter methods called, like reloading the current state, but with a new resource.
Could it be possible to configure a state (and eventually also a configure a default value for all states) so that the update is called only if it is a query string or the hash that is changed? Otherwise, exit the state and enter it again with the new path params.
The next step to fully use the update only on query string would be to have a way to get only the modified params. Something like:
// Before: {search: 'test', page: 1, limit: 10}
// Now: {page: 2, limit: 10, whatever: 'plop'}
function update(params, modifiedParams) {
// params:
params = {page: 2, limit: 10, whatever: 'plop'}
// modifiedParams:
modifiedParams = {
page: {current: 2, previous: 1, status: 'modified', modified: true},
whatever: {current: 'plop', status: 'added', added: true}
search: {previous: 'test', status: 'removed', removed: true}
}
}
the last q.js version is 1.0.0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.