diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Reduck/components/button/button.ejs b/Reduck/components/button/button.ejs new file mode 100644 index 0000000..df9ba7d --- /dev/null +++ b/Reduck/components/button/button.ejs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Reduck/components/button/entry.js b/Reduck/components/button/entry.js new file mode 100644 index 0000000..389ca14 --- /dev/null +++ b/Reduck/components/button/entry.js @@ -0,0 +1,4 @@ +var tpl = require('./button.ejs'); +var style = require('./style/button.css'); +document.write(tpl({text: 'test'})); +console.log(tpl({text: 'test'})); \ No newline at end of file diff --git a/Reduck/components/button/style/button.css b/Reduck/components/button/style/button.css new file mode 100644 index 0000000..976af71 --- /dev/null +++ b/Reduck/components/button/style/button.css @@ -0,0 +1 @@ +button{ padding:10px;} \ No newline at end of file diff --git a/Reduck/components/test-react/entry.js b/Reduck/components/test-react/entry.js new file mode 100644 index 0000000..e69de29 diff --git a/Reduck/components/test/entry.js b/Reduck/components/test/entry.js new file mode 100644 index 0000000..e69de29 diff --git a/Reduck/dist-webpack/js/test.js b/Reduck/dist-webpack/js/test.js new file mode 100644 index 0000000..ac9fc80 --- /dev/null +++ b/Reduck/dist-webpack/js/test.js @@ -0,0 +1,109 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // install a JSONP callback for chunk loading +/******/ var parentJsonpFunction = window["webpackJsonp"]; +/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) { +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0, callbacks = []; +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(installedChunks[chunkId]) +/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]); +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ for(moduleId in moreModules) { +/******/ modules[moduleId] = moreModules[moduleId]; +/******/ } +/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules); +/******/ while(callbacks.length) +/******/ callbacks.shift().call(null, __webpack_require__); + +/******/ }; + +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // object to store loaded and loading chunks +/******/ // "0" means "already loaded" +/******/ // Array means "loading", array contains callbacks +/******/ var installedChunks = { +/******/ 0:0 +/******/ }; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + +/******/ // This file contains only the entry chunk. +/******/ // The chunk loading function for additional chunks +/******/ __webpack_require__.e = function requireEnsure(chunkId, callback) { +/******/ // "0" is the signal for "already loaded" +/******/ if(installedChunks[chunkId] === 0) +/******/ return callback.call(null, __webpack_require__); + +/******/ // an array means "currently loading". +/******/ if(installedChunks[chunkId] !== undefined) { +/******/ installedChunks[chunkId].push(callback); +/******/ } else { +/******/ // start chunk loading +/******/ installedChunks[chunkId] = [callback]; +/******/ var head = document.getElementsByTagName('head')[0]; +/******/ var script = document.createElement('script'); +/******/ script.type = 'text/javascript'; +/******/ script.charset = 'utf-8'; +/******/ script.async = true; + +/******/ script.src = __webpack_require__.p + "" + chunkId + "." + ({}[chunkId]||chunkId) + ".js"; +/******/ head.appendChild(script); +/******/ } +/******/ }; + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + __webpack_require__.e/* require */(1, function(__webpack_require__) { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [__webpack_require__(1)]; (function(require) { + var content = __webpack_require__(1); + //document.open(); + document.write('

' + content + '

