Vue js 2 hide shared component - vue.js

<template>
<div id="app" class="phone-viewport">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<router-view></router-view>
<bottomBar v-bind:visibles='show' ></bottomBar>
</div>
</template>
<script>
export default {
name: '',
show: '',
data () {
return {
visibles: [
{name: 'Football', show: true},
{name: 'Basketball', show: true},
{name: 'Hockey', show: true},
{name: 'VolleyBall', show: false},
{name: 'Baseball', show: false},
]
}
}
}
</script>
I'm looking to hide the bottomBar just on VolleyBall and Beisbol .
But I always have this error "Property or method "show" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
"
<script>
export default {
name: 'app',
data () {
return {}
},
computed: {
hideBottom: function () {
if (this.$router.path === '/baseball' || this.$router.path === '/volleyball') return false
else { return true }
}
}
}

Baseball
You are calling method show which does not exist, that's why you are getting that error.
As I understand your question, you want to hide that component on particular routes?
If so, You need to create computed variable which will determine if it should be shown or not. e.g.:
computed: {
toShowOrNotToShow: function () {
if(this.$router.path === '/baseball' || this.$router.path === '/volleyball') return false;
else
return true;
}
}
Just use it: <bottomBar v-if='toShowOrNotToShow' ></bottomBar>

Related

How to collect selected BootstrapVue (b-form-checkbox) check boxes?

