Vue.js: How do I focus to an input just unhidden - javascript

This must be something really basic, but I can't get it.
I have this piece of code in my <template>:
<div v-if="this.editable">
<input type="text" ref="nota" :id="notaid" v-model="nombre" v-on:keyup.enter="enviar" v-on:blur="enviar" class="form-control">
</div>
<div v-else>
<p #click="makeEditable">{{ nombre }}</p>
</div>
So, when the page loads, editable=false so it displays a text. When you click in the text there's a function that will make editable true, so it displays the input. This works fine. Now, my problem is, how do I focus on the input as soon as editable changes to true?
I have read some articles: here, here, and some others that says I could be focusing on the element with some code as simple as:
this.$refs.nota.focus()
So makeEditable code is:
methods: {
makeEditable() {
this.editable = true;
this.$refs.nota.focus()
},
...
Problem is, I get this error:
app.js:38487 [Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'focus' of undefined"
I'm pretty sure this is because I'm trying to focus to an element that hasn't been created yet, because if I make the same on an input element that is always displayed, it works perfectly.
So which is the right way?

You can try Vue.nextTick(callback) as follow :
methods: {
makeEditable() {
this.editable = true;
Vue.nextTick(() =>
this.$refs.nota.focus()
)
},
...
It tells Vue to wait until the changes are made and then call the callback.
See the documentation for more.

Related

Why isn't NVDA reading my custom error alert consistently?

I've set up a custom html page template for a client's azure b2c login page and the inline errors are getting read back as-expected (can provide additional details about those upon request if it'd be beneficial), but I'm a bit stumped as to why the page-level errors aren't getting read back as well.
The following are the relevant snippets of html from the template that get rendered during the initial page load:
<!-- B2C-Generated Form Element (all relevant html changes happen within this element) -->
<form id="localAccountForm" action="JavaScript:void(0);" class="localAccount" aria-label="Sign in to Your Account">
<!--B2C-Generated Error Container (prior to error / this html is generated/injected by Azure B2C when the page is loaded) -->
<div class="error pageLevel" aria-hidden="true" role="alert" style="display: none;">
<p class="remove"></p>
</div>
<!-- Custom Error Container (prior to error / this html gets added to the template by jQuery once the window is ready) -->
<div role="alert" style="" class="errorContainer no-error" aria-hidden="false">
<p id="pageError" role="" aria-hidden="true" style="" class="remove"></p>
</div>
After the initial content loads (both the content from Azure B2C, as well as the modifications from jQuery), the following logic gets run to ensure all of the error elements on the page are set up properly (or at least that's the intent) & eliminate some differences that may otherwise cause some problems:
initializeAlerts([
'#pageError',
'label[for="signInName"] + .error > p',
'.password-label + .error > p'
]);
// Below functions are loaded & run from a separate .js file:
function initializeAlerts(errorSelectors) {
errorSelectors.forEach((s) => {
// Store the current error & whether or not inline styles were attempting to hide it
var errorMsg = $(s).html();
var errorStyle = `${$(s).attr('style')} ${$(s).parent().attr('style')}`;
// Ensure the parent element has the role="alert" attribute (rather than the error itself)
$(s).attr('role', '');
$(s).parent().attr('role', 'alert');
// Default both the error element & it's parent to be visible
$(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
$(s).parent().addClass('errorContainer').addClass('no-error').removeClass('remove').attr('aria-hidden', 'false').attr('style','');
// If an error message is NOT present, add a class to the parent for styling purposes
if (errorMsg) {
$(s).parent().removeClass('no-error');
}
// If the error was supposed to be hidden dynamically via Azure B2C (i.e. it's a standard error that's prepopulated & simply gets shown/hidden), ensure the error itself is hidden (NOT the parent)
if (errorStyle.indexOf('none') > -1) {
$(s).addClass('remove').attr('aria-hidden', 'true');
$(s).parent().addClass('no-error');
}
// If/when the error gets updated, ensure it gets displayed/read
callbackOnDOMUpdate([s], function() {
var currentError = $(s).html();
if (currentError) {
$(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
$(s).parent().removeClass('no-error');
} else {
$(s).addClass('remove').attr('aria-hidden', 'true').attr('style','');
$(s).parent().addClass('no-error');
}
});
});
}
function callbackOnDOMUpdate(selectors, callback) {
selectors.forEach(selector => {
$(function() {
var target = document.querySelector(selector);
var observer = new MutationObserver(function(mutations, observer) {
callback();
});
observer.observe(target, {
subtree: true,
childList : true,
characterData : true
});
});
});
}
After all of that runs, if the user enters an incorrect user/pass combination, an error is returned to the page (via the "B2C-Generated Error Container") which looks something like this (depending on the specific error returned):
<!--B2C-Generated Error Container within form (after receiving error) -->
<div class="error pageLevel" aria-hidden="false" role="alert" style="display: block;">
<p class="remove">Unable to validate the information provided.</p>
</div>
Though, the client wants some verbiage/styling changes made, so rather than showing that message as-is, a "remove" class is added to it (which is associated with a display: none !important css rule) and my custom error container is updated to show something similar to the message below (again, actual message may vary depending on the message returned from b2c):
<!-- Custom Error Container (after receiving error) -->
<div role="alert" style="display: block">
<p id="pageError">
<strong>We're sorry, the information entered does not match what we have on file.</strong>
Please try re-entering the information below or you can recover your login info.
</p>
</div>
Unfortunately, while this DOES appear to get read as expected every once in a while, most of the time, the only message I hear read is "Sign in to Your Account form continue button" (which seems to be a combination of the aria label for the form element the changes are nested within, the form element name itself & the name of the last button the user clicks prior to seeing the page update).
I've tried to ensure that the error itself is nested as a child element within a parent element that:
Is always visible (from both a css & aria perspective)
Has role='alert'
(and simply show/hide the error itself via the addition/removal of the "remove" class)
... but I must be missing something somewhere, so any help anyone can offer would be appreciated.
So... as it turns out, all of the above code was fine as-is (with the exception of the fact that #1 above wasn't entirely true). The issue turned out to be related to the following...
When the user clicks the "sign in" button, I used $('body').removeClass('loaded'); which in turn would cause:
A loading animation to be displayed
The element containing all page content to be set to display: none;
When an error was detected, I would then similarly fire off the following commands (in this order):
$('body').addClass('loaded'); (thus making everything visible again)
the logic to read the system error that was returned & populate the custom error area of the code accordingly
... so, I tried removing all jquery related to the loading animation to see if perhaps the problem was related to that & sure enough, the error was read as expected.
That being said, I believe the real issue at play here was that the error/alert update was getting completed before all visibility-related changes had kicked through from the css side & therefore NVDA wasn't reading the alert because, from NVDA's perspective, the error was still hidden.
... hopefully the documentation of this experience helps someone else down the road!

Vue and Summernote - content from model not showing in form when loaded from db

Ok, I have a form in which I am using the wysiwyg editor summernote several times. When I fill in my form, the model is updated correctly, the content is shown and the results are saved correctly to the database. BUT when I want to edit and load the data from the database, the model is showing the contents correctly in the developer tools, but nothing makes it on to the screen.
This is what I have:
I have a component to load and initiate the summernote editor
<template>
<textarea class="form-control"></textarea>
</template>
<script>
export default{
props : {
model: {
required: true
},
height: {
type: String,
default: '150'
}
},
mounted() {
let config = {
height: this.height,
};
let vm = this;
config.callbacks = {
onInit: function () {
$(vm.$el).summernote("code", vm.model);
},
onChange: function () {
vm.$emit('update:model', $(vm.$el).summernote('code'));
},
};
$(this.$el).summernote(config);
}
}
</script>
I have a form (here is only one part of it) where I load the Summernote component as html-editor:
<html-editor
:model.sync="form.areaOfWork"
:class="{ 'is-invalid': form.errors.has('areaOfWork') }"
name="areaOfWork"
id="areaOfWork"></html-editor>
In the props, after loading from the DB, the data shows correctly, i.e.:
model:"<p> ... my content ...</p>"
Likewise, in my form it shows correctly, i.e.:
form: Object
areaOfWork: "<p> ... my content ...</p>"
...
But it is not shown in html-editor. I am stuck - maybe it is something super simple I am overlooking, but I did not find anything that helped me so far. Thanks for ideas and inputs
I think the problem stemmed from using jquery and vue in one project!
Because vue uses virtual DOM and jquery changes the real DOM, this makes the conflict!
So it is better not to use both of them in one project or use jquery safe in it like link below:
https://vuejsdevelopers.com/2017/05/20/vue-js-safely-jquery-plugin/
Thanks for your answer, #soroush, but that doesn't seem to be the problem. There is a communication and it seems to work. I do see my form being filled with correct data as well.
After playing around with numerous ideas, I found a simple work-around: watcher
I added
watch: {
areaOfWork: (val) => {
$('#areaOfWork').summernote("code", val);
}
},
to my template and the according variables to my data().
When I call my database and read the item, I provide the data for the watcher and it works.
Still, I do not get why vm.model in the onInit callback is not shown, but as it seems to work, I wanted to provide you with an answer. Maybe it helps someone else
HTML:
<textarea class="summernote-editor"name="memo_content"id="memo_content"</textarea>
JS:
$('#memo_content').summernote("code", response.data.res.memo_content);

Vue.JS - listen to click on component

I'm fairly new to Vue.JS and currently having an issue listening to a click event on a component.
JS:
Vue.component('photo-option', {
data: function () {
return {
count: 0
}
},
props: ['src'],
template: `
<img :src=src v-on:click="$emit('my-event')" />
`
});
HTML:
<photo-option :src=uri v-for='uri in aboutUsPhotos' v-on:my-event="foo" />
...where foo is a method on my main Vue instance.
The above is based on the Vue.JS docs for handling component events, and I can't see what I'm doing wrong. The alert doesn't fire, and there's no errors in the console.
Before I found those docs, I also tried simply adding v-on:click='...' to both the JS (i.e. the template) and the HTML, each with no success.
What am I doing wrong?
[EDIT]
This is happening because the code is picked up by a lightbox script and has its DOM position changed. So presumably the binding/event attachment is being lost.
Does Vue have any way of allowing for this, perhaps by 'reinitialising' itself on an element, or something?

How to trigger the open function for react-dropzone-component using refs?

I am using the react drop-zone component to upload files to the server.
I would like to call the drop-zone open function on button click.
This is what I have tried so far:
I am using refs to reference the drop zone. Also note I have multiple drop-zone's
<DropzoneComponent
style={{ height: 80 }}
ref={this.myRef}
config={config}
eventHandlers={eventHandlers}
djsConfig={djsConfig}
/>
And on a separate button click I am calling a function
openDropZone1 = () => {
this.refs.myRef.open();
this.setState({
bankStatement1: true,
bankStatement2: false,
bankStatement3: false
});
};
On click of the button I get the following error:-
TypeError: Cannot read property 'open' of undefined
on this line this.refs.myRef.open();
Any help or suggestion is most appreciated.
Thank you.
https://github.com/react-dropzone/react-dropzone/tree/master/examples/FileDialog
this is the final answer because it's in the docs if this doesn't solve your problem then it is something wrong with your code

TinyMCE UndoManager. Get event to fire

I have made a webform that uses a TinyMCE editor. When the user clicks on a link to leave the page, I want a message to be displayed if he still has unsaved changes. For this, I was thinking of using the TinyMCE undoManager, http://www.tinymce.com/wiki.php/API3:class.tinymce.UndoManager
Essentially, I want the events onAdd, onUndo, OnRedo to fire. I will then keep track of the number of changes (if any) since the last save. But how do I get them to fire? Where do I set them up?
Also, it would be nice to have access to the hasUndo method in the rest of my javascript code. Again, not sure where to initiate this?
Not sure if it matters, but I'm using Django.
Edit: I have tried
tinyMCE.init({
...,
setup : function(ed) {
ed.UndoManager.onAdd(function(ed) {
ed.windowManager.alert('added.');
});
}
});
This gives me an error: Unable to get value of the property 'onAdd': object is null or undefined
I created a tinym fiddle for this: http://fiddle.tinymce.com/H0caab .
The solution is to use the oninit setting and to addresss the onAdd.add:
tinyMCE.init({
...
setup : function(ed) {
ed.onInit.add(function(ed) {
ed.undoManager.onAdd.add(function(ed) {
alert('added.');
});
});
}
});

Categories

Resources