Update value in multidimensional array in Vue - javascript

I understand from the caveats portion of the Vue docs that updating a value in an array in the following manner will not work:
this.arr[idx] = newVal
and that one should use splice(). I am using a 2D array to store grid data, and I am having a difficult time updating the value when a cell in the grid is clicked.
Here is my template:
<tr
v-for="(row, rowKey, index) in grid"
:key="rowKey">
<th
class="row-col-label"
>{{rowKey+1}}</th>
<td
v-for="(col, colKey, index) in row"
:key="colKey"
#click="selectCell(rowKey, colKey)"
:class="{'selected' : cellSelected(rowKey, colKey)}"
>
{{col}}
</td>
</tr>
And here is the relevant code for the Vue component:
created () {
this.initColHead()
this.createSpreadSheet()
},
data () {
return {
selected: '',
grid: [],
colHead: [' '],
isSelected: false
}
},
methods: {
initColHead () {
this.colHead.push(...'ABC'.split(''))
},
createSpreadSheet () {
for (let i = 0; i <= 2; i++) {
this.grid[i] = []
for (let j = 0; j <= 2; j++) {
this.grid[i][j] = false
}
}
},
selectCell (row, col) {
this.isSelected = true
console.log(`row ${row} col ${col}`)
this.grid[row].splice(col, 1, true)
for (let i = 0; i <= 2; i++) {
for (let j = 0; j <= 2; j++) {
console.log(this.grid[i][j])
}
}
},
cellSelected (row, col) {
return (this.grid[row][col] === true)
}
}
So I am attempting to add a true value to the cell that is click at the given row col locations provided in the my selectCell method. However, the data in my grid is not updated to reflect the newly added value. How exactly do I update values in a multidimensional array in Vue?

One method that works:
selectCell (row, col) {
//make a copy of the row
const newRow = this.grid[row].slice(0)
// update the value
newRow[col] = true
// update it in the grid
this.$set(this.grid, row, newRow)
},
Here is an example.
console.clear()
new Vue({
el: "#app",
created() {
this.initColHead()
this.createSpreadSheet()
},
data() {
return {
selected: '',
grid: [],
colHead: [' '],
isSelected: false
}
},
methods: {
initColHead() {
this.colHead.push(...'ABC'.split(''))
},
createSpreadSheet() {
for (let i = 0; i <= 2; i++) {
this.grid[i] = []
for (let j = 0; j <= 2; j++) {
this.grid[i][j] = false
}
}
},
selectCell(row, col) {
const newRow = this.grid[row].slice(0)
newRow[col] = true
this.$set(this.grid, row, newRow)
},
cellSelected(row, col) {
return (this.grid[row][col] === true)
}
}
})
.selected {
background-color: green;
}
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<table>
<tr v-for="(row, rowKey, index) in grid" :key="rowKey">
<th class="row-col-label">{{rowKey+1}}</th>
<td v-for="(col, colKey, index) in row" :key="colKey" #click="selectCell(rowKey, colKey)" :class="{'selected' : cellSelected(rowKey, colKey)}">
{{col}}
</td>
</tr>
</table>
</div>
If I think of something better I'll update later.

The difficulty is that you're building the array in a way that Vue does not make its rows reactive. You could build the array and then assign it to the data item as a whole so that Vue would make it reactive, or you can build the array (at last the rows) using push, which will make them reactive. Then you can modify individual elements using splice. Modifying Bert's example:
console.clear()
new Vue({
el: "#app",
created() {
this.initColHead()
this.createSpreadSheet()
},
data() {
return {
selected: '',
grid: [],
colHead: [' '],
isSelected: false
}
},
methods: {
initColHead() {
this.colHead.push(...'ABC'.split(''))
},
createSpreadSheet() {
for (var i = 0; i <= 2; i++) {
this.grid.push([]);
for (var j = 0; j <= 2; j++) {
this.grid[i].push(false);
}
}
},
selectCell(row, col) {
this.grid[row].splice(col, 1, true);
},
cellSelected(row, col) {
return (this.grid[row][col] === true)
}
}
})
.selected {
background-color: green;
}
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<table>
<tr v-for="(row, rowKey, index) in grid" :key="rowKey">
<th class="row-col-label">{{rowKey+1}}</th>
<td v-for="(col, colKey, index) in row" :key="colKey" #click="selectCell(rowKey, colKey)" :class="{'selected' : cellSelected(rowKey, colKey)}">
{{col}}
</td>
</tr>
</table>
</div>

