├── README.md
├── bower.json
├── index.js
├── package.json
└── utils
├── bmob.js
├── bmobSocketIo.js
├── common.js
├── underscore.js
└── util.js
/README.md:
--------------------------------------------------------------------------------
1 | Bmob 微信小程序SDK
2 | ===================
3 | 本项目是Bmob 为小程序客户端开发提供 SDK 支持,里面包含实时数据与数据存储相关操作。
4 |
5 |
6 | ### SDK 获取与安装
7 | Bmob 小程序解决方案客户端 [Demo](https://github.com/magic007/wechatAppDemo "Demo") 已经集成并使用最新版的 SDK,并且已经写了大量常见业务代码。需要快速了解的可以从 [Demo](https://github.com/magic007/wechatAppDemo "Demo") 开始。
8 |
9 |
10 | ### 将sdk引入到微信小程序中
11 | ----------
12 | > 方法一:
13 | 1. 下载将`utils`目录 复制至项目目录下
14 | 2. App.js 初始化SDK
15 | ```
16 | const Bmob = require('utils/bmob.js');
17 | Bmob.initialize("你的Application ID", "你的REST API Key");
18 | ```
19 |
20 | > 方法二:
21 | ```
22 | npm install -g bower
23 | bower install bmob-weapp
24 | const Bmob = require('./bower_components/bmob-weapp/utils/bmob.js');
25 | Bmob.initialize("你的Application ID", "你的REST API Key");
26 | ```
27 |
28 |
29 | 本小程序Sdk 不需要购买任何相关服务器,即可操作数据库与微信小程序打通,开箱即用。如是之前平台老用户则不需重复申请账号,并且打通之前的Android,IOS,等平台数据,本示例整合了常用的功能,开发请按照以下步骤操作。
30 |
31 | ### 小程序实战学习视频教程
32 |
33 | #### 链接地址:
34 | [小程序视频教程](http://doc.bmob.cn/video/index.html#3 "小程序视频教程")
35 |
36 | >Tip: 如果你对数据处理要求比较高,可以引入`underscore.js`库。
37 |
38 | ### 配置AppId和AppSecret
39 | ----------
40 | 登录网址后台点击`应用`->`设置`->`应用配置`填写```AppID```,`AppSecret`。
41 | >Tip: 如果你的小程序不需要获取用户`open id`功能,则不需要配置。
42 |
43 | >### 开发文档
44 | [Bmob 小程序开发文档](http://doc.bmob.cn/data/wechat_app/index.html "开发文档")
45 |
46 | ----------
47 |
48 | #### 版本 v3.6.1
49 |
50 | > **Note:**
51 | >
52 | > - 应微信要求,登陆 auth() 函数,不再保存用户头像
53 | >
54 | > - 增加user.getUserInfo()函数, 更新用户昵称头像信息
55 | >
56 | >
57 |
58 | #### 版本 v3.6.0
59 |
60 | > **Note:**
61 | >
62 | > - 登陆 auth() 函数,增加then回调,如果出现错误,控制台会提示
63 | > - 兼容小程序游戏,去掉hideNavigationBarLoading 相关函数
64 | > - 增加微信支付退款功能
65 | > - 修复批量删除函数必须用Bmob对象Bug
66 |
67 | #### 版本 v3.5.0
68 |
69 | > **Note:**
70 | > - 数据SDK整合app.js相关功能
71 | > - 数据启用新域名
72 | > - 增加全新socketSDK
73 | > - 优化登陆流程,登陆更加智能
74 | ----------
75 | #### 版本 v3.4.1
76 | > **Note:**
77 | > - 修复个别时候图片上传进度不正确
78 | > - 增加分组、统计等功能
79 | > - 增加BQL 查询功能
80 |
81 |
82 | #### 版本 v3.4.0
83 | > **Note:**
84 | > - 增加获取手机号解密示例
85 | > - 增加小程序客服回复示例
86 |
87 |
88 | #### 版本 v3.3.2
89 | > **Note:**
90 | > - 修复视频格式Bug
91 | > - 新特性 文件上传增加进度显示
92 |
93 | #### 版本 v3.3.1
94 | > **Note:**
95 | > - 增加试试通讯域名wss.bmobcloud.com
96 | > - 聊天室优化部分流程
97 |
98 |
99 | #### 版本 v3.0.0
100 | > **Note:**
101 | >
102 | > - 此版本主要变动开发者请求API地址,兼容微信白名单工单
103 | > - index代码增加模板消息推送。
104 |
105 | #### 版本 v2.1.0
106 | > **Note:**
107 | >
108 | > - 增加日记详细页面
109 | > - 修复小程序点击事件冒泡问题catchtap
110 | > - 首页增加模糊搜索
111 | > - 修复我发布的没显示bug
112 |
113 | #### 版本 v2.0.2
114 | > **Note:**
115 | >
116 | > - 增加我发布的
117 | > - 修复部分bug
118 |
119 |
120 | #### 版本 v2.0.1
121 | > **Note:**
122 | >
123 | > - 图片上传增加批量上传
124 | > - 页面底部增加版权
125 |
126 | #### 版本 v2.0.0
127 | > **Note:**
128 | >
129 | > - 增加新接口,生成二维码应用内推广链接
130 | > - 升级UI2.0
131 | > - 增加反馈功能模版
132 |
133 | #### 版本 v1.1.0
134 | > **Note:**
135 | >
136 | > - 增加新登录接口,使用请看app.js
137 | > - 增加新图片删除接口,使用请看接口图片上传
138 |
139 | #### 版本 v1.0.0
140 | > **Note:**
141 | >
142 | > - 增加微信支付接口,只需填写key即可微信收款
143 |
144 |
145 | #### 版本 v0.5.0
146 | > **Note:**
147 |
148 | > - 修复返回当前用户延时问题
149 | > - 修复修改用户表缓存没更新问题
150 |
151 | ----------
152 |
153 | #### 版本 v0.4.2
154 | > **Note:**
155 |
156 | > - 修复短信验证Bug
157 | > - 添加短信验证Demo
158 |
159 | ----------
160 |
161 | #### 版本 v0.4.1
162 | > **Note:**
163 |
164 | > - 修复注册跳转错误
165 | > - 增加获取OpenId示例
166 |
167 | ----------
168 |
169 |
170 | #### 版本 v0.4.0
171 | > **Note:**
172 |
173 | > - 添加接口示例
174 | > - 添加短信发送示例
175 | > - 添加退出账户操作
176 | > - 增加个人信息展示
177 |
178 | ----------
179 |
180 | #### 版本 v0.3.0
181 | > **Note:**
182 |
183 | > - 添加微信小程序关联Bmob后端填写key
184 | > - 小程序获取openid
185 |
186 | ----------
187 |
188 | #### 版本 v0.2.0
189 | > **Note:**
190 |
191 | > - 修复部分bug
192 | > - 增加文件上传
193 |
194 | ----------
195 |
196 | #### 版本 v0.1.0
197 | > **Note:**
198 |
199 | > - 数据增删改查
200 | > - 登录
201 | > - 注册
202 | >- 短信
203 |
204 | ----------
205 |
206 | > **Tip:** 更多信息请查看[官方文档](http://docs.bmob.cn/data/wechatApp/a_faststart/doc/index.html "官方使用文档") ,如需帮助可以加入小程序讨论QQ群:**118541934**。
207 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bmob-weapp-sdk",
3 | "authors": [
4 | "Bmob Cloud"
5 | ],
6 | "description": "Bmob 小程序客户端 SDK",
7 | "main": "utils/bmob.js",
8 | "license": "MIT",
9 | "homepage": "",
10 | "ignore": [
11 | "**/.*",
12 | "node_modules",
13 | "bower_components",
14 | "test",
15 | "tests"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // 正确
2 | const Bmob = require('./utils/bmob')
3 | const BmobSocketIo = require('./utils/bmobSocketIo')
4 | export { Bmob, BmobSocketIo};
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bmob-weapp",
3 | "version": "3.5.0",
4 | "description": "小程序SDK",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/bmob/bmob-WeApp-sdk.git"
12 | },
13 | "keywords": [
14 | "bmob"
15 | ],
16 | "author": "magic",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/bmob/bmob-WeApp-sdk/issues"
20 | },
21 | "homepage": "https://github.com/bmob/bmob-WeApp-sdk#readme"
22 | }
23 |
--------------------------------------------------------------------------------
/utils/bmobSocketIo.js:
--------------------------------------------------------------------------------
1 | const Emitter = {
2 | setup(target) {
3 | let listeners = []
4 |
5 | Object.assign(target, {
6 | on(type, handle) {
7 | if (typeof handle == 'function') {
8 | listeners.push([type, handle])
9 | }
10 | },
11 | emit(type, ...params) {
12 | listeners.forEach(
13 | ([listenType, handle]) => type == listenType && handle(...params)
14 | )
15 | },
16 | removeAllListeners() {
17 | listeners = []
18 | }
19 | })
20 | }
21 | }
22 |
23 | /**
24 | * 基于小程序 WebSocket 接口封装信道
25 | */
26 | module.exports = class BmobSocketIo {
27 | constructor(applicationId) {
28 |
29 | this.config = {
30 | host: 'wss.bmobcloud.com'
31 | }
32 | Emitter.setup((this.emitter = {}))
33 | this.applicationId = applicationId
34 | this.initialize()
35 | }
36 | handshake() {
37 | function complete(data) {
38 | if (data instanceof Error) {
39 | self.connecting = false
40 | self.onError(data.message)
41 | } else {
42 | return data.split(':')[0]
43 | }
44 | }
45 |
46 | var url = 'https://'+this.config.host+'/socket.io/1/?t=' + new Date().getTime()
47 | var dataObject = {}
48 | var data = JSON.stringify(dataObject)
49 |
50 | var method = 'GET'
51 |
52 | return new Promise((resolve, reject) => {
53 | wx.request({
54 | method: method,
55 | url: url,
56 | data: data,
57 | header: {
58 | 'content-type': 'text/plain'
59 | },
60 | success: function(res) {
61 | if (res.data && res.data.statusCode) {
62 | return resolve('request error', e)
63 | } else if (res.statusCode != 200) {
64 | return resolve('request error', e)
65 | } else {
66 | return resolve(complete(res.data))
67 | }
68 | },
69 | fail: function(e) {
70 | return resolve('request error', e)
71 | }
72 | })
73 | })
74 | }
75 | initialize() {
76 | this.handshake().then(protocol => {
77 | try {
78 | this.connect(
79 | `wss://${this.config.host}/socket.io/1/websocket/` + protocol,
80 | {}
81 | )
82 | } catch (connectError) {
83 | console.error({ connectError })
84 | throw connectError
85 | }
86 | })
87 | this.on('close', () => {
88 | console.log('连接已中断')
89 | })
90 |
91 | return new Promise((resolve, reject) => {
92 | this.on('server_pub', data => {
93 | switch (data.action) {
94 | case 'updateTable':
95 | this.onUpdateTable(data.tableName, data.data)
96 | break
97 | case 'updateRow':
98 | this.onUpdateRow(data.tableName, data.objectId, data.data)
99 | break
100 | case 'deleteTable':
101 | this.onDeleteTable(data.tableName, data.data)
102 | break
103 | case 'deleteRow':
104 | this.onDeleteRow(data.tableName, data.objectId, data.data)
105 | break
106 | }
107 | })
108 |
109 | //连接上socket.io服务器后触发的事件
110 | this.on('client_send_data', resp => {
111 | this.onInitListen()
112 | })
113 | })
114 | }
115 |
116 | onInitListen() {}
117 |
118 | connect(url, header) {
119 | // 小程序 wx.connectSocket() API header 参数无效,把会话信息附加在 URL 上
120 | const query = Object.keys(header)
121 | .map(key => `${key}=${encodeURIComponent(header[key])}`)
122 | .join('&')
123 | const seperator = url.indexOf('?') > -1 ? '&' : '?'
124 | url = [url, query].join(seperator)
125 |
126 | return new Promise((resolve, reject) => {
127 | wx.onSocketOpen(resolve)
128 | wx.onSocketError(reject)
129 | wx.onSocketMessage(packet => {
130 | try {
131 | let filter = function(str) {
132 | const { name, args } = JSON.parse(str)
133 | return { name, args }
134 | }
135 | let str = packet.data
136 | let startStr = str.slice(0,4)
137 | // 检测心跳
138 | if('2:::'===startStr){
139 | this.emit(false,true)
140 | }
141 | str = str.slice(4)
142 |
143 | // 截取后不能为空
144 | if (str == null || str == '') {
145 | return
146 | }
147 | const { name, args } = filter(str)
148 | let data = args == null ? '' : JSON.parse(args[0])
149 | this.emitter.emit(name, data)
150 | } catch (e) {
151 | console.log('Handle packet failed: ' + packet.data, e)
152 | }
153 | })
154 | wx.onSocketClose(() => this.emitter.emit('close'))
155 | wx.connectSocket({ url, header })
156 | })
157 | }
158 |
159 | on(message, handle) {
160 | this.emitter.on(message, handle)
161 | }
162 |
163 | emit(message, data) {
164 | data=data==undefined?'5:::':'2:::'
165 | message = message?JSON.stringify(message):''
166 | wx.sendSocketMessage({
167 | data: data + message
168 | })
169 | }
170 |
171 | emitData(name, data) {
172 | data = JSON.stringify(data)
173 | return { name: name, args: [data] }
174 | }
175 |
176 | //"unsub_updateTable" ,"unsub_updateRow", "unsub_deleteTable", "unsub_deleteRow"
177 | //订阅更新数据表的数据
178 | updateTable = function(tablename) {
179 | var data = {
180 | appKey: this.applicationId,
181 | tableName: tablename,
182 | objectId: '',
183 | action: 'updateTable'
184 | }
185 | data = this.emitData('client_sub', data)
186 | this.emit(data)
187 | }
188 |
189 | //取消订阅更新数据表的数据
190 | unsubUpdateTable = function(tablename) {
191 | var data = {
192 | appKey: this.applicationId,
193 | tableName: tablename,
194 | objectId: '',
195 | action: 'unsub_updateTable'
196 | }
197 | data = this.emitData('client_sub', data)
198 | this.emit(data)
199 | }
200 |
201 | //订阅行更新的数据
202 | updateRow = function(tablename, objectId) {
203 | var data = {
204 | appKey: this.applicationId,
205 | tableName: tablename,
206 | objectId: objectId,
207 | action: 'updateRow'
208 | }
209 | data = this.emitData('client_sub', data)
210 | this.emit(data)
211 | }
212 |
213 | //取消订阅行更新的数据
214 | unsubUpdateRow = function(tablename, objectId) {
215 | var data = {
216 | appKey: this.applicationId,
217 | tableName: tablename,
218 | objectId: objectId,
219 | action: 'unsub_updateRow'
220 | }
221 | data = this.emitData('client_sub', data)
222 | this.emit(data)
223 | }
224 |
225 | //订阅表删除的数据
226 | deleteTable = function(tablename) {
227 | var data = {
228 | appKey: this.applicationId,
229 | tableName: tablename,
230 | objectId: '',
231 | action: 'deleteTable'
232 | }
233 | data = this.emitData('client_sub', data)
234 | this.emit(data)
235 | }
236 |
237 | //取消订阅表删除的数据
238 | unsubDeleteTable = function(tablename) {
239 | var data = {
240 | appKey: this.applicationId,
241 | tableName: tablename,
242 | objectId: '',
243 | action: 'unsub_deleteTable'
244 | }
245 | data = this.emitData('client_sub', data)
246 | this.emit(data)
247 | }
248 |
249 | //订阅更新数据表的数据
250 | deleteRow = function(tablename, objectId) {
251 | var data = {
252 | appKey: this.applicationId,
253 | tableName: tablename,
254 | objectId: objectId,
255 | action: 'deleteRow'
256 | }
257 | data = this.emitData('client_sub', data)
258 | this.emit(data)
259 | }
260 |
261 | //订阅更新数据表的数据
262 | unsubDeleteRow = function(tablename, objectId) {
263 | var data = {
264 | appKey: this.applicationId,
265 | tableName: tablename,
266 | objectId: objectId,
267 | action: 'unsub_deleteRow'
268 | }
269 | data = this.emitData('client_sub', data)
270 | this.emit(data)
271 | }
272 |
273 | //监听服务器返回的更新数据表的数据,需要用户重写
274 | onUpdateTable = function(tablename, data) {}
275 |
276 | //监听服务器返回的更新数据表的数据,需要用户重写
277 | onUpdateRow = function(tablename, objectId, data) {}
278 |
279 | //监听服务器返回的更新数据表的数据,需要用户重写
280 | onDeleteTable = function(tablename, data) {}
281 |
282 | //监听服务器返回的更新数据表的数据,需要用户重写
283 | onDeleteRow = function(tablename, objectId, data) {}
284 | }
285 |
--------------------------------------------------------------------------------
/utils/common.js:
--------------------------------------------------------------------------------
1 | function showTip(sms, icon, fun, t) {
2 | if (!t) {
3 | t = 1000;
4 | }
5 | wx.showToast({
6 | title: sms,
7 | icon: icon,
8 | duration: t,
9 | success: fun
10 | })
11 | }
12 |
13 | function showModal(c,t,fun) {
14 | if(!t)
15 | t='提示'
16 | wx.showModal({
17 | title: t,
18 | content: c,
19 | showCancel:false,
20 | success: fun
21 | })
22 | }
23 |
24 |
25 | module.exports.showTip = showTip;
26 | module.exports.showModal = showModal;
--------------------------------------------------------------------------------
/utils/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.8.3
2 | // http://underscorejs.org
3 | // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
4 | // Underscore may be freely distributed under the MIT license.
5 |
6 | (function() {
7 |
8 | // Baseline setup
9 | // --------------
10 |
11 | // Establish the root object, `window` in the browser, or `exports` on the server.
12 | //var root = this;
13 |
14 | // Save the previous value of the `_` variable.
15 | //var previousUnderscore = root._;
16 |
17 | // Save bytes in the minified (but not gzipped) version:
18 | var ArrayProto = Array.prototype,
19 | ObjProto = Object.prototype,
20 | FuncProto = Function.prototype;
21 |
22 | // Create quick reference variables for speed access to core prototypes.
23 | var
24 | push = ArrayProto.push,
25 | slice = ArrayProto.slice,
26 | toString = ObjProto.toString,
27 | hasOwnProperty = ObjProto.hasOwnProperty;
28 |
29 | // All **ECMAScript 5** native function implementations that we hope to use
30 | // are declared here.
31 | var
32 | nativeIsArray = Array.isArray,
33 | nativeKeys = Object.keys,
34 | nativeBind = FuncProto.bind,
35 | nativeCreate = Object.create;
36 |
37 | // Naked function reference for surrogate-prototype-swapping.
38 | var Ctor = function() {};
39 |
40 | // Create a safe reference to the Underscore object for use below.
41 | var _ = function(obj) {
42 | if (obj instanceof _) return obj;
43 | if (!(this instanceof _)) return new _(obj);
44 | this._wrapped = obj;
45 | };
46 |
47 | // Export the Underscore object for **Node.js**, with
48 | // backwards-compatibility for the old `require()` API. If we're in
49 | // the browser, add `_` as a global object.
50 | if (typeof exports !== 'undefined') {
51 | if (typeof module !== 'undefined' && module.exports) {
52 | exports = module.exports = _;
53 | }
54 | exports._ = _;
55 | } else {
56 | root._ = _;
57 | }
58 |
59 | // Current version.
60 | _.VERSION = '1.8.3';
61 |
62 | // Internal function that returns an efficient (for current engines) version
63 | // of the passed-in callback, to be repeatedly applied in other Underscore
64 | // functions.
65 | var optimizeCb = function(func, context, argCount) {
66 | if (context === void 0) return func;
67 | switch (argCount == null ? 3 : argCount) {
68 | case 1:
69 | return function(value) {
70 | return func.call(context, value);
71 | };
72 | case 2:
73 | return function(value, other) {
74 | return func.call(context, value, other);
75 | };
76 | case 3:
77 | return function(value, index, collection) {
78 | return func.call(context, value, index, collection);
79 | };
80 | case 4:
81 | return function(accumulator, value, index, collection) {
82 | return func.call(context, accumulator, value, index, collection);
83 | };
84 | }
85 | return function() {
86 | return func.apply(context, arguments);
87 | };
88 | };
89 |
90 | // A mostly-internal function to generate callbacks that can be applied
91 | // to each element in a collection, returning the desired result — either
92 | // identity, an arbitrary callback, a property matcher, or a property accessor.
93 | var cb = function(value, context, argCount) {
94 | if (value == null) return _.identity;
95 | if (_.isFunction(value)) return optimizeCb(value, context, argCount);
96 | if (_.isObject(value)) return _.matcher(value);
97 | return _.property(value);
98 | };
99 | _.iteratee = function(value, context) {
100 | return cb(value, context, Infinity);
101 | };
102 |
103 | // An internal function for creating assigner functions.
104 | var createAssigner = function(keysFunc, undefinedOnly) {
105 | return function(obj) {
106 | var length = arguments.length;
107 | if (length < 2 || obj == null) return obj;
108 | for (var index = 1; index < length; index++) {
109 | var source = arguments[index],
110 | keys = keysFunc(source),
111 | l = keys.length;
112 | for (var i = 0; i < l; i++) {
113 | var key = keys[i];
114 | if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
115 | }
116 | }
117 | return obj;
118 | };
119 | };
120 |
121 | // An internal function for creating a new object that inherits from another.
122 | var baseCreate = function(prototype) {
123 | if (!_.isObject(prototype)) return {};
124 | if (nativeCreate) return nativeCreate(prototype);
125 | Ctor.prototype = prototype;
126 | var result = new Ctor;
127 | Ctor.prototype = null;
128 | return result;
129 | };
130 |
131 | var property = function(key) {
132 | return function(obj) {
133 | return obj == null ? void 0 : obj[key];
134 | };
135 | };
136 |
137 | // Helper for collection methods to determine whether a collection
138 | // should be iterated as an array or as an object
139 | // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
140 | // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
141 | var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
142 | var getLength = property('length');
143 | var isArrayLike = function(collection) {
144 | var length = getLength(collection);
145 | return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
146 | };
147 |
148 | // Collection Functions
149 | // --------------------
150 |
151 | // The cornerstone, an `each` implementation, aka `forEach`.
152 | // Handles raw objects in addition to array-likes. Treats all
153 | // sparse array-likes as if they were dense.
154 | _.each = _.forEach = function(obj, iteratee, context) {
155 | iteratee = optimizeCb(iteratee, context);
156 | var i, length;
157 | if (isArrayLike(obj)) {
158 | for (i = 0, length = obj.length; i < length; i++) {
159 | iteratee(obj[i], i, obj);
160 | }
161 | } else {
162 | var keys = _.keys(obj);
163 | for (i = 0, length = keys.length; i < length; i++) {
164 | iteratee(obj[keys[i]], keys[i], obj);
165 | }
166 | }
167 | return obj;
168 | };
169 |
170 | // Return the results of applying the iteratee to each element.
171 | _.map = _.collect = function(obj, iteratee, context) {
172 | iteratee = cb(iteratee, context);
173 | var keys = !isArrayLike(obj) && _.keys(obj),
174 | length = (keys || obj).length,
175 | results = Array(length);
176 | for (var index = 0; index < length; index++) {
177 | var currentKey = keys ? keys[index] : index;
178 | results[index] = iteratee(obj[currentKey], currentKey, obj);
179 | }
180 | return results;
181 | };
182 |
183 | // Create a reducing function iterating left or right.
184 | function createReduce(dir) {
185 | // Optimized iterator function as using arguments.length
186 | // in the main function will deoptimize the, see #1991.
187 | function iterator(obj, iteratee, memo, keys, index, length) {
188 | for (; index >= 0 && index < length; index += dir) {
189 | var currentKey = keys ? keys[index] : index;
190 | memo = iteratee(memo, obj[currentKey], currentKey, obj);
191 | }
192 | return memo;
193 | }
194 |
195 | return function(obj, iteratee, memo, context) {
196 | iteratee = optimizeCb(iteratee, context, 4);
197 | var keys = !isArrayLike(obj) && _.keys(obj),
198 | length = (keys || obj).length,
199 | index = dir > 0 ? 0 : length - 1;
200 | // Determine the initial value if none is provided.
201 | if (arguments.length < 3) {
202 | memo = obj[keys ? keys[index] : index];
203 | index += dir;
204 | }
205 | return iterator(obj, iteratee, memo, keys, index, length);
206 | };
207 | }
208 |
209 | // **Reduce** builds up a single result from a list of values, aka `inject`,
210 | // or `foldl`.
211 | _.reduce = _.foldl = _.inject = createReduce(1);
212 |
213 | // The right-associative version of reduce, also known as `foldr`.
214 | _.reduceRight = _.foldr = createReduce(-1);
215 |
216 | // Return the first value which passes a truth test. Aliased as `detect`.
217 | _.find = _.detect = function(obj, predicate, context) {
218 | var key;
219 | if (isArrayLike(obj)) {
220 | key = _.findIndex(obj, predicate, context);
221 | } else {
222 | key = _.findKey(obj, predicate, context);
223 | }
224 | if (key !== void 0 && key !== -1) return obj[key];
225 | };
226 |
227 | // Return all the elements that pass a truth test.
228 | // Aliased as `select`.
229 | _.filter = _.select = function(obj, predicate, context) {
230 | var results = [];
231 | predicate = cb(predicate, context);
232 | _.each(obj, function(value, index, list) {
233 | if (predicate(value, index, list)) results.push(value);
234 | });
235 | return results;
236 | };
237 |
238 | // Return all the elements for which a truth test fails.
239 | _.reject = function(obj, predicate, context) {
240 | return _.filter(obj, _.negate(cb(predicate)), context);
241 | };
242 |
243 | // Determine whether all of the elements match a truth test.
244 | // Aliased as `all`.
245 | _.every = _.all = function(obj, predicate, context) {
246 | predicate = cb(predicate, context);
247 | var keys = !isArrayLike(obj) && _.keys(obj),
248 | length = (keys || obj).length;
249 | for (var index = 0; index < length; index++) {
250 | var currentKey = keys ? keys[index] : index;
251 | if (!predicate(obj[currentKey], currentKey, obj)) return false;
252 | }
253 | return true;
254 | };
255 |
256 | // Determine if at least one element in the object matches a truth test.
257 | // Aliased as `any`.
258 | _.some = _.any = function(obj, predicate, context) {
259 | predicate = cb(predicate, context);
260 | var keys = !isArrayLike(obj) && _.keys(obj),
261 | length = (keys || obj).length;
262 | for (var index = 0; index < length; index++) {
263 | var currentKey = keys ? keys[index] : index;
264 | if (predicate(obj[currentKey], currentKey, obj)) return true;
265 | }
266 | return false;
267 | };
268 |
269 | // Determine if the array or object contains a given item (using `===`).
270 | // Aliased as `includes` and `include`.
271 | _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
272 | if (!isArrayLike(obj)) obj = _.values(obj);
273 | if (typeof fromIndex != 'number' || guard) fromIndex = 0;
274 | return _.indexOf(obj, item, fromIndex) >= 0;
275 | };
276 |
277 | // Invoke a method (with arguments) on every item in a collection.
278 | _.invoke = function(obj, method) {
279 | var args = slice.call(arguments, 2);
280 | var isFunc = _.isFunction(method);
281 | return _.map(obj, function(value) {
282 | var func = isFunc ? method : value[method];
283 | return func == null ? func : func.apply(value, args);
284 | });
285 | };
286 |
287 | // Convenience version of a common use case of `map`: fetching a property.
288 | _.pluck = function(obj, key) {
289 | return _.map(obj, _.property(key));
290 | };
291 |
292 | // Convenience version of a common use case of `filter`: selecting only objects
293 | // containing specific `key:value` pairs.
294 | _.where = function(obj, attrs) {
295 | return _.filter(obj, _.matcher(attrs));
296 | };
297 |
298 | // Convenience version of a common use case of `find`: getting the first object
299 | // containing specific `key:value` pairs.
300 | _.findWhere = function(obj, attrs) {
301 | return _.find(obj, _.matcher(attrs));
302 | };
303 |
304 | // Return the maximum element (or element-based computation).
305 | _.max = function(obj, iteratee, context) {
306 | var result = -Infinity,
307 | lastComputed = -Infinity,
308 | value, computed;
309 | if (iteratee == null && obj != null) {
310 | obj = isArrayLike(obj) ? obj : _.values(obj);
311 | for (var i = 0, length = obj.length; i < length; i++) {
312 | value = obj[i];
313 | if (value > result) {
314 | result = value;
315 | }
316 | }
317 | } else {
318 | iteratee = cb(iteratee, context);
319 | _.each(obj, function(value, index, list) {
320 | computed = iteratee(value, index, list);
321 | if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
322 | result = value;
323 | lastComputed = computed;
324 | }
325 | });
326 | }
327 | return result;
328 | };
329 |
330 | // Return the minimum element (or element-based computation).
331 | _.min = function(obj, iteratee, context) {
332 | var result = Infinity,
333 | lastComputed = Infinity,
334 | value, computed;
335 | if (iteratee == null && obj != null) {
336 | obj = isArrayLike(obj) ? obj : _.values(obj);
337 | for (var i = 0, length = obj.length; i < length; i++) {
338 | value = obj[i];
339 | if (value < result) {
340 | result = value;
341 | }
342 | }
343 | } else {
344 | iteratee = cb(iteratee, context);
345 | _.each(obj, function(value, index, list) {
346 | computed = iteratee(value, index, list);
347 | if (computed < lastComputed || computed === Infinity && result === Infinity) {
348 | result = value;
349 | lastComputed = computed;
350 | }
351 | });
352 | }
353 | return result;
354 | };
355 |
356 | // Shuffle a collection, using the modern version of the
357 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
358 | _.shuffle = function(obj) {
359 | var set = isArrayLike(obj) ? obj : _.values(obj);
360 | var length = set.length;
361 | var shuffled = Array(length);
362 | for (var index = 0, rand; index < length; index++) {
363 | rand = _.random(0, index);
364 | if (rand !== index) shuffled[index] = shuffled[rand];
365 | shuffled[rand] = set[index];
366 | }
367 | return shuffled;
368 | };
369 |
370 | // Sample **n** random values from a collection.
371 | // If **n** is not specified, returns a single random element.
372 | // The internal `guard` argument allows it to work with `map`.
373 | _.sample = function(obj, n, guard) {
374 | if (n == null || guard) {
375 | if (!isArrayLike(obj)) obj = _.values(obj);
376 | return obj[_.random(obj.length - 1)];
377 | }
378 | return _.shuffle(obj).slice(0, Math.max(0, n));
379 | };
380 |
381 | // Sort the object's values by a criterion produced by an iteratee.
382 | _.sortBy = function(obj, iteratee, context) {
383 | iteratee = cb(iteratee, context);
384 | return _.pluck(_.map(obj, function(value, index, list) {
385 | return {
386 | value: value,
387 | index: index,
388 | criteria: iteratee(value, index, list)
389 | };
390 | }).sort(function(left, right) {
391 | var a = left.criteria;
392 | var b = right.criteria;
393 | if (a !== b) {
394 | if (a > b || a === void 0) return 1;
395 | if (a < b || b === void 0) return -1;
396 | }
397 | return left.index - right.index;
398 | }), 'value');
399 | };
400 |
401 | // An internal function used for aggregate "group by" operations.
402 | var group = function(behavior) {
403 | return function(obj, iteratee, context) {
404 | var result = {};
405 | iteratee = cb(iteratee, context);
406 | _.each(obj, function(value, index) {
407 | var key = iteratee(value, index, obj);
408 | behavior(result, value, key);
409 | });
410 | return result;
411 | };
412 | };
413 |
414 | // Groups the object's values by a criterion. Pass either a string attribute
415 | // to group by, or a function that returns the criterion.
416 | _.groupBy = group(function(result, value, key) {
417 | if (_.has(result, key)) result[key].push(value);
418 | else result[key] = [value];
419 | });
420 |
421 | // Indexes the object's values by a criterion, similar to `groupBy`, but for
422 | // when you know that your index values will be unique.
423 | _.indexBy = group(function(result, value, key) {
424 | result[key] = value;
425 | });
426 |
427 | // Counts instances of an object that group by a certain criterion. Pass
428 | // either a string attribute to count by, or a function that returns the
429 | // criterion.
430 | _.countBy = group(function(result, value, key) {
431 | if (_.has(result, key)) result[key]++;
432 | else result[key] = 1;
433 | });
434 |
435 | // Safely create a real, live array from anything iterable.
436 | _.toArray = function(obj) {
437 | if (!obj) return [];
438 | if (_.isArray(obj)) return slice.call(obj);
439 | if (isArrayLike(obj)) return _.map(obj, _.identity);
440 | return _.values(obj);
441 | };
442 |
443 | // Return the number of elements in an object.
444 | _.size = function(obj) {
445 | if (obj == null) return 0;
446 | return isArrayLike(obj) ? obj.length : _.keys(obj).length;
447 | };
448 |
449 | // Split a collection into two arrays: one whose elements all satisfy the given
450 | // predicate, and one whose elements all do not satisfy the predicate.
451 | _.partition = function(obj, predicate, context) {
452 | predicate = cb(predicate, context);
453 | var pass = [],
454 | fail = [];
455 | _.each(obj, function(value, key, obj) {
456 | (predicate(value, key, obj) ? pass : fail).push(value);
457 | });
458 | return [pass, fail];
459 | };
460 |
461 | // Array Functions
462 | // ---------------
463 |
464 | // Get the first element of an array. Passing **n** will return the first N
465 | // values in the array. Aliased as `head` and `take`. The **guard** check
466 | // allows it to work with `_.map`.
467 | _.first = _.head = _.take = function(array, n, guard) {
468 | if (array == null) return void 0;
469 | if (n == null || guard) return array[0];
470 | return _.initial(array, array.length - n);
471 | };
472 |
473 | // Returns everything but the last entry of the array. Especially useful on
474 | // the arguments object. Passing **n** will return all the values in
475 | // the array, excluding the last N.
476 | _.initial = function(array, n, guard) {
477 | return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
478 | };
479 |
480 | // Get the last element of an array. Passing **n** will return the last N
481 | // values in the array.
482 | _.last = function(array, n, guard) {
483 | if (array == null) return void 0;
484 | if (n == null || guard) return array[array.length - 1];
485 | return _.rest(array, Math.max(0, array.length - n));
486 | };
487 |
488 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
489 | // Especially useful on the arguments object. Passing an **n** will return
490 | // the rest N values in the array.
491 | _.rest = _.tail = _.drop = function(array, n, guard) {
492 | return slice.call(array, n == null || guard ? 1 : n);
493 | };
494 |
495 | // Trim out all falsy values from an array.
496 | _.compact = function(array) {
497 | return _.filter(array, _.identity);
498 | };
499 |
500 | // Internal implementation of a recursive `flatten` function.
501 | var flatten = function(input, shallow, strict, startIndex) {
502 | var output = [],
503 | idx = 0;
504 | for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
505 | var value = input[i];
506 | if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
507 | //flatten current level of array or arguments object
508 | if (!shallow) value = flatten(value, shallow, strict);
509 | var j = 0,
510 | len = value.length;
511 | output.length += len;
512 | while (j < len) {
513 | output[idx++] = value[j++];
514 | }
515 | } else if (!strict) {
516 | output[idx++] = value;
517 | }
518 | }
519 | return output;
520 | };
521 |
522 | // Flatten out an array, either recursively (by default), or just one level.
523 | _.flatten = function(array, shallow) {
524 | return flatten(array, shallow, false);
525 | };
526 |
527 | // Return a version of the array that does not contain the specified value(s).
528 | _.without = function(array) {
529 | return _.difference(array, slice.call(arguments, 1));
530 | };
531 |
532 | // Produce a duplicate-free version of the array. If the array has already
533 | // been sorted, you have the option of using a faster algorithm.
534 | // Aliased as `unique`.
535 | _.uniq = _.unique = function(array, isSorted, iteratee, context) {
536 | if (!_.isBoolean(isSorted)) {
537 | context = iteratee;
538 | iteratee = isSorted;
539 | isSorted = false;
540 | }
541 | if (iteratee != null) iteratee = cb(iteratee, context);
542 | var result = [];
543 | var seen = [];
544 | for (var i = 0, length = getLength(array); i < length; i++) {
545 | var value = array[i],
546 | computed = iteratee ? iteratee(value, i, array) : value;
547 | if (isSorted) {
548 | if (!i || seen !== computed) result.push(value);
549 | seen = computed;
550 | } else if (iteratee) {
551 | if (!_.contains(seen, computed)) {
552 | seen.push(computed);
553 | result.push(value);
554 | }
555 | } else if (!_.contains(result, value)) {
556 | result.push(value);
557 | }
558 | }
559 | return result;
560 | };
561 |
562 | // Produce an array that contains the union: each distinct element from all of
563 | // the passed-in arrays.
564 | _.union = function() {
565 | return _.uniq(flatten(arguments, true, true));
566 | };
567 |
568 | // Produce an array that contains every item shared between all the
569 | // passed-in arrays.
570 | _.intersection = function(array) {
571 | var result = [];
572 | var argsLength = arguments.length;
573 | for (var i = 0, length = getLength(array); i < length; i++) {
574 | var item = array[i];
575 | if (_.contains(result, item)) continue;
576 | for (var j = 1; j < argsLength; j++) {
577 | if (!_.contains(arguments[j], item)) break;
578 | }
579 | if (j === argsLength) result.push(item);
580 | }
581 | return result;
582 | };
583 |
584 | // Take the difference between one array and a number of other arrays.
585 | // Only the elements present in just the first array will remain.
586 | _.difference = function(array) {
587 | var rest = flatten(arguments, true, true, 1);
588 | return _.filter(array, function(value) {
589 | return !_.contains(rest, value);
590 | });
591 | };
592 |
593 | // Zip together multiple lists into a single array -- elements that share
594 | // an index go together.
595 | _.zip = function() {
596 | return _.unzip(arguments);
597 | };
598 |
599 | // Complement of _.zip. Unzip accepts an array of arrays and groups
600 | // each array's elements on shared indices
601 | _.unzip = function(array) {
602 | var length = array && _.max(array, getLength).length || 0;
603 | var result = Array(length);
604 |
605 | for (var index = 0; index < length; index++) {
606 | result[index] = _.pluck(array, index);
607 | }
608 | return result;
609 | };
610 |
611 | // Converts lists into objects. Pass either a single array of `[key, value]`
612 | // pairs, or two parallel arrays of the same length -- one of keys, and one of
613 | // the corresponding values.
614 | _.object = function(list, values) {
615 | var result = {};
616 | for (var i = 0, length = getLength(list); i < length; i++) {
617 | if (values) {
618 | result[list[i]] = values[i];
619 | } else {
620 | result[list[i][0]] = list[i][1];
621 | }
622 | }
623 | return result;
624 | };
625 |
626 | // Generator function to create the findIndex and findLastIndex functions
627 | function createPredicateIndexFinder(dir) {
628 | return function(array, predicate, context) {
629 | predicate = cb(predicate, context);
630 | var length = getLength(array);
631 | var index = dir > 0 ? 0 : length - 1;
632 | for (; index >= 0 && index < length; index += dir) {
633 | if (predicate(array[index], index, array)) return index;
634 | }
635 | return -1;
636 | };
637 | }
638 |
639 | // Returns the first index on an array-like that passes a predicate test
640 | _.findIndex = createPredicateIndexFinder(1);
641 | _.findLastIndex = createPredicateIndexFinder(-1);
642 |
643 | // Use a comparator function to figure out the smallest index at which
644 | // an object should be inserted so as to maintain order. Uses binary search.
645 | _.sortedIndex = function(array, obj, iteratee, context) {
646 | iteratee = cb(iteratee, context, 1);
647 | var value = iteratee(obj);
648 | var low = 0,
649 | high = getLength(array);
650 | while (low < high) {
651 | var mid = Math.floor((low + high) / 2);
652 | if (iteratee(array[mid]) < value) low = mid + 1;
653 | else high = mid;
654 | }
655 | return low;
656 | };
657 |
658 | // Generator function to create the indexOf and lastIndexOf functions
659 | function createIndexFinder(dir, predicateFind, sortedIndex) {
660 | return function(array, item, idx) {
661 | var i = 0,
662 | length = getLength(array);
663 | if (typeof idx == 'number') {
664 | if (dir > 0) {
665 | i = idx >= 0 ? idx : Math.max(idx + length, i);
666 | } else {
667 | length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
668 | }
669 | } else if (sortedIndex && idx && length) {
670 | idx = sortedIndex(array, item);
671 | return array[idx] === item ? idx : -1;
672 | }
673 | if (item !== item) {
674 | idx = predicateFind(slice.call(array, i, length), _.isNaN);
675 | return idx >= 0 ? idx + i : -1;
676 | }
677 | for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
678 | if (array[idx] === item) return idx;
679 | }
680 | return -1;
681 | };
682 | }
683 |
684 | // Return the position of the first occurrence of an item in an array,
685 | // or -1 if the item is not included in the array.
686 | // If the array is large and already in sort order, pass `true`
687 | // for **isSorted** to use binary search.
688 | _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
689 | _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
690 |
691 | // Generate an integer Array containing an arithmetic progression. A port of
692 | // the native Python `range()` function. See
693 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
694 | _.range = function(start, stop, step) {
695 | if (stop == null) {
696 | stop = start || 0;
697 | start = 0;
698 | }
699 | step = step || 1;
700 |
701 | var length = Math.max(Math.ceil((stop - start) / step), 0);
702 | var range = Array(length);
703 |
704 | for (var idx = 0; idx < length; idx++, start += step) {
705 | range[idx] = start;
706 | }
707 |
708 | return range;
709 | };
710 |
711 | // Function (ahem) Functions
712 | // ------------------
713 |
714 | // Determines whether to execute a function as a constructor
715 | // or a normal function with the provided arguments
716 | var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
717 | if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
718 | var self = baseCreate(sourceFunc.prototype);
719 | var result = sourceFunc.apply(self, args);
720 | if (_.isObject(result)) return result;
721 | return self;
722 | };
723 |
724 | // Create a function bound to a given object (assigning `this`, and arguments,
725 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
726 | // available.
727 | _.bind = function(func, context) {
728 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
729 | if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
730 | var args = slice.call(arguments, 2);
731 | var bound = function() {
732 | return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
733 | };
734 | return bound;
735 | };
736 |
737 | // Partially apply a function by creating a version that has had some of its
738 | // arguments pre-filled, without changing its dynamic `this` context. _ acts
739 | // as a placeholder, allowing any combination of arguments to be pre-filled.
740 | _.partial = function(func) {
741 | var boundArgs = slice.call(arguments, 1);
742 | var bound = function() {
743 | var position = 0,
744 | length = boundArgs.length;
745 | var args = Array(length);
746 | for (var i = 0; i < length; i++) {
747 | args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
748 | }
749 | while (position < arguments.length) args.push(arguments[position++]);
750 | return executeBound(func, bound, this, this, args);
751 | };
752 | return bound;
753 | };
754 |
755 | // Bind a number of an object's methods to that object. Remaining arguments
756 | // are the method names to be bound. Useful for ensuring that all callbacks
757 | // defined on an object belong to it.
758 | _.bindAll = function(obj) {
759 | var i, length = arguments.length,
760 | key;
761 | if (length <= 1) throw new Error('bindAll must be passed function names');
762 | for (i = 1; i < length; i++) {
763 | key = arguments[i];
764 | obj[key] = _.bind(obj[key], obj);
765 | }
766 | return obj;
767 | };
768 |
769 | // Memoize an expensive function by storing its results.
770 | _.memoize = function(func, hasher) {
771 | var memoize = function(key) {
772 | var cache = memoize.cache;
773 | var address = '' + (hasher ? hasher.apply(this, arguments) : key);
774 | if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
775 | return cache[address];
776 | };
777 | memoize.cache = {};
778 | return memoize;
779 | };
780 |
781 | // Delays a function for the given number of milliseconds, and then calls
782 | // it with the arguments supplied.
783 | _.delay = function(func, wait) {
784 | var args = slice.call(arguments, 2);
785 | return setTimeout(function() {
786 | return func.apply(null, args);
787 | }, wait);
788 | };
789 |
790 | // Defers a function, scheduling it to run after the current call stack has
791 | // cleared.
792 | _.defer = _.partial(_.delay, _, 1);
793 |
794 | // Returns a function, that, when invoked, will only be triggered at most once
795 | // during a given window of time. Normally, the throttled function will run
796 | // as much as it can, without ever going more than once per `wait` duration;
797 | // but if you'd like to disable the execution on the leading edge, pass
798 | // `{leading: false}`. To disable execution on the trailing edge, ditto.
799 | _.throttle = function(func, wait, options) {
800 | var context, args, result;
801 | var timeout = null;
802 | var previous = 0;
803 | if (!options) options = {};
804 | var later = function() {
805 | previous = options.leading === false ? 0 : _.now();
806 | timeout = null;
807 | result = func.apply(context, args);
808 | if (!timeout) context = args = null;
809 | };
810 | return function() {
811 | var now = _.now();
812 | if (!previous && options.leading === false) previous = now;
813 | var remaining = wait - (now - previous);
814 | context = this;
815 | args = arguments;
816 | if (remaining <= 0 || remaining > wait) {
817 | if (timeout) {
818 | clearTimeout(timeout);
819 | timeout = null;
820 | }
821 | previous = now;
822 | result = func.apply(context, args);
823 | if (!timeout) context = args = null;
824 | } else if (!timeout && options.trailing !== false) {
825 | timeout = setTimeout(later, remaining);
826 | }
827 | return result;
828 | };
829 | };
830 |
831 | // Returns a function, that, as long as it continues to be invoked, will not
832 | // be triggered. The function will be called after it stops being called for
833 | // N milliseconds. If `immediate` is passed, trigger the function on the
834 | // leading edge, instead of the trailing.
835 | _.debounce = function(func, wait, immediate) {
836 | var timeout, args, context, timestamp, result;
837 |
838 | var later = function() {
839 | var last = _.now() - timestamp;
840 |
841 | if (last < wait && last >= 0) {
842 | timeout = setTimeout(later, wait - last);
843 | } else {
844 | timeout = null;
845 | if (!immediate) {
846 | result = func.apply(context, args);
847 | if (!timeout) context = args = null;
848 | }
849 | }
850 | };
851 |
852 | return function() {
853 | context = this;
854 | args = arguments;
855 | timestamp = _.now();
856 | var callNow = immediate && !timeout;
857 | if (!timeout) timeout = setTimeout(later, wait);
858 | if (callNow) {
859 | result = func.apply(context, args);
860 | context = args = null;
861 | }
862 |
863 | return result;
864 | };
865 | };
866 |
867 | // Returns the first function passed as an argument to the second,
868 | // allowing you to adjust arguments, run code before and after, and
869 | // conditionally execute the original function.
870 | _.wrap = function(func, wrapper) {
871 | return _.partial(wrapper, func);
872 | };
873 |
874 | // Returns a negated version of the passed-in predicate.
875 | _.negate = function(predicate) {
876 | return function() {
877 | return !predicate.apply(this, arguments);
878 | };
879 | };
880 |
881 | // Returns a function that is the composition of a list of functions, each
882 | // consuming the return value of the function that follows.
883 | _.compose = function() {
884 | var args = arguments;
885 | var start = args.length - 1;
886 | return function() {
887 | var i = start;
888 | var result = args[start].apply(this, arguments);
889 | while (i--) result = args[i].call(this, result);
890 | return result;
891 | };
892 | };
893 |
894 | // Returns a function that will only be executed on and after the Nth call.
895 | _.after = function(times, func) {
896 | return function() {
897 | if (--times < 1) {
898 | return func.apply(this, arguments);
899 | }
900 | };
901 | };
902 |
903 | // Returns a function that will only be executed up to (but not including) the Nth call.
904 | _.before = function(times, func) {
905 | var memo;
906 | return function() {
907 | if (--times > 0) {
908 | memo = func.apply(this, arguments);
909 | }
910 | if (times <= 1) func = null;
911 | return memo;
912 | };
913 | };
914 |
915 | // Returns a function that will be executed at most one time, no matter how
916 | // often you call it. Useful for lazy initialization.
917 | _.once = _.partial(_.before, 2);
918 |
919 | // Object Functions
920 | // ----------------
921 |
922 | // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
923 | var hasEnumBug = !{ toString: null }.propertyIsEnumerable('toString');
924 | var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
925 | 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'
926 | ];
927 |
928 | function collectNonEnumProps(obj, keys) {
929 | var nonEnumIdx = nonEnumerableProps.length;
930 | var constructor = obj.constructor;
931 | var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;
932 |
933 | // Constructor is a special case.
934 | var prop = 'constructor';
935 | if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
936 |
937 | while (nonEnumIdx--) {
938 | prop = nonEnumerableProps[nonEnumIdx];
939 | if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
940 | keys.push(prop);
941 | }
942 | }
943 | }
944 |
945 | // Retrieve the names of an object's own properties.
946 | // Delegates to **ECMAScript 5**'s native `Object.keys`
947 | _.keys = function(obj) {
948 | if (!_.isObject(obj)) return [];
949 | if (nativeKeys) return nativeKeys(obj);
950 | var keys = [];
951 | for (var key in obj)
952 | if (_.has(obj, key)) keys.push(key);
953 | // Ahem, IE < 9.
954 | if (hasEnumBug) collectNonEnumProps(obj, keys);
955 | return keys;
956 | };
957 |
958 | // Retrieve all the property names of an object.
959 | _.allKeys = function(obj) {
960 | if (!_.isObject(obj)) return [];
961 | var keys = [];
962 | for (var key in obj) keys.push(key);
963 | // Ahem, IE < 9.
964 | if (hasEnumBug) collectNonEnumProps(obj, keys);
965 | return keys;
966 | };
967 |
968 | // Retrieve the values of an object's properties.
969 | _.values = function(obj) {
970 | var keys = _.keys(obj);
971 | var length = keys.length;
972 | var values = Array(length);
973 | for (var i = 0; i < length; i++) {
974 | values[i] = obj[keys[i]];
975 | }
976 | return values;
977 | };
978 |
979 | // Returns the results of applying the iteratee to each element of the object
980 | // In contrast to _.map it returns an object
981 | _.mapObject = function(obj, iteratee, context) {
982 | iteratee = cb(iteratee, context);
983 | var keys = _.keys(obj),
984 | length = keys.length,
985 | results = {},
986 | currentKey;
987 | for (var index = 0; index < length; index++) {
988 | currentKey = keys[index];
989 | results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
990 | }
991 | return results;
992 | };
993 |
994 | // Convert an object into a list of `[key, value]` pairs.
995 | _.pairs = function(obj) {
996 | var keys = _.keys(obj);
997 | var length = keys.length;
998 | var pairs = Array(length);
999 | for (var i = 0; i < length; i++) {
1000 | pairs[i] = [keys[i], obj[keys[i]]];
1001 | }
1002 | return pairs;
1003 | };
1004 |
1005 | // Invert the keys and values of an object. The values must be serializable.
1006 | _.invert = function(obj) {
1007 | var result = {};
1008 | var keys = _.keys(obj);
1009 | for (var i = 0, length = keys.length; i < length; i++) {
1010 | result[obj[keys[i]]] = keys[i];
1011 | }
1012 | return result;
1013 | };
1014 |
1015 | // Return a sorted list of the function names available on the object.
1016 | // Aliased as `methods`
1017 | _.functions = _.methods = function(obj) {
1018 | var names = [];
1019 | for (var key in obj) {
1020 | if (_.isFunction(obj[key])) names.push(key);
1021 | }
1022 | return names.sort();
1023 | };
1024 |
1025 | // Extend a given object with all the properties in passed-in object(s).
1026 | _.extend = createAssigner(_.allKeys);
1027 |
1028 | // Assigns a given object with all the own properties in the passed-in object(s)
1029 | // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
1030 | _.extendOwn = _.assign = createAssigner(_.keys);
1031 |
1032 | // Returns the first key on an object that passes a predicate test
1033 | _.findKey = function(obj, predicate, context) {
1034 | predicate = cb(predicate, context);
1035 | var keys = _.keys(obj),
1036 | key;
1037 | for (var i = 0, length = keys.length; i < length; i++) {
1038 | key = keys[i];
1039 | if (predicate(obj[key], key, obj)) return key;
1040 | }
1041 | };
1042 |
1043 | // Return a copy of the object only containing the whitelisted properties.
1044 | _.pick = function(object, oiteratee, context) {
1045 | var result = {},
1046 | obj = object,
1047 | iteratee, keys;
1048 | if (obj == null) return result;
1049 | if (_.isFunction(oiteratee)) {
1050 | keys = _.allKeys(obj);
1051 | iteratee = optimizeCb(oiteratee, context);
1052 | } else {
1053 | keys = flatten(arguments, false, false, 1);
1054 | iteratee = function(value, key, obj) { return key in obj; };
1055 | obj = Object(obj);
1056 | }
1057 | for (var i = 0, length = keys.length; i < length; i++) {
1058 | var key = keys[i];
1059 | var value = obj[key];
1060 | if (iteratee(value, key, obj)) result[key] = value;
1061 | }
1062 | return result;
1063 | };
1064 |
1065 | // Return a copy of the object without the blacklisted properties.
1066 | _.omit = function(obj, iteratee, context) {
1067 | if (_.isFunction(iteratee)) {
1068 | iteratee = _.negate(iteratee);
1069 | } else {
1070 | var keys = _.map(flatten(arguments, false, false, 1), String);
1071 | iteratee = function(value, key) {
1072 | return !_.contains(keys, key);
1073 | };
1074 | }
1075 | return _.pick(obj, iteratee, context);
1076 | };
1077 |
1078 | // Fill in a given object with default properties.
1079 | _.defaults = createAssigner(_.allKeys, true);
1080 |
1081 | // Creates an object that inherits from the given prototype object.
1082 | // If additional properties are provided then they will be added to the
1083 | // created object.
1084 | _.create = function(prototype, props) {
1085 | var result = baseCreate(prototype);
1086 | if (props) _.extendOwn(result, props);
1087 | return result;
1088 | };
1089 |
1090 | // Create a (shallow-cloned) duplicate of an object.
1091 | _.clone = function(obj) {
1092 | if (!_.isObject(obj)) return obj;
1093 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
1094 | };
1095 |
1096 | // Invokes interceptor with the obj, and then returns obj.
1097 | // The primary purpose of this method is to "tap into" a method chain, in
1098 | // order to perform operations on intermediate results within the chain.
1099 | _.tap = function(obj, interceptor) {
1100 | interceptor(obj);
1101 | return obj;
1102 | };
1103 |
1104 | // Returns whether an object has a given set of `key:value` pairs.
1105 | _.isMatch = function(object, attrs) {
1106 | var keys = _.keys(attrs),
1107 | length = keys.length;
1108 | if (object == null) return !length;
1109 | var obj = Object(object);
1110 | for (var i = 0; i < length; i++) {
1111 | var key = keys[i];
1112 | if (attrs[key] !== obj[key] || !(key in obj)) return false;
1113 | }
1114 | return true;
1115 | };
1116 |
1117 |
1118 | // Internal recursive comparison function for `isEqual`.
1119 | var eq = function(a, b, aStack, bStack) {
1120 | // Identical objects are equal. `0 === -0`, but they aren't identical.
1121 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
1122 | if (a === b) return a !== 0 || 1 / a === 1 / b;
1123 | // A strict comparison is necessary because `null == undefined`.
1124 | if (a == null || b == null) return a === b;
1125 | // Unwrap any wrapped objects.
1126 | if (a instanceof _) a = a._wrapped;
1127 | if (b instanceof _) b = b._wrapped;
1128 | // Compare `[[Class]]` names.
1129 | var className = toString.call(a);
1130 | if (className !== toString.call(b)) return false;
1131 | switch (className) {
1132 | // Strings, numbers, regular expressions, dates, and booleans are compared by value.
1133 | case '[object RegExp]':
1134 | // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
1135 | case '[object String]':
1136 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
1137 | // equivalent to `new String("5")`.
1138 | return '' + a === '' + b;
1139 | case '[object Number]':
1140 | // `NaN`s are equivalent, but non-reflexive.
1141 | // Object(NaN) is equivalent to NaN
1142 | if (+a !== +a) return +b !== +b;
1143 | // An `egal` comparison is performed for other numeric values.
1144 | return +a === 0 ? 1 / +a === 1 / b : +a === +b;
1145 | case '[object Date]':
1146 | case '[object Boolean]':
1147 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
1148 | // millisecond representations. Note that invalid dates with millisecond representations
1149 | // of `NaN` are not equivalent.
1150 | return +a === +b;
1151 | }
1152 |
1153 | var areArrays = className === '[object Array]';
1154 | if (!areArrays) {
1155 | if (typeof a != 'object' || typeof b != 'object') return false;
1156 |
1157 | // Objects with different constructors are not equivalent, but `Object`s or `Array`s
1158 | // from different frames are.
1159 | var aCtor = a.constructor,
1160 | bCtor = b.constructor;
1161 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
1162 | _.isFunction(bCtor) && bCtor instanceof bCtor) &&
1163 | ('constructor' in a && 'constructor' in b)) {
1164 | return false;
1165 | }
1166 | }
1167 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
1168 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
1169 |
1170 | // Initializing stack of traversed objects.
1171 | // It's done here since we only need them for objects and arrays comparison.
1172 | aStack = aStack || [];
1173 | bStack = bStack || [];
1174 | var length = aStack.length;
1175 | while (length--) {
1176 | // Linear search. Performance is inversely proportional to the number of
1177 | // unique nested structures.
1178 | if (aStack[length] === a) return bStack[length] === b;
1179 | }
1180 |
1181 | // Add the first object to the stack of traversed objects.
1182 | aStack.push(a);
1183 | bStack.push(b);
1184 |
1185 | // Recursively compare objects and arrays.
1186 | if (areArrays) {
1187 | // Compare array lengths to determine if a deep comparison is necessary.
1188 | length = a.length;
1189 | if (length !== b.length) return false;
1190 | // Deep compare the contents, ignoring non-numeric properties.
1191 | while (length--) {
1192 | if (!eq(a[length], b[length], aStack, bStack)) return false;
1193 | }
1194 | } else {
1195 | // Deep compare objects.
1196 | var keys = _.keys(a),
1197 | key;
1198 | length = keys.length;
1199 | // Ensure that both objects contain the same number of properties before comparing deep equality.
1200 | if (_.keys(b).length !== length) return false;
1201 | while (length--) {
1202 | // Deep compare each member
1203 | key = keys[length];
1204 | if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
1205 | }
1206 | }
1207 | // Remove the first object from the stack of traversed objects.
1208 | aStack.pop();
1209 | bStack.pop();
1210 | return true;
1211 | };
1212 |
1213 | // Perform a deep comparison to check if two objects are equal.
1214 | _.isEqual = function(a, b) {
1215 | return eq(a, b);
1216 | };
1217 |
1218 | // Is a given array, string, or object empty?
1219 | // An "empty" object has no enumerable own-properties.
1220 | _.isEmpty = function(obj) {
1221 | if (obj == null) return true;
1222 | if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
1223 | return _.keys(obj).length === 0;
1224 | };
1225 |
1226 | // Is a given value a DOM element?
1227 | _.isElement = function(obj) {
1228 | return !!(obj && obj.nodeType === 1);
1229 | };
1230 |
1231 | // Is a given value an array?
1232 | // Delegates to ECMA5's native Array.isArray
1233 | _.isArray = nativeIsArray || function(obj) {
1234 | return toString.call(obj) === '[object Array]';
1235 | };
1236 |
1237 | // Is a given variable an object?
1238 | _.isObject = function(obj) {
1239 | var type = typeof obj;
1240 | return type === 'function' || type === 'object' && !!obj;
1241 | };
1242 |
1243 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
1244 | _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
1245 | _['is' + name] = function(obj) {
1246 | return toString.call(obj) === '[object ' + name + ']';
1247 | };
1248 | });
1249 |
1250 | // Define a fallback version of the method in browsers (ahem, IE < 9), where
1251 | // there isn't any inspectable "Arguments" type.
1252 | if (!_.isArguments(arguments)) {
1253 | _.isArguments = function(obj) {
1254 | return _.has(obj, 'callee');
1255 | };
1256 | }
1257 |
1258 | // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
1259 | // IE 11 (#1621), and in Safari 8 (#1929).
1260 | if (typeof /./ != 'function' && typeof Int8Array != 'object') {
1261 | _.isFunction = function(obj) {
1262 | return typeof obj == 'function' || false;
1263 | };
1264 | }
1265 |
1266 | // Is a given object a finite number?
1267 | _.isFinite = function(obj) {
1268 | return isFinite(obj) && !isNaN(parseFloat(obj));
1269 | };
1270 |
1271 | // Is the given value `NaN`? (NaN is the only number which does not equal itself).
1272 | _.isNaN = function(obj) {
1273 | return _.isNumber(obj) && obj !== +obj;
1274 | };
1275 |
1276 | // Is a given value a boolean?
1277 | _.isBoolean = function(obj) {
1278 | return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
1279 | };
1280 |
1281 | // Is a given value equal to null?
1282 | _.isNull = function(obj) {
1283 | return obj === null;
1284 | };
1285 |
1286 | // Is a given variable undefined?
1287 | _.isUndefined = function(obj) {
1288 | return obj === void 0;
1289 | };
1290 |
1291 | // Shortcut function for checking if an object has a given property directly
1292 | // on itself (in other words, not on a prototype).
1293 | _.has = function(obj, key) {
1294 | return obj != null && hasOwnProperty.call(obj, key);
1295 | };
1296 |
1297 | // Utility Functions
1298 | // -----------------
1299 |
1300 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
1301 | // previous owner. Returns a reference to the Underscore object.
1302 | _.noConflict = function() {
1303 | root._ = previousUnderscore;
1304 | return this;
1305 | };
1306 |
1307 | // Keep the identity function around for default iteratees.
1308 | _.identity = function(value) {
1309 | return value;
1310 | };
1311 |
1312 | // Predicate-generating functions. Often useful outside of Underscore.
1313 | _.constant = function(value) {
1314 | return function() {
1315 | return value;
1316 | };
1317 | };
1318 |
1319 | _.noop = function() {};
1320 |
1321 | _.property = property;
1322 |
1323 | // Generates a function for a given object that returns a given property.
1324 | _.propertyOf = function(obj) {
1325 | return obj == null ? function() {} : function(key) {
1326 | return obj[key];
1327 | };
1328 | };
1329 |
1330 | // Returns a predicate for checking whether an object has a given set of
1331 | // `key:value` pairs.
1332 | _.matcher = _.matches = function(attrs) {
1333 | attrs = _.extendOwn({}, attrs);
1334 | return function(obj) {
1335 | return _.isMatch(obj, attrs);
1336 | };
1337 | };
1338 |
1339 | // Run a function **n** times.
1340 | _.times = function(n, iteratee, context) {
1341 | var accum = Array(Math.max(0, n));
1342 | iteratee = optimizeCb(iteratee, context, 1);
1343 | for (var i = 0; i < n; i++) accum[i] = iteratee(i);
1344 | return accum;
1345 | };
1346 |
1347 | // Return a random integer between min and max (inclusive).
1348 | _.random = function(min, max) {
1349 | if (max == null) {
1350 | max = min;
1351 | min = 0;
1352 | }
1353 | return min + Math.floor(Math.random() * (max - min + 1));
1354 | };
1355 |
1356 | // A (possibly faster) way to get the current timestamp as an integer.
1357 | _.now = Date.now || function() {
1358 | return new Date().getTime();
1359 | };
1360 |
1361 | // List of HTML entities for escaping.
1362 | var escapeMap = {
1363 | '&': '&',
1364 | '<': '<',
1365 | '>': '>',
1366 | '"': '"',
1367 | "'": ''',
1368 | '`': '`'
1369 | };
1370 | var unescapeMap = _.invert(escapeMap);
1371 |
1372 | // Functions for escaping and unescaping strings to/from HTML interpolation.
1373 | var createEscaper = function(map) {
1374 | var escaper = function(match) {
1375 | return map[match];
1376 | };
1377 | // Regexes for identifying a key that needs to be escaped
1378 | var source = '(?:' + _.keys(map).join('|') + ')';
1379 | var testRegexp = RegExp(source);
1380 | var replaceRegexp = RegExp(source, 'g');
1381 | return function(string) {
1382 | string = string == null ? '' : '' + string;
1383 | return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
1384 | };
1385 | };
1386 | _.escape = createEscaper(escapeMap);
1387 | _.unescape = createEscaper(unescapeMap);
1388 |
1389 | // If the value of the named `property` is a function then invoke it with the
1390 | // `object` as context; otherwise, return it.
1391 | _.result = function(object, property, fallback) {
1392 | var value = object == null ? void 0 : object[property];
1393 | if (value === void 0) {
1394 | value = fallback;
1395 | }
1396 | return _.isFunction(value) ? value.call(object) : value;
1397 | };
1398 |
1399 | // Generate a unique integer id (unique within the entire client session).
1400 | // Useful for temporary DOM ids.
1401 | var idCounter = 0;
1402 | _.uniqueId = function(prefix) {
1403 | var id = ++idCounter + '';
1404 | return prefix ? prefix + id : id;
1405 | };
1406 |
1407 | // By default, Underscore uses ERB-style template delimiters, change the
1408 | // following template settings to use alternative delimiters.
1409 | _.templateSettings = {
1410 | evaluate: /<%([\s\S]+?)%>/g,
1411 | interpolate: /<%=([\s\S]+?)%>/g,
1412 | escape: /<%-([\s\S]+?)%>/g
1413 | };
1414 |
1415 | // When customizing `templateSettings`, if you don't want to define an
1416 | // interpolation, evaluation or escaping regex, we need one that is
1417 | // guaranteed not to match.
1418 | var noMatch = /(.)^/;
1419 |
1420 | // Certain characters need to be escaped so that they can be put into a
1421 | // string literal.
1422 | var escapes = {
1423 | "'": "'",
1424 | '\\': '\\',
1425 | '\r': 'r',
1426 | '\n': 'n',
1427 | '\u2028': 'u2028',
1428 | '\u2029': 'u2029'
1429 | };
1430 |
1431 | var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
1432 |
1433 | var escapeChar = function(match) {
1434 | return '\\' + escapes[match];
1435 | };
1436 |
1437 | // JavaScript micro-templating, similar to John Resig's implementation.
1438 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
1439 | // and correctly escapes quotes within interpolated code.
1440 | // NB: `oldSettings` only exists for backwards compatibility.
1441 | _.template = function(text, settings, oldSettings) {
1442 | if (!settings && oldSettings) settings = oldSettings;
1443 | settings = _.defaults({}, settings, _.templateSettings);
1444 |
1445 | // Combine delimiters into one regular expression via alternation.
1446 | var matcher = RegExp([
1447 | (settings.escape || noMatch).source,
1448 | (settings.interpolate || noMatch).source,
1449 | (settings.evaluate || noMatch).source
1450 | ].join('|') + '|$', 'g');
1451 |
1452 | // Compile the template source, escaping string literals appropriately.
1453 | var index = 0;
1454 | var source = "__p+='";
1455 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
1456 | source += text.slice(index, offset).replace(escaper, escapeChar);
1457 | index = offset + match.length;
1458 |
1459 | if (escape) {
1460 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
1461 | } else if (interpolate) {
1462 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
1463 | } else if (evaluate) {
1464 | source += "';\n" + evaluate + "\n__p+='";
1465 | }
1466 |
1467 | // Adobe VMs need the match returned to produce the correct offest.
1468 | return match;
1469 | });
1470 | source += "';\n";
1471 |
1472 | // If a variable is not specified, place data values in local scope.
1473 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
1474 |
1475 | source = "var __t,__p='',__j=Array.prototype.join," +
1476 | "print=function(){__p+=__j.call(arguments,'');};\n" +
1477 | source + 'return __p;\n';
1478 |
1479 | try {
1480 | var render = new Function(settings.variable || 'obj', '_', source);
1481 | } catch (e) {
1482 | e.source = source;
1483 | throw e;
1484 | }
1485 |
1486 | var template = function(data) {
1487 | return render.call(this, data, _);
1488 | };
1489 |
1490 | // Provide the compiled source as a convenience for precompilation.
1491 | var argument = settings.variable || 'obj';
1492 | template.source = 'function(' + argument + '){\n' + source + '}';
1493 |
1494 | return template;
1495 | };
1496 |
1497 | // Add a "chain" function. Start chaining a wrapped Underscore object.
1498 | _.chain = function(obj) {
1499 | var instance = _(obj);
1500 | instance._chain = true;
1501 | return instance;
1502 | };
1503 |
1504 | // OOP
1505 | // ---------------
1506 | // If Underscore is called as a function, it returns a wrapped object that
1507 | // can be used OO-style. This wrapper holds altered versions of all the
1508 | // underscore functions. Wrapped objects may be chained.
1509 |
1510 | // Helper function to continue chaining intermediate results.
1511 | var result = function(instance, obj) {
1512 | return instance._chain ? _(obj).chain() : obj;
1513 | };
1514 |
1515 | // Add your own custom functions to the Underscore object.
1516 | _.mixin = function(obj) {
1517 | _.each(_.functions(obj), function(name) {
1518 | var func = _[name] = obj[name];
1519 | _.prototype[name] = function() {
1520 | var args = [this._wrapped];
1521 | push.apply(args, arguments);
1522 | return result(this, func.apply(_, args));
1523 | };
1524 | });
1525 | };
1526 |
1527 | // Add all of the Underscore functions to the wrapper object.
1528 | _.mixin(_);
1529 |
1530 | // Add all mutator Array functions to the wrapper.
1531 | _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
1532 | var method = ArrayProto[name];
1533 | _.prototype[name] = function() {
1534 | var obj = this._wrapped;
1535 | method.apply(obj, arguments);
1536 | if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
1537 | return result(this, obj);
1538 | };
1539 | });
1540 |
1541 | // Add all accessor Array functions to the wrapper.
1542 | _.each(['concat', 'join', 'slice'], function(name) {
1543 | var method = ArrayProto[name];
1544 | _.prototype[name] = function() {
1545 | return result(this, method.apply(this._wrapped, arguments));
1546 | };
1547 | });
1548 |
1549 | // Extracts the result from a wrapped and chained object.
1550 | _.prototype.value = function() {
1551 | return this._wrapped;
1552 | };
1553 |
1554 | // Provide unwrapping proxy for some methods used in engine operations
1555 | // such as arithmetic and JSON stringification.
1556 | _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
1557 |
1558 | _.prototype.toString = function() {
1559 | return '' + this._wrapped;
1560 | };
1561 |
1562 | // AMD registration happens at the end for compatibility with AMD loaders
1563 | // that may not enforce next-turn semantics on modules. Even though general
1564 | // practice for AMD registration is to be anonymous, underscore registers
1565 | // as a named module because, like jQuery, it is a base library that is
1566 | // popular enough to be bundled in a third party lib, but not be part of
1567 | // an AMD load request. Those cases could generate an error when an
1568 | // anonymous define() is called outside of a loader request.
1569 | if (typeof define === 'function' && define.amd) {
1570 | define('underscore', [], function() {
1571 | return _;
1572 | });
1573 | }
1574 | }.call(this));
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | function formatTime(date) {
2 | var year = date.getFullYear()
3 | var month = date.getMonth() + 1
4 | var day = date.getDate()
5 |
6 | var hour = date.getHours()
7 | var minute = date.getMinutes()
8 | var second = date.getSeconds()
9 |
10 |
11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
12 | }
13 |
14 | function formatNumber(n) {
15 | n = n.toString()
16 | return n[1] ? n : '0' + n
17 | }
18 |
19 | module.exports = {
20 | formatTime: formatTime
21 | }
22 |
--------------------------------------------------------------------------------