How to get data to Quasar tree node from local JSON file - vue.js

I am very new to Quasar, I want to display Quasar tree nodes from a Local JSON file and with on click of a particular node item I want to show some text.
As of now I am using Quasar tree like this.
<template>
<q-page class="flex fixed-left">
<q-tree
:nodes="simple"
node-key="label"
no-connectors
:expanded.sync="expanded"
text-color="blue"
></q-tree>
</q-page>
</template>
<style>
</style>
<script>
export default {
name: 'HelloWorld',
data () {
return {
expanded: [ 'Resources' ],
simple: [
{
label: 'Resources',
children: [
{
label: 'Projects',
children: [
{
label: 'Data sets' ,
children: [
{
label: 'Items',
}
]
},
{ label: 'Good recipe' }
]
},
{
label: 'Good service (disabled node with icon)',
icon: 'room_service',
disabled: true,
children: [
{ label: 'Prompt attention' },
{ label: 'Professional waiter' }
]
},
{
label: 'Pleasant surroundings (with icon)',
icon: 'photo',
children: [
{
label: 'Happy atmosphere (with image)',
img: 'https://cdn.quasar.dev/img/logo_calendar_128px.png'
},
{ label: 'Good table presentation' },
{ label: 'Pleasing decor' }
]
}
]
}
]
}
}
}
</script>
It was coming like this
Now I wanted to get these nodes from a local JSON file and on click on Items should display some text .
Please help me with this.

To import JSON in ECMAScript:
If you don't have a lot of JSON files and you have the permission to modify and convert them into JS files, you can follow this answer: https://stackoverflow.com/a/39855320/3241933
Then you should import the vanilla JS object into data().
Example solution:
<template>
<q-page class="flex fixed-left">
<q-tree
:nodes="simple"
node-key="label"
no-connectors
:expanded.sync="expanded"
text-color="blue"
></q-tree>
</q-page>
</template>
<style>
</style>
<script>
import jsonData from './data.js'
export default {
name: 'HelloWorld',
data () {
return {
expanded: [ 'Resources' ],
simple: jsonData
}
}
}
</script>
Make sure that your import is an array of object.

Related

Vuejs v-for not working with my data, is my data not formatted correctly?