Related

Shopware 6 : Delete data with custom Administration

I want remove Data of table 'filter_object' with related table 'filter_link' bevor starting the action of button onClickSync().
The 'filter_link' table contains two foreign key : product_id and object_id.
I tried to delete the data by id in a for loop, but this only deletes the data from the 'filter_object' table without related product. In addition it slows down the deletion when I have several data. Could help me please ?
import template from './sw-vehicles-list.html.twig';
const { Component } = Shopware;
const { Criteria } = Shopware.Data;
Component.register('sw-vehicles-list', {
template,
inject: ['repositoryFactory'],
data() {
return {
repository: null,
showAddButton: true,
isLoading: false,
object: null,
};
},
metaInfo() {
return {
title: this.$createTitle()
};
},
computed: {
filterObjectRepository() {
return this.repositoryFactory.create('filter_object');
},
filterLinkRepository() {
return this.repositoryFactory.create('filter_link');
},
productRepository() {
return this.repositoryFactory.create('product');
},
},
created() {
this.object = this.repositoryFactory.create('filter_object');
this.link = this.repositoryFactory.create('filter_link');
},
methods: {
async onClickSync() {
this.isLoading = true;
await this.removeData();
this.repository.search(new Criteria(), Shopware.Context.api).then((result) => {
if (result.length) {
var i;
var manufacturer = [];
for (i = 0; i < result.length; i++) {
manufacturer.push(result[i]['manufacturer']);
}
var manufacturerFilter = Array.from(new Set(manufacturer));
var j;
for ( j = 0; j < manufacturerFilter.length; j++) {
// some code
}
}
});
},
removeData() {
return this.filterObjectRepository.search(new Criteria(), Shopware.Context.api).then((result) => {
if (result.length) {
var i;
for (i = 0; i < result.length; ++i) {
this.filterObjectRepository.delete(result[i]['id'], Shopware.Context.api).then(this.loadObject);
}
return null;
}
});
},
loadObject() {
this.filterObjectRepository.search(new Criteria(), Shopware.Context.api).then((result) => {
this.result = result;
});
},
}
});
You can use the syncDeleted method of the repository to delete multiple records.
const ids = result.map(record => record.id);
this.filterObjectRepository.syncDeleted(ids, Shopware.Context.api)
If you want filter_link records to be deleted when deleting records from filter_object you'll have to set the foreign keys ON DELETE subclause to CASCADE.
ALTER TABLE `filter_link`
DROP FOREIGN KEY `fk.filter_link.object_id`;
ALTER TABLE `filter_link`
ADD CONSTRAINT `fk.filter_link.object_id` FOREIGN KEY (`object_id`) REFERENCES `filter_object` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Pause functions on page history forward backward

