-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcomponent.html
311 lines (266 loc) · 10.7 KB
/
component.html
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
<!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>demo</title>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
</head>
<body>
<script>
var Class = (function() {
var _mix = function(r, s) {
for(var p in s) {
if(s.hasOwnProperty(p)) {
r[p] = s[p]
}
}
};
var _extend = function() {
// 开关 用来使生成原型时, 不调用真正的构成流程init
this.initPrototype = true;
var prototype = new this();
this.initPrototype = false;
var items = Array.prototype.slice.call(arguments) || [];
var item;
// 支持混入多个属性, 并且支持{} 也支持 Function
while (item = items.shift()) {
_mix(prototype, item.prototype || item);
};
// 这边是返回的类, 其实也就是我们返回的子类
function SubClass() {
if(!SubClass.initPrototype && this.init) {
this.init.apply(this, arguments); // 调用init真正的构造函数
};
};
// 赋值原型链, 完成继承
SubClass.prototype = prototype;
// 改变constructor引用
SubClass.prototype.constructor = SubClass;
// 为子类也添加extend方法
SubClass.extend = _extend;
return SubClass;
};
// 超级父类
var Class = function() {};
// 为超级父类添加extend方法
Class.extend = _extend;
return Class;
})();
// 辅助函数, 获取数组里某一个元素的索引 index
var _indexOf = function(array, key) {
if(array === null) return -1;
var i = 0, length = array.length;
for(; i < length; i++) {
if(array[i] === key) {
return i
};
};
return -1;
};
var Event = Class.extend({
// 添加监听
on: function(key, listener) {
// this.__events存储所有的处理函数
if(!this.__events) {
this.__events = {};
};
if(!this.__events[key]) {
this.__events[key] = [];
};
if(_indexOf(this.__events, listener) === -1 && typeof listener === 'function') {
this.__events[key].push(listener);
};
return this
},
// 通知
fire: function(key) {
if(!this.__events || !this.__events[key]) return;
var args = Array.prototype.slice.call(arguments, 1) || [];
var listeners = this.__events[key];
for(var i = 0; i < listeners.length; i++) {
listeners[i].apply(this, args);
};
return this;
},
// 取消监听
off: function(key, listener) {
if(!key && !listener) {
this.__events = {};
};
// 不传监听函数, 就去掉当前key下面所有的监听函数
if(key && !listener) {
delete this.__events[key];
};
if(key && listener) {
var listeners = this.__events[key];
var index = _indexOf(listeners, listener);
if(index > -1) {
listeners.splice(index, 1);
};
};
return this;
}
});
var Base = Class.extend(Event, {
init: function(config) {
// 自动保存配置项
this.__config = config
this.bind()
this.render()
},
// 可以使用get来获取配置项
get: function(key) {
return this.__config[key]
},
// 可以使用set来设置配置项
set: function(key, value) {
this.__config[key] = value
},
bind: function() { },
render: function() { },
// 定义销毁的方法,一些收尾工作都应该在这里
destroy: function() {
//去掉所有的事件监听
this.off()
}
});
var RichBase = Base.extend({
EVENTS: {},
template: '',
init: function(config) {
this.__config = config;
// 解析代理事件
this._delegateEvent();
this.setUp();
},
// 循环遍历EVENTS, 使用jQuery的delegate代理到parentNode
_delegateEvent: function() {
var self = this;
var events = this.EVENTS || {};
var eventObjs, fn, select, type;
var parentNode = this.get('parentNode') || $(document.body)
for(select in events) {
eventObjs = events[select];
for(type in eventObjs) {
fn = eventObjs[type];
parentNode.delegate(select, type, function(e) {
fn.call(null, self, e);
});
};
};
},
// 支持underscore的极简模板语法
// 用来渲染模板, 这边抄underscore的. 非常简单的模板引擎, 支持元素js语法
_parseTemplate: function(str, data) {
var fn = new Function('obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' + str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'") +
"');}return p.join('');");
return data ? fn(data) : fn;
},
// 提供给子类覆盖实现
setUp: function() {
this.render();
},
// 用来实现刷新, 只需要传入之前render时的数据里的key还有更新值, 就可以自动刷新模板
setChuckdata: function(key, value) {
var self = this;
var data = self.get('__renderData');
// 更新对应的值
data[key] = value;
if(!this.template) return;
// 重新渲染
var newHtmlNode = $(self._parseTemplate(this.template, data));
// 拿到存储的渲染后的节点
var currentNode = self.get('__currentNode');
if(!currentNode) return;
// 替换内容
currentNode.replaceWith(newHtmlNode);
self.set('__currentNode', newHtmlNode);
},
// 使用data来渲染模板并且append到parentNode下面
render: function(data) {
var self = this;
// 先存储起来渲染的data, 方便后面的setChuckdata获取使用
self.set('__renderData', data);
if(!this.template) return;
// 使用_parseTemplate解析渲染模板生成html
// 子类可以覆盖这个方法使用其它模板引擎解析
var html = self._parseTemplate(this.template, data);
var parentNode = this.get('parentNode') || $(document.body);
var currentNode = $(html);
// 保留下来留待后面的区域刷新
// 存储起来, 方便后面setChuckdata获取使用
self.set('__currentNode', currentNode);
parentNode.append(currentNode);
},
destory: function() {
var self = this;
// 去掉自身的事件监听
self.off();
// 删除渲染好的dom节点
self.get('__currentNode').remove();
// 去掉绑定的代理事件
var events = self.EVENTS || {};
var eventObjs, fn, select, type;
var parentNode = self.get('parentNode');
for(select in events) {
eventObjs = events[select];
for(type in eventObjs) {
fn = eventObjs[type];
parentNode.undelegate(select, type, fn);
};
};
}
});
var TextCount = RichBase.extend({
// 事件直接在这里注册, 会代理到parentNode节点, parentNode节点在下面指定
EVENTS: {
// 选择器字符串, 支持所有jQuery风格的选择器
'input': {
// 注册keyup事件
keyup: function(self, e) {
// 单向绑定, 修改数据直接更新对应模板
self.setChuckdata('count', self._getNum());
}
}
},
// 指定当前组件的模板
template: '<span id="count"><%= count %>个字</span>',
//私有方法
_getNum: function() {
return this.get('input').val().length || 0;
},
// 覆盖实现setUp方法, 所有逻辑写在这里. 最后可以使用render来决定需不需要渲染模板
// 模板渲染后会append到parentNode节点下面, 如果未指定, 会append到document.body
setUp: function() {
var self = this;
var input = this.get('parentNode').find('#input');
self.set('input', input);
var num = this._getNum();
// 赋值数据, 渲染模板,
self.render({
count: num
})
}
});
$(function() {
new TextCount({
parentNode: $('#app')
})
})
</script>
<div id="app">
<input type="text" id="input" />
</div>
</body>
</html>