认识script插件Bootstrap
认识script插件Bootstrap
以下为学习过程中的极简提炼笔记,以供重温巩固学习
学习准备
准备工作
学习目的
- 通过做案例,熟悉bootstrap
- 通过做实际的案例,熟悉需求拆解
- 通过做需求拆解,熟悉开发思维以及用到的技术链,包括库引入、解构等等
案例效果
案例效果包括:
- 业务1,查询数据
- 业务2,利用bootstrap弹框,点击添加按钮,没有离开当前页面,在当前页面弹出弹框(弹窗)添加数据
- 业务3,修改数据
- 业务4,删除数据
- Bootstrap弹框
- 渲染列表(查)
- 新增图书(增)
- 删除图书(删)
- 编辑图书(改)
- (实际上,在增加/删/改后,需要重新获取新数据显示,所以也有查,查放最前面)
bootstrap插件
bootstrap弹框
由浅入深:先做简化版本的bootstrap弹框
功能:在不离开当前页面的前提下,覆盖弹出一层窗口,揪出显示需要操作的内容,供用户操作
步骤:
- 引入 bootstrap.css库 和 bootstrap.js库
- 使用两个库的样式,准备弹框标签、按钮,确认结构(弹框标签就是个div盒子,可到官方文档查询例子确认)
- 通过 bootstrap.js库 的属性,通过自定义属性,控制弹框的显示和隐藏
bootstrap有两种方式控制弹框的显示和隐藏
通过bootstrap自定义属性,控制显示和隐藏
(自定义)属性控制方式
- 在项目中引入bootstrap.css和bootstrap.js,并准备好显示弹框的按钮
- 先到bootstarp插件库官网,确认需要用到的样式
- 找到对应需要使用的结构,复制用到的标签,粘贴/拉取到自己的项目中
- 确认复制过来的标签的功能,通过浏览器调试确认类和样式如何实现显示和隐藏的控制(通过浏览器调试确认类和样式,是这个modal类名来显示和隐藏的控制)
- 确认各标签对应弹框的哪些部分
- 思考如何通过点击按钮控制弹框出现/隐藏,虽然能通过绑定点击事件,控制display属性,但实际bootstrap内部已经提供一些控制显示和隐藏的(自定义)属性,我们通过使用bootstrap内部提供的自定义属性来关联控制显示隐藏
如何实现显示弹框?
- bootstrap内部已经提供一些控制显示和隐藏的(自定义)属性
两个属性,分别是 data-bs-toggle 和 data-bs-target
data-bs-toggle="modal" :点击会出来一个modal弹框(注意:不是.modal类选择器)
data-bs-target="css选择器":一个网页里面可能会有多个弹框,需要传入 某个需要点击弹出的弹框的css选择器
- 先找到按钮标签,然后绑定bootstrap提供的功能属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bootstrap 弹框</title>
<!-- 引入bootstrap.css -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!--
目标:使用Bootstrap弹框
1. 引入bootstrap.css 和 bootstrap.js
2. 准备弹框标签,确认结构
3. 通过自定义属性,控制弹框的显示和隐藏
-->
<!-- 先找到按钮标签,然后绑定bootstrap.js库提供的功能属性,第一个属性是toggle切换,点击后出来modal类型弹框,并非类选择器,而是bootstrap内部属性 -->
<!-- 在一个网页内可能会有多个弹框,因此需要指定第二个属性target,传进去对应的css选择器 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target=".my-box">
显示弹框
</button>
<!--
弹框标签
bootstrap的modal弹框,添加modal类名(默认隐藏),通过浏览器调试确认类和样式,是这个modal类名来显示和隐藏的控制
-->
<!-- 再加一个mybox,用于区分弹框 -->
<div class="modal my-box" tabindex="-1">
<div class="modal-dialog">
<!-- 弹框-内容 -->
<div class="modal-content">
<!-- 弹框-头部 -->
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- 弹框-身体 -->
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<!-- 弹框-底部 -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<!-- 引入bootstrap.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script插件Bootstrap插件Bootstrap>
</body>
</html>
通过JS控制bootstarp的显示和隐藏
为什么需要使用JS来控制?
因为使用属性控制,在显示和隐藏时,无法执行其他额外的JS逻辑
实际上,在用户点击,框体显示之前,以及点击保存,框体隐藏之前,都需要执行额外的js代码逻辑
通过js来控制bootstrap弹框的代码思路:
- 先做点击编辑姓名出现弹框,点击保存按钮隐藏弹框的主业务逻辑
- 再写其他业务逻辑
- 获取需要控制的哪一个弹框的盒子dom
- 使用bootstrap.js库提供的bootstrap.modal方法,将盒子传进去
- 后续只需要调用modal.方法,来实现控制
// 创建弹框对象
const modalDom = document.querySelector('css选择器')
const modal = new bootstrap.Modal(modalDom)
// 显示弹框
modal.show()
// 隐藏弹框
modal.hide()
总结:
- 单纯显示/隐藏功能的交互按钮,通过属性控制弹框显示和隐藏
- 有额外的逻辑代码时,通过JS控制
JS弹框案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bootstrap 弹框</title>
<!-- 引入bootstrap.css -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!--
目标:使用JS控制弹框,显示和隐藏
1. 创建弹框对象
2. 调用弹框对象内置方法
.show() 显示
.hide() 隐藏
-->
<button type="button" class="btn btn-primary edit-btn">
编辑姓名
</button>
<div class="modal name-box" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">请输入姓名</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="">
<span>姓名:</span>
<input type="text" class="username">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary save-btn">保存</button>
</div>
</div>
</div>
</div>
<!-- 引入bootstrap.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script>
<script>
// 1. 创建弹框对象,将弹框的dom获取到,用自定义的类名,传进去,赋予给modalDom
const modalDom = document.querySelector('.name-box')
// 用bootstrap库提供的bootstrap.Modal对象方法,将对象传进去,赋予给modal
const modal = new bootstrap.Modal(modalDom)
// 编辑姓名->点击->赋予默认姓名->弹框显示
document.querySelector('.edit-btn').addEventListener('click', () => {
document.querySelector('.username').value = '默认姓名'
// 2. 显示弹框
modal.show()
})
// 保存->点击->->获取姓名打印->弹框隐藏
document.querySelector('.save-btn').addEventListener('click', () => {
const username = document.querySelector('.username').value
console.log('模拟把姓名保存到服务器上', username)
// 2. 隐藏弹框
modal.hide()
})
</script>
</body>
</html>
axios搭配bootstrap实战 完成图书管理案例
获取&渲染列表
获取并渲染列表
分析:
- 由于是个人的图书数据,因此每个操作者应该有独立、唯一的标识符;同时能避免同一个数据库多人同时操作数据混淆
- 案例要求每个操作者默认有3本书
- 基于已有的书的数据基础上,再做增删改查
逻辑:
- 使用axios从服务器获取个人数据(获取图书列表数据)
- 确认标签的结构,将数据渲染到页面(通过JS代码,将数据渲染到指定的Dom结构中)
准备的模板代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>案例-图书管理</title>
<!-- 引入的3个css库 -->
<!--字体图标-->
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_3736758_vxpb728fcyh.css">
<!--引入bootstrap.css -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css"rel="stylesheet">
<!--核心样式-->
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<!-- 页面对应标签结构,做到哪个功能就开调试找 -->
<!-- 主体区域 -->
<div class="container">
<!--头部标题和添加按钮-->
<div class="top"> I
<h3>图书管理</h3>
<!-- 添加按钮 -->
<button type="button" class="btn btn-primary plus-btn"data-bs-toggle="modal" data-bs-target=".add-modal">+ 添加</button>
</div>
<!--数据列表 -->
<table class="table" >...</table>
<table class="table">
<thead class="table-light">...</thead>
<tbody class="list">
<tr>
<td>${ index + 1 }</td>
<td>${ item.bookname }</td>
<td>${ item.author }</td>
<td>${ item.publisher }</td>
<td>
<span class="del">删除</span>
<span class="edit">编辑</span>
</td>
</tr>
</tbody>
</table>
</div>
<!--新增-弹出框-->
<div class="modal fade add-modal">
<!--中间白色区域-->
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header top">
<span>添加图书</span>
<button type="button" class="btn-close" aria-label="Close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body form-wrap">
<!-- 新增表单-->
<form class="add-form">
<div class="mb-3">
<label for="bookname" class="form-label">书名</label>
<input type="text" class="form-control bookname" placeholder="请输入书籍名称" name="bookname" >
</div>
<div class="mb-3">
<label for="author" class="form-label">作者</label>
<input type="text"class="form-control author" placeholder="请输入作者名称" name="author">
</div>
<div class="mb-3">
<label for="publisher" class="form-label">出版社</label>
<input type="text" class="form-control publisher" placeholder="请输入出版社名称" name="publisher" >
</div>
</form>
</div>
<div class="modal-footer btn-group">
<button type="button" class="btn btn-primary"data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary add-btn">保存</button>
</div>
</div>
</div>
</div>
<!--编辑-弹出框-->
<div class="modal fade edit-modal">
<!--中间白色区域-->
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header top">
<span>修改图书</span>
<button type="button" class="btn-close" aria-label="Close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body form-wrap" >
<!--编辑表单-->
<form class="edit-form">
<input type="hidden" class="id" name="id" value="84783">
<div class="mb-3">
<label for="bookname" class="form-label">书名</label>
<input type="text" class="form-control bookname" placeholder="请输入书籍名称" name="bookname">
</div>
<div class="mb-3">
<label for="author" class="form-label">作者</label>
<input type="text"class="form-control author" placeholder="请输入作者名称" name="author">
</div>
<div class="mb-3">
<label for="publisher" class="form-label">出版社</label>
<input type="text" class="form-control publisher" placeholder="请输入出版社名称" name="publisher" >
</div>
</div>
<div class="modal-footer btn-group">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary edit-btn">修改</button>
</div>
</div>
</div>
</div>
<!-- 引入的JS库,包括 axios、 form-serialize、 bootstrap -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js "></script>
<script src="./ lib/form-serialize.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js"></script>
<!--核心逻辑-->
<script src="./js/index.js"></script>
</body>
</html>
- 总结:
- 掌握第三方的样式、图标库的引入与使用
增删改查,都通过单独的./js/index.js
来执行
本节渲染列表
目标
先分析思路:
- 待网页加载运行后,会 获取 并 渲染 一次图书列表
- 在后续增、删、改,也就是有了变化以后,还是要重新 获取 并 渲染 一次图书列表
- 获取并渲染的这个业务,有多次使用,应将业务对应的代码,封装成函数复用
/*
*目标1:渲染图书列表
*1.1 获取数据
*1.2 渲染数据
*/
//定义一个全局的常量,操作者的这个常量,提到最外边
const creator = '老张'
//一、先封装-获取并渲染图书列表函数
function getBooksList() {
// 三、
// 1.1 获取数据
axios({
url: 'http://hmajax.itheima.net/api/books',
params: {
//外号:获取对应数据
//creator:'老张'
//属性与变量同名,简写
creator
}
})
.then(result =>{
console.1og(result)
// 定一个列表接收
const bookList = result.data.data
console.log(bookList)
// 1.2渲染数据(浏览器调试,右键,以HTML格式修改,复制过来)将对象映射过来,需要熟悉map这个映射函数
const htmlStr = bookList.map((item,index) =>{
return
`<tr>
<td>${ index + 1 }</td>
<td>${ item.bookname }</td>
<td>${ item.author }</td>
<td>${ item.publisher }</td>
<td>
<span class="del">删除</span>
<span class="edit">编辑</span>
</td>
</tr>`
})
// 数组转字符串
.join('')
// 打印确认
console.log (htmlStr)
//通过浏览器调试,确认<tr>都是放到<tbody>中,因此通过类名获取
document.querySelector('.list').innerHTML = htmlStr
})
}
//二、网页刚打开,就默认加载运行,获取并渲染列表一次
getBooksList()
获取并渲染列表总结:
- 通过axios,以及接口文档信息,先将数据获取回来
- 提取出来两个公共业务逻辑:操作者、获取并渲染列表的函数
- 先渲染DOM大结构,再去修改模板字符串
新增图书
先理清业务思路:
- 确认新增图书的弹框显示(点击新值后弹出,通过bootstrap弹框)
- 点击X、取消、保存,当前弹框隐藏
- 点击保存按钮时,收集用户输入的表单数据,提交给服务器保存
- 重新获取并渲染图书列表
/*
*目标1:渲染图书列表
*1.1 获取数据
*1.2 渲染数据
*/
//定义一个全局的常量,操作者的这个常量,提到最外边
const creator = '老张'
//一、先封装-获取并渲染图书列表函数
function getBooksList() {
// 三、
// 1.1 获取数据
axios({
url: 'http://hmajax.itheima.net/api/books',
params: {
//外号:获取对应数据
//creator:'老张'
//属性与变量同名,简写
creator
}
})
.then(result =>{
console.1og(result)
// 定一个列表接收
const bookList = result.data.data
console.log(bookList)
// 1.2渲染数据(浏览器调试,右键,以HTML格式修改,复制过来)将对象映射过来,需要熟悉map这个映射函数
const htmlStr = bookList.map((item,index) =>{
return
`<tr>
<td>${ index + 1 }</td>
<td>${ item.bookname }/td>
<td>${ item.author }/td>
<td>${ item.publisher }</td>
<td>
<span class="del">删除</span>
<span class="edit">编辑</span>
</td>
</tr>`
})
// 数组转字符串
.join('')
// 打印确认
console.log (htmlStr)
//通过浏览器调试,确认<tr>都是放到<tbody>中,因此通过类名获取
document.querySelector('.list').innerHTML = htmlStr
})
}
//二、网页刚打开,就默认加载运行,获取并渲染列表一次
//预先封装的获取并渲染列表的函数
getBooksList()
/**
* 目标2:新增图书
* 2.1 新增弹框->显示和隐藏
* 2.2 收集表单数据,并提交到服务器保存
* 2.3 刷新图书列表
*/
// 2.1 创建弹框对象(点击添加按钮时,通过属性控制)
const addModalDom = document.querySelector('.add-modal')
const addModal = new bootstrap.Modal(addModalDom)
// 保存按钮->绑定点击事件->隐藏弹框
document.querySelector('.add-btn').addEventListener('click', () => {
// 2.2 收集表单数据,并提交到服务器保存(点击保存按钮,需要使用JS控制)
const addForm = document.querySelector('.add-form')
// 定义一个接收获取图书对象的变量,通过插件的serialize函数收集,第一个参数,传入收集的哪个表单对象,即form class
const bookObj = serialize(addForm, { hash: true, empty: true })
// 通过打印确认是否能拿到 图书对象
// console.log(bookObj)
// 通过axios,收集、提交到服务器
axios({
url: 'http://hmajax.itheima.net/api/books',
// 请求方式
method: 'POST',
data: {
// 请求体传参,是个对象,传递4个参数
// 3个原有的图书对象,展开形成一个新对象,展开语法...
...bookObj,
// 再补充一个
creator
}
})
//回调函数 ,添加后复原重置表单和隐藏弹框都放到回调函数里面
.then(result => {
// 打印结果确认下
// console.log(result)
// 2.3 添加成功后,重新请求并渲染图书列表
getBooksList()
// 重置表单
addForm.reset()
// 隐藏弹框
addModal.hide()
})
})
删除图书
先理清业务思路:
- 确认删除图书的按钮元素,绑定点击事件(对应元素是动态添加的,因此需要委托给父级)
- 确认删除按钮,绑定对应要删除的图书ID,通过ID来确认需要删除的是哪一条数据
- 调用删除接口,将图书ID提交给服务器的删除接口,在服务器端实现删除的业务逻辑
- 删除成功后,从服务器重新获取列表,重新渲染一次,在前端渲染最新的数据
/*
*目标1:渲染图书列表
*1.1 获取数据
*1.2 渲染数据
*/
//定义一个全局的常量,操作者的这个常量,提到最外边
const creator = '老张'
//一、先封装-获取并渲染图书列表函数
function getBooksList() {
// 三、
// 1.1 获取数据
axios({
url: 'http://hmajax.itheima.net/api/books',
params: {
//外号:获取对应数据
//creator:'老张'
//属性与变量同名,简写
creator
}
})
.then(result =>{
console.1og(result)
// 定一个列表接收
const bookList = result.data.data
console.log(bookList)
// 1.2渲染数据(浏览器调试,右键,以HTML格式修改,复制过来)将对象映射过来,需要熟悉map这个映射函数
const htmlStr = bookList.map((item,index) =>{
return
`<tr>
<td>${ index + 1 }</td>
<td>${ item.bookname }/td>
<td>${ item.author }/td>
<td>${ item.publisher }</td>
<td data-id=${item.id}>
<span class="del">删除</span>
<span class="edit">编辑</span>
</td>
</tr>`
})
// 数组转字符串
.join('')
// 打印确认
console.log (htmlStr)
//通过浏览器调试,确认<tr>都是放到<tbody>中,因此通过类名获取
document.querySelector('.list').innerHTML = htmlStr
})
}
//二、网页刚打开,就默认加载运行,获取并渲染列表一次
//预先封装的获取并渲染列表的函数
getBooksList()
/**
* 目标2:新增图书
* 2.1 新增弹框->显示和隐藏
* 2.2 收集表单数据,并提交到服务器保存
* 2.3 刷新图书列表
*/
// 2.1 创建弹框对象(点击添加按钮时,通过属性控制)
const addModalDom = document.querySelector('.add-modal')
const addModal = new bootstrap.Modal(addModalDom)
// 保存按钮->绑定点击事件->隐藏弹框
document.querySelector('.add-btn').addEventListener('click', () => {
// 2.2 收集表单数据,并提交到服务器保存(点击保存按钮,需要使用JS控制)
const addForm = document.querySelector('.add-form')
// 定义一个接收获取图书对象的变量,通过插件的serialize函数收集,第一个参数,传入收集的哪个表单对象,即form class
const bookObj = serialize(addForm, { hash: true, empty: true })
// 通过打印确认是否能拿到 图书对象
// console.log(bookObj)
// 通过axios,收集、提交到服务器
axios({
url: 'http://hmajax.itheima.net/api/books',
// 请求方式
method: 'POST',
data: {
// 请求体传参,是个对象,传递4个参数
// 3个原有的图书对象,展开形成一个新对象,展开语法...
...bookObj,
// 再补充一个
creator
}
})
//回调函数 ,添加后复原重置表单和隐藏弹框都放到回调函数里面
.then(result => {
// 打印结果确认下
// console.log(result)
// 2.3 添加成功后,重新请求并渲染图书列表
getBooksList()
// 重置表单
addForm.reset()
// 隐藏弹框
addModal.hide()
})
})
/**
* 目标3:删除图书
* 3.1 删除元素绑定点击事件->获取图书id
* 3.2 调用删除接口
* 3.3 刷新图书列表
*/
// 3.1 删除元素->点击(事件委托)(对应元素是动态添加的,因此需要委托给父级tbody)
document.querySelector('.list').addEventListener('click', e => {
// 获取触发事件目标元素
// console.log(e.target)
// 判断点击的是删除元素(通过e.target,即 事件对象.target 找到真正触发事件的目标元素,来获取)
if (e.target.classList.contains('del')) {
// console.log('点击删除元素')
// 获取图书id(自定义属性id)(同步在上面css,给标签添加一个自定义属性ID)
// 通过parentNode拿到父级元素,通过dataset拿到id值
const theId = e.target.parentNode.dataset.id
// console.log(theId)
// 3.2 调用删除接口,此为 路径传参 方法,让服务器删除
axios({
url: `http://hmajax.itheima.net/api/books/${theId}`,
method: 'DELETE'
}).then(() => {
// 3.3 刷新图书列表,注意,不要写到.then外面了,因为axios是异步调用的,会导致页面刷新失败
getBooksList()
})
}
})
编辑图书
先理清业务思路:
- 编辑业务与新增类似,点击后出现编辑弹框,需要弹框的显示隐藏
- 编辑前,会有已存在内容,需要获取对应图书ID,从服务器查询最新的信息回填表单
- 点击修改时,表单的数据提交到服务器保存
- 保存成功后,从服务器重新获取列表,重新渲染一次,在前端页面上渲染最新的数据
/*
*目标1:渲染图书列表
*1.1 获取数据
*1.2 渲染数据
*/
//定义一个全局的常量,操作者的这个常量,提到最外边
const creator = '老张'
//一、先封装-获取并渲染图书列表函数
function getBooksList() {
// 三、
// 1.1 获取数据
axios({
url: 'http://hmajax.itheima.net/api/books',
params: {
//外号:获取对应数据
//creator:'老张'
//属性与变量同名,简写
creator
}
})
.then(result =>{
console.1og(result)
// 定一个列表接收
const bookList = result.data.data
console.log(bookList)
// 1.2渲染数据(浏览器调试,右键,以HTML格式修改,复制过来)将对象映射过来,需要熟悉map这个映射函数
const htmlStr = bookList.map((item,index) =>{
return
`<tr>
<td>${ index + 1 }</td>
<td>${ item.bookname }/td>
<td>${ item.author }/td>
<td>${ item.publisher }</td>
<td data-id=${item.id}>
<span class="del">删除</span>
<span class="edit">编辑</span>
</td>
</tr>`
})
// 数组转字符串
.join('')
// 打印确认
console.log (htmlStr)
//通过浏览器调试,确认<tr>都是放到<tbody>中,因此通过类名获取
document.querySelector('.list').innerHTML = htmlStr
})
}
//二、网页刚打开,就默认加载运行,获取并渲染列表一次
//预先封装的获取并渲染列表的函数
getBooksList()
/**
* 目标2:新增图书
* 2.1 新增弹框->显示和隐藏
* 2.2 收集表单数据,并提交到服务器保存
* 2.3 刷新图书列表
*/
// 2.1 创建弹框对象(点击添加按钮时,通过属性控制)
const addModalDom = document.querySelector('.add-modal')
const addModal = new bootstrap.Modal(addModalDom)
// 保存按钮->绑定点击事件->隐藏弹框
document.querySelector('.add-btn').addEventListener('click', () => {
// 2.2 收集表单数据,并提交到服务器保存(点击保存按钮,需要使用JS控制)
const addForm = document.querySelector('.add-form')
// 定义一个接收获取图书对象的变量,通过插件的serialize函数收集,第一个参数,传入收集的哪个表单对象,即form class
const bookObj = serialize(addForm, { hash: true, empty: true })
// 通过打印确认是否能拿到 图书对象
// console.log(bookObj)
// 通过axios,收集、提交到服务器
axios({
url: 'http://hmajax.itheima.net/api/books',
// 请求方式
method: 'POST',
data: {
// 请求体传参,是个对象,传递4个参数
// 3个原有的图书对象,展开形成一个新对象,展开语法...
...bookObj,
// 再补充一个
creator
}
})
//回调函数 ,添加后复原重置表单和隐藏弹框都放到回调函数里面
.then(result => {
// 打印结果确认下
// console.log(result)
// 2.3 添加成功后,重新请求并渲染图书列表
getBooksList()
// 重置表单
addForm.reset()
// 隐藏弹框
addModal.hide()
})
})
/**
* 目标3:删除图书
* 3.1 删除元素绑定点击事件->获取图书id
* 3.2 调用删除接口
* 3.3 刷新图书列表
*/
// 3.1 删除元素->点击(事件委托)(对应元素是动态添加的,因此需要委托给父级tbody)
document.querySelector('.list').addEventListener('click', e => {
// 获取触发事件目标元素
// console.log(e.target)
// 判断点击的是删除元素(通过e.target,即 事件对象.target 找到真正触发事件的目标元素,来获取)
if (e.target.classList.contains('del')) {
// console.log('点击删除元素')
// 获取图书id(自定义属性id)(同步在上面css,给标签添加一个自定义属性ID)
// 通过parentNode拿到父级元素,通过dataset拿到id值
const theId = e.target.parentNode.dataset.id
// console.log(theId)
// 3.2 调用删除接口,此为 路径传参 方法,让服务器删除
axios({
url: `http://hmajax.itheima.net/api/books/${theId}`,
method: 'DELETE'
}).then(() => {
// 3.3 刷新图书列表,注意,不要写到.then外面了,因为axios是异步调用的,会导致页面刷新失败
getBooksList()
})
}
})
/**
* 目标4:编辑图书
* 4.1 编辑弹框->显示和隐藏
* 4.2 获取当前编辑图书数据->回显到编辑表单中
* 4.3 提交保存修改,并刷新列表
*/
// 4.1 编辑弹框->显示和隐藏(弹出出现前,需要回填表单数据,因此使用JS)
const editDom = document.querySelector('.edit-modal')
const editModal = new bootstrap.Modal(editDom)
// 编辑元素->点击->弹框显示
document.querySelector('.list').addEventListener('click', e => {
// 判断点击的是否为编辑元素
if (e.target.classList.contains('edit')) {
// 4.2 获取当前编辑图书数据->回显到编辑表单中
// e.target.parentNode.dataset.id 即目标>父级节点>自定义属性>ID
const theId = e.target.parentNode.dataset.id
axios({
// 获取时,get默认不写,ID写在URL上,因此没有其他的了
url: `http://hmajax.itheima.net/api/books/${theId}`
})
.then(result => {
// console.log(result) 打印确认下result对应数据
const bookObj = result.data.data
// 先获取到这个需要预填写的书名输入框,然后将数据赋予输入框
// document.querySelector('.edit-form .bookname').value = bookObj.bookname
// 再获取到这个需要预填写的作者输入框,然后将数据赋予输入框
// document.querySelector('.edit-form .author').value = bookObj.author
// 优化:3个/多个要素综合
// 实际开发中,从服务器获取到的数据属性名,和表单框的类名是一致(开发中需要弄成一致)
// 数据对象“属性”和标签“类名”一致
// 遍历数据对象,使用属性去获取对应的标签,快速赋值
// 如何遍历数据对象的属性?先取出来形成一个数组,使用Object.keys方法将数组传进去
const keys = Object.keys(bookObj) // ['id', 'bookname', 'author', 'publisher']
// 遍历
keys.forEach(key => {
// 获取标签的代码,嵌入遍历的模板字符串,数据由bookObj对象中取,通过bookObj[key]属性表达式赋予
document.querySelector(`.edit-form .${key}`).value = bookObj[key]
})
})
editModal.show()
}
})
// 修改按钮->点击->隐藏弹框(点击需要收集数据+隐藏,因此使用JS)
document.querySelector('.edit-btn').addEventListener('click', () => {
// 4.3 提交保存修改,并刷新列表
const editForm = document.querySelector('.edit-form')
// const { bookObj } = serialize(editForm, { hash: true, empty: true})
// 直接解构,对象的值直接映射到属性同名的变量中,目的是为了在data请求体参数里面,不用一个一个 bookObj.xxx
const { id, bookname, author, publisher } = serialize(editForm, { hash: true, empty: true})
// 保存正在编辑的图书id,隐藏起来:无需让用户修改
// 修改时需要携带这个服务器提供的图书的ID,为了确认正在编辑保存的这个图书
// <input type="hidden" class="id" name="id" value="84783">
axios({
url: `http://hmajax.itheima.net/api/books/${id}`,
method: 'PUT',
data: {
//bookname:bookObj.bookname
bookname,
author,
publisher,
creator
}
})
.then(() => {
// 修改成功以后,重新获取并刷新列表
getBooksList()
// 隐藏弹框
editModal.hide()
})
})
4.1&4.2总结:
- 先找到编辑对应的html&css弹框,通过JS方式控制显示和隐藏
- 点击编辑时,找到对应的图书ID,查询原ID包含的详细数据
- 通过遍历对象的属性,通过属性和类名的对应关系,快速对应赋值
4.3总结:
- 收集表单数据,注意隐藏的ID表单域,用于保存正在编辑的ID
- 插件获取后,再提交服务器保存
图书管理案例总结
获取&渲染
- 基于axios从服务器拿数据
- 分析数据结构与标签的对应关系,使用JS代码,将标签结构循环出来,插入页面中,将数据渲染填入对应标签中
新增
- 准备新增的表单,给弹框表单绑定点击事件
- 通过插件收集信息,并提交服务器保存
- 重新从服务器获取数据,刷新列表
删除
- 给删除元素绑定点击事件
- 获取对应ID以确认删除哪个对象
- 调用删除接口让服务器删除
- 与新增类似,只要服务器数据发生变化,就重新获取,刷新列表
编辑
- 准备编辑表单
- 确认表单区别(与新增的区别),将正在编辑的数据详情,获取并预先回填表单
- 回写时注意通过对象属性,与标签类名的对应关系,通过遍历对象属性,通过属性获取表单元素,快速赋值
- 确认用户当前正在修改的点击事件,给修改按钮绑定点击事件,收集内容并提交给服务器修改接口
- 与新增类似,只要服务器数据发生变化,就重新获取,刷新列表
通过图片上传案例 认识FormData表单数据对象
通过图片上传案例,搭配axios使用,来认识FormData表单数据对象
实现流程:
- 获取图片文件对象
- 图片文件对象提交到服务器
- 通过接口文档确认服务器的接收方式
- 请求的URL、方法post
- 请求参数body请求体
- 使用 FormData 表单数据方法携带图片文件(不再使用json字符串,因为是传文件,文件不能直接转json字符串)
- 当遇到上传文件的需求时,一般使用浏览器内置的FormData构造函数,来携带文件
- 提交表单数据到服务器,使用图片url网址
- 服务器找到文件保存起来
- 服务器返回图片url网址
- 浏览器通过img标签解析图片url网址
FormData语法案例
新建一个new FormData()数据对象,其内置了一个.append方法,往表单数据对象中传接口文档的参数名&值(接口文档要求:参数名:图片类型;值:文件)
const fd = new FormData()
fd.append(参数名,值)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片上传</title>
</head>
<body>
<!-- 文件选择元素 -->
<input type="file" class="upload">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/*
*目标:图片上传,显示到网页上
*1、获取图片文件
*2.使用 FormData携带图片文件
*3.提交到服务器,获取图片url网址使用
*/
// 文件选择元素->绑定change改变事件
document.querySelector('.upload').addEventListener( 'change', e => {
// 1.获取图片文件
// 使用input元素(type="file")供用户选择上传文件,
// 使用change监听事件,
// 通过e.target获取表单元素,实际上文件是上传到了这个表单元素中
// 打印console.log(e.target)可以看到input元素(type="file" clsaa="upload")
// 打印console.log(e.target.files)可以看到文件列表(filelist:{0:file,length:1})
// 通过files属性获得一个文件伪数组(取出文件对象)
console.log(e.target.files[0]) //打印文件对象:
// 2.使用FormData携带图片文件,new 一个FormData()表单数据的对象fd
const fd = new FormData()
// 到控制台查看本次请求,
// 点击请求,打开载荷,
// 图片文件浏览器进行了格式化(二进制)
// 使用表单数据方法
fd.append( 'img', e.target.files[0])
// 3.提交到服务器,获取图片url网址使用
axios({
// 根据接口文档在axios中传入数据:
url: 'http://hmajax.itheima.net/api/uploadimg',
method: 'POST',
// 传的不是一个对象,而是是表单数据,不用大括号
data: fd
})
.then(result =>{
console.log(result)
//取出图片url网址,用img标签加载显示
const imgUrl = result.data.data.url
// 看到服务器的响应,已返回图片地址,使用图片地址,展示图片
document.querySelector('.my-img').src = imgUrl
})
})
</script>
</body>
更换背景
图片上传的实际应用案例举例:更换网站/头像等背景图+持久化
思路:
- 将用户选择的图片传给服务器保存
- 将返回的图片地址,设置给body-background
- 网址保存在本地,第一次打开时从本地获取
分析:
- 选择图片上传,设置body背景
- 上传成功时,保存url网址
- 网页运行后,获取url网址使用
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>网站-更换背景</title>
<!--初始化样式-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css">
<!--核心样式-->
<link rel="stylesheet" href="./css/index.css"></head>
<body>
<!--部分代码没拿到-->
<div class="container">...</div>
<div class="nav">
<!--部分代码没拿到-->
<div class="left">...</div>
<div class="right">
<!-- 使用label标签(样式容易修改)的for属性关联input标签的id -->
<label for="bg">更换背景</label>
<input class="bg-ipt" type="file" id="bg">
</div>
</div>
<!--部分代码没拿到-->
<div class="search-container">...</div>
</div>
<script src="https: //cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<!--核心代码-->
<script src="./js/index.js"></script>
</body>
</html>
<body>
<script>
/**
* 目标:网站-更换背景
* 1. 选择图片上传,设置body背景
* 2. 上传成功时,"保存"图片url网址
* 3. 网页运行后,"获取"url网址使用
* */
// 使用label标签(样式容易修改)的for属性关联input标签的id
// label标签:扩大表单的交互范围,样式好改,利用<label for="bg">关联<input id="bg">,这样的话,点击label相当于点击在input关联表单
// 给文件选择器input,通过.bg-ipt类,绑定change事件
document.querySelector('.bg-ipt').addEventListener('change', e => {
// 1. 选择图片上传,设置body背景
// 拿到文件对象
console.log(e.target.files[0])
// 新建一个对象
const fd = new FormData()
fd.append('img', e.target.files[0])
axios({
url: 'http://hmajax.itheima.net/api/uploadimg',
method: 'POST',
data: fd
}).
then(result => {
// 确认后台返回的对象
// console.log(result)
// 通过浏览器调试,取出图片地址
const imgUrl = result.data.data.url
// 将图片地址赋予给标签
document.body.style.backgroundImage = `url(${imgUrl})`
// 2. 上传成功时,"保存"图片url网址到本地一份
localStorage.setItem('bgImg', imgUrl)
})
})
// 3. 网页运行后,"获取"url网址使用
// 取出背景url
const bgUrl = localStorage.getItem('bgImg')
// 先打印确认
console.log(bgUrl)
// 背景url && 将图片地址赋予给标签;逻辑与短路,判断空值时就不执行,设置过有值,就拿本地的url
bgUrl && (document.body.style.backgroundImage = `url(${bgUrl})`)
// 设置成功后进行本地存储,注意,每次打开页面时需要判断,
// 本地存有背景图地址才进行设置(使用逻辑与的短路特性):
// 但逻辑与的运算符优先级高于赋值运算符,需要添加小括号
</script>
</body>
案例:个人信息设置
案例包括:用户个人信息文字表单+头像图片表单
步骤:
- 已有信息渲染
- 头像修改
- 表单数据的提交和保存
- 结果提示框
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入了bootstrap的样式 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- 自由编写的核心样式,可去浏览器调试偷代码 -->
<link rel="stylesheet" href="./css/index.css">
<title>个人信息设置</title>
</head>
<body>
<!-- toast 提示框,页面标签,用到哪个就去调试复制路径拿 -->
<div class="toast my-toast" data-bs-delay="1500">
<div class="toast-body">
<div class="alert alert-success info-box">
操作成功
</div>
</div>
</div>
<!-- 核心内容区域,页面标签,用到哪个就去调试复制路径拿 -->
<div class="container">
<ul class="my-nav">
<li class="active">基本设置</li>
<li>安全设置</li>
<li>账号绑定</li>
<li>新消息通知</li>
</ul>
<div class="content">
<div class="info-wrap">
<h3 class="title">基本设置</h3>
<form class="user-form" action="javascript:;">
<div class="form-item">
<label for="email">邮箱</label>
<input id="email" name="email" class="email" type="text" placeholder="请输入邮箱" autocomplete="off">
</div>
<div class="form-item">
<label for="nickname">昵称</label>
<input id="nickname" name="nickname" class="nickname" type="text" placeholder="请输入昵称" autocomplete="off">
</div>
<div class="form-item">
<label>性别</label>
<label class="male-label">
<input type="radio" name="gender" class="gender" value="0">男</label>
<label class="male-label">
<input type="radio" name="gender" class="gender" value="1">女</label>
</div>
<div class="form-item">
<label for="desc">个人简介</label>
<textarea id="desc" name="desc" class="desc" placeholder="请输入个人简介" cols="20" rows="10" autocomplete="off"></textarea>
</div>
<button class="submit">提交</button>
</form>
</div>
<div class="avatar-box">
<h4 class="avatar-title">头像</h3>
<!-- 赋予默认头像 -->
<img class="prew" src="./img/头像.png" alt="">
<!-- 通过浏览器调试检查,发现更换头像的按钮是使用的label标签:扩大表单的交互范围,实际上使用了父属性属性(即label),关联了input文件选择表单元素的id的值(都是upload) -->
<!-- 所以鼠标点击更换头像的标签,相当于点在了文件选择表单上 -->
<label for="upload">更换头像</label>
<input id="upload" type="file" class="upload">
</div>
</div>
</div>
<!-- 引入了axios、bootstrap、form-serialize,以及自己写的JS逻辑 -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
<script src="./lib/form-serialize.js"></script>
<!-- 核心逻辑 -->
<script src="./js/index.js"></script>
</body>
</html>
信息渲染
- 操作者的用户信息:需要给操作者起个外号,告诉服务器操作者是谁,并获取对应的用户信息
- 确认操作者后,先将已有信息获取,并通过JS将信息渲染
- 确认回写时需要单独处理的内容,包括
- 头像类名与标签对不上,但头像是需要给img的src赋值,需要拉出来单独赋值
- 性别复选框,需要添加checked属性=true才能选中,需要单独处理
头像修改
- 涉及图片上传业务
- 前端获取用户重新选择的,在本地的头像文件
- 提交到服务器保存,服务器返回图片url(准备一个表单数据对象,将头像文件装入携带,额外加携带一个操作者的信息)
- 获得新的url,替换原有img标签的src属性
信息修改
- 类似前面的编辑业务
- 先做回显
- 收集用户修改后的表单数据,提交服务器保存
提示框
- 通过bootstrap提供的.Toast类实现,只需要给div加这个类名就可以触发
- 由于提示框只即时反应操作结果,因此bootstrap内部让提示框自动隐藏,如要持续显示时长,data-bs-delay多少毫秒数后再隐藏
- 确认什么时候让提示框出现:提交信息+保存成功以后
- 通过JS方式控制显示
- 备注:bootstrap提供的.modal和toast最大的区别是,Toast类不需要点击,会自己隐藏
<body>
<script>
/**
* 目标1:信息渲染
* 1.1 获取用户的数据
* 1.2 回显数据到标签上
* */
// 获取个人数据后,定义一个全局的用户常量,然后下面params属性简写
const creator = '播仔'
// 1.1 获取用户的数据
axios({
url: 'http://hmajax.itheima.net/api/settings',
// 通过接口文档,我们确认,用户参数位置在query(查询参数)中,因此需要通过params去查询
params: {
creator
}
})
.then(result => {
// 先确认,从服务器接收的结果
// console.log(result)
// 从属性路径取出用户个人信息对象,定义一个常量userObj
const userObj = result.data.data
// 确认是否拿到用户信息对象
// console.log(userObj)
// 1.2 回显数据到标签上
// 表单的回显,遍历数据对象的每一个属性,用属性的英文单词,当作类名,找到对应的标签,给value属性快速赋值
// 用Object.keys方法遍历用户对象,返回属性单词
// 先通过调试确认
Object.keys(userObj).forEach(key => {
// 先判断
if (key === 'avatar') {
// 赋予默认头像,找到类名获取标签,然后给src赋值
document.querySelector('.prew').src = userObj[key]
} else if (key === 'gender') {
// 赋予默认性别
// 获取性别单选框:[男radio元素,女radio元素],定义一个常量gRadioList,通过css选择器class类名,querySelectorAll获取所有
const gRadioList = document.querySelectorAll('.gender')
// 获取性别数字:0男,1女,通过接口文档确认,0代表男1代表女
const gNum = userObj[key]
// 通过性别数字,作为下标,找到对应性别单选框,设置选中状态(此处为接口文档的规定恰巧与数组下标一致)
// console.log(gRadioList[gNum]) 先打印确认一下,是获取到了单选框
// 单选框添加checked = true,设置默认性别
gRadioList[gNum].checked = true
} else {
// 赋予默认内容
// 复选框对应类名赋予默认内容,通过.${key}类名获取表单,给value属性赋值,值来源于=用户对象中同名的属性
document.querySelector(`.${key}`).value = userObj[key]
}
})
})
/**
* 目标2:修改头像
* 2.1 获取头像文件
* 2.2 提交服务器并更新头像
* */
// 文件选择表单元素->change事件
// 通过浏览器调试检查,发现更换头像的按钮是使用的label标签:扩大表单的交互范围,实际上使用了父属性属性(即label),关联了input文件选择表单元素的id的值(都是upload)
// 给文件表单元素,绑定change属性
document.querySelector('.upload').addEventListener('change', e => {
// 2.1 获取头像文件
// 通过打印确定了,拿到了头像文件的文件对象
console.log(e.target.files[0])
// 上传头像是表单数据对象,根据修改个人头像的接口文档,先准备
// 有两对参数
const fd = new FormData()
fd.append('avatar', e.target.files[0])
fd.append('creator', creator) //简写,全局用户常量
// 2.2 提交服务器并更新头像
axios({
url: 'http://hmajax.itheima.net/api/avatar',
// 方法 put 更新
method: 'PUT',
data: fd
})
.then(result => {
// console.log(result)
// 打印确认图片的地址从服务器正常返回后,取出图片的地址,赋值给一个变量
const imgUrl = result.data.data.avatar
// 把新的头像回显到页面上
document.querySelector('.prew').src = imgUrl
})
})
/**
* 目标3:提交表单
* 3.1 收集表单信息
* 3.2 提交到服务器保存
*/
/**
* 目标4:结果提示
* 4.1 创建toast对象
* 4.2 调用show方法->显示提示框
*/
// 保存修改按钮->绑定点击事件,给一个处理函数
document.querySelector('.submit').addEventListener('click', () => {
// 3.1 收集表单信息
// 一次性获取表单数据,通过form-serialize插件收集,需要两个条件,需要表单元素有name属性值,需要在表单范围内
// 表单对象声明一个常量获取标签
const userForm = document.querySelector('.user-form')
// 定义常量接收插件收集的用户对象
const userObj = serialize(userForm, { hash: true, empty: true })
// 打印确认收集到的用户对象,通过浏览器调试---控制台,点击提交按钮时,打印表单数据,就是收集到了
// console.log(userObj)
// 往对象追加一个属性
userObj.creator = creator
// 性别数字字符串,转成数字类型,再赋值回去,符合接口规定才能后续提交
userObj.gender = +userObj.gender
console.log(userObj)
// 3.2 提交到服务器保存
axios({
url: 'http://hmajax.itheima.net/api/settings',
method: 'PUT',
// 请求体传参,值是一个js对象,axios会自动转json字符串
data: userObj
})
.then(result => {
// 4.1 创建toast对象
// 确认提示框盒子
const toastDom = document.querySelector('.my-toast')
// 创建提示框对象,通过js控制
const toast = new bootstrap.Toast(toastDom)
// 4.2 调用show方法->显示提示框
toast.show()
})
})
</script>
</body>