How to optimize the rendering of multiple instances of the same element, with the same property value - lit-element

I have a component based on lit-element that is rendering a combobox with about 200 entries. The rendering of the template is taking about 0.20s.
html`
<select>
${this.list.map((o) => html`
<option id="${index}"
#click="${this.handleClick}">${o}</option>`
)}
</select>`
I'm rendering the same component multiple times, with the same value for the property list. The rendering is taking 0.20s each time, which is quite long if I'm rendering 10 instances of the component. Is there any way to optimize this, specially when knowing that the generated template is the same for every instance of the component ?

You can use an intermediate variable
html`
<select>
${this.options}
</select>`
with a function like this
this.options = this.list.map(
(o) => html`
<option id="${index}" #click="${this.handleClick}">${o}</option>`
);

Related

A way to render multiple root elements on VueJS with v-for directive

Right now, I'm trying to make a website that shows recent news posts which is supplied my NodeJS API.
I've tried the following:
HTML
<div id="news" class="media" v-for="item in posts">
<div>
<h4 class="media-heading">{{item.title}}</h4>
<p>{{item.msg}}</p>
</div>
</div>
JavaScript
const news = new Vue({
el: '#news',
data: {
posts: [
{title: 'My First News post', msg: 'This is your fist news!'},
{title: 'Cakes are great food', msg: 'Yummy Yummy Yummy'},
{title: 'How to learnVueJS', msg: 'Start Learning!'},
]
}
})
Apparently, the above didn't work because Vue can't render multiple root elements.
I've looked up the VueJS's official manual and couldn't come up with a solution.
After googling a while, I've understood that it was impossible to render multiple root element, however, I yet to have been able to come up with a solution.
The simplest way I've found of adding multiple root elements is to add a single <div> wrapper element and make it disappear with some CSS magic for the purposes of rendering.
For this we can use the "display: contents" CSS property. The effect is that it makes the container disappear, making the child elements children of the element the next level up in the DOM.
Therefore, in your Vue component template you can have something like this:
<template>
<div style="display: contents"> <!-- my wrapper div is rendered invisible -->
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</div>
</template>
I can now use my component without the browser messing up formatting because the wrapping <div> root element will be ignored by the browser for display purposes:
<table>
<my-component></my-component> <!-- the wrapping div will be ignored -->
</table>
Note however, that although this should work in most browsers, you may want to check here to make sure it can handle your target browser.
You can have multiple root elements (or components) using render functions
A simple example is having a component which renders multiple <li> elements:
<template>
<li>Item</li>
<li>Item2</li>
... etc
</template>
However the above will throw an error. To solve this error the above template can be converted to:
export default {
functional: true,
render(createElement) {
return [
createElement('li', 'Item'),
createElement('li', 'Item2'),
]
}
}
But again as you probably noticed this can get very tedious if for example you want to display 50 li items. So, eventually, to dynamically display elements you can do:
export default {
functional: true,
props: ['listItems'], //this is an array of `<li>` names (e.g. ['Item', 'Item2'])
render(createElement, { props }) {
return props.listItems.map(name => {
return createElement('li', name)
})
}
}
INFO in those examples i have used the property functional: true but it is not required of course to use "render functions". Please consider learning more about functional componentshere
Define a custom directive:
Vue.directive('fragments', {
inserted: function(el) {
const children = Array.from(el.children)
const parent = el.parentElement
children.forEach((item) => { parent.appendChild(item) })
parent.removeChild(el)
}
});
then you can use it in root element of a component
<div v-fragments>
<tr v-for="post in posts">...</tr>
</div>
The root element will not be rendered in DOM, which is especially effective when rendering table.
Vue requires that there be a single root node. However, try changing your html to this:
<div id="news" >
<div class="media" v-for="item in posts">
<h4 class="media-heading">{{item.title}}</h4>
<p>{{item.msg}}</p>
</div>
</div>
This change allows for a single root node id="news" and yet still allows for rendering the lists of recent posts.
In Vue 3, this is supported as you were trying:
In 3.x, components now can have multiple root nodes! However, this does require developers to explicitly define where attributes should be distributed.
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
Multiple root elements are not supported by Vue (which caused by your v-for directive, beacause it may render more than 1 elements). And is also very simple to solve, just wrap your HTML into another Element will do.
For example:
<div id="app">
<!-- your HTML code -->
</div>
and the js:
var app = new Vue({
el: '#app', // it must be a single root!
// ...
})

