Lit-html select component event handlers - html

I am using a library called lit to create custom web components and i have tried using the #change and #select event handlers to display another component with no luck. I also can't seem to find the info on the docs.
My code looks like this :
return html`
<div>
<bx-select helper-text="Optional helper text" #change=${this._updateValue} label-text="Select" placeholder="HIV Test 1 Results:">
${this.answers?.map(
(item: any) => html`<bx-select-item
label-text=${item.label}
value=${item.concept}
.selected=${this.initialTestVal == item.concept}
>
${item.label}
</bx-select-item>`)}
</bx-select>
<bx-select helper-text="Optional helper text" label-text="Select" placeholder="HIV Test 2 Results:">
${this.answers?.map(
(item: any) => html`<bx-select-item #change=${this._updateValue}
label-text=${item.label}
value=${item.concept}
.selected=${this.confirmedTestVal == item.concept}
>
${item.label}
</bx-select-item>`)}
</bx-select>
<bx-select helper-text="Optional helper text" label-text="Select" placeholder="HIV Test 3 Results:">
${this.answers?.map(
(item: any) => html`<bx-select-item
label-text=${item.label}
value=${item.concept}
.selected=${this.finalTestVal == item.concept}
>
${item.label}
</bx-select-item>`
)}
</bx-select>
</div>`;
Any help/ advise on this will be appreciated.

Based on the name <bx-select> I'll assume you're using Carbon web components.
Unfortunately it doesn't look like it's listed in the doc but the event name that's fired when you select appears to be bx-select-selected so you'd want to add an event listener with #bx-select-selected.
This can be seen here https://web-components.carbondesignsystem.com/?path=/story/components-select--default when you select an option and see the "Actions" tab below.
You can also see the component's source code to see where the event is dispatched here https://github.com/carbon-design-system/carbon-web-components/blob/c318f69d726a72f006befc7aa46b76b33695d07f/src/components/select/select.ts#L62 and the name is defined here https://github.com/carbon-design-system/carbon-web-components/blob/c318f69d726a72f006befc7aa46b76b33695d07f/src/components/select/select.ts#L387.

Related

Routing to a component and subscribing in the same component

