I am very new in angular 2. I am trying to create icon component. The code below works fine in Chrome and Firefox, but doesn't work in Internet Explorer 11.
My component looks as following:
#Component({
selector: 'my-icon',
template: `
<svg *ngIf="_iconClass" class="icon" [ngClass]=_iconClass
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="" attr.xlink:href="#{{ _iconClass }}" />
</svg>
`
})
export class MyIcon {
private _iconClass: string = '';
#Input() set iconClass(i: string) {
if ((i !== undefined) && (i.indexOf('.ico') > -1)) {
// remove .ico from iconname
this._iconClass = i.substr(0, i.length - 4);
} else {
this._iconClass = i;
}
}
Then, I am using it in another component as following:
<my-icon iconClass="icon--user"></my-icon>
I haven't add all the code, hope it still makes sense. When I have checked in Developer tools, tag <use xlink:href> is empty. My assumption was that IE 11 can't identify attr.xlink:href="#{{ _iconClass }}".
I cannot see what is wrong. I would really appreciate any help.
Edit:
This error is printed to the console
EXCEPTION: TypeError: Unable to get property 'contains' of undefined or null reference in [_iconClass in MyIcon#1:9]
private validateIcon(): void {
if ((this._iconClass !== undefined) && (this._iconClass !== '') && (document.getElementById(this._iconClass) === null)) {
if (this._validateIconRunOnce) {
console.warn('Icon(' + this._iconClass + ') not found.');
this._iconClass = 'not-found';
} else {
// delay validate icon for 3s to wait until the iconlibrary is loaded
this._validateIconRunOnce = true;
setTimeout(() => this.validateIcon(), 3000);
}
}
}
Gunter
Thank you very much for your support. I found a solution here: https://github.com/eligrey/classList.js
To support svg in IE9+ it is required to add classList.js.
Related
Context: I'm going to insert an entry animation to some cards. In the cards there are some FAQs obtained from an httpRequest.
//faqs-section.component.ts
In the OnInit I perform some operations including the http request made through NgRx actions. Inside the subscription the faqs array is filled. (This works correctly)
ngOnInit(): void {
this.store.dispatch(helpDeskActions.searchFaqsStart({faq: new FaqModel()}));
this.storeSub = this.store.select('helpDesk').subscribe(helpDeskState => {
this.errorMessage = helpDeskState.errorSearchFaqs;
this.faqs = helpDeskState.faqs;
this.loading = helpDeskState.loadingSearchFaqs;
})
this.searchSubscription = this.keyUp.pipe(
map((event: any) => (<HTMLInputElement>event.target).value),
tap(() => this.loading = true),
debounceTime(300),
distinctUntilChanged()
).subscribe(value => {
this.loading = false;
this.searchValue = value;
});
this.observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('show');
} else {
entry.target.classList.remove('show');
}
})
})
this.faqCards = document.querySelectorAll('.faq-card');
console.log(this.faqCards); // is empty... []
this.faqCards.forEach((elem: Element) => this.observer?.observe(elem));
}
The html code of this section:
<div class="card flex-list faq-card" *ngFor="let faq of faqs | filterfaq:searchValue | loading:loading">
<h3 [innerHTML]="faq.domanda | removeaccent | selectedtext:searchValue"></h3>
<div class="faq-details"
[innerHTML]="faq.risposta | removeaccent | addstyle | selectedtext:searchValue"></div>
<div class="end-align-self">
<button *ngIf="isReading !== '' && isReading === faq.id?.toString()"
class="button-extra-small end-align-self margin-right-4"
(click)="pauseReading($event)">
{{isPaused ? 'Riprendi lettura' : 'Metti in pausa'}}
</button>
<button class="button-extra-small button-outlined end-align-self"
(click)="readFaq($event, faq.domanda, faq.risposta, faq.id)">
{{isReading === faq.id?.toString() ? 'Stop lettura' : 'Leggi la FAQ ad alta voce'}}
</button>
</div>
</div>
Where i just render faqs with ngFor
and the css
.faq-card {
opacity: 0;
filter: blur(6px);
transform: translateY(100%);
transition: all 1s;
}
.show {
opacity: 1;
filter: blur(0);
transform: translateX(0) translateY(0);
}
The problem:
The .show class is never applied. I used IntersectionObserver to animate many other parts of my application, only unlike this one the components were already present as they were static ...
Inside NgOninit the faq array is empty and I believe this is due to the fact that the faq does not yet exist at that moment.
So my question is: how can I go about correcting this?
What I want to do is the one indicated in this youtube video:
https://www.youtube.com/watch?v=T33NN_pPeNI
PS: sorry if my english is not the best. If you need to add information to understand better, do not hesitate to ask me.
Thanks in advance to those who will help me
I have a little problem with loading AdSense ads with react. The site is based on asp.net and I normally render react components on the server-side using #Html.React("name of the component") and then if I need I update components with fetch calls so essentially hybrid solution.
In the case of AdSense, unfortunately, I get this error:
Warning: Did not expect server HTML to contain a <ins> in <ins>
As I understand it complains that server-rendered markup has been changed on the client-side, obviously due to AdSense loading ads once the page is loaded in the browser. In the result, in Chrome I can't see ads but somehow it does manage to load them on Firefox :/
Unfortunately, I'm not sure how can I fix it... how can I let know react that this component is ok to be changed on client-side?
Here is component code:
import React from "react";
import { IAdSenseAdProps } from "./IAdSenseAdProps";
export class AdSenseAd extends React.Component<IAdSenseAdProps, {}> {
constructor(props: IAdSenseAdProps) {
super(props);
}
componentDidMount() {
(window.adsbygoogle = window.adsbygoogle || []).push({});
};
render() {
return (
<ins
className="adsbygoogle"
data-adtest={this.props.dataAdTest}
{...this.props.style != null && { "style": this.props.style }}
{...this.props.dataAdFormat != null && { "data-ad-format": this.props.dataAdFormat }}
{...this.props.dataAdLayoutKey != null && { "data-ad-layout-key": this.props.dataAdLayoutKey }}
data-ad-client={this.props.dataAdClient}
data-ad-slot={this.props.dataAdSlot}>
</ins>
);
}
}
I fully appreciate that there are plenty of similar questions - I've seen them all but none of them seems to be solving this problem. Any help very appreciated :)
I think I have just managed to fix it. I know it's not the best solution but seems to be working. Hope will help others.
I essentially managed to cheat react and added fake HTML structure. Same html structure that AdSense is using so my code now looks like this:
import React from "react";
import { IAdSenseAdProps } from "./IAdSenseAdProps";
export class AdSenseAd extends React.Component<IAdSenseAdProps, {}> {
constructor(props: IAdSenseAdProps) {
super(props);
}
componentDidMount() {
(window.adsbygoogle = window.adsbygoogle || []).push({});
};
render() {
return (
<ins
className="adsbygoogle"
data-adtest={this.props.dataAdTest}
{...this.props.style != null && { "style": this.props.style }}
{...this.props.dataAdFormat != null && { "data-ad-format": this.props.dataAdFormat }}
{...this.props.dataAdLayoutKey != null && { "data-ad-layout-key": this.props.dataAdLayoutKey }}
data-ad-client={this.props.dataAdClient}
data-ad-slot={this.props.dataAdSlot}>
<ins><ins><iframe></iframe></ins></ins>
</ins>
);
}
}
I started getting a new error:
Extra attributes from the server: width,height,frameborder,marginwidth,marginheight,vspace,hspace,allowtransparency,scrolling,allowfullscreen,onload,id,name,style
but at least now ads are loading and working as expected :)
I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
I've reviewed this question and the solutions and am using the JS provided:
A-Frame - playing / pausing sound with a custom ('a-sound') source.
I'm trying to construct a popup that has text displayed but also a narration. I want to have a stop audio button so the user can stop the narration at will. If I use <a-sound>, I don't seem to be able to access/create child elements. If I use <a-entity> I get an error:
"Uncaught TypeError: Cannot read property 'playSound' of undefined".
This is the element:
<a-entity id="wrapper" position="0 0.5 1">
<a-entity narration id="sound" mixin="alpr" sound="src: #piano; autoplay: false; loop: false; volume: 10;">
<a-text id="close" mixin="close">
</a-text>
<a-text stopsound id="stop" mixin="stop-sound">
</a-text>
</a-entity>
This is the JS:
AFRAME.registerComponent('narration', {
init:function() {
let playing = false;
let audio = this.el.components.sound;
this.el.addEventListener('click', () => {
if(!playing) {
audio.playSound();
} else {
audio.stopSound();
}
playing = !playing;
});
this.el.addEventListener('mouseleave', () => {
audio.stopSound();
})
var stopaudio = document.querySelector('#stop');
stopaudio.addEventListener('click', () => {
audio.stopSound();
})
var closeaudio = document.querySelector('#close');
stopaudio.addEventListener('click', () => {
audio.stopSound();
})
}
})
Please let me know what I'm missing. Thanks!
The sound component is not yet initialized. Add sound to your narration component dependencies as described in the docs:
AFRAME.registerComponent(‘narration’,{
dependencies: [‘sound’],
...
}
I have a bunch of logo's, generated with illustrator, that I would like to embed in my website directly. The svgs all have a <style> element where the styles are defined inside the svg element, something like this:
<svg>
<style>
.st1 { fill:#ff00ff; }
.st2 { fill:#ff3421; }
/* ... and so on */
</style>
<!-- svg paths and shapes -->
</svg>
The problem is that these styles interfer with each other. So if the last images defines .st21 {fill:#555555} this style is applied to all path with class="st21", including paths from all previously loaded svg images.
In another thread somebody suggested to wrap my svg-xml with an <object> tag, that doesn't seem to work.
How can I make sure that inline SVG styles are not interfering with each other, without touching the actual SVG code?
here's a pen to illustrate the problem: https://codepen.io/pwkip/pen/RLPgpW
I would suggest to export svg with appropriate CSS properties in the first place.
During export from Illustrator choose :style attributes it would be something like this in svg:
<path style="fill: red"></path>
It could increase your file size but it definitely do the job. I found a nice explanation here
I came up with a JavaScript solution. Although this might be a little overkill and slow if you use a lot of SVGs. But this works fine so far.
What I do is, I iterate over all SVGs and collect/parse their CSS styles. I collect all class names and properties and apply them manually onto the SVG elements.
const svgCollection = document.querySelectorAll( 'svg' );
function parseStyles( styleTag ) {
if ( !styleTag ) {
return {};
}
const classCollection = {};
const plain = styleTag.innerHTML;
const regex = /\.([^\s{]+)[\s]*\{([\s\S]+?)\}/;
const propertyRegex = /([\w\-]+)[\s]*:[\s]*([^;]+)/;
const result = plain.match( new RegExp( regex, 'g' ) );
if ( result ) {
result.forEach( c => {
const classResult = c.match( regex );
const propertiesResult = classResult[ 2 ].match( new RegExp( propertyRegex, 'g' ) );
const properties = propertiesResult.reduce( ( collection, item ) => {
const p = item.match( propertyRegex );
collection[ p[ 1 ] ] = p[ 2 ];
return collection;
}, {} );
classCollection[ classResult[ 1 ] ] = properties;
} );
return classCollection;
}
return {};
}
function applyProperties( element, properties ) {
if ( !properties ) {
return;
}
Object.keys( properties ).forEach( key => {
element.style[ key ] = properties[ key ];
} );
}
function applyStyles( element, styles ) {
const classNames = ( element.getAttribute( 'class' ) || '' ).split( ' ' );
classNames.forEach( c => {
applyProperties( element, styles[ c ] );
} );
element.setAttribute( 'class', '' );
}
for ( let i = 0; i < svgCollection.length; i += 1 ) {
const svg = svgCollection[ i ];
const styles = parseStyles( svg.querySelector( 'style' ) );
const elements = svg.querySelectorAll( '[class]' );
for ( let j = 0; j < elements.length; j += 1 ) {
applyStyles( elements[ j ], styles );
}
}
<p>this shape should be blue:</p>
<svg height="210" width="210">
<style>
.st1 {
fill:blue;
}
</style>
<polygon points="100,10 40,198 190,78 10,78 160,198" class="st1"/>
</svg>
<p>this shape should be red:</p>
<svg height="210" width="210">
<style>
.st1 {
fill:red;
}
</style>
<ellipse cx="105" cy="80" rx="100" ry="50" class="st1" />
</svg>
Even though this works great, I wouldn't suggest it (as mentioned in the comments at your question). Better to set CSS Properties to Presentation Attributes in Illustrater