alasql and alpine.js: reactivity from updates outside of Alpine - alpine.js

I would like to use Alpine.js while keeping as much application state inside of alasql.js as possible. The problem is that data which is loaded after Alpine.start() does not show up in the x-for section. If there is a way to force a wasteful full-refresh (without running Alpine.start() again) I would be interested to know. I tried the customEvents workaround several times but that did not work.
Is there any other way to make something like this work? Without changing too much of the architecture..? I'm curious / stubborn
await fetch(`${API_domain}?playlist=` + playlist)
.then(response => response.json())
.then(data => {
alasql('ATTACH localStorage DATABASE RuntimeDB')
alasql('SET AUTOCOMMIT ON')
alasql(`DELETE from videos where webpage_url = "${data.webpage_url}"`)
alasql(`INSERT INTO videos SELECT * FROM ?`, [[data]])
app.updateView()
})
<div x-data="{
playlistToAdd: '',
async submit($event) {
await app.fetchPlaylist(this.playlistToAdd)
}
}">
<label for="add_new">Add New Playlist or Channel:</label>
<input type="text" name="add_new" id="add_new" x-model="playlistToAdd"
placeholder="https://www.youtube.com/playlist?list=PL8A83124F1D79BD4F" #keyup.enter="submit" />
<button type="button" #click="submit">Submit</button>
</div>
<div x-data>
<template x-for="(pl, index) in app.view.table" :key="index">
<div>
<details x-html="`<summary><a href='${pl.webpage_url}'>${pl.title}</a> by
<a href='${pl.channel_url}'>${pl.uploader}</a> (watched ${pl.playlist_count-pl.entries.length} of ${pl.playlist_count}; ${(pl.playlist_count-pl.entries.length)/pl.playlist_count*100.0}%)</summary>
<template x-for='(v, index) in pl.entries' :key='index'>
<div><span x-text='v.title'></span></div>
</template>
`"></details>
</div>
</template>
</div>
full code here: https://github.com/chapmanjacobd/lb-lite/tree/main/client

As you noticed app.view.table is not reactive. For this purpose you can use the Alpine.store() which is externally accessible and reactive as well:
Alpine.store('table', [])
updateView: function () {
Alpine.store('table', alasql('select * from videos'))
}
And in the template just use $store.table:
<template x-for="(pl, index) in $store.table" :key="index">

Related

Alpinejs property bound to x-show not defined

I'm building a form in Laravel's Blade syntax, and using AlpineJs for some interactivity stuff like showing and hiding content.
My code is here:
<div class="flex items-center" x-data="destinationBuilder">
<x-label required="true" class="mr-5">Destination:</x-label>
<x-basic-input #change="handleDestinationChange" ::value="destination" placeholder="https://google.com"/>
<x-buttons.primary class="ml-5" #check="validateDestination">Check</x-buttons.primary>
</div>
<div class="mt-4">
<button type="button"
class="p-5 w-full text-sm grid grid-cols-[min-content_min-content_auto_min-content] items-center gap-x-3 font-light text-gray-400 hover:bg-gray-300 rounded-full"
#click.camel="toggleAdvancedOptions">
<i class="lni lni-cog"></i>
<span class="whitespace-nowrap">Advanced options</span>
<div class="h-px w-full bg-gray-400"></div>
<i class="lni lni-chevron-down"></i>
</button>
<div x-show="advanced" x-transition x-cloak>
{{-- <x-links.get-parameter-form/>--}}
</div>
</div>
#push('footer-scripts')
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('destinationBuilder', () => ({
destination: '',
advanced: false,
handleDestinationChange() {
if (this.validateDestination()) {
// emit constructed destination up
}
},
validateDestination() {
// check url is in a legit form (with https)
// basic is just url input
// advanced dropdown with get parameters, fragment, http protocol and port
},
toggleAdvancedOptions() {
this.advanced = !this.advanced;
}
}));
})
</script>
#endpush
I'm using a property named advanced to bind to x-show for another component.
When I look in my browser I get the following message: app.js:434 Uncaught ReferenceError: advanced is not defined
I'm not sure if this is due to a weird collision with blade or if I'm missing something fundamental with Alpinejs. I tried renaming the variable to showAdvanced but that didn't work either. I would expect advanced to be recognised as a property and bind to x-show as expected. What am I doing wrong here?
You have the following HTML structure:
<div x-data="destinationBuilder">
...
</div>
<div>
<div x-show="advanced">
...
</div>
</div>
As you see, the second div is not a child element of the first one where the x-data is defined, so it's outside of the scope of destinationBuilder component. Just create a common div (or similar) element that embeds both divs and apply the component x-data there, so each div will have access to the component's scope.

What is this.loading = false?

I am using Laravel and Vue.
When I was searching on the internet I saw the following code.
<template>
<div>
<h3 class="text-center">Create Movie</h3>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="createMovie">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" v-model="movie.name">
</div>
<div class="form-group">
<label>Director</label>
<input type="text" class="form-control" v-model="movie.director">
</div>
<button type="submit" class="btn btn-primary">Create movie</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
movie: {}
}
},
methods: {
createMovie() {
this.axios
.post('http://localhost:8000/api/movie/create', this.movie)
.then(response => (
this.$router.push({name: 'movie'})
))
.catch(error => console.log(error))
.finally(() => this.loading = false)
}
}
}
I am trying to find out what the last line
.finally(() => this.loading = false)
is doing. I am searching on the internet but I can't find what it does. Also, I tried running the code without the last line however, it did not make any change.
Can someone please tell me what this is doing and when it is useful?
Without seeing the associated Vue template we cannot explain what it is doing exactly, however, we can be fairly confident that the value of loading will be used to show/hide some sort of overlay or activity spinner.
The purpose of the overlay/activity spinner is to provide visual feedback to the user that something is happening. This is useful when loading large amounts of data into your page, or when you perform a long running process (such as uploading a large file for example). So rather than the user seeing nothing on first page load, or clicking a button and wondering if it worked, they are provided with something to let them know that something is happening.
A basic example of what this might look like in the Vue template could be:
// if the value of loading is true, show this
<div v-if="this.loading">Loading, please wait ...</div>
// otherwise show this
<div v-else>Other content</div>
Your example is setting the value of loading to false once a response has been received from your axios request. You would probably want to set the value of loading to true prior to making the request to show an overlay/activity spinner.
Uncaught (in promise) ReferenceError: ture is not defined
You have a typo, it should be true not ture.

Using Vue in Laravel Spark - "method not defined on the instance" using vue2-touch-events - what could I do differently?

I am trying to build a very simple, tinder-like swiping application using Laravel Spark and vue2-touch-events. I assume I am doing something extraordinarily dumb, so I appreciate your insight.
I get this warning in my Vue inspector:
[Vue warn]: Property or method "swipeHandler" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Here is my simple version of all of the relevant code. I changed the v-touch:swipe to just longtap for testing.
/resources/js/app.js:
require('spark-bootstrap');
require('./components/bootstrap');
require('vue2-touch-events');
Vue.component('swipeHandler', './components/swipehandler.vue');
var app = new Vue({
mixins: [require('spark')],
components: {
vue2touchevents,
swipeHandler
},
});
swipe.blade.php:
#extends('spark::layouts.app')
#section('content')
<home :user="user" inline-template>
<div class="container">
<div class="row justify-content-center flex-column-reverse flex-md-row">
<div class="col-md-8">
<div class="card text-center">
<div class="card-header">
<div><i v-touch:longtap="swipeHandler">Swipe Here</i></div>
</div>
</div>
</div>
</div>
</div>
</home>
#endsection
/resources/js/components/swipehandler.vue:
<script>
export default {
methods: {
swipeHandler () {
alert("yay!");
console.log("yay!");
}
}
}
</script>
My Best guess is that you may want to try and add a template to your /resources/js/components/swipehandler.vue component:
<template>
<div>Yay</div>
</template>
<script>
export default {
methods: {
swipeHandler () {
alert("yay!");
console.log("yay!");
}
}
}
</script>
I'm not too familiar with how vue2-touch-events works, so I would need to understand that a bit more to give you a better answer.
Best of luck.

Button that shows modal for each b-table row

Im using laravel, vue, and bootstrap-vue.
I have created a vue component that displays a table of elements (subnets in this example).
For each of them I show a component (modal_edit-subnet) thats should open a modal that allows to edit the data of the element of the related row.
The problem is that it shows modals for all of the table elements. For example, if the table has 3 rows, it shows 3 modals (after closing one it shows the next). Each of the modals with the data of each of the rows.
I've tried to add "key"s but no success.
What am i doing wrong?
Thanks!
Component that shows the table
<template>
<div>
<b-card class="text-center">
<b-table small striped hover :items="data_subnets" :fields="fields" :tbody-tr-class="rowClass">
<template slot="[ip_address]" slot-scope="data_subnets">
<b>{{ long2ip(data_subnets.item.ip_address) }}</b>
</template>
<template slot="[actions]" slot-scope="data_subnets">
v-on:deleteSubnet="deleteSubnet"></modal_delete-subnet>
<modal_edit-subnet :key="'modal_edit_subnet' + data_subnets.item.id" :subnet="data_subnets.item" v-on:editSubnet="editSubnet"></modal_edit-subnet>
</template>
</b-table>
</b-card>
</div>
</template>
Modal modal_edit-subnet
<template>
<div>
<b-button size="sm" v-b-modal.modal-edit-subnet>Edit</b-button>
<b-modal
id="modal-edit-subnet"
ref="modal"
title="Edit subnet"
#ok="handleOk"
>
This is subnet {{data_subnet.id}}
</b-modal>
</div>
</template>
The problem is that:
You're rendering a modal for each row of the table and;
Reading the docs, it seems like the modal is triggered by the id, and your b-modal id is not dynamic depending on the row.
How to fix it:
Use just one modal on the b-table level
Dynamically inject id into your modal_edit-subnet component:
<template>
<div>
<b-button size="sm" v-b-modal[id]>Edit</b-button>
<b-modal
:id="id"
ref="modal"
title="Edit subnet"
#ok="handleOk"
>
This is subnet {{data_subnet.id}}
</b-modal>
</div>
</template>
<script>
export default {
props: {
id: {
type: String | Number
}
}
}
</script>
Use v-model (this is the way I would do it)
<template>
<div>
<b-button size="sm" #click="show = true">Edit</b-button>
<b-modal
v-model="show"
ref="modal"
title="Edit subnet"
#ok="handleOk"
>
This is subnet {{data_subnet.id}}
</b-modal>
</div>
</template>
<script>
export default {
data() {
return {
show: false
}
}
}
</script>

How to pass data from one component to other in vue js?

I am learning vue+laravel. I want to pass value from one component to other component? I have used vue router for routing.
Here is the code for first and second component.
SelectPerson.vue
<template>
......
<div>
<input type="number" class="form-control" name="numberOfPersons" placeholder="Enter number of persons here" **v-model="inputPersons"**>
<br>
**<SelectTimeSlot v-bind:numberOfPersons="inputPersons"></SelectTimeSlot>**
</div>
<div>
<button class="btn btn-default float-right mt-2" v-on:click="selectTimeSlots">Next</button>
</div>
......
</template>
<script>
import SelectTimeSlot from './SelectTimeSlot.vue'
export default{
props:['numberOfPersons'],
data(){
return{
**inputPersons:0**
}
},
methods:{
selectTimeSlots(){
this.$router.push({name:'SelectTimeSlot'});
}
}
}
</script>
second component
SelectTimeSlot.vue
<template>
<h5>Welcome, You have selected **{{numberOfPersons}}** persons.</h5>
</template>
Can anybody help me do it?
To pass data from one component to other component, you need to use props:
First component:
<second-component-name :selectedOption="selectedOption"></second-component-name>
<script>
export default {
components: {
'second-component-name': require('./secondComponent.vue'),
},
data() {
return {
selectedOption: ''
}
}
}
</script>
Second Component:
<template>
<div>
{{ selectedOption }}
</div>
</template>
<script>
export default {
props: ['selectedOption']
}
</script>
Please visit this link.
Hope this is helpful for you!
Say I have a page with this HTML.
<div class="select-all">
<input type="checkbox" name="all_select" id="all_select">
<label #click="checkchecker" for="all_select"></label>
</div>
the function checkchecker is called in my methods
checkchecker() {
this.checker = !this.checker
}
This will show or hide my div on that page like this
<div v-show="checker === true" class="select-all-holder">
<button>Select All</button>
</div>
Now if I also want to toggle another div which is inside my child
component on that page I will pass the value like this.
<div class="content-section clearfix">
<single-product :checkers="checker"></single-product> //This is calling my child component
</div>
Now in my Child component I will have a prop declared like this
checkers: {
type: String,
default: false,
},
This is how I will write my div in my child component
<div v-show="checkers === true" class="select-holder clearfix">
<input type="checkbox" class="unchecked" name="single_select" id="1">
</div>

Resources