一.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div class="courses-wrap">
<input type="text" id="js-search-input" placeholder="搜索课程">
<div class="course-tab">
<ul class="course-tab-list js-course-tab-list clearfix">
<li class="tab-item">
<a href="javascript:;" class="course-tab-lk current" data-field="all">全部</a>
</li>
<li class="tab-item">
<a href="javascript:;" class="course-tab-lk" data-field="free">公益课程</a>
</li>
<li class="tab-item">
<a href="javascript:;" class="course-tab-lk" data-field="vip">vip课程</a>
</li>
</ul>
</div>
<!-- <div class="course-card-list-wrap">
</div> -->
<div class="course-card-list-wrap">
<ul class="course-card-list js-course-card-list"></ul>
</div>
</div>
<div id="js-course-data" style="display: none">
[{"id":
"1","course":"前端开发之企业级深度JavaScript特训课【JS++前端】","classes":"19","teacher":"小野","img":"ecmascript.jpg","is_free":"1","datetime":"1540454477","price":"0"},
{"id":
"2","course":"WEB前端工程师就业班之深度JSDOM+讲师辅导-第3期【JS++前端】","classes":"22","teacher":"小野","img":"dom.jpg","is_free":"0","datetime":"1540454477","price":"699"},
{"id":
"3","course":"前端开发之企业级深度HTML特训课【JS++前端】","classes":"3","teacher":"小野","img":"html.jpg","is_free":"1","datetime":"1540454477","price":"0"},
{"id":
"4","course":"前端开发之企业级深度CSS特训课【JS++前端】","classes":"5","teacher":"小野","img":"css.jpg","is_free":"1","datetime":"1540454477","price":"0"},
{"id":
"5","course":"前端就业班VueJS+去哪儿网+源码课+讲师辅导-第3期【JS++前端】","classes":"50","teacher":"哈默","img":"vuejs.jpg","is_free":"0","datetime":"1540454477","price":"1280"},
{"id":
"6","course":"前端就业班ReactJS+新闻头条实战+讲师辅导-第3期【JS++前端】","classes":"21","teacher":"托尼","img":"reactjs.jpg","is_free":"0","datetime":"1540454477","price":"2180"},
{"id":
"7","course":"WEB前端开发工程师就业班-直播/录播+就业辅导-第3期【JS++前端】","classes":"700","teacher":"JS++名师团","img":"jiuyeban.jpg","is_free":"0","datetime":"1540454477","price":"4980"}]
</div>
<script type="text/html" id="js-card-item-tpl">
<li class='card-item'>
<a href="javascript:;" class='img-lk'>
<img src="img/{{img}}" alt="">
</a>
<div class="item-status">
<span class="item-status-text">随到随学</span>
</div>
<h4 class="item-tt">
<!--经验:tt代表title -->
<a href="javascript:;" class='tt-lk'>{{courseName}}</a>
</h4>
<div class="item-line">
<span class='item-price {{isFree}}'>{{price}}</span>
<span class="item-info">{{hours}}课时</span>
</div>
</li>
</script>
<script src="js/1.js"></script>
</body>
</html>
二.js
var initCourseTab = (function (doc) {
var oCourseTabLks = doc.getElementsByClassName('course-tab-lk'),
oCourseCardList = doc.getElementsByClassName("js-course-card-list")[0],
courseData = JSON.parse(doc.getElementById('js-course-data').innerHTML),
cardItemTpl = doc.getElementById('js-card-item-tpl').innerHTML,
oSearchInput = doc.getElementById('js-search-input'); //3
oCourseTabLksLen = oCourseTabLks.length;
return {
searchCourse: function () {
// var val = this.value ;
var val = oSearchInput.value, // val等于输入的内容
len = val.length;
if (len > 0) {
var data = this.searchData(courseData, val);
if (data && data.length > 0) {
oCourseCardList.innerHTML = this.makeList(data);
} else {
oCourseCardList.innerHTML = this.showTip('没有搜索到相关课程');
}
} else {
this.restoreList();
}
},
showTip: function (text) {
return '<div><span>' + text + '</span></div>'
},
tabClick: function () {
var e = e || window.event,
tar = e.target || e.srcElement,
className = tar.className,
item;
if (className === 'course-tab-lk') { //让点击的出现current效果
var field = tar.getAttribute('data-field')
this.changeTabCurrent(tar);
oCourseCardList.innerHTML = this.makeList(this.filterData(field, courseData));
}
},
initCourseList: function () {
var data = this.filterData('all', courseData);
oCourseCardList.innerHTML = this.makeList(data);
},
makeList: function (data) {
var list = '';
data.forEach(function (elem) {
list += cardItemTpl.replace(/{{(.*?)}}/g, function (node, key) {
return {
img: elem.img,
courseName: elem.course,
isFree: elem.is_free === '1' ? 'free' : 'vip',
price: elem.is_free === '1' ? '免费' : ('¥' + elem.price + '.00'),
hours: elem.classes
}[key];
});
});
return list;
},
filterData: function (field, data) {
return data.filter(function (elem) {
switch (field) {
case 'all':
return true;
break;
case 'free':
return elem.is_free === '1';
break;
case 'vip':
return elem.is_free === '0';
break;
default:
return true;
}
})
},
restoreList: function () {
oCourseCardList.innerHTML = this.makeList(courseData);
this.changeTabCurrent(oCourseTabLks[0]);
},
changeTabCurrent: function (currentDom) {
for (var i = 0; i < oCourseTabLksLen; i++) {
item = oCourseTabLks[i];//oCourseTabLks为 上面3个的DOM名
item.className = 'course-tab-lk';
}
currentDom.className += ' current'
},
searchData: function (data, keyword) { // keyword就是val
return data.reduce(function (prev, elem) { //reduce返回一个新的数组,把符合条件的值放入新数组中
var res = elem.course.indexOf(keyword)
// indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。 如果没有找到匹配的字符串则返回 -1。
if (res !== -1) {
prev.push(elem);
}
return prev;
}, [])
}
};
})(document);
; (function (doc) {
var oSearchInput = doc.getElementById('js-search-input');
var oTabList = doc.getElementsByClassName('js-course-tab-list')[0];
var init = function () {
initCourseTab.initCourseList();
bindEvent();
}
function bindEvent() {
// oSearchInput.addEventListener('input', initCourseTab.searchCourse, false);
oSearchInput.addEventListener('input', initCourseTab.searchCourse.bind(initCourseTab), false), //5
oTabList.addEventListener('click', initCourseTab.tabClick.bind(initCourseTab), false);//第三个参数布尔值,默认值是false:事件在冒泡阶段执行,
// 而true:时间在捕获阶段执行
//从此this指向本身,bind改变this指向,和call等一样
}
init();
}(document));
最终效果展示:
JS基本逻辑步骤:
(1)
1.写模板 text/html
2.用一个主模板搭配一个子模板,这样更好维护: 主模板专门调用方法,而子模板专门细写方法,
如下图:
细节总结:
- 立即执行函数后面的括号内填入 实参document ,对应形参doc ,这样doc就能代替document 节省了时间
;(function(doc){...
}(document)
- addEventListener() 第一个参数event,第二个参数function, event,第二个参数function 第三个参数布尔值,默认值是false:事件在冒泡阶段执行, 而true:事件在捕获阶段执行
- 为了让子模块自动执行,设置几个函数调用:
初始化函数:init (init翻译为初始化)
绑定事件bindEvent(bindEvent翻译为绑定事件), 绑定事件里面设置 Click点击事件等,详细click函数在子模块里面写
最后写 init() , 让bindEvent等执行起来
(2) 细写方法:子模板
1.获取DOM元素事件
注意点:第3行获取JSON数据的话要在后面 加 innerHTML
2.设置click事件
注意点:return一个对象,所以后面写{}
3. 过滤
基本步骤为:获取属性、改变指向、填写方法
-
获取属性: 注意点:第19行的 tar.getAttribute('data-field'),能直接得出其存储的值(all、vip等)
-
改变this指向: 第64行更改了指向: 点击页面时,this指向子模块自己 bind改变this指向,和call等一样
指向自己的话就能拿函数内获取的数据去过滤
- 填写方法:
这样搞好过滤函数后,第25行就能输出JSON数据
4. 渲染模板
- 在html中额外填写展示块
<div class="course-card-list-wrap">
<ul class="course-card-list js-course-card-list"></ul>
</div>
之前为了迎合css写的ul、li样式 都要消除掉
- JS中获取元素
oCourseCardList = doc.getElementsByClassName("js-course-card-list"),
- 数据赋值
oCourseCardList.innerHTML = this.makeList(this.filterData(field, courseData));
- 数据渲染: makelist函数
makeList: function (data) {
var list = '';
data.forEach(function (elem) {
list += cardItemTpl.replace(/{{.*?}}/g, function(node,key){
return {
img:elem.img,
courseName:elem.course,
isFree: elem.is_free === '1' ? 'free' : 'vip',
price: elem.is_free === '1'? '免费' : ('¥' + elem.price + '.00'),
hours: elem.classes
}[key];
});
});
},
-
注意点: cardItemTpl是之前 text/html 的id名,
return 里面的键名是根据 tetx/html 里面写的 -
正则表达式经典写法模式:
list += cardItemTpl.replace(/{{.*?}}/g, function(node,key){
return{
}[key]
})
- 《span class='item-price {{isFree}} ' > {{price}} 》在css表达式中 改变免费和价格的颜色: 两个类名不需要打空格:
.course-card-list-wrap .card-item .item-line .item-price.free{
color: green;
}
.course-card-list-wrap .card-item .item-line .item-price.vip{
color: orange;
}
总结
模板渲染知识回顾: 在body中消除掉原来为了迎合css写的li样式
但是要留下 ul
在 text/html 中写原本消除的 li 等可变的数据
text/html中的类名都不变,这样css的效果还在
5.最开始的页面
由于之前去掉了body中的li,所以只能通过点击才能获得li,如果不点击就一直没有页面展示
所以接下来要设置最初展示页面:
1.子模块return{}中 新添方法
initCourseList: function(){
var data = this.filterData('all', courseData);
oCourseCardList.innerHTML = this.makeList(data);
},
没必要填写field,填写个all即可,反正第一页也是all的页面
2.自执行:在主模块中写上方法
var init = function () {
initCourseTab.initCourseList();
bindEvent();
}
三. 搜索功能的实现
因为之前主模块已经写好search事件了,所以这次都是在子模块中填写对应的搜索功能
(1).做出后台输出数据的功能
- 1.因为109行以及获取了输入元素
var oSearchInput = doc.getElementById('js-search-input');
所以 oSearchInput中有自己的属性 'value' ,可以获取输入的内容
第18行就是测试 value 有无效果
- 2. 20行: 如果输入了,即value的长度大于0 就使用searchData函数
21行注意: 这里要在下面改变this的指向,指向函数本身, 改变指向才能拿函数内获取的数据 ;
和 二.(2).3 过滤那里 改变指向一个道理
21行注意: 传入的数据需要传courseData,即json数据
- 3.
第94、96 行仅仅是为了测试
注意: indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。 如果没有找到匹配的字符串则返回 -1。
(2) 做出页面显示效果
1. 第21 行直接去掉console.log(), 将里面的值赋给页面元素的innerHTML
if (len > 0) { //如果输入了,就使用searchData函数
oCourseCardList.innerHTML = this.makeList(this.searchData(courseData, val)); //courseData是JSON数据
}
2.设置输入框字体减少至0时的清空效果
if (len > 0) { //如果输入了,就使用searchData函数
oCourseCardList.innerHTML = this.makeList(this.searchData(courseData, val)); //courseData是JSON数据
} else {
oCourseCardList.innerHTML = oCourseCardList.innerHTML = this.makeList(courseData);
}
逻辑为:当输入框的字符为0时,全部展现
3.上述清空效果只对第一项('全部')有用, 所以还需设置一个方法
if (len > 0) { //如果输入了,就使用searchData函数
oCourseCardList.innerHTML = this.makeList(this.searchData(courseData, val)); //courseData是JSON数据
} else {
this.restoreList();
}
},
四. 缩减代码/抽象函数(经验)
有很多重复的代码,可以将重复的代码抽象出来,单独变成一个方法,其他地方需要时调用即可
如:36行代码与上述restoreList方法中的代码类似
所以我们可以额外创立一个方法 changeTabCurrent ,缩减代码量,提升运算速度 :
36行的也可以调用它:
if (className === 'course-tab-lk') { //让点击的出现current效果
var field = tar.getAttribute('data-field')
this.changeTabCurrent(tar);
oCourseCardList.innerHTML = this.makeList(this.filterData(field, courseData));
}
五. 功能完善:提示没有相关课程等小功能的实现
对应三.(1)
注意:第23行showTip 前面必须加 this 才能找到下面的方法