I work on Nuxt.js (Vue.js) application and modal dialog looks as following:
The code of Basket.vue component looks as following:
<template lang="pug">
b-container(v-if='products.length > 0')
b-row
b-col(:cols="8")
b-container
b-row
b-list-group
b-list-group-item.flex-column.align-items-start(v-for="(product, index) in products" :key="index")
b-container
b-row
b-col(:cols="1").d-flex.align-items-center.justify-content-center
b-form-checkbox {{ index + 1 }}
b-col(:cols="11")
basket-item(:product="product")
b-row.row-middle-style
b-button.close(type='button' aria-label='Close' #click="onRemoveItem")
span(aria-hidden='true') ×
u Remove selected items
b-button(type='button' #click="onContinue()").btn.btn-lg.buy__button Продолжить покупки
b-row
div
hr
| * доставка будет осуществлена из Санкт-Петербурга;
br
| * наш менеджер свяжется с вами сразу после обработки заказа;
...
</template>
<script lang="ts">
import Vue from 'vue'
import BasketItem from './BasketItem.vue'
import { mapState, mapActions } from 'vuex'
export default Vue.extend({
components: {
BasketItem,
},
data() {
return {
}
},
computed: {
...mapState(['products']),
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
},
})
</script>
<style lang="sass">
...
</style>
The code of BasketItem.vue component looks as following:
<template lang="pug">
b-container
b-row.style-item-row
b-col(:cols="3")
img(:src="product.image" :alt="product.alt")
b-col(:cols="4")
div {{ product.name }}
div {{ product.description }}
b-col(:cols="4")
div Цена {{ product.price }}
div Количество {{ product.quantity }}
b-col(:cols="1")
b-button.close(type='button' aria-label='Close' #click="onRemoveItem(product.id)")
span(aria-hidden='true') ×
</template>
<script lang="ts">
import Vue from 'vue'
import { mapActions } from 'vuex'
export default Vue.extend({
props: {
product: {
type: Object,
},
},
data() {
return {
}
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
},
})
</script>
<style lang="sass">
...
</style>
The Vuex code looks as following:
import * as mutationTypes from './mutation_types'
export const state = () => ({
products: [
{
id: 1,
image: 'https://licota.ru/system/product_images/attachments/5d9b/1781/6332/3406/9d00/2a31/small/8bfa7c2c-c7c7-11e4-80f4-002590d99cf6.jpg?1570445184',
alt: 'Товар 1',
name: 'Товар 1',
description: 'Описание 1',
price: 100,
quantity: 4
},
{
id: 2,
image: 'https://licota.ru/system/product_images/attachments/5d9b/329f/6332/3406/9d00/9336/small/e6a69bba-3450-11e9-812c-002590d99cf6.jpg?1570452124',
alt: 'Товар 2',
name: 'Товар 2',
description: 'Описание 2',
price: 200,
quantity: 7
}
]
})
export const getters = {
}
export const actions = {
removeItem({ commit }, id) {
commit(mutationTypes.REMOVE_ITEM, id)
}
}
export const mutations = {
[mutationTypes.REMOVE_ITEM] (state, id) {
state.products = state.products.filter(x => {
return x.id != id
})
}
}
As you can see I have a list of items (there are two). In front of each of them is BootstrapVue (b-form-checkbox) check box. I can select any of them and click "Remove selected items". After that they will be removed. The code for that action doesn't exist at this moment. The Vuex code you can see refers to clicking "x" in upper-right corner of items.
My issue is how to collect selected items that is check boxes that are checked in order to remove appropriate items?
UPDATE:
I've tried you'd suggested, but it doesn't work. The most likely I did something wrong. Actually, when I take a look at Vue section in DevTools, isChecked computed property doesn't change after clicking check boxes. The code looks as following:
Basket.vue:
<template lang="pug">
b-container(v-if='products.length > 0')
b-row
b-col(:cols="8")
b-container
b-row
b-list-group
b-list-group-item.flex-column.align-items-start(
v-for="(product, index) in products"
:key="product.id"
v-bind="{...product}"
v-on="{'update:checked': (data) => handleSetChecked(data)}"
)
b-container.set_pad
b-row
b-col(:cols="12").d-flex.align-items-center.justify-content-center
basket-item(:product="product" :productIndex="index")
b-row.row-middle-style
b-button.close(type='button' aria-label='Close' #click="onRemoveSelectedItems")
span(aria-hidden='true') ×
u Удалить выбранное
b-button(type='button' #click="onContinue()").btn.btn-lg.buy__button Продолжить покупки
b-row
div
hr
| * доставка будет осуществлена из Санкт-Петербурга;
br
| * наш менеджер свяжется с вами сразу после обработки заказа;
...
</template>
<script>
import Vue from 'vue'
import BasketItem from './BasketItem.vue'
import { mapActions } from 'vuex'
export default Vue.extend({
components: {
BasketItem,
},
data() {
return {}
},
computed: {
products() {
return this.$store.getters.getProducts
},
toRemove() {
return this.$store.getters.getProductsToRemove
},
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
handleSetChecked(data) {
this.$store.dispatch("setToRemove", data)
},
handleRemoveItems() {
this.$store.dispatch("removeSelected")
},
onRemoveSelectedItems() {
this.handleRemoveItems()
},
},
})
</script>
BasketItem.vue:
<template lang="pug">
b-container
b-row.style-item-row
b-col(:cols="1").d-flex.align-items-center.justify-content-center
b-form-checkbox(v-model="isChecked") {{ productIndex + 1 }}
b-col(:cols="3")
img(:src="product.image" :alt="product.alt")
b-col(:cols="3")
div {{ product.title }}
div {{ product.description }}
b-col(:cols="4")
div Цена {{ product.price }}
div Количество {{ product.quantity }}
b-col(:cols="1")
b-button.close(type='button' aria-label='Close' #click="onRemoveItem(product.id)")
span(aria-hidden='true') ×
</template>
<script>
import Vue from 'vue'
import { mapActions } from 'vuex'
export default Vue.extend({
props: {
product: {
type: Object,
},
productIndex: {
type: Number,
},
},
data() {
return {}
},
computed: {
isChecked: {
get() {
return this.product.checked
},
set(val) {
this.$emit("update:checked", {
id: this.product.id,
checked: val,
})
}
},
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
},
})
</script>
Vuex:
import * as mutationTypes from './mutation_types'
export const state = () => ({
products: [
{
id: 1,
image: 'https://licota.ru/system/product_images/attachments/5d9b/1781/6332/3406/9d00/2a31/small/8bfa7c2c-c7c7-11e4-80f4-002590d99cf6.jpg?1570445184',
alt: 'Товар 1',
title: 'Товар 1',
description: 'Описание 1',
price: 100,
quantity: 4,
checked: false
},
{
id: 2,
image: 'https://licota.ru/system/product_images/attachments/5d9b/329f/6332/3406/9d00/9336/small/e6a69bba-3450-11e9-812c-002590d99cf6.jpg?1570452124',
alt: 'Товар 2',
title: 'Товар 2',
description: 'Описание 2',
price: 200,
quantity: 7,
checked: false
}
]
})
export const getters = {
getProducts: state => state.products,
getProductsToRemove: state => state.products.filter(({
checked
}) => checked),
}
export const actions = {
setToRemove({
commit
}, data) {
commit("SET_TO_REMOVE", data)
},
removeSelected({
commit
}) {
commit("REMOVE_SELECTED")
},
removeItem({ commit }, id) {
commit(mutationTypes.REMOVE_ITEM, id)
},
}
export const mutations = {
SET_TO_REMOVE(state, {
id,
checked
}) {
state.products = state.products.map(product => {
if (product.id === id) {
return {
...product,
checked,
}
} else {
return product
}
})
},
REMOVE_SELECTED(state) {
state.products = state.products.filter(({
checked
}) => !checked)
},
[mutationTypes.REMOVE_ITEM] (state, id) {
state.products = state.products.filter(x => {
return x.id != id
})
},
}
The reference to "Form Checkbox Inputs":
https://bootstrap-vue.org/docs/components/form-checkbox
I advise you to think through your code, and have the logic steps ready:
You put out a list of UI items, based on a list in the Vuex store
Each UI item has a toggle (checkbox) that sets some part of the state of the item it displays (should it be removed or not)
By clicking the toggle (checkbox), the state of the item should change
By clicking the button (remove), the Vuex state should change: remove every item whose state fulfills a condition (it's toRemove state is true)
That's all. More simply put: don't work by selecting some UI element. Work by updating the state of the data that the UI element is based upon. (And let Vue's reactive capability do the rest.)
The first snippet is an easy one, not using Vuex:
Vue.component('RowWithCb', {
props: ["id", "title", "checked"],
computed: {
isChecked: {
get() {
return this.checked
},
set(val) {
this.$emit("update:checked", val)
}
},
},
template: `
<b-row>
<b-col
class="d-flex"
>
<div>
<b-checkbox
v-model="isChecked"
/>
</div>
<div>
{{ title }}
</div>
</b-col>
</b-row>
`
})
new Vue({
el: "#app",
computed: {
toRemove() {
return this.rows.filter(({
checked
}) => checked)
},
toKeep() {
return this.rows.filter(({
checked
}) => !checked)
},
},
data() {
return {
rows: [{
id: 1,
title: "Row 1",
checked: false,
},
{
id: 2,
title: "Row 2",
checked: false,
}
]
}
},
template: `
<b-container>
<b-row>
<b-col>
<h3>Items:</h3>
<row-with-cb
v-for="row in rows"
:key="row.id"
v-bind="{
...row
}"
:checked.sync="row.checked"
/>
</b-col>
</b-row>
<hr />
<b-row>
<b-col>
<h4>To keep:</h4>
{{ toKeep }}
</b-col>
<b-col>
<h4>To remove:</h4>
{{ toRemove }}
</b-col>
</b-row>
</b-container>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
NOW WITH VUEX
const initialState = () => ({
rows: [{
id: 1,
title: "Row 1",
checked: false,
},
{
id: 2,
title: "Row 2",
checked: false,
}
]
})
const store = new Vuex.Store({
state: initialState(),
mutations: {
RESET(state) {
const init = initialState()
for (key in init) {
state[key] = init[key]
}
},
SET_TO_REMOVE(state, {
id,
checked
}) {
state.rows = state.rows.map(row => {
if (row.id === id) {
return {
...row,
checked,
}
} else {
return row
}
})
},
REMOVE_SELECTED(state) {
state.rows = state.rows.filter(({
checked
}) => !checked)
},
},
actions: {
reset({
commit
}) {
commit("RESET")
},
setToRemove({
commit
}, data) {
commit("SET_TO_REMOVE", data)
},
removeSelected({
commit
}) {
commit("REMOVE_SELECTED")
},
},
getters: {
getRows: state => state.rows,
getRowsToRemove: state => state.rows.filter(({
checked
}) => checked),
getRowsToKeep: state => state.rows.filter(({
checked
}) => !checked),
}
})
Vue.component('RowWithCb', {
props: ["id", "title", "checked"],
computed: {
isChecked: {
get() {
return this.checked
},
set(val) {
this.$emit("update:checked", {
id: this.id,
checked: val,
})
}
},
},
template: `
<b-row>
<b-col
class="d-flex"
>
<div>
<b-checkbox
v-model="isChecked"
/>
</div>
<div>
{{ title }}
</div>
</b-col>
</b-row>
`
})
new Vue({
el: "#app",
store,
computed: {
rows() {
return this.$store.getters.getRows
},
toRemove() {
return this.$store.getters.getRowsToRemove
},
toKeep() {
return this.$store.getters.getRowsToKeep
},
},
methods: {
handleSetChecked(data) {
this.$store.dispatch("setToRemove", data)
},
handleRemoveItems() {
this.$store.dispatch("removeSelected")
},
handleResetStore() {
this.$store.dispatch("reset")
},
},
template: `
<b-container>
<b-row>
<b-col>
<h3>Items:</h3>
<row-with-cb
v-for="row in rows"
:key="row.id"
v-bind="{
...row
}"
v-on="{
'update:checked': (data) => handleSetChecked(data)
}"
/>
</b-col>
</b-row>
<hr />
<b-row>
<b-col>
<h4>To keep:</h4>
{{ toKeep }}
</b-col>
<b-col>
<h4>To remove:</h4>
{{ toRemove }}
</b-col>
</b-row>
<hr />
<b-row>
<b-col>
<b-button
#click="handleRemoveItems"
>
REMOVE
</b-button>
<b-button
#click="handleResetStore"
>
RESET
</b-button>
</b-col>
</b-row>
</b-container>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app"></div>
You can see that I did not mess with any checkboxes or UI elements (the only difference is that the id is also emitted from the UI component). All the functions were moved to the Vuex store & that's all. (A bit more verbose than the first snippet, as I added the remove & the reset functionality.)
LET'S BREAK IT DOWN
1. You need a component that lists items. In this case, let's use the root component for this:
new Vue({
el: "#app",
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
},
{
id: "item_2",
name: "Item 2",
},
]
}
},
computed: {
items() {
return this.baseItems
},
},
template: `
<ul>
<li
v-for="item in items"
:key="item.id"
>
{{ item.name }}
</li>
</ul>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
I deliberately put items as computed - it's going to be ease to switch to Vuex.
2. You know that your listed items are going to be a bit more complex than this, so let's create a component that can handle their upcoming complexity:
Vue.component('ListItem', {
props: ['id', 'name'],
template: `
<li>
{{ name }}
</li>
`
})
new Vue({
el: "#app",
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
},
{
id: "item_2",
name: "Item 2",
},
]
}
},
computed: {
items() {
return this.baseItems
},
},
template: `
<ul>
<list-item
v-for="item in items"
:key="item.id"
v-bind="{
...item,
}"
/>
</ul>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
3. Let's make them removable - one-by-one or in a bunch:
Vue.component('ListItem', {
props: ['id', 'name', 'toRemove'],
computed: {
selected: {
get() {
return this.toRemove
},
set(val) {
this.$emit('update:to-remove', val)
}
},
},
template: `
<li>
<input
type="checkbox"
v-model="selected"
/>
{{ name }}
</li>
`
})
new Vue({
el: "#app",
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
toRemove: false,
},
{
id: "item_2",
name: "Item 2",
toRemove: false,
},
]
}
},
computed: {
items() {
return this.baseItems
},
},
methods: {
handleRemoveSelected() {
this.baseItems = this.baseItems.filter(item => !item.toRemove)
},
},
template: `
<div>
So, it's easier to follow:
{{ items }}
<hr />
<ul>
<list-item
v-for="item in items"
:key="item.id"
v-bind="{
...item,
}"
:toRemove.sync="item.toRemove"
/>
</ul>
<b-button
#click="handleRemoveSelected"
>
REMOVE SELECTED
</b-button>
</div>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
OK, now there is the function you were after - without Vuex
4. Let's mix in Vuex:
move the list to a Vuex state
move the selection to a Vuex action
move the removal to a Vuex action
// initial state is a function per Vuex best practices
const initialState = () => ({
items: [{
id: "item_1",
name: "Item 1",
toRemove: false,
},
{
id: "item_2",
name: "Item 2",
toRemove: false,
}
]
})
const store = new Vuex.Store({
state: initialState(),
mutations: {
// so you are able to reset the store to
// initial state
RESET(state) {
const init = initialState()
for (key in init) {
state[key] = init[key]
}
},
// this handles the "checked" state if each item
SET_TO_REMOVE(state, {
id,
toRemove,
}) {
// returning a new, modified array
// per FLUX process
state.items = state.items.map(item => {
if (item.id === id) {
return {
...item,
toRemove,
}
} else {
return item
}
})
},
// the same function that was in the app before
REMOVE_SELECTED(state) {
state.items = state.items.filter(({
toRemove
}) => !toRemove)
},
},
actions: {
reset({
commit
}) {
commit("RESET")
},
// action to commit the mutation: select item for removal
setToRemove({
commit
}, data) {
commit("SET_TO_REMOVE", data)
},
// action to commit the mutation: remove items
removeSelected({
commit
}) {
commit("REMOVE_SELECTED")
},
},
getters: {
getItems: state => state.items,
}
})
Vue.component('ListItem', {
props: ['id', 'name', 'toRemove'],
computed: {
selected: {
get() {
return this.toRemove
},
set(val) {
this.$emit('update:to-remove', val)
}
},
},
template: `
<li>
<input
type="checkbox"
v-model="selected"
/>
{{ name }}
</li>
`
})
new Vue({
el: "#app",
store,
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
toRemove: false,
},
{
id: "item_2",
name: "Item 2",
toRemove: false,
},
]
}
},
computed: {
items() {
return this.$store.getters.getItems
},
},
methods: {
handleSetToRemove(data) {
// data should be: { id, toRemove }
this.$store.dispatch("setToRemove", data)
},
handleRemoveSelected() {
this.$store.dispatch("removeSelected")
},
handleResetStore() {
this.$store.dispatch("reset")
},
},
template: `
<div>
So, it's easier to follow:
{{ items }}
<hr />
<ul>
<list-item
v-for="item in items"
:key="item.id"
v-bind="{
...item,
}"
#update:to-remove="(toRemove) => handleSetToRemove({
id: item.id,
toRemove,
})"
/>
</ul>
<b-button
#click="handleRemoveSelected"
>
REMOVE SELECTED
</b-button>
<b-button
#click="handleResetStore"
>
RESET
</b-button>
</div>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app"></div>
This is all there's to it.
You handle the selection in the small component - but the change of the selected state is NOT HANDLED inside this component, but emitted to the parent, which in turn handles this by calling a Vuex action. This changes the state in the Vuex store & that "flows" back through the props.
If you click on REMOVE SELECTED, that doesn't look at the components (if they are selected), but calls another Vuex action, that removes all selected items (by filtering) from the state of the Vuex store. As all items are put out from a getter of the Vuex store (and getters update if the underlying state changes), the new state appears in the list.
So, in this case, using Vuex might seem a bit overkill, but if you plan on a more complex state for the listed items, then it may be reasonable.

