How to add multiple conditional classes while migrating Vue to React - javascript

While migrating vue component to React, i am not able to understand that how can i apply those multiple classes in same div tag.
In Vue,
<ul>
<li v-for="m in Menus" #click="moveMenu(m)" class="primaryClass"
:class="{'ur-primay__selected':selectedMenu(m)}"> // selectedMenu() is a method
{{m.name}}
</li>
</ul>
My query is how can i apply this additional non-prop class attribute adding into the tag in React?
I have tried using template literal to add the classes but not able to do that.

classnames utility serves the same purpose in React and accepts an input that is similar to Vue class bindings and allows for a mix of strings, arrays and objects.
In Vue:
<li :class="['primaryClass', {'ur-primay__selected': selectedMenu(m)}]">
In React, an array can be flattened because it's possible to provide multiple arguments to the helper:
<li className={classnames('primaryClass', {'ur-primay__selected': selectedMenu(m)})}>

Using string template with backticks ``:
<div className={`${selectedMenu(m)?'ur-primay__selected':''}`}>

Related

How can I output the <template /> tag to the DOM in Vue.js

I have a custom library built on Stencil that I need to integrate with Vue, and this library relies on the browser's <template /> tag.
For example, I need to render the following code to the dom:
<my-custom-list>
<template>
<my-custom-title></my-custom-title>
<my-custom-description></my-custom-description>
</template>
</my-custom-list>
my-custom-list will then get whatever is inside the template and use it for each of the items in the list.
I managed to get it to work by using the v-html directive, for example:
<my-custom-list v-html="<template><my-custom-title></my-custom-title><my-custom-description></my-custom-description></template>">
</my-custom-list>
However, I would also like to be able to use Vue components inside this template tag, like:
<my-custom-list>
<template>
<my-custom-title></my-custom-title>
<my-custom-description></my-custom-description>
<custom-vue-component></custom-vue-component>
</template>
</my-custom-list>
Some old answers online suggest using the is directive to be able to output the template tag, like <div is="template"> but that has been deprecated.
I am not even sure if this is possible, but any insights would be much appreciated.
You can use the v-pre directive to skip compilation for parts of your components.
<my-custom-list v-pre>
<template>
<my-custom-title></my-custom-title>
<my-custom-description></my-custom-description>
<custom-vue-component></custom-vue-component>
</template>
</my-custom-list>

Can I change element tag name with Polymer data binding?

I have this application which contains many types of Polymer elements that can be added to a main Polymer app element. This main element manages instances of these elements and shows them in a UI.
eg.
item1.html
item2.html
item3.html
my-app.html
As I add new types of items (eg. item4.html), I need to make several changes to the main UI to handle creating, managing, and showing them. Each type is unique enough that I do not want to merge them into a single item type.
What I'd like to do is have each Polymer element 'register' itself into my-app by calling a function which can add a new object to an array.
To do this, my-app will have a property called itemMap which is an array of objects. One property in this object is the type of item.
itemMap: [
{
type: 'item-1',
instances: []
}, {
type: 'item-2',
instances: []
}
...
]
This implementation works in code. When adding a new instance, I can add a new object to the instances array for that type. However, I do not know how to show the items in the UI. As each type is a different Polymer element, I cannot use a simple dom-repeat template. At the same time, I do not want to hardcode each type in the main UI to improve modularity.
Right now I have:
<iron-list id="item-1-list" items="[[item1_array]]" as="item" grid>
<template>
<div class="item">
<item-1 properties=[[item]]></item-1>
</div>
</template>
</iron-list>
<iron-list id="item-2-list" items="[[item2_array]]" as="item" grid>
<template>
<div class="item">
<item-2 properties=[[item]]></item-2>
</div>
</template>
</iron-list>
What I want to do is something like the snippet below, which would work for any type I create.
<template is="dom-repeat" items="{{itemMap}}" as="itemType" id="item-grid">
<iron-list id="[[itemType.type]]-list" as="item" grid items="[[itemType.instances]]">
<template>
<div class="item">
<[[itemType.type]] properties=[[item]]></[[itemType.type]]>
</div>
</template>
</iron-list>
</template>
However, this does not work.
Is this possible, or something equivalent, or am I going down the wrong path completely?
Polymer data binding does not work for tag names. What you might do to implement this kind of behavior is to create another custom element that accepts the item type as a property and dynamically creates an element of that type:
<template is="dom-repeat" items="{{itemMap}}" as="itemType" id="item-grid">
<iron-list id="[[itemType.type]]-list" as="item" grid items="[[itemType.instances]]">
<template>
<div class="item">
<x-item-selector type=[[itemType.type]] properties=[[item]]></x-item-selector>
</div>
</template>
</iron-list>
</template>
There could be several ways to implement the x-item-selector element: declarative and imperative:
Declarative: Create a set of <dom-if>s--one per type
If there are only a few element types and you can list them all, you could create a template for the x-item-selector like below:
<template>
<template is="dom-if" if="[[_isEqual(type, 'item-1')]]" restamp>
<item-1 properties="[[properties]]"></item-1>
</template>
...
</template>
Imperative: Observe the type property and update the child element manually
If you are going to support many types of elements, you might want to avoid a large set of <dom-if>s, and update the children of the x-item-selector element imperatively whenever the type property changes. As a downside, the mapping for the properties property you'll also have to establish manually.
_onTypeChanged(newType, oldType) {
if (newType !== oldType) {
for (let child of this.children) {
this.removeChild(child);
}
const newChild = document.createElement(newType);
newChild.properties = this.properties;
// also need to add some code to update newChild.properties
// when this.properties change
this.appendChild(newChild);
}
}