How to focus a styled component?

I have a styled component:
const StyledComponent = styled.div`
...
`;
and I want to focus it when the component that uses it is mounted:
class someComponent extends React.Component {
componentDidMount() {
this.sc.focus();
}
render() {
return (
<StyledComponent innerRef={(elem) => { this.sc = elem; }}>
...
</StyledComponent>
);
}
}
this technique does not work - is there a solution for this?
You can't focus non-interactive elements without the use of tabIndex ("tabindex" in normal HTML). The "div" element is considered non-interactive (unlike anchor links "a" and button controls "button".)
If you want to be able to focus this element, you'll need to add a tabIndex prop. You can use .attrs if desired to have it be added automatically so you don't need to write the prop every time.
This link
has an example with both React 16 React.createRef() and the callback method.
Have you tried setting the autoFocus attribute, or is that not fit for your use case?
Try passing the autoFocus prop to your StyledComponent like <StyledComponent autoFocus />.

react-virtualized share CellMeasurerCache for multiple Grids

I got few grids side by side, and for first of them i want to calculate row heights dynamically using CellMeasurer, how it's possible to reuse CellMeasurerCache from this grid in another (to synchronize cell heights/widths)?
jsbin example
class App extends React.Component {
constructor(props) {
super(props);
this.leftHeadersCellMeasurerCache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 40
});
}
render() {
return (
<ScrollSync>
{({scrollTop, onScroll}) => (
<div className="row">
<LeftGrid
width={300}
height={500}
scrollTop={scrollTop}
cellMeasurerCache={this.leftHeadersCellMeasurerCache}
/>
<DataGrid
width={400}
height={500}
onScroll={onScroll}
rowHeight={this.leftHeadersCellMeasurerCache.rowHeight}
/>
</div>
)}
</ScrollSync>
)
}
}
PS. Unfortunately cannot use MultiGrid, data on left side is "uneven".
You can't directly share a CellMeasurerCache cache between Grids unless the content of all cells in both Grids are the same (which I doubt is ever the case).
I think you'll want to decorate the CellMeasurerCache in a similar way as MultiGrid does. Your decorator would need to decide when to pass-thru values as-is and when to add a column-offset to avoid clobbering measurements.

Alternative to detailinit event in Angular 2 Kendo grid