How to customize the text on empty cell value using Bootstrap-Vue?

What I want to achieve is put "-" on every empty cell value. Like this (see the "Name" row):
How do I do that? Thanks
You can use add a formatter if you use the fields prop.
Here you can create a method which either returns your name if there is one, or - if there's none.
new Vue({
el: '#app',
data() {
return {
fields: [
{ key: "name", formatter: 'formatName' },
{ key: "age" }
],
items: [
{ age: 40, name: 'Dickerson Macdonald' },
{ age: 21, name: '' },
{ age: 89, name: 'Geneva Wilson' },
{ age: 38, name: 'Jami Carney'}
]
}
},
methods: {
formatName(value) {
return value || '-'
}
}
})
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.css" />
<script src="https://unpkg.com/vue#2.6.2/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app">
<b-table :items="items" :fields="fields"></b-table>
</div>
Alternatively, you can use slots to create the logic in your template, or maybe render something else if there's no name. But if it's simply displaying a -, I'd stick with a formatter.
new Vue({
el: '#app',
data() {
return {
fields: [
{ key: "name" },
{ key: "age" }
],
items: [
{ age: 40, name: 'Dickerson Macdonald' },
{ age: 21, name: '' },
{ age: 89, name: 'Geneva Wilson' },
{ age: 38, name: 'Jami Carney'}
]
}
}
})
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.css" />
<script src="https://unpkg.com/vue#2.6.2/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app">
<b-table :items="items" :fields="fields">
<template #cell(name)="{ value }">
{{ value || '-' }}
</template>
</b-table>
</div>

