How to set caret position in contenteditable div in Angular 4? - html

<div id="editable" contenteditable="true" class="search-input" [textContent]="query" (input)="query=$event.target.textContent" (keyup)="searchClient($event)" (focus)="open()"></div>
This is my HTML code and put the DIV that supports content editable. When I press any key then the searchClient($event) method is triggered and set some value. I need to set the caret(cursor) end of the value and focus it.
I tried out few example but I couldn't figure it out the solution for Angular 4.
Note: I tried out How to set caret(cursor) position in contenteditable element (div)? got this error SearchComponent.html:6 ERROR TypeError: Failed to execute 'setStart' on 'Range': parameter 1 is not of type 'Node'.

You can handle this if you use the Renderer2 API.
1º) Create a variable of the tag you want to focus on and create an event to call a function to change the cursor location. Like:
<div class="example" #tagDiv>
I want to focus in here.
</div>
<button (onclick)="changeCursorLocation()"> Click here </button>
2º) In the component create a ViewChild() and the function:
#ViewChild("tagDiv") tagDivField: ElementRef;
changeCursorLocation(){
const element = this.renderer.selectRootElement('#tagDiv', true);
element.focus();
}
Renderer2: (https://angular.io/api/core/Renderer2)
I did not run this specific peace of code, but i have done something like this in the past and it worked very well. So, I hope it can of any help.
Obs: Remenber to import Renderer2 and ElementRef;

You can change it with this...
<div id="editable" contenteditable="true" class="search-input" [textContent]="query" (input)="Revisedquery=$event.target.textContent" (keyup)="searchClient($event)" (focus)="open()"></div>
Also find this usefull link for deeply study the matter.
https://github.com/angular/angular/issues/9796

Related

*ngIf causing custom directive to not work properly

A custom directive applied to both components(1/2)-in-spotlight is not working properly when using *ngIf. The issue resolves when I remove the *ngIf and one of the components that would not show in the current situation/"mode".
In the HTML file using the component (original):
<div>
<div>
<div>
<div>
<component1-in-spotlight *ngIf="mode===OptionOne"></component1-in-spotlight>
<component2-in-spotlight *ngIf="mode===OptionTwo"></component2-in-spotlight>
</div>
</div>
</div>
</div>
I found 2 solutions but both aren't effective or proper.
Duplicating the surrounding parent/grandparent components (placing the second case in an <ng-template #elseBlock>) and applying ngIf-else to the top most component (in the oversimplified example, a div) works. But, I'd have a lot of duplicate code and is a terrible solution.
Option 1 (to illustrate since it might be a bit confusing for some). In the HTML file using the component:
<div *ngIf="mode===OptionOne"; else myElseBlock">
<div>
<div>
<div>
<component1-in-spotlight></component1-in-spotlight>
</div>
</div>
</div>
</div>
</ng-template #myElseBlock>
<div>
<div>
<div>
<div>
<component2-in-spotlight></component2-in-spotlight>
</div>
</div>
</div>
</div>
</ng-template>
Using [hidden] on the 2 components instead of *ngIf seems fine. But there is never a case where the hidden component will be toggled to visible, it's decided upon creation and stays using either of the 2 components until it's destroyed. So, it should just only have one of the 2 components in DOM. Not just hiding it. Plus, that means flipping the logic--[hidden]="mode!==OptionOne". For now, it's just 2 options and seems unlikely more would be added, but I can't guarantee that.
--
It may seem like these 2 components are the same, so why not just have 1 component and pass in the mode and let the logic decide within the TS file of that component? Well, they both have different services that are injected into the constructor for the component. I was trying that before finding out and remembering that I can't use this before calling super() to decide which service to send up to the base class the component is extending.
Merging the 2 components and using #Input to get the "mode":
In the HTML file using the component:
<div>
<div>
<div>
<div>
<component-in-spotlight-merged [inputMode]="mode"></component-in-spotlight-merged>
</div>
</div>
</div>
</div>
In the component-in-spotlight-merged TS file--what I tried to do:
export class ComponentInSpotlightMergedComponent extends MyComponentBaseComponent {
#Input() inputMode: MyEnumType;
//...
constructor(
myService1: MyService1,
myService2: MyService2,
){
if(this.inputMode === Option1){
super(myService1);
}
else{
super(myService2);
}
}
//...
}
Using [hidden] can be for a quick fix, but is there a proper way to fix this?
Edit:
Not working meaning: It's a custom directive for tabbing focus between elements and the hotkey logic is binded here. Somehow the hotkey works but the focus is not working as it expected and none of my console.log() are outputted.
Angular 9+
You can use Angular NgSwitch directive as shown below.
<div [ngSwitch]="mode">
<!-- the same view can be shown in more than one case -->
<component1-in-spotlight *ngSwitchCase="option1">...</component1-in-spotlight>
<component2-in-spotlight *ngSwitchCase="option2">...</component2-in-spotlight>
<!--default case when there are no matches -->
<some-element *ngSwitchDefault>...</some-element>
</div>
The fix was to use setTimeout(() => myCallbackFn(), 0); (on my hotkey bind function that is called in ngAfterViewInit in a component class down the line--a view grandchild?).
I was in a rabbit hole of reading other stackoverflow questions and found How do I combine a template reference variable with ngIf? where a comment mentioned that ngIf takes a tick of time to evaluate. I eventually searched and found How to check whether ngIf has taken effect.

What is the equivalent of control.disable in case of QueryList in angular

I need to disable some fields based on my angular form. I am trying to disable the DOM elements in component class because many html tags are customized and so disabled attribute cannot be used there. The way I am doing this is using #ViewChild/#ViewChildren in my component and disabling in ngAfterViewInit(). I am not able to disable the elements which are inside ngIf in html. Below is the code:
Html:
<div *ngIf="displayAdvOpt">
<div class="card-title">Rules</div>
<abc-select-field
#rule
width="100%"
label=" "
formControlName="_rules"
[options]="rules"
></abc-select-field>
<div>
Component class:
#ViewChildren('rule') ruleSelect;
When logging ruleSelect in component class, it shows that is a QueryList and not a FormControl, as is the case for the elements not inside ngIf. Due to this, I am not able to do ruleSelect.control.disable() to make it disabled in html. I am doing these in ngAfterViewInit().
Please let me know how can I disable a QueryLst or if there is any other way.
#Abhinash, you can not acces to any element if is not in the screen. As you has the element under a *ngIf you need condition becomes true and "after Angular repaint", enable/disable... so, in general
this.condition=true;
setTimeout(()=>{
this.ruleSelect.....
})
But your abc-select-field, has a FormControl in any way and the FormControl exist even the abc-select-field is not in screen, so if you makes that the elements are disabled depending if the control is disabled
In order to disable the select field, you don't need to know neither that 'distinction' (QueryList/Control) nor a 'local reference' (#rule) :
Simply, in the code you want to disable, you just need to do this:
this.form.get('_rules').disable();
// or this.form.constrols['_rules'].disable();
Similarly, when you want to re-enable it you can use:
this.form.get('_rules').enable();
Below is the workaround I tried.
<div *ngIf="displayAdvOpt">
<div class="card-title">Rules</div>
<abc-select-field
#rule
width="100%"
label=" "
formControlName="_rules"
[options]="rules"
on-mouseover="isDisabled()"
></abc-select-field>
<div>
In component class:
#ViewChild('rule') ruleSelect;
isDisabled() {
if (this.showChanges){
this.ruleSelect.control.disable();
}
I see that in ngAfterViewInit(), ruleSelect is a QueryList but in isDisabled() the method called after on-mousehover, it is coming as AbcSelectFieldComponent and so I can call .control.disable() on it. The only reason I can think of is this- https://stackoverflow.com/a/55610325/4464806
Anymore suggestions are welcome!!

Angular2 function call from html element with no load event (or similiar)

I am new to Angular and have run into a problem that seems to have a javascript work around but they aren't very elegant.
I have a model with an array property. I ngfor the list property to build some html selection options. This is all working nicely. The problem comes when I am trying to set default value...the html elements don't have a load event.
I tried numerous html elements and they don't appear to have a load event either but I certainly could be doing it wrong.
I have seen a solution to put javascript tag right after the html and I could do that but I was really looking for a more elegant way in Angular.
I saw this SO post and thought that was my answer but there is a warning given that I agree with and thus it doesn't appear to be a good solution.
Regardless I tried it just to see if it would work and I got:
Failed to execute 'setAttribute' on 'Element': '{{loadDefaults()}}' is not a valid attribute name
<span {{loadDefaults()}} ></span>
So how can I fire an AS2 function in the component to load the default values?
HTML (btw this is NOT a full page load so there is no body tag):
<tr>
<td *ngFor="let loc of locOptions;">
<span>{{loc.text}}</span>
<input type="radio" name="radiogroup" [value]="loc.value" (change)="onSelectionChange(loc.value)">
</td>
</tr>
Edit
I thought perhaps mistakenly that ngoninit would fire too soon...before the html elements are rendered.
So perhaps what is being suggested is that I add a boolean is default to the model and bind THAT as the element is rendered.
In your ngonit function set this.locOptions to your default values. The value can be changed later on in any function and the change will be reflected in the view. Hope this helps you.
You should use ngOnInit to init you data, and call retrieve your data from your component :
defaults : any;
ngOnInit {
this.defaults = loadDefaults();
}
loadDefaults() {
//get data
}
HTML :
<span>{{defaults}}</span>

Angular2: undefined errors when trying to set focus to an input field using #ViewChild annotation

I have a input field which I set focus to when my view loads in the following way:
ngAfterViewInit() {
this.focusInput.nativeElement.focus();
}
this works fine from within the ngAfterViewInit() function but when I try to do it in another part of my view when a button is clicked I get an exception saying that focusInput is undefined. After reading up a bit it seems like ngIf could be the cause of this as the part of the view that contains the input field #focusInput gets shown or hidden using ngIf. Is there any way I can check using ngOnChanges() or anything else whether the #focusInput input field is currently shown and if it is set focus to it?
It happens when you have ngIf or ngFor directives inside your template and your input can not be linked to focusInput property you added inside your class. Instead use this code:
<input type="text" #myInput />
{{ myInput.focus() }}
Just add {{ myInput.focus() }} right after input inside template
The simplest solution turned out to be writing a custom focus attribute directive. This helped a lot:
How to move focus on form elements the Angular way
I know its very late to answer your question. If you want focus after any click or view change so for this you need to call change detector.
You can call change detection after your view change or a click by calling detectchanges().
`constructor(private detRef:ChangeDetectorRef) {}
#ViewChild('name_input') input: ElementRef;
private buttonClick(): void {
this.detRef.detectChanges();
this.input.nativeElement.focus();
}`
Hope this will helpful.

mootools event listener disappears after element.innerHTML is changed

I putting together a page that will display a set of stored values. I am using mootools and AJAX calls to update the values without needing to refresh the page each time the user selects a new item from the drop down menus.
the HTML each line looks something like:
<div class="selections">
<input class="checkbox selector" type="checkbox" CHECKED />
<span class="b_name">
<select class="b_n selector">
<!-- options -->
</select>
</span>
<span class="b_level">
<select class="b_l selector">
<!-- options -->
</select>
</span>
<span class="values">
<!-- the values -->
</span>
</div>
In the head I have set up an event listener like:
$$('.selector').addEvent('change', function(event){changeValues(this);});
My problem is that when the "b_name" select changes I have to update the list of options in the "b_level" select. I accomplish that by getting a list of the possible options from my database through a PHP script on another page and replacing "b_level"'s innerHTML. Once I do that, the event listener attached to "b_l selector" no longer works.
I tried to resolve this issue by explicitly attaching an event listener to "b_l selector" each time "b_name" changes like so:
row.getElement('.b_l').addEvent('change', function(event){changeValues(row.getElement('.b_l'));});
where 'row' is the html element 'div.selections'.
It still isn't working and I have no idea what's going on. Can anyone offer a suggestion as to how I can get this resolved? or perhaps a better way to do what I'm doing.
This is how JavaScript works, it's not a bug.
What you need to use is Element Delegation - you attach an event to the parent element, in the same time specifying the element that the event should be delegated to.
Here's a basic example of Element Delegation in action: http://jsfiddle.net/oskar/ENR3E/
And the documentation: http://mootools.net/docs/more/Element/Element.Delegation
When you set innerHTML on an element, the element's contents are completely cleared and replaced with a new set of elements -- the ones parsed from the innerHTML property. Any events set on the old elements will not apply to the new ones.
jQuery provides a solution to this problem with live() events. I found a solution here that apparently achieves the same with mootools.
Your approach is correct, there's probably just a bug in your addEvent() code. The reason the event handler disappears when you replace the innerHTML is straightforward enough - you are removing the elements that the handlers are on, so the handlers are removed as well. But your approach to re-add the handler should work.
I think it's possible that it's a scoping issue. What happens if you reference the div explicitly, like this:
row.getElement('.b_l').addEvent('change', function(event){
{
changeValues($$('div.selections .b_l'));
});