In the Angular 2 Kendo grid, I need to show additional info in each cell when the user opens the detail template.
In the Kendo Grid for jQuery I could use the detailinit (http://docs.telerik.com/kendo-ui/api/javascript/ui/grid#events-detailInit) event to accomplish what I need, however, there is no such event in the Angular2 component.
<kendo-grid-column>
<template kendoCellTemplate let-dataItem let-rowIndex="rowIndex">
{{rowIndex}}
<div *ngIf="????">
Need to show this text when detail template is visible
and hide when it's hidden
</div>
</template>
</kendo-grid-column>
<template kendoDetailTemplate let-dataItem>
<section *ngIf="dataItem.Category">
<header>{{dataItem.Category?.CategoryName}}</header>
<article>{{dataItem.Category?.Description}}</article>
</section>
</template>
Here is an example what I need (please see the text in the cells).
At this time, the Angular 2 grid does not provide information whether the detail template is expanded or not. Feel free to suggest this as a feature request.
HACK: To hack around this limitation, you can infer the expanded state from the HTML.
See this plunkr.
private icons(): any[] {
const selector = ".k-master-row > .k-hierarchy-cell > .k-icon";
const icons = this.element.nativeElement.querySelectorAll(selector);
return Array.from(icons);
}
private saveStates(): void {
this.states = this.icons().map(
(icon) => icon.classList.contains("k-minus")
);
}
private isExpanded(index): bool {
return this.states[index] || false;
}
While this works, it is far from ideal, and goes against the Angular philosophy, and may break upon rendering changes.

How to get values returned by child action method in mvc 5 partial view

I am trying not very successfully to get my head around MVC. My home controller contains an Index method that runs OK, so far so good, but I don't know how to call the ChildAction method Home/TopArticle
Action Method
[ChildActionOnly]
public ActionResult TopArticle()
{
return PartialView(_service.GetTopArticle());
}
In my Index view I have the mark up:
#section featured {
#Html.Partial("_TopItem")
}
_TopItem View
#model IEnumerable<MySite.Models.NewPage>
<section class="featured">
<div id="TopItem">
<div id="TopItemImg">
<a href="http://www.mysite.co.uk/">
<img style="border: 1px solid lightgray" width="320" height="233" style="border:1px solid lightgray;" alt="Model.Title" src="/Img/Model.TopItemImage">
</a>
</div>
<div id="TopContent">
<h2></h2>
<div class="dt">
<div class="dl">
#Html.Label(Model.DatePublished.ToString())
#Html.Label(#Html.Action("TopArticle", "Home", new { // am lost at this point}))
</div>
<div class="tl">
#Html.Label(Model.InfoTags ?? "")
</div>
</div>
</div>
</div>
</section>
The Index view is also using #model IEnumerable and I don't actually know whether that's OK or not. The model itself contains everything needed for both the Index and the _TopItem views, it's just that there will be one record returned for the _TopItem view and many for the Index view. Plus the code that runs in _service.GetTopArticle does some non-query stuff that is relevant only for the top article record.
I need a lie down ... and time to learn this stuff properly.
Firstly, regarding your question about calling the child action from your Index view:
Your featured section is currently calling #Html.Partial which means that it will find the "_TopItem" partial view and render it as an html encoded string in the current view (i.e. your Index view).
You specified that you are trying to call the child action TopArticle() and render the partial view returned as a html string in the view. To do this you would need to use:
#section featured {
#Html.Action("TopArticle", "Home")
}
However, I don't believe this is what you do need as you said that your Index view model contains all of the information for both Index and for the _TopItem partial view (see later).
For more information you should do a google search about the differences of views, partial views and child actions.
To correct the code I would start off by ensuring that the _TopItem partial view is correct. I have identified the following issues with the _TopItem partial view, some of which are beyond the scope of the original question:
The model passed in as an IEnumerable of NewPage but your code does not enumerate over several new page objects, it looks like it should just create the html for a single NewPage model. Therefore, I believe the model declaration should be:
#model MySite.Models.NewPage
The tag contains 2 references to the style attribute rather than 1.
The tag contains the alt attribute of alt="Model.Title" which means that alt="Model.Title" will be written directly as html where I expect you would like alt="#Model.Title" to render the contents of the model in the alt attribute.
Similarily, the tag contains src="/Img/Model.TopItemImage" where I expect this should be src="/Img/#Model.TopItemImage"
All of the label tags appear to be incorrect. For example, #Html.Label(Model.DatePublished.ToString()) - Model.DatePublished.ToString() will return a string and this string will then be attempted to be found on the model and will error as that field name does not exist. Therefore, you probably want to write: #Html.Label("DatePublished") or #Html.Label(m => m.DatePublished). With the second label i'm not sure what your trying to achieve but you may want to look up the appropriate articles.
Once, you have the corrected _TopActicle partial view, you can then return to your Index view to render the partial directly:
#section featured {
#Html.Partial("_TopItem", Model.TopArticle)
}
Note, as you have said that your Index model contains the information to pass to the _TopItem partial view, I have assumed that the Index model contains a property called TopArticle of type NewPage. Regardless, you can pass the model into the partial however you find appropriate through the call to #Html.Partial. If you pass the model through the call to #Html.Partial then you may not need the ChildOnlyAction.

Resources