Angular 2/4 set focus on input element - javascript

How can I set focus on an input by (click) event? I have this function in place but I'm clearly missing something (angular newbie here)
sTbState: string = 'invisible';
private element: ElementRef;
toggleSt() {
this.sTbState = (this.sTbState === 'invisible' ? 'visible' : 'invisible');
if (this.sTbState === 'visible') {
(this.element.nativeElement).find('#mobileSearch').focus();
}
}

You can use the #ViewChild decorator for this. Documentation is at https://angular.io/api/core/ViewChild.
Here's a working plnkr: http://plnkr.co/edit/KvUmkuVBVbtL1AxFvU3F
This gist of the code comes down to, giving a name to your input element and wiring up a click event in your template.
<input #myInput />
<button (click)="focusInput()">Click</button>
In your component, implement #ViewChild or #ViewChildren to search for the element(s), then implement the click handler to perform the function you need.
export class App implements AfterViewInit {
#ViewChild("myInput") inputEl: ElementRef;
focusInput() {
this.inputEl.nativeElement.focus()
}
Now, click on the button and then the blinking caret will appear inside the input field. Use of ElementRef is not recommended as a security risk,
like XSS attacks (https://angular.io/api/core/ElementRef) and because it results in less-portable components.
Also beware that, the inputEl variable will be first available, when ngAfterViewInit event fires.

Get input element as native elements in ts file.
//HTML CODE
<input #focusTrg />
<button (click)="onSetFocus()">Set Focus</button>
//TS CODE
#ViewChild("focusTrg") trgFocusEl: ElementRef;
onSetFocus() {
setTimeout(()=>{
this.trgFocusEl.nativeElement.focus();
},100);
}
we need to put this.trgFocusEl.nativeElement.focus(); in setTimeout() then it'll work fine otherwise it will throw undefined error.

try this :
in you HTML file:
<button type="button" (click)="toggleSt($event, toFocus)">Focus</button>
<!-- Input to focus -->
<input #toFocus>
in your ts File :
sTbState: string = 'invisible';
toggleSt(e, el) {
this.sTbState = (this.sTbState === 'invisible' ? 'visible' : 'invisible');
if (this.sTbState === 'visible') {
el.focus();
}
}

try this.
//on .html file
<button (click)=keyDownFunction($event)>click me</button>
// on .ts file
// navigate to form elements automatically.
keyDownFunction(event) {
// specify the range of elements to navigate
let maxElement = 4;
if (event.keyCode === 13) {
// specify first the parent of container of elements
let container = document.getElementsByClassName("myForm")[0];
// get the last index from the current element.
let lastIndex = event.srcElement.tabIndex ;
for (let i=0; i<maxElement; i++) {
// element name must not equal to itself during loop.
if (container[i].id !== event.srcElement.id &&
lastIndex < i) {
lastIndex = i;
const tmp = document.getElementById(container[i].id);
(tmp as HTMLInputElement).select();
tmp.focus();
event.preventDefault();
return true;
}
}
}
}

Related

Simulating a click on dynamically rendered React element

I am creating a geography game where you are supposed to click on a specific country on a world map - if you click on the right one, the country changes color and the game presents a new country to be clicked at. If the player doesn't know, he can click on a button which will show him the correct answer. For this, I want to simulate a click event, so that the same onClick() function is called as if you clicked on the correct country.
I am using D3, and the world map is made up of svg paths. Below is the code I thought would work, using the HTMLElement.click() method:
function simulateClick() {
// for each index in the nodelist,
// if the properties are equal to the properties of currentTargetUnit,
// simulate a click on the path of that node
let nodelist = d3.selectAll(".unit")
for (let i = 0; i < nodelist._groups[0].length; i++) {
if (nodelist._groups[0].item(i).__data__.properties.filename === currentTargetUnit.properties.filename) {
console.log(nodelist._groups[0][i])
// logs the correct svg path element
nodelist._groups[0][i].click()
// logs TypeError: nodelist._groups[0][i].click is not a function
}
}
}
I then looked at some tutorials which say that, for some reason I don't fully understand, you rather need to use React.useRef for this - but in all their examples, they put a "ref" value on an element which is returned from the beginning in the React component, like so:
import React, { useRef } from "react";
const CustomTextInput = () => {
const textInput = useRef();
focusTextInput = () => textInput.current.focus();
return (
<>
<input type="text" ref={textInput} />
<button onClick={focusTextInput}>Focus the text input</button>
</>
);
}
This obviously doesn't work because my svg path elements aren't returned initially. So my question is - how can I achieve this, whether using useRef or not?
Below are some previous questions I looked at which also did not help.
Simulate click event on react element
React Test Renderer Simulating Clicks on Elements
Simulating click on react element
I finally solved it - instead of calling the onClick() which was set inside the node I created a new clickevent with the help of the following code:
function simulateClick() {
let nodelist = d3.selectAll(".unit")
for (let i = 0; i < nodelist._groups[0].length; i++) {
if (nodelist._groups[0].item(i).__data__.properties.filename === currentTargetUnit.properties.filename) {
var event = document.createEvent("SVGEvents");
event.initEvent("click",true,true);
nodelist._groups[0].item(i).dispatchEvent(event);
}
}
}

JavaScript classes, how does one apply "Separation of Concerns" and "Don't repeat Yourself" (DRY) in practice

I'm just in the process of learning how JavaScript classes work and I'm just looking for some advice on how to achieve something quite simple I hope regarding animating some elements.
I have created a class named myAnimation, the constructor takes in 1 argument which is an element. All its doing is fading a heading out and in, all very simple. It works fine when there is just one heading element on the page, I'm just not to sure how I go about getting it to work with more than one heading.
Please excuse my naivety with this; it's all very new to me, this is just a basic example I have managed to make myself to try and help myself understand how it works.
class myAnimation {
constructor(element) {
this.element = document.querySelector(element);
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
button.textContent = 'Show Heading';
}
}
}
const heading = new myAnimation('.heading');
const button = document.querySelector('.button');
button.addEventListener('click', () => {
heading.fadeOut(1);
});
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
After my comment I wanted to make the script run in a way I thought it might have been intended by the OP.
Even though it demonstrates what needs to be done in order to run properly, the entire base design proofs to be not fitting to what the OP really might need to achieve.
The class is called Animation but from the beginning it was intermingling element-animation and changing state of a single somehow globally scoped button.
Even though running now, the design does not proof to be a real fit because one now passes the element that is going to be animated and the button it shall interact with altogether into the constructor.
The functionality is grouped correctly, just the place and the naming doesn't really fit.
The OP might think about a next iteration step of the provided code ...
class Animation {
constructor(elementNode, buttonNode) {
this.element = elementNode;
this.button = buttonNode;
// only in case both elements were passed ...
if (elementNode && buttonNode) {
// couple them by event listening/handling.
buttonNode.addEventListener('click', () => {
// - accessing the `Animation` instance's `this` context
// gets assured by making use of an arrow function.
this.fadeOut(1);
});
}
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
this.button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
this.button.textContent = 'Show Heading';
}
}
}
function initializeAnimations() {
// get list of all elements that have a `heading` class name.
const headingList = document.querySelectorAll('.heading');
// for each heading element do ...
headingList.forEach(function (headingNode) {
// ... access its parent element and query again for a single button.
const buttonNode = headingNode.parentElement.querySelector('.button');
// if the related button element exists ...
if (buttonNode) {
// ... create a new `Animation` instance.
new Animation(headingNode, buttonNode);
}
});
}
initializeAnimations();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... new day, next possible iteration step ...
The 2nd iteration separates concerns.
It does so by renaming the class and implementing only class specific behavior. Thus a FadeToggle class provides just toggle specific functionality.
The code then gets split into two functions that handle initialization. For better reuse the initializing code and the html structure need to be refactored into something more generic. The data attribute of each container that features a trigger-element for fading a target element will be used as a configuration storage that provides all necessary information for the initializing process. (One even can provide individual transition duration values.)
Last there is a handler function that is implemented in a way that it can be reused by bind in order to generate a closure which provides all the necessary data for each trigger-target couple.
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
const { trigger, target } = this;
if (target.isFadeoutActive()) {
trigger.textContent = 'Hide Heading';
} else {
trigger.textContent = 'Show Heading';
}
target.toggleFade();
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
const config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and binding both as context properties to the handler.
trigger,
target
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... afternoon, improve the handling of state changes ...
There is still hard wired data, written directly into the code. In order to get rid of string-values that will be (re)rendered every time a toggle-change takes place one might give the data-based configuration-approach another chance.
This time each triggering element might feature a configuration that provides state depended values. Thus the initialization process needs to take care of retrieving this data and also of rendering it according to the initial state of a fade-toggle target.
This goal directly brings up the necessity of a render function for a trigger element because one needs to change a trigger's state not only initially but also with every fade-toggle.
And this again will change the handler function in a way that in addition it features bound state values too in order to delegate such data to the render process ...
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function renderTargetStateDependedTriggerText(target, trigger, fadeinText, fadeoutText) {
if ((fadeinText !== null) && (fadeoutText !== null)) {
if (target.isFadeoutActive()) {
trigger.textContent = fadeinText;
} else {
trigger.textContent = fadeoutText;
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
// retrieve context data.
const { target, trigger, fadeinText, fadeoutText } = this;
target.toggleFade();
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
let config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// parse a trigger node's fade-toggle configuration and state.
const triggerStates = ((
JSON.parse(trigger.dataset.fadeToggleTriggerConfig || null)
|| {}
).states || {});
// get a trigger node's state change values.
const fadeinStateValues = (triggerStates.fadein || {});
const fadeoutStateValues = (triggerStates.fadeout || {});
// get a trigger node's state change text contents.
const fadeinText = fadeinStateValues.textContent || null;
const fadeoutText = fadeoutStateValues.textContent || null;
// rerender trigger node's initial text value.
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and by binding both and some text values
// that are sensitive to state changes
// as context properties to the handler.
target,
trigger,
fadeinText,
fadeoutText
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button" data-fade-toggle-trigger-config='{"states":{"fadeout":{"textContent":"Hide Heading"},"fadein":{"textContent":"Show Heading"}}}'>Toggle Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Toggle Heading</button>
</div>
This is happening because document.querySelector(".button") only returns the first element with class .button (reference).
You might want to try document.querySelectorAll(".button") (reference) to add your event listeners.
(Though this will only toggle your first heading - for the very same reason. ;))

autofocus in html input tag only works once. Is there a way I can focus the input box automatically in another way?

I'm working on an in-browser editor that requires quick editing of single words. I'm trying to implement it by replacing specific words with input boxes containing those words and then allowing the user to edit that word on the spot.
Because I want the process to be quick, I want to make sure the input field is focused as soon as it is toggled, and my current solution is using the "autofocus" keyword in the input tag.
This works fine for the first edit after the page is loaded, but not for consequent ones.
Demo video:
https://i.gyazo.com/0d1099b2f60b47ff504e3f5bab54fa8f.mp4
I've also tried implementing this using DOM manipulation, but for some reason, it isn't able to find the element with the tag, and I feel like it's because it's being dynamically generated.
Relevant source code:
The HTML template
template: `
<div class="content" *ngFor="let sentence of sentences; let s_i = index;">
<span class="content" *ngFor="let word of sentence; let i = index; trackBy:trackByFn">
<span *ngIf="(currentIndex === i) && (currentSentence === s_i) && !(editing)"
class="selectedWord"
(click)="onClick(s_i, i)">{{word}} </span>
<input *ngIf="(currentIndex === i) && (currentSentence === s_i) && (editing)"
id="editingWord"
(click)="onClick(s_i, i)"
type="text"
[(ngModel)]="sentences[s_i][i]"
autofocus>
<span *ngIf="(currentIndex !== i) || (currentSentence !== s_i)"
(click)="onClick(s_i, i)">{{word}} </span>
</span>
<br>
</div>
<span class="content" *ngFor="let word of array; let i = index;">
</span>
`
My attempt using DOM manipulation to fix this
These methods are called when the edit key is pressed:
private editWord = function() {
this.editing = true;
}
private focusWord = function() {
console.log("Focusing word");
var item = document.getElementsByClassName(".selectedWord");
console.log(item);
setTimeout(() => document.getElementById("#editingWord").focus(), 2000);
}
Additionally, is there a way to have all the characters highlighted when the input box is initially focused? This way the user can immediately start typing to replace the whole box.
Use document.activeElement.blur() before doing focus again.
the more angular approach here is to use ViewChild:
#ViewChild('myInput', {static: false})
myInput;
private focusWord = function() {
this.myInput.nativeElement.select(); // select focuses the input and selects text
}
this may present some timing issues using ngIf requiring timeouts and whatnot, so you could consider using a class instead to hide it:
<input [class.hidden]="(currentIndex === i) && (currentSentence === s_i) && (editing)"
id="editingWord"
(click)="onClick(s_i, i)"
type="text"
[(ngModel)]="sentences[s_i][i]">
with some css like .hidden { display: none; }
the most full featured / reusable solution would be to implement some kind of autoselect directive:
import { AfterContentInit, Directive, ElementRef, Input } from '#angular/core';
#Directive({
selector: '[autoSelect]'
})
export class AutoSelectDirective implements AfterContentInit {
public constructor(private el: ElementRef) { } // inject the element ref
public ngAfterContentInit() { // run at the latest hook
setTimeout(() => {
this.el.nativeElement.select(); // run select after a timeout
}, 500);
}
}
you'd declare / import it as needed, and then usage is simple:
<input [class.hidden]="(currentIndex === i) && (currentSentence === s_i) && (editing)"
id="editingWord"
(click)="onClick(s_i, i)"
type="text"
[(ngModel)]="sentences[s_i][i]"
autoSelect>

