# 给爱的人发个邮件吧
# 课程目标
通过一系列的练习掌握如何分解问题、解决问题,在这个过程中如何设计自己的代码结构,如何优化及重构。
# 课程描述
通过一个小练习综合运用 HTML、CSS、JavaScript,我们要实现一个邮箱输入的提示功能。最终示意图:
部分代码参考:
<div class="wrapper">
<input id="email-input" type="text">
<ul id="email-sug-wrapper" class="email-sug"></ul>
</div>
// 邮箱后缀List参考
var postfixList = ['163.com', 'gmail.com', '126.com', 'qq.com', '263.net'];
# 先来个基础的
# 需求
根据下面需求实现如示意图所示的邮箱输入提示功能,注意,根据要求只需实现下面功能
- 当用户没有任何输入时,提示框消失
- 当用户输入字符后,显示提示框,并且把用户输入的内容自动拼上邮箱后缀进行显示
- 暂时不用考虑示意图中的红色和蓝色背景色的逻辑
- 注意用户输入中前后空格需要去除
# 设计
从今天开始,我们希望你在写代码之前开始画流程图
推荐工具:
- Processon (opens new window) 对于有经验的同学,你们可以跳过下面的部分,零基础的同学请跟着我一起进行设计。
这个任务的总目标是用户有输入的时候,进行对应的提示,所以核心流程是:
用户输入 -> 提示框进行反馈
那核心流程进一步进行拆解,就包括了:
发现用户输入 -> 获取用户输入内容 -> 生成提示框提示内容 -> 进行提示
第一步,发现用户输入,可以利用监听用户在输入框的输入对应的事件,尝试用 keyup、keypress、keydown 以及 oninput 四个事件分别来测试对于用户输入的事件监听,并在事件响应函数中增加 console.log('event handle') 。并尝试以下输入方式,观察提示框内容变化的情况,以及 console 的输出情况:
- 一个字母一个字母的输入
- 一个字母一个字母输入,同时加上按回车键,ESC 键,上下左右键
- 按住某个字母键不动
记住试验结论,然后就上面 4 个事件进行搜索,阅读相关规范标准及技术文章。根据实验结果选择你认为合适的事件监听方式。
第一步是后面几步的起点,所以,我们可以把后面几步各自封装成一个函数,在第一步里进行调用
第二步,获取用户输入,这个比较简单,注意考虑 trim
第三步,生成提示框提示内容,根据第二步获得的内容,遍历 postfixList 数组,生成要在 email-sug-wrapper 的 UL 中填充的内容。
第四步,根据实际是否有提示内容,控制 email-sug-wrapper 的显示 / 隐藏情况
这样,这个任务被分解为 4 个步骤,你要做的就是实现这 4 个步骤的代码细节。
代码结构参考如下:
inputDom的输入监听 = function() {
获取用户输入,生成提示框中的提示内容,将提示内容添加到email-sug-wrapper中
控制email-sug-wrapper的显示/隐藏状态
}
function 获取用户输入() {
拿到input输入框的输入内容trim后返回
}
function 生成提示框中的提示内容() {
获取用户输入
遍历postfixList {
把用户输入和每一个postfix进行结合成为每一个Li
}
返回生成的提示内容
}
function 将提示内容添加到email-sug-wrapper中() {
获取生成提示框中的提示内容
将内容添加到email-sug-wrapper中
}
function 控制email-sug-wrapper的显示/隐藏状态() {
if 用户输入为空 {
隐藏提示框
} else {
显示提示框
}
}
function 隐藏提示框() {
做具体隐藏提示框的操作
}
function 显示提示框() {
做具体显示提示框的操作
}
# 测试用例
测试用例的阅读方式:描述中间有一个 -> 符号,-> 前面的内容表示请你这么操作来测试,-> 后面的内容表示应该出现的结果
- 输入框中没有任何输入内容 -> 无提示框
- 输入框中输入了很多半角或者全角的空格 -> 无提示框
- 输入框中输入了"abc" -> 出现提示框,提示框中的内容为 abc 开头,后面跟着 @163.com,@gmail.com 等一系列的提示
- 输入框中输入了" abc " -> 出现提示框,提示框中的内容为 abc 开头,后面跟着 @163.com,@gmail.com 等一系列的提示
- 输入框中先输入"abc",然后再全部删掉 -> 输入 abc 时出现提示框,全部删除后提示框消失 完成以上测试用例即可,这时候我们有一个最基本的提示功能了,也许你已经发现有一些可以优化的地方,没关系,我们接下来一起一步一步来优化。
# 小优化编码
# 需求
如果我们输入的是 abc@1 ,这个时候出现的提示框内容是
abc@1@163.com
abc@1@gmail.com
abc@1@126.com …… 很明显,上面的提示框不是一个符合用户需求的提示,我们需要做一些优化:
当用户输入含有 @ 符号时,我们选取用户输入的@前面的字符来和后缀拼接
# 设计
同样有经验的同学请跳过,零基础同学和我一起
上面的需求,在我们上一步的结构中,需要在“生成提示框中的提示内容”这个函数中进行调整
我们把需求解读一下,关键在于判断用户输入有没有 @,那如何判断字符串中是否包含某个字符呢?可以回顾一下字符串相关的内容。
在发现有 @ 之后,要做的就是拿到 @ 符号之前的字符串内容,来和 postfixList 进行拼接。
代码结构:
function 生成提示框中的提示内容() {
用来拼接的用户输入内容 = 获取用户输入
if 用户输入含有@ {
用来拼接的用户输入内容 = 只使用@之前的字符串
}
遍历postfixList {
把用来拼接的用户输入内容和每一个postfix进行结合成为每一个Li
}
返回生成的提示内容
}
# 测试用例
- 输入 a -> 出现提示框,提示 a@163.com,a@gmail.com……
- 输入 a@ -> 出现提示框,提示 a@163.com,a@gmail.com……
- 输入 abc@1 -> 出现提示框,提示 abc@163.com,abc@gmail.com……
- 输入 abc@163.com -> 出现提示框,提示 abc@163.com,abc@gmail.com……
# 编码
# 需求
这下出现的提示好多了,不过用户如果已经输入了 @1 ,说明他大概率要输入 163 或者 126 ,我们需要让我们的提示更加符合用户的期望。满足以下需求:
- 当用户输入了 @ 及部分后缀时,只从 postfixList 选取符合用户输入预期的后缀,我们以前缀匹配为要求
- 当用户输入不满足任何前缀匹配时,则显示全部提示
# 设计
这个需求依然需要调整“生成提示框中的提示内容”,如果用户输入的字符含有 @ ,我们需要拿到 @ 之后的字符串,来和 postfixList 中每个字符串做前缀匹配,符合要求的我们才会使用
代码结构
function 生成提示框中的提示内容() {
用来拼接的用户输入内容 = 获取用户输入
if 用户输入含有@ {
用来拼接的用户输入内容 = @之前的字符串
用来前缀匹配的用户输入内容 = @之后的字符串
}
遍历postfixList {
if 用来前缀匹配的用户输入内容前缀匹配遍历字符串元素
把用来拼接的用户输入内容和这个字符串进行结合成为一个Li
}
返回生成的提示内容
}
# 测试用例
- 输入 a@1 -> 出现提示框,提示 a@163.com, a@126.com
- 输入 a@g -> 出现提示框,提示 a@gmail.com
- 输入 a@2 -> 出现提示框,提示 a@263.net
- 输入 a@qq -> 出现提示框,提示 a@qq.com
- 输入 a@163. -> 出现提示框,提示 a@163.com
- 输入 a@126.com -> 出现提示框,提示 a@126.com
- 输入 a@qq.com (两个空格)-> 出现提示框,提示 a@qq.com
- 输入 a@qq.comm -> 出现提示框,出现全部提示
# 新的需求编码
# 需求
上面我们只完成了提示,但提示还没有直接作用到选择中,我们现在完成以下需求:
- 使用 CSS 实现:鼠标滑过提示框的某一个提示时,这个提示内容背景色变化,表示鼠标经过了这个 DOM 节点
- 鼠标如果点击某个提示,则提示内容进入输入框,同时提示框消失
- 在上个步骤结束后,在输入框中任意再输入字符或删除字符,则重新开始出现提示框
# 设计
鼠标点击,是一个新的用户输入,所以我们需要有一个新的事件监听,那用哪一个 DOM 节点来监听这个鼠标事件呢?想一想前面提到的事件代理,选择一个合适的事件监听方式。
当监听到用户点击某一个提示的 Li 后,我们需要做的就是,把 Li 对应的提示内容,放在输入框中,同时隐藏提示框
代码结构
选择一个合适的DOM节点.监听鼠标点击 = function () {
if 被点击的是不是提示框中的Li节点 {
获取被点击Li对应的提示内容
将内容放到input输入框中
隐藏输入框
}
}
# 需求
尝试在输入框中输入<b>
,看看提示框发生了什么
# 阅读
# 设计
我们需要在两个地方进行处理,一个是在生成提示内容那里,对于特殊字符进行转义编码,另一个是在把鼠标点击的提示框内容转回输入框时进行解码。
# 加上键盘
# 需求
我们给提示框加上 3 个按键的功能,分别是回车和上下键,使得可以通过键盘操作进行提示框的选择
- 当有提示框的时候,默认第一个提示为被选择状态,用一个和鼠标滑过不一样的背景色来标识
- 当有输入框的时候,按上键,可以向上移动选择状态,如果按键之前的被选择提示是第一个,则被选状态移到最下面一个
- 当有输入框的时候,按下键,可以向下移动选择状态,如果按键之前的被选择提示是最后一个,则被选状态移到第一个
- 当有输入框时,按回车键,则将当前被选中状态的提示内容,放到输入框中,并隐藏提示框
- 当没有输入框的时候,这 3 个键盘按键无响应
- 当用户输入发生改变的时候,选择状态都重新切回到第一个提示
# 阅读
# 设计
整体任务核心流程为:
监听键盘事件 -> 判断按键 -> 如果是上下键则变更选中状态,如果是回车键则进行内容进输入框操作
监听键盘事件及判断按键:我们需要在键盘监听中,增加对于这 3 个键的特殊处理。回忆一下之前的实验,keydown,keypress,keyup,oninput,对于这 3 个键的监听是和之前的合并,还是另外处理,不妨实验一下。
如果是上下键则变更选中状态:这里的关键在于,我们如何记录选中状态,以及如何改变。对于零基础的同学,在不基于任何框架的情况下,我们介绍两种基本思路:
- 基于 DOM ,当初次渲染提示框时,在第一个 Li 中设定一个选中样式的 CSS ,在按上下键的时候,我们通过样式找到设置了特殊样式的 Li ,清除掉它的样式,然后根据上下键,判断要设置状态的新一个 Li 是谁,并设置上。按回车的时候,则同样是通过样式找到这个 Li ,拿到它的内容,回填给 input 基于 DOM 的思路一代码结构
// 需要修改一下之前的inputDom的输入监听
inputDom的输入监听 = function() {
// 新增
如果按键不是上下及回车重置选中状态()
获取用户输入,生成提示框中的提示内容,将提示内容添加到email-sug-wrapper中
控制email-sug-wrapper的显示/隐藏状态
}
function 重置选中状态() {
找到当前为选中状态的Li
if (当前选中状态Li不是第一个) {
清除掉它的选中状态
设置第一个Li为选中状态
}
}
// 监听特殊3个键的键盘事件,这个事件可能就是inputDom的输入监听,也有可能是另外一个,请自己测试后判断
监听特殊3个键的键盘事件 = function() {
找到当前为选中状态的Li
清除掉它的选中状态
if 按的是上键 {
if 找到的Li不是第一个 {
将它的前一个Li设为选中
} else {
将最后一个Li设为选中
}
}
if 按的是下键 {
if 找到的Li不是最后一个 {
将它的下一个Li设为选中
} else {
将第一个Li设为选中
}
}
if 按的是回车 {
将找到的Li的HTML内容解码后填到input中
隐藏提示框
}
}
- 基于数据,我们设置一个变量,来保存当前选择的 index(即当前选中的是第几行,从 0 开始计数),当发生上下键操作的时候,直接改变 index 值,然后重新渲染提示框中的所有 html 内容,根据 index 设置来操作后的选择提示样式,回车的时候,直接根据 index 来获取对应的内容 基于数据的代码结构
// 增加一个变量,用于存储当前选中的提示Li的序号
var nowSelectTipIndex = 0;
// 需要修改一下之前的“生成提示框中的提示内容()”
function 生成提示框中的提示内容() {
获取用户输入
遍历postfixList {
把用户输入和每一个postfix进行结合成为每一个Li
}
// 新增
将第nowSelectTipIndex个Li的样式设置为被选样式
返回生成的提示内容
}
function 将提示内容添加到email-sug-wrapper中() {
获取生成提示框中的提示内容
将内容添加到email-sug-wrapper中
}
// 需要修改一下之前的inputDom的输入监听
inputDom的输入监听 = function() {
// 新增
如果按键不是上下及回车重置选中状态()
获取用户输入,生成提示框中的提示内容,将提示内容添加到email-sug-wrapper中
控制email-sug-wrapper的显示/隐藏状态
}
function 重置选中状态() {
将 nowSelectTipIndex 设为0
}
// 监听特殊3个键的键盘事件,这个事件可能就是inputDom的输入监听,也有可能是另外一个,请自己测试后判断
监听特殊3个键的键盘事件 = function() {
if 按的是上键 {
if nowSelectTipIndex不是第一个 {
nowSelectTipIndex设置为当前提示框的Li的个数 - 1
} else {
nowSelectTipIndex - 1
}
}
if 按的是下键 {
if nowSelectTipIndex小于Li的最大索引 {
nowSelectTipIndex设置为 0
} else {
nowSelectTipIndex + 1
}
}
if 按的是回车 {
从当前提示框中选第 nowSelectTipIndex 个Li,将其HTML内容解码后填到input中
隐藏提示框
}
}
介绍完两种思路后,请你用两种思路都实现一遍,我们更加鼓励基于数据的思路,这样可以方便我们把用户界面、交互和数据业务逻辑进行解耦。当然上面的数据思路中,我们可以做得更加彻底一些,哪里还可以优化呢?这就是留给大家的作业。
# 优化体验
# 需求
当我们进入页面,或者当我们点击鼠标进行提示选择后,输入框的焦点就不在了,所以请你优化一下用户体验:
- 一进入页面就将焦点放在输入框中
- 用户点击鼠标,进行提示选择后,焦点依然在输入框中
- 用户按 ESC 键的时候,对用户输入进行全选
- 对你还能想到的其它用户体验的方式进行优化
# 提交
将这次任务最后实现的代码进行提交。
# 总结
把今天的学习用时,收获,问题进行记录。