While I understand this is probably a terrible practice, I need to build StencilJS component such that inside render(), I don't want to render component tag itself due to already existing style guide and it expect DOM to be constructed in certain way. Here is what I'm trying to achieve - component code (from HTML or within another component):
<tab-header-list>
<tab-header label="tab 1"></tab-header>
<tab-header label="tab 2"></tab-header>
</tab-header-list>
when rendered, I want generated DOM to be something like:
<tab-header-list>
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
</tab-header-list>
so inside tab-header-list render() function, I'm doing
return (
<ul>
<slot/>
</ul>
);
and I can do this inside tab-header render() function
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
to get what I want but how can I do this with TSX? (for simplicity sake, above code is really simple but what I really need to build is lot more complicated li tag with events etc so I would like to use TSX)
Tried to store DOM to variable but I'm not sure how I can assign it as this.el (outerHTML seem to be only way I can come up with, but I feel there must be better way)
#Element() el: HTMLElement;
#Prop() label: string;
render() {
var tabheaderDOM = (<li>{this.label}</li>);
// how can I assign above DOM to this.el somehow?
//this.el.outerHTML = ?
}
I appreciate any help I can get - thanks in advance for your time!
Unfortunately, you can't use custom elements without tags, but there is a workaround for it:
You can use Host element as reference to the result tag.
render () {
return (
<Host>....</Host>
)
}
Then in your stylesheet you can set the display property for it:
:host {
display: contents;
}
display: contents causes an element's children to appear as if they were direct children of the element's parent, ignoring the element itself
Beware: it doesn't work in IE, opera mini... https://caniuse.com/#feat=css-display-contents
UPD:
If you are not using the shadowDOM then you need to replace :host by the tag name like:
tab-header {
display: contents;
}
Functional components might be able to help you achieve this. They are merely syntactic sugar for a function that returns a TSX element, so they are completely different to normal Stencil components. The main difference is that they don't compile to web components, and therefore only work within TSX. But they also don't result in an extra DOM node because they simply return the template that the function returns.
Let's take your example:
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
you could write it as a functional component:
import { FunctionalComponent } from '#stencil/core';
interface ListItemProps {
label: string;
}
export const ListItem: FunctionalComponent<ListItemProps> = ({ label }) => (
<li>{label}</li>
);
and then you can use it like
import { ListItem } from './ListItem';
#Component({ tag: 'my-comp' })
export class MyComp {
render() {
return (
<ul>
<ListItem label="tab 1" />
<ListItem label="tab 2" />
</ul>
);
}
}
Which will render as
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
Instead of a label prop you could also write your functional component to accept the label as a child instead:
export const ListItem: FunctionalComponent = (_, children) => (
<li>{children}</li>
);
and use it like
<ListItem>tab 1</ListItem>
BTW Host is actually a functional component. To find out more about functional components (and there limitations), see https://stenciljs.com/docs/functional-components.
I have content with placeholder such as [[name]], [[lastname]].
I want everything from [[ until ]] gets highlight, for example in yellow background while the real content that will be save in DB is still plain text.
For easier to understand, please take a look at this link. https://ckeditor.com/docs/ckeditor4/latest/features/placeholder.html
It is placeholder plugin for CKEditor. And here is working sample. https://ckeditor.com/docs/ckeditor4/latest/examples/placeholder.html
Currently I use textpattern plugin for TinyMCE and this code.
tinymce.init({
// options...
'textpattern_patterns': [
{'start': '[[', 'end': ']]', 'cmd': 'KPHW'}
],
'setup': function(editor) {
editor.addCommand('KPHW', function(ui, v) {
let contentText = editor.selection.getContent({ format: 'text' });
editor.execCommand('mceInsertContent', false, '<span style="background-color: yellow;">[[' + contentText + ']]</span>');
});
}
});
But it replace plain text [[placeholder]] with <span style="background: yellow;">[[placeholder]]</span> which is wrong.
Found this variable plugin https://github.com/ziktar/tinymce-variable.
Still have some bugs but is most active.
I'm trying to display some html entities in a form text input, but v-model seems escaping them.
Is there something I need to write to make v-model displaying correctly html entities?
my sample code is
<el-input v-model="data" readonly="readonly"></el-input>
I know about v-html but I prefer keep using v-model due the automatic two-way binding.
UPDATE
Maybe I expressed myself wrong, I want to display the character, not the html entity, so instead 49.42₹ i need to display 49.42₹.
If you v-model a computed that interprets HTML entities, I think you get the effect you want. You can type in entity values and it will interpret them correctly. However, it might prematurely turn  into a different character; you have to type #8377; and then go back in and insert the &.
new Vue({
el: '#app',
data: {
a: '49.42₹'
},
computed: {
asText: {
get() {
return this.toText(this.a);
},
set(newValue) {
this.a = newValue;
}
}
},
methods: {
toText(html) {
const div = document.createElement('div');
div.innerHTML = html;
return div.textContent;
}
}
})
<link href="//unpkg.com/element-ui#1.0.0-rc.3/lib/theme-default/index.css" rel="stylesheet"/>
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<el-input v-model="asText"></el-input>
{{a}}
<div v-html="a"></div>
</div>
I have a form using the reactive form approach. The form is created as follow in my pug:
form([formGroup]='form', novalidate='', (ngSubmit)='postSurvey(form.value, form.valid)')
Everything works fine except when I try to change the form (which is a FormArray) in the javascript part. I get the following error:
EXCEPTION: Error in http://localhost:8080/app/components/fillForm.template.html:0:326 caused by: control.registerOnChange is not a function
core.umd.js:3497 TypeError: control.registerOnChange is not a function
at setUpControl (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:1634:17)
at eval (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:4752:25)
at Array.forEach (native)
at FormGroupDirective._updateDomValue (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:4747:29)
at FormGroupDirective.ngOnChanges (http://localhost:8080/node_modules/#angular/forms/bundles/forms.umd.js:4616:22)
at Wrapper_FormGroupDirective.ngDoCheck (/ReactiveFormsModule/FormGroupDirective/wrapper.ngfactory.js:30:18)
at View_FillFormComponent2.detectChangesInternal (/AppModule/FillFormComponent/component.ngfactory.js:275:32)
at View_FillFormComponent2.AppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12592:18)
at View_FillFormComponent2.DebugAppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12739:48)
at ViewContainer.detectChangesInNestedViews (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12850:41)
at CompiledTemplate.proxyViewClass.View_FillFormComponent0.detectChangesInternal (/AppModule/FillFormComponent/component.ngfactory.js:64:14)
at CompiledTemplate.proxyViewClass.AppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12592:18)
at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12739:48)
at CompiledTemplate.proxyViewClass.AppView.internalDetectChanges (http://localhost:8080/node_modules/#angular/core/bundles/core.umd.js:12577:22)
at CompiledTemplate.proxyViewClass.View_FillFormComponent_Host0.detectChangesInternal (/AppModule/FillFormComponent/host.ngfactory.js:29:19)
My code to change the form is quite complex and I can't simplify it or reproduce it in a plunker. More than finding directly the solution (it's too difficult with so little details), I would like to understand what this error means? And what might cause this error.
I have figured out that the error occurs at [formGroup]='form' in my HTML.
Any suggestion will help.
Update I have filed an issue on angular github here and have proposed a fix here The plunker to reproduce the issue is here
Yes, that error message is a bit cryptic, but if you use FormBuilder, you would see this when you added a control to FormGroup in your component and named it "A", but then either forgot to add input with formControlName="A" to your template, or formControlName for the intended input is not A, or empty, or not present.
Basically, it says: "I cannot match the control I have in FormGroup to the control in the template".
I came across looking for a solution to the similar issue and then found a solution myself.
My issue was the following. I had a form like this
form: FormGroup = new FormGroup({
status: new FormArray([])
});
Initially it was represented by the list of checkboxes for each status on the template. And then I created a custom component to represent status selector and used it in template like so
<status-selector [formControlName]="'status'"></status-selector>
The problem is that formControlName must point to FormControl instance, but actually it was pointing to a FormArray instance. So, changing to status: new FormControl([]) fixed this issue for me.
In my case this error was thrown because I was using FormControlName instead of FormArrayName to bind to a FormArray in my template.
My component code:
public form = new FormGroup({
...
checkboxes: new FormArray([....])
})
My template code that threw error:
<input type="checkbox" formControlName="checkboxes" />
Fix:
<input type="checkbox" formArrayName="checkboxes" />
I have also encountered this error when mixing template driven with reactive driven approaches (by mistake):
<input #inputCtrl
[formControl]="inputCtrl"
/>
inputCtrl was properly defined in the component. Of course, #inputCtrl must be scrapped in order to work (it was hard to see when input had about 10 attributes).
In my case the error occurred when the formControl name was same as a template variable on the page. For example
<select id="status" [formControl]="userStatus">...</select>
<form-status #userStatus ></form-status> //matching template variable name
this.extensionForm = this._fb.group({
id: [''],
category: [12],
extensions: new FormArray([]),
priority: ['', []],
});
formArray.push(this.extensionForm);
Note :- error occurs because of you have used formControlName
where you declare formArray, you must use formArrayName instead
<input type="text" formControlName="extensions" />
simple solution:
<input type="checkbox" formArrayName="extensions" />
If have defined a FormArray field in your form, note that you do NOT need to label it with formControlName="". You need to handle the input and validation in other ways (setters, getters, functions), but will definitely get an error if you try to assign formControlName to a FormArray!
This error also appears when we use a reactive form inside ng-template in conjunction with *ngIf.
To avoid this use ng-container and do not use ngElse.
In my case I got the error when I used formControlName in the template when actual form model was a FormGroup instance. Changing to formControlGroup helped.
Maybe you have moved a control element outside the group in the template.
OK:
<div formGroupName="passwordForm">
Password: <input type="password" formControlName="password">
Confirm: <input type="password" formControlName="confirmPassword">
</div>
Not OK:
Password: <input type="password" formControlName="password">
<div formGroupName="passwordForm">
Confirm: <input type="password" formControlName="confirmPassword">
</div>
In my case the issue was that I was referring to something as a FormGroup instead of a FormControl with an object as the value.
This was my initial, faulty code:
formArray.push(
new FormGroup({
value: new FormControl('', Validators.required),
description: new FormControl('', Validators.required),
tags: new FormArray([], Validators.minLength(1)),
}),
);
and in the template, I was using a component (custom-form-component in this example) which implements ControlValueAccessor:
<li *ngFor="let item of items; let itemIndex = index"
<custom-form-component [formGroupName]="itemIndex"></custom-form-component>
</li>
This group is handled in the custom-form-component component which I'm using, therefore instead a FormControl should be used:
formArray.push(
new FormControl({
value: '',
description: '',
tags: [],
}),
);
and in the template, use formControlName instead of formGroupName:
<li *ngFor="let item of items; let itemIndex = index"
<custom-form-component [formControlName]="itemIndex"></custom-form-component>
</li>
In Angular I had the same problem when I tried to build my own form component and forgot to implement the ControlValueAccessor interface:
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'my-country-select',
templateUrl: './country-select.component.html',
styleUrls: ['./country-select.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CountrySelectComponent),
multi: true,
},
],
})
export class CountrySelectComponent implements OnInit, OnChanges, ControlValueAccessor {
propagateChange = (_: any) => { }; // ControlValueAccessor
private internalValue: string | undefined;
get value(): string | undefined {
return this.internalValue;
}
set value(value: string | undefined) {
this.internalValue = value;
this.propagateChange(value);
}
// some other methods here
// implementing the ControlValueAccessor interface with these three methods
writeValue(obj: any): void {
if (obj) {
this.value = obj;
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
// throw new Error('Method not implemented.');
}
}
I encountered this when I accidentally reset the value of my form control property instead of using the .setValue() method.
Wrong
control = new FormControl([1])... control = []
Right
control = new FormControl([1])... control.setValue([])
Adding in what was causing it in my situation.
.ts file,
...
export class MyComponent {
dateFrom = new FormControl(new Date());
...
}
.html file,
<mat-form-field>
<input matInput [matDatepicker]="dateFrom" [formControl]="dateFrom"
placeholder="Min date">
<mat-datepicker-toggle matSuffix [for]="dateFrom"></mat-datepicker-toggle>
<mat-datepicker #dateFrom ></mat-datepicker>
</mat-form-field>
Basically, within the template file, it didn't know which 'dateFrom' to choose, and chooses the one in the template file, which isn't the FormControl in my typescript file, it's the mat-datepicker element in the template file.
Renaming the FormControl to dateFromCtrl fixed this,
i.e.
...
export class MyComponent {
dateFromCtrl = new FormControl(new Date());
...
}
.html file,
<mat-form-field>
<input matInput [matDatepicker]="dateFrom" [formControl]="dateFromCtrl"
placeholder="Min date">
<mat-datepicker-toggle matSuffix [for]="dateFrom"></mat-datepicker-toggle>
<mat-datepicker #dateFrom ></mat-datepicker>
</mat-form-field>
Works as expected.
Kodos to VS Code for figuring this out. I got pushed this direction by doing a Cmd + Click on the initial 'dateFrom' at [formControl]="dateFrom", and it pointed me to the mat-datepicker element.
to me it happened when I used same [formControl]="carBrand" and [matAutocomplete]="carBrandAuto" from my autocomplete input
I changed this
FROM:
...
<input
[formControl]="carBrand"
[matAutocomplete]="carBrand"
>
<mat-autocomplete matAutocomplete #carBrand="matAutocomplete">
...
TO
...
<input
[formControl]="carBrand"
[matAutocomplete]="carBrandAuto"
>
<mat-autocomplete matAutocomplete #carBrandAuto="matAutocomplete">
...
For future readers, my problem was a simple one and it causes TypeError: control.registerOnChange is not a function
When I was creating the new FormGroup I accidentally used FormGroup instead of FormControl
myForm: FormGroup = new FormGroup({
lname: new FormGroup({}),
fnam: new FormGroup({}),
date: new FormGroup({}),
});
It should have been this:
myForm: FormGroup = new FormGroup({
lname: new FormControl(''),
fnam: new FormControl(''),
date: new FormControl(null),
});
Hopefully this will save someone a few minutes in the future!
I had this error when I tried to access formGroup control like this
myFormGroup['controlName']
instead of using .get('controlName'). and btw If .get('...') is causing some typing problems, see this: https://stackoverflow.com/a/67835904/8094012
In my case, I was passing simple property to formControl i.e.
tagsList: Array<string> = ['banana', 'apple']
<myComponent [formControl]="tagsList"></myComponent>
but generally it should be like
tagsList = new FormControl()
<myComponent [formControl]="tagsList"></myComponent>
or
tagsList: Array<string> = ['banana', 'apple']
<myComponent [(ngModel)]="tagsList"></myComponent>
I am trying to build a rating form and want to use the JQuery UI increment slider except I don't want the value to be numbers, I'd like it to say something like "great" or "bad"
I'm great with HTML & CSS not so much with JavaScript or JQuery. I am taking a class in NYC next month to help me become more fluent in these languages. In the mean time any and all help is appreciated. I tried to hack other scripts I have found but keep running into problems. I ran into this script which is close to what I want, except the increments show #s.
I feel as if this code can be easily modified to do what I want.
http://jsfiddle.net/dmcgrew/EquTn/3/
<html>
<div class="kpa_rate kpa_rate1">
<label for="kpa1_rating_value">Parking:</label>
<div id="1" class="slider"></div>
<input type="text" class="kpa1_rating_value" name="kpa1_rating" value="0" />
</div>
<div class="kpa_rate kpa_rate2">
<label for="kpa2_rating_value">Entrance:</label>
<div id="2" class="slider"></div>
<input type="text" class="kpa2_rating_value" name="kpa2_rating" value="0" />
</div>
</html>
JavaScript
<script>
$(function() {
$( ".slider" ).slider({
range: "max",
min: 0,
max: 5,
value: $("input", this).val(),
slide: function( event, ui ) {
//get the id of this slider
var id = $(this).attr("id");
//select the input box that has the same id as the slider within it and set it's value to the current slider value.
$("span[class*=" + id + "]").text(ui.value);
$("input[class*=" + id + "]").val(ui.value);
}
});
});
</script>
Did you want different words ("great, good, neutral, etc") for each value, or just good/bad at the ends?
If you want diff words for each of the 5 values, then like j08691 said, make an array that has the words you want at the indexes you want.
After this, replace the ui.value in .val(ui.value) with the array of value (Arr[ui.value]).
(I'm not sure you actually need the "span" part, but I don't do jquery, so I'm not sure).
$(function() {
var Array = ["die","very bad", "bad", "neutral","good","great"];
$( ".slider" )...etc
...
$("input[class*=" + id + "]").val(Array[ui.value]);
This worked for me.
I did a bit of tinkering, and I think I've found a way to superficially solve your problem. Since the number inputted as the first value doesn't affect the slider button/thing, I'd say just put the first word that the slider should indicate (in my case, "die").
//value does not equal a number here
In the next bit of code, just change input to Array[input], and you should be all set!
min: 0,
max: 5,
value: $("Array[input]"
I hope this works well enough for you.