I try to set an option for my Vue component after getting my required data through an API. The data is set correctly when the Vue instance is created but it seems that does not affect my condition.
This is the snippet:
import axios from 'axios';
Vue.component("order-now", {
delimiters: ["${", "}"],
props: {
dynamic: {
type: Boolean,
default: false
},
template: null
},
data() {
return {
order: '',
startInterval: false,
}
},
/**
* created
*/
created() {
this.getOrderNow();
this.$options.template = this.template;
},
mounted() {
if(this.startInterval)
this.$options.interval = setInterval(this.getOrderNow(), 10000);
},
/**
* beforeDestroy
*/
beforeDestroy() {
clearInterval(this.$options.interval);
},
methods: {
/**
* getOrderNow
*
* Receive data from api route
* and store it to components data
*/
getOrderNow() {
axios.get('/rest/order-now').then(({data}) => {
this.order = data.orderNow.order;
this.startInterval = data.orderNow.startInterval;
}).catch(e => {
console.error('Could not fetch data for order string.')
});
}
}
});
I call my getOrderNow() method when the created hook is called. This works fine and my data is set.
As you can see, in the mounted() hook, I try to look if setInterval is set true or false and condionally set an option but setInterval is always false.
I thought that might has been changed after calling my method in the created hook but it does not.
this.startInterval is false because it probably never gets set to true at the time mounted() is applied. The thing is that you set startInterval after the promise returned by axios is resolved, which most likely happens after mounted().
To solve this you can just set interval inside axios.then().
Update after reading a comment (working demo):
const API = {
counter: 0,
getItems() {
return new Promise((fulfill) => {
setTimeout(() => {
fulfill(API.counter++);
})
});
},
};
new Vue({
el: "#app",
data: {
interval: false,
data: '',
},
methods: {
fetchThings() {
API.getItems().then((data) => {
this.data = data;
});
},
},
created() {
this.fetchThings();
this.interval = setInterval(this.fetchThings, 1000);
},
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<pre>
{{data}}
</pre>
</div>
And jsfiddle
Related
I have a table and I give a ref to it.
<table ref="table"></table>
I want to calculate the height of this table in watcher:
watch: {
buttonClicked: {
immediate: true,
handler() {
this.$nextTick(() => {
const tableElement = this.$refs.table.$el;
const tableOffsetTop = tableElement.getBoundingClientRect().top;
});
},
},
}
But I am getting an error: Uncaught TypeError: Cannot read properties of undefined (reading '$el')
I tried ti fix it with this.$nextTick but this time I cannot calculate it right.
How can I fix it?
Try without $el:
const app = Vue.createApp({
data() {
return {
height: null
}
},
watch: {
buttonClicked: {
handler() {
this.$nextTick(() => {
const tableElement = this.$refs.table;
const tableOffsetTop = tableElement.getBoundingClientRect().top;
this.height = tableElement.getBoundingClientRect().bottom -tableElement.getBoundingClientRect().top
});
},
immediate: true,
},
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<table ref="table"><tr><td>table</td></tr></table>
height: {{ height }}
</div>
I am using ArcGis in a Nuxt application. I have got a map with a feature layer (hosted on ArcGis) and a legend with a color slider in the top-right corner. The user can visualise different fields from the layer. For each field selected a new renderer is generated and therefore a new colorSlider. My problem is that every time the user select a new field, a new colorSlider is added above the previous one and I end up with three coloSliders in the legend. How can I fix that ?? I tried to destroy the previous colorSlider when I select a new field but it seems to destroy the div which contains the slider and then I have no slider at all anymore... This is the code =>
<template>
<div>
<div id="viewDiv"></div>
<div id="legend"></div>
<div id="containerDiv" class="esri-widget">
<span id="title" class="esri-widget">impact legend</span>
<div id="slider" ref="sliderr"></div>
</div>
</div>
</template>
<script>
import Map from '#arcgis/core/Map'
import MapView from '#arcgis/core/views/MapView'
import esriConfig from '#arcgis/core/config'
import FeatureLayer from '#arcgis/core/layers/FeatureLayer'
import * as colorRendererCreator from '#arcgis/core/smartMapping/renderers/color'
import ColorSlider from '#arcgis/core/widgets/smartMapping/ColorSlider'
export default {
props: {
selectedTab: {
type: Number,
default: 1,
},
},
data() {
return {
url: 'https://blablabla',
countries:
'https://blablabla',
projectLyr: undefined,
countryLyr: undefined,
map: new Map({ basemap: 'osm-light-gray' }),
view: undefined,
fieldName: '',
renderer: {},
filter: '',
rendererResult: undefined,
colorSlider: undefined,
}
},
mounted() {
esriConfig.apiKey =
'myApiKey'
this.projectLyr = new FeatureLayer({
url: this.url,
outFields: ['*'],
})
this.countryLyr = new FeatureLayer({
url: this.countries,
outFields: ['*'],
})
this.view = new MapView({
map: this.map,
center: [15, 50],
zoom: 6,
container: 'viewDiv',
})
this.updateLayer({ layer: this.projectLyr, value: 'Impact_PA_area' })
this.$nuxt.$on('filter-selected', this.updateLayer)
},
beforeDestroy() {
this.$nuxt.$off('tab-selected')
this.$nuxt.$off('filter-selected')
},
methods: {
generateRenderer(lyr) {
const colorParams = {
layer: lyr.layer,
field: `${lyr.field}`,
view: this.view,
theme: 'above-and-below',
}
colorRendererCreator
.createContinuousRenderer(colorParams)
.then((response) => {
// Set the renderer to the layer and add it to the map
this.rendererResult = response
lyr.layer.renderer = this.rendererResult.renderer
})
.then(() => {
// Construct a color slider from the result of smart mapping renderer
this.colorSlider = ColorSlider.fromRendererResult(this.rendererResult)
this.colorSlider.container = 'slider'
this.colorSlider.primaryHandleEnabled = true
this.colorSlider.viewModel.precision = 1
this.view.ui.add('containerDiv', 'top-right')
function changeEventHandler() {
const renderer = lyr.layer.renderer.clone()
const colorVariable = renderer.visualVariables[0].clone()
const outlineVariable = renderer.visualVariables[1]
colorVariable.stops = this.colorSlider.stops
renderer.visualVariables = [colorVariable, outlineVariable]
lyr.layer.renderer = renderer
}
this.colorSlider.on(
['thumb-change', 'thumb-drag', 'min-change', 'max-change'],
changeEventHandler
)
})
.catch((error) => {
console.error('Error: ', error)
})
},
filtering(value) {
if (value.value.isFilter) {
this.filter = `${value.value.value}`
this.projectLyr.definitionExpression = this.filter
} else {
this.projectLyr.definitionExpression = `${value.value.value} AND IS NOT NULL`
if (this.filter !== '') {
this.projectLyr.definitionExpression = this.filter
}
value.isCountry
? this.generateRenderer({
layer: this.countryLyr,
field: value.value.value,
})
: this.generateRenderer({
layer: this.projectLyr,
field: value.value.value,
})
}
},
updateLayer(value) {
this.$nextTick(() => {
if (this.selectedTab === 0) {
this.map.remove(this.projectLyr)
this.map.add(this.countryLyr)
this.filtering({ value, isCountry: true })
} else {
this.map.remove(this.countryLyr)
this.map.add(this.projectLyr)
this.filtering({ value, isCountry: false })
}
})
},
},
}
</script>
<style scoped>
#import 'https://js.arcgis.com/4.23/#arcgis/core/assets/esri/themes/light/main.css';
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#containerDiv {
background-color: white;
padding: 3px;
text-align: center;
min-width: 260px;
}
</style>
I think you can just update the ColorSlider with the new ContinuousRendererResult data instead of trying to destroy/recreate. In a similar way you create it, use updateFromRendererResult method to update it (ArcGIS JS API - ColorSlider).
I was practicing and learning about vuejs, specifically pagination.
I learn how to do it, and implemented it when I made the api calls in the component, then I moves the function to the actions in vuex, and mapped, them, now my pagination is not even showing up.
I wonder where I made the mistake ???
I have carefully paid attention to every single detail, and read about mutatioons, and actions, I do feel like I am doing everything right, I don't even have any mistakes showing up on the console. this is very wierd
my postModule
import axios from "axios";
export const postModule = {
state: () => ({
posts: [],
page: 1,
limit: 10,
totalPages: 0,
}),
mutations: {
setPosts(state, posts) {
state.posts = posts;
},
setPage(state, page) {
state.page = page;
},
setTotalPage(state, totalPages) {
state.setTotalPage = totalPages
},
},
actions: {
async fetchPosts( {state, commit}) {
try {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts", {
params: {
_page: state.page,
_limit: state.limit,
},
});
commit('setTotalPage', Math.ceil(response.headers["x-total-count"] / state.limit));
commit('setPosts', [...state.posts, ...response.data]);
} catch (e) {
console.log(e);
}
},
async loadPosts({
state,
commit
}) {
try {
commit('setPage', state.page + 1)
setTimeout(async () => {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts", {
params: {
_page: state.page,
_limit: state.limit,
},
});
commit('setTotalPage', Math.ceil(response.headers["x-total-count"] / state.limit));
commit('setPosts', [...state.posts, ...response.data]);
});
} catch (e) {
console.log(e);
}
},
},
namespaced: true,
}
<<<<<component >>>>
<template>
<div>
<h1>Page with the posts</h1>
<post-list :posts="posts" />
</div>
<div class="page__wrapper">
<div
v-for="pageNumber in totalPages"
:key="pageNumber"
class="page"
:class="{
'current-page': page === pageNumber,
}"
#click="changePage(pageNumber)"
>
{{ pageNumber }}
</div>
</div>
</template>
<script>
import PostList from "#/components/PostList";
// import axios from 'axios';
import { mapState, mapMutations, mapActions } from "vuex";
export default {
components: { PostList },
data() {
return {
// posts: [],
// page: 1,
// limit: 10,
// totalPages: 0,
};
},
methods: {
changePage(pageNumber) {
this.page = pageNumber;
this.fetchPosts();
},
...mapMutations({
setPage: "setPage",
}),
...mapActions({
// loadPosts: "post/loadPosts",
fetchPosts: "post/fetchPosts",
}),
},
watch: {
page() {
this.fetchPosts();
},
},
mounted() {
this.fetchPosts();
},
computed: {
...mapState({
posts: (state) => state.post.posts,
page: (state) => state.post.page,
limit: (state) => state.post.limit,
totalPages: (state) => state.post.totalPages,
}),
},
};
</script>
<style scoped>
.page__wrapper {
display: flex;
margin: 15px;
}
.page {
border: 1px solid black;
padding: 10px;
}
.current-page {
border: 2px solid red;
font-size: x-large;
}
</style>
I'm trying to take this Flickr jsonp Vue example (https://codepen.io/tomascherry/pen/GrgbzQ) and turn it into a component. However, I cannot figure out how to get jsonFlickrFeed() to map to this.jsonFlickrFeed() (once that function is place inside the component's methods: {}).
Code as follows:
<template>
<!-- HTML HERE -->
</template>
<script>
let callApiTimeout = null
export default {
name: 'Flickr',
filters: {
splitTags: function(value) {
// showing only first 5 tags
return value.split(' ').slice(0, 5)
}
},
directives: {
/* VueJs utilites */
img: {
inserted: function(el, binding) {
this.lazyload(el, binding)
},
update: function(el, binding) {
this.lazyload(el, binding)
}
}
},
data() {
return {
images: [],
query: ''
}
},
watch: {
query: function(value) {
clearTimeout(callApiTimeout)
callApiTimeout = setTimeout(
function() {
const reqURL =
'https://api.flickr.com/services/feeds/photos_public.gne'
const options = {
params: {
format: 'json',
tags: this.query,
jsoncallback: 'this.jsonFlickrFeed'
}
}
this.$http.jsonp(reqURL, options)
}.bind(this),
250
)
}
},
methods: {
/* JSONP callback function */
jsonFlickrFeed(response) {
this.$data.images = response.items
},
/* General utility functions */
lazyload(el, binding) {
const img = new Image()
img.src = binding.value
img.onload = function() {
el.src = binding.value
}
}
}
}
</script>
<style lang="less">
/* STYLE HERE */
</style>
I tried adding the jsoncallback: 'this.jsonFlickrFeed' parameter but that doesn't help.
To make it simpler, just pass the parameter nojsoncallback=1 and it will return the JSON object directly.
I'm trying to implement handsontable. As per my requirement, I want to re-render handsontable from changing a dropdown value, but on dropdown selection, the handsontable does not update properly. Below is my code:
Handsontable.vue:
<template>
<div id="hot-preview">
<HotTable :settings="settings" :ref="referenceId"></HotTable>
<div></div>
</div>
</template>
<script>
import { HotTable } from '#handsontable-pro/vue';
export default {
components: {
HotTable
},
props: ['settings', 'referenceId'],
}
</script>
<style>
#hot-preview {
max-width: 1050px;
height: 400px;
overflow: hidden;
}
</style>
Parent component:
<template>
<div id="provisioning-app">
<v-container grid-list-xl fluid>
<v-select
:items="selectList"
item-text="elementName"
item-value="elementName"
label="Standard"
v-model="selected"></v-select>
<handsontable :settings.sync="settings" :referenceId="referenceId"></handsontable>
</v-container>
</div>
</template>
<script>
import Handsontable from '#/components/Handsontable';
import PrevisioningService from '#/services/api/PrevisioningService';
export default {
components: {
Handsontable
},
data: () => ({
selectList: [],
selectApp: [],
selectedOption: '',
referenceId: 'provision-table',
}),
created(){
PrevisioningService.getProvisioningList(this.$session.get('userId'), this.$session.get('customerId')).then(response => {
this.provisioningList = response;
});
},
beforeUpdate() {
this.provisioningApp = this.getProvisioningAppList;
},
computed: {
settings () {
return {
data: this.getSelectApp,
colHeaders: ["Data Uploaded on", "Duration in Minutes", "Start Time", "Shift","Description","Next Day Spill Over", "Site Name"],
columns: [
{type: 'text'},
{type: 'text'},
{type: 'text'},
{type: 'text'},
{type: 'text'},
{type: 'text'},
{type: 'text'}
],
rowHeaders: true,
dropdownMenu: true,
filters: true,
rowHeaders: true,
search: true,
columnSorting: true,
manualRowMove: true,
manualColumnMove: true,
contextMenu: true,
afterChange: function (change, source) {
alert("after change");
},
beforeUpdate: function (change, source) {
alert("before update");
}
}
},
getSelectApp () {
if(this.selectedOption !== undefined && this.selectedOption !== null && this.selectedOption !== ''){
PrevisioningService.getProvisioningAppList(this.selectedOption, this.$session.get('userId'), this.$session.get('customerId')).then(response => {
this.provisioningApp = response;
return this.provisioningApp;
});
}
}
},
method: {
getSelected () {
return this.selectedOption;
}
}
};
</script>
With the above code, my data is received successfully from the server, but I'm unable to update the data in handsontable, as shown in the following screenshots:
How do I properly render the table after the dropdown selection?
I see two issues:
handsontable appears to not handle dynamic settings (see console errors), so settings should not be a computed property. Since the only settings property that needs to be updated is settings.data, that property alone should be mutated (i.e., don't reset the value of settings).
To address this, move settings into data(), initializing settings.data to null so that it would still be reactive:
data() {
settings: {
data: null,
colHeaders: [...],
...
}
},
computed: {
// settings() { } // DELETE THIS
}
getSelectApp is a computed property that is incorrectly asynchronous (i.e., in this case, it fetches data and handles the response later). A computed property cannot be asynchronous, so this computed property actually returns undefined. While there is a return call inside the computed property, the return does not set the value of the computed property because it's inside a Promise callback:
PrevisioningService.getProvisioningAppList(/*...*/).then(response => {
this.provisioningApp = response;
return this.provisioningApp; // DOES NOT SET COMPUTED PROPERTY VALUE
});
Also note the side effect from this.provisioningApp = response. It doesn't seem this.provisionApp is needed in this code in any case, so it should be removed as clean-up.
It seems the intention of this computed property is to update settings.data based on the value of the selected option. To accomplish that, you would have to use a watcher on selectedOption, which would change settings.data.
watch: {
selectedOption(val) {
PrevisioningService.getProvisioningAppList(/*...*/).then(response => {
this.settings.data = response;
});
}
},
demo