3

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.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Austin Branham
  • 173
  • 3
  • 16

4 Answers4

4

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>
Gander
  • 1,854
  • 1
  • 23
  • 30
  • Gander, thanks for the well thought out answer. You make a great point to consolidate my JSON to one file. Both solutions work for my case, but am going to go with the option to keep all data in one file. Thanks a ton!! – Austin Branham Dec 24 '19 at 02:52
  • Gander, 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. – Austin Branham Dec 07 '20 at 05:03
  • 1
    @AustinBranham I think you should ask a new question (new thread), presenting your current code as an example. And also the code you tried to achieve your new goal. A year has passed, my knowledge has changed, I would like to solve it as a new problem instead of updating the old one, especially since it is already resolved and accepted. – Gander Dec 12 '20 at 09:29
3

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.

Colin
  • 2,428
  • 3
  • 33
  • 48
  • Colin, thanks a ton for the help! I've tried your code above, but it didn't work for me. I understand what you're doing there, just not enough to try and change things around myself to get it to work. How exactly should my JSON file be structure in order for that method to work correctly? Also, I am only getting one warning in the console. This could be possible be contributing? When I am on the work page and I click through to visit a project page, the console is saying: [vue-router] missing param for named route "projectDetails": Expected "name" to be defined. – Austin Branham Dec 12 '19 at 22:46
  • Happy to help. I put together a simple demo of the json find to show you how the JSON should be structured: https://repl.it/@ColinUlin/DelayedGregariousAutoresponder – Colin Dec 13 '19 at 15:24
  • Note that I'm using "require" in the example instead of "import". That's simply due to a repl.it limitation so just ignore it. It's doing the same thing as your `import json from ...` – Colin Dec 13 '19 at 15:25
  • That error also does seem relevant. Can you put `console.log(this.$route.params)` above the return in your `findProject` method and tell me what shows up in your console? – Colin Dec 13 '19 at 15:28
  • So, I put a v-if statement that remedied that error. That console.log returns the correct name for the project. The data is also structured correctly with objects within objects. When I use vue-devtools I can see that the component is returning the correct data based on the name. But it seems that it's not populating the data in the fields correctly. Maybe a problem with my props? I just can't figure it out. Also, when I run ```console.log(projectName)``` within the created lifecycle hook (and I pass in the projectName function, it returns undefined. – Austin Branham Dec 14 '19 at 02:42
  • I zipped my code base here on my g drive. I would honestly be forever grateful if you could take a look at it. I feel like I'm just making a tiny mistake, but can't figure it out. Its like 105mb due to the node modules folder. Thanks for any help in advance Colin! https://drive.google.com/file/d/1YwOoH07SlAPX8R5tu09_0vLPYGQMrxtq/view?usp=sharing – Austin Branham Dec 14 '19 at 02:44
3

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

Community
  • 1
  • 1
Alexis Gatuingt
  • 490
  • 6
  • 20
0

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.

Austin Branham
  • 173
  • 3
  • 16