I am building a post website in vue.
I want to implement the functionality so that people can see how many views ('vues') the post got.
Every time someone clicks on a post, the view count should increase by 1.
I use firebase as my backend.
This is my simplified code based on my question:
<template>
<h1>{{ title }}</h1>
<p>This page has {{ views }}</p>
</template>
<script>
export default {
setup() {
const title = "Blog 1"
let views = 0
return { title, views }
}
};
</script>
Related
I'm storing nav items in my Vuex store and iterating over them for conditional output, in the form of a Vue/Bulma component, as follows:
<b-navbar-item
v-for='(obj, token) in $store.state.nav'
v-if='privatePage'
class=nav-link
tag=NuxtLink
:to=token
:key=token
>
{{obj.text}}
</b-navbar-item>
As shown, it should be output only if the component's privatePage data item resolves to true, which it does:
export default {
data: ctx => ({
privatePage: ctx.$store.state.privateRoutes.includes(ctx.$route.name)
})
}
The problem I have is when I run the dev server (with ssr: false) the component doesn't show up initially when I navigate to the page via a NuxtLink tag. If I navigate to the page manually, or refresh it, the component shows.
I've seen this before in Nuxt and am not sure what causes it. Does anyone know?
recommendation :
use mapState and other vuex mapping helper to have more readable code :).
dont use v-for and v-if at the same element
use "nuxt-link" for your tag
use / for to (if your addresses dont have trailing slash)
<template v-if='privatePage'>
<b-navbar-item
v-for='(obj, token) in nav'
class=nav-link
tag="nuxt-link"
:to="token" Or "`/${token}`"
:key="token"
>
{{obj.text}}
</b-navbar-item>
</template>
and in your script :
<script>
import {mapState} from 'vuex'
export default{
data(){
return {
privatePage: false
}
},
computed:{
...mapState(['privateRoutes','nav'])
},
mounted(){
// it's better to use name as a query or params to the $route
this.privatePage = this.privateRoutes.includes(this.$route.name)
}
}
</script>
and finally if it couldn't have help you , I suggest to inspect your page via dev tools and see what is the rendered component in html. it should be an <a> tag with href property. In addition, I think you can add the link address (that work with refresh and not by nuxt link) to your question, because maybe the created href is not true in navbar-item.
NOTE: token is index of nav array . so your url with be for example yourSite.com/1.so it's what you want?
This question has been answered here: https://stackoverflow.com/a/72500720/12747502
In addition, the solution to my problem was a commented part of my HTML that was outside the wrapper div.
Example:
<template>
<!-- <div>THIS CREATES THE PROBLEM</div> -->
<div id='wrapper'> main content here </div>
</template>
Correct way:
<template>
<div id='wrapper'>
<!-- <div>THIS CREATES THE PROBLEM</div> -->
main content here
</div>
</template>
So after following a beginner Vue tutorial to setup a Todo app, I decided to try to adapt some parts of it for a website I'm trying to make. What I'm stuck on is that despite everything saying my for-loop is supposed to work, it doesn't.
The project itself was created using the vue-cli, and most of the code copy-pasted from the tutorial. (which is working fine with its own for-loop)
It seems like the data might be not passed onto the template maybe?
I have tried:
having the info inside the props and data sections
passing whole object and only parameters to the template
tried with hard-coded values inside array which is iterated on
(After setting up a new vue-cli project:)
App.vue:
<template>
<div id="app">
<create-section v-on:create-section="addSection" />
<section v-for="section in sections" v-bind:key="section.title" :info="section"></section>
</div>
</template>
<script>
import CreateSection from "./components/CreateSection";
import Section from "./components/Section";
export default {
name: "App",
components: {
CreateSection,
Section
},
data() {
return {
sections: []
};
},
methods: {
addSection(section) {
this.sections.push({
title: section.title,
description: section.description
});
console.log(
"Added to sections! : " + section.title + " | " + section.description
);
console.log("Sections length: " + this.sections.length);
}
}
};
</script>
Section.vue
<template>
<div class="ui centered card">
<div class="content">
<div class="header">{{ info.title }}</div>
<div>{{ info.description }}</div>
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: {info: Object},
data() {
return {};
}
};
</script>
Expected result:
Display Section template on the website (after creating it with addSection that another script calls. Not included for brevity)
Actual result:
Nothing is displayed, only a empty tag is added
I believe the problem is that you've called it Section. As <section> is a standard HTML element you can't use it as a component name.
There is a warning built into the library but it seems to be case sensitive, which isn't entirely helpful. Try changing your components section to this:
components: {
CreateSection,
section: Section
},
You should then see the warning.
The fix would just be to call it something else.
This is mentioned in the first entry in the style guide:
https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential
section is an existing HTML5 element, you should name your section component something different.
If you really want to name the component Section, register it as 'v-section'
The problem is that when you do the loop in the <section v-for="section in sections" v-bind:key="section.title" :info="section"></section> the Array sections is not ready, there is nothing there.. so when you add new things to this array you need to trigger (computed prop) to send again the data to the section component.
Aside from the issue with using an existing HTML5 command as a name for your Vue component (you should change that to another name by the way), you should also look into how you declared the props within Section.vue. The code below shows the correct way to do it:
<script type = "text/javascript" >
export default {
props: ['info'],
data() {
return {};
}
};
</script>
The props take in the name of the property being declared from the parent component and should be a string.
Hope this helps.
I am using ionic 2 and am trying to transfer data from one page to another. More specifically one list item on the first page(quickSearch) to another page(quickSearchDetail). I have a picture below to demonstrate this.
When I click on a list it should transfer that data to the next page, however I am having an issue where only the first list item is being transferred irrespective of which item I click (over writing my local storage data).
quickSearch Template list item
<ion-item *ngFor="let item of items" (click)="gotoQuickSearchDetail(item._id)">
<p id="clusterNameFormValue">{{ item.clusterName }}</p>
<p id="currentBUNameFormValue">{{ item.currentBUName }}</p>
<p id="technologyFormValue">{{ item.technology }}</p>
<p id="customerFormValue">{{ item.Customer }}</p>
<p id="clusterHeadNumberFormValue">{{ item.clusterHeadNumber }}</p>
</ion-item>
quickSearch.ts
gotoQuickSearchDetail(){
var clusterName: any = clusterName = document.getElementById("clusterNameFormValue").innerHTML;
var currentBUName: any = currentBUName = document.getElementById("currentBUNameFormValue").innerHTML;
var technology: any = technology = document.getElementById("technologyFormValue").innerHTML;
var customer: any = customer = document.getElementById("customerFormValue").innerHTML;
var clusterHeadNumber: any = clusterHeadNumber = document.getElementById("clusterHeadNumberFormValue").innerHTML;
localStorage.setItem('clustername', clusterName);
localStorage.setItem('currentBUName', currentBUName);
localStorage.setItem('technology', technology);
localStorage.setItem('customer', customer);
localStorage.setItem('clusterHeadNumber', clusterHeadNumber);
this.navCtrl.setRoot(QuickSearchDetail);
}
quickSearchDetail item
<ion-list>
<ion-item>
<p id="clusternameDetail"></p>
<p id="currentBUNameDetail"></p>
<p id="technologyDetail"></p>
<p id="customerDetail"></p>
<p id="clusterHeadNumberDetail"></p>
</ion-item>
</ion-list>
quickSearchDetail.ts
ionViewDidLoad() {
document.getElementById('clusternameDetail').innerHTML = localStorage.getItem('clustername');
document.getElementById('currentBUNameDetail').innerHTML = localStorage.getItem('currentBUName');
document.getElementById('technologyDetail').innerHTML = localStorage.getItem('technology');
document.getElementById('customerDetail').innerHTML = localStorage.getItem('customer');
document.getElementById('clusterHeadNumberDetail').innerHTML = localStorage.getItem('clusterHeadNumber');
}
This is too much to handle :D but, where you are going wrong is using getElementById, and id.. and.. stuff. an ID is unique. And with the *ngFor you are creating more elements with the same ID, this is not allowed. But the browser copes, and gives back the first element it finds with that ID, which explains the behaviour you are seeing.
Another point is the weird usage of localStorage. This does not seem like a use-case for what you are trying to do. I suggest you look at the angular documentation, and specifically at inter component communication.
In short, you probably should create a service, which holds the array list. Then create a navigation parameter on your detail page which will hold the id number to an item in the array list of the service. On that page you can obtain this id and access the service to get the right data from the items.
I could write it all for you, but this is not the right site for that, which will bring me back to my previous mentioned suggestion. Look at angular documentation, to see how you should create a master-detail structure, because getElementById, innerHtml and localStorage is not the way to go
Try to use this
//quickSearch Template list item
<ion-item *ngFor="let item of items" (click)="gotoQuickSearchDetail(item)">
<p id="clusterNameFormValue">{{ item.clusterName }}</p>
<p id="currentBUNameFormValue">{{ item.currentBUName }}</p>
<p id="technologyFormValue">{{ item.technology }}</p>
<p id="customerFormValue">{{ item.Customer }}</p>
<p id="clusterHeadNumberFormValue">{{ item.clusterHeadNumber }}</p>
</ion-item>
//quickSearch.ts
gotoQuickSearchDetail(item: any){
var clusterName: any = clusterName = item.currentBUName;
var currentBUName: any = currentBUName = item.currentBUName;
var technology: any = technology = item.technology;
var customer: any = customer = item.Customer;
var clusterHeadNumber: any = clusterHeadNumber = item.clusterHeadNumber;
localStorage.setItem('clustername', clusterName);
localStorage.setItem('currentBUName', currentBUName);
localStorage.setItem('technology', technology);
localStorage.setItem('customer', customer);
localStorage.setItem('clusterHeadNumber', clusterHeadNumber);
this.navCtrl.setRoot(QuickSearchDetail);
You have loop for items. In that case each items contain same id. For this reason when you try to find a value by element id it always gives you 1st item.
You just need to use navParams . You have a very seriously creative solution but the answer is very simple. For my answer I am going to remove all the ID's , add them back if you need it for styles. Also one thing to note, try not to approach angular with a jquery mindset, because they are 2 different animals.
quickSearch Template list item
<ion-item *ngFor="let item of items">
<p>{{ item.clusterName }}</p>
<p>{{ item.currentBUName }}</p>
<p>{{ item.technology }}</p>
<p>{{ item.Customer }}</p>
<p>{{ item.clusterHeadNumber }}</p>
</ion-item>
So there is your list wrapper in a *ngFor . Your assignment of item is all you need. That item holds all of the data that you want on the next page. It is your "package" ( if you will ). Now all you want to do is pass the "package" to the next page. And we want the passing to start when it is clicked.
Therefore we pass it on the (click) event.
(click)="gotoQuickSearchDetail(item)"
So now we just need to add the method in our Component file, and then with the power of routing in ionic 2 we just send it to the next page.
gotoQuickSearchDetail(item){
this.navCtrl.push(QuickSearchDetail, item); // Notice the 2nd parameter
}
The 2nd parameter passes the item to the page declared in the 1st parameter.
So now you are going to the page QuickSearchDetail and you are passing your "package" item along for the ride.
Now when you get to the next page you need to tell that page .. "Hey i have "package" called item and it has some data.
So in your QuickSearchDetail Component you can add this.
import { NavController, NavParams } from 'ionic-angular';
#Component({
selector: '...',
templateUrl: '...html'
})
export class QuickSearchDetailPage {
itemDetails: any;
constructor(public navCtrl: NavController, public navParams: NavParams){
this.itemDetails = this.navParams.data;
// All the data is now held in the itemDetails variable.
console.log(this.itemDetails);
}
}
You now have assigned the "package" holding the clicked item's data to the variable itemDetails which was "passed" (by default ) to the navParams.data. You can now use the itemDetails in your view.
{{ itemDetails.PROPERTYHERE }}
BONUS:
You can get this stuff for free when you use your terminal to generate a page.
>_ ionic generate page QuickSearchDetail
Also the navCtrl has a third property which has options for animation. Which is fun.
this.navCtrl.push(QuickSearchDetail, item, {
animate: true,
direction: 'forward',
mode: 'ios'});
Read more about the navCtrl here
And read more about the navParams here
Add all data to your Click function and in .ts
gotoQuickSearchDetail(data:any,datab:any.........){
navpushhere(SearchPage,{firstPassed:data,secondpassed:datab}); }
I've been looking for this question for a while and still not quite clear. What approaches you use to build templates for Django application using Reacjs? How do you show different menus for authorized and not authorized users? Is it possible to keep html layout in .html file and just render Reactjs components in required places? How to communicate between those components?
Here's for example:
I have a page like this, with menu, and list of items:
<body>
<div>
{% if not user.authenticated %}
Login
{% else %}
Logout
Add item
{% endif %}
</div>
<ul>
<li>One</li>
<li>Two</li>
</ul>
</body>
If user is not authorized i show a button "Login" in it, otherwise there are two buttons: "Logout" and "Add item". When i click "Add item" button i need to open modal window with form. On form submit i want to append item to <ul> list. This is just example, page can contain a lot of html markup. The questions above.
Please ask in comments if my question is not clear.
I think you are a bit confused, if you are using react ( or any other js framework for single page app) you shouldn't use the django template to render some part ( even though you can ). The logic to show or not the login should be in the react template, not in the django template. For example:
Django template
<head>
<script>
var userInfo = {
authenticated : {% user.authenticated %}
// add anyother user info
}
</script>
<head>
<body>
<div id="app"/>
</body>
JS application:
var React = require("react");
var Component = require("./components/Login");
function render(element, id) {
React.render(<Login/>, document.getElementById(id));
}
render(Component, "app");
Login:
var React = require("react");
var Login = React.createClass({
renderLogin: function () {
var loginButton;
if (userInfo.authenticated) {
loginButton = <LogoutButton />;
} else {
loginButton = <LoginButton />;
}
return loginButton;
}
render: function() {
return (
<div className="Login">
{renderLogin}
</div>
</div>
);
}
});
module.exports = Login;
I wound't just access the global variable from any react component, rather I would access it from the root componenent and then propagate it down to the children components by means of props or context.
I'm trying to figure out how to implement the load more functionality like Telescope. This is what I have originally:
// Iron Router
Router.route('/posts', {
name: 'posts.index',
waitOn: function () {
return Meteor.subscribe('posts', Session.get('postsLimit');
},
data: function () {
return { posts: Posts.find({}, { sort: { createdAt: -1 } }) };
},
action: function () {
this.render();
}
});
// client/views/posts_list.html
<ul>
{{#each posts}}
<li>{{ title }}</li>
{{/each}}
</ul>
<a href"#" class="load-more">Load more</a>
// client/views/posts_list.js
var POSTS_INCREMENT = 3;
Session.setDefault('postsLimit', POSTS_INCREMENT);
Template.PostsIndex.events({
'click .load-more': function (e, tmpl) {
Session.set('postsLimit', Session.get('postsLimit') + POSTS_INCREMENT);
return false;
}
}
});
It makes sense that Meteor will rerender the list when the postsLimit changes. I'm just curious how Telescope did it without re-rendering the list and only render the new posts. From what I see from the code, instead of storing the limit in the Session, the author uses the route top/:limit? and instead of using waitOn, they use onBeforeAction. It's hard to pinpoint which part of the code helps prevent re-rendering the list. Could someone please help explain in detail how they did it?
The part that triggers the re-rendering is actually waitOn. By using waitOn, you're telling Iron Router to redirect you to the loading template while you wait, which is what triggers the re-rendering and sticks you back up at the top of the page.
This is waitOn's job, and it works great when going from page to page, but is obviously not ideal when re-rendering the same page.
By the way, note that the new subscriptions option can also trigger the same behavior (if you've set a global loading template).
So this is why we're using onBeforeAction in this specific case. This pattern is explained in more details in Discover Meteor, by the way.
Don't know if this is helpful but to load more posts all you have to do is add {{> UI.dynamic template=postsLoadMore}} in the postList template.
<template name="posts_list">
{{> UI.dynamic template=postsListIncoming data=incoming}}
<div class="posts-wrapper grid grid-module">
<div class="posts list">
{{#each posts}}
{{> UI.dynamic template=post_item}}
{{/each}}
</div>
</div>
{{> UI.dynamic template=postsLoadMore}}
</template>