I'm looking for a way to extend Ember's Object to include some additional methods, so that they become available every object (View, ArrayController, etc) in my app.
Specifically, I want to define some methods that introduces some naming conventions of the controllers, models, views, templates, helpers, etc.
For example:
If the class name of the View is ArticlesListView then its associated model is Article, the controller action is named list within ArticlesController, the template is in app/articles named list.js.hjs...
The end result should be, for example, App.ArticlesListView.model() would return App.Article.
So how do I extend the core Ember Object?
Ember.Object.extend({ // <--- ???
model: function(context, params){
}
});
The answer to the general question of enhancing an existing object is to use reopen:
Ember.Object.reopen({
foo: function(){
return 'bar';
}
});
As to your more specific question, that is more challenging. An object doesn't typically know about the name of the property it is assigned to. You might be able to achieve your goal by traversing the properties of your namespaces (including App) and find the one that matches the current class. You could cache that property name for future performance.
Another approach would be to define a helper method for defining new models, controllers, etc. which you pass the name into. The method could handle creating the subclass, assigning it to a property of App, and setting an instance variable with the name.
Basically like Luke wrote, with one substantial difference. If you do:
Ember.Object.reopen({
foo: function(){
return 'bar';
}
});
Above replace (not extend - if foo method exist already) Object's property foo.
If you want to extend Ember Object's property foo you need to call _super() in order to include original implementation of the foo method.
Ember.Object.reopen({
foo: function(){
this._super(); // include original `foo` method stuff here
return 'bar'; // then add to `foo` method whatever you want
}
});
BTW, you can extend particular instance as well. var someObject = Ember.Object.extend({}); someObject.reopen({});
Related
I want to be able to write a component which has three sources of classNames:
A set of static classes from the classNames property of the component (static JS code)
A set of user provided classes provided through the classNames property on the HB-helper
Up to here I have tested successfully -- Ember will merge the two sources which I think is nice. I cannot however find where this behaviour is documented.
Now I want to be able to add further classes to this list with a computed property.
The only way to do this seems to be with classNameBindings but this is not sufficient in my case! As the list of classes is quite dynamic and I want to calculate it explicitly.
What I have also tried is to define the classNames as property() but this seems not to be possible according to this issue.
Does anyone know a way to achieve this?
This is one case where I think you don't want to use Ember and instead use jQuery directly. Use Ember to take care of the static classes and the classes provided through the Handlebars helper, but use jQuery to add the computed property classes. For instance, let's says that you have a computed property called extraClasses. Try something like this:
extraClasses: function() {
return ['one', 'two', 'three'];
}.property(),
previousExtraClasses: [],
manageClasses: function() {
var previousExtraClasses = this.get('previousExtraClasses');
var extraClasses = this.get('extraClasses');
// Remove the classes that got removed from `extraClasses`
previousExtraClasses.forEach(function(className) {
if (extraClasses.indexOf(className) < 0) {
this.$().removeClass(className);
}
}, this);
// Add the classes that got added to `extraClasses`
extraClasses.forEach(function(className) {
if (previousExtraClasses.indexOf(className) < 0) {
this.$().addClass(className);
}
}, this);
this.set('previousExtraClasses', extraClasses);
).observesImmediately('extraClasses')
Every time you update extraClasses with a new set of classes, the manageClasses observer will take care of managing the classes on the component. As long as extraClasses returns an array of strings, it can be computed any way you like.
I'm not sure I fully understood your reasoning against classNameBindings. You can get dynamic classes list with help of that property. I've prepared an example in jsbin.
It uses rule that applies to classNameBindings: if no conditional classes have been listed (eg. classNamesBindings: ['isEnabled:enabled:disabled']), than use provided by computed property class name as is (eg. true||false would be in classes list of HTMLElement). So, you need to declare a computed property which returns a string with classes names and to list this CP in component's classNameBindings.
UPDATE
Okay, what luck…this is now the default behavior as of Ember 1.5. Yay!
Original question
Something that really seems intuitive to me but isn't the case in the Ember router. I have a bunch of resources, the vast bulk of which have an index route where I want the model to be the same as the resource route's model--that is, the object selected by convention using the dynamic segment. E.g.:
this.resource("chargeRule", { path: "/chargeRule/:chargeRule_id" }, function(){});
I'm going to be using nesting, so I can't do everything in my chargeRule template, because that template is going to have little but {{outlet}} (kinda like this guy). So my index route is going to be my read-only view in most cases and I'll have an edit sub-route to update a model where needed.
What bugs me is, the default model for an index route (and any other sub-route) is: NOTHING! Tada! Null I think, though it could be empty string or something. So, I now have to create a bunch of tiny Ember.Route subclasses that look exactly like this:
App.ChargeRuleIndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('chargeRule');
}
});
Repeat ad-nauseum for each different model class/route. Really, then about double it because I want the same for /index and /edit routes, maybe more. Personally, I would have thought that the default model (since you can override it anyway) for any child route should have been the parent route's model instead of nothing, but I'd be interested in explanations as to why it's not.
But my question is, can anybody come up with a way for me to make the behavior I'm describing the default in my app? Something I can reopen? So I don't have a dozen or more boilerplate 5-line route objects?
Edit
I might should mention that I know my gripe might seem trivial, but the fact that I have to create so many route subclasses also implies that I should create individual files for each class…right? So that future developers can easily find where a given class is defined? I know that's not an absolute, but is a good practice, I think, and would require me to make as many tiny JS files as tiny classes…which is why I started thinking in terms of changing the default behavior. I'll take comments on that though.
UPDATE
See note at top of question, this is no longer necessary...
Based on #kingpin2k 's answer, I came up with this more generalized possibility:
App.ParentModelRoute = Ember.Route.extend({
model: function() {
return this.modelFor(this.get('routeName').replace(/\..*$/, ''));
}
});
App.ChargeRuleIndexRoute = App.ParentModelRoute.extend();
App.ChargeRuleEditRoute = App.ParentModelRoute.extend();
App.OtherIndexRoute = App.ParentModelRoute.extend();
.
.
.
Which certainly cuts down on the amount of code…but still requires me to explicitly declare a route class for each sub-route. It also may be a bit hacky (the routeName property isn't listed in the API docs?), though it's simple enough it should work pretty widely.
The reason is due to the fact that a route doesn't necessarily have to contain a model. Think of the case of a Albums/New route. It wouldn't actually have the albums resource as its model. For this reason there is no set rule for what the model should be for a model.
That being said, if you feel like lazily building up some sort of default route for your route and extending this, that's entirely possible and can be accomplished a few different ways.
App.DefaultChargeRuleRoute = Ember.Route.extend({
model: function() {
return this.modelFor('chargeRule');
}
});
App.ChargeRuleIndexRoute = App.DefaultChargeRuleRoute.extend();
App.ChargeRuleEditRoute = App.DefaultChargeRuleRoute.extend();
or if you wanted to make some generic model fetching route
App.ModelFetcherRoute = Ember.Mixin.create({
model: function(){
return this.modelFor(this.get('modelToFetch'));
}
});
App.ChargeRuleIndexRoute = Ember.Route.extend(App.ModelFetcherRoute, {
modelToFetch: 'chargeRule'
});
And this could follow the same pattern as the first example if you want to make it even smaller
App.DefaultChargeRuleRoute = Ember.Route.extend(App.ModelFetcherRoute, {
modelToFetch: 'chargeRule'
});
App.ChargeRuleIndexRoute = App.DefaultChargeRuleRoute.extend();
App.ChargeRuleEditRoute = App.DefaultChargeRuleRoute.extend();
I have a route that has a dynamic segment:
this.resource('dog', {path: '/dog/:pet_id'});
For debugging purposes, I would like to linkTo dog with the specific dynamic segment of '666'. But
{{#linkTo 'dog' '666'}}Click to go to dog{{/linkTo}}
is giving me "undefined" instead of "666". Do you know why?
See it running on jsbin.
See the code on jsbin.
Your working jsbin: http://jsbin.com/iwiruw/346/edit
The linkTo helper does not accept strings as a parameter, but instead model from which to pick up the dynamic segments defined in your router map. If you don't have a model at hand leave the parameter out, and all you need to do is to hook into the serialize function of your DogRoute (if you don't have one defined just define it to instruct ember to use yours instead of the automatically defined) and return an object/hash containing the dynamic segments your route expects, this could be anything you want:
App.DogRoute = Ember.Route.extend({
serialize: function(model) {
return {pet_id: 666};
}
});
Hope it helps.
I cleaned up the code a little bit by removing unused bits and switching to the fixture adapter. Here's a working version without the need for a serialize method: http://jsbin.com/iwiruw/347
Ultimately, nothing needed to be changed in the base code beyond using a newer version of Ember and properly setting up the actual model classes and data.
I am converting a project from Backbone (w/ Backbone Relational for ORM) to Ember and I cannot find in the documentation an example of how to use DS.belongsTo to reference an object of the same type.
In otherwords, I have the following model definition (generic pseudo-code for clarity).
BoxModel
Text = String
BoxParent = BoxModel (referenced by id)
I want to re-create this structure in Ember for client-side manipulation etc.
I am also using requirejs so all my object targets for DS.belongsTo have to be included in the define statement and made available as parameters to the anonymous function.
As such, I doubt I can use something like this:
define ([ 'textbox' ]), function(TextBox) {
return DS.Model.extend({
BoxParent: DS.belongsTo(TextBox)
});
});
The reference to the current class definition is not likely to work. In Django, this type of relationship is handled with the special target of "self" but I cannot find any equivalent solution in Ember. Any help would be very much appreciated.
As a final note, this relationship can also be null because not all TextBoxes must have a parent (this is inevitable with this type of relationship unless you have infinite circular references.
Thanks!
Steve
You need to wrap your model name around quotes to use a non initialized model name.
App.Box = DS.Model.extend([
parent: DS.belongsTo('App.Box')
});
What's the difference between ember.js Object methods extend and create?
TLDR: You will find the answer in Ember guides: classes and instances.
Sometimes I see that one in the examples and sometimes the other. In particular, what's the difference between Em.Application.extend({}) and Em.Application.create({})?
If I declare my app like this, what does it mean?
Ember.Application.create({
MyController : Ember.ArrayController.extend({
}),
});
How can I access the instance of MyController? Do I need to create it somehow? I need to push some data into it.
The dead simple answer is that extend defines a new JS Class which inherits from the class you're extending, but does not create an instance of that class. create creates an instance of the class.
Em.Application is a particular case, because you're creating a namespace, not an object instance. I don't know when you'd ever want to extend Em.Application.
App = Em.Application.create(); // I have a new Em.Application namespace
App.A = Em.Object.extend(); // I have defined a new class, App.A,
// which inherits from Em.Object
var a = App.A.create(); // a now contains an instance of App.A.
I'd suggest you read "Naming Conventions", too.
ETA: And "Understanding Ember Objects", as suggested in zaplitny's comment.
From what little I understand, in layman's terms, you extend when you want to define a new object idea with properties that will never change(except for reopen), and exist throughout all versions of this object.
You create when you want to work with a particular individual (instance of) object. In other words, something with properties that will be changed by actions or other particular instances.
Often you only need to create relationships between objects, you don't need to speak about particular individual objects, but rather the idea of the object. Therefore, you don't need to create every time you extend.
Hopefully I'm understanding it correctly.