v-for and component external (croppa) - vue.js

I have a dynamic component with Vue Croppa.
<croppa v-for="(image, key) in images" :key="key" ...props></croppa>
If I add an element in images, it add a new croppa components and work perfect.
The problem is when I try to remove an element.
For example, if I added 3 images:
images: [
'image1.png',
'image2.png',
'image3.png,
]
if I deleted image2 (index: 1) it remove image3.
What I need to do to preserve all config (initial-image, position, flipX, etc...) of croppa by position?
Thanks

Using index argument of v-for directive (in your case key) is reliable way to set item key attribute only if looped array (in your case images) is not going to be mutated.
In example above, when images length changes indexes of rendered items stay the same. Meaning, if second item has been deleted, renderer can notice changes of key indexes
from 0, 1, 2 to 0, 1
Thus, the item with last index 2 is removed.
In order to achieve expected behavior, key attribute should be set to a unique value. For array of objects it is usually an id or any unique property. For array of strings (if values in array are unique) the key attribute can be set to that value. Example:
<croppa
v-for="image in images"
:key="image"
/>
In case if some values in that array are not unique (which should be assumed if values come from external source - API, user input, etc), key attribute should be set to concatenated value and index. Example:
<croppa
v-for="(image, index) in images"
:key="`${image}${index}`"
/>

Related

Using inner component in a loop in Shopware 6 does not persist the values uniquely for each looped component

I am using a v-for over a custom component and passing the item as a prop. But the issue is that each component instance in the loop takes the same item prop. For e.g in the 1st loop a component field has text "abc", then the second looped component also will have the same "abc" text. If I change the text in the 2nd one, it changes in the 1st component too. Is there a way to make the prop unique for each loop ?
For e.g this is the code which calls the inner component:
<template v-for="(businesscase, index) in businessCase.fields">
<custom-case-freetext-field #field-changed="updateFields"
:key="index"
#field-removed="removeFields"
:fields="businessCase.fields"
:index="index">
</custom-case-freetext-field>
</template>
and inside this component I have a basic form
<sw-field :label="$tc('rma.modules.case.freetext.nameLabel')"
:placeholder="$tc('rma.modules.case.freetext.nameLabel')"
required
v-model="fields[index].name">
</sw-field>
<sw-single-select
labelValue="label"
valueProperty="label"
:options="fieldTypes"
:label="$tc('rma.modules.case.freetext.fieldType')"
:placeholder="$tc('rma.modules.case.freetext.fieldType')"
v-model="fields[index].type"
#input="changeType"
required>
</sw-single-select>
If I do :value instead of v-model, the entered value disappears as soon as the element loses focus.
If I use v-model, the data stays there, but then both (or as many are there in the loop) component instances, have data binding between them, so it defeats the purpose of having a loop for multiple components. As seen in the screenshot, I am typing in the 2nd component, but it changes the text for the first one too.
In the above example I am sending the whole array as prop, but I have also tried with individual field element instead of fields
Your are not using the businesscase variable inside your components. And since every component always works on the upper scope property, they will all change the same. Use the innerscope property. If you have problems with reactivity, because you try to mutate props directly work with events emitting the key and the changed value to the upperscope component.

vuejs: insert spans into text - common practice

using vue.js I'm wondering what is considered the best way to insert span elements into a text at multiple given positions? Specifically, in a simplified example, I have:
text = "This is a random text. It goes on and on..."
positions = [0, 5, 17]
span_templ = '<span class="some_class" />' <-- closed span for sake of simplicity
The workflow is: the user clicks on a button, some RESTful request is sent to a server, which returns both text and positions. A <p> shall be updated with the text but with inserted <span> elements.
I'm relatively new to vue.js, so I'm wondering what is more typical in vue.js: rather do the String update in the template {{ ... }} itself or would you first update the String in plan JS and then simply refer to the String in the moustache part?
You can use v-html and pass the HTML string as prop.
Example use: https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML
API doc: https://v2.vuejs.org/v2/api/#v-html

Vue remove child component with it state

I want delete child component. I use this.rows.splice(index, 1)
When i call this.rows.splice(index, 1) VueJs always remove last component from this.$children and save internal state in component.$data ;
Example is here
`https://jsfiddle.net/abratko/gc7h1r34/3/`
How fix it?
Vue associates each data item with each vnode according to the item's index by default. This results in existing Vue components being reused, but bound to different items, when re-rendering the list after an item was removed from the array.
This is why you should always bind key to a value which uniquely identifies that particular item. In your example, since each item is a unique string you can just bind to that:
<row-component v-for="(row, index) in rows" :key="row">
^^^^^^^^^^

In VueJS, v-for doesn't create discrete items when new data is assigned

Currently, when I have a v-for loop like this:
<li v-for="record in records">
<my-vue-list-item :vue-title="record.title"></my-vue-list-item>
</li>
and I update the data driving the v-for (i.e. assign a new value to records), it doesn't actually create new list items based on the new data. It just updates some of the properties on the list item components components.
Here is a JSFiddle illustrating what I'm talking about. Try expanding one of the buttons (e.g. click "Expand Apple"), then click on "Set 2" to see that even when the list items switch, the component stays expanded.
What's the recommended way of getting around this behavior? I want each new list item (when the data is swapped out) to appear like new. (In the fiddle example, which I load a new set, it should not be already expanded.)
You need to let the Vue components know that the item is different. As far as they know, they're still rendering the same element index from the same list.
You can do this by specifying the key in your v-for iterator...
<li v-for="item in items" :key="item">
https://jsfiddle.net/ahxf44jk/21/

Aurelia not outputting attribute with string interpolation in repeat

Is there any reason why a repeat.for binding would remove attributes from elements inside the repeater?
<div repeat.for="i of model.someArray.length">
<label>Some Array - Index ${i + 1}</label>
<input value.bind="model.someArray[i]" some-custom-attribute="someArray[${i}]"/>
</div>
and that some-custom-attribute is not being output within the repeat, but if I were to remove the string interpolation within there then it outputs fine.
== Edit ==
I have put it in a comment but just to make sure everyone is on the same page, ideally this is the output I expect:
<input value.bind="model.someArray[i]" some-custom-attribute="someArray[0]"/>
The some-custom-attribute is not an aurelia attribute, its pure HTML that a 3rd party JS library uses, so the goal here is to get the textual value of the index into the textual attribute value.
model.someArray.length is a number, not an array. You need to iterate over the array. If you do need the current index, the repeater provides the $index property for you to use.
Your code should look like this:
<div repeat.for="item of model.someArray">
<label>Some Array - Index ${$index + 1}</label>
<input value.bind="item" some-custom-attribute.bind="item"/>
</div>
To answer your original question, doing some-custom-attribute="model.someArray[${i}]" makes Aurelia think you are trying to pass a string value to the custom attribute. You can see that in the following gist: https://gist.run/?id=eed8ac8623ff4749aa5bb93c82a7b1fb I've created a custom element that just pushes whatever value it is given in to an element on the page. Note!!! Don't ever do what I'm doing here! I just did this this way so you wouldn't have to open the js console. To actually get a value passed in, you would need to use some-custom-attribute.bind="item" or (to do things how you are doing things, some-custom-attribute.bind="someArray[i]"