There is Home.vue and Statistics.vue pages. Home.vue render the TableFields.vue component. At Home.vue there are fields numbers with initial value "3" set on page load. Set interval on calculate function adds numbers every two seconds. How to achieve, that when going from '/' to '/statistics' all the changing should be paused, and on returning back it should be resumed? Bellow each field at home page, already there are buttons that toggle setInterval() function and stop/resume calculations. Basically, when going from "/" to "/statistics" clearInterval() should be triggered that stops the calculations at "/" page. #Saksham TableFields.vue:
<template>
<div>
<table class="table-a">
<tr>
<th>A</th>
<td class="sign">{{ this.randomSign.A }}</td>
<td>{{ initialValue.A }}</td>
<td v-show="this.randomSign.A == '+'">⬆</td>
<td v-show="this.randomSign.A == '-'">⬇</td>
</tr>
</table>
<button #click="toggleIntervalA()">
<span v-show="this.startStop.A">Stop</span>
<span v-show="!this.startStop.A">Start</span>
</button>
<table class="table-b">
<tr>
<th>B</th>
<td class="sign">{{ this.randomSign.B }}</td>
<td>{{ initialValue.B }}</td>
<td v-show="this.randomSign.B == '+'">⬆</td>
<td v-show="this.randomSign.B == '-'">⬇</td>
</tr>
</table>
<button #click="toggleIntervalB()">
<span v-show="this.startStop.B">Stop</span>
<span v-show="!this.startStop.B">Start</span>
</button>
</div>
</template>
<script>
export default {
name: 'TableFields',
props: {
changesA: {
type: Array,
required: false
},
changesB: {
type: Array,
required: false
}
},
data () {
return {
fields: ['A', 'B'],
startStop: {
A: true,
B: true
},
initialValue: {
A: 3,
B: 3
},
randomNumbersArray: [],
randomSign: {
A: '+',
B: '+'
},
signsArray: ['+', '-'],
interval: {
A: null,
B: null
},
localChanges: {
A: [],
B: []
},
timer: undefined
}
},
computed: {},
methods: {
firstObjects () {
for (let i = 0; i < this.fields.length; i++) {
const date = new Date()
const obj = {}
obj.field = this.fields[i]
obj.value = Number((Math.random() * 1 + 1).toFixed(2))
obj.time = date.toLocaleTimeString()
this.changesA.push(obj[0])
this.changesB.push(obj[1])
this.$emit('update:changesA', this.localChanges.A)
this.$emit('update:changesB', this.localChanges.B)
}
},
replaceNumbersArray () { // replace random A, B numbers at time interval
const n1 = Number((Math.random() * 1 + 1).toFixed(2)) // n1 = first number (A)
const n2 = Number((Math.random() * 1 + 1).toFixed(2)) // n2 = second number (B)
this.randomNumbersArray.splice(0, 2, n1, n2)
},
toggleIntervalA () {
this.startStop.A = !this.startStop.A
if (this.startStop.A) {
this.interval.A = setInterval(this.calculationsA, 2000)
} else {
clearInterval(this.interval.A)
}
},
toggleIntervalB () {
this.startStop.B = !this.startStop.B
if (this.startStop.B) {
this.interval.B = setInterval(this.calculationsB, 2000)
} else {
clearInterval(this.interval.B)
}
},
calculationsA () {
this.randomSign.A = this.signsArray[
Math.floor(Math.random() * this.signsArray.length)
]
this.randomSign.A === '+'
? (this.initialValue.A += this.randomNumbersArray[0])
: (this.initialValue.A -= this.randomNumbersArray[0])
const date = new Date()
const newChange = {}
newChange.field = 'A'
newChange.value = this.randomNumbersArray[0]
newChange.time = date.toLocaleTimeString()
this.changesA.push(newChange)
this.$emit('update:changesA', this.localChanges.A)
},
calculationsB () {
this.randomSign.B = this.signsArray[
Math.floor(Math.random() * this.signsArray.length)
]
this.randomSign.B === '+'
? (this.initialValue.B += this.randomNumbersArray[1])
: (this.initialValue.B -= this.randomNumbersArray[1])
const date = new Date()
const newChange = {}
newChange.field = 'B'
newChange.value = this.randomNumbersArray[1]
newChange.time = date.toLocaleTimeString()
this.changesB.push(newChange)
this.$emit('update:changesB', this.localChanges.B)
}
},
mounted () {
this.firstObjects()
setInterval(this.replaceNumbersArray, 2000)
this.initialValue.A = this.$root.initialValue.A || 3
this.initialValue.B = this.$root.initialValue.B || 3
this.timer = setInterval(_ => {
this.interval.A = this.calculationsA
this.interval.B = this.calculationsB
}, 2000)
},
beforeDestroy () {
clearInterval(this.timer)
this.$root.initialValue.A = this.initialValue.A
this.$root.initialValue.B = this.initialValue.B
}
}
</script>
<style lang="scss" scoped>
.table-b {
margin-top: 15px;
}
.sign {
width: 12px;
text-align: center;
}
button {
border: 1px solid transparent;
border-radius: 0;
background-color: #42b983;
color: #ffffff;
margin-top: 7px;
padding: 5px 10px;
&:hover {
opacity: 0.9;
cursor: pointer;
}
}
</style>
Project repo: link
Try using the beforeDestroy() hook to push the counter to either the root component or to vuex store (if you are using one) and fetch the counter in the mounted() hook once you return to the route to resume from there.
and use in your component as
export default {
...
beforeDestroy () {
this.$root.counter = this.counter;
},
mounted () {
this.counter = this.$root.counter || 0;
}
...
}
I have created a sandbox at https://codesandbox.io/s/preserve-timer-state-2osi7 which preserves the state of timer when we move out of the route and starts the timer from where we left.
Comment Notes:
As per you comment, it seems that you are trying to set a property to an object which is undefined. Initially there is no property by the name initialValue in the root component and you are trying to access a property A inside it.
You need to first check if initialValue is defined and then try to check for A
this.initialValue.A = this.$root.initialValue && this.$root.initialValue.A ? this.$root.initialValue.A : 3
Also make sure your data has initialValue as an empty object
initialValue: {}

