How to apply the lumo theme inside a shadow dom of custom components in Vaadin Flow - themes

Is there a recommended way or best practice to apply the css rules of "global" default or custom themes to the shadow root of custom components created with a lit template?
There is a workaround with a connectedCallback to manually apply the theme with an imported "applyTheme()" function. Another description of the issue and the workaroundsee here
I have also found a ticket, with some sort of solution, but i just can't figure out how to use this to solve my issue.
That workaround doesn't seem to work when working with the Vaadin Designer plugin in intellij, as it doesn't seem to be able to use the "applyTheme()" function (unresolved import of "import { applyTheme } from frontend/generated/theme"). Also i'd like to avoid this workaround where possible.
If there is another solution than using the mentioned workaround, does it also work with custom themes / changed values of lumo css properties?
Example:
You create a custom component based on a lit template like this:
import '#vaadin/vaadin-ordered-layout/vaadin-horizontal-layout';
import '#vaadin/vaadin-ordered-layout/vaadin-vertical-layout';
import '#vaadin/vaadin-select';
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import {applyTheme} from "Frontend/generated/theme";
#customElement('stub-view-card')
export class StubViewCard extends LitElement {
//commented this, so we DO use a shadow root
/*createRenderRoot() {
// Do not use a shadow root
return this;
}*/
connectedCallback() {
super.connectedCallback();
// Apply the theme manually because of https://github.com/vaadin/flow/issues/11160
//this is not needed, if no shadow root like above is used
applyTheme(this.renderRoot);
}
render() {
return html`<li class="bg-contrast-5 flex flex-col items-start p-m rounded-l">
<div
class="bg-contrast flex items-center justify-center mb-m overflow-hidden rounded-m w-full"
style="height: 160px;"
>
<img id="image" class="w-full" />
</div>
<span class="text-xl font-semibold" id="header"></span>
<span class="text-s text-secondary" id="subtitle"></span>
<p class="my-m" id="text"></p>
<span theme="badge" id="badge"></span>
<slot></slot>
</li> `;
}
}
how it should look like
how it looks like without the connectedCallback and applyTheme()
I am using Vaadin 23 and a project generated from vaadin starter. My example was created from the "image list"-template.
I really appreciate any help and documentation that you can point me to :)

Related

switch image when focus/unfocused in svelte

I have a svelte app where I map channelcodes to imported logos and render that logo in a div that is in a focused or unfocused state. Currently I have been mapping 1 image and changing the CSS of the logo(SVG) conditionally. But I find it kind of hacky and makes more sense to switch between a focused and unfocused versions of the logo image. The style of the element I can change, but i don't know how to switch images through my mapping (I want to avoid long if statements or switch cases as there could be many logos).
How would I switch between two images? Let me know if more information is required. (One idea I thought was an array of two images and I pass in an index 0 or 1 somehow)
TS FILE
import { Television_ChannelCode } from '#gql/schema';
import Logo1 from '#logos/Logo1.svelte';
import Logo2F from '#logos/Logo2F.svelte';
import Logo2U from '#logos/Logo2U.svelte'
const map = new Map<Television_ChannelCode, any>();
map.set(Television_ChannelCode.News, Logo1);
//I want to switch between 2, use array?//
map.set(Television_ChannelCode.Sports, [Logo2F, Logo2U]);
export const getLogoFromTelevisionChannelCode = (
televisionChannelCode: Television_ChannelCode,
): any => {
return map.get(televisionChannelCode);
};
SVELTE FILE <--- Can change style but how to swap images??
{#if hasFocus}
<div class={focusedStyle}>
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)}/>
</div>
{:else}
<div class="unfocusedStyle">
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)}/>
</div>
{/if}
In your example the if block could be avoided by combining the class
<div class={hasFocus ? 'focusedStyle' : 'unfocusedStyle'}>
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)}/>
</div>
Your approach with the array could work like this
<div>
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)[hasFocus ? 0 : 1]}/>
</div>
(if every channel had the two version array) but I think the better approach would be to stick to the one component per Logo version, handle the styling intern and export a hasFocus flag from components, so it could look like this
<svelte:component this={getLogoFromTelevisionChannelCode(channelCode)} {hasFocus}/>

Formik Form validation in react

