I'm building some app using Laravel & Vue, and so far so good, but I'm no expert with Vue.
So I have one very "begginers" problem, using live data.
So I want to make button that will check if live data is on or off, and if they turn it on, it must refresh data and set liveData state to true.
For example:
This is my "button" and it's not working as expected, It will change state but data is still no live
<div v-if="liveData">
<div #click="liveData = false">
Turn OFF Live data
</div>
</div>
<div v-else="liveData">
<div #click="liveData = true">
Turn On Live data
</div>
</div>
I have defined state like so:
data() {
return {
liveData: false
}
},
And this is my created() function:
created() {
if(this.liveData){
window.Echo.channel("addOrder").listen(".order-created", (order) => {
this.$store.commit("ADD_ORDER", order);
});
}
this.$store.dispatch("GET_ORDERS");
},
So in this case only button is not working, but if I set state to true it's working perfectly.
What do I need to do here? Do I need to make new function to work or?
Created will only be executed once in your component lifecycle. At this point the value of liveData is always false.
If you click on your "button" the value should change but your code inside of created will not be executed once more.
Instead of created you can use an immediate watcher:
watch: {
liveData: {
immediate: true,
handler(val) {
// your code from created here
}
}
Correct the mistake
<div v-else!="liveData">
<div v-if="liveData">
<div #click="liveData = false">
Turn OFF Live data
</div>
</div>
<div v-else!="liveData">
<div #click="liveData = true">
Turn On Live data
</div>
</div>
or
<div v-else>
Related
I am coding a blog with Nuxt.js, and I am connected with the API of ButterCMS.
I want to get the date (text) of the post, and slice it. My problem is that it return an error : TypeError: null is not an object (evaluating 'document.querySelector(".date").textContent'). When I execute the same code in the JS Console, it is working. I already tried to add a event listener to the load of the page, but it didn't change anything. Here is my code :
document.querySelector(".date").textContent.slice(0, 29);
<template>
<div id="blog-home">
<div class="recent-article-feed">
<div v-for="(post, index) in posts" :key="post.slug + '_' + index">
<router-link :to="'/blog/' + post.slug">
<div class="article">
<div class="dark-window">
<div class="text-box">
<h2 class="title">{{ post.title }}</h2>
<div>
<span class="author">
<i class="fa-solid fa-user"></i> Par Maxime Hamou
</span>
∙
<span class="date">
<i class="fa-solid fa-calendar-days"></i>
{{ post.published }}
</span>
</div>
<p class="description">
{{ post.summary }}
</p>
<p class="read">Lire l'article →</p>
</div>
</div>
</div>
</router-link>
</div>
</div>
</div>
</template>
<style>
#import url("../css/index.css");
#import url("../css/components/recent-article-feed.css");
</style>
<script>
import { butter } from "~/plugins/buttercms";
export default {
data() {
return {
posts: [],
};
},
methods: {
getPosts() {
butter.post
.list({
page: 1,
page_size: 10,
})
.then((res) => {
// console.log(res.data)
this.posts = res.data.data;
});
},
},
created() {
this.getPosts();
},
};
</script>
The first thing is to make sure we understand what the error is trying to tell us.
TypeError: null is not an object (evaluating 'document.querySelector(".date").textContent')
Generally when you see something (like a variable or element) being labeled as null or undefined that means it does not exist. This could be due to a timing issue (being called before it is created/ready) or due to a scope issue (like a variable being called outside of its declared scope/function).
Because you are using an API and template-based code, it is almost certainly a timing issue. You did mention you tried adding an event listener to the page load, however this only works if your element/data is set before the page load, which almost certainly does not happen.
Realistically, the page will always complete its load event first, and then data from the API will be returned. And after all of this, then your template-based code will plug in the data to your page. This can be assumed because your template-based code is using promises (indicated by the .then() method).
So how do you fix this? You have to wait for the element you need to actually exists. The best way I found to do this is with something like a MutationObserver. You can create a function that uses a Mutation Observer on the page and whenever the content of the page changes, it fires some code. Here you can check to see if your date element exists or not. When it does finally exists, this function can return a promise, which allows you to now finally execute some code (like getting the textContent of said element).
So try adding something like this:
// This declares the function to wait for an element to exist
const _ElementAwait = el => {
return new Promise(resolve => {
if(document.querySelector(el)) return resolve(document.querySelector(el));
const observer = new MutationObserver(ml => {
if(document.querySelector(el)) {
resolve(document.querySelector(el));
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
}
// Here we call the function to wait for an element with the class 'date'
_ElementAwait(".date").then(el => {
// Once it does exist, the element is returned and we can get the textContent
el.textContent.slice(0, 29);
});
I just added a setTimeout and it work. Thank you for your help. Here is my new code :
setTimeout(function(){
const date = document.querySelector(".date").textContent.slice(0, 29);
document.querySelector(".date").textContent = "";
document.querySelector(".date").textContent = date;
}, 1000);
EDIT :
I remplaced the setTimeout by butter.page.retrieve directly in the page where I want to execute the script (found in ButterCMS docs). When the page "simple-page" is retrieved, the code that add my script is executed. Here the new code :
butter.page.retrieve("*", "simple-page").then(() => {
const createScript = document.createElement("script");
createScript.src = "../script/post-informations.js";
document.head.appendChild(createScript);
});
Here is a ToDoList from Vue examples.
I want to add some extra features to this small app, e.g. set date for task. Therefore I'd like to show more operations of the task when I click "...".
Below is what I want to avoid, which after clicking another task, the previous click action doesn't be removed:
I try to add a property for each todo, and bind a click function on the "..." (more). Each time click "more", firstly set "isMoreClick" property of all task to false, then toggle the value of "isMoreClick" of current clicked task:
<button class="more"
#click="isMoreClick(todo)"
v-show="!todo.isMoreClick">
</button>
<div class="more-opt" v-show="todo.isMoreClick">
<button class="destroy" #click="removeTodo(todo)"></button>
</div>
...
this.todos.push({
id: todoStorage.uid++,
title: value,
completed: false,
isMoreClick: false // this is what I added
})
...
isMoreClick (todo) {
this.todos.forEach(todo => {
todo.isMoreClick = false
})
todo.isMoreClick = !todo.isMoreClick
}
I think my approach is a little stupid. Is there any better solution? (set a flag symbol?)
You don't say how you're rendering the todo elements. But if you're using a v-for loop, one approach could be
<ul>
<li
v-for="(todo, index) in todos"
:key="index"
>
{{todo.whatever}}
<button
v-if="index !== visibleTodoIndex"
class="more"
#click="visibleTodoIndex = index"
/>
<div
v-else
class="more-opt"
>
<button
class="destroy"
#click="visibleTodoIndex = -1"
/>
</div>
</li>
<ul>
Then just add visibleTodoIndex to the component's data.
It looks to me you need to use a global variable accessible from all todos, not to have a local variable inside each todo and updating it everywhere every time. I recommend using vuex store and updating isMoreClick value via mutations.
I am trying to learn vue.js despite not having any background with javascript. I ran into some code when following a video that was teaching about 'computed', and I tried experimenting on it and had a bit of trouble along the way.
<div id='app'>
<p>Do you see me?</p>
<p v-if="show">Do you also see me?</p>
<button #click="showToggle1">Switch!</button>
</div>
new Vue({
el:'#app',
data:{
show = true;
},
computed:{
showToggle1:function(){
return this.show = !this.show
}
},
methods:{
showToggle2:function(){
this.show = !this.show;
}
});
Basically it's making "Do you also see me?" disappear and appear depending on the value of "show". I know that if you write #click:'showToggle2()' instead of #click:'showToggle1' at the button, the value changes and it works. I'm just having some trouble understanding how computed works and why showToggle1 doesn't change the value of show when I click the button
Some problems.
First, you have syntactical problems. data is an object, so instead of:
data:{
show = true;
}
Should be:
data:{
show: true
}
Next, computed properties are to be used like... properties. For example, like declared in data. So, typicall, you read from them. You don't execute computed properties in #click events. So this code:
<button #click="showToggle1">Switch!</button>
Is not correct. It will error because showToggle1 is not a method, it is, as said, a computed property. What you should have in click is a method, like:
<button #click="showToggle2">Switch!</button>
This will work because showToggle2 is a method. And you should use methods to perform changes.
Not, before going into the last and most tricky part, here's a working demo:
new Vue({
el: '#app',
data: {
show: true
},
computed: {
/*showToggle1: function() {
return this.show = !this.show;
}*/
},
methods: {
showToggle2: function() {
this.show = !this.show;
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id='app'>
<p>Do you see me?</p>
<p v-if="show">Do you also see me?</p>
<hr>
Value of show: {{ show }}<br>
<button #click="showToggle2">Switch2!</button>
</div>
The tricky part is your computed property (which I commented out in the code above):
computed:{
showToggle1:function(){
return this.show = !this.show
}
},
Basically what it is doing is it is automatically negating the value of show whenever it changes.
This happens because the computed property is calculated whenever show updates. And what is happening is:
You initialize data with true (because of data: {show: true}).
The showToggle1 computed auto-recalculates, because it has this.show inside of it (it depends on it).
When recalculating, showToggle1 sets the value of show to false (because of return this.show = !this.show).
That's why show becomes false.
And that's also why whenever you change (even from the method, which is the correct place) the value of show to true it will automatically go back to false. Because any change in show triggers the showToggle1 computed recalculation, which sets show back to false.
In summary:
Use methods to perform changes.
Don't change properties inside computed properties.
I'm trying to bind elements using setAttribute. It works, except it will not allow me to change the value.
Basically I want to pass a value from state as the value in the input.
Currently, the state does NOT update inside the render. It only takes the initial state. In the render, my 'console.log' only fires once.
The correct this.state.answer does appear in componentDidUpdate (and did Mount).
I have put this on JSFiddle https://jsfiddle.net/69z2wepo/91132/
class Hello extends React.Component {
cf = null
state = {answer:''}
componentDidMount(){
this.refs.q1.setAttribute('cf-questions', "How are you?")
this.cf = window.cf.ConversationalForm.startTheConversation({
formEl: this.refs.form,
context: document.getElementById("cf-context"), // <-- bind this to an element instead of html body
flowStepCallback: (dto, success, error) => {
// dto.text contains the value being passed to the form
// State appears in console.log
// dto.text = 'blah' + this.state.answer
// above ONLY passes 'blah'
success()
},
});
}
componentDidUpdate(props) {
this.refs.q1.setAttribute("value", this.state.answer);
}
onChange = e => {
this.setState({ answer: 'X' });
}
render() {
console.log('a change', this.state.answer)
// Only fires once
return (
<div>
<button onClick={this.onChange} className='but'> onChng </button>
<div id="cf-context" >
<form id="form" className="form" ref="form">
<select ref="q1" type="radio" id="links">
<option value="X">X</option>
<option value="Y">Y</option>
</select>
</form>
</div>
</div>
);
}
}
Most probably the issue is within the componentDidMount() method.
I am not sure about what you have been doing. But i guess you are setting the scope of the form Element within those particular div "cf-context".
So even after the state is updated, it takes value 'this' from particular scope which has been set. Try moving the scope even further up towards the top of DOM
I am not sure this is right. Please try
Very new to Angular and after searching all over the show I simply cannot find a solution to my problem.
I have the following function in a directive/controller:
ModalIssueController.prototype.openModal = function (e, issue) {
this._dataService.getMain().then(function (model) {
this._$scope.modalIssue.open = true;
this._$scope.modalIssue.issue = model.getIssueById(issue);
this._windowService.setModalOpen(true);
}.bind(this));
};
The above function is called each time the user clicks on a different issue from a list. This opens a modal and shows the content related to issue.
When the modal is closed via a close button, the following is called:
ModalIssueController.prototype.closeModal = function () {
this._$scope.modalIssue.open = false;
this._windowService.setModalOpen(false);
this._$timeout(function () {
this._$location.url('/');
}.bind(this));
};
The problem is, even though I can see that the value of this._$scope.modalIssue.issue changes to reflect the new issue that was clicked, the content in the modal never changes, but instead, continues to show the data from the first selected issue ;(
Am I missing something here? Is there an additional step I need to add to ensure that the data in the template is updated?
Here is the directive 'set-up':
var ModalIssueDirective = function () {
return {
restrict: 'A',
replace: true,
scope: true,
controller: ModalIssueController,
templateUrl: '/app/lorax/directives/modal-issue.tpl.html'
};
};
And here is the template I am populating:
<section class="modal modal--fade-show modal--issue" ng-show="modalIssue.open" >
Close
<h1 class="detail-header-title">{{::modalIssue.issue.getTitle()}}</h1>
<div class="detail-main__copy">{{::modalIssue.issue.getNarrative()}}</div>
<header class="detail-link__header">
<h1>{{::modalIssue.issue.getMiscLocale().mozDoingLabel}}</h1>
</header>
<p class="detail-link__copy">{{::modalIssue.issue.getMozActionCopy()}}</p>
<a ng-if="::modalIssue.issue.getMozActionLink().length === 1" href="{{::modalIssue.issue.getMozActionLink()[0].url}}" class="btn detail-link__btn">{{::modalIssue.issue.getMozActionLink()[0].copy}}</a>
<a ng-if="::modalIssue.issue.getMozActionLink().length > 1" ng-repeat="link in ::modalIssue.issue.getMozActionLink()" href="{{link.url}}" class="detail-link__multiple">{{link.copy}}<span class="icon-arrow-right"></span></a>
<header class="detail-link__header">
<h1>{{::modalIssue.issue.getMiscLocale().yourDoingLabel}}</h1>
</header>
<p class="detail-link__copy">{{::modalIssue.issue.getYourActionCopy()}}</p>
<a ng-if="::modalIssue.issue.getYourActionLink().length === 1" href="{{::modalIssue.issue.getYourActionLink()[0].url}}" class="btn detail-link__btn">{{::modalIssue.issue.getYourActionLink()[0].copy}}</a>
<a ng-if="::modalIssue.issue.getYourActionLink().length > 1" ng-repeat="link in ::modalIssue.issue.getYourActionLink()" href="{{link.url}}" class="detail-link__multiple">{{link.copy}}<span class="icon-arrow-right"></span></a>
</section>
Thank you in advance for any assistance that can be provided here.
So, turns out :: in Angular templates defines a one-time binding. This essentially means that as soon as, for example, the following expression has been run:
{{::modalIssue.issue.getTitle()}}
and it returned a value that is not undefined, it is considered stable and the expression will never be run again. So, removing :: from each of the relevant lines in the template resolved the issue.
Docs: https://docs.angularjs.org/guide/expression (#see One-Time Binding)