Is there a way to access the children of an astro slot? - astrojs

I am trying to use an astro slot to pass children to a SolidJS component. My Solid component uses its children to generate slides.
<Slider>
{props.children}
</Slider>
The problem is that when I try to do this, the children I pass to the component are wrapped with an <astro-slot>, so my component only "sees" one child. Is there a way to properly access the children of the slot, or to remove the wrapper tag altogether?

Passing Children to Framework Components
You can use named slots to split the child elements up like this
// src/pages/index.astro
<Slider>
<div slot="1"></div>
<div slot="2"></div>
<div slot="3"></div>
</Slider>
Then you can access the named slots using props
// src/components/Slider.tsx
export default function Slider(props) {
return (
<>
{props.1}
{props.2}
{props.3}
</>
);
}

Related

switch image when focus/unfocused in svelte

I have a svelte app where I map channelcodes to imported logos and render that logo in a div that is in a focused or unfocused state. Currently I have been mapping 1 image and changing the CSS of the logo(SVG) conditionally. But I find it kind of hacky and makes more sense to switch between a focused and unfocused versions of the logo image. The style of the element I can change, but i don't know how to switch images through my mapping (I want to avoid long if statements or switch cases as there could be many logos).
How would I switch between two images? Let me know if more information is required. (One idea I thought was an array of two images and I pass in an index 0 or 1 somehow)
TS FILE
import { Television_ChannelCode } from '#gql/schema';
import Logo1 from '#logos/Logo1.svelte';
import Logo2F from '#logos/Logo2F.svelte';
import Logo2U from '#logos/Logo2U.svelte'
const map = new Map<Television_ChannelCode, any>();
map.set(Television_ChannelCode.News, Logo1);
//I want to switch between 2, use array?//
map.set(Television_ChannelCode.Sports, [Logo2F, Logo2U]);
export const getLogoFromTelevisionChannelCode = (
televisionChannelCode: Television_ChannelCode,
): any => {
return map.get(televisionChannelCode);
};
SVELTE FILE <--- Can change style but how to swap images??
{#if hasFocus}
<div class={focusedStyle}>
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)}/>
</div>
{:else}
<div class="unfocusedStyle">
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)}/>
</div>
{/if}
In your example the if block could be avoided by combining the class
<div class={hasFocus ? 'focusedStyle' : 'unfocusedStyle'}>
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)}/>
</div>
Your approach with the array could work like this
<div>
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)[hasFocus ? 0 : 1]}/>
</div>
(if every channel had the two version array) but I think the better approach would be to stick to the one component per Logo version, handle the styling intern and export a hasFocus flag from components, so it could look like this
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)} {hasFocus}/>

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>

Array no reactive when modifying an element, only when adding or deleting

I'm doing crud of publications with Vue components and Laravel.
I have one parent component called publications.vue which has 2 childs called create.vue and list.vue, and list.vue I have another to childs called remove.vue and update.vue.
The thing is when I add or remove a publication in the array it works perfect, but when I modify an element it doesn't react. The controller works perfect, but unless I refresh I don't get anything on the screen.
This is my code:
<template>
<div class="main">
<create
:user="user"
v-if="showCreate"
#create="addPublicationOnClient"
/>
<list
v-for="(publication, index) in publications"
:key="publication.id" :publication="publication" :user="user"
#deleted="deletePublicationOnClient(index)"
#updated="updatePublicationOnClient(index, ...arguments)"
/>
</div>
</template>
addPublication(publication) {
this.publications.unshift(publication); // works perfect
},
deletePublication(index) {
this.publications.splice(index, 1); // works perfect
},
updatePublication(index, editedPublication) {
console.log(editedPublication); // shows the correct value of the edited publication
Vue.set(this.publications, index, editedPublication); // do not react. do not show anything
this.publications.splice(index, 1, editedPublication) // do not react neither. do not show anything
console.log(this.publications); // shows the correct values in the array
}
I will really appreciate any help because I really stuck and I have read a lot of posts, but can't find a solution.
Vue has some really tricky behavior when it comes to arrays of objects.
Vue is watching your array, and when the array's .length is modified or if one of its values is modified vue can "see" that change.
When you update fields of an object in the array you will not get the reactivity, because to Vue the array has not changed. This is because the array's values are references to the object, and when you update the object, those references don't change.
Your approach above seems fine to me, but again there can be really weird issues.
I will highlight two tools to combat these reactivity issues. The first is better for your situation I believe.
Explicitly modify the length of the array.
updatePublication(index, editedPublication) {
this.deletePublication(index);
this.addPublication(index, editedPublication);
}
Force re-rendering using :key. When a key changes in a template, it will force re-rendering of all child elements.
<template>
<div class="main" :key="'updated-'+updated">
...
</template>
data() {
return {
updated: 0,
};
},
...
updatePublication(index, editedPublication) {
this.publications.splice(index, 1, editedPublication);
this.updated++;
}