Using custom element content as item template

I'm writing reusable components for our internal framework that abstract away some monkey code. Most of the scenario's are implemented with slots and work great. However, some scenario's require rendering templates inside for loops, and unfortunately slots aren't supported there.
I came up with the following (working) code:
<template>
<div class="form-group">
<label for.bind="titleSafe" class="control-label">{title}</label>
<select id.bind="titleSafe" value.bind="value" class="form-control">
<option repeat.for="item of itemsSource" >
<template replaceable part="item-template" containerless>${item}</template>
</option>
</select>
</div>
</template>
This code has, IMO, multiple issues that make it a bad candidate for including it in a framework:
It doesn't support default templates like slots does, so when you have only 1 replacable part the syntax is needlessly verbose
Having to use 2 different templating systems (slots + replace-part) in my project seems really counter intuitive and will certainly create confusion/bugs in my dev team
When you use template parts in the example I provided above, you need to know I declared 'item' as iterator in my for loop in order to construct your template correctly
Therefore I went looking for alternatives. After some research I came up with something like this:
<template>
<div class="form-group">
<label for.bind="titleSafe" class="control-label">{title}</label>
<select id.bind="titleSafe" value.bind="value" class="form-control">
<option repeat.for="item of itemsSource" >
<!-- I want to insert my custom element here -->
</option>
</select>
</div>
<slot></slot>
</template>
The above is my select-item custom element. Then I would also create another custom element for the templating of the repeatable item, like select-item-template, I would then use the two together like this:
<select-item title="myTitle" items-source="myItems">
<select-item-template><span>${myItemsProperty}</span></select-item-template>
</select-item>
The strength of this approach would be that you can create complex 'root' custom elements with one default slot. In this slot, you could then define multiple custom 'child' elements that the root element can search for when it's initialized (I know you can do this with the #child and #children decorators, so that part is covered). I'm a bit lost on how I would have to use these custom child element's content in my root custom element though.. How would I take my span element in the above example and prepare it's content to be rendered in the repeater? And would it be possible to take the repeated item set it as the template's datasource so I don't have to specify itemin my templates?
I hope I didn't make this too verbose, but I wanted to explain what my functional requirement is. If you have any resource that can point me in the right direction I would be very grateful!
Use the processContent attribute to transform the element content into a part replacement. The component will still use replace-part internally but consumers of the component won't be exposed to this implementation detail.
https://gist.run?id=2686e551dc3b93c494fa9cc8a2aace09
picker.html
<template>
<label repeat.for="item of itemsSource" style="display: block">
<input type="radio" value.bind="item" checked.bind="value">
<template replaceable part="item-template">${item}</template>
</label>
</template>
picker.js
import {bindable, processContent} from 'aurelia-templating';
import {bindingMode} from 'aurelia-binding';
import {FEATURE} from 'aurelia-pal';
#processContent(makePartReplacementFromContent)
export class Picker {
#bindable itemsSource = null;
#bindable({ defaultBindingMode: bindingMode.twoWay }) value = null;
}
function makePartReplacementFromContent(viewCompiler, viewResources, element, behaviorInstruction) {
const content = element.firstElementChild;
if (content) {
// create the <template>
const template = document.createElement('template');
// support browsers that do not have a real <template> element implementation (IE)
FEATURE.ensureHTMLTemplateElement(template);
// indicate the part this <template> replaces.
template.setAttribute('replace-part', 'item-template');
// replace the element's content with the <template>
element.insertBefore(template, content);
element.removeChild(content);
template.content.appendChild(content);
return true;
}
}
usage
<template>
<require from="picker"></require>
<h1>Default Item Template</h1>
<picker items-source.bind="colors" value.bind="color"></picker>
<h1>Custom Item Template</h1>
<picker items-source.bind="colors" value.bind="color">
<em css="color: ${item}">
${item}
</em>
</picker>
</template>

