"setProperties" does not pass "attrs" as expected - ember.js

I have a wrapping component, which yields some other component in itself. (Think about form control group or a table cell component).
To make it as flexible as possible I pass attrs to the hash property. And all component will set hash on itself.
Ember.Component.reopen({
init: function(){
this._super();
this.setProperties(this.get('hash'));
}
});
http://jsbin.com/suvenijuxu/1/edit?html,js,output
It works fine with Strings but not with objects. In my-wrapper component attrs.model.name has the correct value (see the template), but after attrs is passed to the property hash and set by setProperties it is transformed to MutStream. (see console.log)
Why does this transformation happens? Is this the expected behavior? Is my approach considered as an anti-pattern? Is there a workaround? What does MutStream or MUTABLE_REFERENCE mean?

Related

Apollo Link State Default Resolver Not Working (#client query parameter variables)

Example here: https://codesandbox.io/s/j4mo8qpmrw
Docs here: https://www.apollographql.com/docs/link/links/state.html#default
TLDR: This is a todo list, the #client query parameters don't filter out the list.
This is the query, taking in $id as a parameter
const GET_TODOS = gql`
query todos($id: Int!) {
todos(id: $id) #client {
id
text
}
}
`;
The query passes the variable in there
<Query query={GET_TODOS} variables={{ id: 1 }}>
/* Code */
</Query>
But the default resolver doesn't use the parameter, you can see it in the codesandbox.io example above.
The docs say it should work, but I can't seem to figure what I'm missing. Thanks in advance!
For simple use cases, you can often rely on the default resolver to fetch the data you need. However, to implement something like filtering the data in the cache or manipulating it (like you do with mutations), you'll need to write your own resolver. To accomplish what you're trying to do, you could do something like this:
export const resolvers = {
Query: {
todos: (obj, args, ctx) => {
const query = gql`
query GetTodos {
todos #client {
id
text
}
}
`
const { todos } = ctx.cache.readQuery({ query })
return todos.filter(todo => todo.id === args.id)
},
},
Mutation: {},
}
EDIT: Every Type we define has a set of fields. When we return a particular Type (or List of Types), each field on that type will utilize the default resolver to try to resolve its own value (assuming that field was requested). The way the default resolver works is simple -- it looks at the parent (or "root") object's value and if it finds a property matching the field name, it returns the value of that property. If the property isn't found (or can't be coerced into whatever Scalar or Type the field is expecting) it returns null.
That means we can, for example, return an object representing a single Todo and we don't have to define a resolver for its id or text fields, as long as the object has id and text properties on it. Looking at it another way, if we wanted to create an arbitrary field on Todo called textWithFoo, we could leave the cache defaults as is, and create a resolver like
(obj, args, ctx) => obj.text + ' and FOO!'
In this case, a default resolver would do us no good because the objects stored in the cache don't have a textWithFoo property, so we write our own resolver.
What's important to keep in mind is that a query like todos is just a field too (in this case, it's a field on the Query Type). It behaves pretty much the same way any other field does (including the default resolver behavior). With apollo-link-state, though, the data structure you define under defaults becomes the parent or "root" value for your queries.
In your sample code, your defaults include a single property (todos). Because that's a property on the root object, we can fetch it with a query called todos and still get back the data even without a resolver. The default resolver for the todos field will look in the root object (in this case your cache), see a property called todos and return that.
On the flip side, a query like todo (singular) doesn't have a matching property in the root (cache). You need to write a resolver for it to have it return data. Similarly, if you want to manipulate the data before returning it in the query (with or without arguments), you need to include a resolver.

Ember.computed not functioning as expected when defined within a method

I am attempting to use Ember.computed to setup a computed property from within one of my view's methods. I tried to use the syntax shown in this fiddle, but as you can see, it does not seem to actually do what I was hoping. Any pointers in the right direction would be much appreciated.
http://jsfiddle.net/skane/H5ma5/1/
this.set('myComputed', Ember.computed(function() {return "funky"}).property());
Steve
This won't work this way, since Ember has to do perform some of its magic. I had a look at the source of Ember and found this:
// define a computed property
Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
return this.firstName+' '+this.lastName;
}).property('firstName', 'lastName'));
#method defineProperty
#for Ember
#param {Object} obj the object to define this property on. This may be a prototype.
#param {String} keyName the name of the property
#param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
computed property) or an ES5 descriptor.
You must provide this or `data` but not both.
#param {anything} [data] something other than a descriptor, that will
become the explicit value of this property.
So the following should work in your case:
Ember.defineProperty(this, 'myComputed', Ember.computed(function() {
return "funky";
}).property());

Force a controller to always act as a proxy to a model in Ember