Not sure why the data is showing up as undefined. Is the data not formatted correctly? I've tried several ways to adjust it and nest it differently but nothing is working out so far.
<template>
<main>
<AboutMeComponent v-for="i in about" :key="i.posts.id" :title="i.posts.title" :body="i.posts.body" :skills="i.posts.skills"
:img="i.images.img" />
</main>
</template>
<script>
import AboutMeComponent from '../components/AboutMeComponent.vue';
export default {
data() {
return {
about: {
posts: [
{ id: 1, title: "About Me", body: `Body 1` },
{ id: 2, title: "Skills" },
{ id: 3, title: "Hobbies", body: "Body 3" },
],
images: [
{ img: 'figma.png'},
{ img: 'figma.png'},
{ img: 'figma.png'},
]
}
}
},
components: {
AboutMeComponent
}
}
</script>
I think you are need to set the v-for loop on the about.posts, not on the about. Also, if the img is used for the post, I would suggest you keep them with the post, instead of separated.
So your data will be:
data() {
return {
about: {
posts: [
{ id: 1, title: 'About Me', body: 'Body 1', img: 'figma.png' },
{ id: 2, title: 'Skills', body: 'Body 2', img: 'figma.png' },
{ id: 3, title: 'Hobbies', body: 'Body 3', img: 'figma.png' },
],
}
}
},
And now you can loop over about.posts:
<AboutMeComponent v-for="post in about.posts" :key="post.id" :title="post.title" :body="post.body" :img="post.img" />
I left skills out, as they are not in your data.
Just a small suggestion: using variable names as i makes your code harder to understand. If you had used about in about, you would have felt that this was not what you wanted, because you wanted something to do with a post in posts.
Hope this helps.
Few Observations :
As each img in the images array corresponds to each object in the posts array. Merge that into a single one and then use v-for loop for about.posts.
As few properties are missing. Use Optional chaining (?.) operator so that you can read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.
Live Demo :
Vue.component('aboutmecomponent', {
props: ['key', 'title', 'body', 'skills', 'img'],
template: '<h3>{{ title }}</h3>'
});
var app = new Vue({
el: '#app',
data: {
about: {
posts: [
{ id: 1, title: "About Me", body: "Body 1" },
{ id: 2, title: "Skills" },
{ id: 3, title: "Hobbies", body: "Body 3" },
],
images: [
{ img: 'figma.png'},
{ img: 'figma.png'},
{ img: 'figma.png'},
]
}
},
mounted() {
this.about.posts = this.about.posts.map((obj, index) => {
const newObj = {
...obj,
...this.about.images[index] }
return newObj;
});
delete this.about.images;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<AboutMeComponent
v-for="item in about.posts"
:key="item?.id"
:title="item?.title"
:body="item?.body"
:skills="item?.skills"
:img="item?.img" >
</AboutMeComponent>
</div>

Vue Draggable Recursive / Nested Dragging Weird Behavior

This question will be bountied as soon as I can, so a big rep reward coming to someone!
EDIT: One more layer to this issue I noticed, The lowest level children when I try to drag it somewhere else it doesn't work, acts like it will but goes back where it was (i.e it's children are empty). But when I drag the parent of a child somewhere else it does one of the following:
Moves the parent and it's parent somewhere else where I drag it, but leaves the child where it was i.e
Initial state on a refresh
After I drag col-2 to container-1
or
Moves the child into the container where I dragged the parent to but leaves the parent where it was i.e
Initial state on a refresh
After dragging col-2 into col-1
Original Post
Hey guys, been building a landing page builder and decided vue-draggable would be a nice addition. That said after 3 days of headaches trying to make this work I'm at a loss. So far I've followed the nested example guide which has been KINDA working, in addition, I followed an issue about the nested guide on here adding an emitter to the children for proper updates. Now my getters and setters are firing BUT I'm still having a problem dragging elements(see the video)
http://www.giphy.com/gifs/ZMiyi8LEcI73nye1ZN
As you can see when I drag stuff around it's strange behavior:
Example cases:
When I drag col 2 label into col 1 it moves the children inside into
col one, does not change col 2s place
When I drag paragraph label
anywhere it will not move, shows like it will but when I release
nothing happens
If I drag row 1 from the original starting state you
saw in the gif into the paragraph I end up with the following:
Just 3 sample cases, references:
https://sortablejs.github.io/Vue.Draggable/#/nested-with-vmodel
https://github.com/SortableJS/Vue.Draggable/issues/701#issuecomment-686187071
my code creating these results:
component-renderer.vue (THERE'S A NOTE IN HERE TO READ)
<draggable
v-bind="dragOptions"
:list="list"
:value="value"
style="position: relative; border: 1px solid red"
:tag="data.tagName"
:class="data.attributes.class + ` border border-danger p-3`"
#input="emitter"
#change="onChange" //NOTE: I've tried setting the group here to row instead of in the computed prop below, didn't work
>
<slot></slot>
<component-renderer
v-for="el in realValue"
:key="el.attributes.id"
:list="el.children"
:data="el"
:child="true"
#change="onChange"
>
<span style="position: absolute; top: 0; left: 0; background: red">{{
`${el.tagName} - ${el.attributes.id}`
}}</span>
{{ el.textNode }}
</component-renderer>
</draggable>
</template>
<script>
import draggable from "vuedraggable";
export default {
name: "ComponentRenderer",
components: {
draggable,
},
props: {
data: {
required: false,
type: Object,
default: null,
},
value: {
required: false,
type: Array,
default: null,
},
list: {
required: false,
type: Array,
default: null,
},
child: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
dragOptions() {
return {
animation: 0,
disabled: false,
ghostClass: "row",
group: "row",
};
},
realValue() {
return this.value ? this.value : this.list;
},
},
methods: {
emitter(value) {
this.$emit("input", value);
},
onChange: function () {
if (this.child === true) {
this.$emit("change");
} else {
this.emitter(this.value);
}
},
},
};
</script>
<style scoped></style>
PageEditor.vue:
<div id="wysiwyg-page-editor">
<ChargeOverNavBar />
<div class="editor">
<ComponentRenderer v-model="elements" :data="elements[0]" />
</div>
<ChargeOverFooter />
</div>
</template>
<script>
import ChargeOverNavBar from "#/components/ChargeOverNavBar";
import ChargeOverFooter from "#/components/ChargeOverFooter";
import InlineEditor from "#ckeditor/ckeditor5-build-inline";
import ComponentRenderer from "#/components/module-editor/ComponentRenderer";
export default {
name: "PageEditor",
components: {
ComponentRenderer,
ChargeOverFooter,
ChargeOverNavBar,
},
data() {
return {
editor: InlineEditor,
editorConfig: {},
activeSection: null,
page: {},
panels: {
pageProperties: true,
seoProperties: true,
sectionProperties: false,
},
};
},
computed: {
elements: {
get() {
console.log("getter");
return JSON.parse(JSON.stringify(this.$store.state.editor.editorData));
},
set(value) {
console.log("setter");
this.$store.dispatch("setEditor", value);
},
},
},
};
</script>
<style lang="scss" scoped>
#use "../assets/scss/components/PageEditor";
</style>
editor.js(store module):
state: {
editorData: [],
editorLoading: false,
},
mutations: {
SAVE_EDITOR(state, data) {
state.editorData = data;
},
TOGGLE_EDITOR_LOAD(state, busy) {
state.editorLoading = busy;
},
},
actions: {
setEditor({ commit }, data) {
commit("SAVE_EDITOR", data);
},
loadEditor({ commit }) {
commit("TOGGLE_EDITOR_LOAD", true);
//TODO: Change me to read API DATA
let fakeData = [
{
tagName: "section",
attributes: {
id: "section-1",
class: "test",
},
children: [
{
tagName: "div",
attributes: {
id: "container-1",
class: "container",
},
children: [
{
tagName: "div",
attributes: {
id: "row-1",
class: "row",
},
children: [
{
tagName: "div",
attributes: {
id: "col-1",
class: "col",
},
children: [],
},
{
tagName: "div",
attributes: {
id: "col-2",
class: "col",
},
children: [
{
tagName: "p",
attributes: {
id: "p-1",
class: "p",
},
textNode: "This is my paragraph",
children: [],
},
],
},
],
},
],
},
],
},
];
commit("SAVE_EDITOR", fakeData);
commit("TOGGLE_EDITOR_LOAD", false);
},
},
getters: {
getEditorData: (state) => state.editorData,
getEditorLoading: (state) => state.editorLoading,
},
};
It seems only dragging labels works for moving stuff around but not like i'd expect. I think this makes sense but why can't I drag the body anywhere? It's not slotted in header or footer and the docs says that's the onlytime it wouldn't be? Can I not use as the tag itself and drag it?
As I'm sure all of you can deduce the behavior I'm expecting, anything should be draggable into any section(in the future I want this to change so only cols can be dragged into rows, rows can only be dragged into sections etc(but for now I'm not sure how to do this so we start at the beginning :D)).
Also yes I know those components are kinda messy atm, until I fix this I'm not cleaning them up as I keep drastically changing the contents of these files trying to make it work, sorry it's hacky atm!
Any help or ideas would be amazing!
Thanks guys!

Nuxt.js after page refresh meta are filled from config instead of head method

I had problem with meta in nuxt.js app. I want to fill dynamic meta tags in one detail page
--pages
----event
-----_id.vue
When I navigate on web site via link all work great. But if I just refresh page, meta tags use value from nuxt.config.js. For instance I got 'SiteTitle Nuxt.config.js' instead of 'SiteTitle - Some event title'.
Nuxt version 2.15.3
nuxt.config.js
export default {
head: {
titleTemplate: '%s - SiteTitle',
title: 'SiteTitle Nuxt.config.js',
htmlAttrs: {
lang: 'en'
},
meta: [
{charset: 'utf-8'},
{name: 'viewport', content: 'width=device-width, initial-scale=1'},
{hid: 'description', name: 'description', content: ''}
],
link: [
{rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}
]
}
components: true,
buildModules: [
'#nuxt/typescript-build',
'#nuxtjs/vuetify',
],
modules: [`enter code here`
'#nuxtjs/axios'
],
vuetify: {
customVariables: ['~/assets/variables.scss'],
},
axios: {
baseURL: 'https://api.someurl.com',
}
}
And _id.vue file
<template>
<v-card class="mt-6 mb-5" outlined>
<v-card-title>{{ model.title }}</v-card-title>
</v-card>
</template>
<script lang="ts">
import {Component, Vue} from "nuxt-property-decorator"
import {EventModel} from "~/api/models/EventModel";
import EventApi from "~/api/EventApi";
#Component({
async asyncData({params, $axios}) {
const eventApi = new EventApi($axios)
const model = await eventApi.get(parseInt(params.id))
return { model: model };
},
head(this: EventPage): object {
return {
title: "SiteTitle - " + this.model.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.model.shortDescription
}
]
}
},
})
export default class EventPage extends Vue {
model = {} as EventModel
async fetch() {
}
}
</script>
I tried to call api in fetch, in this case meta values always have valid value when I refresh page, but Facebook Sharing Debugger get meta from nuxt.config.js in this case, and this solution is not suitable
Thanks for your help
You can do one thing
Create one plugin in that you can use this on router.beforeEach like this:
app.router.beforeEach(async(to, _, next) => {
document.title = to.meta.pageTitle ? ('Specify title - ' + ${to.meta.pageTitle}) :'Default Title';
})
In your router file you can do something like this:
{
path: '/path',
name: 'Name',
component: Component,
meta: {
pageTitle: 'Change Password'
}
}