How to color an input box depending on the size of varible in ReactJs?

I have an input box which is disabled. It gets a variable x which is sometimes < 1 or > 1. I want to color the background with red if x > 1, green if x < 1 and greyas a default (without a value).
I tried this:
<Field name="testValue" type="number" component={Input} label="Value:"
onChange={this.changeColor()} id="test"/>
the method changeColor():
changeColor() {
let num = document.getElementById("test");
if(myValue === "") {
num.css("backgroundColor", "grey");
} else if(myValue < 1) {
num.css("backgroundColor", "green");
} else {
num.css("backgroundColor", "red");
}
}
and in the constructor: this.changeColor = this.changeColor.bind(this);
Unfortunately I am getting: Cannot read property 'css' of null
What I am doing wrong? Is it the correct way? Any help or suggestions is very appreciate.
onChange expect a function but you are assigning the value returned by the function, instead of that write it like this remove ():
<Field
name="testValue"
type="number"
component={Input}
label="Value:"
id="test"
onChange={this.changeColor}
/>
But i will suggest you to use these conditions on style tag instead of using document.getElementById('text') then applying css on that, we should avoid the direct dom manipulation with react.
Store the value of input field in state variable myValue then write it Like this:
changeBGColor() {
let myValue = this.state.myValue;
if(myValue === "") {
return "grey";
} else if(myValue < 1) {
return "green";
} else {
return "red";
}
}
<Field
name="testValue"
type="number"
style={{backgroundColor: this.changeBGColor()}}
component={Input}
label="Value:"
id="test"
onChange={this.changeColor}
/>
You mentioned that you used React to render your component. So you should keep in mind, if you are building a React app, that you should not use the document anywhere in your code.
Anyway, to answer your question, if you need to render differently according to some value, in react, that value should live in the state of your component.
In the changeColor callback, you should read the value of the input and write it to the state of the component:
changeColor(event) {
// event.target is the dom element that emitted the event
this.setState({ inputValue: event.target.value });
}
And then, in your render function, you just give the color to the input depending on the value of the state:
render() {
var fieldColor = "red";
if (this.state.inputValue === "") {
fieldColor = "grey";
} else if (this.state.inputValue < 1) {
fieldColor = "green";
}
<Field style={ color: fieldColor } onChange={this.changeColor}/>
}
This way your render depends on the state of the component and the color updates as the user types in.
Now I'd suggest changing the name of the method changeColor to something more relevant to what the new function is doing, something like updateTestValue maybe?

