Best practices for the input helper in new Ember components - javascript

I'm currently learning about Ember's new data-down, actions-up paradigm for components. As discussed here, however, sometimes I want to allow the child component to modify the property explicitly. This is where the mut helper comes in: it creates a wrapper for the passed in value, containing a (readonly?) value and a function to update it. The example on that page is for a simple button which increments a counter.
How does this concept work if I'm using the input helper inside a component? For example, let's say I'm building a form which consists of a bunch of special form components:
// templates/index.hbs
<form>
{{form-control value=(mut model.firstValue)}}
{{form-control value=(mut model.secondValue)}}
</form>
If the form-control component just has the task of wrapping the input control, how do we use the passed-in mut object correctly? Is it something like?
// templates/components/form-control.hbs
{{input type="text" value=attrs.value.value input=attrs.value.update}}
My thinking here: the value of the input element is set to the value of the mut object, and whenever the input value changes (HTML5 input event) the update method of the mut object is called to set the model property to the new value. It seems there's something wrong with my thinking though, because this doesn't work. What is the "standard" way of doing this now? I'm using Ember 1.13.8.

In classic components (as opposed to glimmer components), all bindings are mutable, so it is not generally necessary to use the mut helper. The following should work fine:
// templates/index.hbs
<form>
{{form-control value=model.firstValue}}
{{form-control value=model.secondValue}}
</form>
// templates/components/form-control.hbs
{{input type="text" value=value}}
The intended use of both the mut helper and of attrs is for glimmer components, also called angle bracket components, which are currently not released in Ember.js' stable releases.

Related

Vue.js element reference on attribute

I have the following code:
<input type="text"
#input="disableField($event.target)"
:disabled="isFieldDisabled(????)"
/>
I want to pass to the isFieldDisabled method the same value which I get in $event.target - the element reference. I cannot find the way how to do this. I know that I could use the :ref but in this case I need to do it as shown above.
This is only the example use case. I know that to disable the field I can do it better but I want to show the problem in simplest way.
This isn't possible the way you're asking for it.
The way that Vue works with VNodes, the element won't exist the first time you try to evaluate disabled: Vue evaluates the properties to determine how the DOM should look and then creates the elements to match. As here:
Note that you can only access the ref after the component is mounted. If you try to access input in a template expression, it will be null on the first render. This is because the element doesn't exist until after the first render!
With async components, the attributes will need to have well-defined values even if there isn't a Component to render yet, let alone well-defined elements to position there.
With fragment components, there won't ever be a single HTML element to interact with, because the component represents multiple top-level HTML elements.
Events don't have to play by the same rules, because you're defining handlers: DOM events will come from one source and apply to one target, and absent components don't emit events. Even then, the syntax gives you access to $event, not to an $el for a direct element reference.
The most straightforward way is to use a ref to get to the object after Vue creates it, subject to the same null-on-first-render ref rules above. If you need to pass a handle into a multipurpose method in the shape of getFoo(argument) your argument may have to be a string or enum key you define, not the element itself.

How v-model make a property of object reactive?

Link to my project:
https://codesandbox.io/s/v-model-3j96f
As of my link above, the is a file named HelloWorld.vue inside the "components" folder:
inputvalue.bbbb is a reactive data which is defined in data option, but
It's weird that inputvalue.cccc will become reactive after input with the v-model, but inputvalue.cccc will not reactive with #input.
In this question (Vue.js bind object properties), the first situation should not be possible.
Using v-model will automatically use $set to set the values on nested properties. This ensures this it works with array indices, as well as working for object properties that don't exist, as per your example.
If you're unfamiliar with $set it is documented here:
https://v2.vuejs.org/v2/api/#vm-set
The code for this part of v-model in Vue is here:
https://github.com/vuejs/vue/blob/399b53661b167e678e1c740ce788ff6699096734/src/compiler/directives/model.js#L44
In your example there are two inputs that use cccc. As you noticed, if you edit the input that uses v-model then everything works fine. However, if you use the :value/#input input first then it doesn't work, even if you subsequently use the v-model input. The behaviour is, somewhat oddly, determined by which of those two inputs you edit first.
The reason for that can be seen in the code for $set:
https://github.com/vuejs/vue/blob/399b53661b167e678e1c740ce788ff6699096734/src/core/observer/index.js#L212
The problem is that $set will only add a reactive property if the property doesn't already exist. So if you use the :value/#input input first it will create a non-reactive cccc property and once that is created it can't be made reactive, even if you use $set. It would have to be removed using delete before it could be re-added reactively.

Vue.js get all v-model bindings