How to use customRender in a table cell with Antd and VueJS

I'm using antd in my app and I'm trying to do a customRender to show an image in a cell.
My columns array looks like this:
columns: [
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender () { // h will be injected
return '<div id="foo">bar</div>'
}
},
]
So, as you might imagine, it turns out like this:
I also tried this way:
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender: (text, record, index) => {
return {
children: '<img src="https://via.placeholder.com/300.png/09f/fff">'
}
}
},
Unfortunately, that ended up like this:
Can someone please help me figure out what I'm doing wrong?
You can take advantage of the scopedSlots property within columns, and use it to define a scoped slot for the customRender property.
Here is an example:
const columns = [
{
title: "Image",
dataIndex: "image",
key: "image",
scopedSlots: { customRender: "image-column" },
},
];
Now, in your template, you can use the image-column named slot, like this:
<a-table :columns="columns" :data-source="tableData">
<template slot="image-column" slot-scope="image">
<img :src="image" /> <!-- Add your custom elements here -->
</template>
</a-table>
And here is a component example:
<template>
<a-table :columns="columns" :data-source="tableData">
<template slot="image-column" slot-scope="image">
<img :src="image" />
</template>
</a-table>
</template>
<script>
const columns = [
{
title: "Image",
dataIndex: "image",
key: "image",
scopedSlots: { customRender: "image-column" },
},
];
const tableData = [
{
image: "https://picsum.photos/200",
},
{
image: "https://picsum.photos/200",
},
];
export default {
data() {
return {
columns,
tableData,
};
},
};
</script>
When you use antd's Table, customRender should return a vnode but not a string, so you can try it like this
columns: [
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender () {
return <div id="foo">bar</div>
}
}
]

