导航菜单的数据会以 JSON 的方式存在 options
表中 key
为 nav_menus
的 value
中。
一般系统的
options
表的结构就是键值结构,也就是必然会有两个列,一个是key
,另一个是value
,这种结构比较灵活
在页面加载过后,根据配置选项的键 nav_menus
获取对应的数据(JSON):
1$(function () {
2 /**
3 * 显示消息
4 * @param {String} msg 消息文本
5 */
6 function notify (msg) {
7 $('.alert').text(msg).fadeIn()
8 // 3000 ms 后隐藏
9 setTimeout(function () {
10 $('.alert').fadeOut()
11 }, 3000)
12 }
13
14 /**
15 * 加载导航菜单数据
16 */
17 function loadData () {
18 $.get('/admin/options.php', { key: 'nav_menus' }, function (res) {
19 if (!res.success) {
20 // 失败,提示
21 return notify(res.message)
22 }
23
24 var menus = []
25
26 try {
27 // 尝试以 JSON 方式解析响应内容
28 menus = JSON.parse(res.data)
29 } catch (e) {
30 notify('获取数据失败')
31 }
32
33 // 使用 jsrender 渲染数据到表格
34 $('tbody').html($('#menu_tmpl').render(menus))
35 })
36 }
37
38 // 首次加载数据
39 loadData()
40})
🚩 源代码: step-76
--
思路:在点击保存按钮时,先获取全部导航菜单的数据,然后将界面上填写的数据 push 进去,然后再序列化为一个 JSON 字符串,通过 AJAX 发送到服务端保存。
名词解释:
- 将一个对象转换为一个 JSON 字符串的过程叫做序列化;
- 同理将一个 JSON 字符串转换为一个对象的过程叫做反序列化;
作为当前的情况,我们可以有两种方式获取当前导航菜单数据:
提问:哪一种方式跟合适?
发送异步请求:
之前我们已经定义了一个加载数据的 loadData
函数,但是在这里不能共用,因为在这个函数中拿到数据过后就渲染到界面上了,而我们这里是需要这个数据做后续逻辑。
如果需要公用,则需要改造这个函数,让其返回数据,而不是使用数据。
函数的粒度问题 函数的粒度指的是在同一个函数中业务的数量。
- 粒度越细,公用性越好
- 粒度越粗,调用越方便,性能大多数越好。
返回数据的方式:
如果是一个普通情况下的函数数据返回,直接使用 return
即可,但是此处我们的数据是需要 AJAX 过后才能拿到的,不能使用简单的 return
返回,即异步编程最常见的问题,必须使用回调(委托)解决。
重新封装 loadData()
:
xxxxxxxxxx
231/**
2 * 加载导航菜单数据
3 * @param {Function} callback 获取到数据后续的逻辑
4 */
5function loadData (callback) {
6 $.get('/admin/options.php', { key: 'nav_menus' }, function (res) {
7 if (!res.success) {
8 // 失败,提示
9 return callback(new Error(res.message))
10 }
11
12 var menus = []
13
14 try {
15 // 尝试以 JSON 方式解析响应内容
16 menus = JSON.parse(res.data)
17 } catch (e) {
18 callback(new Error('获取数据失败'))
19 }
20
21 callback(null, menus)
22 })
23}
首次加载数据时:
xxxxxxxxxx
61// 首次加载数据
2loadData(function (err, data) {
3 if (err) return notify(err.message)
4 // 使用 jsrender 渲染数据到表格
5 $('tbody').html($('#menu_tmpl').render(data))
6})
🚩 源代码: step-77
xxxxxxxxxx
141/**
2 * 新增逻辑
3 */
4$('.btn-save').on('click', function () {
5 // 获取当前的菜单数据
6 loadData(function (err, data) {
7 if (err) return notify(err.message)
8
9 console.log(data)
10 })
11
12 // 阻止默认事件
13 return false
14})
封装保存数据函数:
xxxxxxxxxx
151/**
2 * 保存导航菜单数据
3 * @param {Array} data 需要保存的数据
4 * @param {Function} callback 保存后需要执行的逻辑
5 */
6function saveData (data, callback) {
7 $.post('/admin/options.php', { key: 'nav_menus', value: JSON.stringify(data) }, function (res) {
8 if (!res.success) {
9 return callback(new Error(res.message))
10 }
11
12 // 成功
13 callback(null)
14 })
15}
实现保存逻辑:
xxxxxxxxxx
461/**
2 * 新增逻辑
3 */
4$('.btn-save').on('click', function () {
5 var menu = {
6 icon: $('#icon').val(),
7 text: $('#text').val(),
8 title: $('#title').val(),
9 link: $('#link').val()
10 }
11
12 // 数据校验
13 for (var key in menu) {
14 if (menu[key]) continue
15 notify('完整填写表单')
16 return false
17 }
18
19 // 获取当前的菜单数据
20 loadData(function (err, data) {
21 if (err) return notify(err.message)
22
23 // 将界面上的数据追加到已有数据中
24 data.push(menu)
25
26 // 保存数据到服务端
27 saveData(data, function (err) {
28 if (err) return notify(err.message)
29 // 再次加载
30 loadData(function (err, data) {
31 if (err) return notify(err.message)
32 // 使用 jsrender 渲染数据到表格
33 $('tbody').html($('#menu_tmpl').render(data))
34
35 // 清空表单
36 $('#icon').val('')
37 $('#text').val('')
38 $('#title').val('')
39 $('#link').val('')
40 })
41 })
42 })
43
44 // 阻止默认事件
45 return false
46})
将模板中每一个删除按钮调整为:
xxxxxxxxxx
11<a class="btn btn-danger btn-xs btn-delete" href="javascript:;" data-index="{{: #index }}">删除</a>
为所有 btn-delete
添加点击事件:
xxxxxxxxxx
41// 删除按钮是后续创建的所以不能直接绑定事件,这里使用委托事件
2$('tbody').on('click', '.btn-delete', function () {
3 // TODO: ...
4})
思路也是获取已有数据,在已有数据中找到当前数据并移除
xxxxxxxxxx
231/**
2 * 删除指定数据
3 */
4$('tbody').on('click', '.btn-delete', function () {
5 var index = parseInt($(this).parent().parent().data('index'))
6
7 // 获取当前的菜单数据
8 loadData(function (err, data) {
9 if (err) return notify(err.message)
10
11 data.splice(index, 1)
12
13 // 保存数据到服务端
14 saveData(data, function (err) {
15 if (err) return notify(err.message)
16 // 再次加载
17 loadData(function (err, data) {
18 if (err) return notify(err.message)
19 $('tbody').html($('#menu_tmpl').render(data))
20 })
21 })
22 })
23})
思考:这样处理是否会有问题,为什么,如何解决