'); + //document.close(); + }.apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__));}); + console.log(2); + + + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/Reduck/dist-webpack/js/util.js b/Reduck/dist-webpack/js/util.js new file mode 100644 index 0000000..d3e6bca --- /dev/null +++ b/Reduck/dist-webpack/js/util.js @@ -0,0 +1,108 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; +/******/ +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.loaded = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/*!*******************************!*\ + !*** ../src/js/util/state.js ***! + \*******************************/ +/***/ function(module, exports) { + + /** + * 状态机 + * Created by donaldcen on 2015/12/25 + */ + //(function () { + + + /** + * 状态机 + * @constructor + */ + function State(status, start) { + this.tasks = []; + this.index = start || 0; + if (typeof status == 'object') { + + } else { + this.set(0, status); + } + } + + State.prototype = { + set: function (index, value) { + var me = this; + if (typeof index == 'object') { + Object.keys(index).forEach(function (key) { + me.tasks[key] = index[key]; + }); + } else { + this.tasks[Number(index)] = value; + } + return this; + }, + get: function (index) { + index = (typeof index != 'undefined') ? index : this.index; + return this.tasks[index]; + }, + reset: function () { + this.index = 0; + return this; + }, + next: function () { + return this.tasks(++this.index); + } + }; + + + //if (typeof module == 'object' && module.exports) { + // module.exports = State; + //} else { + // return State; + //} + module.exports = State; + //})(); + + +/***/ } +/******/ ]); +//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/Reduck/dist/js/frame/index.js b/Reduck/dist/js/frame/index.js new file mode 100644 index 0000000..53daa02 --- /dev/null +++ b/Reduck/dist/js/frame/index.js @@ -0,0 +1,50 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports) { + + + +/***/ } +/******/ ]); \ No newline at end of file diff --git a/Reduck/package.json b/Reduck/package.json new file mode 100644 index 0000000..9692508 --- /dev/null +++ b/Reduck/package.json @@ -0,0 +1,22 @@ +{ + "name": "tools", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "css-loader": "^0.23.1", + "ejs-loader": "^0.2.1", + "glob": "^6.0.3", + "gulp": "^3.9.0", + "jsx-loader": "^0.13.2", + "react": "^0.14.5", + "style-loader": "^0.13.0", + "webpack": "^1.12.9" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "donaldcen", + "license": "ISC" +} diff --git a/Reduck/src/js/frame/dispatcher/Dispatcher.js b/Reduck/src/js/frame/dispatcher/Dispatcher.js new file mode 100644 index 0000000..8529d65 --- /dev/null +++ b/Reduck/src/js/frame/dispatcher/Dispatcher.js @@ -0,0 +1,15 @@ +/** + * 项目调度器 单例唯一的 + */ +define(function(require, exports, module){ + var Dispatcher = require('frame/lib').Dispatcher; + + var AppDispatcher = new Dispatcher(); + + /** + * 调度器使用规范 + * 触发 + * Dispatcher.dispatch({moduleId: "router", actionId: actionId, params: params}); + */ + module.exports = AppDispatcher; +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/frameDispatcher.js b/Reduck/src/js/frame/frameDispatcher.js new file mode 100644 index 0000000..45e0719 --- /dev/null +++ b/Reduck/src/js/frame/frameDispatcher.js @@ -0,0 +1,65 @@ +define(function (require, exports, module) { + var Dispatcher = require('frame/dispatcher/Dispatcher'); + //注册总线事件 + Dispatcher.register(function (payload) { + var moduleId = payload['moduleId']; + var actionId = payload['actionId']; + var actionModule = actions[moduleId]; + var action = actionModule && actions[moduleId][actionId]; + if (typeof action == 'function') { + action.apply(actionModule, payload['params']); + } + }); + + var actions = {}; + + module.exports = { + /** + * 添加处理方法 + * @param {String} moduleId 模块id + * @param {String} actionId 操作id + * @param {Function} fn 操作方法 + */ + add: function (moduleId, actionId, fn) { + if (typeof fn == 'function') { + (!actions[moduleId]) && (actions[moduleId] = {}); + actions[moduleId][actionId] = fn; + } + }, + /** + * 批量添加 + * @param {String} moduleId 模块id + * @param {Object} obj 模块对象 + */ + addAll: function (moduleId, obj) { + for (var i in obj) { + if (obj.hasOwnProperty(i + '')) { + this.add(moduleId, i, obj[i].bind(obj)); + } + } + }, + /** + * 移出方法 + * @param {} moduleId + * @param actionId + */ + remove: function (moduleId, actionId) { + if (actions.moduleId && actions.moduleId.actionId) { + actions.moduleId.actionId = null; + delete actions.moduleId.actionId; + } + }, + get: function (moduleId, actionId) { + if(moduleId){ + if(actionId && actions[moduleId]){ + return actions[moduleId][actionId]; + }else{ + return actions[moduleId]; + } + }else{ + return actions; + } + } + }; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/index.js b/Reduck/src/js/frame/index.js new file mode 100644 index 0000000..a11b9ca --- /dev/null +++ b/Reduck/src/js/frame/index.js @@ -0,0 +1,26 @@ +/** + * 框架驱动 + * created by donaldcen on 2015/12/17 + */ +define(function(require, exports, module){ + + var Frame = require('frame/main'), + ViewModel = require('frame/model/ViewModel'), + PageModel = require('frame/model/PageModel'), + DataModel = require('frame/model/DataModel'), + Dispatcher = require('frame/dispatcher/Dispatcher'), + undefined; + + module.exports = { + Frame: Frame, + ViewModel: ViewModel, + PageModel: PageModel, + DataModel: DataModel, + Dispatcher: Dispatcher, + lib: { + _: require('lib/underscore'), + Dispatcher: require('lib/Dispatcher') + } + }; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/lib.js b/Reduck/src/js/frame/lib.js new file mode 100644 index 0000000..ee4645d --- /dev/null +++ b/Reduck/src/js/frame/lib.js @@ -0,0 +1,15 @@ +/** + * 框架驱动 + * created by donaldcen on 2015/12/17 + */ +define(function(require, exports, module){ + + module.exports = { + _: require('lib/underscore'), + Dispatcher: require('lib/Dispatcher'), + Backbone : require('lib/backbone'), + React: require('lib/react-addons'), + ReactDOM: require('lib/react-dom') + }; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/loadScript.js b/Reduck/src/js/frame/loadScript.js new file mode 100644 index 0000000..8e628eb --- /dev/null +++ b/Reduck/src/js/frame/loadScript.js @@ -0,0 +1,82 @@ +(function (window) { + /** + * 拉取js + * @param {String} url + * @param {Function} callback 回调 + */ + function getScript(url, callback) { + var head = document.getElementsByTagName('head')[0], + js = document.createElement('script'); + + js.src = url; + js.async = 'async'; + + head.appendChild(js); + + js.onload = function () { + js.onerror = js.onabort = js.onload = null; + callback(null, url); + }; + js.onerror = js.onabort = function () { + js.onerror = js.onabort = js.onload = null; + js.parentNode.removeChild(js); + callback('ERROR:', url); + }; + } + + var cache = {}; + + function saveCache(url) { + (!cache[url]) && (cache[url] = {}); + cache[url].url = url; + } + + /** + * 拉取多个script + * @param list + * @param callback + */ + function loadScriptList(list, callback) { + if (typeof list == 'string') { + list = [list]; + } + var len = list.length; + var errorUrl = []; + var cb = function (err, url) { + if (err) { + errorUrl.push(url); + }else{ + saveCache(url); + } + len--; + if (len <= 0) { + (errorUrl.length == 0) && (errorUrl = null); + console.log('拉取完成,错误:', JSON.stringify(errorUrl)); + callback(errorUrl); + } + }; + //没长度不需要拉取 + if (!len) { + cb(null); + return false; + } + console.log('拉取script', list); + list.forEach(function (item) { + if(!cache.item){ + getScript(item, function (err, url) { + if (err) {//出错重试 + getScript(url, cb); + } else { + cb(null, url); + } + }); + } + }); + } + + function loadScriptByCDN(list, callback) { + + } + + window.loadScript = loadScriptList; +})(window); \ No newline at end of file diff --git a/Reduck/src/js/frame/loadStyle.js b/Reduck/src/js/frame/loadStyle.js new file mode 100644 index 0000000..879c21a --- /dev/null +++ b/Reduck/src/js/frame/loadStyle.js @@ -0,0 +1,49 @@ +/** + * 样式加载模块 + * created by donaldcen on 2015/11/18 + */ +define(function (require, exports, module) { + + var cssCache = {}; + var idlen = 0; + var stylePrev = 'style-'; + module.exports = { + addStyle: function (styleSheet) { + var id = styleSheet.id; + var dom; + idlen++; + if (cssCache[id]) { + dom = document.getElementById(stylePrev + id); + } + if (!dom) { + dom = document.createElement('style'); + dom.id = stylePrev + id; + dom.innerHTML = styleSheet.style; + document.head.appendChild(dom); + }else{ + dom.innerHTML = styleSheet.style; + } + styleSheet.dom = dom; + cssCache[id] = styleSheet; + }, + addStyles: function(cssList){ + var me = this; + (cssList || []).forEach(function(styleSheet){ + me.addStyle(styleSheet); + }); + }, + removeStyle: function(ids){ + var me = this; + if(typeof ids == 'string'){ + ids = [ids]; + } + (ids || []).forEach(function(id){ + if(cssCache[id]){ + cssCache[id].dom.remove(); + cssCache[id] = null; + delete cssCache[id]; + } + }); + } + }; +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/main.js b/Reduck/src/js/frame/main.js new file mode 100644 index 0000000..956fb80 --- /dev/null +++ b/Reduck/src/js/frame/main.js @@ -0,0 +1,276 @@ +/** + * 框架 + * created by donaldcen on 2015/11/9 + */ +define(function (require, exports, module) { + var uri = require('util/uri'), + Dispatcher = require('frame/dispatcher/Dispatcher'); + + var Router = require('frame/router'), + frameDispatcher = require('frame/frameDispatcher'), + Style = require('frame/loadStyle'), + pageCut = require('frame/pageCut'), + undefined; + var UNSUPPORTED = function () { + /* console.error('尚未实现,待具体业务实现')*/ + }; + + + /** + * 框架 + * @constructor + */ + function Frame(config) { + this.userconfig = config || {}; + this._params = {}; + this._uri = {}; + this.getParams(); + this.debug(); + this.initRouter(); + this.load(true); + this.initStore(); + + } + + Frame.prototype = { + dispatcher: frameDispatcher, + style: Style, + module: null, + container: '#container', + /** + * 获取URL参数 + */ + getParams: function () { + this._uri = uri.parseUrl(location.href); + this._params = uri.parseQueryString(location.search); + this._params.module = this._params.module || this.userconfig.normalModule || 'home'; + }, + + debug: function () { + var me = this; + //debug环境 + if (typeof this._params.debug == 'undefined' && window.location.href.indexOf('/src/') < 0) { + window.console = window.console || {}; + window.console.log = function () { + }; + window.console.debug = function () { + }; + window.console.info = function () { + }; + } else { + console.info('开启debug模式'); + this.__debugLoaded = true; + var testDom = document.createElement('button'); + testDom.id = 'devClearCache'; + testDom.innerText = '测'; + testDom.onclick = function () { + me.clearStorage(); + }; + var style = { + position: 'fixed', + top: '40px', + left: '5px', + color: 'white', + opacity: .6, + backgroundColor: '#f60', + borderRadius: '5px', + padding: '5px 10px', + 'z-index': 999 + }; + Object.keys(style).forEach(function (key) { + testDom.style[key] = style[key]; + }); + document.body.appendChild(testDom); + } + }, + clearStorage: function () { + console.log('clear all localStorage'); + Object.keys(localStorage).forEach(function (key) { + localStorage.removeItem(key); + }); + window.localStorage && (window.localStorage.clear()); + window.sessionStorage && (window.sessionStorage.clear()); + }, + /** + * 初始化路由 + */ + initRouter: function () { + this._router = new Router({ + //页面history切换时运行load + popstate: this.load.bind(this) + }); + window.Router = this._router; + }, + /** + * 跳转模块 + * @param {String} moduleId + * @param {Object} params + */ + goModule: function (moduleId, params) { + this._router.set({y: window.scrollY}); + this._router.go(moduleId, '', params); + //this.showLoading && this.showLoading(); //不需要每次都loading + this.load(); + }, + + /** + * 后退调用 + */ + back: function () { + if(typeof this.module.beforeBack == 'function' && !this.module.beforeBack()){ + return true; + } + console.log('back', this._router.getPageIndex()); + if (this._router.getPageIndex()) { + this._router.back(); + //返回true禁止mqq后退; + return true; + } else { + //已经退到底了 + return false; + } + }, + + /** + * 拉取模块 + */ + load: function (reload) { + var moduleId = this._params.module; + //重置加载时间 + window._commonPoints = {head: Date.now()}; + this.getParams(); + /*onpopstate会被手Q调用,因此需要判断*/ + if (reload || (moduleId != this._params.module)) { + //var oldDom = this.pageCut(); + this.destroyModule(); + //删除旧元素 + //(oldDom) && (oldDom.parentNode.removeChild(oldDom)); + this.loadScript(this.loadModule); + } + }, + + initStore: function () { + var me = this; + Dispatcher.register(function (payload) { + console.log('调度器', payload); + var actionId = payload['actionId']; + if (payload['moduleId'] == 'base' || payload['moduleId'] == 'frame') { + //console.log(payload) + if (typeof me[actionId] == 'function') { + me[actionId].apply(me, payload['params']); + } + } + }); + window.Dispatcher = Dispatcher; + }, + + ready: function () { + var me = this; + //回到页面滚动到上次离开的位置 + var y = this._router.get().y || 0; + setTimeout(function () { + window.scrollTo(0, y); + }, 0); + me.hideLoading(); + }, + + + /** + * 拉取模块 + */ + loadModule: function (callback) { + callback = callback || (function () { + }); + var me = this; + //默认走home + var enter = this._params.module || 'home'; + var moduleUrl = 'page/' + enter + '/index'; + if (enter) { + seajs.use(moduleUrl, function (page) { + console.log('加载', moduleUrl); + if (page && typeof page.initialize == 'function') { + me.page = page; + //console.log(module); + //TODO container + page.initialize(me, me._params, document.getElementById('container')); + } else { + console.error(moduleUrl, '加载失败'); + me.showError('模块参数无效[' + moduleUrl + ']', false); + } + }); + } + }, + + + /** + * 销毁模块 + */ + destroyModule: function (page) { + var module = page || this.page; + console.log('销毁模块', module); + if (module) { + //执行模块自身的回收 + if (typeof module.destroy == 'function') { + module.destroy(); + } + this.module = null; + } + }, + /** + * 过滤pageMap + */ + filterPageMap: function (id) { + var map = window.pageMap || {}; + var urls = []; + if (map['common']) { + map['common'].forEach(function (item) { + urls.push(item.replace(/\{([^}]*)\}/g, id || '')); + }); + } + if (map[id]) { + map[id].forEach(function (item) { + urls.push(item.replace(/\{([^}]*)\}/g, id || '')); + }); + } + return urls; + }, + + /** + * 拉取page需要的js + */ + loadScript: function (callback) { + var me = this; + callback = callback || (function () { + }); + if (typeof window.loadScript == 'function') { + if (window.pageMap) { + window.loadScript(this.filterPageMap(this._params.module), function (err) { + if (err) { + me.showError('脚本加载失败,请点击刷新'); + } else { + callback.call(me); + } + }); + } else { + callback.call(this); + } + } else { + // 测试环境没有loadScript + callback.call(this); + } + }, + + /** + * 刷新当前page + */ + reload: function () { + this.load(true); + }, + + + }; + + + module.exports = Frame; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/model/DataModel.js b/Reduck/src/js/frame/model/DataModel.js new file mode 100644 index 0000000..dbc52bf --- /dev/null +++ b/Reduck/src/js/frame/model/DataModel.js @@ -0,0 +1,288 @@ +/** + * dataModel 数据模型 + * Create by donaldcen on 2015/11/6 + */ +define(function (require, exports, module) { + var lib = require('frame/lib'); + var Backbone = lib.Backbone; + var _ = lib._; + var Data = require('business/data'); + var cgiConfig = require('business/cgiConfig'); + var unifyJson = require('util/unifyJson'); + + var UNSUPPORTED = function () { + /* console.error('尚未实现,待具体业务实现')*/ + }; + + //重写Backbone发起请求的接口 + Backbone.sync = function (method, model, options) { + //fetch,save等都会调用 + //console.log(method, model.get('params'), options); + var data; + var params = model.get('params'); + var page = model.page; + //有分页混入分页参数 + if (page) { + params = page.mixinParam(params); + } + //从session缓存读 + data = model.readFromSession(params); + if (data) { + /*读到数据*/ + console.log('从缓存读:', model.cgiId, params, data); + model.saveData(model.processData(null, data)); + } else { + /*没有缓存*/ + model.request(params, options, function (err, data, params, key) { + if (!err) { + model.saveToSession(data, key); + } + var pData = model.processData(err, data); + model.saveData(pData); + }); + } + }; + + var DataModel = Backbone.Model.extend({ + initialize: function () { + //console.log('init', this.id, this._isInit); + if (!this._isInit) { + if (this.cgiId && !this.cgiConfig && cgiConfig[this.cgiId]) { + this.cgiConfig = cgiConfig[this.cgiId]; + } + //不需要拉取格式化数据 + //if (this.defaultData) { + // //this.set('data', this.defaultData); + // this.set('data', this.defaultData); + //} + this.task('init'); + } + this._isInit = true; + this._step = 'initialize'; + }, + task: function (taskId) { + this._step = taskId; + if (typeof this[taskId] == 'function') { + this[taskId](); + } + }, + + /** + * 获取目前执行到的步骤 + * @returns {string|*} + */ + getStep: function () { + return this._step; + }, + + /** + * 初始化 + */ + init: function init() { + if (!this._isInit) { + this._isInit = true; + this.set({state: 'init'}); + this.afterInit(); + } + }, + + /** + * 重置初始化 + */ + reset: function () { + this.beforeReset(); + this._isInit = false; + }, + + /** + * 发送请求 + * @param {Object} params 请求参数 + * @param {Object} options 选项会带回到parse中 + */ + request: function (params, options, callback) { + callback = callback || (function () { + }); + var _params = params; + var key; + var me = this; + //校验请求状态 + try { + key = JSON.stringify(_params); + if (this.get('state') == 'request' && this.get('paramsKey') == key) { + console.warn('请求过快', _params); + return false; + } + this.set({state: 'request', paramsKey: key}); + } catch (e) { + console.error('JSON.stringify', _params, e.message); + } + + + return Data.getCGI(me.cgiConfig, params, function (err, data) { + me.set({state: 'response'}); + if (err) { + me.error(err, params, options.error); + } + callback(err, data, params, key); + }); + }, + + /** + * 数据处理 + * @param {Object | String} err 错误 + * @param {Object} data 数据 + */ + processData: function (err, data) { + var me = this; + var _data = data || {}; + //重置错误 + _data.error = null; + //console.log('data:',params,data); + if (err) { + + me.saveData({error: err}); + return {error: err}; + } else { + //处理分页逻辑 + if (me.page) { + _data = me.page.mixinData(_data); + } + //console.log(_data); + _data = me.beforeUnify(_data); + _data = me.unifyData(_data, me.defaultData); + //if (me.page) {不能统一做,分页后合并数据需要在parse中自己实现,由于各个业务需要连接的字段不同 + // _data = me.page.concatData(_data); + //} + _data = me.parse(_data); + _data._state = 'CGI'; + //console.log(_data); + //me.saveData(_data); + return _data; + } + }, + /** + * 更新数据 + * @param {Object} params 请求参数 + */ + update: function (params) { + var _params = this.get('params') || {}; + _params = _.extend(_params, params || {}); + console.log('掉接口,', this.id, _params); + //如果有分页重置分页数据 + if (this.page) { + this.page.reset(); + } + this.clearData(); + //保存参数发送请求 + this.save(_params); + return this; + }, + /** + * 清空数据 + */ + clearData: function () { + //清空数据 + this.attributes.data = {}; + }, + /** + * 格式化数据,a_a驼峰化aA并进行默认数据处理。 + * 有defaultData时启用,没有在defaultData中定义的数据将会被忽略 + * defaultData中使用驼峰格式,也可以使用函数来计算。 + * 数据中只有undefined的部分会被替换 + * @param {Object} data 数据 + * @param {Object} def 默认模板 + * @returns {*} + */ + unifyData: function (data, def) { + if (def) { + return unifyJson({ + def: def, + src: data + }); + } + return data; + }, + + /** + * 数据格式化前 + * @param data + * @param opt + */ + beforeUnify: function (data, opt) { + //console.log('CGIdata', data); + return data; + }, + /** + * 获取更多,用于分页数据场景 + */ + getMore: function () { + var hasMore = 0; + if (this.page) { + hasMore = this.page.nextPage(); + } + console.log('拉取下一页', hasMore); + if (hasMore) { + this.fetch(); + } + return hasMore; + }, + + /** + * 读取session + * @param params + * @returns {*} + */ + readFromSession: function (params) { + if (this.sessionStorage) { + var key = (this.cgiId || this.id || this.cid) + JSON.stringify(params || this.get('params')); + var data = this.sessionStorage.read(key); + return data; + } else { + return undefined; + } + }, + /** + * 存入session + * @param data + */ + saveToSession: function (data, params) { + if (this.sessionStorage) { + var key = (this.cgiId || this.id || this.cid); + key += (typeof params == 'string') ? params : JSON.stringify(params || this.get('params')); + this.sessionStorage.save(key, data); + } + }, + /** + * 保存数据 + * @param {Object} data CGI回来的数据经过parse后的结果 + */ + saveData: function (data) { + this.set({data: data, state: 'ready'}); + //console.warn('保存:', this.cgiId, this.get('state'), data); + }, + /** + * 错误处理 + * @param {String} errMsg 错误信息 + * @param {Object} params 参数 + * @param {Function} action 操作 + */ + error: function (errMsg, params, action) { + if (typeof this.onError == 'function') { + this.onError(errMsg, params); + } + (typeof action == 'function') && (action(errMsg, params)); + }, + + /* ---------UNSUPPORTED 待实现------------- */ + /** + * 初始化待实现 + */ + afterInit: UNSUPPORTED, + /** + * 重置前 + */ + beforeReset: UNSUPPORTED, + }); + + module.exports = DataModel; +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/model/PageModel.js b/Reduck/src/js/frame/model/PageModel.js new file mode 100644 index 0000000..917cbae --- /dev/null +++ b/Reduck/src/js/frame/model/PageModel.js @@ -0,0 +1,175 @@ +/** + * page 模型 -Backbone View + * Create by donaldcen on 2015/11/6 + */ +define(function (require, exports, module) { + var lib = require('frame/lib'); + var _ = lib._; + var Dispatcher = require('frame/dispatcher/Dispatcher'); + var report = require('business/report'); + var UNSUPPORTED = function () { + // console.error('尚未实现,待具体业务实现') + }; + var Page = function (obj) { + _.extend(this, obj); + }; + Page.prototype = { + title: '', + store: null, + view: null, + styleSheet: [], + beforeInit: function () { + var now = new Date(); + console.log('模块加载完成,耗时:', now - window._commonPoints['head']); + }, + initialize: function (frame, params, container) { + this.beforeInit(frame, params); + + this.id = params.module; + //读取样式 + if (this.styleSheet) { + frame.style.addStyles(this.styleSheet); + } + this.container = container; + + this.setTitle(); + //初始化Store + this.initStore(); + this._listenActions(); //监听渲染 + this.init(frame, params); + this.initView(frame, params, container); + + this.ready(frame, params); + this.setShareButton(frame); + + this._isRender = true; + window.Page = this; + }, + /** + * 设置标题 + */ + setTitle: function () { + if (this.title) { + document.title = this.title; + } + }, + + //设置分享 + setShareButton: function (frame) { + frame.setShareButton('', {title: '加入QQ公会,玩游戏不再孤单', desc: '有福利,有礼包,你还不来?'}, function (type, result) { + report.collect({ + webId: 520002, + opType: type, + classId: 6 + }) + }); + }, + init: function (frame, param) { + if (!param.debug) { + //TODO 移走 + require('util/requireAsyncRetry')(require, 'fastclick', function (FastClick) { + FastClick.attach(document.body); + }); + } + }, + initView: function (frame, params, container) { + var onError = (typeof frame.showError == 'function') && frame.showError.bind(frame) || (function () { + console.error(arguments) + }); + if (this.view) { + var _isShow = false; + this.viewModel = new this.view({ + el: container, models: this.store, onError: onError, show: function () { + !_isShow && frame.ready && frame.ready(); + _isShow = true; + } + }); + } + }, + extend: function (obj) { + return _.extend(this, obj); + }, + + _listenActions: function () { + this.dispatcherId = Dispatcher.register(this.listenActions); + }, + _removeActions: function () { + console.log('销毁调度器', this.dispatcherId); + if (this.dispatcherId) { + Dispatcher.unregister(this.dispatcherId); + this.dispatcherId = null; + } + console.log(Dispatcher.$Dispatcher_callbacks); + }, + /** + * 注册dispatcher://TODO + */ + listenActions: function () { + //console.log('listen Actions'); + //Dispatcher.register + }, + ready: function () { + var now = new Date(); + console.log('渲染完成,耗时:', now - window._commonPoints['head']); + }, + + /** + * 销毁前TODO + */ + beforeDestroy: UNSUPPORTED, + + /** + * 后退前回调TODO,return true正常后退逻辑,return false不继续后退。 + * @returns {boolean} + */ + beforeBack: function(){ + return true; + }, + + /** + * 初始化Store + */ + initStore: function () { + if (this.store) { + for (var i in this.store) { + //console.log(this.store[i].id, this.store[i]._isInit) + if (typeof this.store[i].init == 'function') { + this.store[i].init(); + } + } + } + }, + /** + * 重置store + */ + resetStore: function () { + if (this.store) { + for (var i in this.store) { + console.log('重置', this.store[i].id); + if (typeof this.store[i].reset == 'function') { + this.store[i].reset(); + } + } + } + }, + /** + * 摧毁page + */ + destroy: function () { + //销毁前的业务 + this.beforeDestroy(); + //移出调度器方法 + this._removeActions(); + //重置store model + this.resetStore(); + //view自毁 + if (this.viewModel) { + this.viewModel.destroy(); + this.viewModel = null; + } + console.log('销毁', this.title); + } + }; + module.exports = Page; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/model/ViewModel.js b/Reduck/src/js/frame/model/ViewModel.js new file mode 100644 index 0000000..911fb9e --- /dev/null +++ b/Reduck/src/js/frame/model/ViewModel.js @@ -0,0 +1,290 @@ +/** + * view 模型 -Backbone View + * Create by donaldcen on 2015/11/12 + */ +define(function (require, exports, module) { + var lib = require('frame/lib'); + var Backbone = lib.Backbone; + var _ = lib._; + var React = lib.React; + var ReactDOM = lib.ReactDOM; + var GetData = require('util/getData'); + var RENDER_TYPE = { + 'normal': 0, // 默认开始就渲染 + 'afterData': 1, //等待数据 + 'isMust': 2 //必须的 + }; + var UNSUPPORTED = function () { + // console.error('尚未实现,待具体业务实现') + }; + window.React = React; + window.ReactDOM = ReactDOM; + var View = Backbone.View.extend({ + /** + * 初始化 + * @param opts + */ + initialize: function (opts) { + _.extend(this, opts); + if (typeof this.init == 'function') { + this.init(opts); + } + //this.models = opts.models; + //this.onError = opts.onError /*|| (function(){console.error(arguments)})*/; + this.beforeRender(); + this.addClassName(); + this.render(); + this._afterRender(); + }, + addClassName: function () { + this.el.className = this.elClassName || ''; + }, + /** + * 创建框架 + * @param {String} id div#id + * @returns {Element} + */ + createContainer: function (id, className) { + var dom = document.createElement('div'); + dom.id = id; + dom.className = className || ''; + return dom; + }, + /** + * 自行调用,启动渲染 + */ + refresh: function(){ + this.ready(); + (typeof this.show == 'function') && (this.show()); + }, + beforeRender: UNSUPPORTED, + ready: UNSUPPORTED, + + getRenderType: function(renderType){ + if(typeof renderType == 'string'){ + renderType = renderType.split(' '); + } + var _renderType = {}; + (renderType || []).forEach(function(item){ + _renderType[item] = true; + }); + return _renderType; + }, + /** + * render 渲染 + */ + render: function () { + var me = this; + this.el.innerHTML = ''; + //TODO + //this.el.style.display = 'none'; + var _components; + for (var c in this.components) { + if ((_components = this.components[c]).component) { + //获取渲染类型 + _components._renderType = this.getRenderType(_components.renderType); + //计算容器 + if(!_components.container){ + _components._container = me.createContainer(c, this.components[c].className); + me.el.appendChild(_components._container); + }else if(typeof _components.container == 'string'){ + _components._container = document.querySelector(_components.container); + } + + //bind Model + me.bindModel(_components.models, me.renderComponent.bind(me, _components)); + + me.renderComponent(_components); + + } + } + this.el.style.display = 'block'; + this._isRender = true; + }, + + /** + * 获取还在执行的模块数量 + * @returns {Number} + */ + getDoing: function(){ + var components = this.components; + var array = Object.keys(components); + var len = array.length; + var doing = []; + array.forEach(function(key){ + if(components[key].state == 'ready'){ + len--; + }else{ + doing.push(components[key]); + } + }); + return doing; + }, + + _afterRender: function(){ + this._loadingEvent(); + this.afterRender(); + }, + + _loadingEvent: function(){ + var dom = document.getElementById('loading'); + if(dom){ + dom.onclick = function(){ + dom.style.display = 'none'; + } + } + }, + + /** + * 渲染完成 + */ + afterRender: UNSUPPORTED, + /** + * 绑定数据 + * @param {Array | String} modelIds modelId + * @param {Function} callback 回调 + */ + bindModel: function (modelIds, callback) { + callback = callback || (function () { + }); + var models = this.models; + if (typeof modelIds == 'string') { + modelIds = [modelIds]; + } + (modelIds || []).forEach(function (id) { + if (models[id]) { + models[id].on('change:data', callback); + } + }); + }, + /** + * 移出绑定 + * @param modelIds + */ + unbindModel: function (modelIds) { + var models = this.models; + if (typeof modelIds == 'string') { + modelIds = [modelIds]; + } + (modelIds || []).forEach(function (id) { + if (models[id]) { + models[id].off('change:data'); + } + }); + }, + /** + * 组织提供组件的props + * @param {Array | String} modelIds modelID + * @returns {{actions: *}} + */ + buildProps: function (modelIds) { + var models = this.models; + if (!models) { + return {}; + } + var props = { + actions: models.actions + }; + var error = null; + + if (typeof modelIds == 'string') { + modelIds = [modelIds]; + } + var state = modelIds.length || 0; + (modelIds || []).forEach(function (id) { + if (models[id]) { + props[id] = models[id].get('data'); + console.log(id,models[id].get('state')); + (models[id].get('state') == 'ready') && state--; + if(props[id].error){ + error = props[id].error; + } + } + }); + props.state = state; + //view模块的处理器 + if (typeof this.parseProps == 'function') { + props.error = error; + props = this.parseProps(props) || props; + } + props.error = error; + props.state = state; + props.get = GetData.get; + return props; + }, + /** + * 渲染组件 + * @param {Object} config 组件配置{component: Reactxxx, models: ['AMS'], container: DOM} + */ + renderComponent: function (config) { + var me = this; + if (config.component) { + var props = this.buildProps(config.models);//获取组件的model组织props + console.log(config.id, props); + //必要组件错误退出 + if(props.error && config._renderType['isMust'] && typeof this.onError == 'function'){ + this.onError(props.error); + return false; + } + //afterData组件判断 + if(config._renderType['afterData'] && props.state > 0){ + return false; + } + if(props.state == 0){ + config.state = 'ready'; + console.info(config.id + ' is ready', ',doing: ', this.getDoing()); + } + //组件单元数据校验 + if (typeof config.test == 'function' && !config.test(props)){ + return false; + } + //组件数据格式化 + if (typeof config.dataParse == 'function') { + props = config.dataParse(props) || props; + } + + //为props提供便捷get方法 + props.get = GetData.get; + config._reactComponent = (ReactDOM.render(React.createElement(config.component, props), config._container, function(){ + //console.info('渲染',config.id, props, config._container, config._reactComponent); + if(typeof config.afterRender == 'function'){ + config.afterRender(props, config); + } + me.afterRenderComponent(config, props); + })); + + if(this.getDoing().length <= 1){ + this.refresh(); + } + + } + }, + /** + * 组件渲染后 + * @param {Object} config 组件配置{component: Reactxxx, models: ['AMS'], container: DOM} + */ + afterRenderComponent: function(config){ + if(!this._isStartRender){ + this._isStartRender = true; + } + }, + destroy: function () { + //首先清除焦点 + document.activeElement && document.activeElement.blur(); + var modelIds, container; + for (var c in this.components) { + if (modelIds = this.components[c].models) { + this.unbindModel(modelIds); + } + if(container = this.components[c]._container){ + try{ + ReactDOM.unmountComponentAtNode(container); + }catch(e){ + console.error('react回收失败', e.message); + } + } + } + } + }); + module.exports = View; +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/moduleCtrl.js b/Reduck/src/js/frame/moduleCtrl.js new file mode 100644 index 0000000..dc3cfdf --- /dev/null +++ b/Reduck/src/js/frame/moduleCtrl.js @@ -0,0 +1,153 @@ +/** + * ModuleCtrl 模块生命周期控制器 + * @author donaldcen 20150830 + */ +define(function (require, exports, module) { + var Dispatcher = require('frame/dispatcher/Dispatcher'); + var RouterDB = require('frame/routerModel'); + var routerDB = new RouterDB(); + var ModuleCtrl = { + /** + * 读取Module + * @param {string} md 模块 + * @param {string} ac 操作 + * @param {string} con 数据 + */ + loadModule: function (md, ac, con) { + //console.log('router', md, ac, con); + var me = this; + var cj = {}; + //将数据自动转成对象 + if (con && con.indexOf(':') > -1) { + con.replace(/(\w+)\s*:\s*([\w-]+)/g, function (a, b, c) { + b && (cj[b] = c); + }); + } else { + cj = con; + } + //console.log(cj); + + var moduleUrl = ['modules', md, ac].join('/'), + exports; + + if (exports = this.getModuleByCache(moduleUrl)) {//获取本地缓存模块 + //通知事件总线 + me.dispatch("load", [null, exports, cj]); + } else { + //异步加载模块 + require.async(moduleUrl, function (exports) { + //console.log('异步', exports); + if (exports) { + me.saveModuleByCache(moduleUrl, exports); + //exports(cj); + //通知事件总线 + me.dispatch("load", [null, exports, cj]); + } else { + me.dispatch("load", [moduleUrl + '模块加载失败']); + //console.error(moduleUrl,'加载失败'); + } + }); + } + + }, + + /** + * 缓存模块 + * @param {String} moduleUrl 模块ID,短地址 + * @param {Function} modules 入口模块内容 + */ + saveModuleByCache: function (moduleUrl, modules) { + return routerDB.set(moduleUrl, modules); + }, + + /** + * 删除模块缓存 + * @param {String} moduleUrl 模块地址 + */ + removeModuleByCache: function (moduleUrl) { + return routerDB.unset(moduleUrl); + }, + + /** + * 获取缓存的Module + * @param {Stringf} moduleUrl 模块ID + */ + getModuleByCache: function (moduleUrl) { + if (moduleUrl) { + return routerDB.get(moduleUrl); + } else { + return routerDB; + } + }, + + /** + * 获取样式表 + * @param {String} styleUrl 模块路径 + * @param {Function} callback 加载回调函数 callback(err, data); + */ + getStyleSheet: function (styleUrl, callback) { + var me = this; + styleUrl = 'modules/hall/style/hall.style.js'; + callback = callback || (function () { + }); + require.async(styleUrl, function (exports) { + if (exports) { + callback.call(me, null, exports); + } else { + callback.call(me, styleUrl + '加载失败'); + } + }); + return this; + }, + + /** + * 样式加前缀 + * @param {String} topId 作用域父domID + * @param {String} cssString 加载到的样式表 + */ + filterStyleSheet: function (topId, cssString) { + cssString = (cssString || '').replace(/([^,{}]+)(\s*[{,])/g, function ($1) { + var selecter = $1.replace(/^\s*/,''); + if(selecter != ',' || selecter != '{'){//去除无效样式代码 + return '#' + topId + ' ' + selecter; + }else{ + return $1; + } + + }); + return cssString; + }, + + /** + * 插入样式表 + */ + insertStyleSheet: function (id, styles) { + var style = document.createElement('style'); + style.id = id; + style.innerHTML = styles; + document.head.appendChild(style); + return this; + }, + + /** + * 删除样式表 + */ + removeStyleSheet: function (id) { + document.getElementById(id).remove(); + return this; + }, + + + /** + * 通知事件总线调度器 + * @param {String} actionId 操作id + * @param {Array} params 通知参数 + */ + dispatch: function (actionId, params) { + if (!actionId) return null; + Dispatcher.dispatch({moduleId: "router", actionId: actionId, params: params}); + } + }; + module.exports = ModuleCtrl; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/pageCut.js b/Reduck/src/js/frame/pageCut.js new file mode 100644 index 0000000..36b5a57 --- /dev/null +++ b/Reduck/src/js/frame/pageCut.js @@ -0,0 +1,59 @@ +/** + * page切换 + */ +define(function (require, exports, module) { + var domIndex = 0; + var PageCut = { + /** + * 创建容器 + * @param {String | undefined} id 容器id不填默认 + * @param {document | undefined} dom + * @param {document | undefined} container + * @returns {*} + */ + createContainer: function(id, dom, container){ + var newDom; + if(!id){ + id = 'container' + domIndex++; + } + if(typeof dom == 'object' && dom.cloneNode){ + newDom = dom.cloneNode(); + }else{ + newDom = document.createElement('div'); + } + newDom.id = id; + if(!container || !container.appendChild){ + document.body.appendChild(newDom); + }else{ + container.appendChild(newDom); + } + return newDom; + }, + + /** + * 删除容器 + * @param {String | DOM} selector 选择器或DOM + * @returns {boolean} + */ + removeContainer: function(selector){ + var dom; + if(typeof selector == 'string'){ + dom = document.querySelector(selector); + }else{ + dom = selector; + } + if(typeof dom == 'object' && dom.removeChild){ + dom.parentNode.removeChild(dom); + return true; + }else{ + return false; + } + } + }; + /** + * 切换page + * @param id + * @param callback + */ + module.exports = PageCut; +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/plug/dataAMS.js b/Reduck/src/js/frame/plug/dataAMS.js new file mode 100644 index 0000000..536a29e --- /dev/null +++ b/Reduck/src/js/frame/plug/dataAMS.js @@ -0,0 +1,41 @@ +/** + * AMS模块 + * Created by donaldcen on 2015/11/19 + */ +define(function (require, exports, module) { + var DataModel = require('frame/index').DataModel; + var AMS = require('business/ams'); + + var AMSConfig = DataModel.extend({ + defaults: { + params: {}, + data: {} + }, + initialize: function () { + this.ams = new AMS(this.id); + if (this.defaultData) { + this.set('data', this.defaultData); + } + if (typeof this.init == 'function') { + this.init(); + } + }, + request: function (params, options) { + var me = this; + if (this.ams.id) { + this.ams.get(function (err, data) { + var _data = data || {}; + if(err){ + me.error(err, params, options.error); + }else{ + //_data = me.unifyData(_data, me.defaultData); + _data = me.parse(_data, options); + me.saveData(_data); + } + }); + } + } + }); + module.exports = AMSConfig; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/plug/dataPageMixin.js b/Reduck/src/js/frame/plug/dataPageMixin.js new file mode 100644 index 0000000..37376d9 --- /dev/null +++ b/Reduck/src/js/frame/plug/dataPageMixin.js @@ -0,0 +1,93 @@ +/** + * 分页模型mixin + * created by donaldcen on 2015/11/13 + */ +define(function (require, exports, module) { + var _ = require('lib/underscore'); + var PageMixin = function (opt) { + this.pageOpt = _.extend({ + name_pageId: 'page_id', + name_pageLen: 'num_per_page', + name_hasMore: 'has_more', + num_everyPage: 25, + num_startPage: 0, + num_maxPage: null, + is_hasMore: null + }, opt); + this.pageOpt.num_nowPage = this.pageOpt.num_startPage; + return this; + }; + PageMixin.prototype = { + pageOpt: {}, + nextPage: function () { + if (this.get('tag_hasMore')) { + this.set({ + num_nowPage: this.get('num_nowPage') + 1 + }); + return true; + } else { + return false; + } + + }, + /** + * 混入分页参数 + * @param {Object} params 原请求参数 + * @returns {*} + */ + mixinParam: function (params) { + var opts = this.pageOpt || {}; + params[opts.name_pageId] = opts.num_nowPage; + params[opts.name_pageLen] = opts.num_everyPage; + return params; + }, + /** + * 混入响应数据 + * @param {Object} data 响应数据 + */ + mixinData: function (data) { + data.hasMore = this.hasMore(data); + this.set({tag_hasMore: data.hasMore}); + return data; + }, + /** + * 判断是否有更多 + * @param {Object} data 来源数据 + * @returns {boolean} + */ + hasMore: function (data) { + //通过用户自定义方法判断 + if (typeof this.get('is_hasMore') == 'function') { + return this.get('is_hasMore')(data); + } + //自动判断数据内标识 + if (typeof data[this.get('name_hasMore')] != 'undefined') { + return !!data[this.get('name_hasMore')]; + } + //自动比较页码范围 + if (typeof this.get('num_maxPage') == 'number') { + return this.get('num_maxPage') >= this.get('num_nowPage'); + } + }, + /** + * 重置页码 + */ + reset: function () { + this.set({ + num_nowPage: this.get('num_startPage'), + tag_hasMore: true //重置下一页标志 + }); + }, + set: function (opt) { + this.pageOpt = _.extend(this.pageOpt, opt || {}); + }, + get: function (id) { + return this.pageOpt[id]; + } + }; + module.exports = function (opt) { + + return {page: new PageMixin(opt)}; + }; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/plug/dataSessionStorageMixin.js b/Reduck/src/js/frame/plug/dataSessionStorageMixin.js new file mode 100644 index 0000000..009b2f2 --- /dev/null +++ b/Reduck/src/js/frame/plug/dataSessionStorageMixin.js @@ -0,0 +1,90 @@ +/** + * sessionStorage存储模型mixin + * created by donaldcen on 2015/11/26 + */ +define(function (require, exports, module) { + var _ = require('lib/underscore'); + var SS = function (opt) { + this.opts = _.extend({}, opt); + return this; + }; + SS.prototype = { + opts: {}, + expSuffix: '_exp', + /** + * 存储 + * @param {String} key 存入key + * @param {Object | String} value 存入值 + * @param {Number} exp 到期时间 + */ + save: function (key, value, exp) { + console.info('写入缓存',key, value); + if (typeof value == 'object') { + value = JSON.stringify(value); + } + exp = exp || Number(new Date()) + 24 * 60 * 60 * 1000; // 默认缓存一天 + if (window.sessionStorage) { + + window.sessionStorage[key] = value; + if(this.check(key, value)){ + //打上到期时间 + exp && (window.sessionStorage[key + this.expSuffix] = Number(exp)); + return true; + }else{ + //缓存失败,删除key内容 + this.remove(key); + console.warn('缓存失败:'+key); + return false; + } + } else { + console.error('window sessionStorage undefined!'); + } + return this; + }, + /** + * 检查缓存是否正确 + * @param key + * @param value + */ + check: function(key, value){ + return window.sessionStorage.getItem(key) == value; + }, + + /** + * 移出元素 + * @param key + */ + remove: function (key) { + if (window.sessionStorage) { + window.sessionStorage.removeItem(key); + } + }, + /** + * 读取 + * @param key + * @returns {*} + */ + read: function (key) { + if (window.sessionStorage) { + try { + var isable = (window.sessionStorage[key + this.expSuffix] && Number(window.sessionStorage[key + this.expSuffix]) > Number(new Date())); + if (isable) { + return JSON.parse(window.sessionStorage[key]); + } else { + return undefined; + } + } catch (e) { + return undefined; + } + + } else { + console.error('window sessionStorage undefined!'); + return undefined; + } + } + }; + module.exports = function (opt) { + return {sessionStorage: new SS(opt)}; + }; + window.SS = new SS(); +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/plug/dataStorage.js b/Reduck/src/js/frame/plug/dataStorage.js new file mode 100644 index 0000000..ad0fb85 --- /dev/null +++ b/Reduck/src/js/frame/plug/dataStorage.js @@ -0,0 +1,11 @@ +/** + * 数据仓库dataStorage-管理dataModel + */ +define(function(require, exports, module){ + var Backbone = require('lib/backbone'); + var unifyJson = require('util/unifyJson'); + + var dataModel = Backbone.Model.extend({}); + + module.exports = dataModel; +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/plug/mqq.js b/Reduck/src/js/frame/plug/mqq.js new file mode 100644 index 0000000..7561711 --- /dev/null +++ b/Reduck/src/js/frame/plug/mqq.js @@ -0,0 +1,89 @@ +/** + * mqq相关模块 + */ +define(function (require, exports, module) { + var mqqShare = require('util/mqqShare'); + module.exports = { + initMQQ: function () { + if (mqq) { + this.bindBackEvent(); + } + }, + bindBackEvent: function () { + var me = this; + if (mqq.ui) { + mqq.ui && mqq.ui.setOnCloseHandler(function () { + if (me.back()) { + //SPA还没返回到底 + //SPA处理 + } else { + //到底手Q返回 + mqq.ui.popBack(); + } + }); + } + }, + refreshTitle: function (title) { + if (title) { + document.title = title; + } + if (mqq && mqq.ui && mqq.ui.refreshTitle) { + mqq.ui.refreshTitle(); + } + }, + /** + * 设置右上角分享 + * @param title 右上角的分享内容 + * @param params 分享的内容 + * @param callback 分享的回调 + */ + setShareButton: function (title, params, callback) { + var params = params || {}; + if (!mqq || !mqq.ui) { + return; + } + mqq.ui.setOnShareHandler(function (type) { + mqq.ui.shareMessage({ + title: params.title || '加入QQ公会,玩游戏不再孤单', + desc: params.desc || '有福利,有礼包,你还不来?', + image_url: params.imgUrl || 'http://imgcache.gtimg.cn/club/mqqgame/league_hall_index/201512151756_hall_index_share.png', + share_url: params.shareUrl || 'http://' + location.hostname + location.pathname + '?_wv=1985&module=home', + back: params.back || true, + share_type : type + }, function (result) { + if (typeof callback == 'function') { + callback(type, result) ; + } + }) + }); + mqq.ui.setTitleButtons({ + right: { + title: title || '', + callback: function () { + //首先清除焦点 + document.activeElement && document.activeElement.blur(); + mqq.ui.showShareMenu(); + } + } + }); + }, + + /** + * 隐藏右上角按钮 + * @api protected + */ + hideRightButton: function () { + if (mqq.ui) { + if (mqq.ui.setTitleButtons) { + var right = {hidden: true}; + mqq.ui.setTitleButtons({right: right}); + } + else { + mqq.ui.setActionButton({hidden: true}, function () { + }); + } + } + } + }; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/router.js b/Reduck/src/js/frame/router.js new file mode 100644 index 0000000..09b922f --- /dev/null +++ b/Reduck/src/js/frame/router.js @@ -0,0 +1,102 @@ +/** + * 框架-router模块 + * created by donaldcen on 2015/12/4 + */ +define(function (require, exports, module) { + function Router(config) { + this._stateList = []; + this.init(config); + } + + Router.prototype = { + init: function (config) { + this.listenEvent(); + this.userConfig = config; + this._stateList.push({ + state: {module: ''/*id*/, params: {}/*params*/, index: this._stateList.length}, + title: document.title, + url: location.href + }); + }, + listenEvent: function () { + var me = this; + //手Q初始化时会调用 + window.onpopstate = function (e) { + var index = window.history.state && window.history.state.index; + //如果后退把当前的状态删除 + if(index && me._stateList.length && index < me._stateList.length){ + me._stateList.pop(); + } + if (me.userConfig && typeof me.userConfig.popstate == 'function') { + me.userConfig.popstate(); + } + }; + }, + getUrl: function (url, module, params) { + var _url = (url.split('?') || [])[0]; + var _params = ''; + if (_url) { + (function (obj) { + for (var key in obj) { + _params += key + '=' + obj[key] + '&'; + } + })(params); + return _url + '?module=' + module + '&' + _params; + } else { + return false; + } + }, + /** + * 设置当前路由状态 + * @param obj + */ + set: function(obj, index){ + var me = this; + if(!index){ + index = window.history.state && window.history.state.index || 0; + } + Object.keys(obj).forEach(function(key){ + !me._stateList[index] && (me._stateList[index] = {}); + me._stateList[index][key] = obj[key]; + }); + }, + get: function(index){ + if(!index){ + index = window.history.state && window.history.state.index || 0; + } + return this._stateList[index] || {}; + }, + go: function (id, title, params) { + var url = this.getUrl(location.href, id, params); + if (url) { + var sts = { + state: {module: id, params: params, index: this._stateList.length}, + title: title, + url: url + }; + this._stateList.push(sts); + window.history.pushState(sts.state, sts.title, sts.url); + } else { + console.error('router go url Error', id, params); + } + + }, + _back: function () { + return this._stateList.pop(); + }, + getPageIndex: function(){ + return window.history.state && window.history.state.index || 0; + }, + /** + * 后退,history中有数据就后退,没有就到底了 + * @returns {boolean} + */ + back: function () { + window.history.back(); + } + }; + + + module.exports = Router; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/routerModel.js b/Reduck/src/js/frame/routerModel.js new file mode 100644 index 0000000..b59d662 --- /dev/null +++ b/Reduck/src/js/frame/routerModel.js @@ -0,0 +1,11 @@ +/** + * 路由缓存模块 + */ +define(function(require, exports, module){ + var Backbone = require('lib/backbone'); + var router = Backbone.Model.extend({ + + }); + module.exports = router; + +}); \ No newline at end of file diff --git a/Reduck/src/js/frame/store.js b/Reduck/src/js/frame/store.js new file mode 100644 index 0000000..52dfb64 --- /dev/null +++ b/Reduck/src/js/frame/store.js @@ -0,0 +1,10 @@ +/** + * frame store + * Created by donaldcen on 2015/11/21 + */ +define(function(require, exports, module){ + var AMSConfig = require('storage/ams-main'); + module.exports = { + AMS: AMSConfig + }; +}); diff --git a/Reduck/src/js/frame/store/base.js b/Reduck/src/js/frame/store/base.js new file mode 100644 index 0000000..8c96417 --- /dev/null +++ b/Reduck/src/js/frame/store/base.js @@ -0,0 +1,206 @@ +/** + * 基础交互 + */ + +define(function (require, exports, module) { + var constants = require('business/constants'), + router = require('business/router'), + env = require('business/environment'), + _ = require('lib/underscore'), + report= require('business/report'); + + var Store = require('frame/store'), + UNSUPPORTED = function () { + // console.error('尚未实现') + }, + undefind; + var Frame = { + + test: function () { + }, + /** + * 更新页面标题 + * @param {String} [title=this._getTitle()] + */ + updateTitle: function (title) { + if (typeof title == 'string') { + document.title = title; + mqq && mqq.ui && mqq.ui.refreshTitle && mqq.ui.refreshTitle(); + } + }, + /** + * webview跳转 + * @param {String} url + * @param {Object} [params] 参数 + * @param {Object} [o] 选项 + * @param {Boolean} [o.rightButton=false] 是否显示右上角菜单 + */ + goUrl: function (url, params, o) { + var me = this; + if (me.__going) return; + params = params || {}; + + // 跳转时带上这个参数,以表明使用环境 + if (constants.USE_PROD_CGI) { + params['prodcgi'] = 1; + } + + var wv = 1 // 隐藏底部导航 + //+ 2 // 隐藏功能按钮 + + 1024 // 锁定竖屏 + + 32 // 隐藏功能菜单里的「复制链接」项 + + 128 // 隐藏功能菜单里的「调整字体」项 + + 256 // 隐藏功能菜单里的「用系统浏览器打开」项 + + 512 // 隐藏功能菜单里的「用QQ浏览器打开」项 + + 8192;// 隐藏功能菜单里的「收藏」项 + + if (!o.rightButton) + wv += 2; + + //防止重复加_wv + if (!(/[\?&]_wv=\d+/.test(url)) && !params._wv) + params._wv = wv; + + //透传参数,用于测试环境 + var reg = /\b(dev|local_cgi|prodcgi)\b/g; + var usedParams = (location.search).match(reg); + (usedParams || []).forEach(function (p) { + params[p] = 1; + }); + + //这里是消除点击进入子webview后,<返回 显示成 上一个webview的标题 + var t = document.title; + //me.updateTitle(' '); + setTimeout(function () { + me.updateTitle(t); + }, 300); + + router.redirect(url, params); + me.__going = true; + setTimeout(function () { + me.__going = false; + }, 1000); + }, + + /** + * 跳转 + * @param {String} key linkKey + * @param {Object} params URL参数 + * @param {Object} data 替换数据 + */ + goLink: function (key, params, data) { + data = data || {}; + params = params || {}; + var datas = Store.AMS.get('data') || {}; + var links = datas.links || {}; + var url; + if (links[key]) { + url = links[key].url1; + url = url.replace(/\{([^}]*)\}/g,function($1,$2){ + if(data[$2]){ + return data[$2]; + }else{ + return ''; //$2 + } + }); + this.goUrl(url, params, {}); + } else { + console.error('goLink:', '链接配置不存在', key, params, data); + } + }, + /** + * 隐藏loading + */ + hideLoading: function () { + var dom = document.getElementById('loading'); + if (dom) { + dom.style.display = 'none'; + } + }, + /** + * 显示loading + */ + showLoading: function () { + var dom = document.getElementById('loading'); + if (dom) { + dom.style.display = '-webkit-box'; + } + }, + /** + * 显示错误提示 + * @param {String} err + * @api public + */ + showError: function (err, reloadable) { + reloadable = reloadable || false; + this.showNotice(err, reloadable, 'ui-notice'); + }, + + /** + * 显示消息提示 + * @param {String} msg + * @param {Boolean} [reloadable=false] + * @param {String} [cssClass='ui-notice ui-notice-news'] + * @api public + */ + showNotice: function (msg, reloadable, cssClass) { + var me = this; + cssClass = cssClass || 'ui-notice ui-notice-news'; + msg = msg || '渲染异常[未知错误 -1]'; + me.hideLoading(); + + var id = 'absmod_notice'; + var notice = document.getElementById(id); + var container = document.querySelector(this.container); + container && (container.style.display = 'none'); + if(!notice){ + notice = document.createElement('div'); + notice.id = id; + notice.className = cssClass; + notice.style.display = 'none'; + notice.style.position = 'fixed'; + notice.style.top = 0; + notice.style.left = 0; + notice.style.background = '#fff'; + document.body.appendChild(notice); + } + notice.innerHTML = '

