Vue array updates in developer tools but doesn't re-render - javascript

I have a fairly complicated object with nested components. It looks sort of like this (stripped down for easier reading):
<script type="text/x-template" id="fieldset-template">
<div class="fieldset">
<div class="fieldset-repetition" v-for="(repetition, key) in repetitions">
<div class="field-list">
<field v-for="field in fields" v-bind:key="field.key" v-bind:fieldset="field.fieldset" v-bind:fieldset-key="key" v-bind:field-data="field"></field>
</div>
<div class="repetition-meta">
<a class="move-repetition" v-on:click="moveUp(key)">Up</a>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="field-template">
<div class="field">
<div class="form-group">
<label class="control-label" v-html="name"></label>
<div class="field-repetition" v-for="(repetition, key) in repetitions">
<div class="field-text">
<input class="form-control" v-model="values[key]" />
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('set', {
components: {
field: {
created: function() {
// populate with data
this.populateData();
},
data: function() {
return {
repetitions: [],
values: [],
}
},
methods: {
populateData: function() {
this.repetitions = this.fieldData.repetitions;
this.repetitions.forEach(function(repetition, key) {
this.values = this.fieldData.value[this.fieldsetKey];
}.bind(this));
},
repeatField: function() {
var field = Object.clone(this);
delete field.repetitions;
this.repetitions.push(field);
if (this.widget != 'checkbox') {
this.values.push(this.fieldData.default);
}
else {
this.values.push([this.fieldData.default]);
}
},
},
props: {
fieldData: {
type: Object
},
fieldset: {
type: Object
},
fieldsetKey: {
type: Number
}
},
template: '#field-template'
}
},
data: function() {
return {
fields: [FieldObject1, FieldObject2, FieldObject3],
repetitions: [RepetitionObject1, RepetitionObject2, RepetitionObject3, RepetitionObject4, RepetitionObject5],
}
},
methods: {
moveUp: function(key) {
var field = this.fields[0];
var value = field.value[key];
field.value[key] = field.value[key - 1];
field.value[key - 1] = value;
this.$set(this.fields, 0, field);
}
},
template: '#fieldset-template'
});
</script>
Whenever the moveUp method runs, it updates the fields object, but the field component doesn't re-render.
I have suspicion it is because of the secondary (outer) for cycle for the repetitions, but I couldn't figure a way around it.

this.$set(this.fields, 0, field); won't do anything as this.fields[0] is already equal to field.
Assuming field.value is an array, it's this step that's making a non-reactive change:
field.value[key] = field.value[key - 1];
field.value[key - 1] = value;
See https://v2.vuejs.org/v2/guide/list.html#Caveats
You could write it as:
this.$set(field.value, key, field.value[key - 1]);
this.$set(field.value, key - 1, value);
Or use splice:
field.value.splice(key - 1, 2, field.value[key], field.value[key - 1]);

Related

How to insert vue.js computed data into form data?

