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 = '';
+ 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.$('