I'm looping through a content of an ArrayController whose content is set to a RecordArray. Each record is DS.Model, say Client
{{# each item in controller}}
{{item.balance}}
{{/each}}
balance is a property of the Client model and a call to item.balance will fetch the property from the model directly. I want to apply some formatting to balance to display in a money format. The easy way to do this is to add a computed property, balanceMoney, to the Client object and do the formatting there:
App.Client = DS.Model({
balance: DS.attr('balance'),
balanceMoney: function() {
// format the balance property
return Money.format(this.get('balance');
}.property('balance')
});
This serves well the purpose, the right place for balanceMoney computed property though, is the client controller rather than the client model. I was under the impression that Ember lookup properties in the controller first and then tries to retrieve them in the model if nothing has been found. None of this happen here though, a call to item.balanceMoney will just be ignored and will never reach the controller.
Is it possible to configure somehow a controller to act always as a proxy to the model in all circumstances.
UPDATE - Using the latest version from emberjs master repository you can configure the array controller to resolve records' methods through a controller proxy by overriding the lookupItemController method in the ArrayController. The method should return the name of the controller without the 'controller' suffix i.e. client instead of clientController. Merely setting the itemControllerClass property in the array controller doesn't seem to work for the moment.
lookupItemController: function( object ) {
return 'client';
},
This was recently added to master: https://github.com/emberjs/ember.js/commit/2a75cacc30c8d02acc83094b47ae8a6900c0975b
As of this writing it is not in any released versions. It will mostly likely be part of 1.0.0.pre.3.
If you're only after formatting, another possibility is to make a handlebars helper. You could implement your own {{formatMoney item.balance}} helper, for instance.
For something more general, I made this one to wrap an sprintf implementation (pick one of several out there):
Ember.Handlebars.registerHelper('sprintf', function (/*arbitrary number of arguments*/) {
var options = arguments[arguments.length - 1],
fmtStr = arguments[0],
params = Array.prototype.slice.call(arguments, 1, -1);
for (var i = 0; i < params.length; i++) {
params[i] = this.get(params[i]);
}
return vsprintf(fmtStr, params);
});
And then you can do {{sprintf "$%.2f" item.balance}}.
However, the solution #luke-melia gave will be far more flexible--for example letting you calculate a balance in the controller, as opposed to simply formatting a single value.
EDIT:
A caveat I should have mentioned because it's not obvious: the above solution does not create a bound handlebars helper, so changes to the underlying model value won't be reflected. There's supposed to be a registerBoundHelper already committed to Ember.js which would fix this, but that too is not released yet.

Bind Ember.Map to a Ember.TextField value

How can I bind an Ember Map value to a TextField value. Let's say I have this configuration:
App.AppsController = Em.Controller.extend({
selections: null
});
App.AppsRoute = Ember.Route.extend({
setupControllers: function(controller, model) {
controller.set('selections', Ember.Map.create());
}
});
And in my template:
{{view Ember.TextField valueBinding="bindingToMap"}}
I have tried to valueBinding="controller.selections.somekey" where somekey is a key from my map. However, the value is never bound. Note that initially the map is empty. Can this be the root of the problem?
EDIT:
I have also tried to use the binding with an integer value in the controller and it works. So the problem comes when I bind a more complex data structure such as a Map. I couldn't find anything in the docs explaining how to bind a map.
Caveat: I'm pretty new to Ember myself.
Looking at the implementation of Ember.Map, I don't think you can currently (in 1.0.0-pre2) do this. Ember.Map implements create, get, and set, but is not a normal Ember object. So among other things, "properties" in a map aren't really properties, and there's no observables support. The handlebars implementation relies heavily on observable support, so I think what you're doing won't work.
Someone correct me if I'm wrong, but this is what I'm looking at in the 1.0.0-pre2 code:
var Map = Ember.Map = function() {
this.keys = Ember.OrderedSet.create();
this.values = {};
};
/**
#method create
#static
*/
Map.create = function() {
return new Map();
};
Map.prototype = {
/**
Retrieve the value associated with a given key.
#method get
#param {anything} key
#return {anything} the value associated with the key, or undefined
*/
get: function(key) {
var values = this.values,
guid = guidFor(key);
return values[guid];
},
...
Point being, it implements its own get (and create) rather than extending Ember.Object...so no observable support. Though I might have missed it if it reopen's later or something.
EDIT:
Also, not really what you asked, but if you're building an interface that relies on the existence of some key, you should really probably be defining your own model class that has these keys as properties. You can still set them to null if they're "not set". If you also need the ability to set arbitrary keys, make one of your properties an Ember.Map and call it otherProperties or something, and put them in there. But if your view depends on a known key, it should be a defined property.

Ember.Controller array content filtering

I have a fiddle http://jsfiddle.net/kristaps_petersons/9wteJ/2/ it loads 3 objects and shows them in a view. Data is shown alright, but i can not filter it before i show it.
This
nodes: function(){
this.get('controller.content').filter(function(item, idx, en){
console.log('should log this atleast 3x')
})
return this.get('controller.content')
}.property('controller.content')
method is called when template iterates over array of values, but it never goes in to the loop and print console.log('should log this atleast 3x') why is that?
You are trying to replace controller.content while also binding to it. You need to define another property, such as filteredContent and bind it to controller.content. Take a look at how Ember.SortableMixin computes the variable arrangedContent for controllers with a sortProperties variable defined. Using that method as a template I would implement it like this:
filteredContent: Ember.computed('content', function() {
var content = this.get('content');
return this.filter(function(item, idx, en) {
console.log('should log this atleast 3x');
});
}).cacheable()
This should be implemented in the controller, not the view. The controller is the place for data manipulation, computed properties, and bindings.
Then bind the view layout to filteredContent instead of content to show the filtered data. Then both the original content and the filtered content are available.
Ok i got it working, but it feels a bit strange. First i moved method to Controller class and changed it to look like this:
nodes: function(){
console.log('BEFORE should log this atleast 3x', this.get('content.length'))
this.get('content').forEach(function(item, idx, en){
console.log('should log this atleast 3x')
})
console.log('AFTER should log this atleast 3x', this.get('content.length'))
return this.get('content')
}.property('content').cacheable()
as it should be same as buuda's recomedation, because as i understand from docs .poperty() is the same as Ember.computed. As it was still not working, i changed .property('content') to .property('content.#each') and it was working. Fiddle: http://jsfiddle.net/kristaps_petersons/9wteJ/21/ . I guess, that tempate first creates a binding to controller.content and as content itself does not change does not notify this method again, instead template pulls data as it becomes available.