{"action":"created","issue":{"url":"https://api.github.com/repos/ractivejs/ractive/issues/2637","repository_url":"https://api.github.com/repos/ractivejs/ractive","labels_url":"https://api.github.com/repos/ractivejs/ractive/issues/2637/labels{/name}","comments_url":"https://api.github.com/repos/ractivejs/ractive/issues/2637/comments","events_url":"https://api.github.com/repos/ractivejs/ractive/issues/2637/events","html_url":"https://github.com/ractivejs/ractive/pull/2637","id":172371318,"number":2637,"title":"Attempt to address remaining blockers for 0.8 - RFC/Review","user":{"login":"evs-chris","id":161978,"avatar_url":"https://avatars.githubusercontent.com/u/161978?v=3","gravatar_id":"","url":"https://api.github.com/users/evs-chris","html_url":"https://github.com/evs-chris","followers_url":"https://api.github.com/users/evs-chris/followers","following_url":"https://api.github.com/users/evs-chris/following{/other_user}","gists_url":"https://api.github.com/users/evs-chris/gists{/gist_id}","starred_url":"https://api.github.com/users/evs-chris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/evs-chris/subscriptions","organizations_url":"https://api.github.com/users/evs-chris/orgs","repos_url":"https://api.github.com/users/evs-chris/repos","events_url":"https://api.github.com/users/evs-chris/events{/privacy}","received_events_url":"https://api.github.com/users/evs-chris/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":9,"created_at":"2016-08-22T05:17:47Z","updated_at":"2016-08-30T03:37:54Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/ractivejs/ractive/pulls/2637","html_url":"https://github.com/ractivejs/ractive/pull/2637","diff_url":"https://github.com/ractivejs/ractive/pull/2637.diff","patch_url":"https://github.com/ractivejs/ractive/pull/2637.patch"},"body":"I want to apologize in advance for the big funky PR and the length of the description here. There are a number of chewy issues outstanding in edge that I consider to be blockers and this is my attempt to address them. There are a few somewhat overlapping things combined here:\r\n\r\n## Rebinding\r\nThis moves from view-based rebinding to model-based rebinding, meaning that instead of the shuffle telling the view that everything in the view needs to rebind, the model that is shuffling tells all of its deps that its value is moving to a new model. It also supplies the target model so that the deps can make sure that it matches so that deps on the absolute path `list.0.bar` don't accidentally get shuffled to `list.1.bar` on an unshift. This is a good deal faster than having everything in the view re-resolve and has the added bonus of catching things that aren't view-related, like observers, mappings, and resolvers.\r\n\r\nThere are a handful of outstanding shuffle bugs, particularly on observers, that are resolved by this type of rebinding along with the next change...\r\n\r\n## Mappings\r\nThis also takes the mapping and link constructs and merges them using a new type of model. The `LinkModel` is simply a model that points to another model, which may be a plain data model or another link itself. When a link model is mutated, it triggers the mutation on its source model and lets it filter back to the link, so there's still a single source of truth at the origin. That creates something like a parallel namespace for the link that manages its own deps, resolvers, etc. The separate namespace makes changing the root source of the link much simpler, since now it doesn't have to try to comb through combined deps and figure out which ones should be shuffled off to the new source and which ones should remain on the model.\r\n\r\n**Side Note:** Since there was a ton of overlap between `Model` and `LinkModel`, I rebased them both on a new `ModelBase`. I'm not crazy about the name, but it DRYs up a good bit of code.\r\n\r\nAny regular model may now be turned into a link by having a `LinkModel` installed on it at `_link`. The `ModelBase.link` method takes care of creating a link on a model, transferring (rebinding) the model's deps to the new link, and making sure everything updates. Linking on an already linked model simply updates the source of the link.\r\n\r\nThe issues with shuffling under a mapping e.g. `{{#each list}}<component foo=\"{{.}}\" />{{/each}}` and observers are much easier to address, and I think they're all covered here. Conditional mappings also have their corner cases addressed when combined with the rebinding changes.\r\n\r\nSince the method to generate aggregate root data on `instance.get()` changed to accommodate links, I also added an options object for `get` that allows you to control whether or not you want the aggregate or the source data directly. The root model defaults to providing the virtual data on get, and child models default to providing the source data so as not to be a breaking change. You can now call `instance.get({ virtual: false })` to get the actual root object and `instance.get('some.path', { virtual: true })` to get `some.path` including any children that may be links.\r\n\r\nSince mappings and links are the same thing, this would open up the possibility to have nested mappings for components. It would also make cross instance links equivalent to mappings. I haven't addressed either of those here, but it looks like they'd be easy to achieve.\r\n\r\n## Expressions\r\nExpressions in edge are composed of a proper `Computation` and an `ExpressionProxy` that manages the computation. The problem with that is that the computation is registered with the root of the model based on its signature and signature can, and with expressions often do ,overlap. They also create a giant map to deal with in the root model if there happen to be a few expressions in a large iterative section. I was originally going to try to reuse the computations, but then I realized that expressions with object and array literals in them can't be safely reused.\r\n\r\nThis takes the `ExpressionProxy` and detaches it from a computation to let it stand as its own full model somewhat mirroring what `Computation` does. There is probably more that could be shared between them, but I'll address that later if this works out. Now realistically, there was no way to interact with an expression model outside of the template before, but there is definitely not a way to do so here where the expression is not registered anywhere in the model tree (other than as a dep). So to address that, I extended the context methods to resolve to actual models and perform their operations instead of resolving to keypaths and just calling the appropriate instance method. That adds a bit of code to the context methods, but it also opened up an opportunity to share more code as generalized helpers among the instance methods, notably handling `*` keypath resolution and maps of paths to values.\r\n\r\nSo, you can now cause an update in an expression as long as you have an anchoring event or node info object from an element where the expression is part of the context. To be clear, if you want to call any instance methods from an expression context, you will need to use the context method on the event object to do so, as the keypath can no longer be resolved e.g. `{{#foo()}}<a on-click=\"event.toggle('.bar')\">click</a>{{/}}` as opposed to `{{#foo()}}<a on-click=\"@this.toggle(@keypath + '.bar')\">click</a>{{/}}`. _I_ think that's an overall improvement anyways, and it has the added benefit of not updating the wrong section if the expression keypath happened to overlap somewhere else in the template. I also added `reverse` and `sort` to the context methods, as they were missed the first time around.\r\n\r\n`if`/`elseif`/`else` in edge use a clever negation and stacking of their expressions to split each branch into its own section. The downside to that is that side-effecting or expensive computations with be run an additional time for each successive branch. This PR drops the expression duplication and adds a flag on elseif and else sections that marks them as subordinate. When a subordinate section binds, it looks up its nearest sibling and links it with itself. Then when any of the siblings updates, it notifies its subordinate siblings to update as well, and each of them checks to see that none of the prior siblings is truthy before checking their own condition. This effectively emulates the usual branching method of most languages. This is technically a breaking template change, but the new code can still manage an old template, so I don't think a template bump is necessary since there's already one in edge.\r\n\r\n## Epilogue\r\nIf you have some time to look over the code, I'd really appreciate some review. If you have some more time to build it and try it out with a project, that would be great too! I'm thinking about publishing some scoped Ractive experiments on npm, and this would probably be a good place to start considering that if it passes review I'd still like to get some mileage on it before merging.\r\n\r\nThanks!\r\n\r\n## TODOs\r\n* [x] Implement and test rebinding for decorators\r\n* [x] Implement and test rebinding for transitions"},"comment":{"url":"https://api.github.com/repos/ractivejs/ractive/issues/comments/243324127","html_url":"https://github.com/ractivejs/ractive/pull/2637#issuecomment-243324127","issue_url":"https://api.github.com/repos/ractivejs/ractive/issues/2637","id":243324127,"user":{"login":"evs-chris","id":161978,"avatar_url":"https://avatars.githubusercontent.com/u/161978?v=3","gravatar_id":"","url":"https://api.github.com/users/evs-chris","html_url":"https://github.com/evs-chris","followers_url":"https://api.github.com/users/evs-chris/followers","following_url":"https://api.github.com/users/evs-chris/following{/other_user}","gists_url":"https://api.github.com/users/evs-chris/gists{/gist_id}","starred_url":"https://api.github.com/users/evs-chris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/evs-chris/subscriptions","organizations_url":"https://api.github.com/users/evs-chris/orgs","repos_url":"https://api.github.com/users/evs-chris/repos","events_url":"https://api.github.com/users/evs-chris/events{/privacy}","received_events_url":"https://api.github.com/users/evs-chris/received_events","type":"User","site_admin":false},"created_at":"2016-08-30T03:37:54Z","updated_at":"2016-08-30T03:37:54Z","body":"Also forgot to mention that there is now deprecation in place for calling methods with `@keypath` derivations within an expression context. It'll throw out a warning in debug mode each time it happens, and the deprecation and support will be removed in 0.9. It will also fall over in the same way as current edge if there happens to be a duplicate expression keypath."}}
{"actor":{"display_login":"evs-chris"}}