Vue-router components when using one data set - json

I'm using Vue.js as well as the usual setup that comes with it (vue-router etc). I'm having a problem, using one global data set (JSON output of all projects on a site), to return the data for an individual project when viewing that view. I'm confused to where I am going wrong?
My master page has this simple output:
<div id="app">
<router-view></router-view>
</div>
app.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
import router from './routes.js';
const app = new Vue({
router: router,
data: data
}).$mount('#app');
And my routes.js file has the following:
import VueRouter from 'vue-router';
var routes = [
{
path: '/',
component: require('./views/Projects.vue')
},
{
name: 'project',
path: '/:id',
component: require('./views/Project.vue')
}
]
export default new VueRouter({
routes: routes
});
I have two .vue components; Projects and Project.
Projects.vue looks like the following and seems to work fine:
<template>
<ul>
<li v-for="project in projects">
{{ project.title }}
<router-link :to="{ name: 'project', params: { id: project.id }}">View Details</router-link>
</li>
</ul>
</template>
<script>
module.exports = {
data: function () {
return data
}
}
</script>
However, Project.vue isn't working.
<template>
<div>
<h1>{{ project.title }}</h1>
<router-link to="/">Homepage</router-link>
</div>
</template>
<script>
module.exports = {
data: function () {
var project;
for (var i = 0; i < data.length; i++) {
if (data[i].id == this.$route.params.id) {
project = data[i];
break;
}
}
return {
project: project
}
}
}
</script>
My JSON data is just set in a footer include file.
var data = {"projects":[{"id":1080,"title":"Hopton Yard","name":"hopton-yard","size":null,"img":{"featured":"\/Sites\/hat-projects-vuejs\/site\/assets\/files\/1080\/hat_projects_hoptonyard_exterior-001-1.jpg"}},{"id":1082,"title":"Science Museum Entrances & Supporters\u2019 Centre","name":"science-museum-entrances-supporters-centre","size":null,"img":{"featured":"\/Sites\/hat-projects-vuejs\/site\/assets\/files\/1082\/hat_projects_science_museum.jpg"}},{"id":1084,"title":"Upper & Lower Fosters","name":"upper-lower-fosters","size":null,"img":{"featured":"\/Sites\/hat-projects-vuejs\/site\/assets\/files\/1084\/hat_projects_upper_lower_fosters.jpg"}},{"id":1086,"title":"Jerwood Gallery","name":"jerwood-gallery","size":null,"img":{"featured":"\/Sites\/hat-projects-vuejs\/site\/assets\/files\/1086\/hat_projects_jerwood.jpg"}},{"id":1101,"title":"High House Artists\u2019 Studios","name":"high-house-artists-studios","size":null,"img":{"featured":"\/Sites\/hat-projects-vuejs\/site\/assets\/files\/1101\/hat_projects_high_house_artists_studios.jpg"}},{"id":1115,"title":"Stoke Barn","name":"stoke-barn","size":null,"img":{"featured":"\/Sites\/hat-projects-vuejs\/site\/assets\/files\/1115\/hat_projects_stoke_barn.jpg"}}]}

Your code would work except for one minor mistake. Your global object, data, is not the object you want to iterate over. You want to iterate over data.projects.
var project;
for (var i = 0; i < data.projects.length; i++) {
if (data.projects[i].id == this.$route.params.id) {
project = data.projects[i];
break;
}
}

Related

Import external component and loose reactivity in vue3