I have implemented form validation with formik and react. I am using material-UI.
<Formik
initialValues={{ name: '' }}
onSubmit={values => {
console.log('submitting', values);
}}
validate={values => {
alert();
let errors = {};
if (!values.name) {
errors.name = 'Name is required';
}
return errors;
}}>
{({
handleSubmit,
handleChange,
values,
errors
}) => (
<form onSubmit={handleSubmit}>
<div>
<input name="name"
onChange={handleChange}
name="name"
value={values.name}
type="text"
placeholder="Name">
</input>
{errors.name &&
<span style={{ color: "red", fontWeight: "bold" }}>
{errors.name}
</span>
}
</div>
<div>
<button>Submit</button>
</div>
</form>
)}
</Formik>
Above code is working fine for normal input tags but it is not working for Select and TextField material widgets.
Is there a compatibility issue with material UI ?
Please help.
As Chris B. commented, the solution is to wrap each desired element inside a React component that has what Formik requires. In the case of Material-UI, Gerhat on GitHub has created some of those components.
You can use those by downloading the source from the github link above. There is also a usage example there, showing how to use Gerhat's "wrapper" for a Material TextField and a Select.
In Gerhat's usage example, TextField is a component in that github repo; it isn't the Material UI TextField directly; it is a Formik-compatible "wrapper" around the Material TextField widget.
By looking at gerhat's source code, you can learn how to do the same for other Material widgets you need.
HOWEVER, gerhat's implementation may not be the easiest for a beginner to understand. Those wrappers are easy to use, but it may not be obvious from that code how to write your own wrappers for other widgets or components.
See my answer here for a discussion of Formik's <Field> and useField. Those are easier ways to "wrap" existing React components. (Not specifically Material-UI widgets, but AFAIK, you can wrap those like any other React component.)
If you do want to understand gerhat's approach, here are some comments about the code you'll see at github.
This is the source to TextField.jsx in that repo.
The "heart" of TextField.jsx, is its wrapping around Material's TextField. The generated virtual-DOM element (representing a Material TextField) is seen in these lines:
return (
<TextField
label={label}
error={hasError}
helperText={hasError ? errorText : ''}
{...field}
{...other}
/>
)
See the source link above, for the details of how this is made compatible with Formik. IMHO, a fairly advanced understanding of both React and Formik is required, to understand what is being done there. This is why I mentioned Field and useField as the place to start, for writing your own "Formik-compatible wrappers".
One detail I'll mention. The implementation relies on a file index.js in the same folder as TextField.jsx, to map the default of TextField.jsx, which is FTextField, to the name TextField, which is how you refer to it in the "usage" code at the start of this answer. SO Q&A about index.js file of a React component.
index.js contents:
export { default as TextField } from './TextField'
The other two files in the TextField source folder are ".d.ts" files. See this Q&A to understand those. You don't need them unless you are using TypeScript.

Passing and binding img src from props in Vue.js

I am trying to display an image with the img tag by using a path from props for the src attribute.
I've tried changing the path with #, using the whole path with src, adding ../assets/ in the component and only passing the file name (orange.png) as props.
I always get the default broken image displayed.
When inspecting in the browser, the path seems fine.
When I display the image directly, I can see that the path is resolved to some different path <img data-v-1212d7a4="" src="/img/orange.7b71a54c.png">.
Edit:
Additionally I tried this post Can't dynamically pass relative src path for imgs in Vue.js + webpack ,
where using <img :src="require(picture_src)" /> is given as an answer.
This leads to an error: Error in render: "Error: Cannot find module '../assets/orange.png'"
(Edit2:
This answer in the end worked for me in the end as described in my answer post.)
The same error occurs with the similar webpack method using let images = require.context('../assets/', false, /\.png$/) in my script part, as the answer on this post Can't dynamically pass relative src path for imgs in Vue.js + webpack .
I am new to Vue.js, so I don't exactly know what is happening or how to search for this or it might not have anything to do with what I'm originally trying.
I am able to display my image when I pass the path directly, like this
<img src="../assets/orange.png"/>
Now I'd actually like to pass it to my component in the props and then, inside the component, display it reading the path from props.
Component
<template>
<div>
<img :src=picture_src />
<div class="pic_sub">{{pic_desc}}</div>
</div>
</template>
<script>
export default {
name: 'PictureCard',
props: {
picture_src: String,
pic_desc: String
}
}
</script>
Using the component:
<template>
<div>
<PictureCard pic_desc='some description text' picture_src='../assets/orange.png' />
</div>
</template>
<script>
import PictureCard from './components/PictureCard.vue'
export default {
name: 'app',
components: {
PictureCard
}
}
</script>
If it is possible, I'd love to display my from a path that is passed through the component's props.
Otherwise I'd love to know some other solutions, work-arounds or knowledge on best practices in this case.
This worked for me
<img :src="require(`#/assets/img/${filename}`)">
where filename is passed in as a String prop e.g. "myImage.png".
Make sure you use the path specific to your project.
Source: https://github.com/vuejs-templates/webpack/issues/450
Note: # is a webpack alias for /src that is set by default in Vue projects
After some research, I understand that my problem has to do with webpack and resolving filepaths. I used a modified version from this answer:
Vue.js dynamic images not working
and this answer:
Can't dynamically pass relative src path for imgs in Vue.js + webpack
Since the link in the second answer was dead, here's an active link to require.context documentation:
https://webpack.js.org/guides/dependency-management/#requirecontext
My mistake when trying the second link's answer was that I returned only orange.png as the path, while I needed to add ./ at the beginning.
My working picture component now looks like this.
<template>
<div>
<img :src="resolve_img_url(picture_src)" />
<div class="pic_sub">{{pic_desc}}</div>
</div>
</template>
<script>
export default {
name: 'PictureCard',
props: {
picture_src: String,
pic_desc: String
},
methods: {
resolve_img_url: function (path) {
let images = require.context('../assets/', false, /\.png$|\.jpg$/)
return images("./"+path)
}
}
}
</script>
I edited the regular expression to match .png and .jpg file endings. Therefore passing the prop looks like this now
<PictureCard picture_src='orange.png' pic_desc='some picture description'/>
This works for me:
This is how i use my Componenent.
<image-element
:imageSource="require('#/assets/images/logo.svg')">
</image-element>
My Image Component:
<template>
<div>
...
<img v-bind:src=imageSource />
...
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component, Prop } from 'nuxt-property-decorator'
#Component({
components: {
.....
}
})
export default class extends Vue {
...
#Prop({ default: '' }) imageSource!: String
...
}
</script>
Newer solution:
The 'require()'-method does not work when using Vite.
I got this error: ReferenceError: require is not defined.
This is how I solved it without 'require()' and with composition API:
From parent component:
<ChildComponent icon-filename="icon.svg" />
ChildComponent:
<template>
<div>
<img :src="getImageUrl()">
</div>
</template>
<script setup lang="ts">
import {defineProps} from "vue";
const props = defineProps({
iconFilename: String
})
function getImageUrl() {
// This path must be correct for your file
return new URL(`../assets/icons/${props.iconFilename}`, import.meta.url)
}
</script>
this is my favorite super simple way to do it. It can easily be reused in any file in any folder in my project. Just pass the actual path as a string from the perspective of the parent:
//some file
<ParentA>
<ImageComponent
myImagePath="../../../../../myCat.png"
/>
</ParentA>
//some other file in a different folder in my project
<ParentB>
<ImageComponent
myImagePath="../../myCat.png"
/>
</ParentB>
//child component file
<template functional>
<div>
<img :src="props.myImagePath">
</div>
</template
Thats all not working for me :D
The template File is wrong!
you need to add ":" before you set your prop.
thats how i should use the PictureCard
<PictureCard :picture_src="require('orange.png')"
pic_desc='some picture description'/>
and thats how my PictureCard should look like:
<template>
<div>
<img v-bind:src="picture_src" />
</div>
</template>
export default class PictureCard extends Vue {
#Prop({ default: require("#/assets/orange.svg") }) img!: string
}
so in case no prop is setted, so i added a default prop too.
and yes i only used the image.

