I'm experimenting with Vue.JS and composing components together with dynamically.
There's a strange issue where although it seems to be updating the data correctly, if I remove one of the boxes with the call to splice() it always removes the last item in the rendered HTML.
Here's an example fiddle. I'm testing in Chrome.
https://jsfiddle.net/afz6jjn0/
Just for posterity, here's the Vue component code:
Vue.component('content-longtext', {
template: '#content-longtext',
props: {
model: { type: String, required: true },
update: { type: Function, required: true }
},
data() {
return {
inputData: this.model
}
},
methods: {
updateContent(event) {
this.update(event.target.value)
}
},
})
Vue.component('content-image', {
template: '#content-image',
})
Vue.component('content-list', {
template: '#content-list-template',
props: {
remove: { type: Function, required: true },
update: { type: Function, required: true },
views: { type: Array, required: true }
},
methods: {
removeContent(index) {
this.remove(index)
},
updateContent(index) {
return (content) => this.update(index, content)
},
},
})
Vue.component('content-editor', {
template: '#content-editor',
data() {
return {
views: [
{type: 'content-longtext', model: 'test1'},
{type: 'content-longtext', model: 'test2'},
{type: 'content-longtext', model: 'test3'},
{type: 'content-longtext', model: 'test4'},
{type: 'content-longtext', model: 'test5'},
],
}
},
methods: {
newContentBlock(type) {
this.views.push({type: 'content-longtext', model: ''})
},
updateContentBlock(index, model) {
this.views[index].model = model
},
removeContentBlock(index) {
this.views.splice(index, 1)
},
},
})
let app = new Vue({
el: '#app'
})
I've managed to fix the issue thanks to this documentation.
The crux of it is if you don't have a unique key already, you need to store the array index of the object in the object itself, this is because as you mutate the source array you are also mutating it's keys and as far a Vue is concerned when it renders, the last item is missing, not the removed item.
views: [
{index: 0, type: 'content-longtext', model: 'test1'},
{index: 1, type: 'content-longtext', model: 'test2'},
{index: 2, type: 'content-longtext', model: 'test3'},
{index: 3, type: 'content-longtext', model: 'test4'},
{index: 4, type: 'content-longtext', model: 'test5'},
],
...
newContentBlock(type) {
this.views.push({index: this.views.length, type: 'content-longtext', model: ''})
},
Once you have stored the array index you need to add the :key binding to the iterator in the template, and bind that stored value.
<div v-for="(currentView, index) in views" :key="currentView.index">
<component :is="currentView.type" :model="currentView.model" :update="updateContent(index)"></component>
<a v-on:click="removeContent(index)">Remove</a>
</div>
Finally you must make sure you preserve the integrity of your indexes when you mutate the array.
removeContentBlock(index) {
this.views
.splice(index, 1)
.map((view, index) => view.index = index)
},
https://jsfiddle.net/afz6jjn0/5/
Related
I have the following models defined:
var Order = sequalize.define(
"order",
{
id: {
type: Sequelize.STRING,
primaryKey: true,
},
menuId: {
type: Sequelize.UUID,
field: "menu_id",
},
},
{
timestamps: false,
}
);
Item.belongsToMany(Order, { through: OrderItem });
Order.belongsToMany(Item, { through: OrderItem });
and
var OrderItem = sequalize.define(
"order_item",
{
orderId: {
type: Sequelize.UUID,
field: "order_id",
},
itemId: {
type: Sequelize.UUID,
field: "item_id",
},
count: {
type: Sequelize.INTEGER,
field: "count",
},
},
{
timestamps: false,
freezeTableName: true,
}
);
I am trying to figure out how to add a order with items without creating items but just adding them to the relationship.
I have this initial format for the order:
{
"id": "som-other-id7",
"items": [{"id": "727f9b52-a88b-4ec3-a68c-98d190564497", "count": 2}, {"id": "7dfd30e7-2d4a-4b16-ae3d-20a330d9b438"}],
"menuId": "7715af03-968f-40e5-9eb2-98016f3deeca"
}
and I try to add it to the db in the following way:
Order.create(orderJson)
.then((order) =>
orderJson.items.map((item) => order.addItem(item.id, { count: item.count }))
)
However the count is not populated. I tried:
using setItem instead of addItem
instead of passing item.id passing {itemId, orderId}
You should call addItem like this:
order.addItem(item.id, { through: { count: item.count }})
See an example in BelongsToMany section
I'm working on two vue component.sending parent component array data to child component using props.now i want to set pre-selected value in child component dropdownlist.
Here is my code sample:
props:{
// pre-selected value based on this.
userdata:{
type:[Array,Object],
required:true,
},
roles:{
type:[Array,Object],
required:true,
},
},
data(){
return{
mutableRoles:[],
}
},
and this is my view part:
//i want set pre-selected value on this dropdownlist
<select multiple v-model="mutableRoles" class="form-control">
<option v-for="(role,index) in roles" v-bind:value="role.id" >{{role.name}}</option>
</select>
I have seen many example where show only using string. but in my case both are array.
Try this:
const CurrentRole = Vue.component("current-role", {
template: `
<div>
<label>Options</label>
<select v-model="roleId" #change="changeValue">
<option v-for="v in roles" :key="v.id" :value="v.id">{{v.title}}</option>
</select>
</div>
`,
props: {
userdata: {
type: [Array, Object],
required: true,
},
roles: {
type: [Array, Object],
required: true,
}
},
data: _ => ({
roleId: null
}),
methods: {
changeValue() {
this.userdata.role = this.roles.find(e => e.id == this.roleId)
},
},
mounted() { // for initial state
this.roleId = this.userdata.role.id
},
watch: {
userdata(v) { // for changes on parent
if (v) this.roleId = v.role.id
}
}
})
new Vue({
el: "#app",
data: {
rlist: [{
id: 1,
title: "a"
}, {
id: 2,
title: "b"
}, {
id: 3,
title: "c"
}],
user: {
role: {
id: 3,
title: "c"
}
}
},
methods: {
changeUser() {
this.user = {
role: {
id: 1,
title: "a"
}
}
}
}
})
<script src="https://unpkg.com/vue#2.5.22/dist/vue.js"></script>
<div id="app">
<p>User: {{user}}</p>
<current-role :userdata="user" :roles="rlist">
</current-role/>
<button #click="changeUser">change user</button>
</div>
The select is tailored for primitive values, therefore you'll need to add helper functions.
Higher level vue frameworks such as vue-material, vuetify, element and muse-ui tend to offer components to cope with such problems with a higher abstraction level.
EDIT:
I changed the snippet in order to make it closer to your situation.
Basically i've made proyxy-component which renders different components based on what the :type is and it works great. The point is that I create a schema of the form controls and a separate data object where the data from the form controls is stored. Everything is working good but i have a problem when formData object contains nested objects.
In my example test.test1
How can i make the v-model value dynamic which is generated based on what the string is.
My Compoennt
<proxy-component
v-for="(scheme, index) in personSchema.list"
:key="index"
:type="scheme.type"
:props="scheme.props"
v-model="formData[personSchema.prefix][scheme.model]"
v-validate="'required'"
data-vv-value-path="innerValue"
:data-vv-name="scheme.model"
:error-txt="errors.first(scheme.model)"
></proxy-component>
Data
data() {
return {
selectOptions,
formData: {
person: {
given_names: '',
surname: '',
sex: '',
title: '',
date_of_birth: '',
place_of_birth: '',
nationality: '',
country_of_residence: '',
acting_as: '',
test: {
test1: 'test',
},
},
},
personSchema: {
prefix: 'person',
list: [
{
model: 'given_names',
type: 'custom-input-component',
props: {
title: 'Name',
},
},
{
model: 'surname',
type: 'custom-input-componentt',
props: {
title: 'Surname',
},
},
{
model: 'test.test1',
type: 'custom-input-component',
props: {
title: 'test 1',
},
},
{
model: 'sex',
type: 'custom-select-component',
props: {
title: 'Sex',
options: selectOptions.SEX_TYPES,
trackBy: 'value',
label: 'name',
},
},
],
},
};
},
I would recomment you to write a vue-method (under the data section) that returns the object for v-model
v-model="resolveObject(formData[personSchema.prefix][scheme.model])"
or
v-model="resolveObject([personSchema.prefix] , [scheme.model])"
There you can do handle the dot-notation and return the proper nested property.
I don't think it's possible directly with v-model, you can take a look at:
https://v2.vuejs.org/v2/guide/reactivity.html
Maybe the best solution would be use a watch (deep: true) as a workaround.
(I would try first to use watch properties inside formData[personSchema.prefix][scheme.model].)
I have these two array of objects
todos: [
{
id: 1,
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
name: 'handover',
label: 'Handover (in CRM)'
},
]
And:
todosMoreDetails: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}
]
So that the final array of objects will be a combination of the two, based on the object ID, like below:
FinalTodos: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'handover',
label: 'Handover (in CRM)'
}
]
I tried with merge mergeAll and mergeWithKey but I am probably missing something
You can achieve this with an intermediate groupBy:
Transform the todosMoreDetails array into an object keyed by todo property ID using groupBy:
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
moreDetailsById is an object where the key is id, and the value is an array of todos. If the id is unique, this will be a singleton array:
{
1: [{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}]
}
Now transform the todos array by merging each todo to it's details you retrieve from the grouped view:
var finalTodos = R.map(todo => R.merge(todo, moreDetailsById[todo.id][0]), todos);
An alternate more detailed way:
function mergeTodo(todo) {
var details = moreDetailsById[todo.id][0]; // this is not null safe
var finalTodo = R.merge(todo, details);
return finalTodo;
}
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
var finalTodos = todos.map(mergeTodo);
I guess merge is only used for arrays. Have a search for object "extend". Maybe storing the todo details not in seperate objects is the better solution.
Using jQuery? https://api.jquery.com/jquery.extend/
Using underscore? http://underscorejs.org/#extend
Native approach? https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
Using underscore:
var result = [];
var entry = {};
_.each(todos, function(todo) {
_.each(todosMoreDetails, function(detail) {
if (todo.id == detail.id) {
entry = _.extend(todo, detail);
result.push(entry);
}
}
});
return result;
The title may not be clear, but what I want to achieve is to have Categories with parentCategories. For example:
/-Clothes
/---Men
/---Women
/---Kids
/-----Newborns
So I thought I could make every category have an optional parent category and whenever I add a category with a parent one, find this parent category and add the new subCategory to it as well. Am I clear?
This is what I've done so far:
Category.js (Model)
module.exports = {
connection: 'MongoDB',
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
description: {
type: 'string'
},
products: {
collection: 'product',
via: 'category'
},
parentCategory: {
model: 'category'
},
subCategories: {
collection: 'category',
via: 'parentCategory'
},
addSubCategory: function(sc) {
this.subCategories.push(sc);
}
}
};
It doesnt seem to be working. I'm calling addSubCategory from my controller, sc and this values are correct, but it never adds the "subCategory" attribute to the category. I know this may not be the best approach, any suggestions?
You don't need subCategories attribute. Following design would be my suggestion.
module.exports = {
connection: 'MongoDB',
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
type: {
type: 'string',
enum: ['parent', 'subcategory'],
},
description: {
type: 'string'
},
products: {
collection: 'product',
via: 'category'
},
parentCategory: {
model: 'category' // this exists only if type is 'subcategory' else null
},
addSubCategory: function(sc) {
this.subCategories.push(sc);
}
}
};
Let me know if you have any doubt.