Vue - component props not watching object changes properly

I have two objects in my root - obj and newObj. I watch changes on my obj object with deep: true, and on changes I update newObj accordingly.
In my vue debugger, the newObj seems updated as expected, however the component doesn't perform the for loop count. Or if I try to {{ newObj }}, it logs only the first update.
I tried to re-create the issue on this Fiddle.
my html:
<div id="app">
<button #click="appendTo">Append</button>
<my-comp v-bind:new-obj="newObj"></my-comp>
</div>
and vue:
new Vue({
el: '#app',
data: {
obj: {},
newObj: {}
},
methods: {
appendTo() {
if (typeof this.obj[1] === 'undefined') {
this.$set(this.obj, 1, {})
}
var randLetter = String.fromCharCode(Math.floor(Math.random() * (122 - 97)) + 97);
this.$set(this.obj[1], randLetter, [ [] ])
}
},
watch: {
obj: {
handler(obj) {
var oldKeys = Object.keys(obj)
var newKeys = Object.keys(this.newObj);
var removedIndex = newKeys.filter(x => oldKeys.indexOf(x) < 0 );
for (var i = 0, len = removedIndex.length; i < len; i++) {
delete this.newObj[removedIndex[i]]
}
oldKeys.map((key) => {
if (this.newObj.hasOwnProperty(key)) {
var newInnerKeys = Object.keys(this.newObj[key]);
var oldInnerKeys = Object.keys(obj[key]);
var additions = oldInnerKeys.filter(x => newInnerKeys.indexOf(x) < 0);
for (var i = 0, len = additions.length; i < len; i++) {
// here
this.$set(this.newObj[key], additions[i], [ [] ]);
}
var deletions = newInnerKeys.filter(x => oldInnerKeys.indexOf(x) < 0);
for (var i = 0, len = deletions.length; i < len; i++) {
delete this.newObj[key][deletions[i]]
}
} else {
this.newObj[key] = {}
for (var innerKey in obj[key]) {
this.$set(this.newObj, key, {
[innerKey]: [ [] ]
});
}
}
console.log(obj);
console.log(this.newObj)
});
},
deep: true
}
}
})
Vue.component('my-comp', {
props: ['newObj'],
template: `
<div>
<div v-for="item in newObj">
test
</div>
</div>
`
})
Your data newObj has a getter and setter defined by vue. When the setter is called, the UI is re-rendered. The setter is triggered when you change the reference of newObj, not when you change its value. I mean:
this.newObj = {} // triggered
this.newObj['key'] = 'value' // not triggered
You can add a deep watcher on the property this.newObj. Or change its reference with a trick:
this.newObj = Object.assign({}, this.newObjec);
which create a copy of the object newObject.
Here is the fiddle updated.
https://jsfiddle.net/749nc5d2/

