I have a custom element: my-form, which renders another custom element: my-select.
my-select has a property called option-checked. This property is defined with { reflect: true }, so when an option is checked its attribute is updated in the DOM.
#customElement('my-form')
export class MyForm extends LitElement {
// ...
render() {
return html`
<my-select
.options=${this.options}
option-checked="foo"
></my-select>
`;
}
}
In the my-form custom element I want to watch for changes to the option-checked attribute and update when this happens.
How can I do this?
Related
Having two appBars components in the same page is a good approach (one as header and the other one as footer)? Besides, I am using the MUI createTheme to override some styles. I am doing this to override the appBar component.
components: { ...
MuiAppBar: {
styleOverrides: {
root: {
minHeight: '4.375rem',
backgroundColor: appColors.aqua600,
},
},
}, ...
This works fine, but as was wondering how could I override the style of an AppBar that is renders as 'header' and style the other appBar that is rendered as 'footer'
The component usage:
<AppBar
component="header | footer" ...
</AppBar>
I know that can be easily done with CSS, but I was wondering if this can be done using the createTheme from MUI?
It can be done by overriding styles based on props using ownerState.
Overrides based on props
You can pass a callback as a value in each slot of the component's styleOverrides to apply styles based on props.
The ownerState prop is a combination of public props that you pass to the component + internal state of the component.
You can check more on docs.
So, the custom theme for MuiAppBar should be something like this:
components: {
MuiAppBar: {
styleOverrides: {
root: ({ ownerState }) => {
return {
...(ownerState.component === "header" && {
backgroundColor: "#202020"
})
};
}
}
}
}
I am looking into Preact for my next project.
Since it has no virtual DOM I am wondering if it, like React, prefers you to let the framework manipulate the DOM instead of doing so yourself directly.
Would Preact bump heads with another library that manipulates the DOM such as SVGjs?
Preact is non-destructive when it comes to DOM updates. The official guide already explains how to integrate external DOM manipulations into the preact component:
If using class-based component:
import { h, Component } from 'preact';
class Example extends Component {
shouldComponentUpdate() {
// IMPORTANT: do not re-render via diff:
return false;
}
componentWillReceiveProps(nextProps) {
// you can do something with incoming props here if you need
}
componentDidMount() {
// now mounted, can freely modify the DOM:
const thing = document.createElement('maybe-a-custom-element');
this.base.appendChild(thing);
}
componentWillUnmount() {
// component is about to be removed from the DOM, perform any cleanup.
}
render() {
return <div class="example" />;
}
}
If using hooks, then use memo function from preact/compat:
import { h } from 'preact';
import { useEffect } from 'preact/hooks';
import { memo } from 'preact/compat';
function Example(props) {
const [node, setNode] = setState(null);
useEffect(() => {
const elm = document.createElement('maybe-a-custom-element');
setNode(elm);
// Now do anything with the elm.
// Append to body or <div class="example"></div>
}, []);
return <div class="example" />;
}
// Usage with default comparison function
const Memoed = memo(Example);
// Usage with custom comparison function
const Memoed2 = memo(Example, (prevProps, nextProps) => {
// Only re-render when `name' changes
return prevProps.name === nextProps.name;
});
Also, note that Preact's render() function always diffs DOM children inside of the container. So if your container contains DOM that was not rendered by Preact, Preact will try to diff it with the elements you pass it. - Thus the meaning non-destructive.
I am porting a react app to preact. Handling data from a form on submit throws a TypeError: this.createIssue is not a function at Object.createTestIssue [as click] error.
The code is a follows:
class IssueList extends Component {
state = { issues: [] };
createIssue(newIssue) {
const updatedIssues = this.state.issues.slice();
newIssue.id = this.state.issues.length + 1;
updatedIssues.push(newIssue);
this.setState({ issues: updatedIssues });
}
createTestIssue() {
const issue = {
status: 'New',
owner: 'Pieta',
created: new Date(),
title: 'Completion date should be optional'
};
this.createIssue(issue);
}
render(props, state) {
return (
<div>
<h1>Issue Tracker</h1>
<IssueFilter />
<hr />
<IssueTable issues={state.issues} />
<hr />
<IssueAdd createIssue={this.createIssue} />
<hr />
<button onClick={this.createTestIssue}>Add Test</button>
</div>
);
}
}
export default IssueList;
I have tried to create a constructor and setting the state from inside the constructor, however there is a ReferenceError: state is not defined at new IssueList.
I have looked at linkState module that preact recommends for forms, however, I have not been able to set it up correctly. Do I pass an empty object and an object with the data that I want to be added to my array; something like:
render({}, { state }) {
...
}
But that does not allow me to access the state. Any help is appreciated.
In preact props and state are passed into the render function, as you have shown.
render(props, state) {
...
}
This allows you to use destructuring#MOZDocs to
render({}, { state }) {
...
}
What you have ^^ would not be beneficial, and might hide state behind another object. What you should have is:
render({}, { ...state }) {
...
}
OR in my opinion the best solution assuming state = {key: 'test', value: 1}:
render({}, { key, value }) {
...
}
If you do not wish to use anything from props you can either use the empty object as shown above or just have props, but then not use it.
I am building angular 4 app with ag-grid and I am having an issue with trying to figure out how to put a link in the cell. Can anybody help me with that issue?
Thanks
Please check this demo
cellRenderer: function(params) {
return ''+ params.value+''
}
In this demo, the cell value for the column 'city' is a hyperlink.
I struggled with this the other day and it was bit more complex than I first thought. I ended up with creating a renderer component to which I send in the link and that needed a bit on NgZone magic to work all the way. You can use it in your column definition like this:
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
inRouterLink: '/yourlinkhere',
}
Component where inRouterLink is the link that you send in and params.value is the cell value. That means that you can route to your angular route that could look something like 'yourlink/:id'. You could also simplify this a bit if you don't want a more generic solution by not sending in the link and just hard coding the link in the template and not using the cellRendererParams.
import { Component, NgZone } from '#angular/core';
import { Router } from '#angular/router';
import { AgRendererComponent } from 'ag-grid-angular';
#Component({
template: '<a [routerLink]="[params.inRouterLink,params.value]" (click)="navigate(params.inRouterLink)">{{params.value}}</a>'
})
export class RouterLinkRendererComponent implements AgRendererComponent {
params: any;
constructor(
private ngZone: NgZone,
private router: Router) { }
agInit(params: any): void {
this.params = params;
}
refresh(params: any): boolean {
return false;
}
// This was needed to make the link work correctly
navigate(link) {
this.ngZone.run(() => {
this.router.navigate([link, this.params.value]);
});
}
}
And register it in
#NgModule({
imports: [
AgGridModule.withComponents([
RouterLinkRendererComponent,
])
],
})
UPDATE: I have written a blog post about this: https://medium.com/ag-grid/enhance-your-angular-grid-reports-with-formatted-values-and-links-34fa57ca2952
This is a bit dated, but it may help someone. The solution with typescript on Angular 5 is similar to what C.O.G has suggested.
In the component's typescript file, the column definition can contain a custom cell rendering function.
columnDefs = [
{headerName: 'Client', field: 'clientName' },
{headerName: 'Invoice Number', field: 'invoiceNumber',
cellRenderer: (invNum) =>
`<a href="/invoice/${invNum.value}" >${invNum.value}</a>` },
];
The lambda function is called while rendering the cell. The 'value' of the parameter that gets passed is what you can use to generate custom rendering.
Inspired by #Michael Karén
This is a improved version that is more flexible.
We can set what text to display in link
We can pass more than 2 routerLink parameters
Resolve routerLink according to data
Support target
Display text only if link is not applicable
And more if you wanted to add, just further edit this component
import { Component } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
export interface IRouterLinkRendererComponentOptions {
routerLinkParams?: any[];
linkDescription?: string;
textOnly?: string;
target?: string;
}
#Component({
template: `
<a *ngIf="params.textOnly == null; else textOnlyBlock"
[routerLink]="params.routerLinkParams"
[target]="params.target ? params.target : '_self'"
>
{{ params.linkDescription }}
</a>
<ng-template #textOnlyBlock>
{{ params.textOnly }}
</ng-template>
`
})
export class RouterLinkRendererComponent implements ICellRendererAngularComp {
params: IRouterLinkRendererComponentOptions;
agInit(params: any): void {
this.params = params.routerLinkRendererComponentOptions(params);
}
refresh(params: any): boolean {
return true;
}
}
So that we can dynamically resolve parameters and return text only if wanted in column definition by
{
...
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
routerLinkRendererComponentOptions: (param): IRouterLinkRendererComponentOptions => {
if (param.data.dispatch_adjustment) {
return {
routerLinkParams: ['/adjustments', param.data.dispatch_adjustment.id, 'edit'],
linkDescription: '#' + param.data.dispatch_adjustment.id
};
} else {
return {
textOnly: '-'
};
}
}
},
...
},
Instead of using href in cellRenderer , it's better to use cellrenderer framework as route link works in it.
Another Disadvantage is if you use href then the entire angular application will reload again it changes the navigation state from imperative to popstate. The angular router works on the imperative state.
I had implemented something similar to Michael and Tom, with only [routerLink] and no (click) handler. But recently I started getting the dreaded warning:
Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?
After experimenting for awhile I found this post and added the navigate click handler function, which made the application start working again, however I found that the 'Navigation triggered outside Angular zone' message was still appearing in the logs.
So while the (click)="navigate()" call triggers the navigation inside the ngZone,the [routerLink] call is still being made, which bothered me. I really didn't want two attempts to navigate to happen - in case anything changed with a future API update.
I decided to replace the anchor tag with a span pseudoLink.
.pseudoLink {
color: blue;
text-decoration: underline;
cursor: pointer;
}
#Component({
template: '<span class="pseudoLink" (click)="navigate()">{{mytitle}}</span>'
})
navigate() {
this.ngZone.run(
() => {
console.log("LinkRendererComponent: navigate: (", this.mylink, ")");
this.router.navigate([this.mylink]);
}
);
}
this.mylink is defined in the agInit() method based on parameters passed in via cellRendererParams.
This works well for my main purpose which is to make the cell look like a link. Only thing I lost was the URL path popup in the browser status bar.
Hope this might help someone else.
Using a cell renderer is the correct solution but missing from the top answer is stopping the click event from reaching AgGrid:
cellRenderer: ({value}) => {
const a = document.createElement('a');
a.innerText = a.href = value;
a.target = '_blank';
// Prevent click from reaching AgGrid
a.addEventListener('click', event => { event.stopPropagation() });
return a;
}
If the click bubbles up to AgGrid it will cause row selection changes, etc if those are enabled.
I created a generic component that is usable for any link cell, uses no workarounds, and logs no warnings.
Usage
columnDefs = [
{
colId: 'My Column',
cellRendererFramework: AgGridLinkCellComponent,
cellRendererParams: {
// `text` and `link` both accept either an string expression (same as `field`) or a function that gets ICellRendererParams
text: 'title',
link: (params: ICellRendererParams) => `/my-path/${_.get(params, 'data.id')}`
}
}
]
Register the component in your AppModule:
imports: [
AgGridModule.withComponents([
AgGridLinkCellComponent
])
]
The component itself:
import * as _ from 'lodash';
import {Component} from '#angular/core';
import {AgRendererComponent} from 'ag-grid-angular';
import {ICellRendererParams} from 'ag-grid-community';
#Component({
selector: 'app-ag-grid-link-cell-component',
template: '<a [routerLink]="link">{{ text }}</a>',
})
export class AgGridLinkCellComponent implements AgRendererComponent {
link: string;
text: string;
constructor() {
}
agInit(params: ICellRendererParams): void {
this.refresh(params);
}
refresh(params: ICellRendererParams): boolean {
const dataParams = params.colDef.cellRendererParams;
this.link = _.isFunction(dataParams.link) ? dataParams.link(params) : _.get(params.data, dataParams.link);
this.text = _.isFunction(dataParams.text) ? dataParams.link(params) : _.get(params.data, dataParams.text);
return false;
}
}
We had this problem, and its not straightforward.
We ended up solving it in a different way as we use AdapTable on top of ag-Grid.
So we created an AdapTable Action Column and in the RenderFunction provided the link. That worked best for us as we didnt always want the Link to appear so we could use the ShouldRender function to decide whether or not we wanted to display link for each row.
I have a large forms to submit in single page.
<container>
<formA>
<formB>
<formC>
<submitButton>
<container>
it looks apparently like this. and I have a store which save every form data. then when user click submit button, I gather all form data using vuex store.
The problem is I need to update the form data in store everytime.
so I'll be like this in vue component
watch: {
userInput (val) {
this.updateState(val)
}
update state when input changes by watching form data(binded with v-model).
or like this which is documented in vuex doc.
userInput: {
get () {
return this.$store.state.userInput
},
set (val) {
this.updateState(val)
}
}
well.. I don't think these are good idea. Is there any better way to form handling with vuex?
I made a little tool which makes form handling wit Vuex a lot easier: vuex-map-fields
Example
Store
import Vue from 'vue';
import Vuex from 'vuex';
// Import the `getField` getter and the `updateField`
// mutation function from the `vuex-map-fields` module.
import { getField, updateField } from 'vuex-map-fields';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
fieldA: '',
fieldB: '',
},
getters: {
// Add the `getField` getter to the
// `getters` of your Vuex store instance.
getField,
},
mutations: {
// Add the `updateField` mutation to the
// `mutations` of your Vuex store instance.
updateField,
},
});
Component
<template>
<div id="app">
<input v-model="fieldA">
<input v-model="fieldB">
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
// The `mapFields` function takes an array of
// field names and generates corresponding
// computed properties with getter and setter
// functions for accessing the Vuex store.
...mapFields([
'fieldA',
'fieldB',
]),
},
};
</script>
You can read more about vuex-map-fields on my blog: How to Handle Multi-row Forms with Vue, Vuex and vuex-map-fields
I would use deep watchers for this and have all fields in a object, you could use multiple approaches for saving the data, iterating over Object.keys to store each field with it's variable name in the form object, or storing the entire form, whatever you might need.
You could also use v-model.lazy="form.myfield" to indicate that you only want the binding to update once the user has left the field.
Form component
<template>
<div>
<!-- You can optionally use v-model.lazy="form.field1" to only update once user has exited the field or pressed enter -->
<input v-model="form.field1" />
<input v-model.lazy="form.field2" />
</div>
</template>
<script>
export default {
props: ['value'],
data: function () {
return {
internalForm: {
field1: null,
field2: null
}
}
},
watch: {
internalForm: {
handler: function (newValue) {
// Emit new form object to parent component so we can use v-model there
this.$emit('input', this.form)
// Or save form data
this.handleFormSave(this.form)
},
// Tell vue to do a deep watch of entire form object to watch child items in the object
deep: true
}
}
}
</script>
Parent component
<template>
<form-component v-model="forms.form1" />
<submit-button #click="saveAllFormData" />
</template>
<script>
export default {
data: function () {
return {
forms: {
form1: null // This will be updated when 'input' is emitted
}
}
},
watch: {
forms: {
handler: function (newValue) {
if (allFormsValid && readyToSave)
saveAllFormData(newValue);
},
deep: true
}
}
}
</script>
I had headache regarding this probem to.
Vuex doc describes that we need to update store for every field.
It's a loot of typing whatfor?
We make one solution that works.
It based on cloning store object to local one.
//We are passing (vuexstore) 'item' object from parent component:
//<common-item v-bind:item="item" ....
props: ['item'],
// create localItem - this is reactive object for vuex form
data: () => {
return {
localItem: null
}
},
// make clone on created event
created: function() {
this.localItem = this._clone(this.item)
},
// watch vuexstore 'item' for changes
watch: {
item: function(val) {
this.localItem = this._clone(this.item)
}
},
// map mutations and update store on event
methods: {
...mapMutations([
'editItem'
]),
updateItemHandler: function() {
this.editItem({ item: this._clone(this.localItem) })
},
_clone: function(o){
return JSON.parse(JSON.stringify(o))
}
},
Inside form use:
<input v-model="localItem.text" #keyup="updateItemHandler" type="text" class="form-control"></input>
I think this is only lack of vuex. There should be much shorter and built in solution.