Computed instead of v-if in v-for - vue.js

I have a page that displays the birthdays per month of the teams.
With two buttons that allow you to change the current month.
The solution with v-if works fine but since it is not a good practice I try with a computed property.
<tr v-for="birthday in birthdays" :key="birthday.name" v-if="birthday.month[month]">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="text-sm font-medium text-gray-900">
{{ birthday.name }}
Sample data of birthdays :
[
{
"month": {
"1": false,
"2": false,
"3": true,
"4": false,
"5": false,
"6": true,
"7": true,
"8": false,
"9": true,
"10": false,
"11": true,
"12": false
},
"name": "team 2"
},
{
"month": {
"1": false,
"2": false,
"3": true,
"4": false,
"5": false,
"6": true,
"7": true,
"8": false,
"9": true,
"10": false,
"11": true,
"12": false
},
"name": "team 1"
}
]
and my code with the computed property :
export default {
data() {
return {
birthdays: {},
month: false,
};
},
async asyncData({ $axios, store }) {
let email = store.state.auth.user.email;
let month = new Date().getMonth() + 1;
let { data } = await $axios.get("/birthday/?email=" + email);
return { birthdays: data, month: month };
},
methods: {
nextMonth() {
if (this.month === 12) {
this.month = 1;
} else this.month = this.month + 1;
},
previousMonth() {
if (this.month === 1) {
this.month = 12;
} else this.month = this.month - 1;
},
},
computed: {
fiestas: function () {
let birthdays = this.birthdays;
for (let team in birthdays) {
if (!birthdays[team].month[this.month]) {
birthdays.splice(team, 1);
}
}
return birthdays;
},
},
};
This works for the current month (with a few ms or we see the data before the computed) but when we change the month nothing works like birthdays has been modified.
Maybe in my case it is better to stay on a v-if?

splice modifies array in-place (instead of creating new array) so your fiestas computed is changing this.birthdays array...
Compute should never have a side-effects. Do this instead:
computed: {
teamsWithBirthdayInCurrentMonth: function () {
return this.birthdays
.filter(team => team.month[this.month])
.map(team => team.name)
},
},

Related

Search Builder extension of DataTables does not save values

