I am trying to create Vue elements using template elements in the body of the HTML. It was working pretty well until I tried to use two of them together.
<template id="root-template">
<div>
<app-navbar targetId="#app-sidenav" />
<app-header />
</div>
</template>
For some reason, commenting out app-navbar causes app-header to be displayed, however if app-navbar is displayed, app-header is not being displayed at all! This one has completely stumped me.
const Navbar = Vue.component("app-navbar", {
name: "app-navbar",
template: "#navbar-template",
props: {
targetId: {
type: String,
default: "#app-sidenav"
}
},
mounted() {
this.sidenav = M.Sidenav.init(document.querySelector(this.targetId));
},
});
const Header = Vue.component("app-header", {
name: "app-header",
template: "#header-template"
});
const AppRoot = Vue.component("app-root", {
template: "#root-template",
components: {
Navbar,
Header
},
});
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {}
});
const app = new Vue({
el: "#app",
store,
render: h => h(AppRoot)
});
<script src="https://unpkg.com/vuex#3.1.2/dist/vuex.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons&type=.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<ul class="sidenav" id="app-sidenav">
<li>Sass</li>
<li>Components</li>
<li>Javascript</li>
<li>Mobile</li>
</ul>
<div id="app" />
<template id="navbar-template">
<nav>
<div class="nav-wrapper container">
Logo
<a href="#" data-target="app-sidenav" class="sidenav-trigger">
<i class="material-icons">menu</i>
</a>
<ul class="right hide-on-med-and-down">
<li>Sass</li>
<li>Components</li>
<li>Javascript</li>
<li>Mobile</li>
</ul>
</div>
</nav>
</template>
<template id="header-template">
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<br /><br />
<h1 class="header center orange-text">Todos</h1>
<div class="row center">
<h5 class="header col s12 light">
A modern responsive front-end framework based on Material Design
</h5>
</div>
<div class="row center">
<a href="http://materializecss.com/getting-started.html" id="download-button"
class="btn-large waves-effect waves-light orange">Get Started</a>
</div>
<br /><br />
</div>
</div>
</template>
<template id="root-template">
<div>
<app-navbar target-id="#app-sidenav" />
<app-header />
</div>
</template>
I've also turned this into a Pen. Can anyone tell me why this is happening?
Don't use self-closing tags in regular HTML files.
If you change these lines of HTML:
<app-navbar target-id="#app-sidenav" />
<app-header />
to these:
<app-navbar target-id="#app-sidenav"></app-navbar>
<app-header></app-header>
it will work.
You should put the templates in the functions if you're going with a single file approach. See the below code for an example. The templates aren't parsing properly, because of the self closing syntax, and hence only displaying the one component. When you use template strings you can get the expected display from the templates how you have them now. Just put them inside the component function's template: like so:
const Navbar = Vue.component("app-navbar", {
name: "app-navbar",
template:
`
<nav>
<div class="nav-wrapper container">
Logo
<a href="#" data-target="app-sidenav" class="sidenav-trigger">
<i class="material-icons">menu</i>
</a>
<ul class="right hide-on-med-and-down">
<li>Sass</li>
<li>Components</li>
<li>Javascript</li>
<li>Mobile</li>
</ul>
</div>
</nav>
`,
props: {
targetId: {
type: String,
default: "#app-sidenav"
}
},
mounted() {
this.sidenav = M.Sidenav.init(document.querySelector(this.targetId));
},
});
const Header = Vue.component("app-header", {
name: "app-header",
template:
`
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<br /><br />
<h1 class="header center orange-text">Todos</h1>
<div class="row center">
<h5 class="header col s12 light">
A modern responsive front-end framework based on Material Design
</h5>
</div>
<div class="row center">
<a href="http://materializecss.com/getting-started.html" id="download-button"
class="btn-large waves-effect waves-light orange">Get Started</a>
</div>
<br /><br />
</div>
</div>
`
});
const AppRoot = Vue.component("app-root", {
template:
`
<div>
<!-- Comment out the navbar to see the header. expected behavior is that both ar displayed -->
<app-navbar target-id="#app-sidenav" />
<app-header />
</div>
`,
components: { Navbar, Header },
});
store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {}
});
app = new Vue({
el: "#app",
store,
render: h => h(AppRoot)
});
Remove the templates in your code pen and add this to the JS to see it working. Alternatively you can use the closing tags syntax for your components, but it's more readable and intuitive to use self closing tags and separate your display bits into their functions instead of a large blob of <template></template> tags. Just my opinion of cleaner code, so feel free to use either approach as desired/needed.
Related
I'm making a small app for first time making use of HTML, Javascript and Framework7.
I've installed framework7 and have made the directory and installation of the basic starter
template of files for a blank project...Currently using 'npm start' server, and it returns the default homepage content on the browser. so that's OK...
Right now I only want the 'Calendar/Datepicker' to function. I've gotten the date field to display. But when I try to click it and get the calendar to show, like it does in Framework7 documentation https://v3.framework7.io/docs/calendar.html ,
Nothing comes up. I've now tried multiple times have done just as the guide asked, and there is no response. Is there something extra that I need?
Thanks for any advise.
I've included parts of the code below:
app.js :
import $ from 'dom7'; import Framework7 from 'framework7/bundle';
// Import F7 Styles
import 'framework7/framework7-bundle.css';
// Import Icons and App Custom Styles
import '../css/icons.css'; import '../css/app.css';
// Import Routes
import routes from './routes.js';
// Import Store
import store from './store.js';
// Import main app component
import App from '../app.f7';
var app = new Framework7({
name: 'WeatherStationDat', // App name
theme: 'auto', // Automatic theme detection
el: '#app', // App root element
component: App, // App main component
// App store
store: store,
// App routes
routes: routes,
});
//HERE
var calendar = app.calendar.create({
inputEl: "#calendarinput"
});
home.f7 :
<template>
<div class="page" data-name="home">
<!-- Top Navbar -->
<div class="navbar navbar-large">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="left">
<a href="#" class="link icon-only panel-open" data-panel="left">
<i class="icon f7-icons if-not-md">menu</i>
<i class="icon material-icons if-md">menu</i>
</a>
</div>
<div class="title sliding">WeatherStationDat</div>
<div class="right">
<a href="#" class="link icon-only panel-open" data-panel="right">
<i class="icon f7-icons if-not-md">menu</i>
<i class="icon material-icons if-md">menu</i>
</a>
</div>
<div class="title-large">
<div class="title-large-text">WeatherStationDat</div>
</div>
</div>
</div>
<!-- Toolbar-->
<div class="toolbar toolbar-bottom">
<div class="toolbar-inner">
Left Link
Right Link
</div>
</div>
<!-- Scrollable page content-->
<div class="page-content">
<div class="block block-strong">
<p>Here is your blank Framework7 app. Let's see what we have here.</p>
</div>
<div class="block block-strong">
<p>enter date</p>
<div class="list no-hairlines-md">
<ul>
<li>
<div class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<!-- HERE -->
<input type="text" placeholder="Your birth date" readonly="readonly" id="calendarinput"/>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
export default () => {
return $render;
}
</script>
Ok I figured out a way to get it to work. found out how to do it while debugging another field. I had assumed that because 'var app = new Framework7' code was present in app.js, that's where to put the calendar.create() code. But actually when that code is put in section of home.f7 file, inside 'export default' and 'on pageInit', the
calendar pops up.
notice, $f7.calendar.create() is used in this file, not app.calendar.create(). there actually was one documentation that showed that being done, but I missed it before.
The home.f7 file is used here; I tried putting below script code in app.js
but there was no response from the calendar field.
This is the HTTP that goes in the content section of the home.f7 page:
<!--CALENDAR-->
<div class="block block-strong">
<p>ENTER DATE</p>
<div class="list no-hairlines-md">
<ul>
<li>
<div class="item-content item-input">
<div class="item-inner">
<div class="item-input-wrap">
<input type="text" placeholder="Enter date of record" readonly="readonly" id="calendarinput"/>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
At the end of the file there is a section, This code goes in there:
<script>
export default (props, { $, $f7, $on }) => {
$on('pageInit', () => {
let calendarDefault;
calendarDefault = $f7.calendar.create({
inputEl: '#calendarinput',
});
})
return $render;
}
</script>
I have a problem. It's my first Vue.js project and I need help to solve the following problem. First I get a response from my API, then I get a list of projects and I want to find the project with the same ID as the url parameter. When I try to open the view my console logs the following error:
TypeError: Cannot read property 'title' of undefined
However, it then renders the right project into the template.
Code:
<template>
<div id="wrapper">
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"
integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA=="
crossorigin="anonymous"
/>
<Sidebar></Sidebar>
<div id="content">
<Navbar></Navbar>
<div id="headline">
<ul>
<li>
<h1>Projekt Details</h1>
<Popup></Popup>
</li>
</ul>
</div>
<div id="grid" class="module-grid module-grid-2">
<div class="card">
<div class="card-head">
<div>
<h3>Meta Daten</h3>
</div>
<div></div>
</div>
<div class="card-body">
<ul v-if="filtered_projects != null">
<li>
<div class="list-info">
<p>Projektnummer: {{ filtered_projects.id }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>Autor: {{ filtered_projects.author }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>Firma: {{ filtered_projects.company }}</p>
</div>
</li>
<li>
<div class="list-info">
<p>
Erstellt am:
{{
new Date(filtered_projects.created_at)
.toLocaleString()
.split(",")[0]
}}
</p>
</div>
</li>
<li>
<div class="list-info">
<p>
Letzte Änderung am:
{{
new Date(filtered_projects.updated_at)
.toLocaleString()
.split(",")[0]
}}
</p>
</div>
</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-head">
<div>
<h3>Projekt Übersicht</h3>
</div>
<div></div>
</div>
<div class="card-body">
<form class="edit-form" #submit.prevent="submitProject()">
<label for="title">Überschrift*</label>
<input
v-model="filtered_projects.title"
name="title"
id="title"
class="input"
type="text"
required
maxlength="16"
/>
<label for="text">Text*</label>
<textarea
v-model="filtered_projects.text"
name="text"
id="text"
class="input"
type="text"
required
rows="6"
/>
<label for="finish">Abgeschlossen</label>
<input
v-model="filtered_projects.finish"
name="finish"
id="finish"
class="input w-auto"
type="checkbox"
/>
<button v-if="state.user_info.id === filtered_projects.author || state.user_info.admin === true" type="submit" class="second-btn btn">
Aktualisieren
</button>
</form>
<button v-on:click="deleteProject()" v-if="state.user_info.id === filtered_projects.author || state.user_info.admin === true" class="btn delete-btn">Löschen</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { computed, reactive } from "vue";
import { useStore } from "vuex";
import { useRoute, useRouter } from "vue-router";
import Sidebar from "../components/Sidebar.vue";
import Popup from "../components/Popup.vue";
import Navbar from "../components/Navbar.vue";
export default {
name: "ProjectDetails",
components: {
Sidebar,
Popup,
Navbar,
},
setup() {
const store = useStore();
const route = useRoute();
const router = useRouter();
store.dispatch("company_projects/getProjectList");
store.dispatch("user_info/getUserInfo");
store.dispatch("companies/getCompaniesList");
const state = reactive({
query: route.params.id,
company_projects: computed(
() => store.getters["company_projects/getProjectlist"]
),
user_info: computed(
() => store.getters["user_info/getUserInfo"]
),
companies: computed(
() => store.getters["companies/getCompanieslist"]
),
user_auth_data: computed(
() => store.getters["auth/getAuthData"]
),
});
const filtered_projects = computed(() => state.company_projects.find(obj => {
return obj.id == parseInt(state.query)
}))
async function submitProject() {
await store
.dispatch("company_projects/submitProject", {
id: state.company_project.id,
company: state.company_project.company,
author: state.company_project.author,
title: state.company_project.title,
text: state.company_project.text,
finish: state.company_project.finish,
})
.catch((err) => {
console.log(err);
});
}
async function deleteProject() {
await store
.dispatch("company_projects/deleteProject", {
id: state.company_project.id,
})
.catch((err) => {
console.log(err);
});
router.push("/project");
}
return {
state,
submitProject,
deleteProject,
filtered_projects,
route,
};
},
};
</script>
Do you know a way to solve it better than me?
Thank you very much.
Since you are dispatching the company_projects/getProjectList action (which I suppose is responsible for fetching data from the server) directly in the setup, it is very likely that at the moment your component will render for the 1st time, the company_projects/getProjectList getter (and consequently the state.company_projects computed) will return an empty array and thus filtered_projects is undefined (find)
Which means the part of your template which needs filtered_projects to have a value should be protected with v-if
<form class="edit-form" #submit.prevent="submitProject()" v-if="filtered_projects">
My question might not match to the problem that I am facing, So, I will explain it in detail here.
This is code for SeparateTechnology.vue. I have removed a few methods to make code shorter.
<template>
<div>
<div v-for="mss in ms" :key="mss.name">
<section class="container shadow-box techno-box" v-if="mss.name==selected">
<p>Review the software criteria below and ensure that you complete each section</p>
<h4>{{mss.name}}</h4>
<div class="row shadow-box">
<div class="col-sm-12 col-lg-6">
<p>Select all that apply</p>
<div class="dropdown">
<input
v-model.trim="inputValue"
class="dropdown-input"
type="text"
placeholder="Search for your Solution"
/>
<div class="dropdown-list" v-show="selected">
<div
v-show="itemVisible(item)"
v-for="item in mss.subMenu"
:key="item"
class="dropdown-item"
>
{{ item }}
<button
class="button button-mini button-dark button-rounded itemLabel"
#click="selectSolution(item,mss)"
>
<i class="icon-line-plus"></i>
</button>
</div>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-6 rightList">
<div class="dropdown-list" v-show="selected" v-if="mss.selected_solutions!=''">
<div v-for="item in mss.selected_solutions" :key="item" class="dropdown-item">
{{ item }}
<button
class="button button-mini button-dark button-rounded deleteLabel"
#click="deleteSelectedItem(item,mss)"
>
<i class="icon-line-minus"></i>
</button>
</div>
</div>
<div v-else>
<div class="style-msg errormsg">
<div class="sb-msg">
<i class="icon-remove"></i>You have not selected any solutions.
</div>
</div>
</div>
<button
class="button button-mini"
#click="clearUserOptions(mss)"
v-if="mss.selected_solutions.length > 1"
>
<i class="icon-line-cross"></i>Clear all selection
</button>
</div>
<div style="padding:20px;"></div>
</div>
<div class="row">
<div class="col-sm-12 col-md-3 inputTitle">
<h5>Don't see it in the list above:</h5>
</div>
<input
class="col-sm-12 col-md-6"
type="text"
v-model="value"
#keydown.enter="getUserSolution(value,mss)"
placeholder="Enter your solution here.. "
/>
</div>
<div style="padding:20px;"></div>
<div class="row shadow-box">
<h5
class="col-sm-12"
>Identify how the software solution is leveraged within your organization</h5>
<div
v-for="item in mss.selected_solutions"
:key="item"
class="clearfix col-sm-12 col-md-6"
style="padding:20px"
>
<span v-if="mss.isDifferent=='campaign'">
<div class="card">
<h5 class="card-header">{{item}}</h5>
<CheckBox
:groups="campaignMangment"
name="campaignMangment"
:value="item"
classProp="col-sm-12"
#clicked-show-detail="clickedShowDetailModal"
/>
{{item}} and {{productSelected}}
</div>
</span>
</div>
</div>
<button class="btn btn-primary" #click="postUserDetails(mss)">Submit</button>
</section>
</div>
</div>
</template>
<script>
import CheckBox from "../../../components/checkbox/Checkbox";
export default {
name: "technology",
data() {
return {
usageValue: "",
value: "",
campainCheckedNames: [],
checked: "",
productSelected: [],
checkedValues: "",
exists: null,
inputValue: "",
campaignMangment: [
"Business to Customer (B2C)",
"Business to Business (B2B)",
"Customer to Customer (C2C)",
"Customer to Business (C2B)",
"E-Government",
"M-Commerce",
],
};
},
props: ["ms", "selected"],
//method to show all the solutions that contains the user
methods: {
clickedShowDetailModal: function (campainCheckedNames) {
console.log(campainCheckedNames);
this.productSelected.push(campainCheckedNames);
},
},
components: {
CheckBox,
},
};
</script>
This is CheckBox.vue
<template>
<div class="row col-mb-0">
<div
:class="classProp + ' checkbox-margin'"
v-for="singleSelector in groups"
:key="singleSelector"
>
<div>
<input
:id="singleSelector+groupId"
:value="singleSelector"
class="checkbox-style"
type="checkbox"
v-model="checkedValues"
#change="showDetailModal"
/>
<label :for="singleSelector +groupId" class="checkbox-style-3-label">{{singleSelector}}</label>
</div>
</div>
</div>
</template>
<script>
let groupId = 0;
export default {
props: {
groups: Array,
name: String,
classProp: String,
value: String,
},
data() {
return {
groupId: groupId++,
checkedValues: [],
inputs: {},
};
},
methods: {
showDetailModal: function (e) {
this.$set(this.inputs, this.value, e.target.value);
this.$emit("clicked-show-detail", this.inputs);
},
},
};
</script>
<style scoped>
.checkbox-margin {
margin-bottom: 10px;
}
</style>
Right Now, the line {{item}} and {{productSelected}} prints output like below as shown in screenshot
.
Problem: On every click/unclick of checkbox it adds an item to an array and not in the format I want. and if I select the checkbox on left only, it adds that item on right as well as shown in the screenshot above. It is due to the same array declaration, but I couldn't think more than that.
Expected Output: For every selected item, I want to print the list of selected checkboxes in an array format like below.
"selected_solutions": [{
"name": "Adobe Campaign",
"usage": [ "Business to Customer",...]
}, {
"name": "Marin Software",
"usage": ["M-Commerce",...]
}]
Even small hints or tips would be more than appreciated. I don't mind posting code on gist if required. Thank you.
With checkbox-components you want to emit "event.target.checked" likeso:
this.$emit('input', event.target.checked)
That way it'll behave like a checkbox and you can use a v-model on the parent:
<CheckBox v-model="item.checked" ...>
I do not use v-model inside the component and instead just bind :checked="value", but that might be up to preference. But using both :value and v-model could (should?) cause problems because v-model is actually a :value + emitter under the hood (or so I heard).
Anyway, here is my minimal reusable checkbox-wrapper:
<template>
<input type="checkbox" :checked="value" #change="handleChange" />
</template>
<script>
import Vue from 'vue'
export default Vue.extend({
name: 'MyCheckbox',
props: {
value: { type: Boolean, default: false }
},
methods: {
handleChange(event) {
this.$emit('input', event.target.checked)
}
}
})
</script>
After that is done you can just filter on checked items:
computed: {
checkedSolutions(){
return this.ms[0].selected_solutions
.filter( solution => solution.checked );
}
}
I created a Vue app, where I have a single HTTP request. Once the data returns, I update the profile value. But all components using this value do not get reloaded. Below is the code to better explain what I am trying to accomplish.
I have the main Vue file App.vue:
<template>
<div id="app">
<Navigation />
<Header :profile="profile" />
<About :profile="profile" />
<Services :profile="profile" />
</div>
</template>
<script>
import Navigation from './components/Navigation.vue'
import Header from './components/Header.vue'
import About from './components/About.vue'
import Services from './components/Services.vue'
export default {
name: 'app',
components: {
Navigation,
Header,
About,
Services,
},
data() {
return {
profile: { }
}
},
created() {
this.getUserProfile()
},
methods: {
getUserProfile: async function() {
try {
const response = await fetch('http://localhost:7070/v1/home');
const data = await response.json();
this.profile = data;
} catch (error) {
console.error(error);
}
}
}
}
</script>
<style>
</style>
As you can see, I set the variable profile to empty object at the start. And once the app enters the mounted state, I can the GET request to retrieve the profile data. When I debug, I can clearly see data is not an empty object. The response contains all data.
As you can see from the app file, I import 4 files to add 4 components to the app. All components are done in the same principle.
Here is the navigation.vue content:
<template>
<nav class="navbar navbar-expand-lg navbar-light fixed-top py-3" id="mainNav">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="#page-top">Home</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto my-2 my-lg-0">
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#about">About</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#services">Services</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#career">Career</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
</script>
<style scoped>
</style>
Component header.vue:
<template>
<header class="masthead">
<div class="container h-100">
<div class="row h-100 align-items-center justify-content-center text-center">
<div class="col-lg-10 align-self-end">
<h1 class="text-uppercase text-white font-weight-bold">{{ fullName }}</h1>
<h2 class="text-uppercase text-white font-weight-bold">{{ profession }}</h2>
<hr class="divider my-4">
</div>
<div class="col-lg-8 align-self-baseline">
<p class="text-white-75 font-weight-light mb-5">{{ oneLiner }}</p>
<a class="btn btn-primary btn-xl js-scroll-trigger" href="#about">Find Out More</a>
</div>
</div>
</div>
</header>
</template>
<script>
export default {
props: {
profile: Object
},
computed: {
fullName: function () {
if (typeof(profile) == 'undefined') {
return 'Jernej Klancic';
}
return profile.firstname + ' ' + profile.lastname;
},
profession: function() {
if (typeof(profile) == 'undefined') {
return 'Developer';
}
return profile.profession;
},
oneLiner: function() {
if (typeof(profile) == 'undefined') {
return '';
}
return profile.oneLiner;
}
}
}
</script>
<style scoped>
</style>
Component about.vue:
<template>
<section class="page-section bg-primary" id="about">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 text-center">
<h2 class="text-white mt-0">About</h2>
<hr class="divider light my-4">
<p class="text-white-50 mb-4">{{ aboutMe }}</p>
<a class="btn btn-light btn-xl js-scroll-trigger" href="#services">Expertise</a>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
profile: Object
},
computed: {
aboutMe: function () {
if (typeof(profile) == 'undefined') {
return '';
}
return profile.aboutMe;
}
}
}
</script>
<style scoped>
</style>
Component service.vue:
<template>
<section class="page-section" id="services">
<div class="container">
<h2 class="text-center mt-0">At Your Service</h2>
<hr class="divider my-4">
<div class="row">
<div v-for="skill in skills" v-bind:key="uuid" class="col-lg-3 col-md-6 text-center">
<div class="mt-5">
<i v-bind:class="fontAwesomeDecorator(skill)"></i>
<h3 class="h4 mb-2">{{ skill.type }}</h3>
<p class="text-muted mb-0">{{ skillLevel(skill) }}</p>
</div>
</div>
<div class="col-lg-3 col-md-6 text-center">
<div class="mt-5">
<i class="fas fa-4x fa-laptop-code text-primary mb-4"></i>
<h3 class="h4 mb-2">Other</h3>
<p class="text-muted mb-0">Always eager to learn new language or framework</p>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
profile: Object
},
computed: {
skills: function () {
if (typeof(profile) == 'undefined') {
return [];
}
return profile.favoriteExpertise.proficient.slice(0, 3);
}
},
methods: {
fontAwesomeDecorator: function(skill) {
var style = ['fab', 'fa-4x', 'text-primary', 'mb-4'];
var uppercase = skill.type.toUpperCase();
if (uppercase === 'JAVA') {
style.push('fa-java');
}
if(uppercase === 'JAVASCRIPT') {
style.push('fa-js');
}
if(uppercase === 'ANDROID') {
style.push('fa-android');
}
return style;
},
skillLevel: function(skill) {
switch(skill.rating) {
case 10:
return `Living and breathing ${skill.type} code`;
case 9:
return `${skill.type} marksmen`;
case 8:
return `Bug slayer in the ${skill.type} realm`;
case 7:
return `${skill.type} fanboy`;
case 6:
return `Level ${skill.rating} ${skill.type} wizard`;
case 5:
return `${skill.type} nerd`;
default:
return `${skill.type} motivator stage ${skill.type}`;
}
}
}
}
</script>
<style scoped>
</style>
Can anyone tell what could be wrong? Should I change my approach? Is there a better way to do this in Vue? As said, I retrieve the profile JSON object from the backend. I reassign the profile variable by using this.profile = data; in App.vue. Should this not trigger a reload of data?
You need to access the profile prop using this.profile within the JavaScript portion of you component.
For example, this won't work:
skills: function () {
if (typeof(profile) == 'undefined') {
return [];
}
return profile.favoriteExpertise.proficient.slice(0, 3);
}
Instead of just profile you need to write this.profile.
The ability to drop the this. in the template does not carry over to elsewhere. That's specifically a feature of the templating language. In the <script> section you need to include the this. just like you would in any other JavaScript code. Props, data, computed properties and methods are all accessed as properties of the Vue instance in this way.
I would like to know how to make the contents object visible in the video.vue component.
This is the Video.vue components. This component is where I want to access the content object that is defined in the Home.vue component.
<template>
<div>
<h1>{{title}}</h1>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Video Section'
}
}
}
</script>
This is the Home.vue component:
<template>
<div class="container">
<div class="column is-one-third" v-for="content in contents.results" :content="content" :key="content.id">
<div v-show="loaded" class="loader"></div>
<div class="card" >
<div class="card-image">
<figure class="image">
<img :src="imageUrl + content.backdrop_path" alt="Image">
</figure>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<figure class="image is-25x25">
<img id="poster-image" :src="imageUrl + content.poster_path" alt="Image">
</figure>
</div>
<div class="media-content">
<p id="movie-title" class="title is-4 no-padding">{{content.original_title}}</p>
<p><span class="title is-6"><i class="fas fa-star">{{" " + content.vote_average}}</i></span></p>
<p class="subtitle is-6"><i class="fas fa-calendar-alt">{{" " + content.release_date}}</i></p>
</div>
</div>
<div class="content">
{{ content.overview }}
<div class="background-icon"><span class="icon-twitter"></span></div>
</div>
<div id="footer-card-icons">
<i class="fas fa-info-circle"></i>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
data: () => ({
contents: [],
baseurl: 'https://api.themoviedb.org/3',
apikey: '16667866c29ba1bc29e687b4892b8d5c',
imageUrl: 'https://image.tmdb.org/t/p/w1280',
loaded: true,
}),
created: function(){
this.fetchData();
},
methods:{
fetchData: function(){
console.log('fetch data')
this.$http.get(this.baseurl + '/discover/movie?api_key=' +
this.apikey + '&sort_by=popularity.desc').then(response =>{
this.contents = response.body;
this.loaded = false;
});
}
}
}
</script>
As Emile’s commented, you should use props in order to be able to pass data from parent to child component.
In your Home.vue, add:
<videos :content=“contents”></videos>
But if your contents data type is Array:
<videos v-for=“content in contents” :key=“content.id” :content=“content”></videos>
Notice that if you use v-for to loop a component, you need to add key attribute also.
Finally, in your Video.vue, you need to define the props like below:
<template>
<div>
<p>{{ content.overview }}</p>
</div>
</template>
<script>
export default {
props: [‘content’],
data() {
return {
title: 'Video Section'
}
}
}
</script>
Remember, props are reactive. It will respond to any updates or changes in content.
UPDATE: It seems you have not properly declared your component. See codepen link. Better if you could declare any other components as Single File Components as explained here.