How to handle save/edit button toggles in Vue? - javascript

I have a Vue 'app' of sorts. It's just part of a larger Django application - but I'm using this to springboard my Vue learning.
I'm trying to create unique forms that would be editable.
I have been messing about with this for a while trying to figure out how to 'disable all the forms except the one being edited'.
If a new 'evidence' is added, that form should be enabled and the others uneditable.
If an existing evidence is being edited then the 'add evidence' button should not be active and only the form being edited should be able to be edited.
My Vue looks like this - I have a base container (that's the Vue app) and a component (that is the forms):
var evidenceFormComponent = Vue.component("evidence-form", {
template: "#evidenceFormTemplate",
props: ["csrf_token", "evaluation_id", "element"],
components: {},
data: function () {
console.log("data function");
return {
evidence: getEvidence(),
subdomains: getSubdomains(),
isDisabled: null,
baseUrl: null
};
},
created: function () {
console.log("created_function!");
this.baseUrl = "/api/";
this.subdomainUrl = "/api/";
this.fetchAdditionalEvidence();
this.fetchSubdomainList();
this.isDisabled = true;
},
methods: {
fetchSubdomainList: function () {
// get the evidence if any using a Jquery ajax call
console.log("this should be fetching the subdomains");
return getSubdomains;
},
fetchAdditionalEvidence: function () {
// get the evidence if any using a Jquery ajax call
console.log("this is fetching additional evidence");
return getEvidence();
},
editForm: function (element) {
console.log("editing the form!");
this.isDisabled=false;
},
cancelEdit: function () {
console.log("cancel the edit!");
}
}
// watch:
});
const vm = new Vue({
el: "#evidenceFormsContainer",
data: function () {
console.log("parent data function");
return {
evidence: getEvidence(),
subdomains: getSubdomains(),
isDisabled: false,
baseUrl: null
};
},
methods: {
addForm: function () {
console.log("adding a child form!");
this.evidence.unshift({});
},
}
});
getEvidence and getSubdomains just return generic stuff atm as I would expect from an API.
I have read that it is best to have all UI elements present in case someone has JS disabled or something odd. So I figured I would create all 4 buttons then show/hide depending on if they should be disabled or not.
<button class="btn btn-primary text-white valign-button" v-on:click.prevent="element.isDisabled=false" #click="editForm()">
<i class="far fa-edit"></i> EDIT
</button>
<button :id="'saveButton'+element.id" v-if="element.isDisabled" v-on:click.prevent="element.removedRow=true" class="btn btn-primary text-white valign-button">
<i class="far fa-save"></i> SAVE
</button>
<button class="btn bg-danger text-white valign-button" data-toggle="modal" data-target="#deleteEvidenceModal" v-on:click.prevent>
<i class="fal fa-trash-alt"></i> DELETE
</button>
<button v-if="element.isDisabled" v-on:click.prevent="element.removedRow=true" class="btn bg-secondary text-white valign-button" #click="cancelEdit()">
<i class="fas fa-minus"></i> CANCEL
</button>
The problem I am running into is figuring how to tell if I'm editing one, or if it is a new one being added and properly disabling all the other elements.
For clarity I have made a JSFiddle of this in practice.
When you click 'add evidence' in the example, you will see. The form is 'disabled' still and the other forms still have the ability to click 'edit'.
I'm a bit lost. Would a child component for the buttons be better? Then if I'm editing a form or creating a new one, I can hide all the buttons on all the other instances?
All advice welcome!

Create a global reference to an activeForm element:
data: function () {
console.log("data function");
return {
evidence: getEvidence(),
subdomains: getSubdomains(),
isDisabled: null,
baseUrl: null,
activeForm: null // this is what we're adding
};
},
When you're in a loop, you know the index of the element you're working with. Pass that to your function:
#click="editForm(index)"
Assign that index to your activeForm variable:
editForm (index) {
this.activeForm = index
}
Change your v-if comparator assignment to observe whether the current index is the same as the activeForm:
v-if="activeForm && activeForm === index
In this way, a single variable is responsible for determining the edit state.
If you want to disable all the forms when adding, I'd just make another variable called adding and set it to true/false in the same way we did above with the other functions, and modify the v-if on the edit and delete buttons:
v-if="(activeForm && activeForm === index) && !adding"

Related

Vue.js return component value to vanilla javascript

I'm wondering if I am able to get a components data, the count property in this instance and console.log it into normal javascript, is this possible? I'm wanting to do console.log(btn.data.count) in this case
<div id="app" v-cloak>
<h1>{{greeting}}</h1>
<button-counter></button-counter>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script>
let app = Vue.createApp({
data: function(){
return {
greeting: "hi"
}
}
})
let btn = app.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
console.log(btn.data.count) // doesn't work
app.mount("#app")
</script>
There might be multiple instances of the button-counter component, so you cannot ask for the count.
You can only access the data from within the component itself. For example from within a method that handles the click event:
let btn = app.component('button-counter', {
data: function () {
return {
count: 0
}
},
methods: {
onClick() {
console.log(this.count)
this.count++
}
},
template: '<button v-on:click="onClick">You clicked me {{ count }} times.</button>'
})
You can use console.log normally you just need to use it where it makes sense. It does not make sense using it before #app is mounted.
Try writing a method for your count++ and add console log there you will see that it gets executed every time.

How to trigger scrolltop in vue 2 single page application?

I have used vee-validate for form validation in vue2 application. On click location button I need to scrolltop to the error message.
<a href="javascript:void(0)" class="btn btn-success" style="border-radius: 4px" #click.prevent="addNewLocation"> location
<span class="ti-plus"></span>
</a>
Here addNewLocation() method action is:
addNewLocation () {
this.$validator.validateAll().then(isValid => {
if (isValid) {
this.$store.commit('order/MUTATE_ADD_NEW_LOCATION', {stair_built_location: this.stair_built_location});
let locationArrayIndex = this.order.locations.length - 1;
this.$router.push({ name: 'StairHeader', params: { location_index: locationArrayIndex }});
} else {
window.scrollTo(0,0);
console.log('invalid');
}
});
},
In code window.scrollTo(0,0) does not work. How can I solve this problem?
You need to use scrollBehavior within your routing add function like this
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return { selector: to.hash }
}
}
It should detect if any #something was added in URL and scroll to it.
You can use it with for example router link then pass hash in to="
<router-link :to=" { hash: #IDofDiv } "> scroll to div </router-link>
you should also be able to just add hash from code. You can find more, complex info in vuejs router docs.
Remember to add div with this ID.

