$refs are null after route change - vuejs2

I have a keyboard navigation system. When you press ArrowUp or ArrowDown, an event is emitted FROM app.js (best place I found to listen to these keypresses since they need to be system-wide) TO the mounted() in the component.
The Event.$on() INSIDE the mounted() part of the component then calls a function that uses $refs to identify the currently selected item and, when ENTER is pressed, show it's modal.
app.js code (listen to the keypresses):
else if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'Enter') {
event.preventDefault()
switch (this.$router.currentRoute.path) {
case "/pedidos":
Event.$emit('navegarSetasPedidos', event.key)
break;
case "/clientes":
Event.$emit('navegarSetasClientes', event.key)
break;
}
}
mounted() section of the component in question:
mounted() {
Event.$on('navegarSetasPedidos', (key) => {this.navegarSetas(key)})
}
function responsible for the navigation (sorry for bad formating, haven't figured how stackoverflow's codeblock thing works yet):
navegarSetas(key) {
if (this.navegacaoSetasAtiva == false) {
this.navegacaoSetasAtiva = true
this.navegacaoAtual = 0
} else if (this.modalAtivado == false && this.navegacaoSetasAtiva == true) {
if (key == 'ArrowDown' && this.navegacaoAtual < this.pedidos.length - 1) {
this.navegacaoAtual++
let elementoSelecionado = this.$refs['pedido'+this.navegacaoAtual][0].$el
let boundaries = elementoSelecionado.getBoundingClientRect()
if (boundaries.top < 0 || boundaries.top > (window.innerHeight || document.documentElement.clientHeight)){
elementoSelecionado.scrollIntoView({behavior: 'smooth'})
}
} else if (key == 'ArrowUp' && this.navegacaoAtual <= this.pedidos.length && this.navegacaoAtual > 0) {
this.navegacaoAtual--
let elementoSelecionado = this.$refs['pedido'+this.navegacaoAtual][0].$el
let boundaries = elementoSelecionado.getBoundingClientRect()
if (boundaries.top < 0 || boundaries.top > (window.innerHeight || document.documentElement.clientHeight)){
elementoSelecionado.scrollIntoView({behavior: 'smooth'})
}
} else if (key == 'Enter') {
let pedidoSelecionado = this.pedidos[this.navegacaoAtual].id
Event.$emit('changeShow', pedidoSelecionado)
}
}
This works very well the first time it is acessed. The problem is, if I change the current route to show another component and then return to the previous component, I get a lot of "this.$refs['pedido'+this.navegacaoAtual][0].$el is undefined" errors, but the system still works normally, albeit erratically.
The funny thing is: if I console log "this.$refs['pedido'+this.navegacaoAtual][0].$el is undefined", I'll get an EMPTY log before the errors, then ANOTHER one right below it, this time, not empty.
Everywhere else I've searched this says that the problem is due to how Vue re-renders things, and that I'm calling this event BEFORE it's rendered, which shouldn't be possible since I'm calling it inside mounted().
Any help is greatly appreciated, thank you!

Turns out, after a LOT of searching, the Event.$on event setters also work as the normal JavaScript ones (which makes a lot of sense now that I'm thinking about it)—meaning that you have to destroy them whenever your component is unmounted (aka Destroyed).
Even though VUE Dev Tools was picking only one event after the re-route, it was still firing two (seen through console.log() returning one empty value, a bunch of errors, and another value with filled array AFTER the errors).
The solution to this was simply adding Event.$off('eventName') on the destroyed() function of the component.

Related

Does Cypress .trigger() and .type() actually trigger an actual keydown event?

We have a small Vue 2 app managing a form and its submission.
In the Vue app, there is a method on the the input field for a credit card expiration date.
<input
v-model="expDate"
type="text"
name="expirDate"
placeholder="MMYY"
#blur="checkExpDate"
#keydown="appendSlash($event)"
/>
We have an appendSlash() method that checks each keydown and adds a slash after the first two digits are typed in:
appendSlash(event) {
if (
event.key === "Backspace" ||
event.key === "Tab" ||
(event.keyCode >= 48 && event.keyCode <= 57)
) {
if (
this.expDate.length === 2 &&
event.key !== "/" &&
event.key !== "Backspace"
) {
this.expDate += "/";
return false;
}
return true;
} else {
event.preventDefault();
return false;
}
This works fine if we manually do this in the browser. However, our QA person has tried to automate this via the following Cypress methods, but the slash never gets added.
cy.get('input').type('0925');
cy.get('input').type('09');
cy.wait(5000);
cy.get('input').type('25');
cy.get('input').trigger('keydown', { keyCode: 48 })
cy.wait(5000);
cy.get('input').trigger('keydown', { keyCode: 57 })
cy.wait(5000);
Does .trigger() and .type() provide a real keyboard event, or is our QA just using the Cypress API incorrectly?
(I understand there is a vue-cypress module available, but we can't use it yet in our infrastructure.)
Yes, it does according to the documentation here.
Also, this is the info you get when you click on the type action in the cypress UI and the action output gets printed to the console:
I heard back from the QA person.
They ended up solving it using .clear() and .invoke().
cy.xpath('//input[#name="expirDate"]').clear().invoke('val', '05/25').trigger('input');
They said they had to clear the field first, then put in the number, and finally trigger an input event. It worked.

Error: Maximum update depth exceeded. React Native

I try to setState in compenetDidUpdate but it shows error infinite loop. Got any solution? Originally I put the setState in a function but also face this error. I am using class component code
componentDidUpdate(){
if(isEmpty(this.props.AESDetail) == false){
if(this.props.AESDetail.length != 0){
if(this.props.APIESDetail.length != 0){
if(this.props.APIESDetail.Focus != null){
this.setState({
gotFocusApies: true
})
}
}
}
}
}
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
It looks like you might just need one additional test to make sure gotFocusApies isn't already true.
These if statements are also probably better off combined into one.
Note: list.length != 0 can typically be replaced with list.length or !!list.length.
componentDidUpdate() {
if (
isEmpty(this.props.AESDetail) == false &&
this.props.AESDetail.length &&
this.props.APIESDetail.length &&
this.props.APIESDetail.Focus != null &&
!this.state.gotFocusApies
) {
this.setState({ gotFocusApies: true });
}
}

How to prevent closing of cell edit mode on validation errors with custom vue components in ag-grid

I have succesfully rendered my own component as the cellEditor and would like and on-leave I would like it to try to validate the value and prevent the closing if it fails.
If I look at this then https://www.ag-grid.com/javascript-grid-cell-editing/#editing-api there's cancelable callback functions for editing. But in this callback function is there a way to access the current instantiated component? I would think that would be the easiest way to handle this.
I'm using vee-validate so the validation function is async, just to keep in mind.
Use Full row editing.
Create a global variable like
var problemRow = -1;
Then Subscribe to this events:
onRowEditingStarted: function (event) {
if (problemRow!=-1 && event.rowIndex!=problemRow) {
gridOptions.api.stopEditing();
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
},
onRowEditingStopped: function (event) {
if (problemRow==-1) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
}
if (problemRow == event.rowIndex) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
else{
problemRow=-1;
}
}
},
I had a similar issue - albeit in AngularJS and the non-Angular mode for ag-grid - I needed to prevent the navigation when the cell editor didn't pass validation.
The documentation is not very detailed, so in the end I added a custom cell editor with a form wrapped around the input field (to handle the niceties such as red highlighting etc), and then used Angular JS validation. That got me so far, but the crucial part was trying to prevent the user tabbing out or away when the value was invalid so the user could at least fix the issue.
I did this by adding a value parser when adding the cell, and then within that if the value was invalid according to various rules, throw an exception. Not ideal, I know - but it does prevent ag-grid from trying to move away from the cell.
I tried loads of approaches to solving this - using the tabToNextCell events, suppressKeyboardEvent, navigateToNextCell, onCellEditingStopped - to name a few - this was the only thing that got it working correctly.
Here's my value parser, for what it's worth:
var codeParser = function (args) {
var cellEditor = _controller.currentCellEditor.children['codeValue'];
var paycodeId = +args.colDef.field;
var paycodeInfo = _controller.paycodes.filter(function (f) { return f.id === paycodeId; })[0];
// Check against any mask
if (paycodeInfo && paycodeInfo.mask) {
var reg = new RegExp("^" + paycodeInfo.mask + '$');
var match = args.newValue.match(reg);
if (!match) {
$mdToast.show($mdToast.simple().textContent('Invalid value - does not match paycode format.').position('top right').toastClass('errorToast'))
.then(function(r) {
_controller.currentCellEditor.children['codeValue'].focus();
});
throw 'Invalid value - does not match paycode format.';
}
}
return true;
};
The _controller.currentCellEditor value is set during the init of the cell editor component. I do this so I can then refocus the control after the error has been shown in the toast:
CodeValueEditor.prototype.init = function (params) {
var form = document.createElement('form');
form.setAttribute('id', 'mainForm');
form.setAttribute('name', 'mainForm');
var input = document.createElement('input');
input.classList.add('ag-cell-edit-input');
input.classList.add('paycode-editor');
input.setAttribute('name', 'codeValue');
input.setAttribute('id', 'codeValue');
input.tabIndex = "0";
input.value = params.value;
if (params.mask) {
input.setAttribute('data-mask', params.mask);
input.setAttribute('ng-pattern','/^' + params.mask + '$/');
input.setAttribute('ng-class',"{'pattern-error': mainForm.codeValue.$error.pattern}");
input.setAttribute('ng-model', 'ctl.currentValue');
}
form.appendChild(input);
this.container = form;
$compile(this.container)($scope);
_controller.currentValue = null;
// This is crucial - we can then reference the container in
// the parser later on to refocus the control
_controller.currentCellEditor = this.container;
$scope.$digest();
};
And then cleared in the grid options onCellEditingStopped event:
onCellEditingStopped: function (event) {
$scope.$apply(function() {
_controller.currentCellEditor = null;
});
},
I realise it's not specifically for your components (Vue.js) but hopefully it'll help someone else. If anyone has done it a better way, I'm all ears as I don't like throwing the unnecessary exception!

Polymer2 Shadow dom select child element

I am working on a polymer2 shadow dom template project need to select children elements from parent elements. I found this article introduces a way to select child shadow dom elements that like this:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
However, when I tried in my polymer2 project, like this:
//First: works!!
document.querySelector('container')
.shadowRoot.querySelector('app-grid')
.shadowRoot.querySelector('#apps');
//Second: Doesn't work!// got null
document.querySelector('container::shadow app-grid::shadow #apps')
// Thrird: document.querySelector('* /deep/ #apps') // Doesn't work, got null
I really need the second way or the third, which to put selectors in (), but both couldn't work. Does anyone know why the second one doesn't work? Thank you so much!
::shadow and /deep/ has never(?) worked in Firefox, and is depraved in Chrome 63 and later.
Source
Eric Biedelman has written a nice querySelector method for finding all custom elements on a page using shadow DOM. I wouldn't use it myself, but I have implemented it so I can "querySelect" custom elements in the console. Here is his modified code:
// EXAMPLES
// findCustomElement('app-grid') // Returns app-grid element
// findCustomElements('dom-if') // Returns an array of dom-if elements (if there are several ones)
// findCustomElement('app-grid').props // Returns properties of the app-grid element
function findCustomElement(customElementName) {
const allCustomElements = [];
customElementName = (customElementName) ? customElementName.toLowerCase() : customElementName;
function isCustomElement(el) {
const isAttr = el.getAttribute('is');
// Check for <super-button> and <button is="super-button">.
return el.localName.includes('-') || isAttr && isAttr.includes('-');
}
function findAllCustomElements(nodes) {
for (let i = 0, el; el = nodes[i]; ++i) {
if (isCustomElement(el)) {
el.props = el.__data__ || el.__data || "Doesn't have any properties";
if (customElementName && customElementName === el.tagName.toLowerCase()) {
allCustomElements.push(el);
} else if (!customElementName) {
allCustomElements.push(el);
}
}
// If the element has shadow DOM, dig deeper.
if (el.shadowRoot) {
findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
}
}
}
findAllCustomElements(document.querySelectorAll('*'));
if (allCustomElements.length < 2) {
return allCustomElements[0] || customElementName + " not found";
} else if (customElementName) {
allCustomElements.props = "Several elements found of type " + customElementName;
}
return allCustomElements;
}
Remove the if (isCustomElement(el)) { statement, and you can querySelect whatever element and get an array of it if several of them exists. You can change findAllCustomElements to implement a smarter querySelect using the recursive loop on shadowDoom as base. Again, I wouldn't use this myself – and instead pass on variables from parent element(s) to children where the children have observers that activates specific behaviors – but I wanted to give you a general implementation of a fallback if nothing else works.
The problem with your question is that you don't give any specifics about WHY you want to select the children in the first place.

Vue.js - Element UI - How to know the state of form validation

I'm using, vue-js and element-ui
I would like to check the state of the validation of a form without having to click on a submit button
Example
https://jsfiddle.net/nw8mw1s2/
Steps to reproduce
Click on each field
The verification is triggered with the blur
Start filling the different inputs
Question
How can I do so when the last input is validated, isFormValidated turns to true.
In other words, how can I say "If there is no field with the state error, then turn valudateState to true"
Tips
I guess we can check the validateState of each formItem of the form. But I do not see how to do it concretely.
I would create a new method (say updateIsFormValidated), and bind it to the native focusout event of the form:
<el-form :model="ruleForm2" #focusout.native="updateIsFormValidated" ...>
This method will fire each time any of the inputs in the form loses focus. It will attempt to check that each field in the form component has successfully been validated, firing each 100 milliseconds if the validation status of any of the form items is still pending:
updateIsFormValidated() {
let fields = this.$refs.ruleForm2.fields;
if (fields.find((f) => f.validateState === 'validating')) {
setTimeout(() => { this.updateIsFormValidated() }, 100);
} else {
this.isFormValidated = fields.reduce((acc, f) => {
let valid = (f.isRequired && f.validateState === 'success');
let notErroring = (!f.isRequired && f.validateState !== 'error');
return acc && (valid || notErroring);
}, true);
}
}
Here's a working fiddle.