refreshing component on get method with vuejs

I Have a component which parse a json file on mounted phase.
The problem is :
when I click on a button , a send another GET method to get another json file and transmit it to my compoment.
the problem is that the component don't reload itself with the new props and my component display the old values
If someone know how to refresh components , here is my code
<template>
<div class="perturbo">
<div class="col-md-3 btnMenu">
<button v-for="item,index in perturboButtonData.button_list" type="button"
v-bind:style="'background:white'"
class="btn-lg btn-block myBtnClass"
#click="activeButton(index)">
{{item.label}}
</button>
</div>
<div class="col-md-9">
<div class="row">
<graphe
v-for="ana,index in perturboData.ANA"
:key="ana.id"
:data="ana"
:index="index"
type="number"
:timeSpec="perturboData.liste_dates">
</graphe>
</div>
<div class="row">
<graphe
v-for="dig,index in perturboData.DIG"
:key="dig.id"
:index="index"
:data="dig"
type="number"
isDigit="YES"
:timeSpec="perturboData.liste_dates">
</graphe>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import Graphe from './Graphe/Graphe.vue'
export default {
name: 'perturbo',
components : {
'graphe' : Graphe
},
data: function () {
return {
isActivated: false,
perturboData: {},
perturboButtonData: {}
}
},
methods: {
activeButton : function (index) {
console.log(this.perturboButtonData)
axios.get('./static/cgi/' + this.perturboButtonData.button_list[index].link)
.then((response) => {
this.perturboData = response.data;
this.isActivated = true
})
}
},
mounted : function () {
axios.get('./static/cgi/format_json_perturbo.json')
.then((response) => {
this.perturboButtonData = response.data;
})
}
}
</script>
Here is the code of my graphe component
<template>
<div class="graphe">
<vue-chart
:chart-events="chartEvents"
:columns="columns"
:rows="rows"
chart-type="LineChart"
:options="options">
</vue-chart>
</div>
</template>
<script>
export default {
name: 'graphe',
props: {
data: {},
timeSpec : Array,
index: Number,
type: String,
isDigit:String,
},
data: function () {
return {
chartEvents: {
'select': function() {
},
'ready': function() {
}
},
rows: [],
columns: [],
options: {
title: this.data.name,
hAxis: {
},
vAxis: {
ticks : []
},
width: 650,
height: 350,
curveType : 'function'
}
}
},
methods: {
normaliseData : function () {
for (let i = 0; i < this.timeSpec.length; i++) {
this.rows[i] = []
this.rows[i][0] = parseFloat(this.timeSpec[i])
}
this.columns[0] = { 'type': 'number', 'label': 'time' }
for (let i = 0; i < this.data.data.length; i++){
this.columns[i+1] = {'type': this.type ,'label': this.data.data[i].name}
}
for (let i = 0; i < this.timeSpec.length; i++) {
for (let y = 0; y < this.data.data.length; y++) {
this.rows[i][y+1] = parseFloat(this.data.data[y].data[i])
}
}
if (this.isDigit == "YES"){
this.digRow(this.rows)
for (let v = 0; v < this.data.data.length; v ++){
this.options.vAxis.ticks[v] = { v: v, f: this.data.data[v].name}
}
this.options.curveType = ''
}
},
digRow : function (rowTab) {
let newRow = []
let lengthMax = rowTab.length
let rowTmp = []
let index = 0
for (let i = 0; i < lengthMax; i ++){
rowTmp[index] = []
rowTmp[index][0] = rowTab[i][0]
for(let y = 1; y < rowTab[i].length; y ++){
rowTmp[index][y] = rowTab[i][y] + y - 1
}
if (i + 1 !== rowTab.length)
{
newRow = rowTmp[index].slice()
newRow[0] = rowTab[i+1][0]
rowTmp.splice(index+1,0,newRow)
index = index + 2
}
}
this.rows = rowTmp
}
},
mounted: function () {
// // pour les colones
this.normaliseData()
}
}
</script>
EDIT : I know where the problem is :
The data received from the parent is treated just once on the mounted function ! , that's why it doesn't reload on change
Should I use a watcher on props ? how can I do that
Instead of using a method to normalize the data, use a computed property for your rows, columns and options properties. This way, it will update automatically if any of the dependent properties change.
For example, your options property could be a computed property that looks like this:
computed: {
options() {
let options = {
title: this.data.name,
hAxis: {
},
vAxis: {
ticks : []
},
width: 650,
height: 350,
curveType : 'function'
};
if (this.isDigit == "YES"){
this.digRow(this.rows)
for (let v = 0; v < this.data.data.length; v ++){
options.vAxis.ticks[v] = { v: v, f: this.data.data[v].name}
}
options.curveType = ''
}
return options;
}
}
Now, whenever this.data, this.isDigit, or this.rows changes, the options property will update as well.
Your rows and columns properties would look like this:
rows() {
let rows = [];
for (let i = 0; i < this.timeSpec.length; i++) {
rows[i] = []
rows[i][0] = parseFloat(this.timeSpec[i])
for (let y = 0; y < this.data.data.length; y++) {
rows[i][y+1] = parseFloat(this.data.data[y].data[i])
}
}
return rows;
},
columns() {
let columns = [];
columns[0] = { 'type': 'number', 'label': 'time' }
for (let i = 0; i < this.data.data.length; i++) {
columns[i+1] = {'type': this.type ,'label': this.data.data[i].name}
}
return columns;
},
Your changed property won't force view update
To react to state changes, it’s usually better to use a computed
property or watcher.
Try this variant
watch: {
timeSpec(){
//do something
//this.normaliseData()
}
}