Laravel + Vue.js. Load more data when i click on the button

i have problem. When I click the button, it receives an entire database, but I want laod part database. How can I do this?
For example: After every click I would like to read 10 posts.
Thx for help.
Messages.vue:
<div class="chat__messages" ref="messages">
<chat-message v-for="message in messages" :key="message.id" :message="message"></chat-message>
<button class="btn btn-primary form-control loadmorebutton" #click="handleButton">Load more</button>
</div>
export default{
data(){
return {
messages: []
}
},
methods: {
removeMessage(id){...},
handleButton: function () {
axios.get('chat/messagesmore').then((response) => {
this.messages = response.data;
});
}
},
mounted(){
axios.get('chat/messages').then((response) => {
this.messages = response.data
});
Bus.$on('messages.added', (message) => {
this.messages.unshift(message);
//more code
}).$on('messages.removed', (message) => {
this.removeMessage(message.id);
});
}
}
Controller:
public function index()
{
$messages = Message::with('user')->latest()->limit(20)->get();
return response()->json($messages, 200);
}
public function loadmore()
{
$messages = Message::with('user')->latest()->get();
// $messages = Message::with('user')->latest()->paginate(10)->getCollection();
return response()->json($messages, 200);
}
paginate(10) Loads only 10 posts
You can do it like this:
<div class="chat__messages" ref="messages">
<chat-message v-for="message in messages" :key="message.id" :message="message"></chat-message>
<button class="btn btn-primary form-control loadmorebutton" #click="handleButton">Load more</button>
</div>
export default{
data(){
return {
messages: [],
moreMessages: [],
moreMsgFetched: false
}
},
methods: {
removeMessage(id){...},
handleButton: function () {
if(!this.moreMsgFetched){
axios.get('chat/messagesmore').then((response) => {
this.moreMessages = response.data;
this.messages = this.moreMessages.splice(0, 10);
this.moreMsgFetched = true;
});
}
var nextMsgs = this.moreMessages.splice(0, 10);
//if you want to replace the messages array every time with 10 more messages
this.messages = nextMsgs
//if you wnt to add 10 more messages to messages array
this.messages.push(nextMsgs);
}
},
mounted(){
axios.get('chat/messages').then((response) => {
this.messages = response.data
});
Bus.$on('messages.added', (message) => {
this.messages.unshift(message);
//more code
}).$on('messages.removed', (message) => {
this.removeMessage(message.id);
});
}
}
-initialize a data property morMsgFetched set to false to indicate if more messages are fetched or not
if morMsgFetched is false make the axios request and st the response to moreMessages, then remove 10 from moreMessages and set it to messages[]..
After that set morMsgFetched to true
on subsequest click remove 10 from moreMessages and push it to 'messages[]`
Use Laravels built in pagination.
public function index()
{
return Message::with('user')->latest()->paginate(20);
}
It returns you next_page url which you can use to get more results calculated automatically
This might be too late but i believe the best way to do it is using pagination, Initially onMounted you'll send a request to let's say /posts?page=1, the one is a variable let's say named 'pageNumber', each time the user clicks on the "Load More" button, you'll increment the pageNumber and resent the request, the link will page /posts?page=2 this time, at this point you can append the results you've got to the already existing one and decide if the Load More button should be shown based on the last_page attribute returned by laravel paginator...
I'm sure you already solved your problem or found another alternative, this might be usefull for future developers.

