My project:
I have many posts, the index method returns paginated posts, 3 per page.
However in my Vuejs i don't want to show pages and i use infinite scrolling to show next 3 posts every time user scrolls to bottom of the page.
Everytime i remove a post i manage to remove it in realtime with vue. Page won't get refreshed and the post gets deleted in realtime.
The problem:
When i load posts in frontend, i have 3 posts loaded, then i remove a post for example post #1.
As we know second page in laravel means escape first 3 posts and get the second set
of 3 posts.
Now with the first post removed from database, when i go to bottom of page im expecting to get posts #4 #5 #6, but i will get #5 #6 #7.
reason:
because one post is gone in database and the next set of 3 posts are different now.
But how to solve this?
is there a solution for this problem
Well I think best solution is to update the array of Posts after every delete request. Simply make GET request with current page right after delete operation and update the array and add new value to existing array, if any. Now I have written a sample code for it I hope it will help.
Since you didn't share code so possible Code for your Component
<template>
<div>
<div v-for="post in posts" :key="post._id">
<div>{{post.name}}</div>
<div>
<button #click="deletePost(post)">Delete</button>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
export default() {
data() {
current_page: 1,
posts: []
},
created() {
this.updatePosts();
},
methods: {
updatePosts() {
axios.get("http://www.example.com/posts"{
params: {page: this.current_page}
}).then(res => {
if(res.status == '200') {
res.data.posts.forEach(post => {
if(!this.posts.includes(post)) this.posts.push(post);
});
}
}).catch(err => console.log(err));
},
deletePost(post) {
axios.delete("http://www.example.com/posts",{
params: {id: post.id}
}).then(res => {
if(res.status == '200') {
this.updatePosts(); // this will update array
}
}).catch(err => console.log(err));
}
}
}
</script>
Remember to increase current_page value by 1 when hit bottom of page.
Related
I'm new to Alpine and struggling to wrap my head around how to make a scenario like this work:
Let's say I have a serverside built page, that contains some buttons, that represent newsletters, the user can sign up to.
The user might have signed up to some, and we need to indicate that as well, by adding a css-class, .i.e is-signed-up.
The initial serverside markup could be something like this:
<button id='newsletter-1' class='newsletter-signup'>Newsletter 1</button>
<div>some content here...</div>
<button id='newsletter-2' class='newsletter-signup'>Newsletter 2</button>
<div>more content here...</div>
<button id='newsletter-3' class='newsletter-signup'>Newsletter 3</button>
<div>and here...</div>
<button id='newsletter-4' class='newsletter-signup'>Newsletter 4</button>
(When all has loaded, the <button>'s should later allow the user to subscribe or unsubscribe to a newsletter directly, by clicking on one of the buttons, which should toggle the is-signed-up css-class accordingly.)
Anyway, then I fetch some json from an endpoint, that could look like this:
{"newsletters":[
{"newsletter":"newsletter-1"},
{"newsletter":"newsletter-2"},
{"newsletter":"newsletter-4"}
]}
I guess it could look something like this also:
{"newsletters":["newsletter-1", "newsletter-2", "newsletter-4"]}
Or some other structure, but the situation would be, that the user have signed up to newsletter 1, 2 and 4, but not newsletter 3, and we don't know that, until we get the JSON from the endpoint.
(But maybe the first variation is easier to map to a model, I guess...)
Anyway, I would like to do three things:
Make Alpine get the relation between the model and the dom elements with the specific newsletter id (i.e. 'newsletter-2') - even if that exact id doesn't exist in the model.
If the user has signed up to a newsletter, add the is-signed-up css-class to the corresponding <button> to show its status to the user.
Bind to each newsletter-button, so all of them β not just the ones, the user has signed up to β listens for a 'click' and update the model accordingly.
I have a notion, that I might need to 'prepare' each newsletter-button beforehand with some Alpine-attributes, like 'x-model='newsletter-2', but I'm still unsure how to bind them together when Alpine has initialising, and I have the data from the endpoint,
How do I go about something like this?
Many thanks in advance! π
So our basic task here is to add/remove a specific item to/from a list on a button click. Here I defined two component: the newsletter component using Alpine.data() creates the data (subs array), provides the toggling method (toggle_subscription(which)) and the checking method (is_subscribed(which)) that we can use to set the correct CSS class to a button. It also handles the data fetching in the init() method that executes automatically after the component is initialized. I have also created a save method that we can use to send the subscription list back to the backend.
The second component, subButton with Alpine.bind() is just to make the HTML code more compact and readable. (We can put each attribute from this directly to the buttons.) So on click event it calls the toggle_subscription with the current newsletter's key as the argument to add/remove it. Additionally it binds the bg-red CSS class to the button if the current newsletter is in the list. For that we use the is_subscribed method defined in our main component.
.bg-red {
background-color: Tomato;
}
<script src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js" defer></script>
<div x-data="newsletter">
<button x-bind="subButton('newsletter-1')">Newsletter 1</button>
<button x-bind="subButton('newsletter-2')">Newsletter 2</button>
<button x-bind="subButton('newsletter-3')">Newsletter 3</button>
<button x-bind="subButton('newsletter-4')">Newsletter 4</button>
<div>
<button #click="save">Save</button>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('newsletter', () => ({
subs: [],
init() {
// Fetch list of subscribed newsletters from backend
this.subs = ['newsletter-1', 'newsletter-2', 'newsletter-4']
},
toggle_subscription(which) {
if (this.subs.includes(which)) {
this.subs = this.subs.filter(item => item !== which)
}
else {
this.subs.push(which)
}
},
is_subscribed(which) {
return this.subs.includes(which)
},
save() {
// Send this.sub to the backend to save active state.
}
}))
Alpine.bind('subButton', (key) => ({
'#click'() {
this.toggle_subscription(key)
},
':class'() {
return this.is_subscribed(key) && 'bg-red'
}
}))
})
</script>
I need to call a view in a Laravel Controller, with parameters and with Anchor Tag.
I have this code in my controller:
return view('plans/editPlanView',
['plan' => $plan,
'patient' => $patient,
'aliments'=>$aliments, 'menu'=>$menu, 'tabName'=>$tabName]);
But i need to add an Anchor tag to land in a specific section of the page.
I can't use
return Redirect::to(URL::previous() . "#whatever");
proposed in other posts because i need to pass some parameters.
I think there are some base problem, trying with console this:
$('html, body').animate({
scrollTop: $('#whatever').offset().top
}, 1000);
scrolling to the desired section does not work.
it seems the page makes a small snap but always returns to the top.
Update
I have found the cause of the problem. At the bottom of the blade page I have the following code, without it the anchor tag works fine. Adding it the page makes a small scroll to return to the head. I need to use the datepicker, how can I fix the problem and get the anchor tag to work?
#push('scripts')
<script type="text/javascript">
$(document).ready(function () {
$('.date').datepicker({
firstDayOfWeek: 1,
weekDayFormat: 'narrow',
inputFormat: 'd/M/y',
outputFormat: 'd/M/y',
markup: 'bootstrap4',
theme: 'bootstrap',
modal: false
});
});
</script>
#endpush
You can create the method showPage() in your contoller for example TestController
public function showPage(Request $request)
{
$plan = $request->plan;
...
return view('plans/editPlanView', [
'plan' => $plan,
'patient' => $patient,
'aliments'=>$aliments, 'menu'=>$menu, 'tabName'=>$tabName
]);
}
Then create a route for rendering that view
Route::get('/someurl', 'TestController#showPage')->name('show-page');
And then in your methods you can use something like that:
$url = URL::route('show-page', ['#whatever']);
return Redirect::to($url)
I found a workaround, I added the disable attribute to the date input, in doing so, when the datepicker is initialized, the page does not scroll up. Then, as a last javascript statement I re-enabled the fields:
$('.date').prop("disabled", false);
I'm a frustrated Vue.js noobie coming from jQuery.
I'm trying to do something very basic for a simple project: Delete an article but only after ajax response. While waiting, for the response there's a spinner. (I don't want components or vue files. It's a simple, single file app)
I've been hacking away at it for a few days now and I can't grasp some basic concepts it seems. (Or I want it to behave like jquery)
Fiddle
window.app = new Vue({
el: '#app',
data: {
name: '',
posts: [],
loadingItems: [],
},
created() {
this.fetchData();
},
methods:{
fetchData() {
axios.get('https://jsonplaceholder.typicode.com/posts').then(response => {
this.posts = response.data.slice(0,20);
});
},
removeItem(item) {
var index = this.posts.indexOf(item);
//var loadingIndex = this.loadingItems.push(item) - 1;
//console.log(loadingIndex);
item.isLoading = true;
//Update vue array on the fly hack - https://vuejs.org/2016/02/06/common-gotchas/
this.posts.splice(index, 1,item);
axios.post('//jsfiddle.net/echo/json/', "json='{status:success}'&delay=2")
.then(response => {
this.posts.splice(index, 1);
//this.loadingItems.splice(loadingIndex, 1);
//this.loadingItems.pop(item);
//item.isLoading = false;
//this.posts.splice(index, 1,item);
});
}
},
computed: {
showAlert() {
return this.name.length > 4 ? true : false
}
}
})
<div id="app">
<div v-for="(post,index) in posts" :key="post.id">
<b-card class="mb-2">
<b-card-title>{{post.title}}</b-card-title>
<b-card-text>{{post.body}}</b-card-text>
<a href="#" #click.prevent="removeItem(post)" class="card-link">
Remove
<span v-show="post.isLoading" class="spinner"></span>
</a>
</b-card>
</div>
</div>
Works fine for deleting them 1 by 1 one but not when you click on multiple at the same time, since the index is different by the time the request comes back and it splices the wrong item.
What I've tried so far:
First, it took me a day to figure out that item.isLoading = true; won't work if it wasn't present when the data was first observed (or fetched). However, I don't want to add the property to the database just for a loading animation. So the workaround was to do this.posts.splice(index, 1,item); to "force" Vue to notice my change. Already feels hacky.
Also tried using an array LoadingItems and pushing them while waiting. Didn't work due to the same problem: don't know which one to remove based on index alone.
Studying the TODO app didn't help since it's not quite addressing handling async ajax responses or adding properties at runtime.
Is the best way to do it by using post.id and then trying to pass it and find it back in the array? Really confused and thinking jQuery would have been easier.
Any help will be appreciated. Thanks!
Works fine for deleting them 1 by 1 one but not when you click on multiple at the same time, since the index is different by the time the request comes back and it splices the wrong item.
Don't save the index in a variable. Calculate it every time you need it:
removeItem(item) {
item.isLoading = true;
this.posts.splice(this.posts.indexOf(item), 1, item);
axios.post('/echo/json/', "json='{status:success}'&delay=2")
.then(response => {
this.posts.splice(this.posts.indexOf(item), 1);
});
}
I have a list of orders stored in a db. I use each block to display all orders with a delete button. When I click the delete button, I need to get the id of the CLICKED list item so I can look that order in the db and delete it. I don't know how to get the id of the CLICKED list item and pass it to handledelete function. How do I do that in svelte/sapper?
My code for the page that display all orders :
<script>
let orderz =[]
function handlesave() {
//save all the order data to db...
} // handlesave
function handleDelete () {
fetch('order', {
method: 'POST',
credentials : 'include',
headers: {
'Accept': 'application/json',
'Content-type' : 'application/json'
},
body: JSON.stringify({
// order id to send it to server to delete it from the db
})
}).then(response => response.json())
.then(responseJson => {
console.log("xxxxxxx:", responseJson.orderdetails )
})
.catch(error => console.log(error));
}
</script>
<form on:submit|preventDefault={handlesave}>
<button type="submit">Place Order</button>
</form>
<ul>
{#each orderz as order}
<li >
<p >{order.vendorid} - {order.vendorname} - {order.item}
- {order.price}</p>
<button on:click|once={handleDelete}>Delete</button>
</li>
{/each}
</ul>
You can tell the delete function which id was clicked by simply passing it in as an argument to the function:
function handleDelete(id) {
// Delete logic here
}
<button on:click="{() => handleDelete(id)}">Delete</button>
!! Note that you should not call handleDelete directly in your markup as this will execute the function immediately upon rendering (and thus effectively delete your entry as soon as it appears on screen)
Just add variable to your delete-function:
function handleDelete (id){
... use id to delete item in database...
}
Then add order id also to your on:click:
EDIT: on:click function call fixed as mentioned in other answer
<button on:click|once={()=>handleDelete(order.id)}>Delete</button>
There are other ways to do this, but this is the simplest one.
You donβt need once modifier, if you delete the item.
You will probably need a key with each-loop in order to keep list correctly updated after delete (key = thing.id in following example)
{#each things as thing (thing.id)}
<Thing current={thing.color}/>
{/each}
https://svelte.dev/tutorial/keyed-each-blocks
I have a Laravel + VueJS app. I would like to change the url when the user selects a new year, for example.
The user in on the url idea/[IDEA_ID]/[YEAR] and changes year inside the page, so i want to set the url, but the rest of the work is done through axios call.
Here is how I work for now:
Route::get('idea/{n}/{m}', 'IdeaController#idea')->name('idea');
class IdeaController extends Controller
{
public function idea($id, $year)
{
$sql = "";
$array = DB::connection('ideas')->select( DB::connection('ideas')->raw($sql));
return view('ideas/idea', ['idea' => json_encode($array)] );
}
}
blade view:
#extends('template')
#section('content')
<div>
<div id="app">
<ideapage ideas="{{ $idea }}"></ideapage>
</div>
</div>
#endsection
And in my Vue view (ideapage), I have all the logic and only make axios requests.
The problem is that I want to change my url inside my Vue view, when the user changes the year for example.
Therefore I am wondering if I did the things well. Would it be a better idea to separate the components inside the laravel blade view? And how can I change only a section when the url changes?
I am not using VueRouter: the routes are in web.php only.
Thanks a lot in advance.
After a lot of thinking, I didn't change my architecture for now, and I only use history.pushstate when the year or idea changes
idea.vue
watch: {
idea: function() {
history.pushState({}, null, '/idea/'+this.idea +'/'+this.year)
},
year: function() {
history.pushState({}, null, '/idea/'+this.idea +'/'+this.year)
}
},