I am using the SearchBuilder extension to filter data.
Because I am also using sever-side processing, I force a search only when the user presses the enter key, like example.
But when I click button ">" to add new criteria, the previous value is empty.
Any suggestion for me?
Here my code:
I use search:{true} to force search only search only user press enter
$table = $(`#${_options.table.id}`).DataTable({
dom: 'Blfrtip',
search: {
return: true
},
"processing": true, // for show progress bar
"serverSide": true,
"filter": true,
"pageLength": 2,
"pagingType": "full_numbers",
"ajax": {
"url": _options.table.urls.load,
"type": "POST",
"datatype": "json"
},
"ordering": true,
"order": [[2,'asc']],
//"order": [[0, 'asc'], [1, 'asc']]
"columns":[
{
"data": null,
"orderable": false,
"searchable": false,
"className": "text-center",
render: function (data, type, row, meta) {
let html = `<div class="icheck-primary input-group" style="display:block;">
<input type="checkbox" id="example1_${meta.row}">
<label for="example1_${meta.row}"></label>
</div>`;
return html;
},
searchBuilderType: 'html'
},
{
"data": "id",
"name": "Mã số",
"autoWidth": true,
"visible": false,
"searchable": false
},
{
"data": "title",
"name": "Tên danh mục",
"autoWidth": true,
searchBuilderType: 'string'
},
{
"data": "created",
"name": "Ngày tạo",
"autoWidth": true,
render: function (data, type, row, meta) {
return new Date(data).toLocaleDateString("vi-VN");
}
},
{
"data": "modified",
"name": "Ngày chỉnh sữa",
"autoWidth": true,
render: function (data, type, row, meta) {
return new Date(data).toLocaleDateString("vi-VN");
}
},
{
"data": "active",
"name": "Hoạt động",
"autoWidth": true,
"className": "text-center",
"width": "10%",
render: function (data, type, row, meta) {
let html = `<div class="icheck-primary input-group" style="display:block;">
<input disabled type="checkbox" ${data ? 'checked' : ''} id="example1_active_${meta.row}" name="terms">
<label for="example1_active_${meta.row}"></label>
</div>`;
return html;
}
},
{
"orderable": false,
"width": "15%",
"className": "text-center",
"data": null,
"render": function (data, type, row, meta) {
return `<a class="btn btn-info btn-sm" onclick="Edit('${row.id}', '${row.title}');" href="#">Edit</a> <a href="#" class="btn btn-danger btn-sm" onclick="Delete('${row.id}', '${row.title}');" >Delete</a>`;
}
}
],
"createdRow": function (row, data, dataIndex) {
let checkbox = $(row).children('td').first().find('input[type="checkbox"]');
let headerCheckbox = $($table.column().header()).find('input[type=checkbox]');
headerCheckbox.prop('checked', false);
checkbox.on('change', { rowIndex: dataIndex }, function (e) {
let checked = $(this).prop('checked');
let headerCheckbox = $($table.column().header()).find('input[type=checkbox]');
let tableId = $table.table().node().id;
let deletebutton = $(`button[aria-controls = ${tableId}][name=delete]`);
let rowIdx = e.data.rowIndex;
if (checked) {
$table.rows(rowIdx).select();
$count++;
if ($count == $table.rows().count()) {
headerCheckbox.prop('checked', true);
}
deletebutton.attr('disabled', false);
}
else {
$table.rows(rowIdx).deselect();
$count--;
if ($count == 0) {
deletebutton.attr('disabled', true);
}
headerCheckbox.prop('checked', false);
}
});
},
"initComplete": function (settings, json) {
// Add custom button into table wrapper
$table.buttons().container().appendTo(`#${_options.table.id}_wrapper .col-md-6:eq(0)`);
//Select all bycheckbox header
let headerCheckbox = $($table.column().header()).find('input[type=checkbox]');
headerCheckbox.on('change', { settings: settings }, function (e) {
let checked = $(this).prop('checked');
for (let i = 0; i < $table.rows().count(); i++) {
$($table.rows(i).column().nodes()[i]).find('input[type="checkbox"]').prop('checked', checked);
}
let deletebutton = $(`button[aria-controls = ${_options.table.id}][name=delete]`).attr('disabled', !checked);
(checked) ? $table.rows().select() : $table.rows().deselect();
});
},
"responsive": true,
"lengthChange": false,
"autoWidth": false,
language: {
searchBuilder: {
button: {
0: 'Criteria',
1: 'Criteria (one selected)',
_: 'Criteria (%d)'
}
}
},
"buttons": [
{
text: `<i class="fas fa-plus"> New</i>`,
className: 'btn btn-primary form-group',
attr: {
'name': 'new',
'style': 'background: #007bff !important; border-color: #007bff !important'
},
action: function (e, dt, node, config) {
ajaxFormAction(_options.modal.selector, _options.modal.id, 'Create new category', '/Admin/Category/Create', true, function (e) {
$table.draw();
});
}
},
{
text: `<i class="fas fa-trash"> Delete</i>`,
className: 'btn btn-danger form-group',
attr: {
'name': 'delete',
"disabled": true
},
action: function (e, dt, node, config) {
let ids = [];
let rows = $table.rows({ selected: true }).every(function (rowIdx, tableLoop, rowLoop) {
ids.push(this.data().id);
});
ids = ids.join(',');
ajaxDelete(ids, null, _options.table.urls.delete, function (e) {
$table.draw();
});
}
},
{
text: 'Filter',
className: 'btn btn-default form-group',
extend: 'searchBuilder'
}
]
})
$('<input id="example1_column" type="text" class="form-control form-control-sm" placeholder="aaaa" />').insertBefore($("#example1_filter input"));
return $table

v-on not working with v-for which is created dynamically

