First project in Vue.js. Working with WP REST API.
I am able to get all my posts to show, but as soon as I try to implement the Vue-router my component that shows all the posts, home-post-list, dies at the first v-if="posts" statement.
Clearly, Vue thinks there is no posts so it isn't rendering anything else, but I cannot figure out how to make it recognize posts. I don't get any errors in the console.
WhenI look at the Vue DevTools, I see:
https://www.dropbox.com/s/5441k1kw8ocmzad/Screenshot%202018-05-22%2010.43.24.png?dl=0
So, the router-view appears to be working properly, but props is empty. I thought I was passing props from the main instance to the child component but maybe I am doing something wrong here.
I will show you my current code.
***HomePostList component
const HomePostList = Vue.component('home-post-list', {
props:['posts'],
template: `<div class="cell medium-8">
<div id="all-posts" class="all-posts" v-if="posts">
<div class="grid-x grid-margin-x">
<div class="post medium-6 cell" :class="{'medium-12':index===0}" v-for="(post,index) in posts">
<div class="img-bg" :class="{'first-post':index === 0}" :style="'background-image: url(' + post._embedded['wp:featuredmedia']['0'].source_url + ')'"></div>
<aside class="post-meta grid-x">
<div class="cell small-12">
<h3>{{ post.title.rendered | limitWords(6) }}</h3>
</div>
<div class="cell small-6">
<div class="post-category" v-for="(category,index) in post.cat_name.slice(0,1)">
<a :href="'/category/' + category.slug">{{ category.cat_name }}</a>
</div>
</div>
<div class="cell small-6">
<p><i class="fal fa-calendar-alt"></i> {{ post.date | parseTime }}</p>
</div>
</aside>
</div>
</div>
</div>
</div>`
});
***SinglePost Component
const SinglePost = Vue.component('single-post-template', {
props:['posts'],
template: `<div class="cell medium-8">
<div class="grid-x grid-margin-x">
<p>Single Post here</p>
</div>
</div>`
});
***Routes & Vue Instance
const routes = [
{
path: '/',
component: HomePostList,
props: true
},
{
path: '/post/:postId',
name: 'post',
component: SinglePost
}
];
const router = new VueRouter({
routes
});
new Vue({
el: '#app',
router,
data() {
return{
posts: [],
searchTerm:'',
searchPosts:[],
currentRoute: window.location.pathname
}
},
created (){
var $this = this;
axios
.get(apiRoot + 'posts?_embed')
.then(function (response) {
$this.posts = response.data;
}
)
},
methods: {
loadMorePosts: function(){
var $this = this;
axios
.get(apiRoot + 'posts?_embed')
.then(function (response) {
$this.posts = response.data;
}
)
},
},
computed:{
},
});
***index.php
<?php
/*
Template Name: Front
*/
get_header(); ?>
<!-- Home Page -->
<div class="grid-container">
<div class="grid-x grid-margin-x">
<!-- Main Post Container -->
<router-view></router-view>
<!-- Sidebar -->
<div id="sidebar" class="cell medium-4">
<sidebar-search></sidebar-search>
</div>
</div>
</div>
<?php get_footer();
I got this working by adding posts to the router-view element.
<router-view :posts="posts"></router-view>
Not sure if this is the correct way to do this, but it works.
Related
I'm a bit new to knockout. I'm trying to get a custom component to dynamically load another custom component. I have a variable called location_board that contains html and that html has a custom component in it. . When I use the data-bind="html: location_board" it put the line for the in the dom but it doesn't run the custom component to fill out that node. Note: If I add the npc-widget directly to the template it works. It just doesn't work when it is added though the html binding. From my research I think this means I need to applyBindings on it? I'm not sure how to go about that in this situation though.
Any help is apricated.
Here is my full code for the custom component.
import {Database} from './database.js'
let database = new Database();
import {ResourceManager} from "./resource-manager.js";
let resourceManager = new ResourceManager();
let locationRegister = {
fog_forest: {
name: "The Ghostly Woodland",
image: "url('img/foggy_forest.jpeg')",
description: `
Place holder
`,
location_board: `
<npc-widget id="john-npc" params="id: 1, tree: 'shopkeep', speed: 50"></npc-widget>
<div>In</div>
`
}
};
ko.components.register('location-widget', {
viewModel: function (params) {
let self = this;
self.function = function () {
console.log("Functions!")
}
for(let k in locationRegister[params.id]) {
console.log(k)
this[k] = locationRegister[params.id][k];
}
console.log(this.name)
//return { controlsDescendantBindings: true };
},
template:
`
<div class="row">
<div class="col-lg-12">
<h2 id="title" class="tm-welcome-text" data-bind="html: name"></h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div data-bind="style: { 'background-image': image}" class="location-picture mx-auto d-block">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="location-description mx-auto d-block" data-bind="html: description"></div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-12">
<div id="location_board" data-bind="html: location_board">
</div>
</div>
</div>
`
});
I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
I'm trying to fetch product data from a JSON file, but can't get it to work.
I've tried several things and searched the internet for a solution but none of the examples on the internet equals my situation.
I'm new to both vue and axios, so please excuse my ignorance.
This is what I have so far:
Vue.component('products',{
data: {
results: []
},
mounted() {
axios.get("js/prods.json")
.then(response => {this.results = response.data.results})
},
template:`
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
<div class="">
<div class="mkcenter" style="position:relative">
<a class="item">
<img class="productImg" width="120px" height="120px" v-bind:src="'assets/products/' + product.image">
<div class="floating ui red label" v-if="product.new">NEW</div>
</a>
</div>
</div>
<div class="productItemName" >
<a>{{ product.name }}</a>
</div>
<div class="mkdivider mkcenter"></div>
<div class="productItemPrice" >
<a>€ {{ product.unit_price }}</a>
</div>
<div v-on:click="addToCart" class="mkcenter">
<div class="ui vertical animated basic button" tabindex="0">
<div class="hidden content">Koop</div>
<div class="visible content">
<i class="shop icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
`
})
new Vue({
el:"#app",
});
The json file is as follows
{
"products":[
{
"name": "Danser Skydancer",
"inventory": 5,
"unit_price": 45.99,
"image":"a.jpg",
"new":true
},
{
"name": "Avocado Zwem Ring",
"inventory": 10,
"unit_price": 123.75,
"image":"b.jpg",
"new":false
}
]
}
The problem is only with the fetching of the data from a JSON file, because the following worked:
Vue.component('products',{
data:function(){
return{
reactive:true,
products: [
{
name: "Danser Skydancer",
inventory: 5,
unit_price: 45.99,
image:"a.jpg",
new:true
},
{
name: "Avocado Zwem Ring",
inventory: 10,
unit_price: 123.75,
image:"b.jpg",
new:false
}
],
cart:0
}
},
template: etc.........
As the warnings suggest, please do the following:
Rename the data array from results to products since you are referencing it by the latter one as a name during render.
Make your data option a function returning an object since data option must be a function, so that each instance can maintain an independent copy of the returned data object. Have a look at the docs on this.
Vue.component('products', {
data() {
return {
products: []
}
},
mounted() {
axios
.get("js/prods.json")
.then(response => {
this.products = response.data.products;
});
},
template: `
//...
`
}
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
...
Also, since you're not using CDN (I think), I would suggest making the template a component with a separate Vue file rather than doing it inside template literals, something like that:
Products.vue
<template>
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
<!-- The rest of the elements -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Products',
data() {
return {
products: []
}
},
mounted() {
axios
.get("js/prods.json")
.then(response => {
this.products = response.data.products;
});
}
}
</script>
And then in your main JS file or anywhere else requiring this component:
import Products from './components/Products.vue';
new Vue({
el: '#app',
data() {
return {
//...
}
},
components: {
Products
}
})
<div id="app">
<Products />
</div>
I want the tag that currently says true or false, I want it to say "FREE" if it's free and say "PAID" if it's not.
https://www.eventbriteapi.com/v3/events/search/?location.address=45+Depot+Ave.++Bronx%2C+NY+10457&location.within=50mi&token=6RXWSSZPE4APEYSWTJJF
I'm getting a response from data.events.is_free in the form of a boolean that's true if free and false if not.
I know I have nothing between the v-if tags, but the whole thing goes blank if add anything.
HTML:
<div id="app" class="container">
<h1 id="header" class="title is-1 has-text-centered">Event Lookup for ECH</h1>
<div v-for="event in info">
<h2 class="title is-4" id="eventName">{{ event.name.html }} <span v-if="" class="tag is-success">{{ event.is_free }}</span></h2>
<div id="eventDescription">{{ event.description.text }}</div>
<div id="eventDateTime">{{ event.start.local }} - {{ event.end.local }}</div>
</div>
</div>
Vue / JS
// The plan is to make this more extensive later
// https://www.eventbriteapi.com/v3/events/search/?location.address=45+Depot+Ave.++Bronx%2C+NY+10457&location.within=50mi&token=6RXWSSZPE4APEYSWTJJF
// This is a random address...
const baseUrl = 'https://www.eventbriteapi.com/v3/events/search/?location.address=45+Depot+Ave.++Bronx%2C+NY+10457&location.within=50mi&token=6RXWSSZPE4APEYSWTJJF';
new Vue({
el: '#app',
data () {
return {
info: null
}
},
mounted () {
axios
.get(baseUrl)
.then(response => (this.info = response.data.events))
},
computed () {
isFree
if (response.data.events.is_free == true) {
return true;
}
else {
return false;
}
}
})
https://codepen.io/Mortiferr/pen/qyajJd
Here's a link to my Codepen.
You can use v-if and v-else.
<div id="app" class="container">
<h1 id="header" class="title is-1 has-text-centered">Event Lookup for ECH</h1>
<div v-for="event in info">
<h2 class="title is-4" id="eventName">{{ event.name.html }}
<span v-if="event.is_free" class="tag is-success">Free</span>
<span v-else class="tag is-success">Paid</span>
</h2>
<div id="eventDescription">{{ event.description.text }}</div>
<div id="eventDateTime">{{ event.start.local }} - {{ event.end.local }}</div>
</div>
</div>
You don't need a computed property for this. You can use v-if and v-else.
Alternatively, if you know that you will only have the text "FREE" or "PAID" without any markup needed, you might want to consider just using a ternary expression:
<span class="tag is-success">{{event.is_free ? "FREE" : "PAID"}}</span>
I added a cost class just to make it a little easier to see.
const baseUrl = 'https://www.eventbriteapi.com/v3/events/search/?location.address=45+Depot+Ave.++Bronx%2C+NY+10457&location.within=50mi&token=6RXWSSZPE4APEYSWTJJF';
new Vue({
el: '#app',
data() {
return {
info: null,
isFree: false
}
},
mounted() {
axios
.get(baseUrl)
.then(response => {
this.info = response.data.events;
this.isFree = response.data.events
})
}
})
.cost {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app" class="container">
<h1 id="header" class="title is-1 has-text-centered">Event Lookup for ECH</h1>
<div v-for="event in info">
<!-- Using v-if/else -->
<h2 class="title is-4" id="eventName">{{ event.name.html }}
<span v-if="event.is_free" class="cost tag is-success">FREE</span>
<span v-else class="cost tag is-success">PAID</span>
</h2>
<!-- Using ternary -->
<h2 class="title is-4" id="eventName">{{ event.name.html }}
<span class="cost tag is-success">{{event.is_free ? "FREE" : "PAID"}}</span>
</h2>
<!--<div id="eventDescription">{{ event.description.text }}</div>-->
<div id="eventDateTime">{{ event.start.local }} - {{ event.end.local }}</div>
</div>
</div>
How would I show line space in vue.js. Right now everything is after each other....
Already tried this:
https://laracasts.com/discuss/channels/vue/vuejs-how-to-return-a-string-with-line-break-from-database
But nothing seems work. Trying this for 3 days now -_-.
I'm using Vue.js 1.0 and browserify.
Thanks a lot!
--EDIT--
<template>
<div>
<bar :title="title" />
<div class="Row Center">
<div class="Message Center" v-if="!loading">
<div class="Message__body" v-if="messages">
<div class="Message__item__body" v-for="message in messages" v-link="{ name: 'Message', params: { message: message.slug }}">
<div class="Message__item__body_content">
<p class="Message__title">{{ message.subject }}</p>
</div>
<div class="Message__item__body_content">
<p>Reacties: {{ message.totalReactions }}</p>
</div>
<div class="Message__item__body_content">
<p>Door: {{ message.user.name }} {{ message.user.last_name }}</p>
</div>
</div>
<pagination :last-page="lastPage" :page="page" :name="Message" />
<p v-if="noMessages" class="Collection__none">Er zijn momenteel geen berichten voor het topic {{ topic.name }}.</p>
</div>
</div>
<div class="Loader" v-if="loading">
<grid-loader :loading="loading" :color="color" :size="size" />
</div>
</div>
<div class="Row center" v-if="!loading && page == 1 && topic">
<div>
<button type="submit" class="Btn Btn-main" v-link="{ name: 'NewMessage', params: { topic: topic.slug }}">Nieuw bericht</button>
</div>
</div>
</div>
</template>
<script>
import Bar from '../Shared/Bar.vue';
import Pagination from '../Shared/Pagination.vue';
import Topic from '../../Services/Topic/TopicService';
import { GridLoader } from 'vue-spinner/dist/vue-spinner.min.js';
export default {
components: { Bar, Pagination, GridLoader },
data () {
return {
title: 'Berichten',
messages: [],
topic: null,
noMessages: false,
loading: false,
color: "#002e5b",
page: 1,
lastPage: 1,
}
},
route: {
data ({ to }) {
this.loading = true;
this.page = to.query.page || 1;
Topic.show(this.$route.params.topic, this.page)
.then((data) => {
this.topic = data.data.topic;
if(!data.data.messages.data.length == 0) {
this.messages = data.data.messages.data;
this.lastPage = data.data.messages.last_page;
} else {
this.noMessages = true;
}
this.loading = false;
});
}
}
}
</script>
When I do it like this:
<div class="Message__body__message">
<p>{{ message.message.split("\n"); }}</p>
</div>
It only adds comma's.
--EDIT--
Set container white-space style to pre-line, as in:
<div style="white-space: pre-line;">{{textWithLineBreaks}}</div>
When you split the message, you get multiple data items, which you should handle with a v-for.
But also see LMK's answer wherein you don't have to split the message.
new Vue({
el: '#app',
data: {
message: `this is a message
it is broken across
several lines
it looks like a poem`
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<div id="app">
<template v-for="line in message.split('\n')">{{line}}<br></template>
</div>
You have to transform your data before rendering it with Vue.
const lines = stringWithLineBreaks.split('\n')
// then render the lines
I can give a more specific answer if you share the code you're working with.