I have an Album component which has a series of images displayed. When the user clicks on an image, a method is called which emits a BehaviorSubject (using next) and routes to another component:
Album component html:
<ul class="container">
<li *ngFor="let album of albums">
<img class="album-cover" [src]="album.albumCoverPhotoUrl" alt="album.name" (click)="onAlbumSelect(album)">
<p class="album-name">{{ album.name }}</p>
</li>
</ul>
Album component.ts:
onAlbumSelect(album: Album) {
this.router.navigate(['gallery-list']);
this.albumsService.fromAlbum.next(album);
setTimeout(() => {
this.albumsService.fromAlbum.next(null);
}, 100);
}
In my gallery list component (the component I navigate to above), I subscribe to the BehaviorSubject and filter an array of images using the album name that I get from the emitted data:
ngOnInit() {
this.albumSub = this.albumsService.fromAlbum.subscribe(album => {
this.selectedAlbum = album;
if(this.selectedAlbum) {
this.galleryList = this.images.filter(e => e.album === this.selectedAlbum.name);
}
}
I am using a behavior subject as previously I used a subject instead but this didn't work as I believe the subject was being emitted before the subscription was initialized on the gallery-list component (therefore it was missing the data emitted from the subject).
As you can see from the onAlbumSelect method from within the Album component, I am using a setTimeout method to emit a null value to reset the behviorSubject value so that it doesn't affect other parts of my code (the gallery-list component is also used to display a full list of images rather than just images from 1 album, I have left this part of the code out to as I didn't feel it was necessary to include it).
This feels somewhat 'hacky' and I am just wondering if anyone can think of a better way to approach this?
I would assume that emitting a subject, routing to a component and subscribing to the subject from within the component you are routing to is something that is done frequently in Angular apps so is there a way of doing this using just a subject (rather than a behaviorSubject) but ensuring the subscription is setup before the value is emitted? Sorry for the long post!

React treats Timeline element as null even with a condition check

I am using the react-event-timeline lib (here: https://www.npmjs.com/package/react-event-timeline) to create a timeline for one of my pages.
I create a list of events called: events and pass it to the timeline. The timeline consists of two parts the first is the initial event, when the page is created there is a timeline event at the top of each timeline to catch that. The second part is dynamically generated based on the list of events provided by the state of the page.
React throws this timeline error and based on debugging I think the issue is with the structure of the html below where The && checks are:
The error I get is:
React.cloneElement(...): The argument must be a React element, but you passed null.
The above error occurred in the component:
in Timeline (created by TimelineComponent)
<Timeline className={styles.timeline}>
{(data) && (<TimelineEvent
createdAt={Moment(data.creationDateTime).fromNow()}
icon={<ActionIcon variant={'create'} fontSize={18} />}
iconStyle={this.bubbleStyles('create').icon}
bubbleStyle={this.bubbleStyles('create').bubble}
subtitle={this.emptyContainer()}
contentStyle={contStyle}
title={this.creationTitle(data.user)}/>)}
// ^^^ the creation event
// vvv the dynamic part of the timeline. I map each element in events to a timelineEvent
{events && (events.map((event, index) => (
<TimelineEvent
key={index}
createdAt={Moment(event.creationDateTime).fromNow()}
icon={event.type && <ActionIcon variant={event.type} fontSize={18} />}
iconStyle={event.type && this.bubbleStyles(event.type).icon}
bubbleStyle={event.type && this.bubbleStyles(event.type).bubble}
subtitle={this.emptyContainer()}
contentStyle={contStyle}
title={this.titleElement(event, index)}>
{this.showInfoBox(event.type) &&
(<div className={styles.infoBox}>
<div className={styles.infoBoxBody}>
{this.getInfo(event.type)}
</div>
</div>
)}
<div className={styles.commentBox}>
{event.comment}
</div>
</TimelineEvent>
)))}
</Timeline>
null && something resolves to null.
It looks like <Timeline> can't accept null as its props.children. Try to prepare events before rendering <Timeline>:
let allEvents = [];
if (data) allEvents.push(/*something*/)
if (events) {
allEvents = allEvents.concat(events.map(/*something*/));
}
...
{allEvents.length > 0 && (
<Timeline>
{allEvents.map(event => <TimelineEvent/>)}
</Timeline>
)}
Might be a bug https://github.com/rcdexta/react-event-timeline/blob/master/components/Timeline.js#L8
const childrenWithProps = React.Children.map(children, child => React.cloneElement(child, { orientation }))
My solution for this is when the value is null, just return an empty div and when you run it, it will work fine.
I think the solution above will cause performance issue since it looped twice.
I hope it helps.

Message boxes in Polymer applications

I am working on my first bigger Polymer application and currently have around 30 components. Most of the components need to be able to display (modal) message boxes. For this I implemented a message box component wrapping paper-dialog (similar to other message box components available).
What I don't like is that in every component which wants to display message boxes I need to define an element
<my-message-box id="message-box"></my-message-box>
and then call it like this
this.$["message-box"].information("Something happened...");
This works but my gut feeling is that a message box should be more like a global service, a singleton maybe. In C# f.e. there exists a static method on the MessageBox class.
Is the above mechanism really the recommended way to do it or are there better solutions to it?
My current approach is to create error-dialog and add it as a sibling to my main-app in index.html:
<body>
<main-app></main-app>
<error-dialog></error-dialog>
<noscript>
Please enable JavaScript to view this website.
</noscript>
</body>
error-dialog's ready() method adds a custom event:
ready() {
super.ready();
this.addEventListener('o_error', e => this._errorListener(e));
}
_errorListener(e) {
this.o_error = e.detail;
this.$.errorDlog.open();
}
Now I can open error-dialog from anywhere with
let msg = ...
const dlog = document.querySelector('error-dialog');
dlog.dispatchEvent(new CustomEvent('o_error', {detail: msg, bubbles: true, composed: true}));

How to fire an event whenever `<my-view#>` is active (i.e. comes into view)?

Using Polymer Starter Kit as an example, I would like to have different <app-toolbar> in <my-app> (using property headerType) based on different <my-view#>, i.e.
<my-view1> => headerType = 'my-view1-header'
<my-view2> => headerType = 'my-view2-header'
In my <my-app>, I have created a property headerType and use <dom-if> to show/hide different <app-toolbar>.
My question is how would I always fire an event to <my-app> and set headerType = my-view#-header whenever <my-view#> is active (i.e. comes into view).
I have tried the polymer lifecycle, such as ready(), attached(), etc, and I understand they are only trigger during dom-related events.
I eventually use the _pageChanged observer to call a function on <my-view#>. Below are the snippet of the code.
_pageChanged: function(page) {
let onLoad = function () {
let selected = this.$.ironpages.children[page];
if (Object.getPrototypeOf(selected).hasOwnProperty('viewSelected')) {
selected.viewSelected();
}
}
// Load page import on demand. Show 404 page if fails
var resolvedPageUrl = this.resolveUrl('my-' + page + '.html');
this.importHref(resolvedPageUrl, onLoad, this._showPage404, true);
},
There is some example in Polymer shop template where you can execute something when the visibility of your view change with iron-pages.
you just need to add a property for example visible in each of your view element with Boolean type and observe that property to check whatever the view is visible or not, and then in your iron-pages you need to add selected-attribute property and the value is visible. check Polymer Shop Template.

Handling events from a Kendo MVC Grid's PopUp editor window

I have a Kendo MVC grid that I am creating with the Html.Kendo().Grid helper. When the PopUp editor window opens, I want to catch the event and run a bit of javascript. When I configure a normal kendo window with .Events, the events fire properly and my function runs. However, when I code the .Events property on the .Editable.Window of the grid, the events do not fire.
#(Html.Kendo().Grid<FooRecord>()
.Name("cFooGrid")
.Columns(c =>
{
c.Bound(f => f.Foo);
c.Bound(f => f.Bar);
c.Bound(f => f.Bas);
c.Command(a => a.Edit());
})
.Editable(e => e
.Mode(GridEditMode.PopUp)
.Window(w => w.Events(v => v.Open("OnEditStart").Activate(#<text>function () {console.log("EditWindow.Activate")}</text>)))
)
.ToolBar(t =>
{
t.Create();
})
.DataSource(ds => ds
.Ajax()
.Create(r => r.Action("UpdateIndex", "Home"))
.Read(r => r.Action("IndexList", "Home"))
.Update(u => u.Action("UpdateIndex", "Home"))
.Model( m => {
m.Id(f => f.Foo);
})
)
)
When I review the generated code in Chrome's developer tools, the window is generated without the Activate or Open features:
jQuery(function(){jQuery("#cFooGrid").kendoGrid({"columns":[{"title":"Foo Key","field":"Foo","encoded":true,"editor":null},{"title":"Bar Field","field":"Bar","encoded":true,"editor":null},{"title":"Bas Value","field":"Bas","encoded":true,"editor":null},{"command":[{"name":"edit","buttonType":"ImageAndText","text":"Edit"}]}],"scrollable":false,"editable":{"confirmation":"Are you sure you want to delete this record?","confirmDelete":"Delete","cancelDelete":"Cancel","mode":"popup","template":"\u003cdiv class=\"editor-label\"\u003e\u003clabel for=\"Foo\"\u003eFoo Key\u003c/label\u003e\u003c/div\u003e\u003cdiv class=\"editor-field\"\u003e\u003cinput class=\"k-textbox\" id=\"Foo\" name=\"Foo\" /\u003e\u003cspan class=\"field-validation-valid\" data-valmsg-for=\"Foo\" data-valmsg-replace=\"true\"\u003e\u003c/span\u003e\u003c/div\u003e\u003cdiv class=\"editor-label\"\u003e\u003clabel for=\"Bar\"\u003eBar Field\u003c/label\u003e\u003c/div\u003e\u003cdiv class=\"editor-field\"\u003e\u003cinput class=\"k-textbox\" data-val=\"true\" data-val-maxlength=\"The field Bar Field must be a string or array type with a maximum length of \u0026\\#39;20\u0026\\#39;.\" data-val-maxlength-max=\"20\" id=\"Bar\" name=\"Bar\" /\u003e\u003cspan class=\"field-validation-valid\" data-valmsg-for=\"Bar\" data-valmsg-replace=\"true\"\u003e\u003c/span\u003e\u003c/div\u003e\u003cdiv class=\"editor-label\"\u003e\u003clabel for=\"Bas\"\u003eBas Value\u003c/label\u003e\u003c/div\u003e\u003cdiv class=\"editor-field\"\u003e\u003cinput class=\"k-textbox\" data-val=\"true\" data-val-required=\"The Bas Value field is required.\" id=\"Bas\" name=\"Bas\" /\u003e\u003cspan class=\"field-validation-valid\" data-valmsg-for=\"Bas\" data-valmsg-replace=\"true\"\u003e\u003c/span\u003e\u003c/div\u003e","window":{"title":"Edit","modal":true,"draggable":true,"resizable":false},"create":true,"update":true,"destroy":true},"toolbar":{"command":[{"name":null,"buttonType":"ImageAndText","text":"Add new record"}]},"dataSource":{"type":(function(){if(kendo.data.transports['aspnetmvc-ajax']){return 'aspnetmvc-ajax';} else{throw new Error('The kendo.aspnetmvc.min.js script is not included.');}})(),"transport":{"read":{"url":"/Home/IndexList"},"prefix":"","update":{"url":"/Home/UpdateIndex"},"create":{"url":"/Home/UpdateIndex"}},"serverPaging":true,"serverSorting":true,"serverFiltering":true,"serverGrouping":true,"serverAggregates":true,"filter":[],"schema":{"data":"Data","total":"Total","errors":"Errors","model":{"id":"Foo","fields":{"Foo":{"type":"string"},"Bar":{"type":"string"},"Bas":{"type":"string"}}}}}});});
Or, more specifically:
"window":{"title":"Edit","modal":true,"draggable":true,"resizable":false}
I would expect that the window would be generated with Activate: and Open: parameters, but they don't show up. Can anyone give me a pointer as to whether this just isn't supported or I am doing something wrong?
Edit:
So in order to capture the events as above, there are two steps:
Add this to the grid definition (remove the Window .Events)
.Events(e => e.Edit("OnEditStart"))
Then add a javascript function like this to the page.
function OnEditStart(pEvent) {
var editWindow = pEvent.container.data('kendoWindow');
editWindow.bind('activate', function () {
console.log('Edit start event fired');
});
}
NOTE: There does not appear to be any way to capture the open event since this event is fired on the window before the edit event on the grid.
The "events" of the kendo grid popup are not honoured/serialized (at least not the last time I tested this back in 2014) and so you should use the grid's Edit event to control the "Pop Up" window events
So within your grid add this:
.Events(event => event.Edit("onEdit"))
.//other grid settings here.
Then add a javascript function like this:
function onEdit(e) {
//get window object
var kendoWindow = e.container.data("kendoWindow");
kendoWindow.setOptions({
title: "I have a custom Title"
//do stuff in here
});
}
Then you can apply what ever functions you want to the window via javascript.
I do something similar to this to resize the pop up editor so it takes up 80% of the screen size regardless of the display/device.
If you have something more specific you are after then I will update my answer accordingly.
edit: If you want you can refer to this post from Telerik's own forums which is what I used when I first encountered this issue back in mid 2014.
Kendo Pop Up Editor not firing off applied events