I'm trying to create a custom text editor in React, thus I've created a div which has a contentEditable attribute. Now I want to perform some action when the user selects a part of the inputted text. To do that I'm using the select event as onSelect attribute on my div. The problem is that, select event runs not only when selecting the text, but also when I click on the input box, or after any input. How can I prevent it, so that it gets fired only when the text is selected ?
Component:
function EditorBody(props) {
return (
<div className="editor-body">
<div
className="text-section"
contentEditable
role="textbox"
placeholder="Text goes here ..."
onSelect={() => window.alert("You've selected a text")} // Runs after every input, not only when the text is selected
></div>
</div>
);
}
export default EditorBody;
You can change the logic in your onSelect to be able to determine whether or not to execute the selected logic.
onSelect={(event) => {
if (document.getSelection().toString().length > 0) {
// your selection logic
window.alert(document.getSelection().toString());
}
}}
This way the logic will be executed only if the user is selecting something and not on other primary events that might set off the secondary select event (focus, keypress, etc).
Related
I would like to be able to focus on input field when certain keys are entered. The input I would like to focus on exists inside autocomplete-vue.
This is where I call it:
<autocomplete v-shortkey="['alt', 's']"
#shortkey.native="theAction()"
ref="autocompleteInput"
></autocomplete>
theAction method which I would like to allow me to focus on the input, looks like this:
theAction () {
this.$refs.autocompleteInput.$el.focus()
}
this focus on the whole section which is not what I want. the input exists 2 divs inside the what theAction focuses on. For bettere perspective, this is what this.$refs.autocompleteInput.$el returns :
<div>
<div data-position="below" class="autocomplete">
<input role="combobox" class="autocomplete-input">
</div>
</div>
Any ideas on how I can focus on the input with class autocomplete-input? any suggestion is helpful!
Add a ref in the autocomplete component for the <input> and add a method to focus it
<input role="combobox" class="autocomplete-input" ref="input">
methods: {
focus() {
this.$refs.input.focus()
}
}
You can then call it from the parent component like this
this.$refs.autocompleteInput.focus()
I have an Angular 7 application in which I'm trying to handle a text input in ngAfterViewChecked().
The text input is a node in a mat-tree. It's visibility depends on an ngIf condition. If that condition is not met, I display a span instead. Essentially, if the user double clicks on a node in the tree (a span element), it becomes a text input so that the user can edit the text:
<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
<mat-tree-node *matTreeNodeDef="let node">
<li>
<span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
<input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
</li>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<button mat-icon-button matTreeNodeToggle>
<mat-icon>
{{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
<span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
<input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
<ul [class.collapsed]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</mat-nested-tree-node>
</mat-tree>
When the user double clicks on a node, I not only want it to turn into an input text, I want it to gain focus and select the text inside. In order to do this, I have to get the native element and call .focus() and .select() on it. In order to get the native element, I have to use ViewChildren (where the input is tagged with #nodeNameInput as you can see in the code snippet above). And finally, I need to hook into ngAfterViewChecked() in order to be sure that the QueryList of the ViewChildren is ready.
Here is the code for the component:
#ViewChildren('nodeNameInput') nodeNameInputs: QueryList<ElementRef>;
...
ngAfterViewChecked() {
if (this.nodeNameInputs && this.nodeNameInputs.length) {
this.nodeNameInputs.first.nativeElement.focus();
this.nodeNameInputs.first.nativeElement.select();
}
}
I've ensured that there is only ever one node being edited at a time, so it's safe to use first rather than search through nodeNameInputs to find the one to put in focus and select the text.
This seems to work, but there is a problem. It seems like for every key stroke, ngAfterViewChecked() is also called. What this means is that as the user is editing the text for the node, it gets re-selected for every key stroke. This results in the text the user enters being overwritten on every key stroke.
I have a workaround to this problem:
ngAfterViewChecked() {
if (this.nodeNameInputs && this.nodeNameInputs.length) {
this.nodeNameInputs.first.nativeElement.focus();
if (!this.keyStroked) {
this.nodeNameInputs.first.nativeElement.select();
}
}
}
...where keyStroked is set in the keyPressed handler and set to false in the blur handler.
But I'm wondering if there is another hook that can reliably be used to focus the input and select its text while not responding to key strokes. I chose ngAfterViewChecked because a test showed that it was the only hook in which nodeNameInputs was consistently ready every time (i.e. this.nodeNameInputs.length was always 1). But maybe I missed certain hooks.
My workaround seems like a hack. How would you solve this problem?
Create a focus directive and place that on the input you want focused, you wont have to worry about life cycle events.
import { Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[focus]'
})
export class FocusDirective {
constructor(elm: ElementRef) {
elm.nativeElement.focus();
}
}
and use it
<input focus>
https://stackblitz.com/edit/angular-qnjw1s?file=src%2Fapp%2Fapp.component.html
Suppose I have an input field,
<input id="city" placeholder="city">
and I want to detect whenever user leaves this field. How can I do so?
Normal javascript
var element = document.getElementById("ELEMENT_ID");
element.addEventListener("blur", function() { ... your code here ...});
jQuery
$("#ELEMENT_ID").on("blur", function() { ... your code here ...});
If, by any chance, you're implementing those self-emptying fields with predefined text, use placeholder attribute. If you're changing style based on focus, use :focus CSS selector. Also, change event is emitted if user leaves the field and changed it's contents.
When an element loses focus, the onblur event is fired.
var elem = document.getElementById('city');
elem.addEventListener("blur", function( event ) {
console.log('Elvis has left the city (input)!');
}, true);
https://developer.mozilla.org/en-US/docs/Web/Events/blur
In my view model I have a field. This field can be selected from a drop down list or entered in a textbox. I have two radio buttons which allows to select between drop and textbox.
<div class="frm_row" id="contractorRow">
#Html.RadioButton("IsContractorNew", "false", true)
#Html.Label(#Resources.SomeLabels.Existing)
#Html.RadioButton("IsContractorNew", "true", false)
#Html.Label(#Resources.SomeLabels.New)
<div class="frm_row_input" id="contractorDropDownList">
#Html.DropDownListFor(model => model.CONTRACTOR, Model.Contractors)
#Html.ValidationMessageFor(model => model.CONTRACTOR)
</div>
<div class="frm_row_input" id="contractorTextBox" style="display: none;">
#Html.TextBoxFor(model => model.CONTRACTOR)
#Html.ValidationMessageFor(model => model.CONTRACTOR)
</div>
</div>
I prepared a javascript code for hiding, showing and clearing controls while selecting radio buttons. The problem is - field is bound only to the first control (drop down list).
EDIT:
I solved this problem by creating one hidden field and scripting whole logic to bind active control with it and therefore with the model. Anyway if anyone knows simpler solution, please post it.
I know this is an old question, but for those dealing with this issue, here's a solution:
Explanation
When a form is submitted, input elements are bound to the model by their name attribute. Let's say you use HTML helpers to generate your form, and you generate two input fields which bind to the same property on the model. When rendered to the DOM, they both have the same name attribute.
<input name="Passport.BirthPlace" id="birthPlaceDropDown" ... >
<input name="Passport.BirthPlace" id="birthPlaceInfoTextbox" ... >
When the form is submitted, it will bind the first (in the DOM) input it finds to Passport.BirthPlace
A Solution
The quick and easy way to fix this is to use JQuery to change the name of the field you don't want bound on submit. For me, I use a checkbox to toggle which field shows. When the checkbox changes, I hide one control, change its name attribute, and show the other one (and change it's name attribute to Passport.BirthPlace) It looks like this, basically:
First, I run this on document ready
$('#birthPlaceInfoTextbox').attr('name', 'nosubmit'); // Change name to avoid binding on inactive element
Then, I create a listener for my checkbox which toggles which control should be bound:
$('#notBornUSCheckbox').change(function() {
if (this.checked) {
// Not born us was checked, hide state dropdown and show freeform text box
$('#stateDropDownSection').addClass('d-none'); // Hide drop down
$('#birthPlaceDropDown').attr('name', 'nosubmit'); // Change name to something other than Passport.BirthPlace
$('#birthPlaceInfoTextbox').attr('name', 'Passport.BirthPlace'); // Set this one to be bound to the model
$('#stateTextboxSection').removeClass('d-none'); // Show the textbox field
} else { // Opposite of above lines
$('#stateDropDownSection').removeClass('d-none');
$('#stateTextboxSection').addClass('d-none');
$('#birthPlaceInfoTextbox').attr('name', 'nosubmit');
$('#birthPlaceDropDown').attr('name', 'Passport.BirthPlace');
}
});
Instead of using Razor #Html.TextBoxFor... for your textbox, you could try using raw HTML e.g. <input />. Also, have your JavaScript code remove the other field from the DOM entirely when a radio button is clicked, before submitting the form.
In HTML & JS, how do you make a textfield that has grayed out text telling the user what the field is for that goes away when the user clicks on the field?
For example, in firefox the search field in the top right hand side says which search engine it uses when there's nothing entered, then once you click it's an empty textfield, but if you leave it blank and remove focus from the textfield then the grayed out text is back again.
Is there a name for this behavior? Also, is it possible to do in pure css without the use of js to do the on focus / on blur events?
The effect that you are referring to is often called the placeholder effect. Within HTML5 this effect is possible within certain browsers by simply placing the new attribute 'placeholder' within your input tag. Such as...
<input type='text' placeholder='Place Holder Text'/>
<input type='text'/> <!-- Example with no title-->
<input type='text' title='Your title'/>
This can also be done in JavaScript using CSS by setting a style for an active class and toggling the active style along with the item's title tag. Such as ...
$(document).ready(function(){
// Select all input fields. (You will probably want to filter this down even further).
var inputs = $('input[type=text]');
// Set all the inputs to the title value.
inputs.each(function(){
$(this).val($(this).attr('title')).addClass('unfocused'); // Styling Class for inputs.
});
// When the user focuses on an input
inputs.focus(function(){
var input = $(this);
if(input.val() == input.attr('title')){
$(this).removeClass('unfocused').val('');
}
});
// When the user loses focus on an input
inputs.blur(function(){
var input = $(this);
if(input.val() == ''){ // User has not placed text
input.val(input.attr('title')).addClass('unfocused');
}
});
});
The tested function can be seen here: http://www.jsfiddle.net/F8ZCW/5/
This behavior is on my URL shortener site: http://relk.in
The basic idea is when the onfocus event fires, you modify the CSS of the textfield to a normal class, and then onblur, you re-apply the previous class.
And no, you cannot do this in pure CSS.
Example:
var textfield = document.getElementById('someTextField');
textfield.onfocus = function() {
this.className = this.className.replace('oldClassName', 'newClassName');
};
textfield.onblur = function() {
this.className = this.className.replace('newClassName', 'oldClassName');
}