Parent property is not bind to the child (Ember 2.8) - ember.js

My code:
signup.emblem:
= validating-form onsubmit=(action 'signUp')
= input-field value=username
span {{usernameError}}
validating-form.js:
submit(event) {
console.log(this.get('username') //undefined
this.sendAction('onsubmit')
}
signup.js:
actions: {
signUp() {
console.log(this.get('username')) // value from input
}
}
As you can see the basic idea is some value in input gets validated in validating-form component and then if everything is fine it'll call some controller action or set some properties.
The problem is that apparently this form component isn't bind to properties from controller, even though its child component (input-field) is. Can you tell me what am I doing wrong here?
If I have to bind it explicitely, is there some way to do that with multiple properties at once?

The problem is that the standard input element isn't two-way bound to your username variable. You can bind it quickly using the action and mut helpers.
(example in handlebars, but you should be able to convert to emblem easily enough)
<input value={{username}} onblur={{action (mut username) value='target.value'}}>
This is saying:
on the onblur event
mut(ate) the username
to match the current target.value - which is the value of the input box
You can see evidence of this working in this twiddle
The other option is Input Helpers
I've not used these, as they don't follow the current Ember thinking of Data Down Actions Up, but it should be as simple as:
{{input value=username}}
And this will two-way-bind directly username.

Related

Emberjs: Use args as tracked property

I am doing a store query in the controller and then passing the result down to a child component.
// Controller
#tracked comment;
#action
async fetchComment() {
const comment = await this.store.query('note', {
filter: {
listing: this.listing.id,
},
});
if (comment && comment.length > 0) {
this.comment = comment.firstObject;
}
}
// Template
<MyComponent #comment={{this.comment}} />
I want to use the arg to populate a tracked property in the child component.
// Component
#tracked comment = this.args.comment;
I found this does not work but works well as a getter. However with a getter I am unable to set this to null when deleting that record.
I have also tried to set the tracked property in the Constructor but that didnt work either.
I suspect this has something to do with passing in a promise or store object in the args because this works fine with static data.
why your code does not work
this code can not work:
#tracked comment = this.args.comment;
this is because comment on the controller is initially undefined but will later bet set to comment.firstObject when the network request is done and the await in your fetchComment function returns.
Generally everythings on args basically always behaves like its #tracked (while more accurate you would describe it as getters). So this usually will just update fine. But the assignment #tracked comment = this.args.comment; only happens once when you create the component, so you no longer depend on updates on args.
why you can not set this.args.comment to null
If you use a getter or directly always use this.args.comment you can not change this reference. This is because this.args is always readonly. you can change objects on this.args.something, but you never can change the reference or primitive value on this.args.
Sidenote: this is only true if the component was called with <AngleBracket /> syntax. For the older {{curly-component}} syntax this is not true. So this does not depend on the component itself but how the component gets called.
you could notify the controller to remove the reference
one good thing to do is to pass down a deleteComment action to the component that basically does something like this.comment = null on the controller. then you use this.args.comment directly or by a getter and you can call this.args.deleteComment() to set comment on the controller to null, which will update anything that uses this.args.comment or a getter that returns this.args.comment.
this is essentially because in your architecture the controller owns the data (because it loads it). so the controller is also responsible to delete it.
if you use ember-data you can check isDeleted
if its a ember-data model (which it probably is if you call this.store) then it has a isDeleted property. you can use this to check if the record is deleted, since ember-data records dont disappear if they get deleted. which is exactly because of problems like this.
how you use another property to shadow a argument
you could do something like this to shadow an argument in your component:
#tracked commentIsDeleted = false;
get comment() {
return this.commentIsDeleted
? null
: this.args.comment;
}
this way at first this.comment will work like a normal getter, but you can shadow delete it by setting this.commentIsDeleted = true;. From that on this.comment will be null.

Trigger Event on Bootstrap-Vue Component (testing)

I am writing tests in a vue app (using Jest). While testing a certain component, I need to trigger a change event on a checkbox (which I am using BFormCheckbox for).
When I select the checkbox using the selector the actual checkbox evaluates to ('.custom-control-input'), I can get the test below to pass. However, I would like to use the name of the actual component (BFormCheckbox), which I feel would be easier to follow. Is there any way to make this work?
it('is triggered by clicking a phase checkbox', () => {
// I would like to write:
// const phaseCheckbox = wrapper.find(BFormCheckbox);
// However, I can only get the following to work:
const phaseCheckbox = wrapper.find('.custom-control-input');
// this is true for both selectors
expect(phaseCheckbox.exists()).toBe(true);
phaseCheckbox.trigger('change');
// this fails for wrapper.find(BFormCheckbox)
expect(togglePhaseSpy).toHaveBeenCalled();
});
Since the <input type="checkbox"> is nested inside additional HTML markup (as defined by Bootstrap v4), use the following to access the hidden input:
const phaseCheckbox = wrapper.find(BFormCheckbox).find('input')
This will not tie you to using the inner element classname(s), as they do change depending on the rendering style mode of <b-form-checkbox> (i.e. default custom checkbox, switch style checkbox, button style checkbox, or plain mode checkbox).
Jest documentation shows examples of how to do as you are asking.
Component Constructors:
import Foo from '../components/Foo';
const wrapper = mount(<MyComponent />);
expect(wrapper.find(Foo)).to.have.lengthOf(1);
Component Display Name:
const wrapper = mount(<MyComponent />);
expect(wrapper.find('Foo')).to.have.lengthOf(1);

Format a property before displaying it

Hello StackOverflow experts,
I would like to know if it would be possible to use Ember.js' computed properties to modify the value of the property before returning to whatever object requests it.
Imagine this simple example:
I have a User object with mail property
When I set the property, I want the email address to change from first.last#example.com to first.last#anotherexample.com, then return it
When I request the property ( via User.get ) I want to get the modified property back.
I think it should be pretty simple by utilising another 'helper' property, like formatted_mail, where I would store and retrieve the formatted value, but I wonder if something like this can be done without additional model properties.
So far, I have this coffescript code, but I always get 'undefined' when reading the property, even though I set it before, so I suspect the value does not get saved by Ember anywhere:
mail: ( ( key, value ) ->
if arguments.length == 1
return this.get 'mail'
else
return value.split( '#' )[0] + '#anotherexample.com'
).property 'mail'
Thank you for your assistance!
You are close to solution.
As computed properties are always cached by default in Ember (you could disable this behaviour using .volatile()), you do not have to specify what to do when arguments.length is 1, except if you want to specify a default value.
So here it should looks like:
App.User = Ember.Object.extend({
mail: function(key, value) {
if (arguments.length === 2) {
return value.split('#')[0] + "#tieto.com";
}
return null;
}.property()
});
The return null just specify the default value.
When you set the mail property, it will cache the returned value and always returns it without recomputing this property.
Note that you can do that only because the mail property does not depend on other properties. If you were declaring it with .property('anotherProperty'), the mail property will be recomputed any time anoterProperty changes. So in the example above it will reset it to null.
You can try it in this JSFiddle.

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.

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.