LitElement use of template with binding - lit-element

I was wondering how to make LitElement work similar to Polymer 2/3 templatizer, i.e. grab template from light dom (childElement), templetize it and stamp it. It used to be possible and was used in elements such as dom-repeat etc, however with LitElement and its internal working with tagged string templates I do not see how to make an element accept a template to use internally, but the template being provided by the user of the shipped element (i.e. same as dom-repeat used to allow that).
I am aware of how to do it when writing the code, I just want to allow the consumer of my custom element to be able to provide the template and its bindings to work and not to subclass my element but instead use the simpler and already well known html composition from polymer2/3

You can't really do this with just lit-html, as everything is built on JS template strings. You might be interested in Stampino, which is built on lit-html. It's in pre-release as of this writing.
<template id="root">
<h1>{{ title }}</h1>
<ul>
<template type="repeat" repeat="{{ items }}">
<li>{{ toUpperCase(item) }}</li>
</template>
</ul>
</template>
<output></output>
<script type="module">
import { render } from 'stampino';
render(
document.getElementById('root'),
document.querySelector('output'),
{
letters: ['a', 'b', 'c'],
title: 'Hello World',
toUpperCase(string) {
return string.toUpperCase();
}
}
);
</script>
That would render the following to the <output> element:
<h1>Hello World</h1>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
If you're already writing HTML inside a lit-html template, however, a pattern that may fit your use is so-called "render props". Here's a simplified example:
html`
<list-renderer
.items="${[...items]}"
.template="${item => html`
<list-item>${item}</list-item>
`}"></list-renderer>
`;
in which,
class ListRenderer extends LitHTML {
render() {
return html`
<ul>
${this.items.map(item => this.template(item))}
</ul>
`;
}
}

Related

Is it possible to define slots in neos fusion afx like you can in Vue?

In Vue.js I can define named slots for my components, besides my default slot:
<article>
<header>
<slot name="header">
<h2>Default heading</h2>
</slot>
</header>
<slot/>
</article>
and then use it like this:
<template>
<FooArticle v-for="item in items">
<template #heading>
<h3>{{item}} Heading</h3>
</template>
<p>Just content</p>
</FooArticle>
</template>
<script>
export default {
name: 'App',
components: {
FooArticle
},
data() {
return {
items: ['First', 'Second']
}
}
}
</script>
Is this possible with Neos Fusion, to create a mechanism like this?
Yes this is possible, as you can use the #path decorator to overwrite a property of the wrapper element.
First you define your props and then output them in the renderer.
prototype(Foo.Components:Article) < prototype(Neos.Fusion:Component) {
heading = afx`<h2>Default heading</h2>`
content = ''
renderer = afx`
<article>
<header>
{props.heading}
</header>
{props.content}
</article>
`
}
Then you want to override these "slots" (props) from the outside with the #path decorator. The whole element the decorator is defined on will override the specified prop "heading" of the wrapping element.
prototype(Foo.Site:Home) < prototype(Neos.Fusion:Component) {
items = ${['First', 'Second']}
renderer = afx`
<Neos.Fusion:Loop items={props.items}>
<Foo.Components:Article>
<Neos.Fusion:Fragment #path="heading">
<h3>{item} heading</h3>
</Neos.Fusion:Fragment>
<p>just some content</p>
</Foo.Components:Article>
</Neos.Fusion:Loop>
`
}
FYI, we use a Neos.Fusion:Fragment object to define the path decorator, so the fragment does not render any additional markup like an enclosing <div>. In this simple case, where we only want to render a single element into the slot, we could have omitted the fragment and just set the #path="heading" directly to the <h3>.
Working example in FusionPen
Fusion AFX Docs
Neos Fusion Docs

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!
// ...
})

If element hasClass, add another class to its title value

