AlpineJS catch child component value if set - alpine.js

I am using AlpineJS and x-model, which works great in the following case:
<script src="https://unpkg.com/alpinejs#3.9.5/dist/cdn.min.js"></script>
<form x-data="{ value: '' }">
<input x-model="value" placeholder="Type and see..." />
<br>Value: <span x-text="value"></span>
</form>
But when I have nested components with the same property name, the value doesn't propagate to the parent component, it stays in the child component:
<script src="https://unpkg.com/alpinejs#3.9.5/dist/cdn.min.js"></script>
<form x-data="{ value: '' }">
<div x-data="{ value: '' }">
<input x-model="value" placeholder="Type and see..."/>
<br>Child value: <span x-text="value"></span>
</div>
Parent value: <span x-text="value"></span>
</form>
I know this is a very specific case. One could say: "just make everything be one component". But I cannot do that, because the nested component will be a low level generic component that I need to reuse. And I need the parent to be able to access the same value attached to the model.
Any idea on how I can approach this without using $store?

You can use the x-modelable directive that can expose any Alpine.js property as a target of x-model directive. The child component's property name must be unique. The property name in x-model on the child component should be a parameter of a backend template system' macro function.
<script src="https://unpkg.com/alpinejs#3.9.5/dist/cdn.min.js"></script>
<form x-data="{ value: '' }">
<div x-data="{ innerValue: '' }" x-modelable="innerValue" x-model="value">
<input x-model="innerValue" placeholder="Type and see..." />
<br>Child value: <span x-text="innerValue"></span>
</div>
Parent value: <span x-text="value"></span>
</form>

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.

Thymeleaf setting object attribute based on click

What I am trying to do set object user variable attribute based on click.
`
<form class="container" th:action="#{/processSignup}" method="post"
th:object="${user}">
<div class="switch">
<div class="MenteeSignUp" onclick="tab1();" th:onclick="*{}" >Mentee</div>
<div class="MentorSignUp" onclick="tab2();" th:value="MENTOR" th:field="*{userRole}">Mentor</div>
</div>
`
Trying to add different role-based what user click which either mentor or mentee which you can see from the screenshot.
I am kind of new to thyme leaf, so I tried to have th:onClick and then tried to assign it but it didn't work
form
The code you have there doesn't really make sense. <div />s do not have a value attribute, and the expression in a th:onclick must be valid javascript (instead you have a blank selection variable expressions: th:onclick="*{}"). Maybe you're looking for something like this?
<form class="container" th:action="#{/processSignup}" method="post" th:object="${user}">
<input type="hidden" th:field="*{userRole}" id="userRole" />
<div class="switch">
<div class="MenteeSignUp" onclick="document.getElementById('userRole').value = 'MENTEE';">Mentee</div>
<div class="MentorSignUp" onclick="document.getElementById('userRole').value = 'MENTOR';">Mentor</div>
</div>

Livewire + AlpineJS: Using x-data as wire:click param

I have a Laravel Blade template which has an AlpineJS div defined like this:
<div x-data="{ id: 2 }">
...
<button type="button" wire:click="deleteAddress(id)">Button</button>
</div>
What I want is somehow "pass" that id variable to the wire:click call.
The above code throws an Uncaught ReferenceError: id is not defined in my JS console.
Any ideas? Just starting with the TALL stack and I do not know the optimal workflows yet.
Thanks in advance.
You could use Alpine click listener with the magic $wire, as described here:
https://laravel-livewire.com/docs/2.x/alpine-js
This way you'll stay "inside" Alpine, but with access to your Livewire component method. So it's going to be:
<div x-data="{ id: 2 }">
...
<button type="button" #click="$wire.deleteAddress(id)">Button</button>
</div>
Add a wire:key to the same div as the x-data.
<div wire:key="id" x-data="{ id: 2 }">
...
<button type="button" wire:click="deleteAddress(id)">Button</button>
</div>
I think this is because Livewire only updates what is changing. And the x-data div is the top div of an alpine component. so if you add the id as wire:key to the div that contains the x-data then this div will also get updated and it will rerun the alpine component.

Playwright + CodeceptJS - Unable to find element by Xpath