Storing checked checkboxes into array in angularjs tree structure

Currently i am able to display item that have parent child relation in tree structure using checkboxes. Now I need to store the checked checkboxes into one array so that I can submit that data to server via ajax.
I am new to angularjs. I tried printing using ng-model value. But it doesn't work.
Can you help me with how I can store the checked checkboxes into array.
Below is the code.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var app, list;
list = [
{
name: 'Developer',
opened: true,
children: [
{
name: 'Front-End',id:1,
children: [
{
name: 'Jack',id:2,
title: 'Leader'
},
{
name: 'John',id:3,
title: 'Senior F2E'
},
{
name: 'Jason',id:4,
title: 'Junior F2E'
}
]
},
{
name: 'Back-End',id:5,
children: [
{
name: 'Mary',id:6,
title: 'Leader'
},
{
name: 'Gary',id:7,
title: 'Intern'
}
]
}
]
},
{
name: 'Design',id:8,
children: [{
name: 'Freeman',id:9,
title: 'Designer'
}]
},
{
name: 'S&S',id:10,
children: [{
name: 'Nikky',id:11,
title: 'Robot'
}]
}
];
app = angular.module('testApp', []).controller('treeTable', [
'$scope',
'$filter',
function ($scope, $filter) {
$scope.list = list;
//$scope.item.selected={};
$scope.initCheckbox = function (item, parentItem) {
return item.selected = parentItem && parentItem.selected || item.selected || false;
};
$scope.toggleCheckbox = function (item, parentScope) {
if (item.children != null) {
$scope.$broadcast('changeChildren', item);
}
if (parentScope.item != null) {
return $scope.$emit('changeParent', parentScope);
}
};
$scope.$on('changeChildren', function (event, parentItem) {
var child, i, len, ref, results;
ref = parentItem.children;
results = [];
for (i = 0, len = ref.length; i < len; i++) {
child = ref[i];
child.selected = parentItem.selected;
if (child.children != null) {
results.push($scope.$broadcast('changeChildren', child));
} else {
results.push(void 0);
}
}
return results;
});
return $scope.$on('changeParent', function (event, parentScope) {
var children;
children = parentScope.item.children;
parentScope.item.selected = $filter('selected')(children).length === children.length;
parentScope = parentScope.$parent.$parent;
if (parentScope.item != null) {
return $scope.$broadcast('changeParent', parentScope);
}
});
}
]);
app.filter('selected', [
'$filter',
function ($filter) {
return function (files) {
return $filter('filter')(files, { selected: true });
};
}
]);
</script>
</head>
<body>
<div class="wrapper" ng-app="testApp" ng-controller="treeTable">
<table class="table-nested">
<tbody ng-class="{opened: item.opened}" ng-include="'table_tree.html'" ng-repeat="item in list"></tbody>
</table>
<script id="table_tree.html" type="text/ng-template">
<tr ng-class="{parent: item.children}" ng-init="parentScope = $parent.$parent; initCheckbox(item, parentScope.item)">
<td class="cell-name">
<div class="indent" ng-click="item.opened = !item.opened"></div>
<input ng-change="toggleCheckbox(item, parentScope)" ng-model="item.selected" type="checkbox" />
{{item.name}}
</td>
</tr>
<tr class="children" ng-if="item.children && item.children.length > 0">
<td colspan="4">
<table class="table-child">
<tbody ng-class="{opened: item.opened}" ng-include="'table_tree.html'" ng-init="level = level + 1" ng-repeat="item in item.children"></tbody>
</table>
</td>
</tr>
</script>
{{item.selected | json}}
</div>
</body>
check plunker here
Maybe you can do it like this:
JS:
$scope.seleceds = {};
$scope.initCheckbox = function (item, parentItem) {
return $scope.seleceds[item.id] = parentItem && $scope.seleceds[parentItem.id] || $scope.seleceds[item.id] || false;
};
$scope.toggleCheckbox = function (item, parentScope) {
if (item.children != null) {
$scope.$broadcast('changeChildren', item);
}
if (parentScope.item != null) {
return $scope.$emit('changeParent', parentScope);
}
};
$scope.$on('changeChildren', function (event, parentItem) {
var child, i, len, ref, results;
ref = parentItem.children;
results = [];
for (i = 0, len = ref.length; i < len; i++) {
child = ref[i];
$scope.seleceds[child.id] = $scope.seleceds[parentItem.id];
if (child.children != null) {
results.push($scope.$broadcast('changeChildren', child));
} else {
results.push(void 0);
}
}
return results;
});
return $scope.$on('changeParent', function (event, parentScope) {
var children;
children = parentScope.item.children;
$scope.seleceds[parentScope.item.id] = $filter('selected')(children, $scope.seleceds).length === children.length;
parentScope = parentScope.$parent.$parent;
if (parentScope.item != null) {
return $scope.$broadcast('changeParent', parentScope);
}
});
Extra filter:
app.filter('selected', ['$filter', function ($filter) {
return function (files, obj) {
return $filter('filter')(files, function (value) {
return obj[value.id]
});
};
}]);
app.filter('objToArray', function () {
return function (input) {
var results = [];
for (var key in input) {
input[key] && results.push(Number(key))
}
return results;
}
});
HTML:
<input ng-change="toggleCheckbox(item, parentScope)" ng-model="seleceds[item.id]" type="checkbox"/>
And change {{item.selected | json}} to {{seleceds|objToArray}}
you can see a demo HERE

Categories

Resources