BootstrapVue's b-paginate component renders but doesn't function

Please excuse my ignorance as a new web developer, but I can't seem to get the <b-paginate> component to work in my single file Django and Vue 2 app. I access Bootstrap Vue via CDN.
This is my component, which is placed directly above my main Vue app:
let paginationComponent = Vue.component('pagination-component', {
name: 'paginationComponent',
props:['pantryItems'],
template:`<div class="overflow-auto"><b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
first-text="First"
prev-text="Prev"
next-text="Next"
last-text="Last"
class="mt-4"
></b-pagination></div>`,
data: function() {
return {
rows: this.pantryItems.length,
perPage: 10,
currentPage: 1,
}
},
computed: {
rows: function() {
return this.pantryItems.length
}
}
})
and this is my Vue root app:
let mypantryapp = new Vue ({
el: "#app",
delimiters: ['[[', ']]'],
components: {'pagination-component': paginationComponent},
data: {
pantryItems: [],
name: '',
users: [],
itemName: '',
createdDate: '',
expirationDate: '',
csrf_token: '',
itemErrors: {currentUser: false},
currentUser: {id: false},
owner: '',
itemImgs: [],
tags: [],
by_category: false,
grocery_view: false,
...followed by a bunch of unrelated methods for my pantry inventory app.
This is how I call it in my HTML:
<b-pagination :pantry-items='pantryItems' ></b-pagination>
The component renders on the page with just the number 1 in the middle of the pagination button group and all other buttons greyed out.
The only mistake you've made here is confusing b-pagination for your own pagination-component child component.
You're invoking b-pagination perfectly in your template option, you just need to add the child component itself into your HTML to actually use it. The template parameter will replace the HTML of your child component in the DOM.
Simply change:
<b-pagination :pantry-items='pantryItems'></b-pagination>
to:
<pagination-component :pantry-items='pantryItems'></pagination-component>
And it should work correctly! Example below:
let paginationComponent = Vue.component('pagination-component', {
name: 'paginationComponent',
props: ['pantryItems'],
template: `<div class="overflow-auto"><b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
first-text="First"
prev-text="Prev"
next-text="Next"
last-text="Last"
class="mt-4"
></b-pagination></div>`,
data: function() {
return {
//rows: this.pantryItems.length, *** See note below
perPage: 1, //Lower perPage for example sake
currentPage: 1,
}
},
computed: {
rows: function() {
return this.pantryItems.length
}
}
});
let mypantryapp = new Vue({
el: "#app",
delimiters: ['[[', ']]'],
components: {
'pagination-component': paginationComponent
},
data: {
pantryItems: [1, 2, 3, 4, 5], //Dummy items for example sake
}
});
<!-- Set up Vue and Bootstrap-Vue for snippet -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap#4/dist/css/bootstrap.min.css" /><link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" /><script src="//unpkg.com/vue#latest/dist/vue.min.js"></script><script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<div id="app">
<pagination-component :pantry-items='pantryItems'></pagination-component>
</div>
As one final note, you need only declare rows once. In this case, you should remove it from data, because it will change if your total items changes. Your computed rows will reflect the change, the rows var in data will not (since it will be set once at startup and then never again).

How to use href in Vue and Quill

I am using the Quill editor in Vue.js and it's working great. I have images, etc.
But...the link isn't working. I tried both the "snow" and "bubble" themes.
I type the text, highlight it and then click on the "link". I get the dialog to set the link, but then the link isn't there.
It's working in the JavaScript version, but not the Vue.
Below is my code.
Vue.component('editor', {
template: '<div ref="editor"></div>',
props: {
value: {
type: String,
default: ''
}
},
data: function() {
return {
editor: null
};
},
mounted: function() {
this.editor = new Quill(this.$refs.editor, {
modules: {
toolbar: [
[{ header: [1, 2, 3, 4, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block', 'link']
]
},
//theme: 'bubble',
theme: 'snow',
formats: ['bold', 'underline', 'header', 'italic', 'link'],
placeholder: "Type something in here!"
});
this.editor.root.innerHTML = this.value;
this.editor.on('text-change', () => this.update());
},
methods: {
update: function() {
this.$emit('input', this.editor.getText() ? this.editor.root.innerHTML : '');
}
}
})
new Vue({
el: '#root',
data: {
//model: 'Testing an editor'
model: '',
isShowing: true
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<link href="https://cdn.quilljs.com/1.3.4/quill.snow.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://cdn.quilljs.com/1.3.4/quill.core.css" rel="stylesheet"/>
<!DOCTYPE html>
<html>
<head>
<title>Trying to use the Quill Editor in Vue</title>
</head>
<body>
<div id="root">
<div v-if="isShowing">
<editor v-model="model"></editor>
</div>
<p>I need the v-html directive: <span v-html="model"></span></p>
<p>Raw data: <pre>{{ model }}</pre></p>
<button #click="isShowing = !isShowing">Toggle</button>
</div>
</script>
</body>
</html>
Any help is greatly appreciated.
Thanks, D
I had to place a 'link' into the "formats" as well:
formats: ['bold', 'underline', 'header', 'italic', 'link'],
I updated my code snippet with the correct answer in case anyone else is having this problem.
Thanks!

Dynamic Unorderlist web component generate using stencilJS

Using stenciljs dynamically generate nesting unordered <ul><li>...</li></ul> list, so i and giving input as a Obj={} i am getting some issues. Here is my code below Please help me on this...
1. index.html
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
<title>Stencil Component Starter</title>
<script src="/build/mycomponent.js"></script>
</head>
<body>
<list-component list-object='[
{title: "Point", children: [
{title: "Point"},
{title: "Point"},
{title: "Point"},
{title: "Point", children: [
{title: "Point"},
{title: "Point"},
{title: "Point"}
]}
]},
{title: "Point", children: [
{title: "Point"},
{title: "Point", children: [
{title: "Point"},
{title: "Point"},
{title: "Point"}
]},
{title: "Point"}
]},
]' > </list-component>
</body>
</html>
ISSUE:
I am passing nested object to the custom web component.
In this list.tsx file i am facing problem while passing arguments to the function buildList("?","?")...?
2. list.tsx
import { Component, Prop, State, Watch, Element } from '#stencil/core';
#Component({
tag:'list-component',
styleUrl:'./list-component.css'
})
export class ListComponent{
#State() idName: string;
#Prop() listObject: string;
#Element() flashElement: HTMLElement;
private ulContent: HTMLElement;
componentWillLoad() {
this.ulContent = this.flashElement.querySelector('.ul-content');
this.buildList(this.ulContent,this.listObject);
}
#Watch('listObject')
buildList(parentElement, listObject){
console.log(listObject);
var i, l, list, li, ul1;
if( !listObject || !listObject.length ) { return; }
ul1 = document.createElement('ul');
list = parentElement.appendChild(ul1);
for(i = 0, l = listObject.length ; i < l ; i++) {
li = document.createElement('li');
li.appendChild(document.createTextNode(listObject[i].title));
list.appendChild(li);
this.buildList(li, listObject[i].children);
}
}
render() {
return (
<div class="ul-content"></div>
);
}
}
I see two problems:
1: When Stencil calls #Watch methods it always passes the new and old values as arguments, see https://stenciljs.com/docs/properties#prop-default-values-and-validation. This means you cannot define custom arguments.
You could create an additional function which acts as the watcher and then calls buildList:
#Watch('listObject')
listObjectChanged() {
this.buildList(this.ulContent, this.listObject);
}
2: listObject is a string so you need to JSON.parse it before you can loop over it (and rewrite it so it's valid JSON). Then store that parsed list in a local variable and use it to generate the list. See https://medium.com/#gilfink/using-complex-objects-arrays-as-props-in-stencil-components-f2d54b093e85
There is a much simpler way to render that list using JSX instead of manually creating the list elements:
import { Component, Prop, State, Watch, Element } from '#stencil/core';
#Component({
tag: 'list-component',
styleUrl: './list-component.css'
})
export class ListComponent {
#Element() flashElement: HTMLElement;
#State() idName: string;
#Prop() listObject: string;
#State() list: any[];
#Watch('listObject')
listObjectChanged() {
this.list = JSON.parse(this.listObject);
}
componentWillLoad() {
this.listObjectChanged();
}
renderList(list) {
return list.map(list => <ul>
<li>{list.title}</li>
{list.children && this.renderList(list.children)}
</ul>
);
}
render() {
return (
<div class="ul-content">
{this.renderList(this.list)}
</div>
);
}
}
Hope this helps.