Change 404 page in vuepress

Is it possible to change / customize the 404 page of Vuepress without ejecting and having to change the whole theme?
I am currently using the enhanceApp.js, but I'm unsure how I can change the router options (the catchall route) as the Router is already created. The way I got it working right now is this:
router.beforeEach((to, from, next) => {
if (to.matched.length > 0 && to.matched[0].path === "*") {
next("/404.html");
} else {
next();
}
});
However, this feels like a hack as I always redirect to a custom and existing page containing my 404. Is there a more official way to do this?
I have a site based on Vuepress 1.5.x, and I was able to simply create a page named NotFound.vue under theme/layouts. No enhanceApp.js changes needed.
Vuepress itself seems already aware of this reserved layout name, based on the code here.
I previously had that page named 404.vue, but I was getting a warning saying that pages should follow proper HTML 5 component naming standards. Simply renaming it to NotFound.vue did the trick.
Contents of my NotFound.vue file:
<template>
<BaseLayout>
<template #content>
<v-container class="text-center">
<div class="py-6" />
<h1>Oops!</h1>
<p>Nothing to see here.</p>
<v-btn class="cyan--text text--darken-3"
exact :to="'/'" text>Go home</v-btn>
</v-container>
</template>
</BaseLayout>
</template>
<script>
export default {
name: "NotFound"
};
</script>

Programmatically create special Polymer-Element

I have got a polymer-element with following html:
<polymer-element name="tab-bar">
<template>
<button>Hello</button>
<template repeat="{{item in items}}">
<div>This element is {{ item }}</div>
</template>
</template>
<script type="application/dart" src="tab_bar.dart"></script>
</polymer-element>
The underlying dart class looks as follows:
import 'package:polymer/polymer.dart';
#CustomTag('tab-bar')
class TabBar extends PolymerElement {
List items;
TabBar(List<String> items) {
this.items = toObservable(items);
}
}
With the following approach, it isn't possible to programmatically add the element:
query('body').children.add(createElement(new TabBar(['One','Two','Three'])));
So now, how can I add such a polymer element programatically and also set the list in the constructor?
As of polymer 0.8.5 you can use constructor like
new Element.tag('tag-bar');
also, no more .xtag and no more .host (host == this now)
credits go to Seth Ladd who explained this on polymer mailing list
Note: this only works for polymer.dart < 0.8.5. See other answer for the new way.
Custom elements don't really have constructors like we're familiar with them in Dart. Instead, try this:
var tabBar = createElement('tag-bar');
tabBar.xtag.items = toObservable(['a','b','c']);
query('body').children.add(tabBar);

Resources