Coder Social home page Coder Social logo

abyssa-js's Introduction

abyssa-js's People

Contributors

alexgalays avatar bapmrl avatar kidkarolis avatar pauldijou avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

abyssa-js's Issues

tagging v3.0.0

Hi Alexandre,

Can you tag your 3.0.0 release?

♥ the new saucelabs integration. I hope you find some time to add the travis-ci github hook. (I know @gre can help ). btw. @bobylito and @gre should really take a look at abyssa!

Vdom and Abyssa

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,

Customize hashbang

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: '!'
});

Relative navigation

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});

Test current situation

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.

NotFound exception thrown when State has enter, exit and sub 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?

Support base url

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!

is matching multiple parameters in a route supported?

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".

No magic in Router init

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() )

Reflexion about StateWithParams

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...

screenshot from 2014-07-05 20 02 51

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();

Unknow query

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...

Allow chaining

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?

Exiting all states when changing a query param

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?

Add/remove States at after Router init

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.

Redirect

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.

Access current state

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.

enterPrereqs/exitPrereqs are fundamentally broken

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

  • Introduction

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
    This functionality works fine when navigating normally: I click somewhere, a transition is initiated, after say, 50ms or 5s the transition finishes, the url then updates. Should the transition fail, the url is not changed. That's great.
    Where things get ugly is when a popState event is involved (back/forward buttons, history.back(), etc).
    When this event is dispatched, the browser immediately updates the url; This default behavior cannot be prevented. This implies that to be consistent with the regular navigation scenario, Abyssa have to revert the url to what it was, wait to see if the transition succeed or fail, then change the url back if it did succeed. This is not only a bit ugly, it is impossible to implement:
    • (Not currently implemented) If we were leveraging the url hash, changing window.location.hash generates a new history entry. We're pushing new history entries in the users' browsers just to block navigation, that's awful.
    • Using the history API, we could call history.replace() to mutate the current state: Same issue: We're transforming the navigation history and losing information; An user could keep on clicking the back button and end up overwriting his entire history with the same, current state. Again, awful.
    • This leaves only one option: Not mutating the history, but navigate it. Note that the browsers don't let one read the full history entries for cross site privacy reasons. We could try to find out where the previous state was in the history (By maintaining our own history stack, and give our states unique ids) and call history.previous() or history.back() a number of times or even call history.go(offset). Indeed, on desktop browsers, one can jump in the history a few entries at a time (e.g hold the mouse on the back button, see the list of the last 15 entries and pick one) Again, this is impossible to implement. The history can be interleaved with entries not belonging to our app so we cannot maintain our own history stack. This means we would have to navigate the history in both direction, trying to find and reactivate our previous state; Considering each navigation operation is an asynchronous operation, this would be a truely insane thing to attempt.
  • exitPrereqs
    This functionality was mostly added for symmetry with 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

  • Conclusion

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?

Reusing the same state

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 :-)

Update and diff params

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}
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.