What is the difference between .create() and .createWithMixins()? I am not able to find any documentation regarding this. When I create a view instance using .create() and call this._super() in the didInsertElement method then, following error is thrown:
Ember.Object.create no longer supports defining methods that call
_super.
But, when I replace .create() with .createWithMixins() everything works fine. Here is the code and the sample js fiddle :
App.SampleView = Ember.View.create({
sampleProperty : "a",
didInsertElement : function(){
this._super();
this.set("sampleProperty", "b");
}
});
http://jsfiddle.net/bErRT/3/.
From Wikipedia:
In object-oriented programming languages, a mixin is a class, which
contains a combination of methods from other classes. How such
combination is done depends on language, but it is not by inheritance.
If a combination contains all methods of combined classes it is
equivalent to multiple inheritance.
In Ember instances of objects are created with the create method with no arguments, or with a single hash(kvo) that represent the properties of that type, and they will be automatically populated. Example:
var SomeClass = Ember.Object.extend({
name: '',
url: ''
});
// this instance will have a "name" and a "url" properties with blank values
var someInstance = SomeClass.create();
// this instance will have the same properties, but now
// their values will be populated
var anotherInstance = SomeClass.create({
name: 'Ember.js',
url: 'http://emberjs.com'
})
On the other hand, crateWithMixins, allow you to mix another class definition into a single object instance or into another class. So let's say you have the same SomeClass above, but you don't want to sub-class it via extend and create another type. In this case you can use a Mixin to make sure that only that one instance will have that definition of the two classes. Example:
var SomeClass = Ember.Object.extend({
name: '',
url: ''
});
// note that you don't extend a mixin, you only create
var SomeOtherClass = Ember.Mixin.create({
doSomething: function() {
console.log('doing my thing');
}
});
// This instance will have a method called "doSomething"
var x = SomeClass.createWithMixins(SomeOtherClass, {
name: 'Ember.js',
url: 'http://emberjs.com'
});
// this instance only has methods/properties defined in "SomeClass"
// therefore, no method called "doSomething"
var y = SomeClass.create({
name: 'Google',
url: 'http://google.ca'
});
However, if you want to create a new class with a Mixin, you can extend Em.Object, passing the Mixin as the first argument, as follows:
var AnotherClass = Ember.Object.extend(SomeOtherClass, {
firstName: '',
lastName: ''
});
var z = AnotherClass.create();
z.set('firstName', 'Hiro');
z.set('lastName', 'Nakamura');
z.doSomething();
Check out the API Documentation as well as this JSFiddle.
Edit: As for _super(), you only use this when you create a new class (via extend). When you create instances of existing classes, you shouldn't call _super().
Another thing. I see you're trying to create a View directly. I believe, based on your code, you should be extending Ember.View and let the framework create instance for your at the appropriate time. If you create manually, you'll be responsible for some parts of its workflow like appending it to the DOM, removing it, etc. Maybe I don't see the whole picture, but based on this code alone, I think you should not call create there and call extend instead, and then you'll be able to call _super()
Related
https://github.com/emberjs/ember.js/blob/5fd2d035b30aa9ebfe73de824b3b283ec8e589cc/packages/ember-runtime/lib/system/service.js#L31
In the line I reference above the ember-core team imports this createInjectionHelper and uses it to add a clean/simple api for injecting services like so
App.ApplicationRoute = Ember.Route.extend({
authManager: Ember.inject.service('auth'),
model: function() {
return this.get('authManager').findCurrentUser();
}
});
How can I create something like this myself for a non service?
Your example usage will change slightly from what you have above. We will cover what the injectRepositories does in a little bit.
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
The initializer can be improved with the following changes:
import registerWithContainer from "ember-cli-auto-register/register";
export function initialize(_, application) {
registerWithContainer("repositories", application);
application.inject("repositories", "store", "store:main");
}
export default {
name: "repositories",
after: "store",
initialize: initialize
};
Let's break down what is happening in each line.
registerWithContainer("repositories", application);
In the line above, we are deferring to the ember-addon ember-cli-auto-register. This addon will take a directory, in this situation, the repositories directory and register each object into the Ember container to able to be accessed via a lookup. They will be inserted as if doing the following:
application.register("repositories:person", PersonRepository);
Then we add a function to do the injection using the ember-addon ember-cli-injection:
// app/utils/inject.js
import inject from "ember-cli-injection/inject";
var injectRepositories = inject("repositories");
export default injectRepositories;
This then allows us the opportunity to use the newly created function above to access these objects with the code below:
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
Since each object is now in the container, we can look it up and inject at runtime instead of during the initialization of the application. We register the repositories key in the function and this then returns a computed property (see code below from ember-cli-injection). We do this as a computed property to allow lazy loading. The object is not fetched from the container until the property is accessed.
import Ember from 'ember';
var injection = function(key) {
return function(name) {
return Ember.computed(function(propertyName) {
var objectName = name || propertyName;
return this.container.lookup(key + ':' + objectName);
});
};
};
export default injection;
We also allow for a name to passed to the repositories function, for example repository: injectRepositories('person'). This allows you to name your object whatever you would like when injecting it.
If you would like to just name the object the same as the name of the repository injected into the container you can alternatively do person: injectRepositories(). This will pass the person key to the computed property as the propertyName and since the name was left null when injecting, the objectName will instead be person. This matches the API produces similar results but is not the same as that of the Ember.inject.service and Ember.inject.controller API that is available as of Ember 1.10.
I don't think it's their intention for you to use it this way. The standard way is to use App.inject() if you're using plain ember, or do this in an initializer if you're using ember-cli.
In your case:
// Register the service
App.register('service:auth', {
findCurrentUser: function() {
// Do your magic
}
}, { instantiate: false });
App.inject('route', 'auth', 'service:auth');
Then in your model hook you can use this.auth.findCurrentUser();. You can also inject this into controllers and components if you need. Also note, to keep it clean, that you might want to include a separate module instead of defining your auth module in the service registration.
More info here:
http://emberjs.com/guides/understanding-ember/dependency-injection-and-service-lookup/#toc_dependency-injection-with-code-register-inject-code
NOTE
A service is also not a "special" thing. You can inject anything you want into pretty much anything you want using this method.
Using Ember-Cli, I'm trying to add a register an adapter and then inject it into controllers. I don't understand what values I'm supposed to pass into either function, or why their in that format.
(Following provided by emberjs webpage):
App.register('network:main', App.NetworkAdapter);
App.inject('controller', 'network', 'network:main');
Specifically, I'm unsure of what this "type:name" format is, nor do I understand how the inject values relate to what is in the register function.
Also, does anybody know what the following error means in relation to the application.inject function?
"Error while processing route: index Failed to create an instance of
'qservice:main'. Most likely an improperly defined class or an invalid
module export."
Code:
export default {
name: 'qservice',
initialize: function(container, application){
application.register("qservice:main", "qservice", {singleton: true});
application.inject("controller",'qservice',"qservice:main");
//application.inject("route",'qservice','adapter:main');
}
};
The second parameter of the register function should be a factory (or instance if instantiate is set to false). A factory is something you can call create on and it will return an instance. Singleton is true by default, so there is no need to specify it.
var qservice = Em.Object.extend();
application.register("qservice:main", qservice);
var qserviceInstance = Em.Object.create();
application.register("qservice:main", qserviceInstance, {instantiate:false});
I am unit testing my controller using mocha. My controller looks like:
AS.MyController = Ember.ObjectController.extend(Ember.Validations.Mixin, {
name: null,
description: null,
init: function () {
this._super();
this.get('store').find('something');
},
....
});
And my test begins like:
describe("MyControllerTest", function () {
//tried but didn't work
//delete AS.MyController.init;
var controller = AS.MyController.create();
.....
})
and the browser always throws error on "this.get('store')" call in init. I am not sure if I need to stub things out or there is a work around for it because my test case doesn't rely on store at all. In either case, I couldn't find much out there and would really appreciate any feedback.
Thanks, Dee
JSBIN : http://jsbin.com/aMASeq/3/
UPDATE :
There can be many ways to tackle this issue, but what I ended up doing is re-structuring the controller code a bit by putting all the function calls to store into separate actions and then in init I make calls to these action functions using this.send('actioName'). In my unit test, before instantiating the controller, I reopen the controller to modify these action functions(its easier to change action function than to change init function itself, when trying to change init I always got into some js error). Eg:
AS.MyController.reopen({actions: {setSomeActionThatUsesStore: function () {
//do something that doesn't involve using store
}}});
Controllers get access to the store from the container. You can create a mock container and instantiate the controller with it.
var mockContainer = new Ember.Container();
mockContainer.register('store:main', Ember.Object.extend({
find: function() { ... }
});
var controller = App.PostController.create({ container: mockContainer });
If you need access to the real store then you can just grab the controller from your App's container.
var controller = App.__container__.lookup('controller:post');
That will instantiate a PostController for you that has all of it's dependencies (such as store) wired together.
I am trying to use observers to observe a change on my model after XHR. This is because the earlier approach of extending a fn and calling super is not allowed any more.
Running into this weird issue where my observer doesn't fire:
App = Ember.Application.create({
ready: function () {
console.log('Ember Application ready');
this.topCampaignsController = Ember.ArrayController.create({
content: null
});
App.TopCampaignsModel.create({
// Calling super is no longer allowed in object instances
//success: function () {
// this._super();
// App.topCampaignsController.set('content', this.get('data'));
//},
onDataChange: function () {
console.log('data property on the object changed');
App.topCampaignsController.set('content', this.get('data'));
}.observes('data')
});
}
});
App.TopCampaignsModel = Ember.Object.extend({
data: null,
// this will be actually called from an XHR request
success: function () {
this.set('data', [5,10]);
},
init: function () {
console.log('TopCampaignsModel created');
this.success();
console.log(this.get('data'));
}
});
Jsfiddle here: http://jsfiddle.net/gdXfN/26/
Not sure why the console doesn't log "data property on the object changed". Open to alternative approaches on how I can override the 'success' fn in my instance.
After this commit in December last year, it is no longer possible to set observers during object creation. This resulted in a huge performance win.
To set observers on create you need to use:
var Object = Object.createWithMixins({
changed: function() {
}.observes('data')
});
Here's a fiddle demonstrating this.
The API documentation should be updated accordingly, something I will do later on.
However, I don't advise you to do that, but instead set observers during object definition. The same result can be achieved: http://jsfiddle.net/teddyzeenny/gdXfN/32/
That said, there are two things you are doing that go against Ember concepts:
You should not create controller instances yourself, you should let Ember create them for you:
App.TopCampaignsController = Em.Controller.extend({ content: null });
When the App is initialized, Ember will generate the controller for you.
Models should not be aware of controller existence. Controllers should access models not the other way round.
Models and Controllers will interact together through routes.
For the last two points, you can watch the tutorial at http://emberjs.com/guides/ to see how the Application, Controllers, Models, and Routes should interact. Since you're not using
Ember Data, just ignore DS.Model and imagine an Ember.Object instead. The tutorial can give you a pretty good overview of how objects should interact.
So, I am trying to get a simple propertyBinding to work with emberjs. Specifically, I have a controller with a content property, that gets updated under certain circumstances and a view, which needs that content array to draw some chart.
I have made the most basic example and it doesn't seem to work. My simple example is the following:
Appname.IndexController = Ember.Controller.extend({
value: 'bla'
});
Appname.IndexView = Ember.View.extend({
templateName: 'Index',
propertyBinding: 'Appname.IndexController.value',
didInsertElement: function() {
console.log('Indexview');
console.log(this.get('property'));
}
});
It is as simple as that, and it just does not work. What is really odd though, if I create another testcontroller (rather then extending it) e.g.
Appname.TestController = Ember.Controller.create({
value: 'jpopo'
});
the property binding works all of the sudden. But I just can not get it to work with the IndexController
(And in case the information is necessary, in the Applicaton.hbs I have an outlet)
Thanks for any help
Bindings work for instantiated objects, not for object definitions.
Appname.IndexController is the controller definition, not an instance. It is not what you want to bind to. The Ember.js app will create an instance of IndexController, and it's that created instance that you want to bind to:
To access the actual controller instance from its view, use controller.
Appname.IndexView = Ember.View.extend({
templateName: 'index',
propertyBinding: 'controller.value',
didInsertElement: function() {
console.log(this.get('property'));
}
});
Of course, that is if you follow Ember.js conventions.