I have 2 projects/folders (with Lerna on the root).
The first one is uicomponents with some components and the second one is testing a simple app which uses some component from uicomponents.
I created a simple counter component (Counter.vue) :
<template>
<div>
<h3>Total clicks: {{ count }}</h3>
<div class="button-container">
<button class="inc" #click.prevent="increment">Add</button>
<button class="dec" #click.prevent="decrement">Subtract</button>
</div>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'Counter',
props: {
startingNumber: {
type: Number,
required: false,
default: 0,
},
},
setup(props) {
const count = ref(props.startingNumber);
const increment = () => {
count.value += 1;
alert(count.value);
};
const decrement = () => {
count.value -= 1;
};
return {
count,
increment,
decrement,
};
},
});
</script>
And I import it in my app on a simple page :
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<counter :starting-number="5"></counter>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Counter from '#uicomponents/counter';
export default defineComponent({
name: 'HelloWorld',
components: {
Counter,
},
props: {
msg: {
type: String,
required: false,
default: 'Me',
},
},
});
</script>
Lerna correctly replace all my components path and I retrieve my components Counter in my pages with all HTML. Buttons works well and my alert are displays with the correct value BUT my html are not refreshed.
This text <h3>Total clicks: {{ count }}</h3> stay "Total clicks: 0". My "count" ref is well updated because the alert displayed it correct but not in html.
I have a similar problem with lost reactivity. My setup is a bit different, but in the end it's the same result.
I'm trying to build a small plugin system which loads external components
Roughly I try to do this
// pluginSystem.js is accessible through window.myps
// ...
init(app) {
vueApp = app;
},
// ...
loadPlugin(data) {
vueApp.component(data.component.name, data.component);
}
And my external component looks like this
// main.js
import Counter from './components/Counter.vue';
window.myps.loadPlugin({
component: Counter,
});
Button click in counter, etc. works, console logging is fine as well, but component data is not updated.
I also tried defineComponent and defineAsyncComponent, but as you I had no luck with it...
Try it
import { defineAsyncComponent, defineComponent } from "vue"
components: {
Counter:defineAsyncComponent(() => import("#uicomponents/counter"))
}

How to dynamically change content of component with JSON?

