input 输入框组件

源码:

<template>
<div :class="[
type === 'textarea' ? 'el-textarea' : 'el-input',
inputSize ? 'el-input--' + inputSize : '',
{
'is-disabled': inputDisabled,
'el-input-group': $slots.prepend || $slots.append,
'el-input-group--append': $slots.append,
'el-input-group--prepend': $slots.prepend,
'el-input--prefix': $slots.prefix || prefixIcon,
'el-input--suffix': $slots.suffix || suffixIcon || clearable
}
]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<!--当type的值不等于textarea时-->
<template v-if="type !== 'textarea'">
<!-- 前置元素 -->
<div class="el-input-group__prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</div>
<!--核心部分:输入框-->
<input
:tabindex="tabindex"
v-if="type !== 'textarea'"
class="el-input__inner"
v-bind="$attrs"
:type="type"
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete || autocomplete"
:value="currentValue"
ref="input"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
> <!-- input框内的头部的内容 -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
<slot name="prefix"></slot>
<!--prefixIcon头部图标存在时,显示i标签-->
<i class="el-input__icon" v-if="prefixIcon" :class="prefixIcon"></i>
</span>
<!-- input框内的尾部的内容 -->
<span class="el-input__suffix" v-if="$slots.suffix || suffixIcon || showClear || validateState && needStatusIcon">
<span class="el-input__suffix-inner">
<!--showClear为false时,显示尾部图标-->
<template v-if="!showClear">
<slot name="suffix"></slot>
<i class="el-input__icon" v-if="suffixIcon" :class="suffixIcon"></i>
</template>
<!--showClear为true时,显示清空图标-->
<i v-else class="el-input__icon el-icon-circle-close el-input__clear" @click="clear"></i>
</span>
<!--这里应该是跟表单的校验相关,根据校验状态显示对应的图标-->
<i class="el-input__icon" v-if="validateState" :class="['el-input__validateIcon', validateIcon]"></i>
</span>
<!-- 后置元素 -->
<div class="el-input-group__append" v-if="$slots.append">
<slot name="append"></slot>
</div>
</template>
<!--当type的值等于textarea时-->
<textarea
v-else
:tabindex="tabindex"
class="el-textarea__inner"
:value="currentValue"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="handleInput"
ref="textarea"
v-bind="$attrs"
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete || autocomplete"
:style="textareaStyle"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
>
</textarea>
</div>
</template>
<script>
import emitter from 'element-ui/src/mixins/emitter';
import Migrating from 'element-ui/src/mixins/migrating';
import calcTextareaHeight from './calcTextareaHeight';
import merge from 'element-ui/src/utils/merge';
import { isKorean } from 'element-ui/src/utils/shared'; export default {
name: 'ElInput', componentName: 'ElInput', mixins: [emitter, Migrating], inheritAttrs: false, inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
}, data() {
return {
currentValue: this.value === undefined || this.value === null
? ''
: this.value,
textareaCalcStyle: {},
hovering: false,
focused: false,
isOnComposition: false,
valueBeforeComposition: null
};
}, props: {
value: [String, Number], //绑定值
size: String, //输入框尺寸,只在type!="textarea" 时有效
resize: String, //控制是否能被用户缩放
form: String,
disabled: Boolean, //禁用
readonly: Boolean,
type: { //类型texttextarea和其他原生input的type值
type: String,
default: 'text'
},
autosize: { //自适应内容高度,只对 type="textarea" 有效,可传入对象,如,{ minRows: 2, maxRows: 6 }
type: [Boolean, Object],
default: false
},
autocomplete: {
type: String,
default: 'off'
},
/** @Deprecated in next major version */
autoComplete: {
type: String,
validator(val) {
process.env.NODE_ENV !== 'production' &&
console.warn('[Element Warn][Input]\'auto-complete\' property will be deprecated in next major version. please use \'autocomplete\' instead.');
return true;
}
},
validateEvent: { //输入时是否触发表单的校验
type: Boolean,
default: true
},
suffixIcon: String, //输入框尾部图标
prefixIcon: String, //输入框头部图标
label: String, //输入框关联的label文字
clearable: { //是否可清空
type: Boolean,
default: false
},
tabindex: String //输入框的tabindex
}, computed: {
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
//校验状态
validateState() {
return this.elFormItem ? this.elFormItem.validateState : '';
},
needStatusIcon() {
return this.elForm ? this.elForm.statusIcon : false;
},
validateIcon() {
return {
validating: 'el-icon-loading',
success: 'el-icon-circle-check',
error: 'el-icon-circle-close'
}[this.validateState];
},
//textarea的样式
textareaStyle() {
return merge({}, this.textareaCalcStyle, { resize: this.resize });
},
//输入框尺寸,只在 type!="textarea" 时有效
inputSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
//input是否被禁用
inputDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
//是否显示清空按钮
showClear() {
// clearable属性为true,即用户设置了显示清空按钮的属性;并且在非禁用且非只读状态下才且当前input的value不是空且该input获得焦点或者鼠标移动上去才显示
return this.clearable &&
!this.inputDisabled &&
!this.readonly &&
this.currentValue !== '' &&
(this.focused || this.hovering);
}
}, watch: {
value(val, oldValue) {
this.setCurrentValue(val);
}
}, methods: {
focus() {
(this.$refs.input || this.$refs.textarea).focus();
},
blur() {
(this.$refs.input || this.$refs.textarea).blur();
},
getMigratingConfig() {
return {
props: {
'icon': 'icon is removed, use suffix-icon / prefix-icon instead.',
'on-icon-click': 'on-icon-click is removed.'
},
events: {
'click': 'click is removed.'
}
};
},
handleBlur(event) {
this.focused = false;
this.$emit('blur', event);
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
}
},
select() {
(this.$refs.input || this.$refs.textarea).select();
},
resizeTextarea() {
if (this.$isServer) return;
//autosize自适应内容高度,只对 type="textarea" 有效,可传入对象,如,{ minRows: 2, maxRows: 6 }
const { autosize, type } = this;
if (type !== 'textarea') return;
//如果没设置自适应内容高度
if (!autosize) {
this.textareaCalcStyle = { //高度取文本框的最小高度
minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
};
return;
}
const minRows = autosize.minRows;
const maxRows = autosize.maxRows;
//如果设置了minRows和maxRows需要计算文本框的高度
this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
},
handleFocus(event) {
this.focused = true;
this.$emit('focus', event);
},
handleComposition(event) {
// 如果中文输入已完成
if (event.type === 'compositionend') {
// isOnComposition设置为false
this.isOnComposition = false;
this.currentValue = this.valueBeforeComposition;
this.valueBeforeComposition = null;
//触发input事件,因为input事件是在compositionend事件之后触发,这时输入未完成,不会将值传给父组件,所以需要再调一次input方法
this.handleInput(event);
} else { //如果中文输入未完成
const text = event.target.value;
const lastCharacter = text[text.length - 1] || '';
//isOnComposition用来判断是否在输入拼音的过程中
this.isOnComposition = !isKorean(lastCharacter);
if (this.isOnComposition && event.type === 'compositionstart') {
// 输入框中输入的值赋给valueBeforeComposition
this.valueBeforeComposition = text;
}
}
},
handleInput(event) {
const value = event.target.value;
//设置当前值
this.setCurrentValue(value);
//如果还在输入中,将不会把值传给父组件
if (this.isOnComposition) return;
//输入完成时,isOnComposition为false,将值传递给父组件
this.$emit('input', value);
},
handleChange(event) {
this.$emit('change', event.target.value);
},
setCurrentValue(value) {
// 输入中,直接返回
if (this.isOnComposition && value === this.valueBeforeComposition) return;
this.currentValue = value;
if (this.isOnComposition) return;
//输入完成,设置文本框的高度
this.$nextTick(this.resizeTextarea);
if (this.validateEvent && this.currentValue === this.value) {
this.dispatch('ElFormItem', 'el.form.change', [value]);
}
},
calcIconOffset(place) {
let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []);
if (!elList.length) return;
let el = null;
for (let i = 0; i < elList.length; i++) {
if (elList[i].parentNode === this.$el) {
el = elList[i];
break;
}
}
if (!el) return;
const pendantMap = {
suffix: 'append',
prefix: 'prepend'
}; const pendant = pendantMap[place];
if (this.$slots[pendant]) {
el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${this.$el.querySelector(`.el-input-group__${pendant}`).offsetWidth}px)`;
} else {
el.removeAttribute('style');
}
},
updateIconOffset() {
this.calcIconOffset('prefix');
this.calcIconOffset('suffix');
},
//清空事件
clear() {
//父组件的value值变成了空,更新父组件中v-model的值
this.$emit('input', '');
//触发了父组件的change事件,父组件中就可以监听到该事件
this.$emit('change', '');
//触发了父组件的clear事件
this.$emit('clear');
//更新当前的currentValue的值
this.setCurrentValue('');
}
}, created() {
this.$on('inputSelect', this.select);
}, mounted() {
this.resizeTextarea();
this.updateIconOffset();
}, updated() {
this.$nextTick(this.updateIconOffset);
}
};
</script>

如下图所示:

(2)核心部分 input 输入框

<input
:tabindex="tabindex"
v-if="type !== 'textarea'"
class="el-input__inner"
v-bind="$attrs"
:type="type"
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete || autocomplete"
:value="currentValue"
ref="input"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
>

1、 :tabindex="tabindex" 是控制tab键按下后的访问顺序,由用户传入tabindex;如果设置为负数则无法通过tab键访问,设置为0则是在最后访问。

2、 v-bind="$attrs" 为了简化父组件向子组件传值,props没有注册的属性,可以通过$attrs来取。

3、inputDisabled :返回当前input是否被禁用;readonly:input的原生属性,是否是只读状态;

4、 原生方法compositionstart、compositionupdate、compositionend

compositionstart 官方解释 : 触发于一段文字的输入之前(类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词),通俗点,假如我们要输入一段中文,当我们按下第一个字母的时候触发 。

compositionupdate在我们中文开始输入到结束完成的每一次keyup触发。

compositionend则在我们完成当前中文的输入触发 。

这三个事件主要解决中文输入的响应问题,从compositionstart触发开始,意味着中文输入的开始且还没完成,所以此时我们不需要做出响应,在compositionend触发时,表示中文输入完成,这时我们可以做相应事件的处理。

 handleComposition(event) {
// 如果中文输入已完成
if (event.type === 'compositionend') {
// isOnComposition设置为false
this.isOnComposition = false;
this.currentValue = this.valueBeforeComposition;
this.valueBeforeComposition = null;
//触发input事件,因为input事件是在compositionend事件之后触发,这时输入未完成,不会将值传给父组件,所以需要再调一次input方法
this.handleInput(event);
} else { //如果中文输入未完成
const text = event.target.value;
const lastCharacter = text[text.length - 1] || '';
//isOnComposition用来判断是否在输入拼音的过程中
this.isOnComposition = !isKorean(lastCharacter);
if (this.isOnComposition && event.type === 'compositionstart') {
// 输入框中输入的值赋给valueBeforeComposition
this.valueBeforeComposition = text;
}
}
},
handleInput(event) {
const value = event.target.value;
//设置当前值
this.setCurrentValue(value);
//如果还在输入中,将不会把值传给父组件
if (this.isOnComposition) return;
//输入完成时,isOnComposition为false,将值传递给父组件
this.$emit('input', value);
},

(3)calcTextareaHeight.js使用来计算文本框的高度

//原理:让height等于scrollHeight,也就是滚动条卷去的高度,这里就将height变大了,然后返回该height并绑定到input的style中从而动态改变textarea的height
let hiddenTextarea;
//存储隐藏时候的css样式的
const HIDDEN_STYLE = `
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
`;
//用来存储要查询的样式名
const CONTEXT_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing'
]; function calculateNodeStyling(targetElement) {
// 获取目标元素计算后的样式,即实际渲染的样式
const style = window.getComputedStyle(targetElement);
// getPropertyValue方法返回指定的 CSS 属性的值;这里返回box-sizing属性的值
const boxSizing = style.getPropertyValue('box-sizing');
// padding-bottom和padding-top值之和
const paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'))
);
// border-bottom-width和border-top-width值之和
const borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'))
);
// 其他属性以及对应的值
const contextStyle = CONTEXT_STYLE
.map(name => `${name}:${style.getPropertyValue(name)}`)
.join(';'); return { contextStyle, paddingSize, borderSize, boxSizing };
} export default function calcTextareaHeight(
targetElement, //目标元素
minRows = 1, //最小行数
maxRows = null //最大行数
) {
// 创建一个隐藏的文本域
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
//获取目标元素的样式
let {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(targetElement);
//设置对应的样式属性
hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
hiddenTextarea.value = targetElement.value || targetElement.placeholder || ''; // 获取滚动高度
let height = hiddenTextarea.scrollHeight;
const result = {}; if (boxSizing === 'border-box') {
// 如果是 border-box,高度需加上边框
height = height + borderSize;
} else if (boxSizing === 'content-box') {
// 如果是 content-box,高度需减去上下内边距
height = height - paddingSize;
}
// 计算单行高度,先清空内容
hiddenTextarea.value = '';
// 再用滚动高度减去上下内边距
let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; if (minRows !== null) { // 如果参数传递了 minRows
// 最少的高度=单行的高度*行数
let minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
// 如果是 border-box,还得加上上下内边距和上下边框的宽度
minHeight = minHeight + paddingSize + borderSize;
}
// 高度取二者最大值
height = Math.max(minHeight, height);
result.minHeight = `${ minHeight }px`;
}
if (maxRows !== null) {
let maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
height = Math.min(maxHeight, height);
}
result.height = `${ height }px`;
hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
return result;
};

参考博文:https://www.jianshu.com/p/74ba49507fe6

https://juejin.im/post/5b7d18e46fb9a01a12502616

element-ui input组件源码分析整理笔记(六)的更多相关文章

  1. element-ui 组件源码分析整理笔记目录

    element-ui button组件 radio组件源码分析整理笔记(一) element-ui switch组件源码分析整理笔记(二) element-ui inputNumber.Card .B ...

  2. element-ui button组件 radio组件源码分析整理笔记(一)

    Button组件 button.vue <template> <button class="el-button" @click="handleClick ...

  3. element-ui MessageBox组件源码分析整理笔记(十二)

    MessageBox组件源码,有添加部分注释 main.vue <template> <transition name="msgbox-fade"> < ...

  4. element-ui Rate组件源码分析整理笔记(十三)

    Rate组件源码比较简单,有添加部分注释 main.vue <template> <!--valuenow当前的评分 valuetext当前显示的文本--> <div c ...

  5. element-ui Message组件源码分析整理笔记(八)

    Message组件源码: main.js import Vue from 'vue'; import Main from './main.vue'; import { PopupManager } f ...

  6. element-ui Steps步骤条组件源码分析整理笔记(九)

    Steps步骤条组件源码: steps.vue <template> <!--设置 simple 可应用简洁风格,该条件下 align-center / description / ...

  7. Element UI table组件源码分析

    本文章从如下图所示的最基本的table入手,分析table组件源代码.本人已经对table组件原来的源码进行削减,源码点击这里下载.本文只对重要的代码片段进行讲解,推荐下载代码把项目运行起来,跟着文章 ...

  8. element-ui inputNumber、Card 、Breadcrumb组件源码分析整理笔记(三)

    inputNumber组件 <template> <!--@dragstart.prevent禁止input中数字的拖动--> <div @dragstart.preve ...

  9. element-ui Upload 上传组件源码分析整理笔记(十四)

    简单写了部分注释,upload-dragger.vue(拖拽上传时显示此组件).upload-list.vue(已上传文件列表)源码暂未添加多少注释,等有空再补充,先记下来... index.vue ...

随机推荐

  1. 学习推荐-Redis学习手册

    redis之旅: http://www.cnblogs.com/stephen-liu74/archive/2012/02/27/2370212.html

  2. (转)深入浅出 RPC - 深入篇

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/mindfloating/article/details/39474123 <深入篇>我们 ...

  3. webstorm引用ESLint进行静态代码检查

    安装 ESLint 基于 Node 平台,所以 Nodejs 是必须安装的,然后通过 npm 安装 ESLint 包,至于全局安装还是作为开发依赖安装,取决于个人. 然后在 WebStorm 中,打开 ...

  4. Anaconda 科学计算环境与包的管理

    相信大多数 python 的初学者们都曾为开发环境问题折腾了很久,包管理和 python 不同版本的问题,特别是 window 环境安装个 scrapy 各种报错 ,使用 Anaconda 可以很好的 ...

  5. javascript保留字趣史

    转载自[https://mathiasbynens.be/notes/reserved-keywords](https://mathiasbynens.be/notes/reserved-keywor ...

  6. Tensorflow应用之LSTM

    学习RNN时原理理解起来不难,但是用TensorFlow去实现时被它各种数据的shape弄得晕头转向.现在就结合一个情感分析的案例来了解一下LSTM的操作流程. 一.深度学习在自然语言处理中的应用 自 ...

  7. Executor简介

        Executor是一个接口,这个接口负责执行提交给它的任务(Runnable对象).这个接口能够使“任务提交”与“任务执行”解耦.即某人只要把任务提交给Executor就好了,至于它怎么给任务 ...

  8. 【IT笔试面试题整理】链表

    如何准备 Linked list questions are extremely common These can range from simple (delete a node ina linke ...

  9. windows环境下搭建Java开发环境(一):jdk安装和配置

    一.资源下载 官网:http://www.oracle.com/technetwork/java/javase/downloads/index.html 本人安装的是jdk1.8,百度云资源:链接:h ...

  10. Netbeans 8.0配置Python开发环境

    1. 菜单栏:工具->插件->设置->添加 配置如下信息: http://deadlock.netbeans.org/hudson/job/nbms-and-javadoc/last ...