├── 0000-template.md ├── CODE_OF_CONDUCT.md ├── README.md ├── deprecation-template.md └── text ├── 0001-transform-attribute-meta-parameter.md ├── 0003-block-params.md ├── 0010-engines.md ├── 0011-improved-cp-syntax.md ├── 0015-the-road-to-ember-2-0.md ├── 0024-bound-attributes.md ├── 0045-internet-explorer.md ├── 0046-registry-reform.md ├── 0050-improved-actions.md ├── 0053-helpers.md ├── 0056-improved-release-cycle.md ├── 0057-ember-data-reference-unification.md ├── 0058-helper-listing.md ├── 0061-ember-data-background-fetch.md ├── 0064-contextual-component-lookup.md ├── 0065-deprecation-warning-handlers.md ├── 0091-weakmap.md ├── 0095-router-service.md ├── 0101-ember-data-friendly-errors.md ├── 0120-route-serializers.md ├── 0136-contains-to-includes.md ├── 0139-isHtmlSafe.md ├── 0143-module-unification.md ├── 0150-factory-for.md ├── 0176-javascript-module-api.md ├── 0178-deprecate-ember-k.md ├── 0186-track-unique-history-location-state.md └── 0191-deprecate-component-lifecycle-hook-args.md /0000-template.md: -------------------------------------------------------------------------------- 1 | - Start Date: (fill me in with today's date, YYYY-MM-DD) 2 | - RFC PR: (leave this empty) 3 | - Ember Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | One paragraph explanation of the feature. 8 | 9 | # Motivation 10 | 11 | Why are we doing this? What use cases does it support? What is the expected 12 | outcome? 13 | 14 | # Detailed design 15 | 16 | This is the bulk of the RFC. Explain the design in enough detail for somebody 17 | familiar with the framework to understand, and for somebody familiar with the 18 | implementation to implement. This should get into specifics and corner-cases, 19 | and include examples of how the feature is used. Any new terminology should be 20 | defined here. 21 | 22 | # How We Teach This 23 | 24 | What names and terminology work best for these concepts and why? How is this 25 | idea best presented? As a continuation of existing Ember patterns, or as a 26 | wholly new one? 27 | 28 | Would the acceptance of this proposal mean the Ember guides must be 29 | re-organized or altered? Does it change how Ember is taught to new users 30 | at any level? 31 | 32 | How should this feature be introduced and taught to existing Ember 33 | users? 34 | 35 | # Drawbacks 36 | 37 | Why should we *not* do this? Please consider the impact on teaching Ember, 38 | on the integration of this feature with other existing and planned features, 39 | on the impact of the API churn on existing apps, etc. 40 | 41 | There are tradeoffs to choosing any path, please attempt to identify them here. 42 | 43 | # Alternatives 44 | 45 | What other designs have been considered? What is the impact of not doing this? 46 | 47 | # Unresolved questions 48 | 49 | Optional, but suggested for first drafts. What parts of the design are still 50 | TBD? 51 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | The Ember team and community are committed to everyone having a safe and inclusive experience. 2 | 3 | **Our Community Guidelines / Code of Conduct can be found here**: 4 | 5 | http://emberjs.com/guidelines/ 6 | 7 | For a history of updates, see the page history here: 8 | 9 | https://github.com/emberjs/website/commits/master/source/guidelines.html.erb 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember RFCs 2 | 3 | Many changes, including bug fixes and documentation improvements can be 4 | implemented and reviewed via the normal GitHub pull request workflow. 5 | 6 | Some changes though are "substantial", and we ask that these be put 7 | through a bit of a design process and produce a consensus among the Ember 8 | core team. 9 | 10 | The "RFC" (request for comments) process is intended to provide a 11 | consistent and controlled path for new features to enter the framework. 12 | 13 | [Active RFC List](https://github.com/emberjs/rfcs/pulls) 14 | 15 | ## When you need to follow this process 16 | 17 | You need to follow this process if you intend to make "substantial" 18 | changes to Ember, Ember Data or its documentation. What constitutes a 19 | "substantial" change is evolving based on community norms, but may 20 | include the following. 21 | 22 | - A new feature that creates new API surface area, and would 23 | require a [feature flag] if introduced. 24 | - The removal of features that already shipped as part of the release 25 | channel. 26 | - The introduction of new idiomatic usage or conventions, even if they 27 | do not include code changes to Ember itself. 28 | 29 | Some changes do not require an RFC: 30 | 31 | - Rephrasing, reorganizing or refactoring 32 | - Addition or removal of warnings 33 | - Additions that strictly improve objective, numerical quality 34 | criteria (speedup, better browser support) 35 | - Additions only likely to be _noticed by_ other implementors-of-Ember, 36 | invisible to users-of-Ember. 37 | 38 | If you submit a pull request to implement a new feature without going 39 | through the RFC process, it may be closed with a polite request to 40 | submit an RFC first. 41 | 42 | ## Gathering feedback before submitting 43 | 44 | It's often helpful to get feedback on your concept before diving into the 45 | level of API design detail required for an RFC. **You may open an 46 | issue on this repo to start a high-level discussion**, with the goal of 47 | eventually formulating an RFC pull request with the specific implementation 48 | design. 49 | 50 | ## What the process is 51 | 52 | In short, to get a major feature added to Ember, one must first get the 53 | RFC merged into the RFC repo as a markdown file. At that point the RFC 54 | is 'active' and may be implemented with the goal of eventual inclusion 55 | into Ember. 56 | 57 | * Fork the RFC repo http://github.com/emberjs/rfcs 58 | * Copy `0000-template.md` to `text/0000-my-feature.md` (where 59 | 'my-feature' is descriptive. don't assign an RFC number yet). 60 | * Fill in the RFC. Put care into the details: **RFCs that do not 61 | present convincing motivation, demonstrate understanding of the 62 | impact of the design, or are disingenuous about the drawbacks or 63 | alternatives tend to be poorly-received**. 64 | * Submit a pull request. As a pull request the RFC will receive design 65 | feedback from the larger community, and the author should be prepared 66 | to revise it in response. 67 | * Build consensus and integrate feedback. RFCs that have broad support 68 | are much more likely to make progress than those that don't receive any 69 | comments. 70 | * Eventually, the [core team] will decide whether the RFC is a candidate 71 | for inclusion in Ember. 72 | * RFCs that are candidates for inclusion in Ember will enter a "final comment 73 | period" lasting 7 days. The beginning of this period will be signaled with a 74 | comment and tag on the RFC's pull request. Furthermore, 75 | [Ember's official Twitter account](https://twitter.com/emberjs) will post a 76 | tweet about the RFC to attract the community's attention. 77 | * An RFC can be modified based upon feedback from the [core team] and community. 78 | Significant modifications may trigger a new final comment period. 79 | * An RFC may be rejected by the [core team] after public discussion has settled 80 | and comments have been made summarizing the rationale for rejection. A member of 81 | the [core team] should then close the RFC's associated pull request. 82 | * An RFC may be accepted at the close of its final comment period. A [core team] 83 | member will merge the RFC's associated pull request, at which point the RFC will 84 | become 'active'. 85 | 86 | ## The RFC life-cycle 87 | 88 | Once an RFC becomes active then authors may implement it and submit the 89 | feature as a pull request to the Ember repo. Becoming 'active' is not a rubber 90 | stamp, and in particular still does not mean the feature will ultimately 91 | be merged; it does mean that the core team has agreed to it in principle 92 | and are amenable to merging it. 93 | 94 | Furthermore, the fact that a given RFC has been accepted and is 95 | 'active' implies nothing about what priority is assigned to its 96 | implementation, nor whether anybody is currently working on it. 97 | 98 | Modifications to active RFC's can be done in followup PR's. We strive 99 | to write each RFC in a manner that it will reflect the final design of 100 | the feature; but the nature of the process means that we cannot expect 101 | every merged RFC to actually reflect what the end result will be at 102 | the time of the next major release; therefore we try to keep each RFC 103 | document somewhat in sync with the language feature as planned, 104 | tracking such changes via followup pull requests to the document. 105 | 106 | ## Implementing an RFC 107 | 108 | The author of an RFC is not obligated to implement it. Of course, the 109 | RFC author (like any other developer) is welcome to post an 110 | implementation for review after the RFC has been accepted. 111 | 112 | If you are interested in working on the implementation for an 'active' 113 | RFC, but cannot determine if someone else is already working on it, 114 | feel free to ask (e.g. by leaving a comment on the associated issue). 115 | 116 | ## Reviewing RFC's 117 | 118 | Each week the [core team] will attempt to review some set of open RFC 119 | pull requests. 120 | 121 | We try to make sure that any RFC that we accept is accepted at the 122 | Friday team meeting, and reported in [core team notes]. Every 123 | accepted feature should have a core team champion, who will represent 124 | the feature and its progress. 125 | 126 | **Ember's RFC process owes its inspiration to the [Rust RFC process]** 127 | 128 | [Rust RFC process]: https://github.com/rust-lang/rfcs 129 | [core team]: http://emberjs.com/team/ 130 | [feature flag]: http://emberjs.com/guides/contributing/adding-new-features/ 131 | [core team notes]: https://github.com/emberjs/core-notes/tree/master/ember.js 132 | -------------------------------------------------------------------------------- /deprecation-template.md: -------------------------------------------------------------------------------- 1 | - Start Date: (fill me in with today's date, YYYY-MM-DD) 2 | - RFC PR: (leave this empty) 3 | - Ember Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | One paragraph explanation of the deprecation. 8 | 9 | # Motivation 10 | 11 | Why are we doing this? What are the problems with the deprecated feature? 12 | What is the replacement functionality? 13 | 14 | # Transition Path 15 | 16 | This is the bulk of the RFC. Explain the use-cases that deprecated functionality 17 | covers, and for each use-case, describe the transition path. 18 | 19 | Describe it in enough detail for someone who uses the deprecated functionality 20 | to understand, for someone to write the deprecation guide, and for someone 21 | familiar with the implementation to implement. 22 | 23 | # How We Teach This 24 | 25 | Would the acceptance of this proposal mean the Ember guides must be 26 | re-organized or altered? Does it change how Ember is taught to new users 27 | at any level? 28 | 29 | Does it mean we need to put effort into highlighting the replacement 30 | functionality more? What should we do about documentation, in the guides 31 | related to this feature? 32 | 33 | How should this deprecation be introduced and explained to existing Ember 34 | users? 35 | 36 | # Drawbacks 37 | 38 | Why should we *not* do this? Please consider the impact on teaching Ember, 39 | on the integration of this feature with other existing and planned features, 40 | on the impact of the API churn on existing apps, etc. 41 | 42 | There are tradeoffs to choosing any path, please attempt to identify them here. 43 | 44 | # Alternatives 45 | 46 | What other designs have been considered? What is the impact of not doing this? 47 | 48 | # Unresolved questions 49 | 50 | Optional, but suggested for first drafts. What parts of the design are still 51 | TBD? 52 | -------------------------------------------------------------------------------- /text/0001-transform-attribute-meta-parameter.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2014-08-14 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/1 3 | - Ember Issue: https://github.com/emberjs/data/pull/4086 4 | 5 | # Summary 6 | 7 | For Ember Data. Pass through attribute meta data, which includes `parentType`, `options`, `name`, etc., 8 | to the transform associated with that attribute. This will allow provide the following function signiture updates to `DS.Transform`: 9 | 10 | * `transform.serialize(deserialized, attributeMeta)` 11 | * `transform.deserialize(serialized, attributeMeta)` 12 | 13 | # Motivation 14 | 15 | The main use case is to be able to configure the transform 16 | on a per-model basis making more DRY code. So the transform can be aware of type and options on `DS.attr` can 17 | be useful to configure the transform for DRY use. 18 | 19 | # Detailed design 20 | 21 | ## Implementing 22 | 23 | The change will most likely start in [`eachTransformedAttribute`][1], which gets the attributes for that instance via `get(this, 'attributes')`. In the `forEach` the `name` will be used to get the specific attribute, e.g. 24 | 25 | ```js 26 | var attributeMeta = attributes.get(name); 27 | callback.call(binding, name, type, attributeMeta); 28 | ``` 29 | 30 | The next change will be in [`applyTransforms`][2], where the `attributeMeta` parameter is added and passed to `transform.deserialize` as the second argument. 31 | 32 | You also have to handle the serialization part in [`serializeAttribute`][3], where you pass through the `attribute` parameter to `transform.serialize`. 33 | 34 | ## Using 35 | 36 | A convoluted example: 37 | 38 | ```js 39 | // Example based on https://github.com/chjj/marked library 40 | App.PostModel = DS.Model.extend({ 41 | title: DS.attr('string'), 42 | markdown: DS.attr('markdown', { 43 | markdown: { 44 | gfm: false, 45 | sanitize: true 46 | } 47 | }) 48 | }); 49 | 50 | App.TechnicalPostModel = DS.Model.extend({ 51 | title: DS.attr('string'), 52 | gistUrl: DS.attr('string'), 53 | markdown: DS.attr('markdown', { 54 | markdown: { 55 | gfm: true, 56 | tables: true, 57 | sanitize: false 58 | } 59 | }) 60 | }); 61 | 62 | App.MarkdownTransform = DS.Transform.extend({ 63 | serialize: function (deserialized, attributeMeta) { 64 | return deserialized.raw; 65 | }, 66 | 67 | deserialize: function (serialized, attributeMeta) { 68 | var options = attributeMeta.options.markdown || {}; 69 | 70 | return marked(serialized, options); 71 | } 72 | }); 73 | ``` 74 | 75 | # Drawbacks 76 | 77 | Extra API surface area, although not much. This could also potentially introduce tight coupling between models and transforms if used improperly, e.g. not returning a default value if using type checking. 78 | 79 | # Alternatives 80 | 81 | 1. Passing the information from the server, which is a poor solution. 82 | 2. Writing a new transform for each model/attribute that needs a variation. Although this might be a good solution sometimes if you extend a base transform. 83 | 84 | # Unresolved questions 85 | 86 | Does the whole meta object need to be passed, or do we selectively pass in only the useful properties? Like 87 | `options` and `parentType` and `name`.. 88 | 89 | 90 | 91 | [1]: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/attributes.js#L193 92 | [2]: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/json_serializer.js#L117 93 | [3]: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/json_serializer.js#L528 94 | -------------------------------------------------------------------------------- /text/0003-block-params.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2014-08-18 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/3 3 | - Issues: 4 | - Ember Stream support: emberjs/ember.js#5522 5 | - Handlebars parser support: wycats/handlebars.js#906 6 | - HTMLBars compiler support: tildeio/htmlbars#147 7 | 8 | # Summary 9 | 10 | Introduce block parameters to the Handlebars language to standardize context-preserving helpers, for example: 11 | 12 | ```handlebars 13 | {{#each people as |person|}} 14 | {{person.name}} 15 | {{/each}} 16 | ``` 17 | 18 | # Motivation 19 | 20 | ### The Problem 21 | 22 | There is no idiomatic way to write a helper that preserves context and yields values to its template. This is particularly painful for components which have strict context-preserving semantics. 23 | 24 | ### Current workarounds 25 | 26 | - Don't write components that need to yield a value. 27 | - *Problem:* This may not be an option. 28 | - Invent a non-standard per-helper syntax (like `{{#with foo as bar}}` or `{{#each item in items}}`) that hook into the undocumented `keywords` to inject variables. 29 | - *Problems:* Custom syntaxes are not in the spirit of the Handlebars language and require the consumer to know the special incantation. Component authors must an non-trivial understanding of how `keywords` work. 30 | 31 | ### New possibilities 32 | 33 | ```handlebars 34 | {{#for-each obj as |key val|}} 35 | {{key}}: {{val}} 36 | {{/for-each}} 37 | ``` 38 | 39 | ```handlebars 40 | {{#form-for post as |f|}} 41 | {{f.input "title"}} 42 | {{f.textarea "body"}} 43 | {{f.submit}} 44 | {{/form-for}} 45 | ``` 46 | 47 | 48 | # Detailed design 49 | 50 | - Phase 1: Add block params to the Handlebars language 51 | - Phase 2: Rewrite Ember's helpers to accept streams 52 | - Phase 3: Add block param support to `{{each}}` and `{{with}}` 53 | 54 | ### Phase 1: Add block params to the Handlebars language 55 | 56 | The proposed syntax is `{{#x-foo a b w=x y=z as |param1 param2 ... paramN|}}` and is only available for block helpers. 57 | 58 | The names of the block parameters are compiled into the inner template, but are not known to the helper (`x-foo` in the example above). To call a template and populate its block params we use the arguments option: 59 | 60 | 61 | ```javascript 62 | var template = compile('{{person.name}}', { 63 | blockParams: [ 'person' ] 64 | }); 65 | 66 | template({}, ..., [ personModel ]); 67 | ``` 68 | 69 | More commonly, block params will be defined inside of the template. 70 | 71 | ``` 72 | {{#with currentPost.author as |a|}} 73 | {{a.name}} {{a.email}} 74 | {{/with}} 75 | ``` 76 | 77 | ```javascript 78 | registerHelper('with', function(object, options) { 79 | return options.fn(this, ..., [ object ]); 80 | }); 81 | ``` 82 | 83 | For compatibility reasons, the *number of block params* are passed to the helper so that the pre-block-params behaviour of the helper can be preserved. Example: 84 | 85 | ```javascript 86 | function eachHelper(..., options) { 87 | if (options.blockParamsLength > 0) { /* do new behaviour */ } 88 | else { /* do old behaviour */ } 89 | } 90 | ``` 91 | 92 | ### Phase 2: Rewrite Ember's helpers to accept streams 93 | 94 | In the `with` example above, if the `currentPost` changes the `a` block param should update. This means it's not sufficient to pass only the initial value of the author in the arguments. Instead, we pass a stream which emits values whenever the observed property changes. 95 | 96 | In Handlebars, a block param can appear anywhere that an identifier can, for example `{{log a.name}}`. This means that all helpers would need to be modified to understand streams. 97 | 98 | ### Phase 3: Add block param support to `{{each}}` and `{{with}}` 99 | 100 | Deprecate context-changing and ad-hoc keyword flavors of `{{each}}` and `{{with}}` in favor of block params. 101 | 102 | # Drawbacks 103 | 104 | - Handlebars already has a similar notion of with `data` which can lead to confusion. 105 | 106 | # Alternatives 107 | 108 | To my knowledge, no other designs have been considered. Not implementing this feature would mean that components would continue to be difficult to compose. 109 | 110 | # Unresolved questions 111 | 112 | The associated HTML syntax for HTMLBars needs to be finalized. 113 | -------------------------------------------------------------------------------- /text/0010-engines.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2014-10-24 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/10 3 | - Ember Issue: https://github.com/emberjs/ember.js/pull/12685 4 | 5 | # Summary 6 | 7 | Engines allow multiple logical applications to be composed together into a 8 | single application from the user's perspective. 9 | 10 | # Motivation 11 | 12 | Large companies are increasingly adopting Ember.js to power their entire 13 | product lines. Often this means separate teams (sometimes distributed 14 | around the world) working on the same app. Typically, responsibility is 15 | shared by dividing the application into one or more "sections". How this 16 | division is actually implemented varies from team to team. 17 | 18 | Sometimes, each "section" will be a completely separate Ember app, with 19 | a shared navigation bar allowing users to switch between each app. This 20 | allows teams to work quickly without stepping on each others' toes, but 21 | switching apps feels slow (especially compared to the normally speedy 22 | route transitions in Ember) because the entire page must be thrown out, 23 | then an entirely new set of the same assets downloaded and parsed. 24 | Additionally, code sharing is largely accomplished via copy-and-paste. 25 | 26 | Other times, the separation is enforced socially, with each team 27 | claiming a section of the same app in the same repository. 28 | Unfortunately, this approach leads to frequent conflicts around shared 29 | resources, and feedback from tests gets slower and slower as test suites 30 | grow in size. 31 | 32 | A more modular approach is to break off elements of a single application into 33 | separate [addons](http://www.ember-cli.com/user-guide/#addons). Addons are 34 | essentially mixins for [ember-cli](http://www.ember-cli.com/) applications. In 35 | other words, the elements of an addon are merged with those of the application 36 | that includes them. While addons allow for distributed development, testing, and 37 | packaging, they do not provide the logical run-time separation required for 38 | developing completely independent "sections" of an application. Addons must 39 | function within the namespace, registry, and router of the application in which 40 | they are included. 41 | 42 | Engines provide an alternative to these approaches that allows for distributed 43 | development, testing, and packaging, _as well as_ logical run-time separation. 44 | Because engines are derived from applications, they can be just as 45 | full-featured. Each has its own namespace and registry. Even though engines are 46 | isolated from the applications that contain them, the boundaries between them 47 | allow for controlled sharing of resources. 48 | 49 | Engines can be either "routable" or "route-less": 50 | 51 | * Routable engines provide a routing map which can be integrated with the 52 | routing maps of parent applications or engines. Routing maps are alway eager 53 | loaded, which allows for deep linking into an engine's routes regardless of 54 | whether the engine itself has been instantiated. 55 | 56 | * Route-less engines can isolate complex functionality that is not related to 57 | routing (e.g. a chat engine in a sidebar). Route-less engines can be rendered 58 | into outlets ad hoc as routes are loaded. 59 | 60 | The potential scope of engines is large enough that this feature merits 61 | development and delivery in multiple phases. A minimum viable version could be 62 | released sooner, which could be augmented with more advanced features later. 63 | 64 | An initial release of engines could provide the following benefits: 65 | 66 | * Distributed development - Engines can be developed and tested in isolation 67 | within their own Ember CLI projects and included by applications or other 68 | engines. Engines can be packaged and released as addons themselves. 69 | 70 | * Integrated routing - Support for mounting routable engines in the routing maps 71 | of applications or other engines. 72 | 73 | * Ad hoc embedding - Support for embedding route-less engines in outlets as 74 | needed. 75 | 76 | * Clean boundaries - An engine can cooperate with its parents through a few 77 | explicit interfaces. Beyond these interfaces, engines and applications are 78 | isolated. 79 | 80 | Subsequent releases of engines could allow for the following: 81 | 82 | * Lazy loading - An engine could allow its parent to boot with only its routing 83 | map loaded. The rest of the engine could be loaded only as required (i.e. 84 | when a route in an engine is visited). This would allow applications to boot 85 | faster and limit their memory consumption. 86 | 87 | * Namespaced access to engine resources from applications - This could open up 88 | the potential for applications to use, and extend, an engine's resources much 89 | like resources in other addons, but without the possibility of namespace 90 | collisions. 91 | 92 | ## Detailed design 93 | 94 | Engines are very similar to regular applications: they can be developed in 95 | isolation in Ember CLI, include addons, and contain all the same elements, 96 | including routes, components, initializers, etc. The primary differences are 97 | that an engine does not boot itself and an engine does not control the router. 98 | 99 | ### Engine internals 100 | 101 | New `Engine` and `EngineInstance` classes will be introduced. 102 | 103 | Applications and engines will share ancestry. It remains TBD whether 104 | applications will subclass engines, or whether a common ancestor will be 105 | introduced. 106 | 107 | Engines and applications will share the same pattern for registry / container 108 | ownership and encapsulation. Both will also have initializers and instance 109 | initializers. 110 | 111 | Engine instances will have access to their parent instances. An engine's parent 112 | could be either an application or engine. 113 | 114 | #### Routable vs. route-less engines 115 | 116 | Routable engines will define their routes in a new `Ember.Routes` class. This 117 | class will encapsulate the functionality provided by `Router#map`, and will be 118 | used internally by `Ember.Router` as well (with no public interface changes of 119 | course). 120 | 121 | Route-less engines do not define routing maps nor can they contain routes. 122 | 123 | ### Developing engines 124 | 125 | Engines can be developed in isolation as Ember CLI addon projects or as part of 126 | a parent application. 127 | 128 | #### Engines as addons 129 | 130 | Engines can be created as separate addon projects with: 131 | 132 | ``` 133 | ember engine 134 | ``` 135 | 136 | This will create a special form of an ember addon. The file structure will match 137 | that of a standard addon, but will have an `engine` directory instead of an 138 | `addon` directory. 139 | 140 | Engines can be unit tested and can also be integration tested within a dummy 141 | app, just like standard addons. 142 | 143 | #### In-repo engines 144 | 145 | An engine can be created within an existing application's project using a 146 | special `in-repo-engine` generator (similar to the `in-repo-addon` generator): 147 | 148 | ``` 149 | ember g in-repo-engine 150 | ``` 151 | 152 | In-repo engines can be unit tested in isolation or integration testing with the 153 | main application (instead of a dummy application). 154 | 155 | > Note: In-repo addons currently are created in the `/lib` directory (e.g. 156 | `/lib/my-addon`). Unit tests and integration tests are currently co-mingled with 157 | tests for the main application. It's recommended that in-repo engines provide 158 | better test separation than is provided for regular addons, and perhaps the 159 | whole in-repo addon directory structure should be re-examined at the same time 160 | in-repo engines are introduced. 161 | 162 | #### Engine directory structure 163 | 164 | An engine's directory will contain a file structure identical to the `app` 165 | directory in a standard ember-cli application, with the following exceptions: 166 | 167 | * `engine.js` instead of `app.js` - defines the `Engine` class and 168 | loads its initializers. 169 | 170 | * `routes.js` instead of `router.js` - defines an engine's routing map in a 171 | `Routes` class. This file should be deleted entirely for route-less engines. 172 | 173 | ### Installing engines 174 | 175 | Engines developed as addons can be installed in an application just like any 176 | other addon: 177 | 178 | ``` 179 | ember install 180 | ``` 181 | 182 | During development, you can use `npm link` to make your engine available in 183 | another parent engine or application. 184 | 185 | ### Mounting routable engines 186 | 187 | The new `mount()` router DSL method is used to mount an engine at a particular 188 | "mount-point" in a route map. 189 | 190 | For example, the following route map mounts the `discourse` engine at the 191 | `/forum` path: 192 | 193 | ``` 194 | Router.map(function() { 195 | this.mount('discourse', {path: '/forum'}); 196 | }); 197 | ``` 198 | 199 | > Note: If unspecified, `path` will match the name of the engine. 200 | 201 | Calls to `mount` can be nested within routes. An engine can be mounted at 202 | multiple routes, and each will represent a new instance of the engine to be 203 | created. 204 | 205 | ### Mounting route-less engines 206 | 207 | A `mount()` DSL will also be added to routes, which will enable embedding of 208 | route-less engines in outlets. This can be called from `renderTemplate` (or 209 | `renderComponents` once routable components are introduced). 210 | 211 | `mount` has a similar signature to `render`, although it is obviously 212 | engine-specific instead of template-specific. `mount` can be used to specify 213 | a target template and outlet as follows: 214 | 215 | ``` 216 | renderTemplate: function() { 217 | // Mount the chat engine in the sidebar 218 | this.mount('chat', { 219 | into: 'main', 220 | outlet: 'sidebar' 221 | }); 222 | } 223 | ``` 224 | 225 | As a result, the engine's `application` template will be rendered into the 226 | `sidebar` outlet in the application's `main` template. 227 | 228 | ### Loading phases 229 | 230 | Engines can exist in several phases: 231 | 232 | * Booted - an engine that's been installed in a parent application will have 233 | its dependencies loaded and its (non-instance) initializers invoked when the 234 | parent application boots. 235 | 236 | * Mounted - Routable and route-less engines have slightly different concepts of 237 | "mounting". A routable engine is considered mounted when it has been included 238 | by a router at one or more mount-points. A route-less engine is considered 239 | mounted as soon as a route's `mount` call resolves. 240 | 241 | * Instantiated - When an engine is instantiated, an `EngineInstance` is created 242 | and an engine's instance initializers are invoked. A routable engine is 243 | instantiated when a route is visited at or beyond its mount-point. A 244 | route-less engine is instantiated as soon as it is mounted. 245 | 246 | Special `before` and `after` hooks could be added to application instance 247 | initializers that allow them to be ordered relative to engine instance 248 | initializers. 249 | 250 | ### Engine boundaries 251 | 252 | Besides its routing map, an engine does not share any other resources with its 253 | parent by default. Engines maintain their own registries and containers, which 254 | ensure that they stay isolated. However, some explicit sharing of resources 255 | between engines and parents is allowed. 256 | 257 | #### Engine / parent dependencies 258 | 259 | Dependencies between engines and parents can be defined imperatively or 260 | declaratively. 261 | 262 | Imperative dependencies can be defined in an engine's instance initializers. 263 | When an engine is instantiated, the `parent` property on its `EngineInstance` is 264 | set to its parent instance (either an `ApplicationInstance` or 265 | `EngineInstance`). Since the engine instance is available in the instance 266 | initializer, this `parent` property can also be accessed. This allows an engine 267 | instance to interrogate its parent, specifically through its `RegistryProxy` and 268 | `ContainerProxy` interfaces. 269 | 270 | Alternatively, declarative dependencies can be defined on a limited basis. The 271 | initial API will be limited: an engine can define an array of `services` that it 272 | requires from its parent. 273 | 274 | For example, the following engine expects its parent to provide `store` and 275 | `session` services: 276 | 277 | ``` 278 | import Ember from 'ember'; 279 | 280 | var Engine = Ember.Engine.extend({ 281 | dependencies: { 282 | services: [ 283 | 'store', 284 | 'session' 285 | ] 286 | } 287 | }); 288 | 289 | export default Engine; 290 | ``` 291 | 292 | The parent application can provide a re-mapping of services from its namespace 293 | to that of the engine via an `engines` declaration. 294 | 295 | In the following example, the application shares its `store` service directly 296 | with the `checkout` engine. It also shares its `current-user` service as the 297 | `session` service requested by the engine. 298 | 299 | ``` 300 | import Ember from 'ember'; 301 | 302 | var App = Ember.Application.extend({ 303 | engines: { 304 | checkout: { 305 | dependencies: { 306 | services: [ 307 | 'store', 308 | {session: 'current-user'} 309 | ] 310 | } 311 | } 312 | } 313 | }); 314 | 315 | export default App; 316 | ``` 317 | 318 | When engines are instantiated, the listed dependencies will be looked up on 319 | the parent and made accessible within the engine. 320 | 321 | Note that the `engines` declaration provides further space to define 322 | characteristics about an engine, such as whether it should be eager or 323 | lazy-loaded, URLs for manifest files, etc. 324 | 325 | # Drawbacks 326 | 327 | This RFC introduces the new concept of engines, which increases the 328 | learning curve of the framework. However, I believe this issue is 329 | mitigated by the fact that engines are an opt-in packaging around 330 | existing concepts. 331 | 332 | In the end, I believe that "engines" are just a small API for composing 333 | existing concepts. And they can be introduced at the top of the 334 | conceptual ladder, once users are comfortable with the basics of Ember, 335 | and only for those working on large teams or distributing addons. 336 | 337 | # Alternatives 338 | 339 | Several incomplete alternatives are discussed in the Motivations section above. 340 | 341 | I know of no alternatives being discussed in the Ember community that meet the 342 | same needs as engines; namely, for development _and_ run-time isolation. 343 | 344 | # Unresolved questions 345 | 346 | ## Non-CLI Users 347 | 348 | This RFC assumes Ember CLI. I would prefer to prove this out in Ember 349 | CLI before locking down the public APIs/hooks the router exposes for 350 | locating and mounting engines. Once this is done, however, we should 351 | expose and document those hooks so users who cannot use Ember CLI for 352 | whatever reason can still take advantage of composability. 353 | 354 | ## Declarative dependencies 355 | 356 | The initial scope of declarative dependency sharing is limited in scope to 357 | services. Should other types of dependencies be declaratively shareable? 358 | Should addons be the recommended path to share all other dependencies? 359 | 360 | ## Async mounting of route-less engines 361 | 362 | `Route#renderTemplate` is called synchronously, although `Route#mount` should 363 | surely be async. How async mounting is represented in the route lifecycle is 364 | TBD. A solution isn't proposed here because the problem is shared by routable 365 | and async components, and a common solution should be reached. 366 | 367 | ## Lazy loading manifests 368 | 369 | In order to facilitate lazy loading of engines, we will need to determine a 370 | structure for manifest files that contain an engine's assets. Furthermore, an 371 | application will need to be configurable with URLs for these manifests. 372 | 373 | It's likely that an engine's routing map will always be needed at the time of 374 | application deployment. Allowing lazy loading of routing maps would prevent the 375 | formation of any links from a parent application into an engine's routes. 376 | 377 | When developed in isolation as addons, engines will have their own sets of 378 | dependencies. These dependencies will be treated like any other addons when 379 | engines are deployed together with an application. However, in order to support 380 | lazy loading, it would be ideal to dedupe dependencies in order to create a lean 381 | and conflict-free asset manifest. 382 | 383 | Reference: deduping strategy discussed by @wycats in 384 | [this Google doc](https://docs.google.com/a/tomdale.net/document/d/12CsR-zli5oP2TDWOef_-D28zjmbVD83hU4q9_VTk-9s/edit). 385 | 386 | ## Namespaced access to engine resources 387 | 388 | The concept of namespaced access to engine resources is mentioned above as a 389 | potential goal of a future release of engines. This will require further 390 | discussion to decide how it should work both technically and semantically, and 391 | how it applies to lazy-loaded engines. 392 | 393 | If these problems can be resolved, this feature would allow for more flexibility 394 | in parent / engine interactions. Instead of just allowing engines to look up 395 | resources in a parent, the inverse could also be allowed. 396 | 397 | For example, if the `authentication` engine contains 398 | `engines/authentication/models/user.js`, a parent application could look up this 399 | same model through a namespace. Perhaps as follows: 400 | 401 | ```js 402 | container.lookup('authentication@model:user'); 403 | ``` 404 | 405 | Other APIs in Ember would need to be extended to support namespaces to 406 | take full advantage of this feature. For example, components that ship 407 | with an engine might be accessed from the primary application like this: 408 | 409 | ```handlebars 410 | {{authentication@login-form obscure-password=true}} 411 | ``` 412 | -------------------------------------------------------------------------------- /text/0011-improved-cp-syntax.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2014-09-30 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/11 3 | - Ember Issue: https://github.com/emberjs/ember.js/pull/9527 4 | 5 | # Summary 6 | 7 | Improve computed property syntax 8 | 9 | # Motivation 10 | 11 | Today, the setter variant of CP's is both confusing, and looks scary as sin. 12 | (Too many concepts must be taught and it is too easy to screw it up.) 13 | 14 | # Detailed design 15 | 16 | today: 17 | ------ 18 | 19 | ```js 20 | fullName: Ember.computed('firstName', 'lastName', function(key, value) { 21 | if (arguments.length > 1) { 22 | var names = value.split(' '); 23 | this.setProperties({ 24 | firstName: names[0], 25 | lastName: names[1] 26 | }); 27 | return value; 28 | } 29 | 30 | return this.get('firstName') + ' ' + this.get('lastName'); 31 | }); 32 | ``` 33 | 34 | Tomorrow: 35 | --------- 36 | 37 | ```js 38 | fullName: Ember.computed('firstName', 'lastName', { 39 | get: function(keyName) { 40 | return this.get('firstName') + ' ' + this.get('lastName'); 41 | }, 42 | 43 | set: function(keyName, fullName, oldValue) { 44 | var names = fullName.split(' '); 45 | 46 | this.setProperties({ 47 | firstName: names[0], 48 | lastName: names[1] 49 | }); 50 | 51 | return fullName; 52 | } 53 | }); 54 | ``` 55 | 56 | 57 | Notes: 58 | ------ 59 | 60 | * we should keep `Ember.computed(fn);` as shorthand for getter only 61 | * `get` xor `set` variants would also be possible. 62 | * `{ get() { } }` is es6 syntax for `{ get: function() { } )` 63 | 64 | Migration 65 | --------- 66 | 67 | * 1.x support both, detect new behaviour by testing if the last arg is not null and typeof object 68 | * 1.x+1 deprecate if last arg is a function and its arity is greater than 1 69 | 70 | 71 | # Drawbacks 72 | 73 | N/A 74 | 75 | # Alternatives 76 | 77 | N/A 78 | 79 | # Unresolved questions 80 | 81 | None 82 | -------------------------------------------------------------------------------- /text/0024-bound-attributes.md: -------------------------------------------------------------------------------- 1 | - 2014-11-26 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/24 3 | - Ember Issue: 4 | 5 | # Summary 6 | 7 | Unlike Handlebars, HTMLBars parses HTML as it parses a template. 8 | Bound attributes are one syntax now possible. 9 | 10 | For example, this variable `color` is bound to set a class: 11 | 12 | ```hbs 13 |
14 | ``` 15 | 16 | Though traditional HTML attribute syntax should be preserved (using 17 | `class` and not `className` for example), the default path will be 18 | to set attributes as properties on the DOM node. 19 | 20 | However this happy path has several important exceptions, and results 21 | in a few strange edge cases. This rfc will go into detail about the 22 | expected behavior without talking about the implementation of attribute 23 | on the Ember rendering pipeline. 24 | 25 | # Motivation 26 | 27 | `{{bind-attr` is a verbose syntax and difficult for new developers to 28 | understand. 29 | 30 | # Detailed design 31 | 32 | Given a use of bound attributes: 33 | 34 | ```hbs 35 | 36 | ``` 37 | 38 | There are three important inputs: 39 | 40 | * The element (`tagName`, `namespaceURI`) 41 | * The attribute name 42 | * The value (literal or stream) 43 | 44 | The following described the algorithm for updating the attribute/property 45 | value on an element. 46 | 47 | 1. If the element has an SVG namespace, use `setAttribute`. Setting SVG attributes 48 | as properties is not supported. 49 | 2. If the attribute name is `style`, use `setAttribute`. 50 | 3. Normalize the property name as described in `propertyNameFor` below. If a normalized 51 | name is returned, set that property on the element (`element[normalizedPropName]`). 52 | If it is not returned, set with `setAttribute`. 53 | 54 | `propertyNameFor` is a normalization setup for attribute names that takes the element 55 | and attribute name as input. 56 | 57 | 1. Build a list of normalized properties for the passed element `normalizedAttrs[element.tagName][elementAttrName.toLowerCase()] = elementAttrName` 58 | 2. Fetch the normalized property name from this list `normalizedAttr = normalizedAttrs[element.tagName][passedAttrName.toLowerCase()]` 59 | 3. Return this normalized attr. If an `attrName` is did not normalize to a property (for example `class`), null is returned 60 | 61 | ### Acknowledged edge cases 62 | 63 | * Boolean attrs with blank string won't work like they would in HTML: `` would be false 64 | * Some selectors may not work as expected. `` will not result in a working `[value=red]` selector 65 | 66 | # Drawbacks 67 | 68 | None. 69 | 70 | # Alternatives 71 | 72 | Two obvious alternatives considered in detail are Angular and React. 73 | 74 | In **Angular 2.0**, [a new prop/attr/event syntax](http://www.beyondjava.net/blog/angularjs-2-0-sneak-preview-data-binding/) 75 | is being introduced. 76 | 77 | Setting an attribute just like setting an HTML attribute: 78 | 79 | ```html 80 | 81 | ``` 82 | 83 | Properties are flagged with the `[]` syntax: 84 | 85 | ```html 86 | 87 | ``` 88 | 89 | Angular is limited by it's HTML templating here. The value must be quoted 90 | to have complex content, where as in HTMLBars it is easier to bend the 91 | rules to introduce literal values: `disabled={{controller.isInputDisabled}}`. 92 | 93 | Events are out of our immediate purview in this RFC, but for completeness 94 | note Angular's syntax: 95 | 96 | ```html 97 | 98 | ``` 99 | 100 | **React's JSX** has its own [property syntax](http://facebook.github.io/react/docs/jsx-in-depth.html), 101 | one that diverges from traditional HTML by focusing entirely on properties 102 | instead of attributes. This means the templates are well prepared for 103 | use with components, but also that JSX must maintain a large whitelist of 104 | special cases such as [supported tags](http://facebook.github.io/react/docs/tags-and-attributes.html) 105 | and [some HTML attributes](http://facebook.github.io/react/docs/jsx-gotchas.html). 106 | 107 | In general we would prefer to have Ember templates be as close to HTML 108 | as possible, without requiring developers to learn a new set of property 109 | names replacing the attribute names they already know. 110 | 111 | # Unresolved questions 112 | 113 | * How do we deal with `on*` attributes? 114 | * Should we do anything special about generic element properties like `
`? 115 | * Should HTMLBars unbound attributes use the same alorithm? 116 | 117 | There is a spike of significant depth [in PR #9721](https://github.com/emberjs/ember.js/pull/9721) 118 | and a followup [in PR #9977](https://github.com/emberjs/ember.js/pull/9977). 119 | -------------------------------------------------------------------------------- /text/0045-internet-explorer.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2015-06-07 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/45 3 | - Ember Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | Solicit feedback about the support timeframe for Internet Explorer 8 and Internet Explorer 9. 8 | 9 | # Motivation 10 | 11 | As Ember heads towards version 2.0, it is a good time to evaluate our browser support matrix. Ember follows Semantic Versioning, and we consider browser compatibility to be under the umbrella of those guarantees. In other words, we will continue to support whatever browsers we officially support in Ember 2.0 until Ember 3.0. 12 | 13 | Ember 1.x did not have an official browser support matrix, but we would like to correct this for Ember 2.0. 14 | 15 | We want to make this decision on the basis of the browsers that our community still needs to support, while weighing that against the costs we bear as a community to support older browsers. This RFC will lay out some of those costs, so we can decide what tradeoff is most appropriate. 16 | 17 | Members of the core team maintain many different kinds of apps across many different kinds of companies. Some of us work on applications with small, agile teams, while others work inside of large corporations with many engineers. When this topic came up amongst the team, we discovered that, across all these different companies and Ember apps, no one was still supporting IE8. 18 | 19 | Because of this, the core team's impression is that the costs of IE8 support now far exceed the benefits, and we are considering dropping support 20 | for IE8 in Ember 2.0. Before we make the decision, we want to hear from the rest of the community. Supporting IE8 incurs significant cost, both in terms of features and maintenance, and we want the community to help us think through the cost-benefit analysis. 21 | 22 | Ember is more than just the framework's code. When people use Ember, they expect to be able to use Ember's tooling, read Ember's documentation, find solutions to problems on Stack Overflow, and read tutorials produced by community members. All of these are shackled to the limitations of IE8, and by dropping support for IE8, people can begin to rely on the improved baseline of ES5. 23 | 24 | Below, we outline the costs of continuing to support IE8, so that you can help us make a considered decision. 25 | 26 | # Detailed design 27 | 28 | ## IE8 29 | 30 | ### Eliminate `get()` 31 | 32 | Currently, accessing properties on an Ember object requires using the `.get()` method. By using this abstraction, we have been able to implement several powerful features, such as proxies and computed properties, even on older browsers like IE8 that lack getters and setters. 33 | 34 | However, ECMAScript 5, which shipped in **2009**, added support for getters to JavaScript itself, and we would like to use this feature in Ember to eliminate the explicit calls to `get`. Developers new to the framework tell us that having to remember to use `.get()` is a big source of confusion. More seasoned developers get used to it, but moving the Ember object model closer to the pure JavaScript object model is a major goal for Ember 2.x. While many of the features of ES6 classes can be transpiled, getters and setters require engine support, and could not be used if we needed to support IE8. 35 | 36 | ### More ES6 Features, Today 37 | 38 | While much of ES6 can be transpiled correctly to ES3 (the version of JavaScript included with IE8), transpiling ES6 modules and classes requires `defineProperty`. 39 | 40 | Continued support for IE8 limits our ability to adopt new ES6 features in the internals of Ember, and to talk about them in our documentation. 41 | 42 | One example: In ES6, classes define their methods as non-enumerable properties. Transpiling this to existing browsers is only possible with `defineProperty`, which is not included in IE8. Trying to transpile ES6 classes to work on IE8 would lead to apps exhibiting subtly different behavior that would be painful to debug. IE8 users would discover that the larger Ember ecosystem was incompatible with their apps in hard-to-predict ways, and we think the ecosystem is one of the biggest advantages Ember offers. 43 | 44 | In other words, we don't think we can make the full transition to JavaScript classes a first-class part of the Ember experience if we still support IE8. As we did with modules, we would like to move more of our core to JavaScript features in the future, which would be significantly stymied by the lack of `defineProperty` in IE8. 45 | 46 | ### Remove the jQuery Dependency 47 | 48 | For its entire lifetime, Ember has relied on jQuery to smooth the rough edges of browser compatibility when interacting with the DOM. When people think about that dependency, they often assume that we could just replace calls to things like `.attr` with their more verbose DOM counterpart and call it a day. 49 | 50 | jQuery does more than just patch over IE8 rough spots; it also serves as the central place for normalizing behavior that can differ significantly across browsers. If we tried to pick-and-choose pieces of jQuery to pull into Ember, we would also be responsible for backporting any changes made to jQuery. We'd rather just rely on jQuery directly; that's what dependencies are for. 51 | 52 | The jQuery dependency has helped us with a few cross-browser areas: 53 | 54 | * Portable `DOMContentLoaded` (via `jQuery.ready`) 55 | * Support for event delegation across a wide variety of events. 56 | * Attribute and property normalization, which has already been implemented by HTMLBars 57 | * HTML parsing, which has also been implemented by HTMLBars 58 | 59 | Of these, proper support for event delegation is the largest remaining reason to rely on jQuery. IE9's support for the capture phase of events makes it simpler to support event delegation properly across all event types without a normalization layer. 60 | 61 | ### Support More Event Types 62 | 63 | Many newly specified events in the web platform (such as the media events) do not bubble, which is a problem for frameworks like Ember that rely on event delegation. However, the capture API, which was added in IE9, is invoked properly for all events, and does not require a normalization layer. Not only would supporting the capture API allow us to drop the jQuery dependency, but it would allow us to properly handle these non-bubbling events. This would allow you to use events like `playing` in your components without having to manually set up event listeners. 64 | 65 | ### CSS Improvements in Ember 2.x 66 | 67 | Today, the main Ember framework does very little to directly help with CSS. We expect that to change in the 2.x series, as we explore ways to help tame the CSS beast. 68 | 69 | However, a number of important CSS features landed in IE9: CSS3 selectors, full support for `querySelectorAll`, `getComputedStyle`, `calc()` to name a few. Productively tackling the CSS problem without these features would be like fighting with both hands tied behind our backs, and it may be impossible for us to robustly tackle the problem until Ember 3.0 if we needed to continue to support IE8. 70 | 71 | While it may be theoretically possible to implement some form of this feature in IE8, it is likely that the cost of doing so in a backwards-compatible way would significantly add to development time; perhaps so significantly it would be better to wait until we drop support for IE8 than attempt to bolt it on to a browser released half a decade ago. 72 | 73 | ### Maintenance Costs 74 | 75 | While it's very easy to weigh the costs of features that we could not implement at all due to IE8, there is a much more pernicious cost that is harder to see. 76 | 77 | Support for IE8 adds costs, sometimes significant, to every new feature we work on. For example, broken support for text nodes in IE8 significantly impeded early work on Glimmer. Every new area of work requires budgeting a significant amount of time for IE8 support. 78 | 79 | This is not surprising. When asked many years ago what jQuery could do when IE6 was gone, John Resig replied that we would gain little from dropping IE6, and that the benefits would not come until jQuery could drop IE8, the last version of IE featuring the bugs that made IE6 so difficult to develop for. 80 | 81 | Quite often, we will assume that a feature is ready to ship, and only discover subtle issues in IE8 very close to the release once it has been tested. We estimate that support for legacy Internet Explorer slowed down the development of HTMLBars by 2x. 82 | 83 | In short, we would be able to implement more features more quickly without the burden of bugs that were first introduced 15 years ago. 84 | 85 | ## What About IE9? 86 | 87 | In the first decade of 2000, browsers were updated very slowly, and every new release took a long time to be supplanted by the next release. As the last version of Internet Explorer supported by Windows XP, IE8 is a relic of this bygone era. In contrast, IE9 usage was quickly supplanted by IE10, and that pattern continues with IE11. 88 | 89 | The public trackers have IE9 at a lower share of total usage than IE8, so it might be worth considering dropping them together. Our decision for Ember 2.0 will likely hold until late 2016, so it's worth considering more than just the current moment when making the decision. 90 | 91 | While IE9 added support for the ES5 features we need to move into the future for JavaScript, IE10 added support for the last great wave of web features. Here is a sampling: 92 | 93 | * Flexbox and Grid Layout 94 | * Offline storage (IndexedDB, File, Blob) 95 | * Web Workers 96 | * Typed Arrays 97 | * Web Sockets 98 | * App Cache 99 | * History API 100 | 101 | Several of these features are required for asm.js, and in total, they make the web platform a capable application runtime. While we don't have any immediate plans to take advantage of these web features right now, the best experiments that people are doing today rely on them. By assuming IE10 as the baseline across the entire ecosystem, we would be able to do much more aggressive experimentation on the web platform. 102 | 103 | # Drawbacks 104 | 105 | Many users have told us that they chose Ember because of the community's commitment to backwards compatibility. When we announced in early 2014 that we would continue to support IE8 for at least another year, other libraries and frameworks had already dropped support. That being said, there will always be organizations using Ember that exist on the tail-end of browser adoption patterns. We risk alienating or upsetting those users by dropping support for a browser that, while on the way out, is not yet completely gone. 106 | 107 | However, in many cases, the requirement of IE8 support is driven by non-technical management who do not have a strong sense of the experience of using apps in IE8. In practice, many applications are not rigorously tested in older browsers, and the performance of IE8 is so bad that applications written using any framework perform poorly. Techniques that framework and application developers use to make Chrome fast quite often have pathological characteristics on browsers with a DOM and JavaScript engine written in the 90s. 108 | 109 | Still, some people make it work, and dropping IE8 support may prevent those teams from staying with the community as it migrates to Ember 2.0. 110 | 111 | # Alternatives 112 | 113 | ## Drop IE8 Support During 2.x 114 | 115 | One alternative we have considered is deprecating IE8 support prior to releasing 2.0, but still maintaining it for a few point releases to give IE8 more time to lose market share. 116 | 117 | After discussing with the core team, we believe that this would be a violation of our Semantic Versioning commitment to users. Specifically, we want to avoid a large group of apps getting stuck midway through the 2.x cycle. Version numbers are an important tool for developers, maintainers and ecosystems to communicate compatibility. Tools such as package managers rely on version numbers correctly indicating breaking changes. 118 | 119 | We consider browser compatibility to be a feature of Ember, and dropping IE8 support in a minor release would be akin to stripping out any other major feature. While the ecosystem would muddle along in either case, such a move would cause exactly the kind of ecosystem fragmentation that Semantic Versioning is designed to prevent. 120 | 121 | If we want to communicate the idea that changing versions comes with a reduction in functionality, we should do that the same way we always do, by incrementing the major version. 122 | 123 | ## Early 3.0 124 | 125 | Another option is to release 3.0 in six months, rather than the nearly two years between Ember 1.0 and Ember 2.0. 126 | 127 | Correctly tuning the cadence of major releases is a delicate tradeoff. Semantic Versioning allows us to easily communicate about breaking changes, and some take this as a license to make them frequently. However, a robust ecosystem relies on a certain measure of stability. 128 | 129 | We believe that the frustration of breaking changes every six months (or even a year) would outweigh whatever benefits it would provide. Ember's biggest goal is building a shared foundation for our ecosystem to build on, and this requires a careful commitment to stability. 130 | 131 | While we could make a "small" breaking release soon after 2.0, breaking changes inherently fragment the ecosystem, and we hope that the years to come bring more stability for add-on authors and tool-makers, not less. 132 | 133 | ## Bring Your Own Compatibility 134 | 135 | Some libraries attempt to thread the needle of IE8 compatibility by asking users to bring their own compatibility libraries. They write the internals of their framework as if IE8 did not exist, and require end users to use polyfills to make the environment look equivalent to newer browsers. For example, React asks users to bring libraries such as `es5-shim`, `es5-sham`, `console-polyfill` and `html5shiv` if they want IE8 support. 136 | 137 | Facebook.com supports IE8, and uses React, so there is a path to using React with IE8. This path is partially documented on the React website. This gives us a perfect opportunity to evaluate the impact of this strategy in the real world. We admire the React team's work in this area: support for IE8 is difficult and triaging and fixing IE8 bugs requires diligent effort. 138 | 139 | After reviewing the [IE8-compatibility issues filed on React.js tracker](https://github.com/facebook/react/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+ie8), we believe there are significant user experience costs to this strategy. 140 | 141 | We have spent considerable effort on first-class IE8 support in Ember 1.x, and we feel that users who require IE8 support will have a better experience using Ember 1.14 (with the subset of the ecosystem that supports 1.x) than trying to cobble together a solution that works reliably in a version of Ember with second-class, bring-your-own-compatibility support. 142 | 143 | # Unresolved questions 144 | 145 | We are relying on the community to help us weigh the above tradeoffs. The more data you can provide about the browser makeup of your customers (especially as it affects revenue), the better we can reason whether now is the time to remove IE8 (and possibly IE9) support. 146 | 147 | If you cannot share the information publicly, please email whatever information you consider useful to browserusage@emberjs.com. We will keep it in the strictest of confidence. -------------------------------------------------------------------------------- /text/0046-registry-reform.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2015-04-09 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/46 3 | - Ember Issue: https://github.com/emberjs/ember.js/pull/11440 4 | 5 | # Summary 6 | 7 | Fully encapsulate and privatize the `Container` and `Registry` classes by 8 | exposing a select subset of public methods on `Application` and 9 | `ApplicationInstance`. 10 | 11 | # Motivation 12 | 13 | The `Container` and `Registry` classes currently lead a confusing life of 14 | semi-private exclusion within Ember applications. They are undocumented 15 | publicly but not fully private either, as knowledge of their particulars is 16 | required for developing both initializers and unit tests. This situation has 17 | become untenable as the new `Registry` class has been extracted from 18 | `Container`, and the complexity of their usage has grown across 19 | `Application` and `ApplicationInstance` classes. 20 | 21 | We can bring sanity to this situation by continuing the work started at the 22 | `Application` level to expose methods such as `register` and `inject` from the 23 | internally maintained `Registry`. 24 | 25 | Furthermore, once `Container` and `Registry` are fully private, their 26 | architecture and documentation can be cleaned up. For instance, a 27 | `Container` can freely reference its associated `Registry` as `registry` 28 | rather than `_registry`, as it can be assumed that only framework developers 29 | will reference this property. 30 | 31 | # Detailed design 32 | 33 | `Application` will expose the following methods from its internally maintained 34 | registry: 35 | 36 | * `register` 37 | * `inject` 38 | * `registerOptions` - mapped to `Registry#options` 39 | * `registerOptionsForType` - mapped to `Registry#optionsForType` 40 | 41 | `ApplicationInstance` will also expose the the same methods. However, these 42 | methods will be exposed from its own internally maintained registry, which 43 | has the associated `Application`'s registry configured as a "fall back". No 44 | direct path will be provided from the `ApplicationInstance` to the 45 | `Application`'s registry. 46 | 47 | `ApplicationInstance` will also expose the following methods from its 48 | internally maintained container: 49 | 50 | * `lookup` 51 | * `lookupFactory` 52 | 53 | `ApplicationInstance` will cease exposing `container`, `registry`, and 54 | `applicationRegistry` publicly. 55 | 56 | `Application` initializers will receive a single argument to `initialize`: 57 | `application`. 58 | 59 | Likewise, `ApplicationInstance` initializers will receive a single argument 60 | to `initialize`: `applicationInstance`. 61 | 62 | `Container` and `Registry` will be made fully private and documented as 63 | such. Each `Container` will freely reference its associated `Registry` as 64 | `registry` rather than `_registry`. 65 | 66 | [ember-test-helpers](https://github.com/switchfly/ember-test-helpers) 67 | will provide an `isolatedApplicationInstance` method instead of an 68 | `isolatedContainer` for unit testing. A mechanism will be developed to specify 69 | which initializers should be engaged in the initialization of this instance. 70 | In this way, we can avoid duplication of registration logic, as is currently 71 | done in a most un-DRY manner in the [isolatedContainer](https://github.com/switchfly/ember-test-helpers/blob/master/lib/ember-test-helpers/isolated-container.js#L56-L79). 72 | 73 | # Drawbacks 74 | 75 | This refactor will require maintaining backwards compatibility and 76 | deprecation warnings until Ember 2.0. This will temporarily increase 77 | internal code complexity and file sizes. 78 | 79 | # Alternatives 80 | 81 | The obvious alternative is to make `Container` and `Registry` fully public 82 | and documented. An application's registry would be available as a `registry` 83 | property. An application instance's container would remain available as 84 | `container`. 85 | 86 | We could still pass an `Application` into application initializers 87 | and an `ApplicationInstance` into application instance initializers. 88 | 89 | If this alternative is taken, I would suggest that `Application` should 90 | deprecate `register` and `inject` in favor of calling the equivalents on its 91 | public `registry`. 92 | 93 | Regardless of which alternative is chosen, we should ensure that the public 94 | aspects of container and registry usage are well documented. 95 | 96 | # Unresolved questions 97 | 98 | * Are the public methods listed above sufficient or should any others be 99 | exposed? 100 | 101 | * What mechanism should be used to engage initializers in unit and 102 | integration tests? Should test modules simply have an `initializers` array, 103 | similar to the current `needs` array? 104 | 105 | * Given the semi-private nature of containers and registries, we may not need 106 | to worry about semver for deprecations. However, we should be good citizens 107 | and properly deprecate as much as possible. Some real world use cases in 108 | initializers will no doubt be a surprise, so we need to tread carefully. 109 | -------------------------------------------------------------------------------- /text/0050-improved-actions.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2014-05-06 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/50 3 | - Ember Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | The `{{action` helper should be improved to allow for the creation of 8 | closed over functions that can be passed between components and passed 9 | the action handlers. 10 | 11 | See [this example JSBin from @rwjblue](http://emberjs.jsbin.com/rwjblue/466/edit?html,js,output) 12 | for a demonstration of some of these ideas. 13 | 14 | # Motivation 15 | 16 | Block params allow data to be passed from one component to a downstream 17 | component, however there is currently no way to pass a callback to a downstream 18 | component. 19 | 20 | # Detailed design 21 | 22 | First, the existing uses of `{{action` will be maintained. An action can be attached to an 23 | element by using the helper in element space: 24 | 25 | ```hbs 26 | {{! app/index/template.hbs }} 27 | {{! submit action will hit immediate parent }} 28 | 29 | ``` 30 | 31 | An action can be passed to a component as a string: 32 | 33 | ```hbs 34 | {{! app/index/template.hbs }} 35 | {{my-button on-click="submit"}} 36 | ``` 37 | 38 | ```js 39 | // app/components/my-button/component.js 40 | export default Ember.Component.extend({ 41 | click: function(){ 42 | this.sendAction('on-click'); 43 | } 44 | }); 45 | ``` 46 | 47 | Or a default action can be passed: 48 | 49 | ```hbs 50 | {{! app/index/template.hbs }} 51 | {{my-button action="submit"}} 52 | ``` 53 | 54 | ```js 55 | // app/components/my-button/component.js 56 | export default Ember.Component.extend({ 57 | click: function(){ 58 | this.sendAction(); 59 | } 60 | }); 61 | ``` 62 | 63 | In all these cases, `submit` is called on the parent context relative to the scope `action` is 64 | attached in. The value `"submit"` is attached to the component in the last two as 65 | `this.attrs.on-click` or `this.attrs.action`, although it is not directly used. 66 | 67 | ### Creating closure actions 68 | 69 | Closure actions are created in a template and may be used in all places a string 70 | action name can be used. For example, this current functionality: 71 | 72 | ```hbs 73 | 74 | ``` 75 | 76 | Would be written using a closure action as: 77 | 78 | ```hbs 79 | 80 | ``` 81 | 82 | The functionality is exactly the same as the string-based action example. 83 | How does that happen? 84 | 85 | * `(action "submit")` reads the `submit` function off the current scope's 86 | `actions.submit` property. 87 | * It then creates a closure to call that function. 88 | * `{{action` receives that function as a param. It registers a listener (in 89 | this case on click) and when fired calls the closure function. 90 | 91 | Consider usage on the calling side. With the current string-based actions: 92 | 93 | ```hbs 94 | {{my-component action="submit"}} 95 | ``` 96 | 97 | ```js 98 | export default Ember.Component.extend({ 99 | click: function(){ 100 | this.sendAction(); // submit action, legacy 101 | // this.attrs.action is a string 102 | this.attrs.action; // => "submit" 103 | } 104 | }); 105 | ``` 106 | 107 | With closure actions, the action is available to call directly. The `(action` helper 108 | wraps the action in the current context and returns a function: 109 | 110 | ```hbs 111 | {{my-component action=(action "submit")}} 112 | ``` 113 | 114 | ```js 115 | export default Ember.Component.extend({ 116 | click: function(){ 117 | this.sendAction(); // submit action, legacy 118 | // this.attrs.action is a function 119 | this.attrs.action(); // submit action, new style 120 | } 121 | }); 122 | ``` 123 | 124 | A more complete example follows, with a controller for context: 125 | 126 | ```js 127 | // app/index/controller.js 128 | export default Ember.Controller.extend({ 129 | actions: { 130 | submit: function(){ 131 | // some submission task 132 | } 133 | } 134 | }); 135 | ``` 136 | 137 | ```hbs 138 | {{! app/index/template.hbs }} 139 | {{my-button save=(action 'submit')}} 140 | ``` 141 | 142 | ```js 143 | // app/components/my-button/component.js 144 | export default Ember.Component.extend({ 145 | click: function(){ 146 | this.attrs.save(); 147 | // for backwards compat, you may also this.sendAction('save'); 148 | } 149 | }); 150 | ``` 151 | 152 | ### Hole punching with a closure-based action 153 | 154 | The current system of action bubbling falls down quickly when you want to pass a message through multiple 155 | levels of components. A closure based action system helps address this. 156 | 157 | Instead of relying on bubbling, a closure action wraps an action from the current context's 158 | `actions` hash in a function that will call it on that context. For example: 159 | 160 | ```hbs 161 | {{! app/index/template.hbs }} 162 | {{my-form submit=(action 'submit')}} 163 | ``` 164 | 165 | ```hbs 166 | {{! app/components/my-form/template.hbs }} 167 | {{my-button on-click=attrs.submit}} 168 | ``` 169 | 170 | ```hbs 171 | {{! app/components/my-button/template.hbs }} 172 | 173 | ``` 174 | 175 | ```js 176 | // app/components/my-button/component.js 177 | export default Ember.Component.extend({ 178 | click: function(){ 179 | this.attrs['on-click'](); 180 | // for backwards compat, you may also this.sendAction(); 181 | } 182 | }); 183 | ``` 184 | 185 | A closure action can also be called by an action handler: 186 | 187 | ```hbs 188 | {{! app/index/template.hbs }} 189 | {{my-form submit=(action 'submit')}} 190 | ``` 191 | 192 | ```hbs 193 | {{! app/components/my-form/template.hbs }} 194 | {{my-button on-click=submit}} 195 | ``` 196 | 197 | ```hbs 198 | {{! app/components/my-button/template.hbs }} 199 | 200 | ``` 201 | 202 | Lastly, closure actions allow for yielding an action to a block. For example: 203 | 204 | ```hbs 205 | {{! app/index/template.hbs }} 206 | {{my-form save=(action 'submit') as |submit reset|}} 207 | 208 | {{! ^ goes to my-form's save attr property, which 209 | is the submit action on the outer scope }} 210 | 211 | {{! ^ goes to my-form }} 212 | 213 | {{! ^ goes to outer scope }} 214 | {{/my-form}} 215 | ``` 216 | 217 | ```hbs 218 | {{! app/components/my-form/template.hbs }} 219 | {{yield attrs.save (action 'reset')}} 220 | ``` 221 | 222 | ```js 223 | // app/components/my-form/component.js 224 | export default Ember.Component.extend({ 225 | actions: { 226 | reset: function(){ 227 | // rollback 228 | } 229 | } 230 | }); 231 | ``` 232 | 233 | ### Currying arguments with a closure-based action 234 | 235 | With string-based actions, an argument can be passed to the called function. For 236 | example: 237 | 238 | ```hbs 239 | 240 | ``` 241 | 242 | ```js 243 | export default Ember.Component.extend({ 244 | actions: { 245 | save: function(model) { 246 | model.save(); 247 | } 248 | } 249 | }); 250 | ``` 251 | 252 | Closure actions allow for another opportunity to curry arguments. Arguments 253 | set by an element action helper simply add to the end of the arguments list: 254 | 255 | ```hbs 256 | {{! app/index/template.hbs }} 257 | {{my-component save=(action "save" model)}} 258 | ``` 259 | 260 | ```hbs 261 | {{! app/components/my-component/template.hbs }} 262 | 263 | ``` 264 | 265 | ```js 266 | // app/index/controller.js 267 | export default Ember.Controller.extend({ 268 | actions: { 269 | save: function(model, prefs) { 270 | model.set('prefs', prefs); 271 | model.save(); 272 | } 273 | } 274 | }); 275 | ``` 276 | 277 | Multiple arguments can be curried or set at any level. If an action is called ala 278 | `this.attrs.save(additionalPrefs)`, that final argument is added 279 | to the end of the arguments list. 280 | 281 | ### Re-targeting the scope of a closure action 282 | 283 | The `target` option may be provided to specify what scope the closure is called 284 | with. For example: 285 | 286 | ```hbs 287 | {{! app/index/template.hbs }} 288 | 289 | ``` 290 | 291 | Much like with the `{{action` helper, passing both a 292 | target and a bound argument will throw. 293 | 294 | The default target for a closure is always the current scope. 295 | 296 | * When routable components land, the current component will be the default target. 297 | * If a controller is the current scope, that controller will also be a default target. 298 | * A route will *never* be a closure action target. String actions will continue 299 | to have their current behavior of bubbling to the route. 300 | 301 | A later proposal will determine how actions on a route are passed to a routable 302 | component. 303 | 304 | ### Return values of a closure action 305 | 306 | Closure actions return the returned value of their called function. For example: 307 | 308 | ```js 309 | // app/index/controller.js 310 | export default Ember.Controller.extend({ 311 | actions: { 312 | submit: function(){ 313 | return 'great success'; 314 | } 315 | } 316 | }); 317 | ``` 318 | 319 | ```hbs 320 | {{! app/index/template.hbs }} 321 | {{my-button save=(action 'submit')}} 322 | ``` 323 | 324 | ```js 325 | // app/components/my-button/component.js 326 | export default Ember.Component.extend({ 327 | click: function(){ 328 | var result = this.attrs.save(); 329 | // for backwards compat, you may also this.sendAction('save') but 330 | // in that case you do not have access to the return value. 331 | result; // => 'great success' 332 | } 333 | }); 334 | ``` 335 | 336 | ### Actionable object with INVOKE 337 | 338 | `{{mut` is a new helper in Ember.js. It is not yet widely used in Ember apps, but its 339 | interaction with the action helper is important to align early on. 340 | 341 | Mut objects represent a modifiable value. For example with tag-based components: 342 | 343 | ```hbs 344 | {{! app/index/template.hbs }} 345 | 346 | ``` 347 | 348 | This will cause a mutable property to be added to `attrs`. To update the name, 349 | `this.attrs.name.update(newName)` can be called. The value can be read (in 350 | JavaScript) as `this.attrs.name.value`. 351 | 352 | Often, a mutable value will be set as the result of an action. Mutable values 353 | can be called actionable. For example: 354 | 355 | ```hbs 356 | {{! app/index/template.hbs }} 357 | 358 | ``` 359 | 360 | ```js 361 | // app/components/my-form/component.js 362 | export default Ember.Component.extend({ 363 | click() { 364 | const value = this.get('newValue'); 365 | this.attrs.submit(value); 366 | } 367 | }); 368 | ``` 369 | 370 | What is happening here? 371 | 372 | * `(mut model.name)` creates a mutable object for the `model.name` value. 373 | * `{{action (mut model.name)}}` tests the passed object for a property with the 374 | key `INVOKE` (an internal symbol). This value is a function that updates the mutable value. 375 | * Action wraps the calling of the `INVOKE` property in a function like any 376 | other action, and passes it to the `attrs`. 377 | 378 | Thus, when the action is called the argument is passed to `INVOKE` which uses 379 | it to update the mutable value. This is a simple way to enable the "actions up" 380 | part of component-driven app architecture without ceremony around changing state. 381 | 382 | ### Plucking a property from the first argument with value 383 | 384 | A component (or when Ember supports this better, an element) may emit an event 385 | object and pass it to an action. In this case the value will need to be read off 386 | the event before it can be passed to the action function. For example: 387 | 388 | ```hbs 389 | {{input input=(action 'setName')}} 390 | ``` 391 | 392 | ```js 393 | export default Ember.Component.extend({ 394 | actions: { 395 | setName(event) { 396 | this.get('model').set('name', event.target.value); 397 | } 398 | } 399 | }); 400 | ``` 401 | 402 | The action serves only to read the value off of the event. Here the `value` 403 | option can be used as sugar to accomplish the same task: 404 | 405 | ```hbs 406 | {{input input=(action (mut model.name) value="target.value")}} 407 | ``` 408 | 409 | The `value` path is read off of whatever the first argument to the actions is. 410 | 411 | * `(mut model.name)` becomes a function, our action 412 | * When the `input` event fires, the function is called with the event as the 413 | first argument. 414 | * The first argument is re-written to the value of `event.target.value` 415 | * The function wrapping the `mut` is set 416 | * The `mut` is updated. 417 | 418 | This option is designed to align with future plans for `on-some-event` handlers 419 | for html elements. 420 | 421 | # Drawbacks 422 | 423 | Currently `{{action` is only used in an element space: 424 | 425 | ```hbs 426 | 427 | ``` 428 | 429 | The closure usage is a new, perhaps `action` is not the right word. However the two 430 | behaviors are pretty similar in their conceptual behavior. 431 | 432 | * `{{action` in element space attaches an event listener that fires a bubbling 433 | action. 434 | * `(action` closes over an action from the current scope so it can be attached 435 | via `{{action` or passed around and called later. 436 | 437 | This confusion should go away as we move to an `on-click` event listener pattern, 438 | ala ` 98 | {{/with}} 99 | ``` 100 | 101 | The returned value of the `(action` nested helper (a function) closes over the 102 | action being called (`actions.save` on the context and the `model` property). 103 | The `{{action` helper can accept this resulting value and invoke the action 104 | when the user clicks. 105 | 106 | The `(component` helper will close over a component name. The 107 | `{{component` helper will be modified to accept this resulting value and invoke 108 | the component: 109 | 110 | ```hbs 111 | {{#with (component "user-profile") as |uiPane|}} 112 | {{component uiPane}} 113 | {{/with}} 114 | ``` 115 | 116 | Additionally, a bound value may be passed to the `(component` helper. For 117 | example `(component someComponentName)`. 118 | 119 | Attrs for the final component can also be closed over. Used with yield, this 120 | allows for the creation of components that have attrs from other scopes. For 121 | example: 122 | 123 | ```hbs 124 | {{! app/components/user-profile.hbs }} 125 | {{yield (component "user-profile" user=user.name age=user.age)}} 126 | ``` 127 | 128 | ```hbs 129 | {{#user-profile user=model as |profile|}} 130 | {{component profile}} 131 | {{/user-profile}} 132 | ``` 133 | 134 | Of course attrs can also be passed at invocation. They smash any conflicting 135 | attrs that were closed over. For example `{{component profile age=lyingUser.age}}` 136 | 137 | Passing the resulting value from `(component` into JavaScript is permitted, 138 | however that object has no public properties or methods. Its only use would 139 | be to set it on state and reference it in template somewhere. 140 | 141 | ### Hash helper 142 | 143 | Unlike values, components are likely to have specific names that are semantically 144 | relevent. When yielded to a new scope, allowing the user to change the name 145 | of the component's variable would quickly lead to confusing addon documentation. 146 | For example: 147 | 148 | ```hbs 149 | {{#with (component "user-profile") as |dropDatabaseUI|}} 150 | {{component dropDatabaseUI}} 151 | {{/with}} 152 | ``` 153 | 154 | The simplest way to enforce specific names is to make building hashes 155 | of components (or anything) easy. For example: 156 | 157 | ```hbs 158 | {{#with (hash profile=(component "user-profile")) as |userComponents|}} 159 | {{component userComponents.profile}} 160 | {{/with}} 161 | ``` 162 | 163 | The `(hash` helper is a generic builder of objects, given hash arguments. It 164 | would also be useful in the same manner for actions: 165 | 166 | ```hbs 167 | {{#with (hash save=(action "save" model)) as |userActions|}} 168 | 169 | {{/with}} 170 | ``` 171 | 172 | ### Component helper shorthand 173 | 174 | To complete building a viable DSL, `.` invocation for `{{` components will be 175 | introduced. For example this `{{component` invocation: 176 | 177 | ```hbs 178 | {{#with (hash profile=(component "user-profile")) as |userComponents|}} 179 | {{component userComponents.profile}} 180 | {{/with}} 181 | ``` 182 | 183 | Could be converted to drop the explicit `component` helper call. 184 | 185 | ```hbs 186 | {{#with (hash profile=(component "user-profile")) as |userComponents|}} 187 | {{userComponents.profile}} 188 | {{/with}} 189 | ``` 190 | 191 | A component can be invoked like this only when it was created by the 192 | `(component` nested helper form. For example unlike with the `{{component` 193 | helper, a string is not acceptable. 194 | 195 | To be a valid invocation, one of two criteria must be met: 196 | 197 | * The component can be called as a path. For example `{{form.input}}` or `{{this.input}}` 198 | * The component can be called as a helper. For example `{{form.input value=baz}}` or `{{this.input value=baz}}` 199 | 200 | And of course a `.` must be present in the path. 201 | 202 | # Drawbacks 203 | 204 | This proposal encourages aggressive use of the `(` nested helper syntax. 205 | Encouraging this has been slightly controversial. 206 | 207 | No solution for angle components is presented here. The syntax for `.` 208 | notation in angle components is coupled to a decision on the syntax for 209 | bound, dynamic angle component invocation (a `{{component` helper for angle 210 | components basically). 211 | 212 | `(component 'some-component'` may be too verbose. It may make sense to simply 213 | allow `(some-component`. 214 | 215 | Other proposals have leaned more heavy on extending factories in JavaScript 216 | then passing an object created in that space. Some arguments against this: 217 | 218 | * Getting the container correct is tricky. Who sets it when? 219 | * Properties on the classes would not be naturally bound, as they are in this proposal. 220 | * As soon as you start setting properties, you likely want a `mut` helper, 221 | `action` helper, etc, in JavaScript space. 222 | * Keeping the component lookup in the template layer allows us to take advantage 223 | of changes to lookup semantics later, such as local lookup in the pods 224 | proposal. 225 | 226 | # Alternatives 227 | 228 | All pain, no gain. Addons really want this. 229 | 230 | # Unresolved questions 231 | 232 | There has been discussion of if a similar mechanism should be available for 233 | helpers. 234 | -------------------------------------------------------------------------------- /text/0065-deprecation-warning-handlers.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2015-06-30 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/65 3 | - Ember Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | Deprecations and warnings in Ember.js should have configurable runtime handlers. 8 | This allows default behavior (logging, raise when `RAISE_ON_DEPRECATION` is true) 9 | to be overridden by an enviornment (Ember's tests), addon, or other tool 10 | (like the Ember Inspector). 11 | 12 | Ember-Data and the Ember Inspector have both requested a public 13 | API for changing how deprecation and warning messages are handled. The requirements 14 | for these and other requests are complex enough that deferring the message 15 | behavior into a runtime hook is the suggested path. 16 | 17 | # Motivation 18 | 19 | `Ember.deprecate` and `Ember.warn` usually log messages. With `ENV.RAISE_ON_DEPRECATION` 20 | all deprecations will throw an exception. In some scenarios, this 21 | is less than ideal: 22 | 23 | * Ember itself needs a way to silence some deprecations before their usage 24 | is completely removed from tests. For example, many view APIs in Ember 1.13. 25 | * The Ember inspector desires to raise on specific deprecations, or silence 26 | specific deprecations. 27 | * Ember-Data also desires to silence some deprecations in tests 28 | 29 | In [PR #1141](https://github.com/emberjs/ember.js/pull/11419) 30 | a private log level API has been introduced, which allows finer grained control 31 | if specific deprecations should be logged, throwing an error or be silenced 32 | completely. For example: 33 | 34 | ```js 35 | Ember.Debug._addDeprecationLevel('my-feature', Ember.Debug._deprecationLevels.LOG); 36 | // ... 37 | Ember.deprecate("x is deprecated, use Y instead", false, { id: 'my-feature' }); 38 | ``` 39 | 40 | Initially a public version of this API was discussed, but it quickly became 41 | clear that a runtime hook provided more flexibility without incurring the 42 | cost of a complex log-level API. 43 | 44 | Note that "runtime" refers to Ember itself. A custom handler could be injected 45 | into Ember-CLI's template compilation code. "runtime" in this context still 46 | refers to handling deprecations raised during compilation. 47 | 48 | # Detailed design 49 | 50 | A handler for deprecations can be registered. This handler will be called 51 | with relevent information about a deprecation, including guarantees about 52 | the presence of these items: 53 | 54 | * The deprecation message 55 | * The version number where this deprecation (and feature) will be removed 56 | * The "id" of this deprecation, a stable identifier independent of the message 57 | 58 | Additionally, an application instance may be passed with the options. An example 59 | handler would look like: 60 | 61 | ```js 62 | import { registerHandler } from "ember-debug/deprecations"; 63 | 64 | registerHandler(function deprecationHandler(message, options) { 65 | // * message is the deprecation message 66 | // * options.until is the version this deprecation will be removed at 67 | // * options.id is the canonical id for this deprecation 68 | if (options.until === "2.4.0") { 69 | throw new Error(message); 70 | } else { 71 | console.log(message); 72 | } 73 | }); 74 | ``` 75 | 76 | Warnings are similar, but will not recieve an `until` value: 77 | 78 | ```js 79 | import { registerHandler } from "ember-debug/warnings"; 80 | 81 | registerHandler(function warningHandler(message, options) { 82 | // * message is the warning message 83 | // * options.id is the canonical id for this warning 84 | if (options.id !== 'view.rerender-on-set') { 85 | console.log(message); 86 | } 87 | }); 88 | ``` 89 | 90 | ##### chained handlers 91 | 92 | Since several handlers may be registered, a method of deferring to a previously 93 | registered handler must be allowed. A third option is passed to handlers, the 94 | function `next` which represents the previously registered handler. 95 | 96 | For example: 97 | 98 | ```js 99 | import { registerHandler } from "ember-debug/deprecations"; 100 | 101 | registerHandler(function firstDeprecationHandler(message, options, next) { 102 | console.warn(message); 103 | }); 104 | 105 | registerHandler(function secondDeprecationHandler(message, options, next) { 106 | if (options.until === "2.4.0") { 107 | throw new Error(message); 108 | } 109 | next(...arguments); 110 | }); 111 | ``` 112 | 113 | The first registed handler will receive Ember's default behavior as `next`. 114 | 115 | ##### new assertions for deprecate and warn 116 | 117 | Ember's APIs for deprecation and warning do not currently require any information 118 | beyond a message. It is proposed that deprecations be **required** to pass 119 | the following information: 120 | 121 | * Message 122 | * Test 123 | * Canonical id (with a format of `package-name.some-id`) 124 | * Release when this deprecation will be stripped 125 | 126 | For example: 127 | 128 | ``` 129 | import Ember from "ember"; 130 | 131 | Ember.deprecate("Some message", false, { 132 | id: 'ember-routing.query-params', 133 | until: '3.0.0' 134 | }); 135 | ``` 136 | 137 | If this information is not present and assertion will be made. 138 | 139 | Warnings likewise will be required to pass a canonical id: 140 | 141 | ``` 142 | import Ember from "ember"; 143 | 144 | Ember.warn("Some warning", {id: 'ember-debug.something'}); 145 | ``` 146 | 147 | ##### default handlers 148 | 149 | The default handler for deprecation should be quite simple, and mirrors current 150 | behavior: 151 | 152 | ```js 153 | function defaultDeprecationHandler(message, options) { 154 | if (Ember.ENV.RAISE_ON_DEPRECATION) { 155 | throw new Error(format(message, options)); 156 | } else { 157 | console.log(format(message, options)); 158 | } 159 | } 160 | ``` 161 | 162 | The default handler for warnings would be simple `console.log`. 163 | 164 | # Drawbacks 165 | 166 | By not providing a robust log-level API, we are punting complexity to the 167 | consumer of this API. For a low-level tooling API such as this one, it seems 168 | and appropriate tradeoff. 169 | 170 | # Alternatives 171 | 172 | Each app can stub out `deprecate` and `warn`. 173 | 174 | # Unresolved questions 175 | 176 | `RAISE_ON_DEPRECATION` could be considered deprecated with this new API. 177 | -------------------------------------------------------------------------------- /text/0091-weakmap.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2015-09-11 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/91 3 | - Ember Issue: [#12224](https://github.com/emberjs/ember.js/pull/12224) / [#12990](https://github.com/emberjs/ember.js/pull/12990) / [#13688](https://github.com/emberjs/ember.js/pull/13688) 4 | 5 | # Summary 6 | 7 | Introduce `Ember.WeakMap` (`@ember/weakmap`), an ES6 enspired WeakMap. A 8 | WeakMap provides a mechanism for storing and retriving private state. The 9 | WeakMap itself does not retain a reference to the state, allowing the state to 10 | be reclaimed when the key is reclaimed. 11 | 12 | A traditional WeakMap (and the one that will be part of the language) allows 13 | for weakness from key -> map, and also from map -> key. This allows either the 14 | Map, or the key being reclaimed to also release the state. 15 | 16 | Unforunately, this bi-directional weakness is problemative to polyfil. Luckily, 17 | uni-directional weakness, in either direction, "just works". A polyfil must 18 | just choose a direction. 19 | 20 | *Note: Just like ES2015 WeakMap, only non null Objects can be used as keys* 21 | *Note: `Ember.WeakMap` can be used interchangibly with the ES2015 WeakMap. This 22 | will allow us to eventually cut over entirely to the Native WeakMap.* 23 | 24 | # Motivation 25 | 26 | It is a common pattern to want to store private state about a specific object. 27 | When one stores this private state off-object, it can be tricky to understand 28 | when to release the state. When one stores this state on-object, it will be 29 | released when the object is released. Unfortunately, storing the state 30 | on-object without poluting the object itself is non-obvious. 31 | 32 | As it turns out, Ember's Meta already solves this problem for 33 | listeners/caches/chains/descriptors etc. Unfortunately today, there is no 34 | public API for apps or addons to utilize this. `Ember.WeakMap` aims to be 35 | exactly that API. 36 | 37 | Some examples: 38 | 39 | * https://github.com/offirgolan/ember-cp-validations/blob/master/addon/utils/cycle-breaker.js 40 | * https://github.com/stefanpenner/ember-state-services/ (will soon utilize the user-land polyfil of this) to prevent common leaks. 41 | 42 | # Detailed design 43 | 44 | ## Public API 45 | 46 | ```js 47 | import WeakMap from '@ember/weak-map' 48 | 49 | var private = new WeakMap(); 50 | var object = {}; 51 | var otherObject = {}; 52 | 53 | private.set(object, { 54 | id: 1, 55 | name: 'My File', 56 | progress: 0 57 | }) === private; 58 | 59 | private.get(object) === { 60 | id: 1, 61 | name: 'My File', 62 | progress: 0 63 | }); 64 | 65 | 66 | private.has(object) === true; 67 | private.has(otherObject) === false; 68 | 69 | private.delete(object) === private; 70 | private.has(object) === false; 71 | ``` 72 | 73 | ## Implementation Details 74 | 75 | The backing store for `Ember.WeakMap` will reside in a lazy `ownMap` named 76 | `weak` on the key objects `__meta__` object. 77 | 78 | Each `WeakMap` has its own internal GUID, which will be the name of its slot, 79 | in the key objects meta weak bucket. This will allow one object to belong in 80 | multiple weakMaps without chance of collision. 81 | 82 | Concrete Implementation: https://github.com/emberjs/ember.js/pull/12224 83 | Polyfill: https://www.npmjs.com/package/ember-weakmap 84 | 85 | # Drawbacks 86 | 87 | * implementing bi-direction Weakness in userland is problematic. 88 | * Using WeakMap will insert a non-enumerable `meta` onto the key Object. 89 | 90 | # Alternatives 91 | 92 | * Weakness could be implemented in the other direction, but this has questionable utility. 93 | 94 | # Unresolved questions 95 | 96 | N/A 97 | -------------------------------------------------------------------------------- /text/0095-router-service.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2015-09-24 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/95 3 | - Ember Issue: https://github.com/emberjs/ember.js/pull/13868 4 | 5 | # Summary 6 | 7 | This RFC proposes: 8 | 9 | - creating a public `router` service that is a superset of today's `Ember.Router`. 10 | 11 | - codifying and expanding the supported public API for the `transition` object that is currently passed to `Route` hooks. 12 | 13 | - introducing the `get-route-info` template helper 14 | - introducing the `#with-route-info` template keyword 15 | - introducing the `readsRouteInfo` static property on `Component` and `Helper`. 16 | 17 | These topics are closely related because they share a unified `RouteInfo` type, which will be described in detail. 18 | 19 | # Motivation 20 | 21 | Given the modern Ember concepts of Components and Services, it is clear that routing capability should be exposed as a Service. I hope this is uncontroversial, given that we already implement it as a service internally, and given that usage of these nominally-private APIs is already becoming widespread. 22 | 23 | The immediate benefit of having a `RouterService` is that you can inject it into components, giving them a friendly way to initiate transitions and ask questions about the current global router state. 24 | 25 | A second benefit is that we have the opportunity to add new capabilities to the `RouterService` to replace several common patterns in the wild that dive into private internals in order to get things done. There are several places where we leak internals from router.js, and we can plug those leaks. 26 | 27 | A `RouterService` is great for asking global questions, but some questions are not global and today we incur complexity by treating them as if they are. For example: 28 | 29 | - `{{link-to}}` can use implicit models from its context, but that breaks when you're trying to animate to or from a state where those models are not present. 30 | 31 | - `{{link-to}}` has a lot of complexity and performance cost that deals with changing its active state, and the precise timing of when that should happen. 32 | 33 | - there is no way to ask the router what it would do to handle a given URL without actually visiting that URL. 34 | 35 | All of the above can be addressed by embracing what is already internally true: "the current route" is not a single global, it's a dynamically-scoped variable that can have different values in different parts of the application simultaneously. 36 | 37 | # Detailed design 38 | 39 | ## RouterService 40 | 41 | By way of a simple example, the router service behaves like this: 42 | 43 | ```js 44 | import Component from 'ember-component'; 45 | import service from 'ember-service/inject'; 46 | 47 | export default Component.extend({ 48 | router: service(), 49 | actions: { 50 | goToMars() { 51 | this.get('router').transitionTo('planet.mars'); 52 | } 53 | } 54 | }); 55 | ``` 56 | 57 | Like any Service, it can also be injected into Helpers, Routes, etc. 58 | 59 | ### Relationship between EmberRouter and RouterService 60 | 61 | Q: "Why are you calling this thing 'router' when we already have a router? Shouldn't the new thing be called 'routing' or something else?". 62 | 63 | A: We shouldn't have two things. From the user's perspective, there is just "the router", and it happens to be available as a service. While we're free to continue implementing it as multiple classes under the hood, the public API should present as a single, coherent concept. 64 | 65 | Terminology: 66 | 67 | - `EmberRouter` is the class that we already have today, defined in `ember-routing/system/router` and available publicly as `Ember.Router` 68 | - `RouterService` is the new class I am proposing. 69 | 70 | `EmberRouter` has the following public API today: 71 | 72 | - `map` 73 | - `location` 74 | - `rootURL` 75 | - `willTransition` 76 | - `didTransition` 77 | 78 | That API will be carried over verbatim to `RouterService`, and the publicly accessible `Ember.Router` class will *become* `RouterService`. In terms of implementation, I expect the existing `EmberRouter` class will continue to exist mostly unchanged. But public access to it will be moderated through `RouterService`. 79 | 80 | ### New Methods: Initiating Transitions 81 | 82 | ```js 83 | transitionTo(routeName, ...models, queryParams) 84 | replaceWith(routeName, ...models, queryParams) 85 | ``` 86 | 87 | These two have the same semantics as the existing methods on `Ember.Route`: 88 | 89 | ### New Method: Checking For Active Route 90 | 91 | - `isActive(routeName, ...models, queryParams)` 92 | 93 | The arguments have the same semantics as `transitionTo`, the return value is a boolean. This should provide the same logic that determines whether to put an active class on a `link-to`. Here's an example of how we can implement `is-active` as a helper, using this method: 94 | 95 | ```js 96 | import Helper from 'ember-helper'; 97 | import service from 'ember-service/inject'; 98 | import observer from 'ember-metal/observer'; 99 | 100 | export default Helper.extend({ 101 | router: service(), 102 | compute([routeName, ...models], hash) { 103 | let allModels; 104 | if (hash.models) { 105 | allModels = models.concat(hash.models); 106 | } else { 107 | allModels = models; 108 | } 109 | return this.get('router').isActive(routeName, ...models, hash.queryParams); 110 | }, 111 | didTransition: observer('router.currentRoute', function() { 112 | this.recompute(); 113 | }); 114 | }); 115 | ``` 116 | 117 | ```hbs 118 | {{!- Example usage -}} 119 |
  • 120 | 121 | {{!- Example usage with generic routeName and list of models (avoids splat) -}} 122 | 123 | 124 | {{!- Note that the complexities of currentWhen can be avoided by composing instead. }} 125 | 126 | 127 | ``` 128 | 129 | ### New Method: URL generation 130 | 131 | `urlFor(routeName, ...models, queryParams)` 132 | 133 | This takes the same arguments as `transitionTo`, but instead of initiating the transition it returns the resulting root-relative URL as a string (which will include the application's `rootUrl`). 134 | 135 | A `url-for` helper can be implemented almost identically to the `is-active` example above. 136 | 137 | ### New Method: URL recognition 138 | 139 | `recognize(url)` 140 | 141 | Takes a string URL and returns a `RouteInfo` for the leafmost route represented by the URL. Returns `null` if the URL is not recognized. This method expects to receive the actual URL as seen by the browser _including_ the app's `rootURL`. 142 | 143 | 144 | Example: this feature can replace [this use of private API in ember-href-to](https://github.com/intercom/ember-href-to/blob/b8cf0699eec6a65570b05e4fc22b27d8cea49c42/app/instance-initializers/browser/ember-href-to.js#L34). 145 | 146 | 147 | ### New Method: Recognize and load models 148 | 149 | `recognizeAndLoad(url)` 150 | 151 | Takes a string URL and returns a promise that resolves to a `RouteInfoWithAttributes` for the leafmost route represented by the URL. The promise rejects if the URL is not recognized or an unhandled exception is encountered. This method expects to receive the actual URL as seen by the browser _including_ the app's `rootURL`. 152 | 153 | ### Deprecating willTransition and didTransition 154 | 155 | Application-wide transition monitoring events belong on the Router service, not spread throughout the Route classes. That is the reason for the existing `willTransition` and `didTransition` hooks/events on the Router. But they are not sufficient to capture all the detail people need. See for example, https://github.com/nickiaconis/rfcs/blob/1bd98ec534441a38f62a48599ffa8a63551b785f/text/0000-transition-hooks-events.md 156 | 157 | In addition, they receive handlerInfos in their arguments, which are an undocumented internal implementation detail of router.js that doesn't belong in Ember's public API. Everything you can do with handlerInfos can be done with the RouteInfo type that is proposed in this RFC, with the benefit of sticking to supported public API. 158 | 159 | So we should deprecate willTransition and didTransition in favor of the following new events. 160 | 161 | ### New Events: routeWillChange & routeDidChange 162 | 163 | The `routeWillChange` event fires whenever a new route is chosen as the desired target of a transition. This includes `transitionTo`, `replaceWith`, all redirection for any reason including error handling, and abort. Aborting implies changing the desired target back to where you already were. Once a transition has completed, `routeDidChange` fires. 164 | 165 | Both events receive a single `transition` argument as described in the "Transition Object" section below, which explains the meaning of `from` and `to` in more detail. 166 | 167 | Redirection example: 168 | 169 | 1. current route is A 170 | 2. user initiates a transition to B 171 | 3. routeWillChange fires `from` A `to` B. 172 | 4. B redirects to C 173 | 5. routeWillChange fires `from` A `to` C. 174 | 6. routeDidChange fires `from` A `to` C. 175 | 176 | Abort example: 177 | 178 | 1. current route is A 179 | 2. user initiates a transition to B 180 | 3. routeWillChange fires `from` A `to` B. 181 | 4. in response to the previous routeWillChange event, the transition is aborted. 182 | 5. routeWillChange fires `from` A `to` A. 183 | 8. routeDidChange fires `from` A `to` A. 184 | 185 | Error example: 186 | 187 | 1. current route is A 188 | 2. user initiates a transition to B.index 189 | 3. routeWillChange fires `from` A `to` B. 190 | 4. B throws an exception, and the router discovers a "B-error" template. 191 | 5. routeWillChange fires `from` A `to` B-error 192 | 6. routeDidChange fires `from` A `to` B-error 193 | 194 | These are events, not extension hooks -- now that we are exposing a Service, it makes more sense to subscribe to its events than extend it. 195 | 196 | ### New Properties 197 | 198 | `currentRoute`: an observable property. It is guaranteed to change whenever a route transition happens (even when that transition only changes parameters and doesn't change the active route). You should consider its value deeply immutable -- we will replace the whole structure whenever it changes. The value of `currentRoute` is a `RouteInfo` representing the current leaf route. `RouteInfo` is described below. 199 | 200 | `currentRouteName`: a convenient alias for `currentRoute.name`. 201 | 202 | `currentURL`: provides the serialized string representing `currentRoute`. 203 | 204 | ### Query Parameter Semantics 205 | 206 | Today, `queryParams` impose unnecessarily high cost because we cannot generate URLs or determine if a link is active without taking into account the default values of query parameters. Determining their default values is expensive, because it involves instantiating the corresponding controller, even in cases where we will never visit its route. 207 | 208 | Therefore, the `queryParams` argument to the new `urlFor`, `transitionTo`, `replaceWith`, and `isActive` methods defined in this document will behave differently. 209 | 210 | - default values will not be stripped from generated URLs. For example, `urlFor('my-route', { sortBy: 'title' })` will always include `?sortBy=title`, whether or not `title` is the default value of `sortBy`. 211 | 212 | - to explicitly unset a query parameter, you can pass the symbol `Ember.DEFAULT_VALUE` as its value. For example, `transitionTo('my-route', { sortBy: Ember.DEFAULT_VALUE })` will result in a URL that does not contain any `?sortBy=`. 213 | 214 | (Sticky parameters are still allowed, because they only apply when the destination controller has already been instantiated anyway.) 215 | 216 | 217 | 218 | ## RouteInfo Type 219 | 220 | A RouteInfo object has the following properties. They are all read-only. 221 | 222 | - name: the dot-separated, fully-qualified name of this route, like `"people.index"`. 223 | - localName: the final part of the `name`, like `"index"`. 224 | - params: the values of this route's parameters. Same as the argument to `Route`'s `model` hook. Contains only the parameters valid for this route, if any (params for parent or child routes are not merged). 225 | - paramNames: ordered list of the names of the params required for this route. It will contain the same strings as `Object.keys(params)`, but here the order is significant. This allows users to correctly pass params into routes programmatically. 226 | - queryParams: the values of any queryParams on this route. 227 | - parent: another RouteInfo instance, describing this route's parent route, if any. 228 | - child: another RouteInfo instance, describing this route's active child route, if any. 229 | 230 | Notice that the `parent` and `child` properties cause `RouteInfos` to form a linked list. So even though the `currentRoute` property on `RouterService` points at the leafmost route, it can be traversed to discover everything about all active routes. As a convenience, `RouteInfo` also implements `Enumerable` over all the reachable `RouteInfos` from topmost to leafmost. This makes it possible to say things like: 231 | 232 | ```js 233 | router.currentRoute.find(info => info.name === 'people').params 234 | ``` 235 | 236 | ## RouteInfoWithAttributes 237 | 238 | This type is almost identical to `RouteInfo`, except it has one additional property named `attributes`. The attributes contain the data that was loaded for this route, which is typically just `{ model }`. 239 | 240 | ## Transition Object 241 | 242 | A `transition` argument is passed to `Route#beforeModel`, `Route#model`, `Route#afterModel`, `Route#willTransition`, and `Router#willTransition`. Today `transition`'s public API is only really `abort()` and `retry()`. 243 | 244 | ### New Properties: `from` and `to` 245 | 246 | I'm proposing we add `from` and `to` properties on `transition` whose values are `RouteInfo` instances representing the initial and final leafmost routes for this transition. Like all RouteInfos, these are read-only and internally immutable. They are not observable, because a `transition` instance is never changed after creation. 247 | 248 | On an initial full-page load, the `from` property will be `null`. This creates a public API for distinguishing in-app transitions from full-page reloads. 249 | 250 | ### Example: testing whether route will remain active 251 | 252 | Here's an example showing how `willTransition` can figure out if the current route will remain active after the transition: 253 | 254 | ```js 255 | willTransition(transition) { 256 | if (!this.transition.to.find(route => route.name === this.routeName)) { 257 | alert("Please save or cancel your changes."); 258 | transition.abort(); 259 | } 260 | } 261 | ``` 262 | 263 | ### Example: parent redirecting to a fallback model 264 | 265 | Here's an example of a parent route that can redirect to a fallback model, without losing its child route: 266 | 267 | ```js 268 | this.route('person', { path: '/person/:person_id' }, function() { 269 | this.route('index'); 270 | this.route('detail'); 271 | }); 272 | 273 | //PersonRoute 274 | const fallbackPersonId = 0; 275 | model({ personId }, transition) { 276 | return this.get('store').find('person', personId).catch(err => { 277 | this.replaceWith(transition.to.name, fallbackPersonId); 278 | }); 279 | } 280 | 281 | // If personId 5 is invalid, and the user visits /person/5/detail, they will get 282 | // redirected to /person/0/detail. And /person/5 will get redirected to /person/0. 283 | ``` 284 | 285 | 286 | ### Actively discourage use of private API 287 | 288 | This RFC provides public API for doing the things people have become accustomed to doing via private API. To eliminate confusion over the correct way, we should hide all the private API away behind symbols, and provide deprecation warnings per our usual release policy around breaking "widely-used private APIs". 289 | 290 | Some of the private APIs we should mark and warn include: 291 | 292 | - transition.state 293 | - transition.params 294 | - `lookup('router:main')` (should use `service:router` instead) 295 | 296 | 297 | ## Dynamically-Scoped Route Info 298 | 299 | "The current route" is not a global value -- it varies from place to place within an application. Internally, Ember already models route info as a dynamically-scoped variable (currently named `outletState`). This RFC proposes publicly exposing that value in order to make things like `link-to` easier to implement directly on public primitives, and in order to enable stable public API for addons usage like `{{liquid-outlet}}`. 300 | 301 | We propose `get-route-info` for reading the current route info in handlebars: 302 | 303 | ```hbs 304 | {{!- retrieve the value of a dynamically scoped variable }} 305 | {{some-component currentRoute=(get-route-info)}} 306 | ``` 307 | 308 | We propose `readsRouteInfo` for defining a component that reads route info: 309 | 310 | ```js 311 | let MyComponent = Ember.Component.extend({ 312 | didInsertElement() { 313 | // Accessing routInfo here is intended to be indistinguishable 314 | // from a normal, explicitly-passed input argument. 315 | doSomethingWith(this.get('routeInfo')); 316 | } 317 | }); 318 | MyComponent.reopenClass({ 319 | // This is where we declare that we need access to routeInfo 320 | readsRouteInfo: true 321 | }); 322 | ``` 323 | 324 | And `readsRouteInfo` also works on `Helper`: 325 | 326 | ```js 327 | let MyHelper = Ember.Helper.extend({ 328 | compute(params, hash) { 329 | // routeInfo is indistinguishable from a normally-passed hash argument 330 | return doSomethingWith(hash.routeInfo); 331 | } 332 | }); 333 | MyHelper.reopenClass({ 334 | readsRouteInfo: true 335 | }); 336 | ``` 337 | 338 | We propose the `#with-route-info` keyword for setting a new route info: 339 | 340 | ```hbs 341 | {{#with-route-info someValue}} 342 | {{!- 343 | within this block AND ALL ITS DESCENDANTS until 344 | otherwise overridden by another set-route-info statement, 345 | `get-route-info` returns someValue. 346 | -}} 347 | {{/with-route-info}} 348 | ``` 349 | 350 | Note that there is no `set-route-info`. You can only introduce new scopes, not mutate your containing scope. There is also no way to set routeInfo directly from Javascript -- your component must use a `with-route-info` block within its handlebars template. 351 | 352 | ### routeInfo's type, and examples 353 | 354 | The value returned from `get-route-info` and acceptd by `with-route-info` is always a `RouteInfoWithAttributes` object. This enables several nice things, which I will illustrate with examples: 355 | 356 | 1. Here is a simplified `is-active` helper that will always update at the appropriate time to match exactly what is rendered in the current outlet. It will maintain the correct state even during animations. Instead of injecting the router service, it consumes the `routeInfo` from its containing environment: 357 | 358 | ```js 359 | Ember.Helper.extend({ 360 | compute([routeName], { routeInfo }) { 361 | return !!routeInfo.find(info => info.name === routeName); 362 | } 363 | }).reopenClass({ 364 | readsRouteInfo: true 365 | }); 366 | ``` 367 | 368 | A more complete version that also matches models and queryParams can be written in the same way. 369 | 370 | 2. We can improve `link-to` so that it always finds implicit model arguments from the local context, rather than trying to locate them on the global router service. This will fix longstanding bugs like https://github.com/ember-animation/liquid-fire/issues/347 and it will make it easier to test components that contain `{{link-to}}`. This would also open the door to relative link-tos. 371 | 372 | 3. `liquid-outlet` can be implemented entirely via public API. It would become: 373 | 374 | ```hbs 375 | {{#liquid-bind (get-route-info) as |currentRouteInfo|}} 376 | {{#with-route-info currentRouteInfo}} 377 | {{outlet}} 378 | {{/with-route-info}} 379 | {{/liquid-bind}} 380 | ``` 381 | 382 | 4. Prerendering of non-current routes becomes possible. You can use `recognizeAndLoad` to obtain a `RouteInfoWithAttributes` and then use `{{#with-route-info myRouteInfo}} {{outlet}} {{/with-route-info}}` to render it. 383 | 384 | 385 | # Drawbacks 386 | 387 | This RFC deprecates only two public extension hooks API, so the API-churn burden may appear low. However, we know that use of the private APIs we're deliberately disabling is widespread, so users will experience churn. We can provide our usual deprecation cycle to give them early warning, but it still imposes some cost. 388 | 389 | This RFC doesn't attempt to change the existing and fairly rich semantics for initiating transitions. For example, you can pass either models or IDs, and those have subtle semantic differences. I think an ideal rewrite would also change the semantics of the route hooks and transitionTo to simplify that area. 390 | 391 | # Alternatives 392 | 393 | ## Less Churn 394 | 395 | We could adopt some of the existing broadly used APIs as de-facto public. This avoids churn, but imposes a complexity burden on every new learner, who needs to be told "this is a weird API, but it's what we're stuck with". 396 | 397 | ## Semver Lawyering 398 | 399 | I'm interpreting router.js's public/private documentation as out-of-scope for Ember's semver. The fact that we pass an instance of router.js's Transition as our `transition` argument is not documented. An alternative interpretation is that we need to continue supporting those methods marked as public in router.js's docs. 400 | 401 | ## Optional Helpers 402 | 403 | I didn't propose shipping `is-active` and `url-for` template helpers -- I merely showed that they're easy to build using the router service. But we should arguably just ship them as part of the framework too. 404 | 405 | ## Branching Route Hierarchies 406 | 407 | I am implicitly assuming we will only ever have linear route hierarchies, where a given route has at most one child. I can imagine eventually wanting a way to support branching route hierarchies, where each branch can transition independently. I'm not trying to account for that future. 408 | 409 | ## Route.parentRoute 410 | 411 | This RFC makes it possible for a route to determine its parent's name dynamically via public API, and thus access its parent's model/params/controller: 412 | 413 | ```js 414 | beforeModel(transition) { 415 | const parentInfo = transition.to.find(info => info.name === this.routeName).parent; 416 | const parentModel = this.modelFor(parentInfo.name); 417 | } 418 | ``` 419 | 420 | However, this pattern feels awkward, and I think it justifies just adding a public `parentRouteName()` method to `Route` that would simplify to: 421 | 422 | ```js 423 | beforeModel(transition) { 424 | const parentModel = this.modelFor(this.parentRouteName()); 425 | } 426 | ``` 427 | Possibly we *want* this to feel awkward because it's a weird thing to do. 428 | 429 | ## Naming of Ember.DEFAULT_VALUE Symbol 430 | 431 | Should we introduce new API via the `Ember` global and switch to a module export once all the rest of Ember does, or should we just start with a module export right now? If so, what module? 432 | 433 | import { DEFAULT_VALUE } from 'ember-routing'; 434 | 435 | -------------------------------------------------------------------------------- /text/0101-ember-data-friendly-errors.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2015-10-23 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/101 3 | - Ember Issue: https://github.com/emberjs/data/pull/3930 4 | 5 | # Summary 6 | 7 | Add more illustrative detail to the default Ember Data Adapter Errors. 8 | 9 | # Motivation 10 | 11 | With a production Ember project, it's common to have many errors of the form "Adapter Error", 12 | originating from deep in the Ember Data stack and carrying little context about what the 13 | original error cause was. 14 | 15 | The intent is to add the original request URL, the response code, and some payload information 16 | to the default Error message for `DS.AdapterError`s. From there Errors can be handled or 17 | tracked as normal. 18 | 19 | # Detailed design 20 | 21 | I've been using something similar to the following Adapter (`friendly-error-adapter.js`): 22 | 23 | ```js 24 | import ActiveModelAdapter from 'active-model-adapter'; 25 | 26 | import DS from 'ember-data'; 27 | 28 | export default ActiveModelAdapter.extend({ 29 | 30 | ajax(url, method) { 31 | this.lastRequest = { 32 | url: url, 33 | method: method 34 | }; 35 | return this._super(...arguments); 36 | }, 37 | 38 | handleResponse: function (status, headers, payload) { 39 | let payloadContentType = headers["Content-Type"].split(";").get("firstObject"); 40 | let shortenedPayload; 41 | 42 | if (payloadContentType === "text/html" && payload.length > 250) { 43 | shortenedPayload = "[omitted long blob of HTML]"; 44 | } else { 45 | shortenedPayload = payload; 46 | } 47 | 48 | let errorMessage = `Ember Data Error (${this.lastRequest.method} ${this.lastRequest.url} returned a ${status}). \n Payload (${payloadContentType}): \n\n ${shortenedPayload}`; 49 | 50 | if (this.isSuccess(status, headers, payload)) { 51 | return payload; 52 | } else if (this.isInvalid(status, headers, payload)) { 53 | return new DS.InvalidError(payload.errors, errorMessage); 54 | } 55 | 56 | let errors = this.normalizeErrorResponse(status, headers, payload); 57 | 58 | return new DS.AdapterError(errors, errorMessage); 59 | } 60 | }); 61 | ``` 62 | 63 | (Note that the code inside the adapter could be MUCH simpler and cleaner, the above 64 | is a very quick hacked up example! :bomb:) 65 | 66 | The intent is to get an error message out of the form: 67 | 68 | 1. "Ember Data Error" 69 | 2. Request Method & URI 70 | 3. Response Status 71 | 4. Response Content Type 72 | 5. A sane representation of the Response payload 73 | 74 | # Drawbacks 75 | 76 | Adding complexity to an Error handler always runs the risk of generating errors inside 77 | the handler itself, which would not be overly friendly. 78 | 79 | # Alternatives 80 | 81 | There's probably quite a few different pieces of information that could be included 82 | in the message. 83 | 84 | We could also potentially look at attaching the extra information to other fields on 85 | the `AdapterError` (and its subclasses). The only drawback there would be that most 86 | error reporters would then not include that information by default. 87 | 88 | # Unresolved questions 89 | 90 | * Exact Error Message Format 91 | -------------------------------------------------------------------------------- /text/0120-route-serializers.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2016-02-11 2 | - RFC PR: [emberjs/rfcs#120](https://github.com/emberjs/rfcs/pull/120) 3 | - Ember Issue: https://github.com/emberjs/ember.js/pull/13016 4 | 5 | # Summary 6 | 7 | This RFC proposes replacing the existing [`Route#serialize`](http://emberjs.com/api/classes/Ember.Route.html#method_serialize) method with an equivalent method on the options hash passed into [`this.route` within `Router#map`](http://emberjs.com/api/classes/Ember.Router.html#method_map). The primary goal here is to enable asynchronous Engines by decoupling information about how to link to a route from the actual route object. 8 | 9 | # Motivation 10 | 11 | As we move towards an increasingly asynchronous world with Engines, we need to separate knowledge about how to _**link** to a route_ and how to _**enter** a route_. Linking to a route should be able to happen _before_ a route object is instantiated, which is the behavior needed to asynchronously load Engines. However, in our current reality, these concerns are coupled and a route object must be instantiated before being able to link to _or_ enter a route. 12 | 13 | By separating these concerns, we can preemptively load the information on how to link to a route without also requiring all the knowledge of how to enter that route. This would be beneficial in both the asynchronous and synchronous worlds by allowing us to defer work. 14 | 15 | Since the `serialize` method is the only method currently used by the `Route` class to define how to link to a route, the proposal is to extract this method into the space which currently contains the other linking information (e.g., the Router's map). 16 | 17 | _**Note:** this separation of concerns will also need to be implemented in router.js for the preemptive loading proposed here to actually work, but we can prepare for that future world by creating a separation of concerns within application code now._ 18 | 19 | # Detailed Design 20 | 21 | Since the current API is a simple function, the new hash option will also be a simple function that mirrors the signature of the original. Here's an example: 22 | 23 | ```js 24 | // app/router.js 25 | function serializePostRoute(model, params) { 26 | // serialize the model into the dynamic paths 27 | } 28 | 29 | export default Router.map(function() { 30 | this.route('post', { path: '/post/:id', serialize: serializePostRoute }); 31 | }); 32 | ``` 33 | 34 | Preserving the current function signature means that refactoring existing code should be simple in most cases. Here's the example currently given in the Ember docs (updated to reflect Ember-CLI): 35 | 36 | ```js 37 | // app/routes/post.js 38 | import Ember from 'ember'; 39 | export default Ember.Route.extend({ 40 | model(params) { 41 | // the server returns `{ id: 12 }` 42 | return Ember.$.getJSON('/posts/' + params.post_id); 43 | }, 44 | 45 | serialize(model) { 46 | // this will make the URL `/posts/12` 47 | return { post_id: model.id }; 48 | } 49 | }); 50 | 51 | // app/router.js 52 | export default Router.map(function() { 53 | this.route('post', { 54 | path: '/post/:id' 55 | }); 56 | }); 57 | ``` 58 | 59 | Here is that same code refactored for the proposal: 60 | 61 | ```js 62 | // app/routes/post.js 63 | import Ember from 'ember'; 64 | export default Ember.Route.extend({ 65 | model(params) { 66 | // the server returns `{ id: 12 }` 67 | return Ember.$.getJSON('/posts/' + params.post_id); 68 | } 69 | }); 70 | 71 | // app/router.js 72 | function serializePostRoute(model) { 73 | // this will make the URL `/posts/12` 74 | return { post_id: model.id }; 75 | } 76 | 77 | export default Router.map(function() { 78 | this.route('post', { path: '/post/:id', serialize: serializePostRoute }); 79 | }); 80 | ``` 81 | 82 | # Migration Plan 83 | 84 | Even though the refactoring needed here is easy, we still need a clear (though simple) migration plan. 85 | 86 | The first step will be to introduce the new option into the Router's callback `route` function. Once that is done, we can deprecate `Route#serialize` over the remainder of the 2.x series to give developers the time to update their code base. We can then remove support in 3.x. 87 | 88 | As noted in the "Motivation" section, there is still work to be done in router.js in order to support this separation of concerns. Due to this, the initial implementation of this new option will essentially be a polyfill that proxies to the corresponding `Route#serialize` property internally. This will set us up for an internal migration at a later point to actually separate the two; this, however, should not affect developers as it will be internal. 89 | 90 | # Pedagogy (How We Teach This) 91 | 92 | Once the new option is introduced, the Ember guides will need to be updated to reflect this. Those changes should be relatively straightforward as shown in the example above. This will help introduce the feature to new users and those users that haven't used `Route#serialize` before. Since inline serializers in the router map can be distracting to understanding the general layout of a codebase, we should teach them as defined outside the map itself (as in the code example in this RFC). 93 | 94 | For existing users, we can introduce this feature through deprecation warnings (as mentioned above). The deprecations should briefly introduce the new option and point to an appropriate deprecation guide that explains how to migrate. 95 | 96 | # Drawbacks 97 | 98 | - Adds another option to the Router map. Though this is largely mitigated due to the fact that this feature is not in wide use currently. 99 | - Can be sort of ugly to format. 100 | 101 | # Alternatives 102 | 103 | - Introduce a standalone module to represent the `Route#serialize`. This was the first proposal of this RFC and there is much opposition to introducing yet another construct for Ember-CLI and developers to manage. The approach proposed above avoids this major drawback. 104 | - Introduce a holistic construct to represent route linking information. Instead of introducing a new option as a function, we could introduce a class that would represent all the information needed to link to a route. Since there is not currently much other information needed, this seems overkill and would suffer similar opposition as the first alternative. 105 | - Don't do this and continue loading and instantiating all route information upfront. This prevents us from improving performance by keeping concerns coupled with prevents introducing async engines. It also requires all Route classes be instantiaed upfront. 106 | 107 | # Unresolved Questions 108 | 109 | - Do we wish to apply a similar approach for default query params? And if so, do we wish to incorporate that approach into this new construct? 110 | -------------------------------------------------------------------------------- /text/0136-contains-to-includes.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2016-04-16 2 | - RFC PR: https://github.com/emberjs/rfcs/pull/136 3 | - Ember Issue: https://github.com/emberjs/ember.js/pull/13553 4 | 5 | # Summary 6 | 7 | [`contains`](http://emberjs.com/api/classes/Ember.Array.html#method_contains) is 8 | implemented on `Ember.Array`, but [contains was renamed to includes in 2014] 9 | (https://github.com/tc39/Array.prototype.includes/commit/4b6b9534582cb7991daea3980c26a34af0e76c6c) 10 | - this proposal is for `contains` to be deprecated in favour of an `includes` 11 | method on `Ember.Array` 12 | 13 | # Motivation 14 | 15 | Motivation is to stay in line with web standards 16 | 17 | # Detailed design 18 | 19 | First, implement `includes` polyfill in compliance with `includes` spec. Polyfill 20 | sample from MDN is: 21 | 22 | ```js 23 | if (!Array.prototype.includes) { 24 | Array.prototype.includes = function(searchElement /*, fromIndex*/ ) { 25 | 'use strict'; 26 | var O = Object(this); 27 | var len = parseInt(O.length) || 0; 28 | if (len === 0) { 29 | return false; 30 | } 31 | var n = parseInt(arguments[1]) || 0; 32 | var k; 33 | if (n >= 0) { 34 | k = n; 35 | } else { 36 | k = len + n; 37 | if (k < 0) {k = 0;} 38 | } 39 | var currentElement; 40 | while (k < len) { 41 | currentElement = O[k]; 42 | if (searchElement === currentElement || 43 | (searchElement !== searchElement && currentElement !== currentElement)) { // NaN !== NaN 44 | return true; 45 | } 46 | k++; 47 | } 48 | return false; 49 | }; 50 | } 51 | ``` 52 | 53 | Then, alias `contains` to `includes` with deprecation warning, deprecate in line with standard 54 | deprecation process. I don't believe that adding the additional parameter will 55 | have any affect on existing usage of `contains`. 56 | 57 | # How We Teach This 58 | 59 | * Update any references in docs and guides to `includes` 60 | * Write a deprecation guide, mentioning any edge cases where the new `includes` behaves differently to `contains`, and giving migration examples 61 | * Indicate in api docs that this is a polyfill 62 | 63 | # Drawbacks 64 | 65 | * May break existing apps 66 | * [Was considered before but was too early](https://github.com/emberjs/ember.js/issues/5670#issuecomment-64084814) 67 | 68 | # Alternatives 69 | 70 | Keep current methods 71 | 72 | # Unresolved questions 73 | 74 | None 75 | -------------------------------------------------------------------------------- /text/0139-isHtmlSafe.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2016-04-18 2 | - RFC PR: [#139](https://github.com/emberjs/rfcs/pull/139) 3 | - Ember Issue: [emberjs/ember.js#13666](https://github.com/emberjs/ember.js/pull/13666) 4 | 5 | # Summary 6 | 7 | Introduce `Ember.String.isHtmlSafe()` to provide a reliable way to determine if an object is an "html safe string", i.e. was it created with `Ember.String.htmlSafe()`. 8 | 9 | 10 | # Motivation 11 | 12 | Using `new Ember.Handlebars.SafeString()` is slated for deprecation. Many people are currently using the following snippet as 13 | a mechanism of type checking: `value instanceof Ember.Handlebars.SafeString`. Providing `isHtmlSafe` offers a 14 | cleaner method of detection. Beyond that, the aforementioned test is a bit leaky. It requires the developer to understand 15 | `htmlSafe` returns a `Ember.Handlerbars.SafeString` instance and thus limits Ember's ability to change 16 | `htmlSafe` without further breaking it's API. 17 | 18 | Based on our app at Batterii and some research on Github, I see two valid use cases for introducing this API. 19 | 20 | First, and most commonly, is to make it possible to test addon helpers that are expected to return a safe string. I believe this test on ember-i18n says it all: ["returns HTML-safe string"](https://github.com/jamesarosen/ember-i18n/blob/master/tests/unit/utils/i18n/default-compiler-test.js#L56-L59). 21 | 22 | The second use case is to do type checking. In our app, we have an `isString` utility that is effectively: 23 | 24 | ```javascript 25 | import Ember from 'ember'; 26 | 27 | export default function(value) { 28 | return typeof value === 'string' || value instanceof Ember.Handlebars.SafeString; 29 | } 30 | ``` 31 | 32 | Newer versions of ember-i18n, doing `this.get('i18n').t('someTranslatedValue')` will return a safe string. Thus our `isString` utility has to consider that. 33 | 34 | 35 | # Detailed design 36 | 37 | `isHtmlSafe` will be added to the `Ember.String` module. The implementation will basically be: 38 | 39 | ```javascript 40 | function isHtmlSafe(str) { 41 | return str && typeof str.toHTML === 'function'; 42 | } 43 | ``` 44 | 45 | It will be used as follows: 46 | 47 | ```javascript 48 | if (Ember.String.isHtmlSafe(str)) { 49 | str = str.toString(); 50 | } 51 | ``` 52 | 53 | 54 | # Transition Path 55 | 56 | As part of landing `isHtmlSafe` we will simultaneously re-deprecate `Ember.Handlebars.SafeString`. This deprecation will 57 | take care to ensure that `str instanceof Ember.Handlebars.SafeString` still passes so that we can continue to 58 | maintain backwards compatibility. 59 | 60 | Additionally, a polyfill will be implemented to help provide forward compatibility for addon maintainers and others 61 | looking to get a head while still on older versions of Ember. Similar to [ember-getowner-polyfill](https://github.com/rwjblue/ember-getowner-polyfill). 62 | 63 | 64 | # How We Teach This 65 | 66 | I think we'll continue to refer to these strings as "html safe strings". This RFC does not 67 | introduce any new concepts, rather it builds on an existing concept. 68 | 69 | I don't believe this feature will require guide discussion. I think API Docs will suffice. 70 | 71 | The concept of type checking is a pretty common programming idiom. It should be relatively self 72 | explanatory. 73 | 74 | 75 | # Drawbacks 76 | 77 | The only drawback I see is that it expands the surface of the API and it takes a step 78 | towards prompting "html safe string" as a thing. 79 | 80 | 81 | # Alternatives 82 | 83 | An alternative would be to expose `Ember.Handlerbars.SafeString` publicly once again. Users 84 | could revert back to using `instanceof` as their type checking mechanism. 85 | 86 | 87 | # Unresolved questions 88 | 89 | There are no unresolved questions at this time. 90 | -------------------------------------------------------------------------------- /text/0150-factory-for.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2016-06-11 2 | - RFC PR: [#150](https://github.com/emberjs/rfcs/pull/150) 3 | - Ember Issue: [#14360](https://github.com/emberjs/ember.js/pull/14360) 4 | 5 | # Summary 6 | 7 | With the goal of making significant performance improvements and of adding 8 | public API to support use cases long-served by a private API, a new API of 9 | `factoryFor` will be added to `ApplicationInstance` instances. 10 | 11 | # Motivation 12 | 13 | Ember's dependency injection container has long supported fetching a factory 14 | that will be created with any injections present. Using the private API that 15 | provided this support allows an instance of the factory to be created 16 | with initial values passed via `create`. For example: 17 | 18 | ```js 19 | // app/logger/main.js 20 | import Ember from 'ember'; 21 | 22 | export default Ember.Logger.extend({ 23 | someService: Ember.inject.service() 24 | }); 25 | ``` 26 | 27 | ```js 28 | import Ember from 'ember'; 29 | const { Component, getOwner } = Ember; 30 | 31 | export default Component.extend( 32 | init() { 33 | this._super(...arguments); 34 | let Factory = getOwner(this)._lookupFactory('logger:main'); 35 | this.logger = Factory.create({ level: 'low' }); 36 | } 37 | }); 38 | ``` 39 | 40 | In this API, the `Factory` is actually a subclass the original main logger 41 | class. When `_lookupFactory` is called, an additional `extend` takes place 42 | to add any injections (such as `someService` above). The class/object setup 43 | looks like this: 44 | 45 | * In the module: `MyClass = Ember.Object.extend(` 46 | * In `_lookupFactory`: `MyFactoryWithInjections = MyClass.extend(` 47 | * And when used: `MyFactoryWithInjections.create(` 48 | 49 | The second call to `extend` implements Ember's owner/DI 50 | framework and permits `someService` to be resolved later. The "owner" object 51 | is merged into the new `MyFactoryWithInjections` class along with any 52 | registered injections. 53 | 54 | This "double extend" (once at define time, once at `_lookupFactory` time) 55 | takes a toll on performance booting an app. This design flaw has motivated 56 | a desire to keep `_lookupFactory` private. 57 | 58 | The `MyFactoryWithInjections` class also features as a cache. Because it is 59 | attached to the owner/container, it is cleared between test runs or 60 | application instances. To illustrate, this flow-chart shows how 61 | `MyFactoryWithInjections` diverges between tests: 62 | 63 | ``` 64 | +-------------------------------+ 65 | | | 66 | | /app/models/foo.js | 67 | | | 68 | +-------------------------------+ 69 | | 70 | first test run | nth test run 71 | +----------------+---------------+ 72 | | | 73 | v v 74 | +---------------------+ +--------------------+ 75 | |resolve('model:foo') | === |resolve('model:foo')| 76 | +---------------------+ +--------------------+ 77 | | | 78 | | | 79 | v v 80 | 81 | extend(injections) extend(injections) 82 | 83 | | | 84 | | | 85 | | | 86 | v v 87 | +--------------------------+ +---------------------------+ 88 | |lookupFactory('model:foo')| !== |lookupFactory('model:foo') | 89 | +--------------------------+ +---------------------------+ 90 | ``` 91 | 92 | Despite the design flaws in this API, it does fill a meaningful role in 93 | Ember's DI solution. Use of the private API is common. Some examples: 94 | 95 | * [ember-cart](https://github.com/DockYard/ember-cart) uses the functionality to create model objects without 96 | tying them to the store [example a](https://github.com/DockYard/ember-cart/blob/c01eb22eaf2e97f8c80481c3174d4be917e476a9/tests/dummy/app/controllers/application.js#L16), 97 | [example b](https://github.com/DockYard/ember-cart/blob/c01eb22eaf2e97f8c80481c3174d4be917e476a9/tests/dummy/app/models/dog.js#L11) 98 | * Ember-Data's [`modelFactoryFor`](https://github.com/emberjs/data/blob/54ea432b1cbb0d1231d9a0454b09d3b3a0bc2533/addon/-private/system/store.js#L1868) 99 | 100 | The goal of this RFC is to create a public API for fetching factories with 101 | better performance characteristics than `_lookupFactory`. 102 | 103 | # Detailed design 104 | 105 | Throughout this document I reference Ember 2.12 as it is the next LTS at writing. This 106 | proposal may ship for 2.12-LTS or be bumped to the next LTS. 107 | 108 | This feature will be added in these steps. 109 | 110 | 1. In Ember introduce a `ApplicationInstance#factoryFor` based on 111 | `_lookupFactory`. It should be documented that certain behaviors 112 | inherent to "double extend" are not supported. In development builds 113 | and supporting browsers, wrap return values in a Proxy. The proxy should 114 | throw an error when any property besides `create` or `class` is accessed. 115 | `class` must return the registered factory, not the double extended factory. 116 | 2. In the same release add a deprecation message to usage of `_lookupFactory`. 117 | As this API is intimate it must be maintained through at least one LTS 118 | release (2.12 at this writing). 119 | 3. In 2.13 drop `_lookupFactory` and migrate the `factoryFor` implementation to avoid 120 | "double-extend" entirely. 121 | 122 | Additionally, a polyfill will be released for this feature supporting prior 123 | versions of Ember. 124 | 125 | #### Design of `ApplicationInstance#factoryFor` 126 | 127 | A new API will be introduced. This API will return both the original base 128 | class registered into or resolved by the container, and will also return a function 129 | to generate a dependency-injected instance. For example: 130 | 131 | ```js 132 | import Ember from 'ember'; 133 | const { Component, getOwner } = Ember; 134 | 135 | export default Component.extend( 136 | init() { 137 | this._super(...arguments); 138 | let factory = getOwner(this).factoryFor('logger:main'); 139 | this.logger = factory.create({ level: 'low' }); 140 | } 141 | }); 142 | ``` 143 | 144 | Unlike `_lookupFactory`, `factoryFor` will not return an extended class with 145 | DI applied. Instead it will return a factory object with two properties: 146 | 147 | ```js 148 | // factoryFor returns: 149 | let { 150 | 151 | // a function taking an argument of initial properties passed to the object 152 | // and returning an instance 153 | create, 154 | 155 | // The class registered into (or resolved by) the container 156 | class 157 | 158 | } = owner.factoryFor('type:name'); 159 | ``` 160 | 161 | This API should meet two requirements of the use-cases described in 162 | "Motivation": 163 | 164 | * Because `factoryFor` only returns a `create` method and reference to the 165 | original class, its internal implementation can diverge away from the 166 | "double extend". A side-effect of this is that the 167 | class of an object instantiated via `_lookupFactory(name).create()` 168 | and `factoryFor(name).create()` may not be the same, given the 169 | same original factory. 170 | * The presence of `class` will make it easy to identify the base class of the 171 | factory at runtime. 172 | 173 | For example today's `_lookupFactory` creates an inheritance structure like 174 | the following: 175 | 176 | ``` 177 | Current: 178 | +-------------------------------+ 179 | | | 180 | | /app/models/foo.js | 181 | | | 182 | +-------------------------------+ 183 | | 184 | | 185 | | 186 | v 187 | +--------------------+ 188 | | Class[model/Foo] | 189 | +--------------------+ 190 | | 191 | | 192 | | 193 | first test run | nth test run 194 | +-----------+----------+ 195 | | | 196 | | | 197 | | | 198 | v v 199 | +--------------------+ +--------------------+ 200 | | subclass of | | subclass of | 201 | | Class[model/Foo] | | Class[model/Foo] | 202 | +--------------------+ +--------------------+ 203 | ``` 204 | 205 | Between test runs 2 instances of `model:foo` will have a common 206 | shared ancestor the grandparent `Class[model/Foo]`. 207 | 208 | This implementation of `factoryFor` proposes to remove the intermediate 209 | subclass and instead have a generic 210 | factory object which holds the injections and allows for injected instances 211 | to be created. The resulting object graph would look something like this: 212 | 213 | ``` 214 | Proposed: 215 | +-------------------------------+ 216 | | | 217 | | /app/models/foo.js | 218 | | | 219 | +-------------------------------+ 220 | | 221 | | 222 | | 223 | v 224 | +--------------------+ 225 | | Class[model/Foo] | 226 | +--------------------+ 227 | | 228 | | 229 | | 230 | first test run | nth test run 231 | +----------+-----------+ 232 | | | 233 | | | 234 | | | 235 | v v 236 | +--------------------+ +--------------------+ 237 | | Factory of | | Factory of | 238 | | Class[model/Foo] | | Class[model/Foo] | 239 | +--------------------+ +--------------------+ 240 | ``` 241 | 242 | With `factoryFor` instances of `model:foo` will share a common constructor. 243 | Any state stored on the constructor would of course leak between the tests. 244 | 245 | An example implementation of `factoryFor` can be reviewed [on this GitHub 246 | comment](https://github.com/emberjs/rfcs/issues/125#issuecomment-193827658). 247 | 248 | ##### Implications for `owner.register` 249 | 250 | Currently, factories registered into Ember's DI system are required to 251 | provide an `extend` method. Removing support for extend-based DI in `_lookupFactory` 252 | will permit factories without `extend` to be registered. Instead factories 253 | must only provide a `create` method. For example: 254 | 255 | ```js 256 | let factory = { 257 | create(options={}) { 258 | /* Some implementation of `create` */ 259 | return Object.create({}); 260 | } 261 | }; 262 | owner.register('my-type:a-factory', factory); 263 | let factoryWithDI = owner.factoryFor('my-type:a-factory'); 264 | 265 | factoryWithDI.class === factory; 266 | ``` 267 | 268 | ##### Development-mode Proxy 269 | 270 | Because many developers will simply re-write `_lookupFactory` to `factoryFor`, 271 | it is important to provide some aid and ensure they actually complete the 272 | migration completely (they they avoid setting state on the factory). A proxy 273 | wrapping the return value of `factoryFor` and raising assertions when any 274 | property besides `create` or `class` is accessed will be added in development. 275 | 276 | Additionally, using `instanceof` on the result of `factoryFor` should be 277 | disallowed, causing an exception to be raised. 278 | 279 | A good rule of thumb is that, in development, using anything besides `class` or 280 | `create` on the return value of `factoryFor` should fail with a helpful message. 281 | 282 | ##### Releasing a polyfill 283 | 284 | A polyfill addon, similar to [ember-getowner-polyfill](https://github.com/rwjblue/ember-getowner-polyfill) 285 | will be released for this feature. This polyfill will provide the `factoryFor` 286 | API going back to at least 2.8, provide the API and silence the deprecation 287 | in versions before `factoryFor` is available, and be a no-op in versions where 288 | `factoryFor` is available. 289 | 290 | # How We Teach This 291 | 292 | This feature should be introduced along side `lookup` in the 293 | [relevant guide](https://guides.emberjs.com/v2.6.0/applications/dependency-injection/). 294 | The return value of `factoryFor` should be taught as a POJO and not as 295 | an extended class. 296 | 297 | #### Example deprecation guide: Migrating from `_lookupFactory` to `factoryFor` 298 | 299 | Ember owner objects have long provided an intimate API used to 300 | fetch a factory with dependency injections. This API, `_lookupFactory`, is deprecated 301 | in Ember 2.12 and will be removed in Ember 2.13. To ease the transition to this 302 | new public API, a polyfill is provided with support back to at least Ember 2.8. 303 | 304 | `_lookupFactory` returned the class of resolved factory extended with 305 | a mixin containing its injections. For example: 306 | 307 | ```js 308 | let factory = Ember.Object.extend(); 309 | owner.register('my-type:a-name', factory); 310 | let klass = owner._lookupFactory('my-type:a-name'); 311 | klass.constructor.superclass === factory; // true 312 | let instance = klass.create(); 313 | ``` 314 | 315 | `factoryFor` instead returns an object with two properties: `create` and `class`. 316 | For example: 317 | 318 | ```js 319 | let factory = Ember.Object.extend(); 320 | owner.register('my-type:a-name', factory); 321 | let klass = owner.factoryFor('my-type:a-name'); 322 | klass.class === factory; // true 323 | let instance = klass.create(); 324 | ``` 325 | 326 | A common use-case for `_lookupFactory` was to fetch an factory with 327 | specific needs in mind: 328 | 329 | * The factory needs to be created with initial values (which cannot be 330 | provided at create-time via `lookup`. 331 | * The instances of that factory need access to Ember's DI framework (injections, 332 | registered dependencies). 333 | 334 | For example: 335 | 336 | ```js 337 | // app/widgets/slow.js 338 | import Ember from 'ember'; 339 | 340 | export default Ember.Object.extend({ 341 | // this instance requires access to Ember's DI framework 342 | store: Ember.inject.service(), 343 | 344 | convertToModel() { 345 | this.get('store').createRecord('widget', { 346 | widgetType: 'slow', 347 | name, canWobble 348 | }); 349 | } 350 | 351 | }); 352 | ``` 353 | 354 | ```js 355 | // app/services/widget-manager.js 356 | import Ember from 'ember'; 357 | 358 | export default Ember.Service.extend({ 359 | 360 | init() { 361 | this.set('widgets', []); 362 | }, 363 | 364 | /* 365 | * Create a widget of a type, and add it to the widgets array. 366 | */ 367 | addWidget(type, name, canWobble) { 368 | let owner = Ember.getOwner(this); 369 | // Use `_lookupFactory` so the `store` is accessible on instances. 370 | let WidgetFactory = owner._lookupFactory(`widget:${type}`); 371 | let widget = WidgetFactory.create({name, canWobble}); 372 | this.get('widgets').pushObject(widget); 373 | return widget; 374 | } 375 | 376 | }); 377 | ``` 378 | 379 | For these common cases where only `create` is called on the factory, migration 380 | to `factoryFor` is mechanical. Change `_lookupFactory` to `factoryFor` in the 381 | above examples, and the migration would be complete. 382 | 383 | ##### Migration of static method calls 384 | 385 | Factories may have had static methods or properties that were being accessed 386 | after resolving a factory with `_lookupFactory`. For example: 387 | 388 | ```js 389 | // app/widgets/slow.js 390 | import Ember from 'ember'; 391 | 392 | const SlowWidget = Ember.Object.extend(); 393 | SlowWidget.reopenClass({ 394 | SPEEDS: [ 395 | 'slow', 396 | 'verySlow' 397 | ], 398 | hasSpeed(speed) { 399 | return this.SPEEDS.contains(speed); 400 | } 401 | }); 402 | 403 | export default SlowWidget; 404 | ``` 405 | 406 | ```js 407 | let factory = owner._lookupFactory('widget:slow'); 408 | factory.SPEEDS.length; // 2 409 | factory.hasSpeed('slow'); // true 410 | ``` 411 | 412 | With `factoryFor`, access to these methods or properties should be done via 413 | the `class` property: 414 | 415 | ```js 416 | let factory = owner.factoryFor('widget:slow'); 417 | let klass = factory.class; 418 | klass.SPEEDS.length; // 2 419 | klass.hasSpeed('slow'); // true 420 | ``` 421 | 422 | # Drawbacks 423 | 424 | The main drawback to this solution is the removal of double extend. Double 425 | extend is a performance troll, however it also means if a single class is registered 426 | multiple times each `_lookupFactory` returns a unique factory. It is plausible 427 | that some use-case relying on this behavior would get trolled in the migration 428 | to `factoryFor`, however it is unlikely. 429 | 430 | For example these cases where state is stored on the factory would no 431 | longer be scope to one instance of the owner (like one test). Instead, setting 432 | a value on the class would set it on the registered class. 433 | 434 | Some real-world examples of setting state on the factory class: 435 | 436 | - ember-model 437 | - https://github.com/ebryn/ember-model/blob/master/packages/ember-model/lib/model.js#L404 and https://github.com/ebryn/ember-model/blob/master/packages/ember-model/lib/model.js#L457 438 | with `factoryFor` will increment a shared counter across application and 439 | container instances. 440 | - https://github.com/ebryn/ember-model/blob/master/packages/ember-model/lib/model.js#L723-L725 441 | would also set properties on the base `Ember.Model` factory instead of 442 | an extension of that class. 443 | - ember-data 444 | - If attrs change between test runs (seems very unlikely) then https://github.com/emberjs/data/blob/387630db5e7daec6aac7ef8c6172358a3bd6394c/addon/-private/system/model/attr.js#L57 445 | would be affected. The CP of `attributes` will have a value cached on the 446 | factory, and where with `_lookupFactory`'s double-extend the cache would be 447 | on the extended class, in `factoryFor` that CP cache will be on the 448 | class registered as a factory. 449 | - Any other of the following: 450 | - `lookupFactory(x).reopen` / `reopenClass` at runtime (or test time to monkey patch code) 451 | - `lookupFactory(x).something = value` 452 | 453 | # Alternatives 454 | 455 | More aggressive timelines have been considered for this change. 456 | 457 | However we have considered the possibility that removing `_lookupFactory` in 2.13 458 | (something LTS technically permits) would be too aggressive for the 459 | community of addons. Providing a polyfill is part of the strategy to handle 460 | this change. 461 | 462 | # Unresolved questions 463 | 464 | Are there any use-cases for the double extend not considered? 465 | -------------------------------------------------------------------------------- /text/0186-track-unique-history-location-state.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2016/12/05 2 | - RFC PR: (leave this empty) 3 | - Ember Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | Track unique history location states 8 | 9 | # Motivation 10 | 11 | The path alone does not provide enough information. For example, if you 12 | visit page A, scroll down, then click on a link to page B, then click on 13 | a link back to page A. Your actual browser history stack is [A, B, A]. 14 | Each of those nodes in the history should have their own unique scroll 15 | position. In order to record this position we need a UUID 16 | for each node in the history. 17 | 18 | This API will allow other libraries to reflect upon each location to 19 | determine unique state. For example, 20 | [ember-router-scroll](https://github.com/dollarshaveclub/ember-router-scroll) 21 | is making use of a [modified `Ember.HistoryLocation` object to get this 22 | behavior](https://github.com/dollarshaveclub/ember-router-scroll/blob/master/addon/locations/router-scroll.js). 23 | 24 | Tracking unique state is required when setting the scroll position 25 | properly based upon where you are in the history stack, as described in 26 | [Motivation](#motivation) 27 | 28 | # Detailed design 29 | 30 | Code: [PR#14011](https://github.com/emberjs/ember.js/pull/14011) 31 | 32 | We simply unique identifier (UUID) so we can track uniqueness on two 33 | dimensions. Both `path` and the generated `uuid`. A simple UUID 34 | generator such as 35 | https://gist.github.com/lukemelia/9daf074b1b2dfebc0bd87552d0f6a537 36 | should suffice. 37 | 38 | # How We Teach This 39 | 40 | We could describe what meta data is generated for each location in the 41 | history stack. For example, it could look like: 42 | 43 | ```js 44 | // visit page A 45 | 46 | [ 47 | { path: '/', uuid: 1 } 48 | ] 49 | 50 | // visit page B 51 | 52 | [ 53 | { path: '/about', uuid: 2 }, 54 | { path: '/', uuid: 1 } 55 | ] 56 | 57 | // visit page A 58 | 59 | [ 60 | { path: '/', uuid: 3 }, 61 | { path: '/about', uuid: 2 }, 62 | { path: '/', uuid: 1 } 63 | ] 64 | 65 | // click back button 66 | 67 | [ 68 | { path: '/about', uuid: 2 }, 69 | { path: '/', uuid: 1 } 70 | ] 71 | ``` 72 | 73 | # Drawbacks 74 | 75 | * The property access is writable 76 | 77 | # Alternatives 78 | 79 | The purpose for this behavior is to enable scroll position libraries. 80 | There are two other solutions in the wild. One is in the guides that 81 | suggests resetting the scroll position to `(0, 0)` on each new route 82 | entry. The other is 83 | [ember-memory-scroll](https://github.com/ef4/memory-scroll) which I 84 | believe is better suited for tracking scroll positions for components 85 | rather than the current page. 86 | 87 | However, in both cases neither solution provides the experience that 88 | users have come to expect from server-rendered pages. The browser tracks 89 | scroll position and restores it when you revisit the page in the history 90 | stack. The scroll position is unique even if you have multiple instances 91 | of the same page in the stack. 92 | 93 | # Unresolved questions 94 | 95 | None at this time. 96 | -------------------------------------------------------------------------------- /text/0191-deprecate-component-lifecycle-hook-args.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2016-12-14 2 | - RFC PR: [#191](https://github.com/emberjs/rfcs/pull/191) 3 | - Ember Issue: [#14711](https://github.com/emberjs/ember.js/pull/14711) 4 | 5 | # Summary 6 | 7 | We would like to deprecate and remove the **arguments** passed to the `didInitAttrs`, `didReceiveAttrs` and `didUpdateAttrs` component lifecycle hooks. These arguments are currently undocumented on purpose and considered a private API, imposes an unnecessary performance hit on *all* components whether they are used or not, and can be easily replicated by the users in cases where they are needed. 8 | 9 | # Motivation 10 | 11 | In the road leading up to Ember.js 2.0, [new lifecycle hooks](http://emberjs.com/blog/2015/06/12/ember-1-13-0-released.html#toc_component-lifecycle-hooks) were introduced to components in order to help users shift to a new mental model, dubbed Data Down Actions Up. The hooks were introduced by name, and their semantics explained, but there were no mentions of possible arguments received by them. 12 | 13 | This lack of documentation for lifecycle hook arguments was deliberate. The hooks were introduced as an experiment with an eye to the then-upcoming angle bracket components, so the arguments to the hooks were considered private by the framework maintainers, as their design was still ongoing. 14 | 15 | However, references to the lifecycle hook arguments started appearing in community resources. Users started betting on these arguments as the way forward, which in conjunction with the lack of an RFC process and clear messaging from the Ember.js maintainers lead to confusion. 16 | 17 | This left the core team in a difficult position. Despite no longer endorsing lifecycle hook arguments, trying to communicate such could have the reverse effect by pointing a spotlight at them. The purpose of this RFC is then to clarify that lifecycle hook arguments have no future in the framework, and you should update your code to not make use of them. 18 | 19 | The reason to officially deprecate lifecycle hook arguments is not only about messaging, but also because providing these arguments imposes an unnecessary performance penalty to every component in your application even if the arguments are not used. 20 | 21 | To provide the arguments to the lifecycle hooks, Ember.js has to eagerly "reify" and save-off any passed-in attributes to allow diffing and construct several wrapper objects. In the few occasions where this logic is actually necessary, developers should be able to use programmatic patterns familiar to them and manually track changes as needed, as exemplified in the Transition Path section below. 22 | 23 | # Transition Path 24 | 25 | The transition path followed will be the standard one, which encompasses using the deprecation API to deprecate the feature and the related deprecation guide. While the lifecycle hooks share a deprecation identifier, they will be addressed in turn. 26 | 27 | ### `didInitAttrs` 28 | 29 | Since this lifecycle hook is [already deprecated](http://emberjs.com/deprecations/v2.x/#toc_ember-component-didinitattrs), we suggest taking this chance to address two deprecations at the same time. Imagine you have a component that stores a timestamp when it's initialized for later comparison. 30 | 31 | Before: 32 | 33 | ``` javascript 34 | Ember.Component.extend({ 35 | didInitAttrs({ attrs }) { 36 | this.set('initialTimestamp', attrs.timestamp); 37 | } 38 | }); 39 | ``` 40 | After: 41 | 42 | ``` javascript 43 | Ember.Component.extend({ 44 | init() { 45 | this._super(...arguments); 46 | 47 | this.set('initialTimestamp', this.get('timestamp')); 48 | } 49 | }); 50 | ``` 51 | ### `didReceiveAttrs` 52 | 53 | Let's say you want to animate a map widget from the old coordinates to the new coordinates. 54 | 55 | Before: 56 | 57 | ``` javascript 58 | Ember.Component.extend({ 59 | didReceiveAttrs({ oldAttrs, newAttrs }) { 60 | if (oldAttrs && oldAttrs.coordinates !== newAttrs.coordinates) { 61 | this.map.move({ from: oldAttrs.coordinates, to: newAttrs.coordinates }); 62 | } 63 | } 64 | }); 65 | ``` 66 | After: 67 | 68 | ``` javascript 69 | Ember.Component.extend({ 70 | didReceiveAttrs() { 71 | let oldCoordinates = this.get('_previousCoordinates'); 72 | let newCoordinates = this.get('coordinates'); 73 | 74 | if (oldCoordinates && oldCoordinates !== newCoordinates) { 75 | this.map.move({ from: oldCoordinates, to: newCoordinates }); 76 | } 77 | 78 | this.set('_previousCoordinates', newCoordinates); 79 | } 80 | }); 81 | ``` 82 | ### `didUpdateAttrs` 83 | 84 | This hook is very similar to `didReceiveAttrs`, except it only runs on re-renders and not the initial render. 85 | 86 | Before: 87 | 88 | ``` javascript 89 | Ember.Component.extend({ 90 | didUpdateAttrs({ oldAttrs, newAttrs }) { 91 | if (oldAttrs && oldAttrs.coordinates !== newAttrs.coordinates) { 92 | this.map.move({ from: oldAttrs.coordinates, to: newAttrs.coordinates }); 93 | } 94 | } 95 | }); 96 | ``` 97 | After: 98 | 99 | ``` javascript 100 | Ember.Component.extend({ 101 | didUpdateAttrs() { 102 | let oldCoordinates = this.get('_previousCoordinates'); 103 | let newCoordinates = this.get('coordinates'); 104 | 105 | if (oldCoordinates && oldCoordinates !== newCoordinates) { 106 | this.map.move({ from: oldCoordinates, to: newCoordinates }); 107 | } 108 | 109 | this.set('_previousCoordinates', newCoordinates); 110 | } 111 | }); 112 | ``` 113 | # How We Teach This 114 | 115 | Due to the previous undocumented nature of the arguments, there is no official documentation that will require updating deprecated usage. 116 | 117 | As required for framework deprecations, there will be a deprecation guide written up and linked from within the deprecation message. This deprecation guide will address the more common usage patterns associated with lifecycle hook arguments, such as the Transition Path example. 118 | 119 | Additionally, the usage patterns present in the deprecation guide could also be documented in the component section of the official Guides, as a proactive approach for teaching newcomers. 120 | 121 | # Drawbacks 122 | 123 | One immediate drawback of this proposal is that due to references to the arguments in community resources, there are uses of them in the wild. Updating deprecated code will have to be done mostly manually, as automation might prove difficult. 124 | 125 | Another drawback is that by the very nature of publishing this RFC, attention will be drawn to the arguments. It is our hope that the increased awareness will be a net positive due to the clear guidance gained by users of the framework. 126 | 127 | It is then our assessment that these drawbacks are outweighed by the benefits of the change. 128 | 129 | # Alternatives 130 | 131 | There are two standout alternatives to the proposal presented here which are doing nothing, or making the arguments public and supporting them going forward, both of which are less than ideal for reasons stated previously. 132 | 133 | Doing nothing would perpetuate the confusion surrounding lifecycle hook arguments. While it might be argued that that ship has sailed, we prefer to think that it's never too late to provide users of the framework with clearer messaging regarding usage of certain features. 134 | 135 | Making the arguments public and supported would mean supporting APIs that did not go through the RFC process, meaning they do not align with some of the current values of the framework, nor would iteration on them would be possible without introducing breakage. Additionally, there are some performance penalties to supporting these arguments, mentioned in the Motivation section. 136 | 137 | # Unresolved questions 138 | 139 | None. 140 | --------------------------------------------------------------------------------