Using Laravel eloquent calculate total of like and passing into vue js file

Currently i am implementing a like system into my laravel project with Vue js. The scenario is after users liked the post. It will showing the total like of the post beside the like button. If using controller and passing data into view is easy to be done. But when change to Vue js i have no idea how to do it. This is my Like.vue file.
<template>
<button class="btn btn-primary" v-if="!auth_user_likes_post" #click="like()">
Support
</button>
<button class="btn btn-primary" v-else #click="unlike()">
Unsupport
</button>
export default {
mounted(){
},
props: ['id'],
computed: {
likers() {
var likers = []
this.post.likes.forEach( (like) => {
likers.push(like.user.id)
})
return likers
},
auth_user_likes_post() {
var check_index = this.likers.indexOf(
this.$store.state.auth_user.id
)
if (check_index === -1)
return false
else
return true
},
post() {
return this.$store.state.posts.find( (post) => {
return post.id === this.id
})
}
},
methods: {
like() {
this.$http.get('/like/' + this.id)
.then( (resp) => {
this.$store.commit('update_post_likes', {
id: this.id,
like: resp.body
})
})
},
unlike() {
this.$http.get('/unlike/' + this.id)
.then( (response) => {
this.$store.commit('unlike_post', {
post_id: this.id,
like_id: response.body
})
})
}
}
}
The like and unlike functions has been done and working perfectly. Now i just need to show the total number.
This is Feed.vue file. The like was adding to this page.
<div class="post-description">
<p>{{ post.content }}</p>
<div class="stats">
<like :id="post.id"></like>
<a href="#" class="stat-item">
<i class="fa fa-retweet icon"></i>12
</a>
<a href="#" class="stat-item">
<i class="fa fa-comments-o icon"></i>3
</a>
</div>
</div>
If my function cannot be achieved the expected scenario then can you recommend other ways which can do that?
I have the returned array with likers. But at beginning i never though using this could be achieved my goal. So this {{ likers.length }} actually can show the total number.