I'm using slick carousel, and once a div is active I want to open the corresponding description.
Problem I'm having is with this code:
if ($('div').hasClass('active')) {
var title = $(this).attr('title');
$('ul li').removeClass('open');
$(title).addClass('open');
}
What I'm trying to achieve:
Once a div gets class 'active', I want to take its title value, and use it as a id link to list element I want to display(add class to).
Here is a FIDDLE.
Use event handling, not class monitoring.
The slick carousel API has events for this, I believe you want to use the afterChange event to act on the active element after it has been made visible.
Check out the docs and examples, especially the section titled "Events" on Slick page: http://kenwheeler.github.io/slick/
And I think you don't want to use title attribute for this because that is for tooltips. I recommend data-* attributes instead. And element IDs should generally start with a letter and not a number (was required in HTML4 and makes life easier when mapping IDs to JavaScript variables; though if you are using HTML5 I think this requirement is no longer in effect).
HTML
<div id="carousel">
<div data-content-id="content1">
Selector 1 </div>
<div data-content-id="content2">
Selector 2 </div>
<div data-content-id="content3">
Selector 3 </div>
</div>
<ul class="content">
<li id="content1">Content 1</li>
<li id="content2">Content 2</li>
<li id="content3">Content 3</li>
</ul>
JavaScript
$('#carousel').on('afterChange', function(event, slick, currentSlide) {
// get the associated content id
var contentId = $(slick.$slides.get(currentSlide)).data("content-id");
if(contentId && contentId.length)
{
var $content = $("#" + contentId);
$(".content>li").removeClass("open"); // hide other content
$content.addClass("open"); // show target content, or whatever...
}
});
I have found a solution:
$('.slider').on('afterChange', function(event, slick, currentSlide, nextSlide){
var contentId= $(slick.$slides.get(currentSlide)).data('content');
if(contentId)
{
$(".content li").removeClass('open');
$('#' + contentId).addClass('open');
}
});
Working fiddle

How to render the HTML into react component

I want to render the pure HTML coming from some external source into react component. I saw few solutions where people are talking about some conversion tools (HTML to JSX) but I want to handle everything in my component so while mounting it will get the HTML response and that needs to render.
You can use dangerouslySetInnerHTML for this:
function createMarkup() { return {__html: 'First ยท Second'}; };
<div dangerouslySetInnerHTML={createMarkup()} />
But as the method name suggests: you should be very sure of what you are doing there and the security implications it has.
This shouldn't be difficult to do . Assign your HTML to a div and then render it using {variable name} JSX allows you to do this and with ES6 integration you can also use class instead of className.
var Hello = React.createClass({
render: function() {
var htmlDiv = <div>How are you</div>
return <div>Hello {this.props.name}
{htmlDiv}
</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

iron:router syntax Layout

I am using iron router to render a template within meteor framwork, as i was following probably an outdated tutorial, it seems to me there is a change in syntaxes which i could not figure out.
layout.html
<div class="container">
<div class="row">
<div class="span2">
<p>cell</p>
</div>
<div class="span7">
<p>cell</p>
</div>
<div class="span3">
<p>cell</p>
</div>
</div>
</div>
index.js
function.setDefault ('category', null );
Router.configure({
layoutTemplate:'layout',
yieldTemplates:{
'products':{to:'products'},
'cart':{to:'cart'},
'categories':{to:'categories'}
}
});
Router.route(function(){
this.route('/', layout);
this.route('/products',{
data:function(){
Session.set('category',this.params.name);
},
template:'layout',
path:'/:name'
})
});
The following error occurs
unexpected token (1:8)
Where you have Router.route and use this.route in a function, Router.route should read Router.map however this is deprecated in favour of Router.route (without the map wrapper) as below:
Session.setDefault ('category', null );
Router.configure({
layoutTemplate:'layout',
yieldTemplates:{
'products':{to:'products'},
'cart':{to:'cart'},
'categories':{to:'categories'}
}
});
//You will need to declare a template at the least here so it knows what to render to main area
Router.route('/', {template: "template_name");
Router.route('/products/:name',{
onBeforeAction:function(){
Session.set('category',this.params.name);
this.next ();
},
//you don't need this to be layout
//as you are defining a default layout above
//but you will need to specify a template for the main yield area
template:'template_name'
// you don't need to specify path here as it will use '/products'
//if you want to specify a route name use line below
//,name: 'routename'
});
Where url would be /products/product_name
Where template_name is the template you want to render in your main {{> yield}}
In your layout template you need to place the following for your yields wherever you want to display them
{{> yield 'products'}}
{{> yield 'categories'}}
{{> yield 'cart'}}
{{> yield }} //this is the template you specify in template: 'template_name'
(Done from my phone so can't test but can update later if it doesn't work for you)

Resources