一个 to do list 网页应用

目标

  1. 单页面纯前端
  2. html5数据存储
  3. 定时提醒
  4. 自定义alert,不冻结其他

工具

  1. jQuery – 快捷操作js
  2. store.js – 简易使用数据存储
  3. browser-sync – 模拟服务器,浏览器同步更新
  4. datetimepicker – 时间选择插件

流程

结构框架

注意要点

  1. 响应式网页。代码编辑工具可以快速创建页面代码,自动添加窗口视图的要素,页面渲染的模式。
  2. 结构需要在最初规划好,后面结构减少变动。

代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>The To Do List</title>
<link rel="stylesheet" href="./node_modules/normalize.css/normalize.css">
<link rel="stylesheet" href="./node_modules/jquery-datetimepicker/build/jquery.datetimepicker.min.css">
<link rel="stylesheet" href="./css/base.css">
</head>
<body>
<div class="remind-msg">
<span></span>
<button>I know</button>
</div>
<div class="container">
<h1>My To Do List</h1>
<div class="task-add">
<input name="input-content" type="text" placeholder="e.g. Today, first event." autocomplete="off" autofocus="true">
<button>Submit</button>
</div>
<div class="task-list">
</div>
<div class="task-detail-mask"></div>
<div class="task-detail">
</div>
</div>
<video class="remind-music" src="./alert.mp3"></video>
<script src="./node_modules/jquery/dist/jquery.js"></script>
<script src="./js/store.min.js"></script>
<script src="./node_modules/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js"></script>
<script src="./js/base.js"></script>
</body>
</html>

样式渲染

注意要点

  1. 跨浏览器页面初始化。可以使用reset.css,建议使用normalize.css。
  2. css3的元素处于过渡阶段。如果要兼容旧浏览器,可以使用autoprefixer,如果不考虑,就不要添加前缀。新的浏览器如果要实验新的css元素,需要在浏览器设置,以后推出的css元素不需要再加前缀。

代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/* ¹«¹²²¿·Ö */
*{
/*background-color: rgba(0,0,0,.1);*/
box-sizing: border-box;
transition: all .2s;
outline-color: #953535;
}
.clx{
*zoom: 1;
}
.clx:after{
content: "";
display: table;
clear: both;
}
.flr{
float: right;
}
body{
background-color: #ADB1F0;
color: #fff;
}
/* Ö÷Ì岿·Ö */
.remind-msg{
display: none;
padding: 10px;
text-align: center;
background-color: #65E468;
}
.container{
position: relative;
max-width: 700px;
margin: 0 auto;
padding: 0 10px;
}
h1{
text-align: center;
}
input,.task-item,button,.task-detail,textarea{
padding: 10px;
border-radius: 3px;
}
textarea,input[type=text],input[type=date],button{
border: 0;
}
textarea,input[type=text],input[type=date]{
display: block;
width: 100%;
background-color: #eee;
box-shadow: inset 0 1px 2px rgba(0,0,0,.4);
}
input[type=text]:hover,input[type=date]:hover,input[type=text]:focus,input[type=date]:focus{
background-color: #fff;
box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
}
button{
display: inline-block;
cursor: pointer;
}
input[type=submit]{
color: #333;
}
.task-list{
margin: 5px 0;
}
.task-add input[type=text]{
float: left;
width: 84%;
margin-right: 1%;
}
.task-add button,.task-item{
box-shadow: 0 2px 3px rgba(0,0,0,.4);
}
.task-add button{
width: 15%;
border: 0;
background-color: #6F73B3;
}
.task-add button:hover{
background-color: #3E4289;
}
.task-item{
margin-bottom: 2px;
background-color: #fff;
color: #333;
cursor: pointer;
}
.task-item:hover{
background-color: #ddd;
}
.task-item-completed{
color: #aaa;
text-decoration: line-through;
opacity: .7;
}
.task-content{
margin-left: 10px;
}
.action{
color: #888;
font-size: 80%;
}
.action:hover{
color: #333;
}
.task-detail,.task-detail-mask{
display: none;
position: absolute;
}
.task-detail{
top: 0;
right: 0;
width: 50%;
/*min-height: 230px;*/
height: 100%;
overflow: auto;
padding: 10px;
background-color: rgba(255, 255, 255, 0.4);
color: #333;
border-radius: 3px 0 0 3px;
}
.task-detail > *{
margin-bottom: 5px;
}
.task-detail textarea{
min-height: 100px;
}
.remind input[type=text]{
margin-bottom: 5px;
}
.task-detail-mask{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(30,30,30,.6);
}
.remind-music{
opacity: 0;
}
/* ×Ô¶¨Òåalert */
.alert-box{
position: fixed;
width: 240px;
height: auto;
color: #555;
text-align: center;
background-color: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0,0,0,.5);
}
.alert-mask{
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: #000;
opacity: .5;
}
.alert-title{
padding: 5px 10px;
font-weight: 900;
font-size: 20px;
}
.alert-confirm{
margin-right: 5px;
}
.alert-cancel{
margin-bottom: 5px;
}