Saving multiple data dynamic form in Laravel 5.1 and Vue JS

I have to add/post data form. But the form dynamically can increase as user 'click' on a button. I've already browse about it and there some answer i get like using $request->all() to fetch all data from input forms.
And then my problem is, my app using VueJS as front-end. Is there any some configuration on VueJS script to post all data from that dynamic form??
My Blade template that will be increase dynamically:
<div id="form-message">
{!! Form::text('rows[0][DestinationNumber]', null, [
'id' => 'recipient',
'class' => 'form-control',
'v-model' => 'newMessage.DestinationNumber'
])
!!}
{!! Form::textarea('rows[0][TextDecoded]', null, [
'rows' => '3',
'id' => 'recipient',
'class' => 'form-control',
'v-model' => 'newMessage.TextDecoded'
])
!!}
</div>
That zero number will increase depends on how much user click add button.
And then here my VueJS script
var newSingleMessage = new Vue({
el: '#newsinglemsg',
data: {
newMessage: {
DestinationNumber: '',
TextDecoded: ''
},
},
methods: {
onSubmitForm: function(e) {
e.preventDefault();
var message = this.newMessage;
this.$http.post('api/outbox', message);
message = { DestinationNumber: '', TextDecoded: '' };
this.submitted = true;
}
}
});
On laravel controller, i have simple logic to test result how data passed.
$input = $request->all();
$output = dd($input);
return $output;
And, I test it using 2 additional form. So, the data should be 3 rows. The result (checked from FireBug) to be like this
{"DestinationNumber":"1234567890","TextDecoded":"qwertyuio"}
Data passed just one, and then the type is JSON. Even I use return $output->toArray(), type still JSON.
Oh yeah, once more. Idk how to make the zero number increase dynamically using javascript. When testing, i just manual add the form. Here my add click function javascript
var i = 0,
clone = $('#form-message').clone(),
recipient = document.getElementById('recipient');
recipient.setAttribute('name', 'rows['+ i +'][DestinationNumber]');
clone.appendTo('.form-message:last');
i++;
For second and next rows, name attribute not added on the input elements.
Thanks
You're mixing blade and jquery and vue in a way that is pretty confusing. Check out this JS fiddle that accomplishes all of this with Vue:
https://jsfiddle.net/cr8vfgrz/10/
You basically have an array of messages that are automatically mapped to inputs using v-for. As those inputs change, your messages array changes. Then when submit is pressed, you just post this.messages and the array of messages is sent to server. Then you can clear the array to reset the form.
Template code:
<div id="form-message">
<button class="btn btn-default" #click="addNewMessage">New Message</button>
<template v-for="message in messages">
<input type="text" v-model="message.DestinationNumber" class="form-control">
<textarea rows="3" v-model="message.TextDecoded" class="form-control"></textarea>
</template>
<button class="btn btn-success" #click.prevent="submitForm">Submit</button>
</div>
Vue code:
var newSingleMessage = new Vue({
el: '#form-message',
data: {
messages: [
{
DestinationNumber: '',
TextDecoded: ''
}
],
submitted:false
},
methods: {
addNewMessage: function(){
this.messages.push({
DestinationNumber: '',
TextDecoded: ''
});
},
submitForm: function(e) {
console.log(this.messages);
this.$http.post('api/outbox', {messages:this.messages})
.then(function(response){
//handle success
console.log(response);
}).error(function(response){
//handle error
console.log(response)
});
this.messages = [{ DestinationNumber: '', TextDecoded: '' }];
this.submitted = true;
}
}
});
Edit:
In the controller you can use $request->input('messages'); which will be the array of messages. You can insert multiple new Outbox model using:
Outbox::insert($request->input('messages'));
or
foreach($request->input('messages') as $message){
Outbox::create($message);
}

Categories