preface

Hi, I’m Allyn.

Some time ago, the product added a requirement to us during the acceptance. When users submitted the form, they should scroll to the place where the verification error occurred, like this:

I said, “This is going to be a lot of work, because it involves communicating with multiple subform components, calculating the location of elements on the page, and writing the animation, which is about 8 hours of work. Why don’t we do it?”

Her willow eyebrow wrinkled, considered for a long time, way: no! The user experience is critical, so do your best and just anticipate the development time.

I look embarrassed, very reluctantly said: ok, ah, that online time to the back of a day, I try to make this feature.

When the project manager knew to add this requirement, he said: Ok, you come on! Let’s just delay the launch by a day,

My heart said: small son, give me more demand, I finished in an hour, lie to you said eight hours you really believe, and one more day to learn (mo) xi (yu), yeah!

Demand analysis

This requirement is mainly used for super-long forms that are longer than one page.

To do this you need a Web API, element.scrollintoView

The Element interface’s scrollIntoView() method scrolls the Element’s parent container, making the Element called scrollIntoView() visible to the user.

Obviously, we can do this by finding the first element on the page that failed validation when the form submission failed and calling the scrollIntoView method on that element.

The scrollIntoView API does the job of calculating element positions and animations, but how do we find the first element on the page to fail validation?

In the case of element-UI’s el-Form component, when we open the console, we can see that the element is tagged with an IS-error class. Obviously, we can call the element’s getElementsByClassName method to find all the elements that failed. I’ll just take the first one.

Now the last question is, how to communicate with the parent of multiple child component forms?

In VUE development, form validation is typically written to the child component’s validate method, followed by defining a ref on the parent component, and then calling the child’s validate method via this.$refs (as element-UI does).

Code implementation

Using the GIF above as an example, assume that this page has five subform components.

The parent component:  <ComponentA ref="componentAEle" /> <ComponentB ref="componentBEle" /> <ComponentC ref="componentCEle" /> <ComponentD ref="componentDEle" /> <ComponentE ref="componentEEle" /> <button @click="validate"> async validate() { const validRefs = [ this.$refs.componentAEle, this.$refs.componentBEle, this.$refs.componentCEle, this.$refs.componentDEle, this.$refs.componentEEle ] for (const i in validRefs) { const res = await validRefs[i]?.validate() if (! res) { return false } } return true }Copy the code
<el form ref="componentAFormEle"> < EL form ref="componentAFormEle"> </el-form> validate() { return new Promise(resolve => { this.$refs.componentAFormEle.validate(async valid => { if (! valid) { await this.$nextTick() this.$utils.scrollToError(this.$refs.componentAFormEle.$el) } resolve(valid) }) }) }Copy the code
** @param {string} [scrollOption={* behavior: 'smooth', * block: 'center' *}] / const scrollToError = (el, scrollOption = {behavior: 'smooth', block: 'center' } ) => { const isError = el.getElementsByClassName('is-error') isError[0].scrollIntoView(scrollOption) }Copy the code

This.$nextTick() : $nextTick(); this.$nextTick();

The undefined error occurs because the scrollIntoView method is executed before the is-error element appears on the page. So call this.$nextTick() and wait for this round of view updates before calling the subsequent scrollToError method.

conclusion

This is very unvue, and Vue recommends not writing native DOM events until you have to, but I feel like I’ve reached a point where you can do it better, so let me know in the comments section

In addition

Preface In addition to the product acceptance to us add demand is true, the other are I made up ~

The reality is that:

Product Manager: How can there be so many reasons for such a simple function? This requirement must be made.

Project Manager: Eight man-hours for such a small requirement? No way. Work late at night, make it happen, and then add a little demand and it doesn’t count.

Project Manager: One day later? You’re dreaming. Early start time is normal, delayed performance is deducted.

.

HHH, life is not easy, Alynn sigh, but still want to keep optimistic, study hard!

I hope my article will be helpful to you on the road ahead.

update

After the article was posted, there was a comment from my partner:

  • What’s the best thing to do if it’s a complex form that’s collapsed?
  • The form is in a table, the table can be scrolled, how to deal with?
  • Can’t you just find the first incorrect form element focus?
  • ScrollIntoview has compatibility issues

Complex forms are collapsed

The implementation looks something like this:

At this point, it is very difficult to call scrollIntoView directly, so you need to manually expand the folded component and then call scrollIntoView.

The only way to do this is to emit events inside the folding component.

Start by adding a ref to the collapsed component.

Form component <el-form>... <ExpandComponent ref="expandComponentEle"> <el-form-item> ... <el-form-item/> <ExpandComponent/> ... </el-form>Copy the code