In my code I can usually find an element by Xpath and perform actions like shown below
await I.fillField('//*[#id="edit-name"]','user1');
I am seeing the following error when I perform the following action. As the ID is dynamically created. Is there a recommended approach to tackle this type of elements?
await I.fillField('//*[#id="crmUiId_1"]','SomeTextHere');
Error:
**TypeError: Cannot read property '$$' of null
at findElements (node_modules/codeceptjs/lib/helper/Playwright.js:2087:18)
at Playwright._locate (node_modules/codeceptjs/lib/helper/Playwright.js:822:12)**
Associated HTML:
<div crm-ui-field="{name: 'caseTypeDetailForm.title', title: ts('Title')}" class="ng-isolate-scope crm-section"><div class="label">
<label crm-ui-for="caseTypeDetailForm.title" crm-depth="1" crm-ui-force-required="" for="crmUiId_1"><span ng-class="cssClasses"><span ng-transclude=""><span class="ng-binding ng-scope">Title</span></span><span crm-ui-visible="crmIsRequired" class="crm-marker ng-isolate-scope" title="This field is required." style="visibility: inherit;">*</span></span></label>
<!-- ngIf: help -->
</div>
<div class="content" ng-transclude="">
<input crm-ui-id="caseTypeDetailForm.title" type="text" name="title" ng-model="caseType.title" class="big crm-form-text ng-pristine ng-scope ng-empty ng-invalid ng-invalid-required ng-touched" required="" id="crmUiId_1">
</div>
<div class="clear"></div>
</div>
The issue here is actually the ID "crmUiId_1" is dynamically generated. Instead I tried xpath with ng-model="caseType.title" but it doesn't seem to be working either.
I would just make sure you wait for it:
await page.waitForSelector('#crmUiId_1')
await page.fill('#crmUiId_1', 'whatever')
Otherwise the page might still be loading.

What is the proper way to edit items in a listview when using Kendo UI Mobile & MVVM?

What is the proper way to edit items in a listview when using Kendo UI Mobile & MVVM?
I don't get the expected results when using the following:
HTML
<div id="itemsView"
data-role="view"
data-model="vm">
<ul data-role="listview" data-bind="source: items"
data-template="itemsTemplate">
</ul>
<script id="itemsTemplate" type="text/x-kendo-template">
<li>
#=Name#
</li>
</script>
<input type="text" data-bind="value: newValue" />
<button data-role="button" data-bind="click: update">update</button>
</div>​
JavaScript
var vm = kendo.observable({
items: [{
Name: "Item1"}],
newValue: '',
update: function(e) {
var item = this.get("items")[0];
item.set("Name", this.get("newValue"));
//adding the follwoing line makes it work as expected
kendo.bind($('#itemsView'), vm);
}
});
kendoApp = new kendo.mobile.Application(document.body, {
transition: "slide"});​
I expect the listview to reflect the change to the Name property of that item. Instead, a new item is added to the listview. Examining the array reveals that there is no additional item, and that the change was made. (re)Binding the view to the view-model updates the list to reflect the change. Re-Binding after a change like this doesn't seem to make any sense.
Here is the jsfiddle:
http://jsfiddle.net/5aCYp/2/
Not sure if I understand your question properly: but this is how I did something similar with Kendo Web UI, I expect mobile is not so different from Web UI from API perspective.
$element.kendoListView({
dataSource: list,
template: idt,
editTemplate: iet,
autoBind: true
});
The way I bind the listview is different, but I guess you can get similar results with your method as well.
I pass two templates to the list view, one for displaying and one for editing.
Display template contains a button (or any element) with css class k-edit to which kendo will automatically bind the listview edit action.
display template:
<div class="item">
# if (city) { #
#: city #<br />
# } #
# if (postCode) { #
#: postCode #<br />
# } #
<div class="btn">
<span class="k-icon k-edit"></span>Edit
<span class="k-icon k-delete"></span>Delete
</div>
</div>
Edit template
<div class="item editable">
<div>City</div>
<div>
<input type="text" data-bind="value: city" name="city" required="required" validationmessage="*" />
<span data-for="city" class="k-invalid-msg"></span>
</div>
<div>Post Code</div>
<div>
<input type="text" data-bind="value: postCode" name="postCode" required="required" validationmessage="*" />
<span data-for="postCode" class="k-invalid-msg"></span>
</div>
<div class="btn">
<span class="k-icon k-update"></span>Save
<span class="k-icon k-cancel"></span>Cancel
</div>
</div>
Clicking that element will put the current element on edit mode using the editTemplate.
Then on the editTemplate there is another button with k-update class, again to which kendo will automatically bind and call the save method on the data source.
Hopefully this will give you more ideas on how to solve your issue.
The problem was caused by the <li> in the template. The widget already supplies the <li> so the additional <li> messes up the rendering. This question was answered by Petyo in the kendo ui forums

Resources