Bind dictionary of attributes on element definition - javascript

I am trying to bind a dictionary of attributes like {'id': 'myid', 'class': 'my-1 pr-md-2', ...} at the time I am defining the element. I do not want to set that attributes after the DOM is loaded by Javascript.
I am struggling with the correct form of doing this. I can not bind them one by one declaring the attribute name and value manually as they are user given parameters. I have think of appending them to the attributes property, but I do not know if it is advisable to do it this way.
This is my data structure:
<template is="dom-repeat" items="{{links}}" as="link">
<a class="nav-item nav-link mr-md-2" on-tap="changePage">{{link.title}}</a>
</template>
and the attributes are saved in each link.attributes property. My solution would be something like this:
HTML
<template is="dom-repeat" items="{{links}}" as="link">
<a attributes={{appendAttributes(link)}} class="nav-item nav-link mr-md-2" on-tap="changePage">{{link.title}}</a>
</template>
JS
appendAttributes: function(link){
//Get current attributes of the element and append the ones in link.attributes
}
Is this the correct way to handle it?

As far as I know this is not possible with Polymer's templating system: there's no way to access the element to which a computed binding is applied to.
This
<a attributes={{appendAttributes(link)}}></a>
can't work because the attributes property is read-only.
I can not bind them one by one declaring the attribute name and value manually as they are user given parameters
Actually if you know in advance what attributes/properties have to be set you can still set them dynamically:
<a id=[[userGivenId]]
class$=[[userGivenClass]]
...
></a>
Anyway, there is a lit-html directive made by open-wc called spread which does just what you want. This would require rewriting your component using LitElement to something like this:
import { LitElement, html, property, customElement } from 'lit-element';
import { repeat } from 'lit-html/directives/repeat';
import { spread } from '#open-wc/lit-helpers';
#customElement('my-element')
export class MyElement extends LitElement {
#property() userGivenId;
#property() links;
// ...
render() {
return html`
${repeat(this.links, link => html`
<a ...=${spread({
id: this.userGivenId,
'?my-boolean-attribute': true
'.myProperty': { foo: 'bar' },
'#my-event': () => console.log('my-event fired'),
})}
class="nav-item nav-link mr-md-2"
#click=${e => this.changePage(e)}
>${link.title}</a>
`)}
`;
}
}
With some limitations, PolymerElements and LitElements can coexist in the same project so converting a single component shouldn't cause any trouble.

Related

Check if an element contains a css class in React

There are multiple tabs like this:
<Menu.Item className="tab-title tab-multiple" key="key">
<p className="tab-title-text">
Tab title
<span className="items-counter">{showText}</span>
</p>
</Menu.Item>
the one that is the active/selected one, beside of its original class (tab-title tab-multiple) it also has active and its class looks like this: active tab-title tab-multiple
I want to show that element only if the class contains "active".
Is there a way to do this in React? Without taking in account onClick.
Tried with a ternary but it seems it does not work:
{element.classList.contains('active') ? (
<span className="items-counter">{showText}</span>
) : (<></>)}
Normally, you don't have to do that in React because you drive the classes on the element from state information in your component, and so you just look at that state information rather than looking at the class list. Your best bet by far is to do that, rather than accessing the DOM class list later.
If the active class is being added by something outside the React realm that's operating directly on the DOM element, you'll have to use a ref so you can access the DOM element.
To create the ref:
const ref = React.createRef();
To connect it to your React element, you add the ref property;
<Menu.Item className="tab-title tab-multiple" key="key" ref={ref}>
Then when you need to know, you check the current property on the ref:
if (ref.current && ref.current.classList.contains("active")) {
// ...
}
Beware that if you do that during a call to render (on a class component) or to your functional component's function, on the first call the ref will be null and on subsequent calls it'll always refer to the element for the previous version of the component. That element will probably get reused, but not necessarily.
React is driven by the data model (props & state). Use whatever data property you use to assign the active class name, to also hide/show the contents.
Another option is to use CSS:
.items-counter {
color: red;
}
.tab-title:not(.active) .items-counter {
display: none;
}
<div class="tab-title tab-multiple" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Not Active</span>
</p>
</div>
<div class="tab-title tab-multiple active" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Active</span>
</p>
</div>
You need to have an indicator, that maintains the active class.
let className ="";
if(isActive)
{
className = className +" active"; // props.isActive in case of child component
}
Now that you have added the className based on the flag.
instead of checking for,
if(element.classList.contains('active'))
you can check for,
if(isActive)
This is applicable for subcomponents also, where you read the isActive flag through props.

Understanding Vue.js CSS Class Binding Ordering

Can anyone help me understand how to control the ordering of a component root element css class and any css class that may be bound from the parent calling the component?
Here is a fiddle that depicts what I'm noticing (snippet example below):
https://jsfiddle.net/cicsolutions/b6rnaw25/
You'll notice if you have a component with a class on its root element, if that class is a string, Vue's class binding places the class at the beginning of the resulting bound class list. This is what I would expect because because the component sets the base css class and then you can customize the styles when you use the component by adding classes to the component html element. Then Vue binds/joins the classes together.
In the next examples in the fiddle, I'm showing the use of a css class that is dynamic (i.e. not a static string). In these cases, Vue places the component's root element class at the end of the bound class list.
I'm working on a component that I hope others will use, so I'd like to set my component class on the root element, and then if anyone wants to override those styles, they can just add their own class on the component tag.
I also need the root element class to be dynamic, so I must use an array or an object to handle the class binding.
Does anyone know why Vue places the component root css class at the beginning for static classes and at the end for dynamic classes? That seems strange to me, but perhaps it's intentional for a reason that eludes me.
None the less, how would I go about ensuring that my component's root element class is always first in the resulting bound class list, when I need it to be a dynamic class?
Vue.directive('bound-class', (el) => {
const boundClass = el.attributes.class.nodeValue
const boundClassPrintout = document.createElement('div')
boundClassPrintout.innerHTML = 'Resulting Bound Class: ' + boundClass
el.appendChild(boundClassPrintout)
});
// STATIC CSS CLASS -> becomes 1st class in bound class list (expected)
Vue.component('string-test', {
template: `<div class="string-class" v-bound-class><slot></slot></div>`
});
// DYNAMIC CSS CLASS -> becomes last class in bound class list (unexpected)
Vue.component('array-test', {
template: `<div :class="['array-class']" v-bound-class><slot></slot></div>`
});
// DYNAMIC CSS CLASS -> becomes last class in bound class list (unexpected)
Vue.component('object-test', {
template: `<div :class="{ 'object-class': true }" v-bound-class><slot></slot></div>`
});
new Vue({
el: "#app",
computed: {
vueVersion() {
return Vue.version
}
}
})
body {
background: #20262E;
padding: 20px;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
}
h2 {
margin-bottom: 0.75rem;
}
<link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Vue version: {{ vueVersion }}</h2>
<string-test class="p-2 mb-2 border">Root class (string-class) at beginning (expected)</string-test>
<array-test class="p-2 mb-2 border">Root class (array-class) at end (unexpected)</array-test>
<object-test class="p-2 mb-2 border">Root class (object-class) at end (unexpected)</object-test>
</div>
I suspect that there's no particular reason why Vue inserts static classes first; possibly it's just mirroring the order of the input parameters in the renderClass function.
Also the order of rule sets in CSS files matters; the order of class names in the class attribute of elements does not. And neither order has anything to do with the cascade, which refers to child elements inheriting styles from their parents. Perhaps you've confused that with the order of declarations within a block or within an inline style. In that case order does matter:
<p class="red blue">
Order doesn't matter in the class attribute above. If
the class styles contradict, whichever is defined last
will win regardless of how they're ordered in the attribute.
</p>
<p class="blue red">
This paragraph will be styled identically to the previous
one, despite the change in class order.
</p>
<p style="color: red; color: blue">
Order does matter here. The text color will be blue.
</p>

How to find class name from inspected code

When I inspected my web page I got this code
<li id="SR_R1_tab" class="t-Tabs-item a-Tabs-before a-Tabs-selected is-active" aria-controls="SR_R1" role="tab" aria-selected="true">
It is an oracle apex tabular region's sub region. I want to perform some css modifications and javascript actions in this class(sub regions of tabular region). How to find the exact class from this inspected code?. Should I use the class 't-Tabs-item' or 't-Tabs-item a-Tabs-before a-Tabs-selected is-active' ?
This element has 4 css classes:
t-Tabs-item
a-Tabs-before
a-Tabs-selected
is-active
In the debugger, you need to observe what each class does to your UI and you can decide which class to override.
Example: if you want to change the styling for each element, you will probably have to override t-Tabs-item:
.t-Tabs-item {
background: red;
}
But if you only want to change the appearance of the selected item, then you will probably have to override a-Tabs-selected. In that case you should also add the more generic .t-Tabs-item class in order to avoid side effects.
.t-Tabs-item.a-Tabs-selected {
background: green;
}
The li element you posted has the class attribute:
class="t-Tabs-item a-Tabs-before a-Tabs-selected is-active"
This means it has the following class names:
t-Tabs-item
a-Tabs-before
a-Tabs-selected
is-active
If you want to target only this li, by class name, and considering no other HTML elements have the same exact class t-Tabs-item a-Tabs-before a-Tabs-selected is-active, use this class name for selection:
.t-Tabs-item.a-Tabs-before.a-Tabs-selected.is-active { ... }
If you want to select any element which has the class name t-Tabs-item, use it as a selector, but consider that if other HTML elements in your page have this class, your selection would return multiple elements.
As per #str's comment to your question, if you want to target this specific li element, it's best to use an id selector:
#SR_R1_tab { ... }

Aurelia conditionally wrapping slots in components

I'm creating Aurelia components which wrap material-components-web, cards specifically right now and am wondering what's the correct way of implementing multiple content sections (actions, etc.).
Slots seem to be the right choice but I cannot just put the actions div on the template at all times, but only if any actions are actually present.
Simply put I need to check if a slot has been defined inside the component template.
<template>
<div class="card">
<div class="content">
<slot></slot>
</div>
<!-- somehow check here if the slot has been defined -->
<div class="actions">
<slot name="actions"></slot>
</div>
</div>
</template>
Out-of-the-box, there is no direct way to do this like a $slots property, however you should be able to access slots via the template controller instance itself: au.controller.view.slots - the specific slot inside of this array has more information about the slot itself and its children.
Here is an example of an Aurelia application with a modal component (custom modal element). The modal itself has a slot where HTML can be projected inside of it. We have a header, body and footer.
Each predefined slot inside of our custom element should show up inside of a children object, where the property name is the name of our slot. If you do not provide a name for a slot (the default slot) the name of it internally is: __au-default-slot-key__.
We first check if the slot exists and then we check the length of its children, the children array exists inside each slot. If a slot has no HTML projected into it, it will have a children length of zero. This is reliable, because default content defined inside of the slot does not get put into the children array, only projected HTML does.
You'll see the work is being done mostly inside of modal.html, but pay close attention to modal.js where we inject the element reference of the custom element and then access the Aurelia instance using au to get to the controller containing our slots itself.
There is one caveat with this approach: you cannot use if.bind to conditionally remove HTML inside of your custom element. If you use if.bind on a DIV containing a slot, it actually removes its slot reference so it can't be checked. To work around this, just use show.bind (as I do in my provided running example).
Use CSS :empty Selector
CSS is the right tool for this job, not Aurelia. The :empty selector will allow you to display: none the div.actions when the slot isn't populated.
.card .actions:empty {
display: none;
}
According to the :empty selector spec as explained by CSS-Tricks, white space will cause empty to fail to match, so we just need to remove the white space around the slot.
<div class="actions"><slot name="actions"></slot></div>
Working example here: https://gist.run/?id=040775f06aba5e955afd362ee60863aa
Here's a method I've put together to detect if any slots have children (excluding HTML comments)
TypeScript
import { autoinject } from 'aurelia-framework';
#autoinject
export class MyClass {
private constructor(readonly element: Element) {
}
private attached() {
}
get hasSlotChildren(): boolean {
if (!this.element ||
!(this.element as any).au) {
return false;
}
let childrenCount = 0;
const slots = (this.element as any).au.controller.view.slots;
for (let slotName of Object.keys(slots)) {
const slot = slots[slotName];
if (slot.children &&
slot.children.length > 0) {
for (let child of slot.children) {
if (child instanceof Comment) {
// Ignore HTML comments
continue;
}
childrenCount++;
}
}
}
return childrenCount > 0
}
}
HTML
<template
class="my-class"
show.bind="hasSlotChildren"
>
<slot></slot>
</template>

Vuejs list loop - add class based on properties of each child

I have a list from the data fetched from an API. I would like to check if each element in list has a sub-element/child or not, and then add a class based on decision.
My attempt:
<template>
...
<ul>
<li v-for="nav in navigation" :class="{conditionalClass: isNavDropdown(nav)}">xxxx</li>
</ul>
...
</template>
And then in methods I have this function:
<script>
...
methods: {
isNavDropdown: function (nav) {
return nav.children[0] !== null
}
}
...
</script>
This doesn't work. Printing nav in console gives me a strange object with elements like reactiveGetter() and reactiveSetter() (multiple of them).
Is it even possible to achieve such a thing with vuejs? Or do I need to use javascript tricks to achieve this?
You can check that within your HTML itself, without the need of a separate method.
...
<li v-for="nav in navigation" :class="{conditionalClass: nav.children}">xxxx</li>
...

Categories