I am creating my design portfolio using Vue CLI 3. The architecture of my website is very simple. I have a home page, about page, work page, and several individual project pages:
Home
About
Work
Project
Project
Project
The work page consists of several links that would click through to the individual project pages. The work component is set up like so:
<template>
<div>
<projectLink v-for="data in projectLinkJson" />
</div>
</template>
<script>
import projectLink from '#/components/projectLink.vue'
import json from '#/json/projectLink.json'
export default {
name: 'work',
data(){
return{
projectLinkJson: json
}
},
components: {
projectLink
}
}
</script>
As you can see, I'm importing JSON to dynamically render the content. Next, the projectLink component can be seen in the code block below. Within this component I am passing a param into <router-link> called projectName
<template>
<router-link :to="{ name: 'projectDetails', params: { name: projectName }}">
<h1>{{ title }}</h1>
</router-link>
</template>
<script>
export default {
name: 'projectLink',
props: {
title: String,
projectName: String
}
}
</script>
My routes.js file is setup like so:
const routes = [
{ path: '/', component: home },
{ path: '/about', component: about },
{ path: '/work', component: work },
{
path: "/work/:name",
name: "projectDetails",
props: true,
component: projectDetails
},
];
and my JSON is like so:
{
"0": {
"title": "test",
"projectName": "test"
}
}
Lastly, my projectDetails component is the component that is where I am having this issue:
<template>
<div>
<div
v-for="(data,index) in projectDetailsJson" v-if="index <= 1">
<h1>{{ data.title }}</h1>
<p>{{ data.description }}</p>
</div>
</div>
</template>
<script>
import json from '#/json/projectDetails.json'
export default {
name: 'projectDetails',
data(){
return{
projectDetailsJson: json
}
},
props: {
description: String,
title: String
}
}
</script>
I am successfully routing to the URL I want, which is /project/'name'. I want to use the projectDetails component as the framework for each of my individual project pages. But how do I do this dynamically? I want to retrieve data from a JSON file and display the correct object from the array based on the name that was passed to the URL. I do not want to iterate and have all of the array display on the page. I just want one project to display.
Quick solution:
projectDetails.vue
<template>
<div>
<div>
<h1>{{ projectDetails.title }}</h1>
<p>{{ projectDetails.description }}</p>
</div>
</div>
</template>
<script>
import json from '#/json/projectDetails.json';
export default {
name: 'projectDetails',
props: {
name: String,
},
data() {
return {
projectDetails: Object.values(json).find(project => project.title === this.name),
};
},
};
</script>
In my opinion, a better solution:
I don't get the idea that you keep project data in 2 separate JSON files. During compilation, both files are saved to the resulting JavaScript file. Isn't it better to keep this data in 1 file? You don't have to use all of your data in one place. The second thing, if you have a project listing then you can do routing with an optional segment, and depending on whether the segment has a value or not, display the listing or data of a particular project. Then you load project data only in one place, and when one project is selected, pass its data to the data rendering component of this project. Nowhere else do you need to load this JSON file.
routes.js
import home from '#/components/home.vue';
import about from '#/components/about.vue';
import work from '#/components/work.vue';
const routes = [
{path: '/', name: 'home', component: home},
{path: '/about', name: 'about', component: about},
{path: '/work/:name?', name: 'work', component: work, props: true},
];
export default routes;
work.vue
<template>
<div>
<project-details v-if="currentProject" :project="currentProject"/>
<projectLink v-else
v-for="project in projects"
v-bind="project"
v-bind:key="project.projectName"
/>
</div>
</template>
<script>
import projectLink from './projectLink';
import projectDetails from './projectDetails';
import json from '#/json/projectLink.json';
export default {
name: 'work',
props: {
name: String,
},
data() {
return {
projects: Object.values(json),
};
},
computed: {
currentProject() {
if (this.name) {
return this.projects.find(
project => project.projectName === this.name,
);
}
},
},
components: {
projectLink,
projectDetails,
},
};
</script>
projectDetails.vue
<template>
<div>
<div>
<h1>{{ project.title }}</h1>
<p>{{ project.description }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'projectDetails',
props: {
project: Object,
},
};
</script>
projectLink.vue (changed only one line)
<router-link v-if="projectName" :to="{ name: 'work', params: { name: projectName }}">
A full working example:
Vue.component("navigation", {
template: "#navigation"
});
const Projects = {
template: "#projects",
props: ["projects"]
};
const Project = {
template: "#project",
props: ["project"]
};
const HomePage = {
template: "#home"
};
const AboutPage = {
template: "#about"
};
const WorkPage = {
data() {
return {
projects: [{
slug: "foo",
name: "Foo",
desc: "Fus Ro Dah"
},
{
slug: "bar",
name: "Bar",
desc: "Lorem Ipsum"
}
]
};
},
props: {
slug: String
},
template: "#work",
components: {
Projects,
Project
},
computed: {
currentProject() {
if (this.slug) {
return this.projects.find(project => project.slug === this.slug);
}
}
}
};
const router = new VueRouter({
routes: [{
path: "/",
name: "home",
component: HomePage
},
{
path: "/about",
name: "about",
component: AboutPage
},
{
path: "/work/:slug?",
name: "work",
component: WorkPage,
props: true
}
]
});
new Vue({
router,
template: "#base"
}).$mount("#app");
ul.nav {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
ul.nav>li {
float: left;
}
ul.nav>li>a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
ul.nav>li>a:hover {
background-color: #111;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.3/vue-router.min.js"></script>
<div id="app"></div>
<script type="text/x-template" id="base">
<div id="app">
<div>
<navigation></navigation>
<router-view></router-view>
</div>
</div>
</script>
<script type="text/x-template" id="navigation">
<ul class="nav" id="navigation">
<li>
<router-link :to="{name: 'home'}">Home</router-link>
</li>
<li>
<router-link :to="{name: 'about'}">About</router-link>
</li>
<li>
<router-link :to="{name: 'work'}">Work</router-link>
</li>
</ul>
</script>
<script type="text/x-template" id="home">
<div id="home">This is Home Page</div>
</script>
<script type="text/x-template" id="about">
<div id="about">This is About Page</div>
</script>
<script type="text/x-template" id="work">
<div id="work">
<project v-if="currentProject" :project="currentProject"></project>
<projects v-else :projects="projects"></projects>
</div>
</script>
<script type="text/x-template" id="projects">
<div id="projects">
<ul>
<li v-for="project in projects" :key="project.slug">
<router-link :to="{name: 'work', params:{ slug: project.slug}}">{{project.name}}</router-link>
</li>
</ul>
</div>
</script>
<script type="text/x-template" id="project">
<div id="project">
<h2>{{project.name}}</h2>
<p>{{project.desc}}</p>
</div>
</script>
Great work thus far, Austin! You're very close having this working. There are a few different ways you could parse out the correct data from your JSON file into the projectDetails component, but I'll just demo my preferred way.
First, you're going to need a bit of vanilla JS to search through your JSON file and return only the row that you want. I would do this as a method since the data isn't going to be changing or requiring the component to re-render. So, after your props, I would add something like this:
methods: {
findProject(projectName) {
return Object.values(json).find(project => project.title === projectName)
}
}
Note that this is going to return the first project that matches the project name. If you have projects with the exact same project name, this won't work.
Next, you'll just need to update the default value of projectDetailsJson to call this method and pass the route's project name. Update data with something like this:
data() {
return {
projectDetailsJson: this.findProject(this.$route.params.name)
}
}
If that doesn't work, we may need to set the projectDetailsJson in the created lifecycle hook, but try the above code first.
If I understood correctly, you want to keep a parent component as a layout for all of your page?
If I always understood correctly, you must use the children property of vuerouter
https://router.vuejs.org/guide/essentials/nested-routes.html
import layout from 'layout';
const projectRoute = {
path: '/project',
component: Layout, // Load your layout
redirect: '/project/list',
name: 'Project',
children: [
{
path: "list", // here the path become /project/list
component: () => import('#/views/project/List'), // load your components
name: "List of project",
},
{
path: "detail/:id",
component: () => import('#/views/project/Detail'),
name: "Detail of project",
}
],
};
So you can create your layout and add everything you want, this will be available on all child components, and you can use $emit, $refs $props ect...
+
You can create an file routes/index.js and create folder routes/modules . Inside this, you can add your routes/modules/project.js and load the modules in routes/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
import projectRoutes from "./modules/project";
const routes = [
projectRoutes,
{
// other routes....
},
]
export default new VueRouter({
routes,
mode: 'history',
history: true,
});
#see the same doc : https://router.vuejs.org/guide/essentials/nested-routes.html
Finally, you just have to do the processing on the layout, and use the props to distribute the values ​​both in detail and in the project list; and use the filter methods described just above
I hope I have understood your request, if this is not the case, let me know,
see you
Edit: Here is a very nice architecture with vue, vuex and vuerouter. maybe inspire you
https://github.com/tuandm/laravue/tree/master/resources/js
For everyone, to take this one step further. How would you show only the projectLinks that match the current URL? So if I have three different JSON projectTypes: design, code, motion. If the URL contains motion in it, how do I filter my projectLink components to show only those that have a matching JSON value of either design, code or motion. Essentially I'm just trying to filter.

Vue.js: Failed to generate render function

I try to run a simple Vue.js, but I constantly receive the following error:
[Vue warn]: It seems you are using the standalone build of Vue.js in
an environment with Content Security Policy that prohibits
unsafe-eval. The template compiler cannot work in this environment.
Consider relaxing the policy to allow unsafe-eval or pre-compiling
your templates into render functions.
main.js:3180 [Vue warn]: Failed to generate render function:
EvalError: Refused to evaluate a string as JavaScript because
'unsafe-eval' is not an allowed source of script in the following
Content Security Policy directive: "default-src 'self'". in
with(this){return _c('div',{attrs:{"id":"app"}})}
(found in )
Yet, I can't understand what is causing this error. I don't seem to use any dynamic templates and everything should be pre-compiled. Here is the code of my application:
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home = Vue.component('home-component', {
render() {
return <div>
<router-link to="/users">Users</router-link>
<router-link to="/about">About</router-link>
</div>
}
})
const Users = Vue.component('users-component', {
render() {
return <p>Users</p>
}
})
const NotFound = Vue.component('not-found-component', {
render() {
return <p>Not Found</p>
}
})
const routes = [
{ path: '/', component: Home },
{ path: '/users', component: Users }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
And here is how I process JavaScript files in my gulpfile:
const paths = {
main: 'web/javascript/src/main.js',
allJs: 'web/javascript/**/*.{js,vue}',
resultJs: 'public/assets/javascript/main.js',
};
gulp.task('scripts', function() {
return browserify(paths.main)
.transform(babelify, { presets: ['es2015'], plugins: ["transform-runtime"] })
.transform(vueify)
.bundle()
.pipe(fs.createWriteStream(paths.resultJs))
}
There are seem to be many similar questions on StackOverflow, but none of them helped me.
The broweser is throwing Content Security Policy (CSP) error, to get around this issue you should consider switching to the runtime-only build, which is fully CSP-compliant.
https://v2.vuejs.org/v2/guide/installation.html#CSP-environments
Also, You have return incorrectly written. It should return jsx in pair of paranthesis (). The code below should help
import Vue from 'vue'
import VueRouter from 'vue-router'
const Home = Vue.component('home-component', {
render(h) {
return(
<div>
<router-link to="/users">Users</router-link>
<router-link to="/about">About</router-link>
</div>
)
}
})
const Users = Vue.component('users-component', {
render(h) {
return(
<p>Users</p>
)
}
})
const NotFound = Vue.component('not-found-component', {
render(h) {
return(
<p>Not Found</p>
)
}
})
const routes = [
{ path: '/', component: Home },
{ path: '/users', component: Users }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')

How to bulid Application template Vue

I'm new to Vue and to ES6
And I did the following:
import Vue from 'vue'
import VueRouter from 'vue-router'
import $ from 'jquery'
Vue.use(VueRouter)
var App = {};
App.template = `
<main-menu></main-menu>
<router-view></router-view>`;
const header = Vue.component('main-menu', require('./components/app/header.vue'));
const facebook = Vue.component('facebook-gif', require('./components/facebook.vue'));
const router = new VueRouter({
routes: [
{
path: '/',
},
{
path: '/facebook',
component: {
facebook: facebook
}
}
]
});
App.app = new Vue({
router,
render (h) {
return h(`div`, App.template)
}
}).$mount('#app');
But what it's nothing render , I just see the main-menu and router-view tags in my broswer...
And when I edit my html and put there this:
<div id="app">
<main-menu></main-menu>
<router-view></router-view>
</div>
I get this error when I'm trying to enter the facebook route:
[Vue warn]: Failed to mount component: template or render function not defined.
and the facebook template is wrapped with template tag and it's inside a vue file
facebook.vue
<template>
<div>
dsfsdsfdsf
<div v-if="showLogin">
<button v-on:click="login">Log In With Facebook</button>
<span v-if="error">{{ error }}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showLogin: true,
error:null,
}
},
methods() {
return {
login() {
}
}
}
}
</script>
But the main-menu component is render...
What is the problem?
EDIT
I downloaded the example like wostex said
I create an App.vue file contain:
<template>
<div id="app">
<main-menu></main-menu>
<router-view></router-view>
</div>
</template>
I edit my html file to contain
I added in app.js
import App from './components/App.vue'
const v = new Vue({
el: "#app",
router,
render: h => h(App)
});
and my html file contain:
<div id="app"></div>
and I get this error:
vue.common.js:436 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <Anonymous>
<App> at /opt/lampp/htdocs/gif/resources/assets/js/components/App.vue
<Root>
it happens because the facebook component, when I'm in the main page I don't see the error, only when I enter the facebook route
It seems your first set up was failing because of what build you we're using. For eg:
App.app = new Vue({
router,
render (h) {
return h(`div`, App.template) // this part can be roughly
// translated to `return h(`div`,
// `<one-component></one-component>
// <router-view></router-view>`)` and I believe
// this failed due to the fact that when you pass
// template string to the render method it needs to
// be compiled and your setup didn't account for that.
}
}).$mount('#app');
Your other set up ( as per suggestion of #wostex ) is much better but I think that here you are missing .$mount('#app') at the end of your Vue initialization. So:
const v = new Vue({
router,
render: h => h(App)
}).$mount('#app')
I looked at Laracasts in Jeffry tutorial about Vue
He did there like that:
const router = new VueRouter({
routes: [
{
path: '/',
},
{
path: '/facebook',
component: require('./components/facebook.vue')
}
]
});
and it work's
So thanks every one that tried to help me

VueJS and a static JSON endpoint without IDs

So I'm trying to create a VueJS application, and I was given a set of JSON objects that are retrievable through a .json endpoint.
I'll call them People. So I get an array of people in this.people after using VueResource.
I'm able to iterate and get all the name displayed on the side, however, since its not an API nor has unique IDs minus their array indexes, I am having trouble trying to narrow down each object and create a single Person view page.
Hence if it was a normal api, I could do '/people/:id', but I can't. I'm also wondering if I may have stored the Prop/Component correctly.
I put together a quick example of how this might work with the Star Wars API.
const Loading = {
template: `<h1>Loading...</h1>`
};
const People = {
props: ["people"],
template: `
<div>
<h1>People</h1>
<ul>
<li v-for="person in people">
<router-link :to='{name: "person", params:{person: person}}'>{{person.name}}</router-link>
</li>
</ul>
</div>
`
};
const PersonDetails = {
props: ["person"],
template: `
<div>
<h1>{{person.name}}</h1>
<div>Height: {{person.height}}</div>
<div>Mass: {{person.mass}}</div>
<div>Hair Color: {{person.hair_color}}</div>
<br/>
<router-link to="people">Back to people</router-link>
</div>
`
};
const routes = [
{ path:"/", component: Loading},
{ path: "/people", name: "people", component: People},
{ path: "/person", name: "person", component: PersonDetails, props: true},
]
const router = new VueRouter({
routes
})
new Vue({
el: "#app",
router,
data:{
people:[]
},
mounted(){
this.$axios.get("https://swapi.co/api/people/")
.then((response) => {
this.people = response.data.results
this.$router.push("people")
})
}
});
Here is the working example.