运行逻辑

注意要点

  1. 定时提醒功能,需要轮询状态,存储在localStorage 数据在定时器中,没有立刻更新,点击确认信息里将页面一并刷新,才得到最新数据状态。
  2. 自定义alert功能,需要处理回调函数,一开始无法知道那个时间进行,使用deferred对象来进行延时操作。

代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
;(function () {
'use strict';
var $form_task_add = $('.task-add'),
$complete_state,
$delete_buttons,
$detail_buttons,
$detail_task = $('.task-detail'),
$detail_task_mask = $('.task-detail-mask'),
task_list = {},
$remind_msg = $('.remind-msg'),
$remind_music = $('.remind-music');
init();
$form_task_add.find('button').on('click',listen_task_add);
$form_task_add.keydown(function (e) {
if (e.keyCode === 13) {
listen_task_add(e);
}
});
$detail_task_mask.click(listen_task_detail_off);
function init() {
task_list = store.get('task_list') || [];
// store.clear(); // 清空数据
// 检测数据存储,页面初始化
if (task_list.length) {
render_task_list()
}
// 检测数据存储中提醒时间
task_remind_time()
// 提示信息处理
$remind_msg.find('button').click(remind_msg_hide);
}
/*
* 自定义alert
*/
function todo_alert(argument) {
if (!argument) {
console.error('conf title is required')
}
var conf = {},
alert_box,
alert_mask,
alert_title,
alert_content,
alert_confirm,
alert_cancel,
defer,
is_confirm,
timer;
if (typeof argument == 'string') {
conf.title = argument
} else {
conf = $.extend(conf,argument)
}
alert_box = $('<div>' +
'<div><div class="alert-title">' + conf.title + '</div></div>' +
'<div><button class="alert-confirm">Confirm</button><button class="alert-cancel">Cancel</button></div>' +
'</div>').attr('class','alert-box')
alert_mask = $('<div>').attr('class','alert-mask')
alert_title = alert_box.find('.alert-title')
alert_content = alert_box.find('.alert-content')
alert_confirm = alert_box.find('.alert-confirm')
alert_cancel = alert_box.find('.alert-cancel')
defer = $.Deferred();
timer = setInterval(function () {
if (is_confirm !== undefined) {
defer.resolve(is_confirm) // 执行状态是"已完成"
clearInterval(timer)
dismiss_alert()
};
},50)
function dismiss_alert () {
alert_box.remove();
alert_mask.remove();
}
alert_confirm.click(function (e) {
is_confirm = true
})
alert_cancel.click(function (e) {
is_confirm = false
})
$(window).resize(function (e) {
adjust_position(alert_box)
});
$('body').append(alert_mask).append(alert_box); // alert填充到body
$(window).resize(); // 先触发一次重排
/*
* 防止执行状态被外部改变
* 返回promise对象,要想改变执行状态,只能操作原来的deferred对象
*/
return defer.promise();
}
// 元素动态居中
function adjust_position (element) {
var $window = $(window),
window_w = $window.width(),
window_h = $window.height(),
element_w = element.width(),
element_h = element.height(),
left,
top;
left = (window_w - element_w) / 2;
top = ((window_h - element_h) / 2) - 20; // 视觉上居中在测量上稍微上移
element.css({
left: left,
top: top
})
}
function task_remind_time() {
var current_time,
item = store.get('task_list'),
remind_time,
sub_time;
var check_timer = setInterval(function () {
for (var i = 0; i < task_list.length; i++) {
var _item = item[i];
if (!_item || !_item.date || _item.informed) {
continue
}
current_time = (new Date()).getTime();
remind_time = (new Date(_item.date)).getTime();
sub_time = current_time - remind_time;
if (sub_time >= 1) { // 等到达提醒时间后进行后续
updateTask(i,{informed: true});
remind_msg_show(_item.content);
}
}
},1000)
}
function remind_msg_show(content) {
if (!content) {
return
}
$remind_msg.find('span').html(content)
$remind_msg.fadeIn(200)
$remind_music.get(0).play()
}
function remind_msg_hide() {
$remind_msg.hide()
window.location.reload() // 页面刷新,数据更新
}
function listen_task_add(e) {
e.preventDefault();
// 空对象
var new_task = {};
var $input = $form_task_add.find('input[name=input-content]');
// 获得输入内容
new_task.content = $input.val();
if (!new_task.content) {
return
}
// 存储数据更新页面
if (addTask(new_task)) {
$input.val(null);
}
}
function addTask(new_task) {
task_list.push(new_task);
refresh_task_list();
return true // 触发后续事件
}
function listen_task_delete() {
$delete_buttons.on('click',function (e) {
var $this = $(this),
$item = $this.parent().parent(),
index = $item.data('index');
// confirm("Do you want to delete this item?") ? deleteTask(index) : null;
// 不冻结页面的alert
todo_alert("Do you want to delete this item?").then(function (result) {
result ? deleteTask(index) : null;
})
})
}
function deleteTask(index) {
// 数据库中没有相应索引的数据,索引从0开始
if (index === undefined || !task_list[index]) {
return
}
delete task_list[index];
refresh_task_list();
}
function listen_task_detail() {
var index;
// 双击item打开详情页
$('.task-item').dblclick(function (e) {
index = $(this).data('index');
detailTask(index);
})
// 单击button打开详情页
$detail_buttons.on('click',function (e) {
var $this = $(this),
$item = $this.parent().parent();
index = $item.data('index');
detailTask(index);
})
}
function detailTask(index) {
// 数据库中没有相应索引的数据,索引从0开始
if (index === undefined || !task_list[index]) {
return
}
render_task_detail(index);
$detail_task.fadeIn(500);
$detail_task_mask.show();
}
function updateTask(index,data) {
if (index === undefined || !task_list[index]) {
return
}
task_list[index] = $.extend({},task_list[index],data); // 扩展数据
refresh_task_list();
}
function listen_task_detail_off() {
$detail_task.fadeOut(500);
$detail_task_mask.hide();
}
function listen_task_completed() {
$complete_state.click(function (e) {
var $this = $(this),
index = $this.parent().parent().data('index'),
isCompleted = task_list[index].completed;
if (isCompleted) {
updateTask(index,{completed:false})
} else {
updateTask(index,{completed:true})
}
})
}
/*
* 更新数据存储内容
*/
function refresh_task_list() {
store.set('task_list',task_list); // 数据更新
render_task_list(); // 页面更新
}
/*
* 根据数据渲染列表模板
*/
function render_task_list() {
// 生成列表内容
var $task_list = $('.task-list');
$task_list.html(null); // 容器先清空
for (var i = 0; i < task_list.length; i++) {
var $task = render_task_item(task_list[i],i),
item = task_list[i];
// 容器填充内容,从前面填充,完成放后面
if (item && item.completed) {
$task.addClass('task-item-completed')
$task_list.append($task)
} else {
$task_list.prepend($task)
}
}
$delete_buttons = $('.action.delete'); // 获得列表中所有删除按钮
listen_task_delete(); // 监听删除事件,jquery手动绑定数据和页面的变化
$detail_buttons = $('.action.detail'); // 获得列表中所有详情按钮
listen_task_detail(); // 监听详情事件,jquery手动绑定数据和页面的变化
$complete_state = $task_list.find('.complete[type=checkbox]'); // 获得列表中所有选择框
listen_task_completed(); // 监听选择事件,jquery手动绑定数据和页面的变化
}
/*
* 取得单项模板结构
*/
function render_task_item(data,index) {
if (!data || !index) {
return
}
// 列表模板
var task_item_tpl = "<div class='task-item' data-index='" + index + "'>" +
"<span>" +
"<input type='checkbox' class='complete' " + (data.completed ? 'checked' : '') + ">" +
"</span>" +
"<span class='task-content'>" + data.content +
"</span>" +
"<span class='flr'>" +
"<span class='action delete'>&nbsp;Delete</span>" +
"<span class='action detail'>&nbsp;Detail</span>" +
"</span>" +
"</div>";
return $(task_item_tpl)
}
/*
* 渲染详情模板结构
*/
function render_task_detail(index) {
if (index === undefined || !task_list[index]) {
return
}
var item = task_list[index],
task_detail_tpl = "<form>" +
"<div class='content'>" + (item.content || "") +
"</div>" +
"<div>" +
"<input type='text' name='detail-content' value='" + (item.content || "") + "' style='display:none'></div>" +
"</div>" +
"<div>" +
"<div class='desc'>" +
"<textarea name='detail-desc'>" + (item.desc || "") + "</textarea>" +
"</div>" +
"</div>" +
"<div class='remind'>" +
"<label for='remind-date'>Remind time</label>" +
"<input type='text' name='remind-date' id='remind-date' value='" + (item.date ? item.date : '') + "'>" +
"</div>" +
"<div>" +
"<button type='submit'>Update</button>" +
"</div>" +
"</form>";
// 详情页先清空再填充内容
$detail_task.html(null);
$detail_task.html(task_detail_tpl);
// 获得详细页中元素
var $update_form = $detail_task.find("form"),
$detail_task_content = $update_form.find('.content'),
$detail_task_content_input = $update_form.find('input[name=detail-content]'),
$detail_task_desc = $update_form.find('textarea[name=detail-desc]'),
$detail_task_date = $update_form.find('input[name=remind-date]');
// 详情页提交数据
$update_form.submit(function (e) {
e.preventDefault(); // 阻止表单提交
var data = {};
data.content = $detail_task_content_input.val();
data.desc = $detail_task_desc.val();
data.date = $detail_task_date.val();
// 更新数据,关闭详情页
updateTask(index,data);
listen_task_detail_off();
})
// 详情页标题内容编辑
$detail_task_content.dblclick(function (e) {
$detail_task_content_input.show();
$(this).hide();
})
// 详情页提醒时间设置
$detail_task_date.datetimepicker();
}
})()