Best practice for accessing parent query object from child components - apollo-client

I have a "event" page that runs an Apollo query for an event with a given ID. There are multiple child components that use the event object to access various properties. I am struggling to find the best pattern for making that object available to the child components. Do I want to have additional queries in the child components that just access the cache exclusively? Or is there a way to have the components defer until the query is complete.

In general, I only run queries from "page" components and pass data down as props to lower level components:
So I would do something like
/pages/EventPage.vue
<template>
<div>
<EventDetails :event="event">
</div>
</template>
<script>
import EventDetails from '/wherever/my/component/is/EventDetails.vue'
import MY_EVENT_QUERY from '/wherever/my/query/is/EventQuery.gql'
export default {
apollo: {
event: {
query: MY_EVENT_QUERY
}
}
}
</script>
/components/EventDetails.vue
<template>
<div>
{{event.name}}
</div>
</template>
<script>
export default {
props: {
event: {
type: Object,
default: { name: '' }
}
}
}
</script>
There might be rare times where I would have a sub-component fetch its own data. I'm thinking of a form where you have select box that needs to load the list of dropdown items or something. But I would not re-query the same event item over and over again.

Related

How to use interval in alpine js?

I am trying to add a timer to my cards. So, what I do is. I receive data from DB and pass the data to the records[] array and then I show the data on frontend.
<template x-for="record in records">
<span x-text="record.created_at">2022-10-31 18:41:20</span>
</template>
But what I want is to show the seconds between created at to now time and the seconds should be changed as time passes kind of like how much time is passed since the record is created.
Actually, in my case, I need to change the text within the loop. I tried something like this
https://cdn.jsdelivr.net/gh/kevinbatdorf/alpine-magic-helpers#latest/dist/interval.js
<template x-for="record in records">
<span x-text="$interval(getTimer(record), 1000)">2022-10-31 18:41:20</span>
</template>
<script type="text/javascript">
function dateComponent() {
return {
init: function() {
// fetch data
},
getTimer: function(record) {
console.log(record)
}
}
}
</script>
But this runs the interval only once. Is this possible in alpine js. and if yes, please guide me to where I am doing wrong.

Mixing Alpine.js with 'static' serverside markup, while getting the benefits of binding, etc

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>

Custom Component in Laravel Spark

I try to add my custom component to my Laravel Spark instance and always get the error:
Property or method "obj" is not defined on the instance but referenced during render..
It all works fine if i only bind a data value (ex. "testkey") without a loop...but if i add the for loop i receive this error...so my code:
app.js (spark)
require('spark-bootstrap');
require('./components/bootstrap');
//my new Component
import OmcListObjects from './components/modules/omc/objectlist.vue';
Vue.component('omc-objectlist', OmcListObjects);
var app = new Vue({
mixins: [require('spark')]
});
my Component (objectlist.vue)
<template>
<div :for="(obj in objlist)" class="property-entry card col- col-md-4 shadow-sm">
</div>
</template>
<script>
export default {
data () {
return {
objlist: [{title: 'test1'}, {title: 'test2'}],
testkey: 'testval'
}
}
}
</script>
I think you mean by :for the v-for directive, directives are always prefixed by v- like v-for one in which the compiler can recognize the obj variable as an temporary element used in the loop, but if you set :for which is recognized as a prop bound to a data or another property.

How to pass custom nativescript-Vue component to custom child Component

I would like to add a dynamic child component to a parent component using Nativescript-Vue. For example:
<template>
<MyParentComponent>
<MyChildComponent :foo="foo"></MyChildComponent>
</MyParentComponent>
<template>
<script>
import MyParentComponent from './components/MyParentComponent';
import MyChildComponent from './components/MyChildComponent';
export default {
components: {
MyParentComponent,
MyChildComponent
},
data: function(){
return {
foo: ''
}
}
}
</script>
I think I need to define a slot in the parent component where the child component should be inserted, but I don't know how this should be done.
Any ideas?
In MyParentComponent's template you need to add a <slot /> tag, that's where Vue will insert the content.
Read more about slots, and what they can do in the Vue docs:
https://v2.vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots

Laravel + Bootstrap-Vue Table not accepting data

I'm currently playing with Laravel Spark and I'm slowly learning the Vue.js system.
I have a bunch of data I want to display in a table that is pulled from AWS DynamoDB. I am successfully parsing this data in sorts of ways and can display the data in a standard static Bootstrap table. I'm now trying to use the Vue.js version and I cannot for the life of me get this data to display at all. If I insert dummy data into the Vue Component, the dummy data shows so it must be the way I'm passing the data in.
My code as follows:
TableController.php
public function show()
{
$data = $this->fetchAWSData($condition); // This is my separate AWS method
return view('table')->with('items', $data);
}
table.blade.php
#extends('spark::layouts.app')
#section('content')
<home :user="user" inline-template>
<div class="container-fluid" style="text-align: left">
<h1>Data</h1>
<MyTable items={{ $items }}></MyTable>
</div>
</home>
#endsection
MyTable.vue
<template>
<b-table striped hover :items=items></b-table>
</template>
<script>
export default {
data() {
return {
items: this.items
}
}
}
</script>
What am I doing wrong here? I've tried formatting my data all sorts of ways; JSON, manually, Arrays... nothing works. So it must be the way I'm passing it in.
Any insight would be AMAZING :)
You have to use props to be able to pass attributes to Vue's components.
MyTable.vue
...
<script>
export default {
props: ['items'],
}
</script>
Then you can pass data to component:
<MyTable :items="{{ $items }}">

Resources