Vuejs how to pass component data from caller

My main page renders a list of data coming from controller with foreach
#foreach ($sales as $sale)
<button id="{{$sale->id}}" #click="editClicked({{$sale}})">
#endforeach
I have an edit component placed on the page like this, I display it modally via showEditModal conditional
<edit v-if="showEditModal" #hide="showEditModal=false"></edit>
The component in brief is declared in Edit.vue:
<template name="edit">
...
</template>
This is simply a standard form with input fields, bound via v-model to the sale.
Essentially it is an update form, I intend to load the data from the main page into each input field to be edited.
My app.js simply sets showEditModal = true, in order to display the edit form on top of the main page.
Basically i don't want to have to call controller via GET method on loading the modal since i already have the data in the main page as $sale object, so im just wondering how do I pass in the $sale to the Edit.vue component ?
I thought that in the <edit> component usage, it would need to bind the sale object, however I'm unsure how that would work since it comes from the foreach loop.
I do, also have the data in the app.js method as passed in via #click="editClicked({{$sale}})", but again, i'm unsure how to use that to pass through ?
You're right, you would want to pass the current sale item as a property to the edit modal. What I would do is add a data property to your main Vue called selectedSale.
data:{
selectedSale: null
}
Then in your editClicked method, set selectedSale
methods:{
editClicked(sale){
this.selectedSale = sale
...
}
}
Finally, pass it as a property.
<edit :sale="selectedSale" v-if="showEditModal" #hide="showEditModal=false"></edit>

Performance: Why does AngularJS evaluate watch when scope model hasn't changed?

So I have a watch on scope A. Why does AngularJS evaluate it when a local variable on sibling scope B changes? The data model of scope A hasn't changed.
Here is a minimal example of this:
there is a custom watch on scope A.
the input element is bound to the text variable of scope B.
Note that the text is not shown, because it's not visible from scope A.
Controllers:
function Ctrl1($scope) {
console.log($scope); // first scope
// my custom watch expression
var count = 0;
$scope.$watch(function() {
count++;
console.log("call count: " + count);
}, function() {
// the listener does nothing
// I'm just interested in when the watch expression is called
});
}
function Ctrl2($scope) {
console.log($scope); // second scope
}
HTML:
<div ng-app>
<div ng-controller="Ctrl1">
{{text}}
</div>
<div ng-controller="Ctrl2">
<input type="text" ng-model="text"></input>
</div>
</div>
(Feel free to try this code here: http://jsfiddle.net/s3Wz5/4/ )
If you type some text in the input element (scope B), the custom watch of the scope A is evaluated. Why is this? Why doesn't AngularJS know that there are no changes to the data model of scope A whatsoever?
Update: Some clarification:
I don't want to watch text. This is a performance related question, wondering why the watch is evaluated even though text cannot possibly be read from the other scope!
Because $watch is evaluated in every digest cycle. Change value of a ng-model triggers digest cycle. This behavior is different from $scope.$digest(), which only triggers digestion on current and child scopes.
I think ng-model might NOT use $scope.$digest() internally. It might use some other mechanism to trigger digest after value changes.

Resources