I am using preactjs to create my application. On top of this, I am using kendo grid. Inside the grid, I want to show a hyperlink. if the user clicks on the link, it should change the route. To render the link, i am using preact-router.
Here is the working fiddle.
let { h, render, Component } = preact;
// import { ... } from 'preact';
let { route, Router, Link } = preactRouter;
/** #jsx h */
class App extends Component {
componentDidMount() {
console.log('did mount !');
$("#grid").kendoGrid({
selectable: "multiple cell",
allowCopy: true,
columns: [
{ field: "productName",
template: function(e) {
return <link href="/">Home</link>
} },
{ field: "category" }
],
dataSource: [
{ productName: "Tea", category: "Beverages" },
{ productName: "Coffee", category: "Beverages" },
{ productName: "Ham", category: "Food" },
{ productName: "Bread", category: "Food" }
]
});
};
render({}, { }) {
return (
<div>
<h1>
Preact Kickstart
<sub>powered by preact</sub>
</h1>
<div id="grid"></div>
</div>
);
}
}
// Start 'er up:
render(<App />, document.body);
This is not even related to preact.
What you're doing is rendering a kendo grid using via jquery inside a preact component and using a preact component as a template.
One way to fix this is to return a html string:
template: function(e) {
const linkEl = $('<a>')
.attr('href', '#') // keep the a el but do not redirect to a different page on click
.text('Home')
.click((e) => {
e.preventDefault(); // prevent the original a tag behavior
route('/'); // this is using the `route` from preactRouter, which is already included at the top of the original file
});
return linkEl[0].outerHTML; // access the JS DOM element from jQuery element and get it's full html
}
I've also replaced the link (which is a typo, probably should've been Link) with a tag since there is no link element in basic html. Even though the name is wrong, this still works because the JSX transformer will interpret all the lowercase components as string name instead of using the component as a function (see how Babel compiles it). The transformer will generate h("link", { href: "/" }, "Home") and the h function returns an object which is then rendered as [Object object] because this is what happens when you try to convert to a string via .toString function. If preact would work in this case, it would render the actual <link href="/">Home</link> to the user, which would not have the desired behavior (except if a custom link component is defined somewhere else).
You can't return a preact component here because the kendo grid template is expecting a string template. One way would be to convert the preact component to a string, but I'm not sure that's even possible, I have never seen it done and you shouldn't have to.
Note: As said, you shouldn't have to be converting little parts of React or React-like to html. I would strongly advise against mixing preact code with jQuery which is rendering the kendo grid in your case. These two libraries are doing the rendering very differently. While jQuery is using the old approach of directly modifying the DOM and replacing the whole subtree, React (and all the implementation, such as preact) are rendering to virtual DOM and then have internal logic which figures out the difference with the actual DOM the user is seeing and display only the minimum difference so it makes the least updates required. With my quick google search, I have found react-kendo, but it doesn't seem very popular. There are also some blog posts from the kendo team themselves, but I haven't found any official support. If you want to use preact, try to find the (p)react way of doing it, for your example you could be using something like react-table (official demo). If on the other hand, you want to use powerful UI tools provided by kendo, you would be better off not adding the preact to the mix, it will make things more complex without much benefit or it could even make the whole thing worse.
Related
I'm trying to add JSX elements like <a>, <p> and else to an array of objects in a template string, this project has a portfolio.jsx file that holds the array, another file called Projects.jsx that maps the array, and a ProjectsItem.jsx that holds all the jsx and props to display the data.
export default [
{
title: "Tenzies Game",
imgUrl: "images/projects/tenziesgamepreview.png",
description: `This is the final Capstone Project for ${theJSXelement}
that teached me a lot about React, everything from props, state, making API calls, useEffects,
React forms, and more!`,
stack: [reactLogo, sassLogo],
link: "https://fredtenziesgame.netlify.app/",
codeLink: "https://github.com/fred-gutierrez/tenzies-game",
}
]
As shown above, I'm trying to add it within the description object template string, but when trying to do so, it gives me either the stringified object or [object, Object], which is displayed on the object.
I tried putting a const with the JSX element outside the array, like this:
const newJSXelement = (<a href="https://scrimba.com/learn/learnreact" target="_blank">
Learn React for free
</a>)
export default [
{
title: "Tenzies Game",
imgUrl: "images/projects/tenziesgamepreview.png",
description: `This is the final Capstone Project for ${theJSXelement}
taughtached me a lot about React, everything from props, state, making API calls, useEffects,
React forms, and more!`,
stack: [reactLogo, sassLogo],
link: "https://fredtenziesgame.netlify.app/",
codeLink: "https://github.com/fred-gutierrez/tenzies-game",
}
]
And i tried inserting it it inline
${(<a href="https://scrimba.com/learn/learnreact" target="_blank">
Learn React for free
</a>)}
, it gives me [object, Object] or the stringified element with all the specific types and details.
I'm fairly new in React so I'm unsure if this is even posible, but if anything, I'm very thankful if anyone is able to help me out with this situation, thanks!
Just in case, the GitHub repository for this project is: https://github.com/fred-gutierrez/React-Portfolio
It can be done by adding a div for the description. Hope it maybe helpful.
export default [
{
title: "Tenzies Game",
imgUrl: "images/projects/tenziesgamepreview.png",
description: <div>This is the final Capstone Project for {theJSXelement}
that teached me a lot about React, everything from props, state, making
API calls, useEffects,
React forms, and more!</div>,
stack: [reactLogo, sassLogo],
link: "https://fredtenziesgame.netlify.app/",
codeLink: "https://github.com/fred-gutierrez/tenzies-game",
}
]
I'm using a vaadin-dialog in my Web Components app. I would like the components inside to render on properties change but they don't.
I have a couple properties in my web component:
static get properties() {
return {
title: { type: String },
username: { type: String },
signUpOpened: { type: Boolean },
};
}
The dialog is defined as such. I use a function to bind it to the DialogRenderer. The dialog shouldn't be always open, so the user clicks it to open it.
render() {
return html`
<main>
<h1>${this.title}</h1>
<vaadin-dialog
header-title="SignUp"
.opened="${this.signUpOpened}"
#opened-changed="${e => (this.signUpOpened = e.detail.value)}"
${dialogRenderer(this.signUpRenderer)}
id="signUp">
</vaadin-dialog>
</main>
`;
}
signUpRenderer() {
return html`
<vaadin-text-field value="${this.username}" #change="${(e) => {
this.username = e.target.value
}}" label="email"></vaadin-text-field>
<vaadin-text-field value="${this.username}" }}" label="email-copy"></vaadin-text-field>
<p>${this.username}</p>
`;
}
You can try the demo over here, the source code is here.
When changing the value of the text field, you can see that the other text field and the paragraph don't update.
Can you folks direct me towards what is going on?
The dialogRenderer directive that you are using to render the contents of the dialog needs to know whether the data used in the rendering has changed in order to determine whether it should rerender the contents or not.
For that purpose the directive has a second parameter that allows you to pass in an array of dependencies that will be checked for changes.
In your example, you should apply the directive like so, in order for it to re-render when the username changes:
<vaadin-dialog
...
${dialogRenderer(this.signUpRenderer, [this.username])}>
</vaadin-dialog>
This is explained in the directive's JSdoc, unfortunately the directives do not show up in the component's API docs at the moment. You could open an issue here and request to improve the component documentation to add a section that explains how the directives work.
Is it possible to define a "sub component" inline in Vue3 which can be reused in the same .vue file/component, but without actually defining a new .vue file (i.e. without defining this sub component as a new component).
The use case is that I have a very specific way of formatting <select> options which is used for multiple <select>s within the same component (.vue file), but which will not be used anywhere else (it's also small, so I am inclined to define this options formatting part inline). I don't necessarily want to copy and paste the formatting (and it would be good to keep it within the same .vue file because it's small).
I realise that this is only syntactic sugar which may or may not be relevant in specific cases (I'm also not seeking advice on whether or not this is a good idea). I'm just looking for a way this can be done (if not, that's also an answer ;-))
You can define h() function to create vnodes inside your setup script in vue 3.
for example:
<template>
<MyComponent />
</template>
<script setup>
const MyComponent = h('div', { class: 'bar', innerHTML: 'hello' })
</script>
vue document will help you completely. document
You could do something like this in Vue2
Vue.component('my-checkbox', {
template: '<div class="checkbox-wrapper" #click="check"><div :class="{ checkbox: true, checked: checked }"></div><div class="title">{{ title }}</div></div>',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
Probably still works
Using Angular 10
There are many questions on SO that are similar to this, but I have yet to find one that answers my situation.
I'm hoping someone can guide me.
I'm using a third party library to display 360° photos. This third party library has a built-in API to display hotspots in the scene. Simply give the library the element you want to be the hotspot, and it takes care of the rest.
I have most of it working as expected, but there are a couple pieces that are not.
So far, I'm dynamically generating my components like so:
this._hotspotFactory = this.resolver.resolveComponentFactory(HotspotComponent);
const component = this._hotspotFactory.create(this.injector);
//Hydrate component with bunch of data
component.instance.id = data.id;
...
// Create the Hotspot with Third Party
// Calling this third party method injects the native element into the DOM.
// Passing the nativeElement in. Looks great at first glance.
const hotspot = this._scene.createHotspot(data, component.location.nativeElement);
this.appRef.attachView(component.hostView);
component.hostView.detectChanges();
if(component.instance.over.observers.length) {
hotspot.on('over', (evt) => {
this.zone.run(() => {
component.instance.over.emit(evt);
});
});
}
if(component.instance.out.observers.length) {
hotspot.on('out', (evt) => {
this.zone.run(() => {
component.instance.out.emit(evt);
});
});
}
if(component.instance.navigate.observers.length) {
hotspot.on('click', (evt) => {
this.zone.run(() => {
component.instance.navigate.emit(evt);
})
});
}
No errors are thrown and I successfully see the hotspot where it should be in the scene. Even Data interpolation in the HotspotComponent template occurs as expected.
BUT, [ngStyle] bindings never result in dynamic styling in HotspotComponent.
I'm 99% sure this is because change detection is not taking place in the component.
I am manually attaching the view with this.appRef.attachView(component.hostView) because the third party is responsible for injecting the element into the DOM, not Angular. Thus Angular needs to know about it so it will perform change detection.
Even with manually calling attachView, I still think Angular doesn't know about this component in the view because the Angular Chrome Extension debugger doesn't register it in its dev tools as a known component in the view....despite be seeing it on screen and in the DOM.
What am I missing?
What change detection strategy does the component have?
When a component is added to a view, it's life cycle hooks will be triggered by angular(ngOninit, ngAfterContentInit etc). Log something in these and see if theme life cycle hooks are being called. Irrespective of the change detection strategy one change detection cycle should happen on the component after it is added to view.
If the life cycle hook invoking is not happening, then it would mean that angular is not involved in adding the element to DOM.
It seems angular has a lifecycle hook precisely for your use-case 'ngDoBootstrap'.
As we can not debug your full source code, from the information you have mentioned it seems the dynamic component you are trying to attach to the view is not available to Angular in NgModule. Every component that angular bootstraps must be in NgModule.
you can although bootstrap it dynamically using 'ngDoBootstrap'.
It is used in the following manner:
ngDoBootstrap(appRef: ApplicationRef) {
this.fetchDataFromApi().then((componentName: string) => {
if (componentName === 'ComponentOne') {
appRef.bootstrap(ComponentOne);
} else {
appRef.bootstrap(ComponentTwo);
}
});
}
In your case, you can do it before attaching the component to the view.
...
appRef.bootstrap(component);
this.appRef.attachView(component.hostView);
component.hostView.detectChanges();
...
Please check the documentation here: https://angular.io/api/core/ApplicationRef
We use resolveComponentFactory method given by ComponentFactoryResolver class present in angular which is used for component level lazy loading. For the confirmation that your component is really breaked in chunks, do ng build --prod or you con and you will see the generated .js chunk for SoftwareListComponent.
app.component.html
<button (click)="loadSoftwareListDynamically()> Load </button>
<div #softwareListContainer></div>
app.component.ts
constructor(
private componentFactoryResolver: ComponentFactoryResolver
) {}
#ViewChild('softwareListContainer', { read: ViewContainerRef })
softwareListContainer: ViewContainerRef;
loadSoftwareListDynamically() {
import('../common-features/software-list/software-list.component').then(
({ SoftwareListComponent }) => {
const componentFactory =
this.componentFactoryResolver.resolveComponentFactory(
SoftwareListComponent
);
this.softwareListContainer.createComponent(componentFactory);
}
);
}
software-list.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
#NgModule({
declarations: [
SoftwareListComponent
],
imports: [
CommonModule,
RouterModule,
SwiperModule <- External Library
],
})
export class SoftwareListModule {}
For more info, you can go over to my complete discussion for Lazy loading of components created using ComponentFactoryResolver. You will get more info here ->
StackOverflow Discussion
Stackblitz Link Here
I am a junior developer who lacks experience, so I apologize if my question showcases signs of sheer ignorance. My title may not be expressive of the problem I face, so I shall do my best to be descriptive.
In my project, I am making use of a 3rd party component (a dropdown menu), which I would like to modify in my application. I don't want to fork and edit their code since I would like to pull the latest styling changes since my modification is only slight, being that I would like to add some text next to the dropdown icon.
Here is a (simplified) version of the code.
<template>
<overflow-menu
ref="overflow_menu"
>
<overflow-menu-item
v-for="item in overflowMenuItems"
:id="item.id"
:key="item.name"
>
{{ item.tabName }}
</overflow-menu-item>
</overflow-menu>
</template>
<script>
import { OverflowMenu, OverflowMenuItem } from '#some-library/vue'; //Don't have control of the implementation of these components.
export default {
name: 'CustomOverflowMenu',
components: {
OverflowMenu,
OverflowMenuItem,
},
props: {
overflowMenuItems: Array,
label: String,
},
mounted() {
this.injectOverflowMenuLabel();
},
methods: {
injectOverflowMenuLabel() {
const overflowMenuElement = this.$refs.overflow_menu.$el.firstChild;
const span = document.createElement("span");
const node = document.createTextNode(this.$props.label);
span.appendChild(node);
overflowMenuElement.insertBefore(
span,
overflowMenuElement.firstChild,
);
}
}
};
</script>
It functionally works ok, however, it doesn't seem a particularly elegant solution, and I feel as if I could be doing it in a more "Vuey" way. I also am greeted with a Vue warning of: Error in created hook: "TypeError: Cannot read property 'insertBefore' of undefined. This ultimately means I am not able to mount my component and unit test my custom overflow menu.
Is there a way to get this functionality, but in a more maintainable manner. I would either like to simplify the logic of the injectOverflowMenuLabel function, or perhaps there is a completely alternative approach that I haven't considered.
Would appreciate any help, you lovely people.
Thanks,
Will.