Then, when clicking submit, call this.$nextTick() first, wait for the view to update, the is-error element in the page will be rendered, and then emit a validate event to the collapsecomponent to process the expansion event inside the collapsecomponent.

validate() { return new Promise(resolve => { this.$refs.componentAFormEle.validate(async valid => { if (! valid) { await this.$nextTick() this.$refs.expandComponentEle.$emit('validate') await this.$nextTick() this.$utils.scrollToError(this.$refs.componentAFormEle.$el) } resolve(valid) }) }) }Copy the code

After receiving the validate event inside the collapsing component, it determines whether there are any faulty elements and expands them if there are.

Mounted () {this.$on('validate', () => { const hasError = this.$el.getElementsByClassName('is-error').length if (hasError) { this.renderExpand = true } })}Copy the code

Although the function is implemented, to be honest, the folding component is very hurt, because the folding component’s own responsibility is only to deal with the expansion and collapse, the verification of the form is actually written in the folding component, sad ah, but I have no other solution, if a better implementation method, be sure to let me know in the comments section ~

The form is in a table, and the table is scrollable

The implementation looks something like this:

vxe-table

If the table component uses vxe-table, it is comfortable because vxE-table already implements this function.

The code is simple:

async validate() { const errMap = await this.$refs.vxeGrid.validate(true).catch(errMap => errMap) return ! errMap },Copy the code

You don’t need to configure either, just call the validate method, because autoPos defaults to true.

But because scrollIntoView is not tuned, the page scrolling animation is gone, and then we have to find a way to convince the product to accept 😄

As an aside, for those of you who have not used VXE-table, a strong amway wave can enable you to deal with all the basic scenarios to be used in the development of tables. The most critical point is that the performance is good.

Once, in order to meet the abnormal needs of the product, I packaged the El-Table component of Element-UI severely. I needed to support all kinds of messy functions and wrote messy code. Finally, I found that there was a performance bottleneck and too much data would become stuck.

Until I know and VXE-table this form development artifact, I am tearless ah, why no one told me earlier…

el-table

If I was using el-table, I thought before I started writing the code that if I had a checksum error and the element wasn’t in the viewable area, it would be hard to implement, and I would have to manually handle scroll events or something like that, but I didn’t want to write it because I thought it would be complicated.

On second thought, the scrollIntoView API should be implemented directly. After all, as long as you can find the is-error class of the page, the element in the table is not display: None like the element in the collapsed component.

Indeed as expected:

The code is as follows:

<div class="scroll-into-view"> <el-form ref="paramsList" :model="paramsList"> <el-table :data="paramsList.watchList" border style="width: <el-table-column prop="sampleName" label=" sampleName" width="180"> </el-table-column> <el-table-column prop="sampleLot" </el-table-column> <el-table-column prop="lastNum" </el-table-column prop="lastNum" </el-table-column> <el-table-column prop="takeNum" label=" takeNum" width="80"> <template slot-scope="scope"> <el-form-item :prop="'watchList.'  + scope.$index + '.takeNum'" :rules="{ required: true, message: 'Required', trigger: 'blur' }" > <el-input-number style="width: 100%" v-model="scope.row.takeNum" :controls="false" :min="0" :max="scope.row.lastNum" ></el-input-number> </el-form-item> </template> </el-table-column> </el-table> </el-form> </div> < button@click ="submit"> submit() { this.$refs.paramsList.validate() this.$nextTick(() => { const isError = this.$refs.paramsList.$el.getElementsByClassName('is-error') isError[0].scrollIntoView({ behavior: 'smooth', block: 'center' }) }) }Copy the code

The fact proves, think so much is inferior to direct start work to write code, be in there think along while, return inferior to start work to try first, in case realized 😂

Can’t you just find the first incorrect form element focus?

Of course, but the product to page scrolling animation ah, focus no animation effect, uncomfortable.

Another point is that to call the focus method, you need to get the component instance of the first incorrect form element.

Compared to getElementsByClassName, it’s really cumbersome to traverse the parent $children component and find elm under $vNode to match for IS-error.

ScrollIntoview has compatibility issues

This did not expect, went to check the PC browser

Then I tested some supported browsers and found scrollIntoView

There is no animation, but it can still be positioned, just like the focus method that calls the element instance directly.

Overall, compatibility is ok, and I hope that those of you reading this will encounter chrome-only users in 2022. ^_^

So say, a small demand of product manager, lead to so many ghost problems, I estimate eight hours of working hours, also estimate less, when WE can not be cut working hours 😄