<template>
<form #submit.prevent="uploadMeasurement(measure)">
<input v-model="measure.length">
<input v-model="measure.width">
</form>
</template>
<script>
export default {
data() {
return {
measure: this.createFreshMeasure(),
};
},
computed: {
sqftTotal: function() {
return this.length * this.width;
}
},
methods: {
uploadMeasurement(measure) {
MeasurementService.uploadMeasurement(measure)
.then(...);
this.measure = this.createFreshMeasure();
})
.catch(error => {
this.error = error.response.data.error;
});
},
createFreshMeasure() {
return {
length: this.length,
width: this.width,
sqftTotal: this.sqftTotal
};
}
</script>
On submit, I'd like to calculate a square footage value using the values placed into the length and width inputs and send all three into the Mongo database.
The database is storing a value for sqftTotal when I send a hard-coded value directly over Postman, so it's capable of doing it, but this Vue form isn't accomplishing that task.
methods: {
uploadMeasurement() {
let measure = this.measure;
measure.sqftTotal = this.sqftTotal;
MeasurementService.uploadMeasurement(measure)
...
Got it, thanks to everyone for your input. Had to remove the argument from the method and declare it before the service call.
The easiest way to accomplish this would be something like this.. I have commented different options within the code to help explain things..
new Vue({
el: "#root",
template: `
<div>
<form ref="form">
<!--<input v-model="measure.length">
<input v-model="measure.width">-->
<input v-model="length">
<input v-model="width">
</form>
<button #click.prevent="uploadMeasurement">Submit</button>
</div>
`,
data: {
//measure: ""
length: "",
width: "",
},
computed: {
sqftTotal: function() {
//return this.measure.length * this.measure.width;
return this.length * this.width;
}
},
methods: {
uploadMeasurement() {
/** This is where you would POST the data **/
// You can either submit the form:
// -NOTE: '...$refs.form...' below must match the ref
// you assign to the <form ref="form"> element.
// this.$refs.form.$el.submit();
// ~ OR ~
// manually POST via fetch, etc:
// fetch('/url/to/post/to', {
// method: 'POST',
// body: JSON.stringify({
// length: this.measure.length,
// width: this.measure.width,
// sqftTotal: this.sqftTotal
// })
// })
alert(JSON.stringify({
//length: this.measure.length,
//width: this.measure.width,
length: this.length,
width: this.width,
sqftTotal: this.sqftTotal
}));
},
createFreshMeasure() {
this.length = 10;
this.width = 5;
//return {
// length: 10,
// width: 5
//};
}
},
created() {
this.createFreshMeasure();
//this.measure = this.createFreshMeasure();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="root"></div>
I recommend cleaning up your code like below, as Vue often has issues when using object properties as a model like that
<template>
<form #submit.prevent="uploadMeasurement()">
<input v-model="length">
<input v-model="width">
</form>
</template>
<script>
export default {
data() {
return {
length: null,
width: null,
};
},
computed: {
sqftTotal: function() {
return this.length * this.width;
}
},
methods: {
uploadMeasurement() {
MeasurementService.uploadMeasurement({
length: this.length,
width: this.width,
sqftTotal: this.sqftTotal
})
.then(() => {
console.log('save success!');
})
.catch(error => {
this.error = error.response.data.error;
});
},
}
</script>

Validation and default popup close

I have created a custom node in node-red
<script type="text/javascript">
RED.nodes.registerType('project', {
category: 'My Category',
color: 'rgb(192, 237, 192)',
defaults: {
name: { value: "", required:true },
project: { value: "", required:true }
},
inputs: 0,
outputs: 1,
onpaletteadd: function (index) {
var node = this;
var sessionStorageData = sessionStorage.getItem(node.z);
if (sessionStorageData && (JSON.parse(sessionStorageData)).id != node.id) {
alert("Flow cannot have more then one Project node!!!")
}
if (!sessionStorageData) {
sessionStorageData = { id: node.id }
} else {
sessionStorageData = JSON.parse(sessionStorageData);
}
sessionStorageData.project = node.project;
sessionStorage.setItem(node.z, JSON.stringify(sessionStorageData));
},
oneditprepare: function (index) {
var node = this;
$.ajax({
type: "GET",
url: "../getExternalData?path=get",
dataType: "json",
success: function (data1) {
this.preload = true;
var appenddata1 = "";
$.each(data1, function (key, val) {
appenddata1 += "<option value = '" + key + "'>" + val + " </option>";
});
$("#node-input-project").append(appenddata1);
$("#node-input-project").val(node.project);
}
});
},
oneditsave: function (index) {
var node = this;
var sessionStorageData = sessionStorage.getItem(node.z);
if (!sessionStorageData) {
sessionStorageData = { id: node.id }
} else {
sessionStorageData = JSON.parse(sessionStorageData);
}
sessionStorageData.project = $("#node-input-project").val();
sessionStorage.setItem(node.z, JSON.stringify(sessionStorageData));
},
icon: "cog.png",
label: function () {
return this.name || "Project";
}
});
</script>
<script type="text/x-red" data-template-name="project">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i>Workflow Name</label>
<input type="text" id="node-input-name" placeholder="Workflow Name"/>
</div>
<div class="form-row">
<label for="node-input-project"><i class="icon-tag"></i> Project</label>
<select type="text" id="node-input-project">
<option value=" ">Please select a Project</option>
</select>
</div>
</script>
to my surprise required is only making the text box red and is not stopping the popup close on click of Done button, i tried return false in oneditsave but that doesn't help either.
As described in the Node-RED docs on creating nodes, you can add a validate function to the defaults section of the node's html file.
There are 2 built in validators,
RED.validators.number()
RED.validators.regex(re)
But you can attach your own function as well:
defaults: {
minimumLength: { value:0, validate:RED.validators.number() },
lowerCaseOnly: {value:"", validate:RED.validators.regex(/[a-z]+/) },
custom: { value:"", validate:function(v) {
var minimumLength=$("#node-input-minimumLength").length?$("#node-input-minimumLength").val():this.minimumLength;
return v.length > minimumLength
} }
},
But even after using either these or the required: true the user will still be able to hit the Done button. A warning will be shown to the user when they try to deploy a flow that contains nodes that have missing or invalid field values but there is no way to stop a user deploying a flow with bad input data.

Not getting any results back from Fuse.js with Vue

So I'm fairly new to Vue and I'm trying to make a customer list search work with Fuse.js.
I do get the array of customers back and it's being assigned to customer_search. my keys are populated properly and the only issue is that results doesn't return anything. I'm wondering if I need to structure my customer array differently or am I missing something else altogether?
Any help would be appreciated.
Here is my code:
<template>
<div>
<div class="container">
<h1>Search</h1>
<input type="text" class="input-search" value="" v-model="query">
<p v-html="results"></p>
<p v-for="info in data" >{{info}}</p>
</div>
</div>
</template>
<script>
import Fuse from 'fuse.js'
import $ from 'jquery'
import PageService from '../../common/services/PageService'
const Search = {
data(){
return {
data: {},
fuse: {},
results: {},
query: '',
options: {
keys: [
'id',
'name',
'company',
],
minMatchCharLength: 3,
shouldSort: true,
threshold: 0.5
},
}
},
methods:{
runQuery(query){
if(query.length >= 3)
this.results = this.fuse.search(query)
},
},
computed:{
customers: function(){
return this.data
},
customer_search: function(){
return Object.values(this.data)
},
},
watch: {
query: function(){
this.runQuery(this.query)
}
},
created(){
this.fuse = new Fuse(this.customer_search, this.options)
if(this.$store.state.search != ''){
this.query = this.$store.state.search
}
PageService.getSearchObject().then((response)=>{
this.data = response.data
}).catch((err)=>{
console.log('Error')
});
},
}
export default Search
</script>
I think your runQuery method is created before your this.fuse get created so the this.fuse inside your runQuery method is not up-to-date.
Maybe try:
methods:{
runQuery(query){
if(query.length >= 3)
this.results = new Fuse(this.customer_search, this.options).search(query)
},
},

Unable to bind value return from function in v-for?

I am not able to bind function returned value with dom.
i want to display "no data - DOM" if function isNoStatuses(id,index) returns true in v-for element else it should not display.
you can refer below code. i am using vuejs2.
I just want to get status while v-for. the v-for element should show dom based on status.
Here is my sample code:
<div class="row gutter wrap justify-stretch" v-for="(row, index) in items">
<div>{{row.name}}</div> <!-- display data -->
<!-- if data not available i.e isNoStatuses(id,index) return true display below-->
<div v-if="isNoStatuses(row.id,index) === true">
<div class="card">
<div class="card-content">
<i class="material-icons">error</i> Oops! content not found!
</div>
</div>
</div>
</div>
<script>
import appService from 'service'
export default {
data () {
return {
searchedTopic: '',
items: [{id: 1, name: 'abc'}, {id: 2, name: 'xxabc'}, {id: 3, name: 'dabsc'}],
noMoreData: false
}
},
methods: {
logout () {
appService.logout()
},
isNoStatuses (idStr, i) {
appService.getstatus(idStr).then((resp) => {
var res = resp.data
console.log('get res: ' + JSON.stringify(res))
if (res.success === 1) {
return false
}
else {
return true
}
}).catch((e) => {
console.log('error while getting : ' + JSON.stringify(e))
})
},
created () {
appService.checkAuth()
},
components: {
},
mounted () {
}
}
</script>
---
i have also tried like this: the status will be taken after mounted and response status will be store in status[].
..
<div class="row gutter wrap justify-stretch" v-for="(row, index) in items">
<div>{{row.name}}</div> <!-- display data -->
<!-- if data not available i.e isNoStatuses(id,index) will set status[index] true display below-->
<div v-if="status[index] === true">
<div class="card">
<div class="card-content">
<i class="material-icons">error</i> Oops! content not found!
</div>
</div>
</div>
..
<script>
import appService from 'service'
export default {
data () {
return {
searchedTopic: '',
items: [{}, {}, {}],
noMoreData: false,
status: []
}
},
methods: {
logout () {
appService.logout()
},
isNoStatuses (idStr, i) {
appService.getstatus(idStr).then((resp) => {
var res = resp.data
console.log('get res: ' + JSON.stringify(res))
if (res.success === 1) {
this.status[i] = false
}
else {
this.status[i] = true
}
}).catch((e) => {
console.log('error while getting : ' + JSON.stringify(e))
})
},
created () {
appService.checkAuth()
},
components: {
},
mounted () {
for (var i = 0; i < this.items.length; i++) {
this.isNoStatuses(this.item[i].id_str, i)
}
}
}
</script>

Rivets.js sync model with children component

i'm trying to write a module in rivets.js.
When i create a component with another nested component, the model is not in sync.
I just cant figure.
How do i sync value of hash to be same in the parent and child component?
Thank you.
Here is the fiddle (pen): http://codepen.io/anon/pen/qNmNJO?editors=1010
rivets.formatters.log = (data) => {
console.log(data);
};
rivets.formatters.filter = (items, arg) => {
console.log(items);
items = items.filter((item) => {
return item[arg];
});
return items;
};
rivets.formatters.eq = (value, arg) => {
return value == arg;
};
rivets.formatters.gt = (value, arg) => {
return value > arg;
};
rivets.formatters.lt = (value, arg) => {
return value < arg;
};
function ItemList(attributes) {
this.data = attributes;
this.checkbox_change = function(e, data) {
data.data.hash++;
};
}
rivets.components['item-list'] = {
template: function() {
return `
<div class="dest_wrap" rv-each-destination="data.destinations">
<label>
<input data-type="country" rv-on-click="checkbox_change" rv-checked="destination.selected" type="checkbox">
{ destination.name }
</label>
{ data.hash | log }
<span rv-text="data.hash"></span>
</div>
`;
},
initialize: function(el, attributes) {
return new ItemList(attributes);
}
};
// https://github.com/whayler1/rivets-example
let model = {
hash: 0,
destination_tree: [{
name: 'Itálie',
selected: false,
id: 1
}, {
name: 'Chorvatsko',
selected: true,
id: 2
}, {
name: 'Bulharsko',
selected: true,
id: 3
}],
};
function DestinationPicker(attributes) {
this.data = attributes;
}
rivets.components['destination-picker'] = {
template: function() {
return `
<div>
<item-list
hash="data.hash"
destinations=".data.destinations"
level="'countries'"
/>
</div>
<input type="number" rv-value="data.hash">
<span rv-text="data.hash"></span>
`;
},
initialize: function(el, attributes) {
return new DestinationPicker(attributes);
}
};
window.rivets_view = rivets.bind($('destination-picker'), model); // K čemu mohou přistupovat elementy
<script src="https://cdnjs.cloudflare.com/ajax/libs/rivets/0.9.0/rivets.bundled.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<main class="row">
<destination-picker destinations="destination_tree" hash="hash"></destination-picker>
</main>

Categories