I have a custom select web component created with StencilJS and I am using it in AngularJS like this:
<my-select>
<my-option ng-repeat="item in items">
{{item.value}}
</my-option>
</my-select>
The problem is that whenever I push a new item on the items array (items.push({key: 'some', value: 'Some Value'})), the expression seems to print also it's own syntax in the DOM like this:
This is the <my-option> StencilJS component structure:
render() {
return (
<div class="option-container" onClick={this.selectOption(this)}>
<slot />
</div>
);
}
Any help would be so much appreciated. Thank you.
Did you tried some newer Browser like the newest Firefox or Chrome? I faced some really stupid issues because Edge Browser for example does render the <slot tag as an String initially and just renders the innerHTML once. The other Browser will keep the slot as an DOM Element so its more dynamic.
Related
What I'm trying to do
A simple way to render the content of a markdown file when it's passed as a string to another component using .compiledContent (or even using .rawContnent)? Or even a better way than this as obviously usually in Astro we can use the <Content /> Component, but from my knowledge, I can't pass a component or this functionality to another component without using a <slot /> in the parent component.
I have some JS for the parent component and using a <slot/> instead of passing the props to the component would change things, so hopefully looking for a solution with using this.
My setup
Data stored in /src/data/experience as markdown files with a year and a description formatted as markdown in the content section of each file
A component called Tabs.astro which takes props of headings and contents which are both lists of strings
A page /src/pages/experience.astro with the Tabs component in it which is displaying this data
I take the below code to get the data from the markdown files and pass the years and descriptions to the Tab component.
experience.astro
---
import Tabs from "../components/Tabs.astro";
const jobs = await Astro.glob("../data/experience/*.md");
const years = jobs.map((job) => job.frontmatter.year);
const descriptions = jobs.map((job) => job.compiledContent);
---
<!-- My component taking the data to be rendered -->
<Tabs headings={years} contents={descriptions} />
Tabs.astro
And the component renders the info like so
<!-- Tabs -->
<div class="tabs">
<ul class="tabs-header">
{
headings.map((heading) => (
<li>{heading}</li>
))
}
</ul>
<ul class="tabs-content">
{contents.map((content) => <li class="tab">{content}</li>)}
</ul>
</div>
My current solution
At the moment using .compiledContent gets me the correct HTML, however it is all in a string so the HTML doesn't actually render.
What I'm looking for
Is there a native way in Astro to pass markdown as a prop to a component?
If not is there a manual and recommended way in Astro to convert a markdown string and sanitise it to protect against XSS attacks? (if this is a risk in Astro when rendered statically?)
If not what are your most recommended ways to render markdown and sanitise it in JS?
Thanks so much for your time and help! I'm loving using Astro
p.s Also happy to concede and just use a <slot/> in my component if needed... ;)
Astro has a set:html directive you can use in combination with a Fragment like this
<Fragment set:html={post.compiledContent()}/>
After a bit of struggling with this myself, the current solution from the Astro docs for a single file without looping is the following.
Import your file with {Content as YourAliasName} from '../yourPath/yourFileName.md'
Then just use it as a tag <YourAliasName />
Example from the docs for reference:
---
import {Content as PromoBanner} from '../components/promoBanner.md';
---
<PromoBanner />
https://docs.astro.build/en/guides/markdown-content/#the-content-component
I am using an Angular Wrapper for JSON Editor like this:
<div *ngFor="let act of editedActions" class="w-100-p p-24">
{{act.test_step_id}}
<json-editor [options]="editorOptions" [(data)]="act.action_json" [(eventParams)]="act.test_step_id" (jsonChange)="changeStepActions($event)"></json-editor>
<button mat-raised-button class="w-100-p mt-24" color="primary" (click)="editRecordJson(act.test_step_id)">
<span>Update</span>
</button>
</div>
The problem is that eventParams should be different for each editor but it is not varying.
I think problem is this component code (but not sure) (This line is in the component taken from github):
#ViewChild('jsonEditorContainer', { static: true }) jsonEditorContainer: ElementRef;
The component is behaving like a singleton. Any help?
Edit: I edited this repo and added jsonchange event. Details here
You may want to use #ViewChildren with a direct reference to the component instead of a template variable string, to get all the JSON editors references:
#ViewChildren(JsonEditorComponent) jsonEditorContainers: QueryList<ElementRef>;
// ...
jsonEditorContainers.find(...);
It returns a QueryList that allows you to iterate through all ElementRef, and monitor the changes with an Observable changes.
What is eventParams? What is jsonChange? I could be wrong, but data doesn't seem to be two way bindable either, according to the source code.
It seems like you might be looking for something like this:
<div *ngFor="let act of editedActions" class="w-100-p p-24">
<json-editor [options]="editorOptions"
[data]="act.action_json"
(change)="changeStepActions($event, act.test_step_id)">
</json-editor>
</div>
You can then read the test_step_id in your changeStepActions method. If this works, I don't know how you made it compile in the first place.. are you using a CUSTOM_ELEMENTS_SCHEMA?
Its not necessary to use #ViewChildren for that you have to rewrite the entire code of component, make sure while using #ViewChild you pass correct editor reference.
As following
#ViewChild('carEditor' ) carEditor: JsonEditorComponent;
#ViewChild('mobileEditor') mobileEditor: JsonEditorComponent;
Stackblitz example for refernce :
Click here for code example
To use multiple jsoneditors in your view you cannot use the same editor options.
You should have something like:
<div *ngFor="let prd of data.products" class="w-100-p p-24" >
<json-editor [options]="makeOptions()" [data]="prd" (change)="showJson($event)"></json-editor>
</div>
makeOptions = () => {
return new JsonEditorOptions();
}
I have my template something like this,
<ul id="items">
{{#each items as |item index|}}
<li>
<!-- <input type="checkbox" id={{concat "pf" index}} /> -->
{{input type="checkbox" id=(concat "pf" index)}}
</li>
{{/each}}
</ul>
And I have my js something like this,
import Ember from "ember";
export default Ember.Controller.extend({
appName: "Ember Twiddle",
items: [{}],
init: function() {
$(document).on("click.somename", function (e) {
alert($(e.target).parent().length); //should get parent as "li" and length as 1
});
}
});
What happening for my project is that, if I click on ember input helper I am not able to get the parentElement using jQuery. But when I changed that input helper to normal <input type="checkbox" />, I am able to get the parentElement. I don't know whether Safari/Ember is using any virtual DOM or something to render its own helpers. This issue is happening only in Safari. In Chrome, it is working fine.
Unfortunately, I am not able to make a fiddle/plunker as the issue is not able to replicate in them. Sorry about that. In my project, I am using ember#2.7.3.
Any suggestions/help on this issue?
While I'd recommend trying to find a way to avoid using the jQuery click event in the first place, it sounds like an issue with how the different browsers treat parent. I would prefer to use .closest('li').eq(0) to access the first parent that matches the element you are looking for.
In the main template for our Angular 4 application, which is sort of the 'root' template. It contains the container elements for the content area of the page and a sidebar off to the side.
Everything is working great except for older browsers (our customers are very large companies that only have older IE's, so we have to support IE10 and IE11) where the app is really unresponsive (in terms of UI updating and css transitions framerate).
One thing I noticed is that even if I rely on change detection that's purely inside the scope of the main app component, it triggers and executes all of the template code for all of the child components (4 times, in fact).
Here's the main part of my template:
<div class="page-content sidenav-open" id="page-content" #pageContent (window:resize)="adjustMainContent()">
<div [class]="'sidenav ' + (isAnimating ? 'isanimating' : '')" [style.width.px]="sidebar.width" [style.marginLeft.px]="sidebar.isVisible ? 0 : -sidebar.width">
<div class="vertical-resize-bar" (mousedown)="dragStart($event)"> </div>
<div class="sidenav-widget search-widget">
<app-navigation>Loading navigation...</app-navigation>
</div>
</div>
<div [class]="'main-content ' + (isAnimating ? 'isanimating' : '')" (transitionend)="isAnimating = false" [style.width.px]="mainContentWidth">
<router-outlet></router-outlet>
</div>
</div>
Again, functionally, it works great (in Chrome and Firefox, etc...), but not older browsers/computers. I just want those variables in my template snippet there to not trigger change detection on the app-navigation component and its children and whatever component gets loaded into router-outlet.
Am I doing anything (or multiple things) obviously wrong here? I should mention that this is our first Angular app.
I'm trying to work in the concept of a loading tag I can wrap other elements to give a consistent loading display when retrieving data async. Consider the following code:
This example relies on browserify (require) but shouldn't make a difference to
the question
<test>
<loading>
<ul>
1. = <li each={ items }>{ title }</li>
or
2. = <li each={ parent.items }>{ title }</li>
or
3. = <li each={ opt.data.items }>{ title }</li>
</ul>
</loading>
<script>
require('riot');
require('./loading.tag');
this.items = [
{ title: 'title 1'},
{ title: 'title 2'}
];
this.on('mount', function () {
riot.mount('loading', /* for 3 = */ {data: this.items});
})
</script>
</test>
As you can see, the tag is <test /> and contains a nested tag <loading /> which wraps the content <test /> displays. Problem is I'm unsure of the correct way to reference the items array (which would in the real world be pulled in via ajax). I tried options 1 & 2 but nothing would display. 3 works (passing the data as opts) but doesn't feel right.
It may have something to do with <yield /> which is how <loading /> is displaying its contents but I don't know why.
<loading>
<div><yield /></div>
</loading>
When I tried your above code 2 got the correct result. I have a few issues with your above code that may be causing you issues.
1) I've never used require inside of a tag. I doubt it works when requiring a tag file. The .tag extension does nothing. It's the type="riot/tag" that signals a script tag is not javascript but a special script that can be used by riot.
2) You've closed the tag </about> instead of </test>. I think your riot tag just won't compile under these circumstances.
3) I don't know what version of riot you're using, but in 2.3.12 if you mount the test tag then any children (in this case your "loadings") will automatically mount provided you have already loaded the .tag file. I think the problem is that you're calling mount on your "loading" tag twice, which may divorce them from their parent.
I think your issue is that the <loading> tag means nothing when <test> is mounted. You then require loading.tag, which then allows loading to be mounted. Try requiring <loading> before you mount test. I made a js fiddle with how I would do it. Hopefully this answers your question.
https://jsfiddle.net/cm09mtc5/