' + msg + '

'; + notice.style.display = 'block'; + if (reloadable) { + notice.onclick = function () { + me.hideNotice(); + me.reload(); + }; + } + }, + + /** + * 隐藏提示模块 + */ + hideNotice: function(){ + var id = 'absmod_notice'; + var notice = document.getElementById(id); + var container = document.querySelector(this.container); + notice && (notice.style.display = 'none'); + container && (container.style.display = 'block'); + }, + /** + * 上报操作 + */ + report:function(params){ + var data = { + webId: 1801 + }; + data = _.extend(data,params); + report.collect(data); + }, + /** + * 后退前的事件 + * @returns {Boolean} 返回false来禁止本次后退 + * `抽象方法` + */ + _beforeBack: UNSUPPORTED + }; + module.exports = Frame; + +}); \ No newline at end of file diff --git a/Reduck/src/js/lib/Dispatcher.js b/Reduck/src/js/lib/Dispatcher.js new file mode 100644 index 0000000..0724c2e --- /dev/null +++ b/Reduck/src/js/lib/Dispatcher.js @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Dispatcher + * @typechecks + */ + +"use strict"; +define(function (require, exports, module) { + var invariant = function (condition, format, a, b, c, d, e, f) { + if (false) { + if (format === undefined) { + throw new Error('invariant requires an error message argument'); + } + } + + if (!condition) { + var error; + if (format === undefined) { + error = new Error( + 'Minified exception occurred; use the non-minified dev environment ' + + 'for the full error message and additional helpful warnings.' + ); + } else { + var args = [a, b, c, d, e, f]; + var argIndex = 0; + error = new Error( + 'Invariant Violation: ' + + format.replace(/%s/g, function () { + return args[argIndex++]; + }) + ); + } + + error.framesToPop = 1; // we don't care about invariant's own frame + throw error; + } + }; + + var _lastID = 1; + var _prefix = 'ID_'; + + /** + * Dispatcher is used to broadcast payloads to registered callbacks. This is + * different from generic pub-sub systems in two ways: + * + * 1) Callbacks are not subscribed to particular events. Every payload is + * dispatched to every registered callback. + * 2) Callbacks can be deferred in whole or part until other callbacks have + * been executed. + * + * For example, consider this hypothetical flight destination form, which + * selects a default city when a country is selected: + * + * var flightDispatcher = new Dispatcher(); + * + * // Keeps track of which country is selected + * var CountryStore = {country: null}; + * + * // Keeps track of which city is selected + * var CityStore = {city: null}; + * + * // Keeps track of the base flight price of the selected city + * var FlightPriceStore = {price: null} + * + * When a user changes the selected city, we dispatch the payload: + * + * flightDispatcher.dispatch({ + * actionType: 'city-update', + * selectedCity: 'paris' + * }); + * + * This payload is digested by `CityStore`: + * + * flightDispatcher.register(function(payload) { + * if (payload.actionType === 'city-update') { + * CityStore.city = payload.selectedCity; + * } + * }); + * + * When the user selects a country, we dispatch the payload: + * + * flightDispatcher.dispatch({ + * actionType: 'country-update', + * selectedCountry: 'australia' + * }); + * + * This payload is digested by both stores: + * + * CountryStore.dispatchToken = flightDispatcher.register(function(payload) { + * if (payload.actionType === 'country-update') { + * CountryStore.country = payload.selectedCountry; + * } + * }); + * + * When the callback to update `CountryStore` is registered, we save a reference + * to the returned token. Using this token with `waitFor()`, we can guarantee + * that `CountryStore` is updated before the callback that updates `CityStore` + * needs to query its data. + * + * CityStore.dispatchToken = flightDispatcher.register(function(payload) { + * if (payload.actionType === 'country-update') { + * // `CountryStore.country` may not be updated. + * flightDispatcher.waitFor([CountryStore.dispatchToken]); + * // `CountryStore.country` is now guaranteed to be updated. + * + * // Select the default city for the new country + * CityStore.city = getDefaultCityForCountry(CountryStore.country); + * } + * }); + * + * The usage of `waitFor()` can be chained, for example: + * + * FlightPriceStore.dispatchToken = + * flightDispatcher.register(function(payload) { + * switch (payload.actionType) { + * case 'country-update': + * flightDispatcher.waitFor([CityStore.dispatchToken]); + * FlightPriceStore.price = + * getFlightPriceStore(CountryStore.country, CityStore.city); + * break; + * + * case 'city-update': + * FlightPriceStore.price = + * FlightPriceStore(CountryStore.country, CityStore.city); + * break; + * } + * }); + * + * The `country-update` payload will be guaranteed to invoke the stores' + * registered callbacks in order: `CountryStore`, `CityStore`, then + * `FlightPriceStore`. + */ + + function Dispatcher() { + this.$Dispatcher_callbacks = {}; + this.$Dispatcher_isPending = {}; + this.$Dispatcher_isHandled = {}; + this.$Dispatcher_isDispatching = false; + this.$Dispatcher_pendingPayload = null; + this.queue = []; + } + + /** + * Registers a callback to be invoked with every dispatched payload. Returns + * a token that can be used with `waitFor()`. + * + * @param {function} callback + * @return {string} + */ + Dispatcher.prototype.register = function (callback) { + var id = _prefix + _lastID++; + this.$Dispatcher_callbacks[id] = callback; + return id; + }; + + /** + * Removes a callback based on its token. + * + * @param {string} id + */ + Dispatcher.prototype.unregister = function (id) { + invariant( + this.$Dispatcher_callbacks[id], + 'Dispatcher.unregister(...): `%s` does not map to a registered callback.', + id + ); + delete this.$Dispatcher_callbacks[id]; + }; + + /** + * Waits for the callbacks specified to be invoked before continuing execution + * of the current callback. This method should only be used by a callback in + * response to a dispatched payload. + * + * @param {array} ids + */ + Dispatcher.prototype.waitFor = function (ids) { + invariant( + this.$Dispatcher_isDispatching, + 'Dispatcher.waitFor(...): Must be invoked while dispatching.' + ); + for (var ii = 0; ii < ids.length; ii++) { + var id = ids[ii]; + if (this.$Dispatcher_isPending[id]) { + invariant( + this.$Dispatcher_isHandled[id], + 'Dispatcher.waitFor(...): Circular dependency detected while ' + + 'waiting for `%s`.', + id + ); + continue; + } + invariant( + this.$Dispatcher_callbacks[id], + 'Dispatcher.waitFor(...): `%s` does not map to a registered callback.', + id + ); + this.$Dispatcher_invokeCallback(id); + } + }; + + /** + * Dispatches a payload to all registered callbacks. + * + * @param {object} payload + */ + Dispatcher.prototype.dispatch = function (payload) { + //invariant( + // !this.$Dispatcher_isDispatching, + // 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.' + //); + if (this.$Dispatcher_isDispatching) { + console.log('调度器ing,加入队列'); + this.queue.push(payload); + return false; + } + this.$Dispatcher_startDispatching(payload); + try { + for (var id in this.$Dispatcher_callbacks) { + if (this.$Dispatcher_isPending[id]) { + continue; + } + this.$Dispatcher_invokeCallback(id); + } + } finally { + this.$Dispatcher_stopDispatching(); + } + }; + + /** + * Is this Dispatcher currently dispatching. + * + * @return {boolean} + */ + Dispatcher.prototype.isDispatching = function () { + return this.$Dispatcher_isDispatching; + }; + + /** + * Call the callback stored with the given id. Also do some internal + * bookkeeping. + * + * @param {string} id + * @internal + */ + Dispatcher.prototype.$Dispatcher_invokeCallback = function (id) { + this.$Dispatcher_isPending[id] = true; + this.$Dispatcher_callbacks[id](this.$Dispatcher_pendingPayload); + this.$Dispatcher_isHandled[id] = true; + }; + + /** + * Set up bookkeeping needed when dispatching. + * + * @param {object} payload + * @internal + */ + Dispatcher.prototype.$Dispatcher_startDispatching = function (payload) { + for (var id in this.$Dispatcher_callbacks) { + this.$Dispatcher_isPending[id] = false; + this.$Dispatcher_isHandled[id] = false; + } + this.$Dispatcher_pendingPayload = payload; + this.$Dispatcher_isDispatching = true; + }; + + /** + * Clear bookkeeping used for dispatching. + * + * @internal + */ + Dispatcher.prototype.$Dispatcher_stopDispatching = function () { + this.$Dispatcher_pendingPayload = null; + this.$Dispatcher_isDispatching = false; + var payload = this.queue.pop(); + if (payload) { + this.dispatch(payload); + } + }; + + + module.exports = Dispatcher; +}); \ No newline at end of file diff --git a/Reduck/src/js/lib/backbone.js b/Reduck/src/js/lib/backbone.js new file mode 100644 index 0000000..dbcf512 --- /dev/null +++ b/Reduck/src/js/lib/backbone.js @@ -0,0 +1,879 @@ +define(function(require, exports, module){ +(function (t, e) { + if (typeof define === "function" && define.amd) { + define(["lib/underscore", "lib/zepto", "exports"], function (i, r, s) { + t.Backbone = e(t, s, i, r) + }) + } else if (typeof exports !== "undefined") { + var i = require("lib/underscore"); + var r = require("lib/zepto"); + e(t, exports, i, r) + } else { + t.Backbone = e(t, {}, t._, t.jQuery || t.Zepto || t.ender || t.$) + } +})(this, function (t, e, i, r) { + var s = t.Backbone; + var n = []; + var a = n.push; + var o = n.slice; + var h = n.splice; + e.VERSION = "1.1.2"; + e.$ = r; + e.noConflict = function () { + t.Backbone = s; + return this + }; + e.emulateHTTP = false; + e.emulateJSON = false; + var u = e.Events = { + on: function (t, e, i) { + if (!c(this, "on", t, [e, i]) || !e)return this; + this._events || (this._events = {}); + var r = this._events[t] || (this._events[t] = []); + r.push({callback: e, context: i, ctx: i || this}); + return this + }, once: function (t, e, r) { + if (!c(this, "once", t, [e, r]) || !e)return this; + var s = this; + var n = i.once(function () { + s.off(t, n); + e.apply(this, arguments) + }); + n._callback = e; + return this.on(t, n, r) + }, off: function (t, e, r) { + var s, n, a, o, h, u, l, f; + if (!this._events || !c(this, "off", t, [e, r]))return this; + if (!t && !e && !r) { + this._events = void 0; + return this + } + o = t ? [t] : i.keys(this._events); + for (h = 0, u = o.length; h < u; h++) { + t = o[h]; + if (a = this._events[t]) { + this._events[t] = s = []; + if (e || r) { + for (l = 0, f = a.length; l < f; l++) { + n = a[l]; + if (e && e !== n.callback && e !== n.callback._callback || r && r !== n.context) { + s.push(n) + } + } + } + if (!s.length)delete this._events[t] + } + } + return this + }, trigger: function (t) { + if (!this._events)return this; + var e = o.call(arguments, 1); + if (!c(this, "trigger", t, e))return this; + var i = this._events[t]; + var r = this._events.all; + if (i)f(i, e); + if (r)f(r, arguments); + return this + }, stopListening: function (t, e, r) { + var s = this._listeningTo; + if (!s)return this; + var n = !e && !r; + if (!r && typeof e === "object")r = this; + if (t)(s = {})[t._listenId] = t; + for (var a in s) { + t = s[a]; + t.off(e, r, this); + if (n || i.isEmpty(t._events))delete this._listeningTo[a] + } + return this + } + }; + var l = /\s+/; + var c = function (t, e, i, r) { + if (!i)return true; + if (typeof i === "object") { + for (var s in i) { + t[e].apply(t, [s, i[s]].concat(r)) + } + return false + } + if (l.test(i)) { + var n = i.split(l); + for (var a = 0, o = n.length; a < o; a++) { + t[e].apply(t, [n[a]].concat(r)) + } + return false + } + return true + }; + var f = function (t, e) { + var i, r = -1, s = t.length, n = e[0], a = e[1], o = e[2]; + switch (e.length) { + case 0: + while (++r < s)(i = t[r]).callback.call(i.ctx); + return; + case 1: + while (++r < s)(i = t[r]).callback.call(i.ctx, n); + return; + case 2: + while (++r < s)(i = t[r]).callback.call(i.ctx, n, a); + return; + case 3: + while (++r < s)(i = t[r]).callback.call(i.ctx, n, a, o); + return; + default: + while (++r < s)(i = t[r]).callback.apply(i.ctx, e); + return + } + }; + var d = {listenTo: "on", listenToOnce: "once"}; + i.each(d, function (t, e) { + u[e] = function (e, r, s) { + var n = this._listeningTo || (this._listeningTo = {}); + var a = e._listenId || (e._listenId = i.uniqueId("l")); + n[a] = e; + if (!s && typeof r === "object")s = this; + e[t](r, s, this); + return this + } + }); + u.bind = u.on; + u.unbind = u.off; + i.extend(e, u); + var p = e.Model = function (t, e) { + var r = t || {}; + e || (e = {}); + this.cid = i.uniqueId("c"); + this.attributes = {}; + if (e.collection)this.collection = e.collection; + if (e.parse)r = this.parse(r, e) || {}; + r = i.defaults({}, r, i.result(this, "defaults")); + this.set(r, e); + this.changed = {}; + this.initialize.apply(this, arguments) + }; + i.extend(p.prototype, u, { + changed: null, validationError: null, idAttribute: "id", initialize: function () { + }, toJSON: function (t) { + return i.clone(this.attributes) + }, sync: function () { + return e.sync.apply(this, arguments) + }, get: function (t) { + return this.attributes[t] + }, escape: function (t) { + return i.escape(this.get(t)) + }, has: function (t) { + return this.get(t) != null + }, set: function (t, e, r) { + var s, n, a, o, h, u, l, c; + if (t == null)return this; + if (typeof t === "object") { + n = t; + r = e + } else { + (n = {})[t] = e + } + r || (r = {}); + if (!this._validate(n, r))return false; + a = r.unset; + h = r.silent; + o = []; + u = this._changing; + this._changing = true; + if (!u) { + this._previousAttributes = i.clone(this.attributes); + this.changed = {} + } + c = this.attributes, l = this._previousAttributes; + if (this.idAttribute in n)this.id = n[this.idAttribute]; + for (s in n) { + e = n[s]; + if (!i.isEqual(c[s], e))o.push(s); + if (!i.isEqual(l[s], e)) { + this.changed[s] = e + } else { + delete this.changed[s] + } + a ? delete c[s] : c[s] = e + } + if (!h) { + if (o.length)this._pending = r; + for (var f = 0, d = o.length; f < d; f++) { + this.trigger("change:" + o[f], this, c[o[f]], r) + } + } + if (u)return this; + if (!h) { + while (this._pending) { + r = this._pending; + this._pending = false; + this.trigger("change", this, r) + } + } + this._pending = false; + this._changing = false; + return this + }, unset: function (t, e) { + return this.set(t, void 0, i.extend({}, e, {unset: true})) + }, clear: function (t) { + var e = {}; + for (var r in this.attributes)e[r] = void 0; + return this.set(e, i.extend({}, t, {unset: true})) + }, hasChanged: function (t) { + if (t == null)return !i.isEmpty(this.changed); + return i.has(this.changed, t) + }, changedAttributes: function (t) { + if (!t)return this.hasChanged() ? i.clone(this.changed) : false; + var e, r = false; + var s = this._changing ? this._previousAttributes : this.attributes; + for (var n in t) { + if (i.isEqual(s[n], e = t[n]))continue; + (r || (r = {}))[n] = e + } + return r + }, previous: function (t) { + if (t == null || !this._previousAttributes)return null; + return this._previousAttributes[t] + }, previousAttributes: function () { + return i.clone(this._previousAttributes) + }, fetch: function (t) { + t = t ? i.clone(t) : {}; + if (t.parse === void 0)t.parse = true; + var e = this; + var r = t.success; + t.success = function (i) { + if (!e.set(e.parse(i, t), t))return false; + if (r)r(e, i, t); + e.trigger("sync", e, i, t) + }; + q(this, t); + return this.sync("read", this, t) + }, save: function (t, e, r) { + var s, n, a, o = this.attributes; + if (t == null || typeof t === "object") { + s = t; + r = e + } else { + (s = {})[t] = e + } + r = i.extend({validate: true}, r); + if (s && !r.wait) { + if (!this.set(s, r))return false + } else { + if (!this._validate(s, r))return false + } + if (s && r.wait) { + this.attributes = i.extend({}, o, s) + } + if (r.parse === void 0)r.parse = true; + var h = this; + var u = r.success; + r.success = function (t) { + h.attributes = o; + var e = h.parse(t, r); + if (r.wait)e = i.extend(s || {}, e); + if (i.isObject(e) && !h.set(e, r)) { + return false + } + if (u)u(h, t, r); + h.trigger("sync", h, t, r) + }; + q(this, r); + n = this.isNew() ? "create" : r.patch ? "patch" : "update"; + if (n === "patch")r.attrs = s; + a = this.sync(n, this, r); + if (s && r.wait)this.attributes = o; + return a + }, destroy: function (t) { + t = t ? i.clone(t) : {}; + var e = this; + var r = t.success; + var s = function () { + e.trigger("destroy", e, e.collection, t) + }; + t.success = function (i) { + if (t.wait || e.isNew())s(); + if (r)r(e, i, t); + if (!e.isNew())e.trigger("sync", e, i, t) + }; + if (this.isNew()) { + t.success(); + return false + } + q(this, t); + var n = this.sync("delete", this, t); + if (!t.wait)s(); + return n + }, url: function () { + var t = i.result(this, "urlRoot") || i.result(this.collection, "url") || M(); + if (this.isNew())return t; + return t.replace(/([^\/])$/, "$1/") + encodeURIComponent(this.id) + }, parse: function (t, e) { + return t + }, clone: function () { + return new this.constructor(this.attributes) + }, isNew: function () { + return !this.has(this.idAttribute) + }, isValid: function (t) { + return this._validate({}, i.extend(t || {}, {validate: true})) + }, _validate: function (t, e) { + if (!e.validate || !this.validate)return true; + t = i.extend({}, this.attributes, t); + var r = this.validationError = this.validate(t, e) || null; + if (!r)return true; + this.trigger("invalid", this, r, i.extend(e, {validationError: r})); + return false + } + }); + var v = ["keys", "values", "pairs", "invert", "pick", "omit"]; + i.each(v, function (t) { + p.prototype[t] = function () { + var e = o.call(arguments); + e.unshift(this.attributes); + return i[t].apply(i, e) + } + }); + var g = e.Collection = function (t, e) { + e || (e = {}); + if (e.model)this.model = e.model; + if (e.comparator !== void 0)this.comparator = e.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (t)this.reset(t, i.extend({silent: true}, e)) + }; + var m = {add: true, remove: true, merge: true}; + var y = {add: true, remove: false}; + i.extend(g.prototype, u, { + model: p, initialize: function () { + }, toJSON: function (t) { + return this.map(function (e) { + return e.toJSON(t) + }) + }, sync: function () { + return e.sync.apply(this, arguments) + }, add: function (t, e) { + return this.set(t, i.extend({merge: false}, e, y)) + }, remove: function (t, e) { + var r = !i.isArray(t); + t = r ? [t] : i.clone(t); + e || (e = {}); + var s, n, a, o; + for (s = 0, n = t.length; s < n; s++) { + o = t[s] = this.get(t[s]); + if (!o)continue; + delete this._byId[o.id]; + delete this._byId[o.cid]; + a = this.indexOf(o); + this.models.splice(a, 1); + this.length--; + if (!e.silent) { + e.index = a; + o.trigger("remove", o, this, e) + } + this._removeReference(o, e) + } + return r ? t[0] : t + }, set: function (t, e) { + e = i.defaults({}, e, m); + if (e.parse)t = this.parse(t, e); + var r = !i.isArray(t); + t = r ? t ? [t] : [] : i.clone(t); + var s, n, a, o, h, u, l; + var c = e.at; + var f = this.model; + var d = this.comparator && c == null && e.sort !== false; + var v = i.isString(this.comparator) ? this.comparator : null; + var g = [], y = [], _ = {}; + var b = e.add, w = e.merge, x = e.remove; + var E = !d && b && x ? [] : false; + for (s = 0, n = t.length; s < n; s++) { + h = t[s] || {}; + if (h instanceof p) { + a = o = h + } else { + a = h[f.prototype.idAttribute || "id"] + } + if (u = this.get(a)) { + if (x)_[u.cid] = true; + if (w) { + h = h === o ? o.attributes : h; + if (e.parse)h = u.parse(h, e); + u.set(h, e); + if (d && !l && u.hasChanged(v))l = true + } + t[s] = u + } else if (b) { + o = t[s] = this._prepareModel(h, e); + if (!o)continue; + g.push(o); + this._addReference(o, e) + } + o = u || o; + if (E && (o.isNew() || !_[o.id]))E.push(o); + _[o.id] = true + } + if (x) { + for (s = 0, n = this.length; s < n; ++s) { + if (!_[(o = this.models[s]).cid])y.push(o) + } + if (y.length)this.remove(y, e) + } + if (g.length || E && E.length) { + if (d)l = true; + this.length += g.length; + if (c != null) { + for (s = 0, n = g.length; s < n; s++) { + this.models.splice(c + s, 0, g[s]) + } + } else { + if (E)this.models.length = 0; + var k = E || g; + for (s = 0, n = k.length; s < n; s++) { + this.models.push(k[s]) + } + } + } + if (l)this.sort({silent: true}); + if (!e.silent) { + for (s = 0, n = g.length; s < n; s++) { + (o = g[s]).trigger("add", o, this, e) + } + if (l || E && E.length)this.trigger("sort", this, e) + } + return r ? t[0] : t + }, reset: function (t, e) { + e || (e = {}); + for (var r = 0, s = this.models.length; r < s; r++) { + this._removeReference(this.models[r], e) + } + e.previousModels = this.models; + this._reset(); + t = this.add(t, i.extend({silent: true}, e)); + if (!e.silent)this.trigger("reset", this, e); + return t + }, push: function (t, e) { + return this.add(t, i.extend({at: this.length}, e)) + }, pop: function (t) { + var e = this.at(this.length - 1); + this.remove(e, t); + return e + }, unshift: function (t, e) { + return this.add(t, i.extend({at: 0}, e)) + }, shift: function (t) { + var e = this.at(0); + this.remove(e, t); + return e + }, slice: function () { + return o.apply(this.models, arguments) + }, get: function (t) { + if (t == null)return void 0; + return this._byId[t] || this._byId[t.id] || this._byId[t.cid] + }, at: function (t) { + return this.models[t] + }, where: function (t, e) { + if (i.isEmpty(t))return e ? void 0 : []; + return this[e ? "find" : "filter"](function (e) { + for (var i in t) { + if (t[i] !== e.get(i))return false + } + return true + }) + }, findWhere: function (t) { + return this.where(t, true) + }, sort: function (t) { + if (!this.comparator)throw new Error("Cannot sort a set without a comparator"); + t || (t = {}); + if (i.isString(this.comparator) || this.comparator.length === 1) { + this.models = this.sortBy(this.comparator, this) + } else { + this.models.sort(i.bind(this.comparator, this)) + } + if (!t.silent)this.trigger("sort", this, t); + return this + }, pluck: function (t) { + return i.invoke(this.models, "get", t) + }, fetch: function (t) { + t = t ? i.clone(t) : {}; + if (t.parse === void 0)t.parse = true; + var e = t.success; + var r = this; + t.success = function (i) { + var s = t.reset ? "reset" : "set"; + r[s](i, t); + if (e)e(r, i, t); + r.trigger("sync", r, i, t) + }; + q(this, t); + return this.sync("read", this, t) + }, create: function (t, e) { + e = e ? i.clone(e) : {}; + if (!(t = this._prepareModel(t, e)))return false; + if (!e.wait)this.add(t, e); + var r = this; + var s = e.success; + e.success = function (t, i) { + if (e.wait)r.add(t, e); + if (s)s(t, i, e) + }; + t.save(null, e); + return t + }, parse: function (t, e) { + return t + }, clone: function () { + return new this.constructor(this.models) + }, _reset: function () { + this.length = 0; + this.models = []; + this._byId = {} + }, _prepareModel: function (t, e) { + if (t instanceof p)return t; + e = e ? i.clone(e) : {}; + e.collection = this; + var r = new this.model(t, e); + if (!r.validationError)return r; + this.trigger("invalid", this, r.validationError, e); + return false + }, _addReference: function (t, e) { + this._byId[t.cid] = t; + if (t.id != null)this._byId[t.id] = t; + if (!t.collection)t.collection = this; + t.on("all", this._onModelEvent, this) + }, _removeReference: function (t, e) { + if (this === t.collection)delete t.collection; + t.off("all", this._onModelEvent, this) + }, _onModelEvent: function (t, e, i, r) { + if ((t === "add" || t === "remove") && i !== this)return; + if (t === "destroy")this.remove(e, r); + if (e && t === "change:" + e.idAttribute) { + delete this._byId[e.previous(e.idAttribute)]; + if (e.id != null)this._byId[e.id] = e + } + this.trigger.apply(this, arguments) + } + }); + var _ = ["forEach", "each", "map", "collect", "reduce", "foldl", "inject", "reduceRight", "foldr", "find", "detect", "filter", "select", "reject", "every", "all", "some", "any", "include", "contains", "invoke", "max", "min", "toArray", "size", "first", "head", "take", "initial", "rest", "tail", "drop", "last", "without", "difference", "indexOf", "shuffle", "lastIndexOf", "isEmpty", "chain", "sample"]; + i.each(_, function (t) { + g.prototype[t] = function () { + var e = o.call(arguments); + e.unshift(this.models); + return i[t].apply(i, e) + } + }); + var b = ["groupBy", "countBy", "sortBy", "indexBy"]; + i.each(b, function (t) { + g.prototype[t] = function (e, r) { + var s = i.isFunction(e) ? e : function (t) { + return t.get(e) + }; + return i[t](this.models, s, r) + } + }); + var w = e.View = function (t) { + this.cid = i.uniqueId("view"); + t || (t = {}); + i.extend(this, i.pick(t, E)); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents() + }; + var x = /^(\S+)\s*(.*)$/; + var E = ["model", "collection", "el", "id", "attributes", "className", "tagName", "events"]; + i.extend(w.prototype, u, { + tagName: "div", $: function (t) { + return this.$el.find(t) + }, initialize: function () { + }, render: function () { + return this + }, remove: function () { + this.$el.remove(); + this.stopListening(); + return this + }, setElement: function (t, i) { + if (this.$el)this.undelegateEvents(); + this.$el = t instanceof e.$ ? t : e.$(t); + this.el = this.$el[0]; + if (i !== false)this.delegateEvents(); + return this + }, delegateEvents: function (t) { + if (!(t || (t = i.result(this, "events"))))return this; + this.undelegateEvents(); + for (var e in t) { + var r = t[e]; + if (!i.isFunction(r))r = this[t[e]]; + if (!r)continue; + var s = e.match(x); + var n = s[1], a = s[2]; + r = i.bind(r, this); + n += ".delegateEvents" + this.cid; + if (a === "") { + this.$el.on(n, r) + } else { + this.$el.on(n, a, r) + } + } + return this + }, undelegateEvents: function () { + this.$el.off(".delegateEvents" + this.cid); + return this + }, _ensureElement: function () { + if (!this.el) { + var t = i.extend({}, i.result(this, "attributes")); + if (this.id)t.id = i.result(this, "id"); + if (this.className)t["class"] = i.result(this, "className"); + var r = e.$("<" + i.result(this, "tagName") + ">").attr(t); + this.setElement(r, false) + } else { + this.setElement(i.result(this, "el"), false) + } + } + }); + e.sync = function (t, r, s) { + var n = T[t]; + i.defaults(s || (s = {}), {emulateHTTP: e.emulateHTTP, emulateJSON: e.emulateJSON}); + var a = {type: n, dataType: "json"}; + if (!s.url) { + a.url = i.result(r, "url") || M() + } + if (s.data == null && r && (t === "create" || t === "update" || t === "patch")) { + a.contentType = "application/json"; + a.data = JSON.stringify(s.attrs || r.toJSON(s)) + } + if (s.emulateJSON) { + a.contentType = "application/x-www-form-urlencoded"; + a.data = a.data ? {model: a.data} : {} + } + if (s.emulateHTTP && (n === "PUT" || n === "DELETE" || n === "PATCH")) { + a.type = "POST"; + if (s.emulateJSON)a.data._method = n; + var o = s.beforeSend; + s.beforeSend = function (t) { + t.setRequestHeader("X-HTTP-Method-Override", n); + if (o)return o.apply(this, arguments) + } + } + if (a.type !== "GET" && !s.emulateJSON) { + a.processData = false + } + if (a.type === "PATCH" && k) { + a.xhr = function () { + return new ActiveXObject("Microsoft.XMLHTTP") + } + } + var h = s.xhr = e.ajax(i.extend(a, s)); + r.trigger("request", r, h, s); + return h + }; + var k = typeof window !== "undefined" && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent); + var T = {create: "POST", update: "PUT", patch: "PATCH", "delete": "DELETE", read: "GET"}; + e.ajax = function () { + return e.$.ajax.apply(e.$, arguments) + }; + var $ = e.Router = function (t) { + t || (t = {}); + if (t.routes)this.routes = t.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments) + }; + var S = /\((.*?)\)/g; + var H = /(\(\?)?:\w+/g; + var A = /\*\w+/g; + var I = /[\-{}\[\]+?.,\\\^$|#\s]/g; + i.extend($.prototype, u, { + initialize: function () { + }, route: function (t, r, s) { + if (!i.isRegExp(t))t = this._routeToRegExp(t); + if (i.isFunction(r)) { + s = r; + r = "" + } + if (!s)s = this[r]; + var n = this; + e.history.route(t, function (i) { + var a = n._extractParameters(t, i); + n.execute(s, a); + n.trigger.apply(n, ["route:" + r].concat(a)); + n.trigger("route", r, a); + e.history.trigger("route", n, r, a) + }); + return this + }, execute: function (t, e) { + if (t)t.apply(this, e) + }, navigate: function (t, i) { + e.history.navigate(t, i); + return this + }, _bindRoutes: function () { + if (!this.routes)return; + this.routes = i.result(this, "routes"); + var t, e = i.keys(this.routes); + while ((t = e.pop()) != null) { + this.route(t, this.routes[t]) + } + }, _routeToRegExp: function (t) { + t = t.replace(I, "\\$&").replace(S, "(?:$1)?").replace(H, function (t, e) { + return e ? t : "([^/?]+)" + }).replace(A, "([^?]*?)"); + return new RegExp("^" + t + "(?:\\?([\\s\\S]*))?$") + }, _extractParameters: function (t, e) { + var r = t.exec(e).slice(1); + return i.map(r, function (t, e) { + if (e === r.length - 1)return t || null; + return t ? decodeURIComponent(t) : null + }) + } + }); + var N = e.History = function () { + this.handlers = []; + i.bindAll(this, "checkUrl"); + if (typeof window !== "undefined") { + this.location = window.location; + this.history = window.history + } + }; + var R = /^[#\/]|\s+$/g; + var O = /^\/+|\/+$/g; + var P = /msie [\w.]+/; + var C = /\/$/; + var j = /#.*$/; + N.started = false; + i.extend(N.prototype, u, { + interval: 50, atRoot: function () { + return this.location.pathname.replace(/[^\/]$/, "$&/") === this.root + }, getHash: function (t) { + var e = (t || this).location.href.match(/#(.*)$/); + return e ? e[1] : "" + }, getFragment: function (t, e) { + if (t == null) { + if (this._hasPushState || !this._wantsHashChange || e) { + t = decodeURI(this.location.pathname + this.location.search); + var i = this.root.replace(C, ""); + if (!t.indexOf(i))t = t.slice(i.length) + } else { + t = this.getHash() + } + } + return t.replace(R, "") + }, start: function (t) { + if (N.started)throw new Error("Backbone.history has already been started"); + N.started = true; + this.options = i.extend({root: "/"}, this.options, t); + this.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + var r = this.getFragment(); + var s = document.documentMode; + var n = P.exec(navigator.userAgent.toLowerCase()) && (!s || s <= 7); + this.root = ("/" + this.root + "/").replace(O, "/"); + if (n && this._wantsHashChange) { + var a = e.$('