I am looping a bit complex data using v-for. I have a text box to add new data set to it. Each cell in the table toggles to edit mode on double clicking it. It works fine when there are no new entries added. But when I add a new entry using 'Add a category' textbox, the double click is not working properly. Here is jsfiddle. Can someone help on this?
https://jsfiddle.net/9b7ckjrt/1/
<div id="app">
<greeting></greeting>
</div>
<template id="example">
<div class="hello">
<div>
<table border="1">
<thead>
<tr>
<td></td>
<td></td>
<td v-for="month in 3" :key="month">{{ month }} / {{ selectedYear }}</td>
</tr>
</thead>
<tbody v-for="(subCategories, category) in expectedMonthlyBudget" :key="category">
<tr v-for="(yearExpenses, subCategory, idx) in subCategories" :key="subCategory">
<td :rowspan="Object.keys(subCategories).length + 1" v-if="idx == 0">
{{ category }}
</td>
<td>
{{ subCategory }}
</td>
<td v-for="month in 3" :key="month" #dblclick="toggleEditingMoney(yearExpenses[selectedYear][month])">
<div v-if="!yearExpenses[selectedYear][month].editing">
{{ yearExpenses[selectedYear][month].value }}
</div>
<div v-if="yearExpenses[selectedYear][month].editing">
<input #keyup.esc="cancelEditingMoney(yearExpenses[selectedYear][month])" #keyup.enter="doneEditingMoney(yearExpenses[selectedYear][month])" #blur="doneEditingMoney(yearExpenses[selectedYear][month])" v-focus type="text" v-model="yearExpenses[selectedYear][month].value" />
</div>
</td>
</tr>
<tr>
<td>
<input type="text" placeholder="Add a category..." #keyup.enter="addSubCategory(category)" #keyup.esc="cancelAddingSubCategory(category)" v-model="newCategory[category]" />
</td>
<td v-for="month in 3" :key="month"></td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
Vue.component('greeting', {
template: '#example',
directives: {
focus: {
// directive definition
inserted: function (el) {
el.focus()
}
}
},
methods: {
toggleEditingMoney: function(element) {
this.beforeEditingCache = element.value;
element.editing = true;
},
doneEditingMoney: function(element) {
if(element.value.toString().trim() == '') { element.value = 0; }
if(isNaN(element.value)) { element.error = true; }
else { element.error = false; }
element.editing = false;
},
cancelEditingMoney: function(element) {
element.value = this.beforeEditingCache;
element.editing = false;
},
cancelAddingSubCategory: function(category) {
this.newCategory[category] = '';
},
addSubCategory: function(category) {
for (var i = 3; i > 0; i--) {
this.expectedMonthlyBudget[category][this.newCategory[category]] = {
"2020": {
"1": {
"value": 0
},
"2": {
"value": 0
},
"3": {
"value": 0
}
}
};
}
this.newCategory[category] = '';
},
},
data: function() {
return {
selectedYear: 2020,
cachedMoney: 0,
newCategory: {
"Income": '',
"Expense": '',
"EMI": '',
"EquityInvestment": '',
"DebtInvestment": ''
},
beforeEditingCache: 0,
expectedMonthlyBudget: {
"Income": {
"Salary": {
"2020": {
"1": {
"value": 1231312,
"editing": false
},
"2": {
"value": 324,
"editing": false
},
"3": {
"value": 324324,
"editing": false
}
}
},
"Illegal": {
"2020": {
"1": {
"value": 1231312,
"editing": false
},
"2": {
"value": 324,
"editing": false
},
"3": {
"value": 324324,
"editing": false
}
}
}
},
"Expense": {
"Rent": {
"2020": {
"1": {
"value": 1231312,
"editing": false
},
"2": {
"value": 324,
"editing": false
},
"3": {
"value": 324324,
"editing": false
}
}
},
"Car": {
"2020": {
"1": {
"value": 1231312,
"editing": false
},
"2": {
"value": 324,
"editing": false
},
"3": {
"value": 324324,
"editing": false
}
}
},
"Bike": {
"2020": {
"1": {
"value": 1231312,
"editing": false
},
"2": {
"value": 324,
"editing": false
},
"3": {
"value": 324324,
"editing": false
}
}
}
}
}
}
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
When adding properties to an object you run into what Vue's docs call Change Detection Caveats
In relation to your code what this means is that this part
addSubCategory: function(category) {
for (var i = 3; i > 0; i--) {
this.expectedMonthlyBudget[category][this.newCategory[category]] = {
won't add the new object to Vue's change detection flow.
You have to use Vue.set (or its alias, this.$set):
addSubCategory: function(category) {
for (var i = 3; i > 0; i--) {
this.$set(this.expectedMonthlyBudget[category], this.newCategory[category], {
"2020": {
"1": {
"value": 0,
"editing": false
},
"2": {
"value": 0,
"editing": false
},
"3": {
"value": 0,
"editing": false
}
}
});
}
this.newCategory[category] = '';
},
Note that I've also initialized the "editing" property (instead of creating it inside toggleEditingMoney method via element.editing = true;), as this is also necessary so Vue is aware of its presence.

Vue - Clear true false toggles from out side a loop

I am relatively new to Vus.js and vanilla js, Im looking for some help. I have a loop where filters are toggled between false and true depending on the criteria. I have a button outside of all the vue loops that I would like to use to clear all the toggles - set all the data back to false.
I am using axios and a json file for my data. Theere are three filess I am working with filters.json, filters.js and the html
Thanks in advance for you help
json
[{
"name": "Category",
"filterVisible": false,
"buttonActive": false,
"values": [{
"name": "Single Cask",
"selected": false
},
{
"name": "Regional Malt",
"selected": false
},
{
"name": "New releases",
"selected": false
}
]
},
{
"name": "Region",
"filterVisible": false,
"buttonActive": false,
"values": [{
"name": "Highland",
"selected": false
},
{
"name": "Speyside",
"selected": false
},
{
"name": "Islay",
"selected": false
}
]
},
{
"name": "Brand",
"filterVisible": false,
"buttonActive": false,
"values": [{
"name": "Regional Malts",
"selected": false
},
{
"name": "Single Casks",
"selected": false
},
{
"name": "The Big Yin",
"selected": false
},
{
"name": "The Wee Yin",
"selected": false
}
]
},
{
"name": "Price",
"filterVisible": false,
"buttonActive": false,
"values": [{
"name": "£1-50",
"selected": false
},
{
"name": "£51-100",
"selected": false
}
]
}
]
fliters.js
var vm = new Vue({
el: '#app',
data: {
shopFilters: []
},
created() {
axios
.get(`shopFilters.json`)
.then(response => {
// JSON responses are automatically parsed.
this.shopFilters = response.data;
})
},
computed: {
}, // end computed
methods: {
// my attempt at clearing filter
clearAll: function(filter) {
shopFilters.filterVisible = false
}
}
});
html
<div class="shop__filter">
<div class="shop__filter-header">
<h6 class="shop__filter-heading"><img src="img/filter-symbol.png" />Filter</h6>
<p class="shop__filter-showing">showing 1-8 of 120</p>
**this is the bit I want to call the Function on**
<button #click="clearAll()" class="btn btn-white btn-mobile">clear filters</button>
</div>
<form>
<dl class="shop__filter-list">
<template v-for="filter in shopFilters">
<dt class="shop__filter-dt">
<button class="shop__btn-type"
v-on:click="filter.buttonActive = !filter.buttonActive"
:class="{'active' : filter.buttonActive}"
#click="toggle(filter)"
#click.self.prevent>
{{ filter.name }}
</button>
</dt>
<dd class="shop__filter-dd"
v-show="filter.filterVisible || option.selected"
:id="filter.name"
v-for="option in filter.values">
<button class="shop__btn-filter"
#click.self.prevent
v-on:click="option.selected = !option.selected"
:class="{'active' : option.selected}">
{{ option.name }}
</button>
</dd>
</template>
</dl>
</form>
</div>
I got a fix for this
clearAll: function() {
for (var i = 0; i < this.shopFilters.length; i++) {
var filter = this.shopFilters[i];
filter.buttonActive = false;
filter.filterVisible = false;
for (var j = 0; j < filter.values.length; j++) {
var option = filter.values[j];
option.selected = false;
}
}
}, // end clearAll

Filter on nested (recursive) data ( Vue 2 )

Here is an example of my JSON data :
"data":[
{
"id":01,
"name":"test",
"parent_id":null,
"children":[
{
"id":15,
"name":"subChild",
"parent_id":21,
"children":[
{
"id":148,
"name":"subSubChild",
"parent_id":22,
"children":[
....
]
}
]
}
]
},
I would like to filter this level by level. I have made this method :
computed: {
filteredData: function () {
let filterData = this.filter.toLowerCase()
return _.pickBy(this.data, (value, key) => {
return _.startsWith(value.name.toLowerCase(), filterData)
})
},
This work for only the first "level" and I tried several solutions but none worked for children.
So, I would like to be able to filter by several levels.
If you have an idea!
Thank you
A recursive function could come in handy for this particular purpose.
Try the following approach, and for better view, click on Full page link next to the Run code snippet button down below.
new Vue({
el: '#app',
data() {
return {
filter: '',
maintainStructure: false,
data: [{
"id": 01,
"name": "test",
"parent_id": null,
"children": [{
"id": 15,
"name": "subChild",
"parent_id": 21,
"children": [
{
"id": 148,
"name": "subSubChild",
"parent_id": 22,
"children": []
},
{
"id": 150,
"name": "subSubChild3",
"parent_id": 24,
"children": []
}
]
}]
}]
}
},
methods: {
$_find(items, predicate) {
let matches = [];
for (let item of items) {
if (predicate(item)) {
matches.push(item);
}
else if (item.children.length) {
let subMatches = this.$_find(item.children, predicate);
if (subMatches.length) {
if (this.maintainStructure) {
matches.push({
...item,
children: subMatches
});
}
else {
matches.push(subMatches);
}
}
}
}
return matches;
},
filterBy(item) {
return item.name.toLowerCase().startsWith(this.filter.toLowerCase());
}
},
computed: {
filteredData() {
return this.$_find(this.data, this.filterBy);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<label>Filter by <code>item.name</code>:</label>
<input v-model.trim="filter" placeholder="e.g. subsub" />
</div>
<div>
<label>
<input type="checkbox" v-model="maintainStructure" /> Maintain structure
</label>
</div>
<hr />
<pre>{{filteredData}}</pre>
</div>
Note that I'm prefixing the function with $_ to sort of mark it as private function (as recommended in this Style Guide) since we're not going to invoke it anywhere else.

How to get checkbox value from Datatable?

I want to get the selected checkbox value from the data table, in my table, I have two columns and the first one is for the checkbox and the second one is for display values.
Here is just returning a checkbox. How can we know that there is a click that happens?
Help me.
Here is the code
function BindColumSelectTable(DataExchangeList) {
debugger
$('#columnSelectTable').DataTable({
"data": DataExchangeList,
"destroy": true,
"columns": [
{
data: 'check', render: function (data, type, row) {
debugger;
return '<input type="checkbox"/>'
}
},
{ data:"FieldCaption" },
],
"columnDefs": [
{
orderable: false,
className: "select-checkbox",
targets:0
},
{ className:"tabletdAdjust","targets":[1]}
],
});}
I'm using jquery data table
function BindColumSelectTable(DataExchangeList) {
$('#columnSelectTable').DataTable({
"data": DataExchangeList,
"destroy": true,
"columns": [
{
data: 'check', render: function (data, type, row) {
var checkbox = $("<input/>",{
"type": "checkbox"
});
if(data === "1"){
checkbox.attr("checked", "checked");
checkbox.addClass("checked");
}else{
checkbox.addClass("unchecked");
}
return checkbox.prop("outerHTML")
}
},
{ data:"FieldCaption" },
],
"columnDefs": [
{
orderable: false,
className: "select-checkbox",
targets:0
},
{ className:"tabletdAdjust","targets":[1]}
],
});}
Try this
Here is the answer I use onclick function for each click it will trigger the function
function BindColumSelectTable(DataExchangeList) {
debugger
$('#columnSelectTable').DataTable({
"data": DataExchangeList,
"destroy": true,
"columns": [
{
data: 'ColumnCheck', render: function (data, type, row) {
debugger;
return '<input type="checkbox" onclick="ColumnCheck(this)"/>'
}
},
{ data:"FieldCaption" },
],
"columnDefs": [
{
orderable: false,
className: "select-checkbox",
targets:0
},
{ className:"tabletdAdjust","targets":[1]}
],
});
}
the above code is the same i used in the question only one thing i added is an onclick function
and the onclick function is
function ColumnCheck(thisObj) {
debugger;
var dataExchangeCheckColumnVM = $('#columnSelectTable').DataTable().row($(thisObj).parents('tr')).data();
var dataExchangeCheckColumnList = $('#columnSelectTable').DataTable().rows().data();
for (var i = 0; i < dataExchangeCheckColumnList.length; i++) {
if (dataExchangeCheckColumnList[i].FieldCaption !== null) {
if (dataExchangeCheckColumnList[i].FieldCaption === dataExchangeCheckColumnVM.FieldCaption) {
dataExchangeCheckColumnList[i].ColumnCheck = thisObj.checked;
}
}
}
_dataExchangeColumnList = dataExchangeCheckColumnList;
}
so i used an property **ColumnCheck ** it is boolean variable. on each iteration it will added a true value if check box is checked