Vue.js vuetify i18n : How to translate dynamically the Toolbar items?

I don't have any problem in localizing the components and views strings but I am lock into finding a way to localize dynamically the Toolbar items ( and of course the same items in the navigation drawer..
Currently they are displayed in App.vue as menuItems[i].title
<v-toolbar-items class="hidden-xs-only">
<v-btn flat :to="menuItems[0].link">
<v-icon left>{{ menuItems[0].icon }}</v-icon>
<span>{{ menuItems[0].title }}</span>
</v-btn>
with the script:
<script>
export default {
data () {
return {
appName: 'myAPP',
sideNav: false,
menuItems: [
{ icon: 'home', title: 'Home', link: '/home' },
{ icon: 'info', title: 'About', menu: [{ title: 'Company', link: '/company' }, { title: 'Office', link: '/office' }] },
{ icon: 'people', title: 'Members', menu: [], link: '/members' },
{ icon: 'local_library', title: 'Blog', link: '/blog' },
{ icon: 'local_grocery_store', title: 'Shopping', link: '/shopping' }
]
}
},
methods: {
switchLocale: function (newLocale) {
this.$store.dispatch('switchI18n', newLocale)
}
}
}
</script>
Should I use a computed value ? or use directly $t() in the template ?
feedback, advices and links appreciated
UPDATE
main.js
Vue.filter('translate', function (value) {
if (!value) return ''
value = 'lang.views.global.' + value.toString()
return i18n.t(value)
})
locales/i18n/en_US
{
"views": {
"global": {
"Home": "Home",
"Section1": "Section 1",
..
Vue provides filter to help us to format the common text.
So I think it will be one of your choices.
You can click above link to follow the guide to set up your filters.
Edit:
I just realized Vue-filters should not be dependent on this context as the Vue author said. So updated my answer as below:
Then the codes will be like below:
// create vue-i18n instance
const i18n = new VueI18n({
locale: getDefaultLanguage(),
messages: langs
})
// create global filter
Vue.filter('myLocale', function (value) {
return i18n.t(value)
})
In your views or components:
<template>
<v-toolbar-items class="hidden-xs-only">
<v-btn flat :to="menuItems[0].link">
<v-icon left>{{ menuItems[0].icon }}</v-icon>
<span>{{ menuItems[0].title | myLocale }}</span>
</v-btn>
</template>
<script>
export default {
data () {
return {
appName: 'myAPP',
sideNav: false,
menuItems: [
{ icon: 'home', title: 'Home', link: '/home' },
{ icon: 'info', title: 'About', menu: [{ title: 'Company', link: '/company' }, { title: 'Office', link: '/office' }] },
{ icon: 'people', title: 'Members', menu: [], link: '/members' },
{ icon: 'local_library', title: 'Blog', link: '/blog' },
{ icon: 'local_grocery_store', title: 'Shopping', link: '/shopping' }
]
}
},
filters: {
myLocaleWhichNotWork: function (value) {
return this.$t(value) // this won't work because filters should not be dependent on this context
}
}
}
</script>