Access and modify JSON property using v-model on VUE JS - json

I generate a JSON with a lot of nested data, one of those properties called 'value' inside 'operations' is used in the v-model of every input of a dynamic generated html table. Initially that property is set to 0 and the input is set with that value but when I change the value in the input it doesn't reflect in the json
The JSON
[
{
"establishment_id":1,
"values":[
{
"capa_id":"A",
"operations":[
{
"operation_id":1,
"value":0
},
{
"operation_id":2,
"value":0
},
{
"operation_id":3,
"value":0
},
{
"operation_id":4,
"value":0
}
]
},
{
"capa_id":"B",
"operations":[
{
"operation_id":1,
"value":0
},
{
"operation_id":2,
"value":0
},
{
"operation_id":3,
"value":0
},
{
"operation_id":4,
"value":0
}
]
},
{
"capa_id":"C",
"operations":[
{
"operation_id":1,
"value":0
},
{
"operation_id":2,
"value":0
},
{
"operation_id":3,
"value":0
},
{
"operation_id":4,
"value":0
}
]
},
]
},
]
The component
Establishments, Operations and CAPA are objects taken from an API via Axios, those data are used to generate the table and the json for the input data
<v-expansion-panels multiple focusable popout class="py-5">
<v-expansion-panel class="expandable" v-for="(establishment, establishmentIndex) in establishments">
<v-expansion-panel-header class="exp_estb">
</v-expansion-panel-header>
<v-expansion-panel-content class="ma-0 pa-0">
<v-row no-gutters>
<v-col cols="12">
<form>
<v-simple-table>
<template>
<thead>
<tr>
<th scope="col">ESTABLECIMIENTO</th>
<th v-for="(operation, index) in operations" scope="col">
{{ operation.description }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(capa, capaIndex) in capas">
<td>{{capa.name}}</td>
<td v-for="(operation, operationIndex) in operations">
<span>{{ operation_data[establishmentIndex].values[capaIndex].operations[operationIndex].value }}</span>
<v-row class="pr-5">
<v-col cols="12" class="mr-2 pr-5">
<v-text-field v-model="operation_data[establishmentIndex].values[capaIndex].operations[operationIndex].value" label="" color="#7F53DD"></v-text-field>
</v-col>
</v-row>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</form>
</v-col>
</v-row>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
Computed
computed:
{
operation_data: function()
{
let operation_data=[]
let operationsWithContent=[]
let capasWithOperations=[]
var establishments = Object.values(this.establishments)
var capas = Object.values(this.capas)
var operations = Object.values(this.operations)
operations.forEach(operation => operationsWithContent.push({ operation_id:operation.id, value: 0 }))
capas.forEach(capa => capasWithOperations.push({ capa_id:capa.id, operations:operationsWithContent }))
establishments.forEach( establishment => operation_data.push({ id:establishment.id, values:capasWithOperations}) )
return operation_data
}
},

Is there a specific reason you need a computed here? It seems more logical to fetch your data using a method and saving that result in a variable. Then you can assign the variable to the v-model like you already did in your example and it should work out of the box.
data() {
return {
operation_data: null
}
},
mounted() {
this.fetchData();
},
methods: {
fetchData() {
// Get your data here with axios
this.operation_data = resultOfAxios;
}
}
For your question on why the data doesn't reflect back:
A computed is initially just a getter. You have to manually add a setter for the computed for it to be able to update data.

Related

How to commit multiple nested values that are supposed to be in the same Vuex Store object and that are not mapped to the store object?

How would it work if I want to commit the input value and the selected options in the Vuex store (without the "label" string so that the object I send matches my Vuex store object) ?
Template
<div v-for="(section, indexSections) in sections" :key="indexSections">
<div v-for="(item, indexItem) in section" :key="indexItem">
<div>
<select
v-model="sections[indexSection][indexItem].options"
:options="selectOptions"
></select>
<b-input
type="text"
v-model="sections[indexSection][indexItem].sectionItem"
></b-input>
<b-button #click="removeItem({section,item})"/>
</div>
</div>
<div">
<b-button #click="addNewItem(section)"/>
<b-button #click="addNewSection"/>
</div>
</div>
Data
selectOptions: [
{
options: { option1: true, option2: true },
label: "First"
},
{
options: { option1: false, option2: true },
label: "Second"
}
]
Computed
Computed: {
sections: {
get() {
return this.$store.state.sections;
}
}
Store
sections: [
[{
sectionItem: "",
options: {
strict: true,
includes: true
}
}]
],
You cannot use v-model in order to change the Vuex state, that is what the mutations are for.
v-model is just syntatic sugar and handles the event and updating of values. You have to implement v-on:change yourself and call a Vuex mutation to set the selected option.
Your selectOptions array looks unusual. Usually you just have a label and a value for options. The value is the selected value when the user selects an option.
<template>
<div>
<select #change="onChange">
<option v-for="option in selectOptions" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
</template>
<script>
export default {
data () {
return {
selectOptions: [
//...
],
}
},
methods: {
onChange(event) {
this.$store.commit('setSelectedOption', event.target.value);
},
},
};
</script>

Error of displaying data in server side datatables (Angular)

I'm fairly new in using Angular. At the moment I'm implementing datatables into a webpage. For that, I'm following this guide: https://l-lin.github.io/angular-datatables/#/basic/server-side-angular-way.
Despite the guide, I gave it my own twist. Resulting in a buggy datatable.
No pagination, all names on one page, data gets lost after searching for a name,..
I make use of forkJoin inside the dtOptions, maybe that's the bug? Anyone got an idea?
My code
export class NewPackageComponent implements OnInit {
dtOptions: DataTables.Settings = {};
pageEvent: PageEvent;
clickedTransactionID: string;
packagesArray: string[]
constructor(public packageToolkitService: PackageToolkitService) { }
ngOnInit(): void {
let that = this;
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 10,
processing: true,
ajax: (dataTablesParameters: any, callback) => {
forkJoin([that.packageToolkitService.getPackages()])
.subscribe(packages => {
that.packagesArray = packages[0]['packageNames']
})
}
};
}
PackageToolkitService
getPackages() {
return this.http.get(buildUrl('.gui.wmPackage/_get?'), httpOptions);
}
HTML
<table datatable [dtOptions]="dtOptions" class="row-border hover">
<thead>
<tr>
<th>NAME</th>
</tr>
</thead>
<tbody *ngIf="packagesArray?.length != 0">
<tr *ngFor="let package of packagesArray">
<td>{{ package }}</td>
</tr>
</tbody>
<tbody *ngIf="packagesArray?.length == 0">
<tr>
<td colspan="3" class="no-data-available">No data!</td>
</tr>
<tbody>
</table>
JSON GET Response
{
"requestHdrs": {
"Content-Type": "application/json",
},
"packageMap": [
{
"build": "161",
"name": "WmTN",
"description": "Trading Networks",
"version": "10.1.0.0.161"
},
{
"build": "",
"name": "test",
"description": "",
"version": ""
},
{
"name": "be_nrb_rst_shared_party_v2",
"version": "2.0.0"
}
],
"packageNames": [
"WmTN",
"test",
"be_nrb_rst_shared_party_v2"
]
}
Anyone got an idea?

Error in vue.js (Multiple root nodes returned from render function)

i'm trying to generate html tags (child nodes) from JSON file with vue.js but i have this Error in console:
(Multiple root nodes returned from render function. Render function should return a single root node)
error screenshot
javaScript Code:
const createComponent = (dNode, h) => {
// Handle empty elements and return empty array in case the dNode passed in is empty
if (_.isEmpty(dNode)) {
return [];
}
// if the el is array call createComponent for all elements
if (_.isArray(dNode)) {
return dNode.map((child) => createComponent(child, h))
}
let children = [];
if (dNode.children && dNode.children.length > 0) {
dNode.children.forEach((c) => {
if (_.isString(c)) {
children.push(c)
} else {
children.push(createComponent(c, h))
}
});
}
// Need to clone
const properties = _.cloneDeep(dNode.properties)
return h(dNode.tagName, properties, children.length > 0? children : dNode.textNode)
}
/**
* A sample component uses the recursive createComponent to render a DOM / List of DOM nodes
*/
const MyComponent = Vue.component('my-component', {
render: function (h) {
return createComponent(this.nodes, h)
},
props: {
nodes: {
type: Array,
required: true
}
}
});
new Vue({
el: "#app",
data: {
nodes: []
},
methods: {
getChildrens() {
this.$http.get('nodes.json').then(response => {
this.nodes = response.body;
}, response => {});
}
},
created() {
this.getShortCodes();
this.getChildrens();
}
});
this is nodes.json File Content
[
{
"tagName": "div",
"children": [
{
"tagName": "h1",
"textNode": "Great News"
},
{
"tagName": "h3",
"textNode": "YOU CAN CREATE VUE COMPONENTS OUT OF JSON"
},
{
"tagName": "a",
"properties": {
"attrs": {"href": "#"}
},
"textNode": "Vue.js"
},
{
"tagName": "h2",
"textNode": "Hello from the other side"
}
]
},
{
"tagName": "div",
"children": [
{
"tagName": "h1",
"textNode": "another title"
},
{
"tagName": "h3",
"textNode": "third item"
},
{
"tagName": "a",
"properties": {
"attrs": {"href": "#"}
},
"textNode": "Vue.js"
},
{
"tagName": "h2",
"textNode": "Hello from the other side"
}
]
}
]
This is the vue.js component which i passed nodes as a props
<div id="app">
<div>
<my-component :nodes="nodes"></my-component>
</div>
</div>
Your createComponent returns an array of VNodes on line 9.
return dNode.map((child) => createComponent(child, h))
It seems that you are always passing an array of node definitions on your component and so you are generating an array of VNodes and Vue doesn't like you to have more than one root element in a component.
You have a couple of ways out of this:
Wrap your array in another element. Something like this:
render: function (h) {
return h('div', {}, createComponent(this.nodes, h))
},
Generate one MyComponent for each top element in your JSON.
You could also change the definition of createComponent to always return a single component, but this could break the semantics of createComponent and you may not have access to that code.
This might be possible with this plugin: https://www.npmjs.com/package/vue-fragments
The plugin let's you do cool stuff like this:
import Fragment from 'vue-fragment'
Vue.use(Fragment.Plugin)
// or
import { Plugin } from 'vue-fragment'
Vue.use(Plugin)
// …
export const MyComponent {
template: '
<fragment>
<input type="text" v-model="message">
<span>{{ message }}</span>
</fragment>
',
data() { return { message: 'hello world }}
}
So the fragment itself won't be in the dom. Hope this helps you out, even though i'm quite late with this answer I see.

angular - read property name from JSON as column name and show another property value in the column

I have json file in a structure (showing one of its objects) as
[
...
{
"Tag": "YearOfOperation",
"Type": 9,
"Value": "2018"
}
...
]
on html template I have a table. On table's "Year Of Operation" row , I want to display "2018". I am trying to iterate over json (i.e find by row/tag name) and display display "Value" of the json. In html template, I want to have something like:
<table>
<tr>
<td> Year Of Operation </td> // row name is hard coded. it doesn't depend on json
<td> display json "Value" where "Tag"=="YearOfOperation" </td>
</tr>
</table>
app.component.ts
public productData: any;
ngOnInit() {
this.getJSON().subscribe(res => {
this.productData = JSON.parse(res);
})
}
public getJSON(): Observable<any> {
return this.http.get('./images/output_test.json')
.pipe(
map(res => res)
)
// .catch((error:any) => console.log(error));
}
Thank you in advance.
If your JSON is something like below and i understood your question correctly, then this is what should work for you:
app.json
[
{ "Tag":"Location", "type": 9, "Value":"Europe" },
{ "Tag":"Location", "type": 10, "Value":"US" },
{ "Tag":"YearOfOperation", "type": 9, "Value":"2016" },
{ "Tag":"YearOfOperation", "type": 10, "Value":"2018" },
{ "Tag":"TaxId", "type": 9, "Value":"txn123" },
{ "Tag":"TaxId", "type": 10, "Value":"txn124" }
]
app.component.html
<table>
<thead><tr><th>Location</th><th>Year Of Operation</th><th>Tax ID</th></tr></thead>
<tbody>
<tr *ngFor="let product of products">
<td *ngIf="product?.Tag === 'Location'; else blankValue">{{product?.Value}}</td>
<td *ngIf="product?.Tag === 'YearOfOperation'; else blankValue">{{product?.Value}}</td>
<td *ngIf="product?.Tag === 'TaxId'; else blankValue">{{product?.Value}}</td>
</tr>
</tbody>
</table>
<ng-template #blankValue><td></td></ng-template>
app.component.ts
public products: any;
ngOnInit() {
this.getJSON().subscribe(res => {
this.products = <Array<{}>>res;
})
}
public getJSON(): Observable<any> {
return this.http.get('./images/output_test.json')
.pipe(
map(res => res),
catchError((error) => { console.error(error); return error;})
);
}
app.component.css
th {
padding:10px;
border:1px solid black;
}
td {
padding:10px;
border:1px solid black;
}

AngularJS using ng-show based on grandson json element

I have the following json data in my application:
{
"Success":true,
"Error":null,
"Data":{
"VersionId":1,
"SecretaryId":1,
"SecretaryName":"Foo",
"Status":1,
"Schools":[
{
"SchoolId":123456,
"SchoolName":"Equipe de Desenvolvimento do Portal",
"ContractStatus":1,
"TotalTeachers":2,
"TotalStudents":0,
"Grades":[
{
"GradeId":1,
"GradeName":"2º Year",
"TotalStudents":0,
"Classes":[
{
"SelectedYear":{
"AvailableYear":null,
"Contract":null,
"Id":2,
"Canceled":false,
"EngagedAreas":null,
"Registrations":null
},
"Id":2,
"Name":"A",
"TotalStudents":20,
"TotalA3":1,
"StudentsPCD":"1,2,3"
},
{
"SelectedYear":{
"AvailableYear":null,
"Contract":null,
"Id":2,
"Canceled":false,
"EngagedAreas":null,
"Registrations":null
},
"Id":3,
"Name":"B",
"TotalStudents":25,
"TotalA3":0,
"StudentsPCD":"1"
}
]
},
{
"GradeId":2,
"GradeName":"3º Year",
"TotalStudents":0,
"Classes":[
]
},
{
"GradeId":3,
"GradeName":"4º Year",
"TotalStudents":0,
"Classes":[
]
}
]
},
{
"SchoolId":52640002,
"SchoolName":"EscTesRelatDir",
"ContractStatus":0,
"TotalTeachers":0,
"TotalStudents":0,
"Grades":[
{
"GradeId":1,
"GradeName":"2º Year",
"TotalStudents":0,
"Classes":[
]
},
{
"GradeId":2,
"GradeName":"3º Year",
"TotalStudents":0,
"Classes":[
]
}
]
}
]
}
}
I use this json to dynamically create some HTML.
For each school a div is created, this is the first level. For each grade in the school, a table is created, this is the second level. And for each class in the grade a table row is created, this is the third level.
I'm wondering how could I use the ng-show command to show the first level div only if the related school have at least one class.
I've searched a lot, but I couldn't find an answer yet.
EDIT:
Here is my html code.
<div ng-repeat="school in data.schools" ng-show="{{ school.Grades.<AnyClass?>.length > 0 }}">
<h2>{{ school.SchoolName }}</h2>
<span><strong>Total Teachers:</strong> {{ school.TotalTeachers }}</span>
<table ng-repeat="grade in school.Grades" class="table table-bordered table-striped table-condensed" ng-show="{{ grade.Classes.length > 0 }}">
<tbody>
<tr>
<th colspan="4">{{ grade.GradeName }}</th>
</tr>
<tr>
<th>Class</th>
<th>Total Students</th>
<th>Total A3</th>
<th>PCD Students</th>
</tr>
<tr ng-repeat="class in grade.Classes">
<td>{{ class.Name }}</td>
<td>{{ class.TotalStudents }}</td>
<td>{{ class.TotalA3 }}</td>
<td>{{ class.StudentsPCD }}</td>
</tr>
</tbody>
</table>
</div>
EDIT
This is how I'm making my requests:
//rest_client.js
"use strict";
(function(){
var restClient = angular.module("restClient", ["ngResource"]);
var serviceURL = "/habileapp/api/";
restClient.factory("RegistrationResource", ["$resource", function ($resource) {
return $resource(serviceURL + "Registration", null, {
"get": {
"method": "get"
},
"save": {
"method": "post",
"url": serviceURL + "Registration/Save"
},
"finalize": {
"method": "post",
"url": serviceURL + "Registration/Finalize"
}
});
}]);
})();
//part of app.js
RegistrationResource.get({
"idAplicacao": 1,
"idSecretaria": 1
}, function (data) {
if (data.Success) {
$scope.data = data.Data;
runApplication();
} else {
toastr.error(Errors.Code1);
}
hideLoading();
});
I don't think it is possible within your template since you cannot bubble up through the different ng-repeats. But you could add another attribute to each school object whether its Classes array length is greater than 0 within your controller:
//part of app.js
RegistrationResource.get({
"idAplicacao": 1,
"idSecretaria": 1
}, function (data) {
if (data.Success) {
angular.forEach(data.Schools, function(item) {
angular.forEach(item.Grades, function(item2) {
(item2.Classes.length > 0) && item.show = true;
});
});
$scope.data = data.Data;
runApplication();
} else {
toastr.error(Errors.Code1);
}
hideLoading();
});
Then, just add ng-show="show" in your markup.
After getting the data you can change it or add any "helper" properties that you can use to control the elements you want to show or how to show them. Consider the following example which makes any entry red that has a "long title", being more than 40 characters:
HTML template
<body ng-controller="Ctrl">
<b><u>Posts</u></b>
<div ng-repeat="post in posts">
<span ng-class="{ 'red': post.longTitle }">
id:{{ post.id }} title:{{ post.title }}
</span>
</div>
</body>
JavaScript
app.controller('Ctrl', function($scope, $http) {
var url = 'http://jsonplaceholder.typicode.com/posts';
// get data
$http.get(url).then(function(response) {
// take top 10
var data = response.data.slice(0, 10);
// add helper property called "long title" that
// doesn't exists in original data
angular.forEach(data, function(current) {
current.longTitle = current.title.length > 40 ? true : false;
});
// apply
$scope.posts = data;
});
});
CSS
.red {
color: red;
}
Result