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. [LeetCode] 反转整数

    题目: 给定一个 32 位有符号整数,将整数中的数字进行反转. 示例 1: 输入: 123 输出: 321 示例 2: 输入: -123 输出: -321 示例 3: 输入: 120 输出: 21 注 ...

  2. AutoCompleteTextView搭配Poi搜索实现多项选择

    项目需要 需要用到AutoCompleteTextView控件,在输入之后能在下方产生一个推荐结果的列表,就类似于金山词霸一类软件.输入一两个字符就能出来一系列类似的的单词, 这里做的例子是输入城市名 ...

  3. [Umbraco] 自定义DataType中Data Editor Setting Type

    上一篇介绍了在定义Document Type中的属性时用到的Data Type,当使用dropdown list如何调用外部数据源,可以根据提供的数据连接字符串,sql语句就能实现你想要显示的数据. ...

  4. TypeScript设计模式之装饰、代理

    看看用TypeScript怎样实现常见的设计模式,顺便复习一下. 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想 ...

  5. MATLAB下数组随机打乱顺序的方法

    一:问题 有两个规模相同的数组,两个数组相同位置的元素一一对应,现在要将两数组的元素同时打乱顺序,并且乱序后的两数组对应位置元素要保持乱序前的对应关系. 二:方法  采用randperm()函数,产生 ...

  6. 启动HDFS之后一直处于安全模式org.apache.hadoop.hdfs.server.namenode.SafeModeException: Log not rolled. Name node is in safe mode.

    一.现象 三台机器 crxy99,crxy98,crxy97(crxy99是NameNode+DataNode,crxy98和crxy97是DataNode) 按正常命令启动HDFS之后,HDFS一直 ...

  7. (转)Linux PS 详解

    原文:https://cn.aliyun.com/jiaocheng/162702.html 摘要:原文地址:http://www.cnblogs.com/wangkangluo1/archive/2 ...

  8. PHP的语言构造器

    isset和empty看起来像是函数,我们也经常把它当作函数一样使用,但是实际上,它们是语言构造器. php中的语言构造器就相当于C中的预定义宏的意思,它属于php语言内部定义的关键词,不可以被修改, ...

  9. 计数排序/Counting Sort

    计数排序的算法思想: 对于每一个元素x,只要确定了元素x有多少个比它小的元素,那么就可以知道其最终的位置. 记输入数组为A[n],存放最后排序输出的数组为B[n],提供临时存储空间的中间数组记为C[k ...

  10. POJ 2083 Fractal 分形题目

    这两天自学了一线算法导论里分治策略的内容,秉着只有真正投入投入编程,才能更好的理解一种算法的思想的想法,兴致勃勃地找一些入门的题来学习. 搜了一下最后把目光锁定在了Poj fractal这一个题上.以 ...