How to create an SVG component dynamically in Angular2? - svg

I am creating a web application which uses SVG.
I have created components consist of SVG element, and they are put into a root svg element.
They have attribute selector, because SVG/XML document tree is strict so I cannot use element selector.
And they have a template starts with svg:g tag:
#Component({
selector:'[foo]',
template: '<svg:g>...</svg:g>',
})
In the application, I want to create a component when a user press a button,
and simultaneously start dragging it.
I thought it can be achieved by creating a component dynamically using ComponentResolver:
#ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef
private componentResolver: ComponentResolver
onMouseDown() {
this.componentResolver
.resolveComponent(FooComponent)
.then((factory) => {
const dynamicComponent = this.dynamicComponentTarget.createComponent(factory, 0)
const component: FooComponent = dynamicComponent.instance
const element = dynamicComponent.location.nativeElement
// add event listener to start dragging `element`.
})
}
Component is created when onMouseDown() called, but its DOM element is div, so it is illegal element in svg document and cannot be displayed.
I have tried with selector='svg:g[foo]', then g element is created, but its namespace is not for SVG (http://www.w3.org/2000/svg), but normal HTML namespace (http://www.w3.org/1999/xhtml) and its class is HTMLUnknownElement > g.
I also tried with selector='svg:svg[foo]', then svg:svg element is created and it is displayed. But svg:svg cannot move with transform attribute so this doesn't work well for my application.
How can I dynamically create svg:g element for attribute selector component?
I am using Angular2: 2.0.0-rc4.

You're right about the namespacing issues keeping the g element from rendering as svg. Unfortunately, attaching the node as an svg element is the only way to feasibly get the component to namespace properly.
However, this doesn't mean this won't work. If you add the drag functionality as a directive on the g element in the template, it will be compiled with your component, and you can offset your logic into that directive. The top level svg will be namespaced correctly, and the template will inherit this accordingly.
import {Component, Input} from '#angular/core';
#Component({
selector: 'svg:svg[customName]', // prevent this from hijacking other svg
template: '<svg:g dragDirective>...</svg:g>', // note the directive added here
style: []
})
export class GComponent {
constructor() { }
}
This may not be ideal, but until https://github.com/angular/angular/issues/10404 is resolved, there's not much of an alternative.

I am not sure that my solution is right but it works for me (even with ivy):
#Component({
...
})
class ParentComponent {
constructor(
private injector: Injector,
private appRef: ApplicationRef,
private componentFactoryResolver: ComponentFactoryResolver,
) {}
createDynamicComponent() {
let node = document.createElementNS("http://www.w3.org/2000/svg", "svg");
let factory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
let componentRef = factory.create(this.injector, [], node);
this.appRef.attachView(componentRef.hostView);
}
}
After that you must manually append node into DOM.

Instead of trying to create your component to the view with the Component Resolver I will do this instead.
Create a object with properties which match the attributes you want to pass to your SVG Component.
Append this object to an array (ex.svgItems).
Add *ngFor="svgItem in svgItems" to the SVG component you want to create dynamically.
Hope it's clear and solve your problem.

Related

Drawer Component Backdrop blocking users from interacting with page will it is open

I have a Drawer Component anchored at the bottom but I would still like to interact with the page above the drawer but either I can click out of if but the drawer closes so I tried the variants persistent and permanent both didn't work they actually made it so nothing at all happens when I click out of if. I think it has something to do with the spacing or padding above, but if anyone knows how to disable that, it would be greatly appreciated.
I solved it slightly differently, by removing the "inset" CSS property of the .MuiDrawer-modal div:
.MuiDrawer-modal {
inset: unset !important;
}
Figured out my problem, I ended us having to do some height changes to the Paper component and it seemed to work the way I wanted. You can overrided the css with makeStyles method in the #material-ui/core/styles directory. I used the classes property example
// Outside the component
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
drawer: {
css here ...
}
})
// Inside Component
const classes = useStyles() // the make styles returns a function and calling the useStyles returns an object with the css.
// Inside return
<Drawer
anchor="bottom"
classes={{ paper: classes.drawer }}
>
Content...
</Drawer>

LitElement <slot> not wokring