With Vue.js you can you bind form input to a property:
https://v2.vuejs.org/v2/guide/forms.html
I am working on a project where I 'dynamically' generate a form though. Here the v-model bindings are just attributes that are being set based on the name that an input has.
The problem though is that I am left wondering how to properly get these v-model bindings. So far all I have see is people manually setting a property on their Vue instance for the binding:
new Vue({
el: '...',
data: {
checkedNames: []
}
})
Is it possible to somehow grab all of the v-model bindings though without setting up the data properties manually?
When creating a dynamic form with a variation an dynamic number of fields this would help out a lot.
I guess one option is simply retrieving the name atributes from the input with javascript but I am hoping there might be something in place already since v-model is probably calling a setter that might already be known in the Vue instance.
Reworked the example from the answer:
https://jsfiddle.net/yMv7y/2477/
According to the official documentation:
it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method.
This means that your starting data can be something like:
{
customForm: [],
values: {}
}
Let's assume customForm is an array of field objects that describe a bunch of fields in your custom form. It's empty at the beginning because you say your form will be created dynamically.
Once you generate that dynamic data, all you need to do is assign it to customForm and loop through it to add reactive properties with the aforementioned Vue.set method.
Here's a JSFiddle with an example. I'm using this.$set, but that's just an alias for Vue.set.

Items added to list are non-reactive in version 2 of Vue.js

In Vue 1.0 I used to add items to an array used in a v-for by simply calling items.push({}) as you can see here:
http://jsbin.com/figiluteni/1/edit?html,js,output
The exact same code in Vue 2.0 inserts a non-reactive object to the array:
http://jsbin.com/zuwihahiwa/1/edit?html,js,output
(Note that the newly added items are not live updated when edited)
<button #click="items.push({})">Add item</button>
I know that Vue inserts hooks on arrays when initialized, but being a Vue model binding that creates the new item's "name" property, I thought it could be automatically hooked like in Vue 1.
I find this new behavior very inconvenient, for it forces me to add a prototype of the object I want to add in Vue's data and clone it:
http://jsbin.com/bamasobuti/1/edit?html,js,output
In Vue's data:
item_prototype: {id: null, name: ""}
In the template:
<button #click="items.push(_.clone(item_prototype))">Add item</button>
My question is: Is there a recommended way of adding elements without having to keep a prototype of an empty element?
The change is not related to reactivity system change, but rather how v-model works in 2.0: it no longer magically creates non-existent paths and make them reactive for you.
This change is intended to force you to think about your data shape and to make your application state more predictable, similar to how you are expected to declare the reactive properties in your component's data option.
If I were you, I'd create a method and just do this.items.push({ id: null, name: '' }). There's no need to clone it.
According Vue's documentation, v-model="item.prop" is just syntactic sugar for:
v-bind:value="item.prop" v-on:input="item.prop = $event.target.value".
To make it work, just stop using
v-model="item.prop" and use this instead:
:value="item.prop" #input="$set(item,'prop',$event.target.value)"

AngularJS: Binding once to an attribute

I am trying to bind once an object key/field to the value of an attribute (data-oldField) so that if its value changes via user input (angular x-editable tables) I can grab the html element via data-newField and get the value of data-oldField so that I can rename the field/key in the object. I tried using the native :: expression to bind once, but the value of data-oldField changes when a change to the field name is submitted so that the values of date-oldField and data-newField are equal afterwards which is precisely what I do not want.
I also tried using the angular-once library and adding the directives once once-attr-field='field' as per the api, but I got the same result.
<tr ng-repeat='(field, value) in user.data'>
<td>
<span editable-text='field' e-name='name' e-form='rowform' data-newField='{{ field }}' data-oldField='{{ ::field }}' e-required>
{{ field }}
</span>
</td>
...
</tr>
Edit:
Plunker
I was unable to get the values of the data-oldfield and data-newfield attributes to show on the view, but if you observe the values of the attributes using your brower's dev tools and press the "Rename Field" button, you can see the that the value of data-oldfield changes even though I'm using one time binding. Maybe I'm misunderstanding how the $watchers work for this kind of binding?
From the angular docs it appears that using :: will be updating the object to a new value but will not be updating to view:
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
I'm not quite sure I understand the question but one approach would be to
defined a function in your controller that would be called on ng-change that could perform your logic.
Another would be to $watch the model but watching can be expensive
UPDATE
I'm a bit confused what exactly you're trying to accomplish but here is a plunkr that has an ng-change where you can reference the new value and the old value of $scope.user using a copy of the object. You can also use the renameField function however you'd like

Categories

Resources