Angular2 [ngClass] - Combining direct binding with conditional classes

I currently have two different working implementations of [ngClass] on an element;
[ngClass]="{ selected: element.isSelected, highlighted: element.isHighlighted}"
and
[ngClass]="element.customClasses"
Is it possible to combine both of these approaches in the template, or do I have to create a method in my component to return an array of classes based on the logic above?
Thanks!
I opted for using [class.*] to set the conditional classes, leaving [ngClass] to handle the binding;
<div
[ngClass]="element.customClasses"
[class.selected]="element.isSelected"
[class.highlighted]="element.isHighlighted"
></div>
You can directly set the customClasses to your template as
<span class="customClasses">something</span>
And also you can use [ngClass] which will append your classes based on the condition, so putting together
<span [ngClass]="{ selected: element.isSelected,
highlighted: element.isHighlighted}"
class="customClasses">something
</span>

Custom template(transclusion without ng-content) for list component in Angular2

I have a list component which shows only names. list component should be able to take custom template which will be given by user.
List Component
import {Component } from 'angular2/core';
#Component({
selector: 'my-list',
template: `<p>This is List</p>
<ul>
<li *ngFor="#i of data"><div class='listItem'>{{i.name}}</div></li>
</ul>`
})
export class MyList implements OnInit{
data: Array<any> = [{name: 'John', age: 26},{name: 'Kevin', age: 26}, {name:'Simmons', age:26}];
}
My Requirement
<my-list>
<div>{{i.name}}-{{i.age}}</div> //user should be able to provide custom template like this
</my-list>
I tried this with ng-content but it throws error. In angular 1 same thing used to work with transcluded content. do we have any alternative of manual transclusion in angular 2 and if not then how could we implement this feature in angular2.
Here is Plunker
You need to use ngForTemplate, I've created PrimeNG DataList and many other DataComponents using this technique and it works great. Demo;
http://www.primefaces.org/primeng/#/datalist
Code;
https://github.com/primefaces/primeng/blob/master/src/app/components/datalist/datalist.ts
In your component, define a templateRef with contentchild;
#ContentChild(TemplateRef) itemTemplate: TemplateRef;
Your template becomes;
template: `<p>This is List</p>
<ul>
<template ngFor [ngForOf]="data" [ngForTemplate]="itemTemplate"></template>
</ul>`
So that your users can define content like;
<my-list>
<template #anything>
<div>{{anything.i.name}}-{{anything.i.age}}</div>
</template>
</my-list>
There was a question regarding this in the past (see Use content of component template in angular 2) and this doesn't seem to be supported.
There are two things here:
When you provide an input template for the component, your i is variable is evaluated against the current component and not my-list one. If you want to use its properties you must do something like that:
<my-list #myList>
<div>{{myList.i.name}}-{{myList.i.age}}</div> //user should be able to provide custom template like this
</my-list>
The other problem is the ability to use ng-content within a loop and it's not supported. I think that we could add an issue for this...
Here is a the plunkr I used for my tests: https://plnkr.co/edit/a06vVP?p=preview.
You can find a short interesting guide which shows you how to build such a list-component with custom-template
here.

Categories