Skip to content

RFC: Huge Refactor + Feature Additions #286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nateabele opened this issue Jul 30, 2013 · 50 comments
Closed

RFC: Huge Refactor + Feature Additions #286

nateabele opened this issue Jul 30, 2013 · 50 comments
Labels
Milestone

Comments

@nateabele
Copy link
Contributor

Brace yourselves. This is pretty freakin' long.

I Have a Branch to Which I Have Committed A Whole Bunch of Crap

For reference, see here. We'll be talking about what's going on in those last few commits (the ones with my name on them). Let's just go straight down the list, shall we?

State-Building Refactor

The registerState() method, which is used to... register new states, was previously pretty large. I have refactored it into a state builder object, which makes the various aspects of constructing a state much more comprehensible and easy to extend (albeit only internally for now... maybe we can leverage this in the provider API to allow people to generate or auto-assign their own state attributes... I literally thought of that just now). It also makes registerState() around 20 lines of code, which is nice.

The ^ Character

We have a new magical character: ^. This character is basically used to append or inherit from the current state when transitioning. You can check out the tests which demonstrate this functionality using the new $state.go() short-hand method. You can transition to a ^child state, a ^.sibling state, or a ^.^.parent state. This syntax is implemented directly in findState(), so it should work for anything that accepts a state name, however, any calls to findState() must be refactored to pass the state that the lookup is relative to. I also plan to make the ^ character for inheriting parent URLs, i.e. url:^/child`, because the current functionality of appending to the parent URL automatically is confusing and inflexible.

Also, you may notice that those tests demonstrate...

Cascading Parameter Inheritance

Yup, that's a thing now. It happens automatically when using $state.go(), or when passing { inherit: true } to $state.transitionTo() (and yup, that's been refactored to accept an options hash as its third parameter).

The $view service

The template-management code has been refactored into the new $view service. This code is still pretty crap, and needs some work, but at least it's not directly embedded inside $state anymore. I also still need to write some tests for it, so there's that, too.

View Targeting

I'm kind of lying by including this in the list, because it's actually not done (yet), but will be committed soon. In short, the current approach to view targeting is crap. It is the single most confusing aspect of what is an otherwise fairly straightforward library. We've gotten more questions on that, than on any other single aspect of this library (except maybe for resolves, but those fixes are already in progress).

Here's the thing: if I'm targeting a view, I should be able to use a simple dot-syntax, i.e. viewA.viewB.viewC, regardless of what state created that view. If a named view exists, then it exists. If it doesn't, it doesn't. Which state created it is totally irrelevant. This also makes view targeting much more flexible, as we can do things like allow relative targeting with ^, similar to the above. This is going to get way more important as people try to use this library to scale out projects, or create redistributable 3rd-party parts of applications, which may be "mounted" at arbitrary parent states within an application.

In Summary

I didn't want to submit a PR for this until we had a chance to discuss it, and because it's still in progress, but I wanted to collect everyone's thoughts on this as I wrap up these changes. Thanks for reading. /cc @ksperling, @timkindberg, @ProLoser, @stu-salsbury, @eahutchins

@timkindberg
Copy link
Contributor

I was actually just poking around your fork last night and already reviewed all of your commits. I was really happy with what I saw and super fucking excited to see so much progress! I do need to review it all again, now that I know you want actual feedback. Comments forthcoming...

@eahutchins
Copy link

The usage of ^ and . in state addressing is a bit confusing I think... ^ visually implies "go up", which should be the parent state, and a bare . is commonly used (in filesystems and other software) to mean "current". I'd assume .child would be the way to get to a child state from current, ^.sibling works but interprets the characters the other way around, and I think ^ most intuitively means "parent" and ^^ "grandparent" etc.

I realize that this means your change to the URL-relative addressing no longer matches semantically but I'd suggest not fighting years of intuition concerning filesystems and use ./url to indicate a URL relative to the parent, and perhaps the same interpretation for ^/url to mean sibling.

@timkindberg
Copy link
Contributor

Mostly I'm just super happy about all of it. Your refactoring has helped immensely with my understanding of the code, especially the stateBuilder object.

I do have to second the concern by @eahutchins on the go method's relative symbol usage. I'm not finding it easily comprehensible. I also know we have this problem because:

  1. We don't want to use '/'; concerned it may create confusion with urls.
  2. We are using '.' as our parent child separator in state declarations. So then we can't use it as both a separator and a targeting device.

Let me go off on a tangent, just to get some ideas out.

One solution, which would require some breaking changes would be to use something else in state declarations. Maybe a pipe. So instead of:.state('parent.child') we'd do .state('parent|child'). This gives us back the '.' period for the go() method. For example, state.go('.|sibling'), state.go('..|parent'), and state.go('..|..|gpa'). Maybe not pipe, but something.

Ideally, we should have a common use for any characters used in ui-router:

  • & - Any sort of appending functionality
  • ^ - The root of something
  • .. - Any sort of upward targeting or upward navigation.

Some other ideas for child name separators:

  • 'parent>child' state.go('..>parent')
  • 'parent~child' state.go('..~parent')

Finally, it would also be ideal to only have to use .. to get to a parent via state.go('..') and maybe '&' to get to a sibling via state.go('&sibling') and to a child via state.go('&|child').

Hey, or we could just use the god damn / character!!! FFS! Sorry for over abundance of feedback @nateabele. I'm not tied to all of these ideas. Just wanted to share them.

@eahutchins
Copy link

@timkindberg: what do you object to about a leading . as the "from current" indicator? If . is the composition operator I think it's still pretty intuitive that there's an implicit "current" if the parent is omitted (I've seen this pattern elsewhere in other DSLs). I think using the | is confusing given Angular's filters. FWIW I think forcing someone to use a character like ^ to mean "root" is less obvious than just defaulting to absolute addressing if there's no leading .. Making relative addressing the exceptional case that stands out visually will help newbies I think (they'll probably start with absolute addressing and add relative paths when their project grows in complexity). At any rate I agree there should be a consistent (and hopefully intuitive/terse) way to specify relative relationships across the ui-router use cases.

@ProLoser
Copy link
Member

What is the problem with using urls as a reference? People will not have to
learn a new syntax. Is it possible to do both urls and states in these
methods?

I think it would be best to use common knowledge convention such as :

/root
./current
../parent

Etc
On Jul 30, 2013 12:03 PM, "Tim Kindberg" [email protected] wrote:

Mostly I'm just super happy about all of it. Your refactoring has helped
immensely with my understanding of the code, especially the stateBuilder
object.

I do have to second the concern by @eahutchinshttps://github.com/eahutchinson the
go method's relative symbol usage. I'm not finding it easily
comprehensible. I also know we have this problem because:

  1. We don't want to use '/'; concerned it may create confusion with urls.
  2. We are using '.' as our parent child separator in state declarations.
    So then we can't use it as both a separator and a targeting device.

Let me go off on a tangent, just to get some ideas out.

One solution, which would require some breaking changes would be to use
something else in state declarations. Maybe a pipe. So instead of:
.state('parent.child') we'd do .state('parent|child'). This gives us back
the '.' period for the go() method. For example, state.go('.|sibling'),
state.go('..|parent'), and state.go('..|..|gpa'). Maybe not pipe, but
something.

Ideally, we should have a common use for any characters used in ui-router:

  • & - Any sort of appending functionality
  • ^ - The root of something
  • .. - Any sort of upward targeting or upward navigation.

Some other ideas for child name separators:

  • 'parent>child' state.go('..>parent')
  • 'parent~child' state.go('..~parent')

Finally, it would also be ideal to only have to use .. to get to a parent
via state.go('..') and maybe '&' to get to a sibling via
state.go('&sibling') and to a child via state.go('&|child').

Hey, or we could just use the god damn / character!!! FFS! Sorry for over
abundance of feedback @nateabele https://github.com/nateabele. I'm not
tied to all of these ideas. Just wanted to share them.


Reply to this email directly or view it on GitHubhttps://github.com//issues/286#issuecomment-21813930
.

@timkindberg
Copy link
Contributor

@eahutchins yeah I don't really object, I think we are the same page. I was just throwing some ideas out. @ProLoser, the reason we didn't want to use url syntax is because we are afraid it will create confusion, mixing the idea of states with urls, when they are different concepts. It's still a good option though, but it just carries that weight with it.

@ProLoser what do you mean by:

Is it possible to do both urls and states in these methods?

@timkindberg
Copy link
Contributor

@eahutchins when I said that I think ^ should be reserved for "root" I was talking about how you can use the ^ character at the beginning of the url property to specify that its a "root" url. I didn't like the idea of having it mean "root" in one place and "up once" in another place.

@ProLoser
Copy link
Member

I think you are creating confusion by making up a completely arbitrary new syntax out of thin air. This syntax is used for folder traversal, and unless the paradigm doesn't match 1-1 I don't think it's a problem.

@timkindberg
Copy link
Contributor

Ok let's just play it out then. If we used / as our separator for all things... some examples:

// configuration
$stateProvider
.state("parent", {
    templateUrl: 'parent.html',
    url: "/parent"
})
.state("parent/child", {
    templateUrl: 'child.html',
    url: "&/child", // use '&' to append to parent state's url
});

// navigation
$state.transitionTo("parent/child", { param1: 'hello' });

// absolute addressing
$state.go("/parent/child"); 

// relative addressing
$state.go("./sibling"); 
$state.go("../parent"); 
$state.go("../../../ancestor"); 

// util methods
$state.includes("parent/child");
$state.is("parent/child");
$state.href("parent/child", { param1: 'hello' });
// directives
<a ui-sref="parent/child({ param1: 'hello' })">Link</a>

I think this is making a lot of sense to me... what do you guys think?

@ProLoser
Copy link
Member

Now I'm confused.

You are using & in a url option?
On Jul 30, 2013 7:00 PM, "Tim Kindberg" [email protected] wrote:

Ok let's just play it out then. If we used / as our separator for all
things... some examples:

// configuration$stateProvider.state("parent", {
templateUrl: 'parent.html',
url: "/parent"}).state("parent/child", {
templateUrl: 'child.html',
url: "&/child", // use '&' to append to parent state's url});
// navigation$state.transitionTo("parent/child", { param1: 'hello' });
// absolute addressing$state.go("/parent/child");
// relative addressing$state.go("./sibling"); $state.go("../parent"); $state.go("../../../ancestor");
// util methods$state.includes("parent/child");$state.is("parent/child");$state.href("parent/child", { param1: 'hello' });

// directivesLink

I think this is making a lot of sense to me... what do you guys think?


Reply to this email directly or view it on GitHubhttps://github.com//issues/286#issuecomment-21835858
.

@laurelnaiad
Copy link

  1. Is this absolute, or relative?
    $state.transitionTo("parent/child", { param1: 'hello' });
  2. If relative, equivalent to
    $state.transitionTo("./parent/child", { param1: 'hello' }); ?
  3. If absolute, equivalent to
    $state.transitionTo("/parent/child", { param1: 'hello' }); ?
  4. What does these test? Is it relative? How does a relative test for where you are presently make any sense?
$state.includes("parent/child");
$state.is("parent/child");
  1. '../parent' looks like it really means '../uncle' which I'd hope to avoid.

  2. '../../../ancestor' seems like the brother of your great grandfather (I'm not good enough at genealogy to know whether you call that a cousin thrice removed or a 3rd uncle, or what, but if I ever write code like that, please put me down/out of my misery.

    I guess I'm ok with the syntax if 'parent/child' is equivalent to './parent/child', even if I'm not sure all this relativity is a "good thing." I guess I'd try to stick to these patterns in my app code:

  • './sibling' or 'sibling' (I see little difference in coding style here)
  • '..' (go up)
  • '/absolutely/poistively/a/fifth-level/state' (9 times out of time I'd hope to stick to this)

@nateabele -- I want to find time to look at your work. I'm under a deadline and not getting much sleep but hopefully soon. Thanks for keeping me in the loop!

@nateabele
Copy link
Contributor Author

$state.includes("parent/child"); $state.is("parent/child");

No me gusta. It confuses state names with URLs, between which there should be strict conceptual boundaries. Kinda ambivalent about the other bits as well. We should be able to have a single, consistent character to represent inheritance & upward traversal.

@timkindberg
Copy link
Contributor

@ProLoser I kind of snuck that '&' in there. It was an idea I had a while back, which actually most were in favor of. Using '&' to explicitly declare appended urls instead of doing it automatically for the user. There were lots of users who didn't realize the appending was happening. The '&' is similar to appending in LESS. Its actually already on the roadmap: #181.

@stu-salsbury I numbered your questions.

  1. Neither, transitionTo, includes and is all use a qualified state name.
  2. See 1
  3. See 1
  4. See 1
  5. '../parent' could also be used to mean '../uncle'. The '..' takes you up one level, and then the '/name' tells you which state at that level. Just like '../../name' could be used to access the grandparent or one of its siblings.
  6. '../../../ancestor' I was just trying to show that its possible.
  7. I agree, those could also be options

@nateabele of course you are ambivalent. You probably just wanted to merge your hard work instead of having us bitch about it. I don't blame you.
You say:

We should be able to have a single, consistent character to represent inheritance & upward traversal.

Considering @eahutchins, @ProLoser and I all feel like the ^ character is a bit confusing, what other ideas do you like?

@ProLoser
Copy link
Member

Whatever, I put in my 2c but I'm not married to it. I will let you guys
work it out.
On Jul 30, 2013 8:01 PM, "Tim Kindberg" [email protected] wrote:

@ProLoser https://github.com/ProLoser I kind of snuck that '&' in
there. It was an idea I had a while back, which actually most were in favor
of. Using '&' to explicitly declare appended urls instead of doing it
automatically for the user. There were lots of users who didn't realize the
appending was happening. The '&' is similar to appending in LESS. Its
actually already on the roadmap: #181#181
.

@stu-salsbury https://github.com/stu-salsbury I numbered your questions.

  1. Neither, transitionTo, includes and is all use a qualified state name.
  2. See 1
  3. See 1
  4. See 1
  5. '../parent' could also be used to mean '../uncle'. The '..' takes
    you up one level, and then the '/name' tells you which state at that level.
    Just like '../../name' could be used to access the grandparent or one of
    its siblings.
  6. '../../../ancestor' I was just trying to show that its possible.
  7. I agree, those could also be options

@nateabele https://github.com/nateabele of course you are ambivalent.
You probably just wanted to merge your hard work instead of having us bitch
about it. I don't blame you.
You say:

We should be able to have a single, consistent character to represent
inheritance & upward traversal.

Considering @eahutchins https://github.com/eahutchins, @ProLoserhttps://github.com/ProLoserand I all feel like the
^ character is a bit confusing, what other ideas do you like?


Reply to this email directly or view it on GitHubhttps://github.com//issues/286#issuecomment-21837405
.

@nateabele
Copy link
Contributor Author

@nateabele of course you are ambivalent. You probably just wanted to merge your hard work instead of having us bitch about it. I don't blame you.

No, feedback is good, and I appreciate it. I have more work to do on this stuff anyway. I just don't want to go down the rabbit hole of single-character syntax deliberations. The most important things are consistency and overall conceptual integrity of the library.

I've participated in more than enough of these discussions to know that trying to speculate about what will or won't confuse people is not useful. Maintain conceptual integrity, and follow a simple set of well-documented rules. The rest will take care of itself.

Anyway, that's enough preaching from me. I honestly don't care what the specific character is (I actually had it as & initially, until I went back and re-read some previous discussions and changed it to ^), so long as it isn't one of . or / (also, the more I think about it, the more I think using / as a state separator character is a really bad idea).

@nateabele
Copy link
Contributor Author

@ProLoser:

What is the problem with using urls as a reference?

This has already been discussed to death in other contexts. :-) One of the explicit goals of the project is to use it completely without URLs. There are explicit conceptual and abstraction-layer boundaries between the two, and changing to URL-style state selectors muddies the water in a really bad way.

@laurelnaiad
Copy link

I think the answer is colons. :)

Edit: I'm only partially joking.

@jeme
Copy link
Contributor

jeme commented Jul 31, 2013

Just to add a reference to where the / discussion took place: #86 (can't remember if we had it in other places as well?)

I'm personally in favor for the /, but won't go into it again, just putting a vote and reference...

@nateabele
Copy link
Contributor Author

Hey @jeme, thanks for the reference. Sorry I forgot to cc you. :-)

@timkindberg
Copy link
Contributor

After thinking about the / some more I do see now that its the greater of two evils. I'd rather a person have to reread documentation on the new syntax than potentially confuse state addressing with url addressing.

Ok moving on...


I re-read @eahutchins early post again and it actually sums up how I am feeling.

First he says:

The usage of ^ and . in state addressing is a bit confusing I think... ^ visually implies "go up", which should be the parent state, and a bare . is commonly used (in filesystems and other software) to mean "current". I'd assume .child would be the way to get to a child state from current, ^.sibling works but interprets the characters the other way around, and I think ^ most intuitively means "parent" and ^^ "grandparent" etc.

But I wanted to flesh it out some more and see it in action. So let's write it out (you might be able to tell that I like to see this stuff visually and I hope it helps you guys too).

If we assume that ^ takes us up, . takes us down, and ~ takes us laterally (just go with it). If we don't start with one of those special characters then its absolute referencing.

// -- ABSOLUTE -- //
// No prepended special character
$state.go('full.state.name'); 

// -- CHILD -- //
// '.' takes us down, but we also have to say which child
$state.go('.child'); 

// -- GRANDCHILD -- //
// Same as child but repeat '.name' as needed
$state.go('.child.grandchild'); 

// -- SIBLING -- //
// '~' takes us lateral, but we also have to say which sibling
$state.go('~sibling'); 

// -- SIBLING'S CHILD -- //
$state.go('~sibling.child'); 

// -- PARENT -- //
// '^' takes us up. No need to specify which parent, there's only one.
$state.go('^');

// -- PARENT'S SIBLING -- //
// '^' takes us up. Need to specify the state if its not going to be the parent.
$state.go('^uncle');

// -- GRANDPARENT -- //
// Same as parent, can use as many '^' as needed
$state.go('^^');

// -- GRANDPARENT'S SIBLING -- //
$state.go('^^greatuncle');

It's easy to understand, doesn't use /, and solves all targeting needs.

@nateabele
Copy link
Contributor Author

I dig that, barring the ^^.foo bit, which I still think needs to be ^.^.foo.

@timkindberg
Copy link
Contributor

Hmm I disagree. That looks like up, down, up, down to me. Also I don't have a ^^.foo in my example, it's just ^^foo.

@timkindberg
Copy link
Contributor

So I came up with the specs on the referencing system I am proposing. Its fairly technical but it shows the usage rules. And would help with the parsing logic.

Symbols:
^ = up one state level
. = down one state level
~ = same state level
s = a required statename
[s] = an optional statename

Chunks:
^[s] - 'up' chunk
.s - 'down' chunk
~s - 'lat' chunk

Usage Symbols:
(0+) - zero or more of a chunk
(1+) - one or more of a chunk
(1) - only one of a chunk

Usage Rules:
Chunks can be combined in the following ways, with no "separators".
up(1+) down(0+) - one or more 'ups' followed by zero or more 'downs'
down(1+) - one or more 'downs'
lat(1) down(0+) - one 'lat' followed by zero or more 'downs'

@eahutchins
Copy link

If you think of these things as operators I think it will be a lot less confusing:

composition: LHS . RHS means "find the substate RHS of LHS, where empty LHS is "current state"

ascent: LHS ^ means "parent of LHS", again where empty LHS is "current state"

I really don't like ^uncle because it makes the definition of ^ more complicated, and I don't think ~ as a lateral move has a simple definition or is doing anything particularly useful (^^.uncle vs ^~uncle). Keeping entities to a minimum is a good thing I think. Either way I really need this functionality and as long as it works and I can explain it to a designer I'm happy.

note: I do think being able to ascend out of some LHS will come in handy in cases where you're given a state name and for whatever reason can't parse it out.

@timkindberg
Copy link
Contributor

@eahutchins can you list out the various uses of your proposal, that would help me. Just copy and modify my list from above comment. Because I didn't intend to use a ~ character until I needed it to achieve all referencing scenarios.

@laurelnaiad
Copy link

@nateabele, I don't think we need uncle support. The costs seem to outweigh the gains. If you drop it you can go all the way back to just supporting '^' and '.'

^ goes up, and ~ goes lateral and . goes down and ^ does not support symbols after the ^^^^s

What kind of real life circumstances require "uncles". Bailing you out of jail, going to a ballgame. Neither requires going to his house.

Seriously, though, on the rare case where you need to visit your uncle you can you use a naked (i.e. absolute) state path? I figure it's most likely to show up when you're building a tree in the UI, so using absolute state paths would make some sense...

@timkindberg
Copy link
Contributor

@eahutchins

I don't think ~ as a lateral move has a simple definition or is doing anything particularly useful (^^.uncle vs ^~uncle)

Just wanted to note that ~ was needed because without a prepended character you get absolute referencing.

Also I did not use a ^. combo anywhere in my spec, not sure why you guys keep adding that . in there. Your example actually doesn't match my spec in any way. ^^.uncle is unnessary (use ^uncle instead) and ^~uncle is invalid per my spec. Either way I'd like to see your proposal written out and how it would satisfy all of the use cases, because I have a feeling it wouldn't be as pretty as you are expecting.

@stu-salsbury

I don't think we need uncle support.

That's fine, but the fact stands that my proposal is pretty elegant while achieving all possible use cases. If we don't want to include uncle targeting (or other types of targeting) because its too much extra logic behind the scenes, then that's fine. However, my syntax and the rules for referencing are solid so if we ever wanted to add the functionality in, I'll just be recommending and referring to the same spec.

@nateabele
Copy link
Contributor Author

@timkindberg

Hmm I disagree. That looks like up, down, up, down to me.

But that doesn't make sense in context. Compare to your example of ../../../ before. Remember: a small number of simple, consistent rules that apply universally, so $state.$parent.$parent.foo collapses to ^.^.foo.

@timkindberg
Copy link
Contributor

@nateabele Is that how your current syntax works?

@nateabele
Copy link
Contributor Author

@timkindberg Basically, yes. Don't get me wrong. the regular expression that parses the path syntax is easy enough to change (it's already gone through a few different syntax iterations), i just think the double ^^ for grandparents is wrong.

@timkindberg
Copy link
Contributor

@nateabele okay so your proposal would be like this?

^ - means current state
. - means down

// -- ABSOLUTE -- //
$state.go('full.state.name'); 

// -- CHILD -- //
$state.go('^.child'); 

// -- GRANDCHILD -- //
$state.go('^.child.grandchild'); 

// -- SIBLING -- //
$state.go('^sibling'); 

// -- SIBLING'S CHILD -- //
$state.go('^sibling.child'); 

// -- PARENT -- //
$state.go('^');

// -- PARENT'S SIBLING -- //
$state.go('^.^.uncle');

// -- GRANDPARENT -- //
$state.go('^.^');

// -- GRANDPARENT'S SIBLING -- //
$state.go('^.^.^.greatuncle');

The only thing that makes no sense is when ^ is used twice, then its being used as a "here" first and then an "up" second. That's not consistent. I like my proposal much better, but I'd be ok with this if that's what others prefer. Also let me know if I got anything wrong.

@timkindberg
Copy link
Contributor

@nateabele

i just think the double ^^ for grandparents is wrong

I remember @ksperling being in favor of that syntax... where the hell is he anyway? Probably changing diapers.

@nateabele
Copy link
Contributor Author

@timkindberg

I remember @ksperling being in favor of that syntax...

Yeah, he was also in favor of ......, which... no. :-P Anyway, your proposal above is almost identical to mine, I just need to jump up one more level at the outset. I'll put together some tests tomorrow with the above as use cases.

@eahutchins
Copy link

I'm working on a commercial product so discussing specific use cases is a bit problematic.
That being said I think composing reusable components is an obvious use case.

I'll put a brief note where and why I differ below (but I can live with what you're proposing):

// -- ABSOLUTE -- //
// No prepended special character
$state.go('full.state.name'); 

// -- CHILD -- //
// '.' takes us down, but we also have to say which child
$state.go('.child'); 

// -- GRANDCHILD -- //
// Same as child but repeat '.name' as needed
$state.go('.child.grandchild'); 

agreed.

// -- SIBLING -- //
// '~' takes us lateral, but we also have to say which sibling
$state.go('~sibling'); 

// -- SIBLING'S CHILD -- //
$state.go('~sibling.child'); 

In my syntax ~ would be ^.
(I don't object to ~ as a shorcut but it isn't needed):

// -- SIBLING -- //
// `^` takes us up, then `.` down to sibling
$state.go('^.sibling'); 

// -- SIBLING'S CHILD -- //
$state.go('^.sibling.child'); 
// -- PARENT -- //
// '^' takes us up. No need to specify which parent, there's only one.
$state.go('^');

// -- GRANDPARENT -- //
// Same as parent, can use as many '^' as needed
$state.go('^^');

agreed.

// -- PARENT'S SIBLING -- //
// '^' takes us up. Need to specify the state if its not going to be the parent.
$state.go('^uncle');

// -- GRANDPARENT'S SIBLING -- //
$state.go('^^greatuncle');

Here I think the ^ operator is getting overloaded to also compose a child if there's
an identifier starting the next token, which is extra work in the parser/evaluator I'm guessing.
(maybe not). If it falls out free then I've got no objection if it works that way, but if
it more complexity to support it it really isn't needed. Instead in my syntax:

// -- PARENT'S SIBLING, AKA GRANPA'S CHILD -- //
// '^' takes us up twice, `.` down to uncle
$state.go('^^.uncle');

// -- GRANDPARENT'S SIBLING -- //
$state.go('^^^.greatuncle');

and you can do:

// -- STATIC RELATIVE -- //
// up from a static path to a relative path
$state.go(myPath + '^.checkout');

The grammar is something like:

NAME: [_$0-9A-Za-z]+

EXPR:
    <null> // return "current"
|   NAME  // current = absolute root path "NAME"
|   EXPR '.' NAME // current = current's substate "NAME"
|   EXPR '^' // current = current's parent or root (got this wrong on first post)

I also think whatever gets picked should make the most sense in the bigger context (i.e. require the least new learning to understand).

@timkindberg
Copy link
Contributor

@eahutchins I'm in complete agreement with your proposal. @nateabele what do you think of it?

@eahutchins
Copy link

There are a few weird corner-cases in that grammar, NAME1 NAME2 routes to absolute NAME2 which could be fixed or lived with (I could see use cases for being able to pass a leading space ' reset.to.here' to some function I have no control over which always prepends a path like $state.go($path + $argument), not all of them good use cases).

In the future we might want other operators like EXPR > NAME to find any descendant (I could see $state.go('>CANCEL') as useful in some circumstances, for example).

@jeme
Copy link
Contributor

jeme commented Aug 1, 2013

I gotta side with @nateabele here on the ^^^ vs ^.^.^... (I think)... at least the later is favorable for 2 reasons to me (now that / is of the chart)

1. How I would understand it

. doesn't mean down, but is instead a "selector separator"... The next selector in the expression is always "down wards" from the "currently selected" (we don't mean $state.current here)... (except for ^ which is upwards)

So conceptually it becomes (pseudo code):

var current = $state.current;
foreach(selector in expression.split('.')) {
  current = select(current, selector);
}
return current;

Might be because how I think about these things.

In other words:

{I start here}.{from there i wan't to go here}.{and then here}

2. Implementation

Second reason is that it may be simpler to implement.

Illustration

State Tree Illustrations

@timkindberg
Copy link
Contributor

@atian25 please start a new discussion. This one is solely for reviewing @nateabele changes as listed in his original post.

@timkindberg
Copy link
Contributor

@jeme

I gotta side with @nateabele here on the ^^^ vs ^.^.^... (I think)

I hear that and I get that, and I got it when @nateabele was explaining it, and honestly I'm fine if that's how it needs to be. Its just simply that I like how it looks without the . between each one, but its no big deal.

So anyway I think we are to a point where we all more or less agree. Here are the final use cases per all discussions:

^ - means up from previous state
. - is accessor of previous state

// -- ABSOLUTE -- //
$state.go('full.state.name'); 

// -- CHILD -- //
$state.go('.child'); 

// -- GRANDCHILD -- //
$state.go('.child.grandchild'); 

// -- SIBLING -- //
$state.go('^.sibling'); 

// -- SIBLING'S CHILD -- //
$state.go('^.sibling.child'); 

// -- PARENT -- //
$state.go('^');

// -- PARENT'S SIBLING -- //
$state.go('^.^.uncle');

// -- GRANDPARENT -- //
$state.go('^.^');

// -- GRANDPARENT'S SIBLING -- //
$state.go('^.^.^.greatuncle');

Can we all agree on this?

@eahutchins
Copy link

I'm fine with it, it's basically viewing ^ as an implicit sub-state of every state rather than an operator (much like a filesystem ..). It will be weird if other true operators get added though (like the > descendant selector I proposed), do all of those get added as implicit sub-states or what? Also . is an operator either way, so now you have three classes of things to worry about (names, . and the special ^ name).

I think this grammar is a bit more complicated too:

NAME: [_$0-9A-Za-z]+

FIRST_ELEMENT:
    <null> // return "current"
|   NAME // current = absolute root path "NAME"
|   '^' // parent of "current" or root

ELEMENT: NAME | '^'

EXPR:
    FIRST_ELEMENT
|   EXPR '.' ELEMENT // current = current's substate "NAME", or parent if "NAME" is "^"

@eahutchins
Copy link

Actually @jeme your last example is an absolute state, selecting the green 3 not the blue one. .3 would do what you indicated.

@timkindberg
Copy link
Contributor

whoa I totally did not see @jeme 's graphic before...

@timkindberg
Copy link
Contributor

@jeme great graphic! will def be using that in the guide!!!

@eahutchins

I'm fine with it, it's basically viewing ^ as an implicit sub-state of every state rather than an operator (much like a filesystem ..). It will be weird if other true operators get added though (like the > descendant selector I proposed), do all of those get added as implicit sub-states or what? Also . is an operator either way, so now you have three classes of things to worry about (names, . and the special ^ name).

Its hard to tell what you were in favor of here. Is this for or against keeping ^^ as a valid reference?

@eahutchins
Copy link

I'd prefer ^ as an operator (and I think it's simpler to understand and implement), resulting in ^^ being legal, but I need something working soon more than I need pretty grammars ☺

@timkindberg
Copy link
Contributor

@eahutchins ok cool, me too. Ok this is the FINAL spec. Leaving some room for @nateabele to implement however is easiest.

^ - means up from previous state
. - is accessor of previous state

// -- ABSOLUTE -- //
$state.go('full.state.name'); 

// -- CHILD -- //
$state.go('.child'); 

// -- GRANDCHILD -- //
$state.go('.child.grandchild'); 

// -- SIBLING -- //
$state.go('^.sibling'); 

// -- SIBLING'S CHILD -- //
$state.go('^.sibling.child'); 

// -- PARENT -- //
$state.go('^');

// -- PARENT'S SIBLING -- //
$state.go('^^.uncle');
// if possible, otherwise
$state.go('^.^.uncle');

// -- GRANDPARENT -- //
$state.go('^^');
// if possible, otherwise
$state.go('^.^');

// -- GRANDPARENT'S SIBLING -- //
$state.go('^^^.greatuncle');
// if possible, otherwise
$state.go('^.^.^.greatuncle');

NO MORE DISCUSSION ON THIS IF AT ALL POSSIBLE! I'm channeling @nateabele right now. I'm sure he's sick of this circus (however necessary).

@jeme
Copy link
Contributor

jeme commented Aug 1, 2013

This would largely be my approach on the implementation:

function lookup(path) {
    var sp = path.split('.'); 
    var sc = current.split('.'); //the "full name" of the current state.

    function select(selector) {
        switch (selector) {
            case "":
                sc = [];
                break;
            case "^":
                sc.pop();
                break;
            default:
                sc.push(selector);
                break;
        }
    }

    for (var i = 0; i < sp.length; i++) 
        select(sp[i]);

    return sc.join('.'); //the targeted state name
}

and in order to treat my.selecter as from root and visa-versa with .my.selector being relative, I would add the expression modifyer:

function modify(path) {
    switch (path.charAt(0)) {
        case '.':
            return path.substring(1);
        case '^':
            return path;
    }
    return '.' + path;
}

A Demonstrating fiddle on the output.

http://jsfiddle.net/kcGzD/1/

It lacks Error handling as it is there, but I didn't know if you had an final implementation already @nateabele... if not, well your welcome to grap it... (it only returns the targeted name)

@jeme
Copy link
Contributor

jeme commented Aug 1, 2013

Ohh... and ill correct the diagram according to what @nateabele comes up with if you wan't to use it @timkindberg, but please download it and put it somewhere else to host (github or something), as I won't guaranty that it stays in my dropbox >.<

@nateabele
Copy link
Contributor Author

I have just committed and merged the final implementation for this. The tests show all the various targeting permutations.

As you can see, relative targets must always be dot-separated, because (a) the parser code is just simpler, and (b) having multiple ways of doing such low-level syntax things is generally regarded as A Bad Idea™.

Having said that, I do want to thank everyone for their input, as the final implementation is clearly more robust than my initial one. @jeme thanks for putting together that awesome diagram, it will be of great use in the docs for this.

@timkindberg you may notice that not all of your permutations are covered directly in the tests (great uncles, and so on). I didn't want to add a bunch more states to the test, but those are still covered by the syntax.

And @eahutchins, bringing the BNF! Remind me never to oppose you in a CS debate. :-)

@timkindberg
Copy link
Contributor

💯 This is so cool @nateabele! I think I can thank you from all of us for taking the time (that you could have been doing other things) to make some much needed and incredibly valuable changes to ui-router. Bravo dude!

@jeme
Copy link
Contributor

jeme commented Aug 12, 2013

Bah... ill comment on the new issue instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants