Customizing native web components styling by the consumer - javascript

I've got a bunch of web components built where a consumer may be consuming one or many of these components.
They are used by adding a script tag to the components minified JS and then using the web component in your code like so: <my-cool-component data="someData" />.
CSS is built into these components, however, I need to add functionality to allow the consumers to customize the color theme, and I'm wondering if my approach here could be improved, or if there is a better approach all together.
Right now I have it setup so that when someone consumes any of the components, like my-cool-component and they have a window.coolComponentOptions set like this:
window.coolComponentOptions = {
PRIMARY_BUTTON_BACKGROUND_COLOR: 'tomato',
PRIMARY_BUTTON_COLOR: '#fff',
URL_COLOR: 'tomato',
FONT: 'Arial',
SECONDARY_BUTTON_BACKGROUND_COLOR: 'lightblue',
... // Obviously this could grow tremendously
}
I do a check inside of my-cool-component and see if the coolComponentOptions exist and if so I map them to the CSS variables and use them inside of any of the components.
Alternatively I could make these values props on the component itself like <my-cool-component primary-btn-color="tomato" /> but this could cause the consumer to have an element with tons of attributes, and when they consume <other-cool-comp /> they will need to repeat these attributes again.
I'm curious if there are any other (better) options than the way I am doing it with the window variable, or if that approach could be improved upon or is completely acceptable?

CSS variables can be global CSS definitions.
And shadowParts give you even more global CSS / shadowDOM styling.
Very good explainer by Monica Dinculescu, former Google Web Components team member
https://meowni.ca/posts/part-theme-explainer/

Related

How do popular React component libraries like MUI/Bootstrap change classNames on elements?

https://jquense.github.io/react-widgets/docs/Multiselect/
If you look at the multiselect at this link, and inspect element, when you click into the input you'll see the main div element change classnames from 'rw-popup-container' to 'rw-popup-container rw-slide-transition-exited'. The class 'rw-slide-transition-exited' contains display=none in css which makes the dropdown disappear.
This process of adding/subtracting classnames is extremely snappy and common among various React libraries like MUI/React Bootstrap. You can inspect the source HTML and see they are all doing it. How, exactly, are they doing this? I've looked through the source JS but I can't figure it out. It doesn't appear to be jQuery addClass()/removeClass() and they are doing conditional rendering in React (which is laggy from personal experience).
As you said, this is pretty commong in React libraries (VueJs and Angular libraries as well).
All the modern javascript frameworks have a way to conditionally set the styles of a component, and they just refresh that attribute, there's no need to re-render everything.
Particullary for React, you can unse the "className" proeprty for that, instead of passing an string you can pass a function, and that will dynamically change the classes in the component.
Example:
Using the same example you used, if you go here, you'll see the code for that component.
https://github.com/jquense/react-widgets/blob/f604f9d41652adc29ccd3455bf17997bc001d9ef/packages/react-widgets/src/Multiselect.tsx#L632
(I marked line 632, because that's were the magic happens)
className={cn(className, 'rw-multiselect')}
In there you can see that className is getting a function (since it's between curly brackets it will be evaluated instead of just passing the value).
And if I'm correct, it is using this other library: https://github.com/JedWatson/classnames
which allows you to conditionally set classes.

ReactJS and targetting/wrapping elements like jQuery

I am trying to add 'sticky note' annotations (which i call TourPoints) to a React-based prototype I am creating. I created a TourPoint component which I have been manually 'wrapping' around elements of my interface as I go. The TourPoint displays the 'content' as a pink tag on the side of the element.
<TourPoint content="Sticky note content goes in this prop">
<button id="elementToWrap">Button element to annotate</button>
</TourPoint>
However, this gets a little messy and bloats my code... With jQuery, I used to be able to write a script where I could keep something like TourPoints neatly in a separate javascript file, then simply target DOM ids or classes to append elements.
$("#elementToWrap").wrap( "<div class='tourpoint'>Sticky note content goes here</div>" );
// $.append() or $.insertBefore() were also useful functions for this kind of thing
I am wondering how I might do a similar thing in React, and thought refs { useRef } might come to the rescue - but have not used refs before and can't quite get my head around if this is the right approach, or whether I am barking up the wrong tree with this.
The idea would be able to reference a ref globally (?) so that i can simply append the TourPoint to the element from a separate js/jsx file (sorry, no code example, as I really don't know what this would look like...)
The ease of having my TourPoints managed from a central file for the application what i am trying to achieve. The application has multiple pages and use React-Router.
Any pointers on how to think about this problem in the 'React' way would be most welcome.

Component based clientlibs AEM

Is it better to split up clientlibs by components if it means more calls to the server?
I.e. using
<%#taglib prefix="ui" uri="http://www.adobe.com/taglibs/granite/ui/1.0" %>
<ui:includeClientLib categories="mqd.component.accordion" />
in the <component>.jsp instead of compiling all the CSS in a single stylesheet.
From what I know, this is more of a decision based your use case, there is no one approach which fits all the scenarios -
Loading CSS at component level
When you load CSS at the component level, it is not available in the HEAD section when the page rendering process kicks off. It will only render your component CSS when it encounters it somewhere in the body tag.
Conditionally loading CSS based on the component is not available by default, you would have to write custom logic to achieve this.
From this post,
One way to achieve this is to intercept that behaviour. Use a filter
and buffer all data written to the output buffer in memory. Then you
can render safely all components and if you encounter your special
component you can set a flag in the request attributes. The filter can
then check for these attributes, change the buffer accordingly and
then send everything out. That approach is a bit risky, because it
can consume a lot of memory. And it changes the rendering performance and behaviour of your page. But it might be worth a try.
Also, with component level CSS, you would have to ensure the styles
for a component don't affect styles for another component, i.e. you
would have to use narrow selectors to do this and ensure you don't
break anything else in the process.
Also, with component level CSS, you would have to ensure the styles for a component don't affect styles for another component, i.e. you would have to use narrow selectors to do this and ensure you don't break anything else in the process.
Other approaches
Using page components - If you have a component which has a lot of styles and you don't want this to get loaded on every other page, you can look at using page components(different templates) to achieve this. Each page component can load a different group of clientslibs based on its use.
Using deferred clientlibs - If your layout constantly changes and you’re worried about how big your clientlibs file has become, deferred clientlibs might be a better option. Example from the link listed below -
… [Navigation component logic]
<ici:js-defer>
<cq:includeClientLib js=”icidigital.components.navigation”/>
</ici:js-defer>
[Navigation component end]
… [Sitemap component logic]
<ici:js-defer>
<cq:includeClientLib js=”icidigital.components.siteMap”/>
</ici:js-defer>
[Sitemap component end]
becomes…
<div class=”footer” />
<script type=”text/javascript” src=”path/to/programmatically/combined/deferred/clientlib.js”></script>
</div>
Whatever approach you take, ensure caching, page load times, maintenance, performance, etc are taken into account.
Further read
Best approaches to clientlibs in AEM
CSS best practices in clientlibs

TVML Creating Dynamic Templates

Is it possible to create dynamic templates/pages with TVML without relying on Apple's standard templates?
Say I'd like to create a template that has 2 images next to each other and on top of these images a couple of textfields that I want to position based on some parameters.
Could I create a template/view i UIKit and populate it from JS as an alternative?
How would I go about this?
I think you're talking about a mix of "divTemplate", which exists precisely to let you use whatever layout you want with no predefined structure or placement, and using native code to define your own custom TVML elements.
From Apple's documentation, "There is no built-in layout for the contained elements. Use the style properties listed in TVML Styles to personalize the elements placed inside of the div template." It's for completely free-form by-hand element layout.
As for the other path: the way to add a UIKit view to TVML is by defining a custom element. That's how the TVML system knows where your custom view is supposed to go within the TVML "page". If you go down that path, you'll want to read up on TVMLKit, not just TVML and TVJS. You make a class that conforms to the TVInterfaceCreating protocol, potentially create a subclass of TVViewElement (if one of the existing classes isn't enough for your needs), register the element with [TVElementFactory registerViewElementClass: [MyTVViewElement class] forElementName:#"mycustomelement" ], and register your TVInterfaceCreating class with [[TVInterfaceFactory sharedInterfaceFactory] setExtendedInterfaceCreator: myInterfaceCreator].
So the way I see it, you could use a divTemplate and a ton of carefully crafted style info to do it entirely without any custom code or UIView objects, or you could make one or a small number of custom elements for the part you're talking about. I do not know if you can make a new "template" that is itself a custom element, however -- that's something I haven't tried yet.
If you decide to go down the native code path and you find that the TVViewElement-related APIs aren't rich enough for the coordination you want between JavaScript and your custom view, you may also need to read up on the JavaScriptCore APIs. Those let you expose arbitrary native-backed functionality via JavaScript, via the APIs on JSContext. In a TVML app, a good place to hook into all this is by implementing the - appController:(TVAppController *) evaluateAppJavaScriptInContext:(JSContext *) method from the TVApplicationControllerDelegate protocol.

Shadow DOM, aim of using it

I have studied Shadow DOM recently, and I was wondering what are the aims of using it instead of the main one.
What does it gives ? Why dont we use standard DOM instead of it (except for styling scoping) ?
It allows you to encapsulate functionality, effectively putting it in a black box. It means you can create [reusable] components whose inner workings aren't exposed; this is impossible using standard DOM.
As an example, take HTML input elements. So, say, the file type of input. To use it on an HTML page, you simply add <input type="file" />, and it works. You don't need to add any extra code or HTML or CSS to handle how it works, it just does, and you can't access the internal bits of it. If you were to write a piece of UI, using HTML/CSS/JS, that did the same thing, it would be fairly complex. But the file input is just a single tag that you can use anywhere, it always does the same thing. The web component family of specs allow you to create your own elements that work in this way, and the Shadow DOM is a critical part of this. You can create a new element, like <my-fantastic-file-input />, with its functionality encapsulated. It has its own internal DOM subtree, but that subtree isn't directly accessible; ditto with scoped styles. The new component does not expose its implementation details to the document.
You can do most of this stuff using the DOM, but the implementation will be wholly tied into the document/application structure. With components, you extract that implementation, and you can reuse it, pass it around, publish it and let other people drop it into their applications/documents, and be sure it will work in exactly the same way, anywhere. You cannot really do that as things currently stand by using the standard DOM.
This is from 2011, and slightly out-of-date, but it's a list of some possible use cases for the component model: http://www.w3.org/2008/webapps/wiki/Component_Model_Use_Cases

Categories

Resources