I'm trying to make the code below work knowing that computed properties can't take parameters. Do you have any idea ? I'm exploring the use of watchers on functions but I was wondering if there was not an easier solution to do this.
var app = new Vue({
el: '#app',
data() {
return {
sessions: {
"156": {
tickets: {
"01": {
available: true,
},
"02": {
available: false,
},
}
},
},
tickets: {
"01": {
attr: "somestring",
},
"02": {
attr: "someotherstring",
},
},
},
};
},
computed: {
sessionTickets(session) {
let _this = this;
let sessionTickets = {};
$.each(_this.session.tickets, function(ticketId, sessionTicket) {
if(sessionTicket.available) {
sessionTickets[ticketId] = _this.tickets[ticketId];
}
});
return sessionTickets;
},
},
});
<div v-for="session in sessions">
<div v-for="sessionTicket in sessionTickets(session)">
{{ sessionTicket.attr }}
</div>
</div>
Thanks to "WallOp" for making me realize that my computed property is in a sessions loop and so it can normally become a class method and be refreshed on sessions refresh !
I think you can use computed property. Just filter tickets of sessions. Like this:
var app = new Vue({
el: '#app',
data() {
return {
sessions: {
"156": {
tickets: {
"01": {
available: true,
},
"02": {
available: false,
},
}
},
},
tickets: {
"01": {
attr: "somestring",
},
"02": {
attr: "someotherstring",
},
},
},
};
computed: {
filteredSessions() {
return this.sessions.map( session => {
let tickets = {};
for(key in session.tickets) {
if(session.tickets[key].available && this.tickets.hasOwnProperty(key)) {
tickets[key] = this.tickets[key];
}
}
session.tickets = tickets;
return session;
});
},
},
});
<div v-for="session in filteredSessions">
<div v-for="ticket in session.tickets">
{{ ticket.attr }}
</div>
</div>
Related
I have this component that shows a whole bunch of different components. Like so:
computed: {
...mapGetters('forms', ['formErrors']),
input: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
component() {
const components = {
'ls-text-field': () =>
import('../../../../common/ls-text-field.vue'),
'simple-date-picker': () =>
import('../../../../common/simple-date-picker.vue'),
select: 'v-select',
combobox: 'v-combobox',
};
return components[this.setting.component];
},
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
},
};
return {
...attrs[this.setting.component],
'error-messages': this.formErrors(this.setting.key),
};
},
},
and the Template looks something like this:
<template>
<v-col md="4" cols="12">
<component
:is="component"
v-bind="attributes"
v-model="input"
:search-input.sync="searchSku"
/>
But you'll notice I had to do v-model in the template and not in the computed property. I suppose there is NO way to do this:
attributes() {
const attrs = {
'ls-text-field': {
label: this.setting.name,
},
'simple-date-picker': {},
select: {
label: 'Select this foo',
items: this.setting.options,
},
combobox: {
'return-object': true,
items: this.productList,
loading: this.loading_product_list,
'item-value': 'sku',
'item-text': 'sku',
label: this.setting.name,
name: this.setting.key,
'v-model': this.item.info.someKey // This doesn't seem possible
},
};
I am using the vue wrapper for jexcel and am attempting to trigger the undo function from the toolbar computed field. I cannot seem to access the instance of the spreadsheet. it throws a this.undo is undefined error
<template lang="html">
<div class="wrapper-jexcel">
<button class="" #click="getData(jExcelObj)">Data</button>
<button class="" #click="jExcelObj.undo()">Undo</button>
<input
type="button"
value="Add new row"
#click="jExcelObj.insertRow()"
/>
<div id="spreadsheet" ref="spreadsheet"></div>
</div>
</template>
import jexcelStyle from "jexcel/dist/jexcel.css"; // eslint-disable-line no-unused-vars
import jexcel from "jexcel"; // eslint-disable-line no-unused-vars
import db from '#/firebase/init'
import firebase from 'firebase'
export default {
name: "workbook",
data() {
return {
workbookid: this.$route.params.workbookid,
myCars: [],
columns: [
{ type: "text", title: "Car", width: "120px" },
{
type: "dropdown",
title: "Make",
width: "250px",
source: ["Alfa Romeo", "Audi", "BMW", "Honda", "Porshe"]
},
{ type: "calendar", title: "Available", width: "250px" },
{ type: "image", title: "Photo", width: "120px" },
{ type: "checkbox", title: "Stock", width: "80px" },
{
type: "numeric",
title: "Price",
width: "120px",
mask: "$ #.##,00",
decimal: ","
},
{ type: "color", width: "100px", render: "square" }
]
};
},created() {
this.getworkbook()
},
methods: {
onchange(){
console.log('change');
},
insertRowc() {
console.log(this);
// this.spreadsheet.insertRow();
},
undo(){
console.log('test');
jExcelObj.undo();
},
getData(payload) {
console.log(this.myCars);
console.log(payload);
// this.myCars = payload.data
}
},
computed: {
jExcelOptions() {
var self = this;
return {
data: this.myCars,
columns: this.columns,
search: true,
//fullscreen: true,
minDimensions: [20, 40],
defaultColWidth: 100,
allowComments: true,
toolbar: [
{ type:'i', content:'undo', onclick:function() { return jExcelObj.undo(); } },
{ type:'i', content:'redo', onclick:function() { this.redo(); } },
{ type:'i', content:'save', onclick:function () { test.download(); } },
{ type:'select', k:'font-family', v:['Arial','Verdana'] },
{ type:'select', k:'font-size', v:['9px','10px','11px','12px','13px','14px','15px','16px','17px','18px','19px','20px'] },
{ type:'i', content:'format_align_left', k:'text-align', v:'left' },
{ type:'i', content:'format_align_center', k:'text-align', v:'center' },
{ type:'i', content:'format_align_right', k:'text-align', v:'right' },
{ type:'i', content:'format_bold', k:'font-weight', v:'bold' },
{ type:'color', content:'format_color_text', k:'color' },
{ type:'color', content:'format_color_fill', k:'background-color' },
]
};
}
},
mounted: function() {
//console.log(this.jExcelOptions);
//console.log(this.$refs["spreadsheet"]);
const jExcelObj = jexcel(this.$refs["spreadsheet"], this.jExcelOptions);
// Object.assign(this, jExcelObj); // pollutes component instance
Object.assign(this, { jExcelObj }); // tucks all methods under jExcelObj object in component instance
// console.log(this.jExcelObj);
}
};
should i be passing the instance into the computed method? I am struggling to understand how to manage instances of a wrapper plugin and accessing the methods.
You can get the instance using that:
var yourTableInstance = document.getElementById('spreadsheet').jexcel;
yourTableInstance.undo();
yourTableInstance.getData();
mounted: function() {
let spreadsheet = jspreadsheet(this.$el, options);
Object.assign(this, spreadsheet);
}
If I have code like the code below in my vue.js, upon clicking a button, how can I only display the array item I clicked ( for e.g, Donnie) and hide all of the rest? (Joanne, Peter e.t.c), then when you click the only displayed element again, make all of the other array elements display again?
const app = new Vue({
el: '#app',
data: {
keyword: '',
friends: [
{
name: "Donnie",
age: "20"
},
{
name: "Joanne",
age:"19",
},
{
name: "David",
age: "26"
},
{
name: "Peter",
age: "23"
},
{
name: "John",
age: "29"
},
{
name: "Jason",
age: "19"
},
],
},
computed: {
filteredList() {
return this.friends.filter((friend) => {
return friend.name.toLowerCase().includes(this.keyword) + friend.age.includes(this.keyword) + friend.name.includes(this.keyword);
});
}
},
methods:{
exclude(friend) {
console.log(!friend.name);
},
}
})
HTML
<div v-for="friend in filteredList" class="card" #click="exclude(friend)">
{{friend.name}} - {{friend.age}}
</div>
You should be able to add an identity check to your filter expression if an item has been clicked.
Start by adding a property to store the clicked friend. I'll call mine selected
data {
selected: null,
keyword: '',
//etc
}
Then in your exclude method
exclude (friend) {
this.selected = this.selected ? null : friend
}
now your computed property can filter the list based on the selected friend first, then fall back to the keyword match
filteredList () {
return this.selected ? [this.selected] : this.friends.filter(friend => {
let search = this.keyword.trim().toLowerCase()
return friend.name.toLowerCase().includes(search) || friend.age.includes(search)
})
}
I think that's what you're looking for:
const app = new Vue({
el: '#app',
data: {
keyword: '',
friends: [
{
name: "Donnie",
age: "20"
},
{
name: "Joanne",
age:"19",
},
{
name: "David",
age: "26"
},
{
name: "Peter",
age: "23"
},
{
name: "John",
age: "29"
},
{
name: "Jason",
age: "19"
},
],
selected: null
},
computed: {
filteredList() {
if (!this.selected) {
return this.friends.filter((friend) => {
return friend.name.toLowerCase().includes(this.keyword) + friend.age.includes(this.keyword) + friend.name.includes(this.keyword);
});
} else {
return [this.selected];
}
},
},
methods:{
exclude(friend) {
if(!this.selected) {
this.selected = friend;
} else {
this.selected = null;
}
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="friend in filteredList" class="card" #click="exclude(friend)">
{{friend.name}} - {{friend.age}}
</div>
</div>
The trick here is that the selected data property store the friend and also doubles as a checker if there's a friend, so if not, show all, if is, show only that one.
I have this jSON structure.
{
"customer": {
"idcustomer": 2,
"name": "test_2",
"vat": "test_vat_2",
"obs": "obs_2",
"deleted": 0
},
"addresses": [
{
"idaddress": 9,
"street": "street_2_9",
"number": "number_2_9",
"country": "country_2_9",
"default": true,
"label": "labe_2_9",
"deleted": 0
},
{
"idaddress": 10,
"street": "1",
"number": "number_2_9",
"country": "country_2_10",
"default": false,
"label": "label_2_10",
"deleted": 0
}
],
"contacts": []
}
With knockout mapping plugin I am able to generate a knockout observable object. However, when trying to add extra properties to the object using the mapping parameter I find some issues. The goal is to add "SelectedAddress" to the main object and in each address a "defaultLabel" observabale.
Currently i have this mapping structure to add the property to the address children:
var mapping = {
'addresses': {
create: function (options) {
return (new (function () {
this.defaultLabel= ko.computed(function () {
return (this.default() == 0) ? "" : this.label();
}, this);
ko.mapping.fromJS(options.data, {}, this);
})());
}
},
}
and this to add the "SelectedAddress" to the main JSON:
create: function (options) {
return new function () {
var model = ko.mapping.fromJS(options.data, {}, this);
// Direccion
model.direccionSeleccionada = ko.observable();
model.getDireccion = ko.computed({
read: function() {
if (model.direccionSeleccionada() != null) {
return model.direccionSeleccionada();
} else {
return [{
idaddress: -1,
street : '',
number: '',
country: '',
default: '',
label: '',
deleted: '',
}];
}
},
write: function(value) {
self.direccionSeleccionada(value);
},
owner: self
});
}
}
I can not find a way to have them both
Ideas?
Thank you
I figured it out. Just for someone; Just as simple as when generatint he mapping of "addresses", add inside another mapping for it.
var mapping = {
create: function (options) {
return new function () {
var model = ko.mapping.fromJS(options.data, {
'adresses': {
create: function (options) {
return (new(function () {
this.labelDefault= ko.computed(function () {
return (this.default() == 0) ? "" : this.label();
}, this);
ko.mapping.fromJS(options.data, {}, this);
})( /* call the ctor here */ ));
}
},
}, this);
model.direccionSeleccionada = ko.observable();
model.getDireccion = ko.computed({
read: function () {
if (model.selectedAddress() != null) {
return model.selectedAddress();
} else {
return [{
idaddress: -1,
street: '',
number: '',
country: '',
default: '',
label: '',
deleted: ''
}];
}
},
write: function (value) {
self.selectedAddress(value);
},
owner: self
});
}
}
}
Thank you!
I just started out working with Vue and I'm trying to visualise a nested list.
The list-items should contain triple-state checkboxes:
When a child item is checked, the parent item's checkbox should become 'indeterminate'. When all child-checkboxes are checked, the parent checkbox should also become checked.
When a parent item checkbox is checked, all child item checkboxes (also the ones nested deeper) should be selected too.
I kind of have a working solution (check out this pen or the code below) but the checkbox-logic is still flawed. For this example, checked boxes are green, indeterminate ones are orange and unchecked ones are red.
I've run out of ideas how to fix it. Could someone shed some light on how to accomplish this in Vue?
'use strict';
Vue.component("book-chapter", Vue.extend({
name: "book-chapter",
props: ["data", "current-depth"],
data: function() {
return {
checked: this.data.checked,
indeterminate: this.data.indeterminate || false
};
},
methods: {
isChecked: function() {
return this.checked && !this.indeterminate;
},
isIndeterminate: function(){
return this.indeterminate;
},
toggleCheckbox: function(eventData) {
if (this.currentDepth > 0){
if (!this.data.children) {
this.checked != this.children
} else {
this.indeterminate = !this.indeterminate;
}
}
if (eventData) {
// fired by nested chapter
this.$emit('checked', eventData);
} else {
// fired by top level chapter
this.checked = !this.checked;
this.$emit('checked', {
data: this.data
});
}
},
isRootObject: function() {
return this.currentDepth === 0;
},
isChild: function() {
return this.currentDepth === 2;
},
isGrandChild: function() {
return this.currentDepth > 2;
}
},
template: `
<div class='book__chapters'>
<div
class='book__chapter'
v-bind:class="{ 'book__chapter--sub': isChild(), 'book__chapter--subsub': isGrandChild() }"
v-show='!isRootObject()'>
<div class='book__chapter__color'></div>
<div
class='book__chapter__content'
v-bind:class="{ 'book__chapter__content--sub': isChild(), 'book__chapter__content--subsub': isGrandChild() }">
<div class='book__chapter__title'>
<span class='book__chapter__title__text'>{{data.title}}</span>
</div>
<div class='book__chapter__checkbox triple-checkbox'>
<div class='indeterminatecheckbox'>
<div
class='icon'
#click.stop="toggleCheckbox()"
v-bind:class="{'icon--checkbox-checked': isChecked(), 'icon--checkbox-unchecked': !isChecked(), 'icon--checkbox-indeterminate': isIndeterminate()}">
</div>
</div>
</div>
</div>
</div>
<book-chapter
ref='chapter'
:current-depth='currentDepth + 1'
v-for='child in data.children'
key='child.id'
#checked='toggleCheckbox(arguments[0])'
:data='child'>
</book-chapter>
</div>
`
}));
Vue.component("book", Vue.extend({
name: "book",
props: ["data"],
template: `
<div class='book'>
<book-chapter
:data='this.data'
:currentDepth='0'>
</book-chapter>
</div>
`
}));
var parent = new Vue({
el: "#container",
data: function() {
return {
book: {}
};
},
mounted: function() {
this.book = {
"title": "Book",
"children": [{
"title": "1 First title",
"children": [{
"title": "1.1 Subtitle"
}, {
"title": "1.2 Subtitle"
}]
}, {
"title": "2 Second title",
"children": [{
"title": "2.1 Subtitle",
"children": [{
"title": "2.1.1 Sub-Sub title"
}, {
"title": "2.1.2 Another sub-sub title"
}]
}]
}]
}
}
});
Update: fixed a bug found by #PhillSlevin. See pen here
Check this pen, is it what you want to achieve?
I think you can use either eventbus or vuex to solve this problem,
if you treated every 's section as a component.
'use strict';
var bus = new Vue();
var book = {
"title": "Book",
"children": [{
"title": "1 First title",
"children": [{
"title": "1.1 Subtitle"
}, {
"title": "1.2 Subtitle"
}]
}, {
"title": "2 Second title",
"children": [{
"title": "2.1 Subtitle",
"children": [{
"title": "2.1.1 Sub-Sub title"
}, {
"title": "2.1.2 Another sub-sub title"
}]
}]
}]
};
Vue.component('book', {
template: `
<div class="book__chapter">
<p :class="'book__title ' + status" #click="clickEvent">{{title}} {{parent}}</p>
<book v-for="child in children" :key="child" :info="child"></book>
</div>
`,
props: ['info'],
data() {
return {
parent: this.info.parent,
title: this.info.title,
children: [],
status: this.info.status,
};
},
created() {
const info = this.info;
if(info.children) {
info.children.forEach(child => {
child.status = "unchecked";
// use title as ID
child.parent = info.title;
});
this.children = info.children;
}
},
mounted() {
const vm = this;
bus.$on('upside', (payload) => {
const targetArr = vm.children.filter((child) => child.title === payload.from);
if (targetArr.length === 1) {
const target = targetArr[0];
target.status = payload.status;
if (vm.children.every(ele => ele.status === 'checked')) {
vm.status = 'checked';
} else if (vm.children.every(ele => ele.status === 'unchecked')) {
vm.status = 'unchecked';
} else {
vm.status = 'indeterminate';
}
bus.$emit('upside', {
from: vm.title,
status: vm.status,
});
}
});
bus.$on('downside', (payload) => {
if (payload.from === this.parent) {
if (payload.status === 'checked') {
vm.status = 'checked';
vm.children.forEach(child => child.status = 'checked');
} else if (payload.status === 'unchecked') {
vm.status = 'unchecked';
vm.children.forEach(child => child.status = 'unchecked')
}
bus.$emit('downside', {
from: vm.title,
status: vm.status,
})
}
});
},
methods: {
clickEvent() {
if (this.status === 'checked') {
this.status = 'unchecked';
this.children.forEach(child => child.status = 'unchecked');
} else {
this.status = 'checked';
this.children.forEach(child => child.status = 'checked');
}
const vm = this;
bus.$emit('upside', {
from: vm.title,
status: vm.status,
});
bus.$emit('downside', {
from: vm.title,
status: vm.status,
});
},
}
});
var parent = new Vue({
el: "#container",
data: function() {
return {
book
};
},
});
.book__title.unchecked::after {
content: '□';
}
.book__title.indeterminate::after {
content: '△';
}
.book__title.checked::after {
content: '■';
}
.book__chapter {
display: block;
position: reletive;
margin-left: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.js"></script>
<div id="container">
<book :info="book" :parent="'container'"></book>
</div>