Update value of input type time (rerender) and focus on element again with React

In the spec for my app it says (developerified translation): When tabbing to a time element, it should update with the current time before you can change it.
So I have:
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} />
Also relevant
changes(evt) {
let ch = {};
ch[evt.target.name] = evt.target.value;
this.model.set(ch);
},
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
}
},
The component will update when the model changes. This works well, except that the input loses focus when tabbing to it (because it gets rerendered).
Please note, if I would use an input type="text" this works out of the box. However I MUST use type="time".
So far I have tried a number of tricks trying to focus back on the element after the re-render but nothing seems to work.
I'm on react 0.14.6
Please help.
For this to work, you would need to:
Add a focusedElement parameter to the components state
In getInitialState(): set this parameter to null
In handleTimeFocus(): set focusElement to 'timeElem` or similar
Add a componentDidUpdate() lifecycle method, where you check if state has focusedElement set, and if so, focus the element - by applying a standard javascript focus() command.
That way, whenever your component updates (this is not needed in initial render), react checks if the element needs focus (by checking state), and if so, gives the element focus.
A solution for savages, but I would rather not
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
this.forceUpdate(function(){
event.target.select();
});
}
},
try using autoFocus attrribute.
follow the first 3 steps mention by wintvelt.
then in render function check if the element was focused, based on that set the autoFocus attribute to true or false.
example:
render(){
var isTimeFocused = this.state.focusedElement === 'timeElem' ? true : false;
return(
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} autoFocus={isTimeFocused} />
);
}

Categories