I'm creating my custom accordion element. In which I'll have 2 components 1 for ul and other for li.
Content in file accordion-ul.ts, in which I've a slot where I want my li.
import { html, customElement, property, LitElement } from 'lit-element';
import { Accordion } from 'carbon-components';
#customElement('accordion-panel')
export default class AccordionPanel extends LitElement {
firstUpdated() {
const accordionElement = document.getElementById('accordion');
Accordion.create(accordionElement);
}
connectedCallback() {
super.connectedCallback();
}
render() {
return html`
<ul data-accordion class="accordion" id="accordion">
<slot></slot>
</ul>
`;
}
createRenderRoot() {
return this;
}
}
NOTE: I'm getting an error in the console in the firstUpdated() : Uncaught (in promise) TypeError: Cannot read property 'nodeName' of null.
The way I'm using it for testing:
<accordion-panel><li>test</li></accordion-panel>
IDK, it's not working and nothing is printing on the screen. On inspecting the element, I can see there's empty in DOM.
Your problem is that you're trying to use slots, which are a shadow DOM feature but you're not using shadow DOM (since you're overwriting the createRenderRoot method to prevent the creation of the shadowRoot)
So, if you want to use slots, just remove the createRenderRoot function from your class and use shadow DOM
Edit:
You should also update your firstUpdated method so that this part:
const accordionElement = document.getElementById('accordion');
Uses the element from your shadow DOM
const accordionElement = this.shadowRoot.querySelector('.accordion');
Then again, CarbonComponents styling will probably not work so you'll need to add those in some other way

Extending styled components

I have a Field Formik's component, in order to apply custom CSS I do:
const Input = styled(Field)`
// CSS goes here
`
And use Input component, works fine. However I use exactly the same CSS in many places, so I've extracted those CSS to standalone styled-component called SuperInput
Now, how can extend style-componet? I need something like
const Input = styled(Field)`
// inlucde CSS from SuperInput component here
`
Example code.
import styled from 'styled-components'
const SuperInput = styled.input`
// CSS here
`
import { Field } from 'formik'
import { SuperInput } from 'styled_components/super_input'
const SomeFormComponent = () => (
<>
// How to use here <Field /> that has <SuperInput /> CSS applied?
</>
)
Basically you just need to spread or append inside the template literals to get it to work. you can keep the common CSS something like
import styled, { css } from "styled-components"
const commonCss = css`
background: red;
`
And can use it in your component like this:
const Input = styled(Field)`
// CSS goes here
${commonCss}
color: hotpink;
`;
const Input1 = styled(Field)`
${commonCss}
color: lightblue;
`;
This should allow you to use the common CSS in various styled components.
For more info, you can read through css styled component API
Edit:
Styled components create HOCs.
After the added superInput definition, I understand what you are trying to do. So your superInput is creating a button with the certain css properties which you are trying to reuse. In that case when you are using Field and trying to extend SuperInput which is a button doesnot make sense. Your Field component is by default a input element(text box), it can be checkbox, radio, file input also.Whatever CSS is written in superInput can be extracted the way I mentioned above and used at multiple places. The way you are trying to do is not the way styled component is designed. That's my understanding
Note : I may be wrong here about whether it is possible or not. But that's what i can say according to my awareness . Anyone Feel free to correct me if I am wrong.

How to focus a styled component?

I have a styled component:
const StyledComponent = styled.div`
...
`;
and I want to focus it when the component that uses it is mounted:
class someComponent extends React.Component {
componentDidMount() {
this.sc.focus();
}
render() {
return (
<StyledComponent innerRef={(elem) => { this.sc = elem; }}>
...
</StyledComponent>
);
}
}
this technique does not work - is there a solution for this?
You can't focus non-interactive elements without the use of tabIndex ("tabindex" in normal HTML). The "div" element is considered non-interactive (unlike anchor links "a" and button controls "button".)
If you want to be able to focus this element, you'll need to add a tabIndex prop. You can use .attrs if desired to have it be added automatically so you don't need to write the prop every time.
This link
has an example with both React 16 React.createRef() and the callback method.
Have you tried setting the autoFocus attribute, or is that not fit for your use case?
Try passing the autoFocus prop to your StyledComponent like <StyledComponent autoFocus />.

Vue component not rendered inside router-view

recently I've been using vue in frontend and vue-router with it to shape a SPA.
My problem is that I am not able to access a component defined in main Vue instance:
import ElementComponent from './Element';
const app = new Vue({
el: '#app',
router,
components: { Element: ElementComponent }
});
Whenever I do <element></element> within the #app scope the component gets rendered but I can not use the element inside a route component.
My routes are defined like this:
var routes = [
{ path: '/section/part', component: require('../views/Part') }];
And then provided to router instance:
new VueRouter({routes});
Breakpoint whenever I try to call <element></element> inside Part component template I get this in vuedevtools: [Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
(found in at C:\Users\Doe\project\js\views\Part.vue)
You need to import the component into the views/Part component. So do the same thing that you did in the main Vue instance, but only in the part component.
I believe if you want to make components global you need to do use
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
reference https://v2.vuejs.org/v2/guide/components.html#Registration

Resources