├── .edpproj └── .gitignore ├── .gitignore ├── README.md ├── demo ├── .gitignore ├── ActionPanel.html ├── ChildView.html ├── Test.js ├── TestView.js ├── chi │ ├── .edpproj │ │ └── .gitignore │ ├── README.md │ ├── index.htm │ ├── package.json │ ├── server │ │ ├── affair.js │ │ ├── app.js │ │ ├── database.js │ │ ├── member.js │ │ └── package.json │ └── src │ │ ├── affair │ │ ├── Form.js │ │ ├── FormModel.js │ │ ├── FormView.js │ │ ├── List.js │ │ ├── ListModel.js │ │ ├── ListView.js │ │ ├── config.js │ │ ├── css │ │ │ └── form.css │ │ ├── form.tpl │ │ └── list.tpl │ │ ├── common │ │ ├── css │ │ │ └── operation.css │ │ ├── esl-1.3.0.min.js │ │ ├── esl-css.js │ │ ├── main.js │ │ └── sugar-1.3.9.min.js │ │ └── member │ │ ├── Form.js │ │ ├── FormModel.js │ │ ├── FormView.js │ │ ├── List.js │ │ ├── ListModel.js │ │ ├── ListView.js │ │ ├── config.js │ │ ├── css │ │ └── form.css │ │ ├── form.tpl │ │ └── list.tpl └── esl.js ├── doc ├── ActionPanel.md ├── UIModel.md └── UIView.md ├── edp-build-config.js ├── module.conf ├── package.json ├── src ├── .jshintrc ├── ActionDialog.js ├── ActionPanel.js ├── ChildView.js ├── RemoteTreeStrategy.js ├── UIModel.js └── UIView.js └── test ├── UIView.js ├── asset ├── css │ └── jasmine.css └── js │ ├── async.js │ ├── esl.js │ ├── jasmine-html.js │ └── jasmine.js ├── matchers.js ├── run.htm └── tpl └── plain.tpl /.edpproj/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/ef/53701663eb471f4a824151262c618458b6487789/.edpproj/.gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | dep 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EF (Enterprise Framework) 2 | 3 | EF是[ER](https://github.com/ecomfe/er)与[ESUI](https://github.com/ecomfe/esui)整合而成的高效率业务系统开发框架。 4 | 5 | ## 关于名称 6 | 7 | EF是 **Enterprise Framework** 的简称,表明其作为企业级开发框架的身份。 8 | 9 | 另外该项目负责人很喜欢一部叫EF的动画 10 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | dep 2 | *.css -------------------------------------------------------------------------------- /demo/ActionPanel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ActionPanel 6 | 7 | 54 | 55 | 56 |
57 | 104 | 105 | -------------------------------------------------------------------------------- /demo/ChildView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ChildView 6 | 7 | 54 | 63 | 64 | 65 | 66 | Refresh 67 | 77 | 78 | -------------------------------------------------------------------------------- /demo/Test.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | var Action = require('er/Action'); 3 | var Deferred = require('er/Deferred'); 4 | var util = require('er/util'); 5 | 6 | function Test() { 7 | Action.apply(this, arguments); 8 | } 9 | 10 | Test.prototype.enter = function () { 11 | console.log('enter test'); 12 | var me = this; 13 | setTimeout( 14 | function () { 15 | var event = me.fire('test', { x: 1 }); 16 | console.log(event.isDefaultPrevented()); 17 | console.log(event.isPropagationStopped()); 18 | }, 19 | 500 20 | ); 21 | return Deferred.resolved(); 22 | }; 23 | 24 | util.inherits(Test, Action); 25 | return Test; 26 | }) -------------------------------------------------------------------------------- /demo/TestView.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | var View = require('er/View'); 3 | var util = require('er/util'); 4 | 5 | function TestView() { 6 | View.apply(this, arguments); 7 | } 8 | 9 | TestView.prototype.render = function () { 10 | this.getContainerElement().innerHTML = 'Hello World, it\'s now ' + (new Date()).toLocaleTimeString();; 11 | }; 12 | 13 | util.inherits(TestView, View); 14 | return TestView; 15 | }) -------------------------------------------------------------------------------- /demo/chi/.edpproj/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/ef/53701663eb471f4a824151262c618458b6487789/demo/chi/.edpproj/.gitignore -------------------------------------------------------------------------------- /demo/chi/README.md: -------------------------------------------------------------------------------- 1 | # 吃! 2 | 3 | 饭团管理系统 4 | 5 | ## 启动方法 6 | 7 | 1. `cd {ef的根目录}/demo/chi` 8 | 2. `mkdir lib && cd lib` 9 | 3. `git clone git://github.com/ecomfe/er.git` 10 | 4. `git clone git://github.com/ecomfe/esui.git` 11 | 5. `cd {ef的根目录}/demo/chi/server` 12 | 6. `npm install` 13 | 7. `node app.js` 14 | 8. 访问 **http://localhost:8788** -------------------------------------------------------------------------------- /demo/chi/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 吃! 6 | 7 | 8 | 9 |
10 | 11 | 12 | 61 | 62 | -------------------------------------------------------------------------------- /demo/chi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chi", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "ef": "3.1.x" 7 | }, 8 | "edp": { 9 | "wwwroot": "/", 10 | "depDir": "dep", 11 | "srcDir": "src", 12 | "loaderAutoConfig": "js,htm,html,tpl,vm,phtml", 13 | "loaderUrl": "http://s1.bdstatic.com/r/www/cache/ecom/esl/1-8-0/esl.js", 14 | "dependencies": { 15 | "ef": "3.1.x" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /demo/chi/server/affair.js: -------------------------------------------------------------------------------- 1 | var database = require('./database'); 2 | 3 | function getAffair(context) { 4 | var member = database.members({ id: +context.member }).get()[0]; 5 | var affair = { 6 | id: +context.id, 7 | time: context.time, 8 | member: { 9 | id: member.id, 10 | name: member.name 11 | }, 12 | type: +context.type, 13 | amount: +context.amount 14 | }; 15 | affair.balance = affair.type === 0 16 | ? member.balance + affair.amount 17 | : member.balance - affair.amount; 18 | 19 | return affair; 20 | } 21 | 22 | exports.list = function (req, res) { 23 | var page = database.getPage('affairs', req.body.page, req.body.pageSize); 24 | 25 | res.writeHead(200, { 'Content-Type': 'application/json' }); 26 | res.end(JSON.stringify(page)); 27 | }; 28 | 29 | exports.save = function (req, res) { 30 | var id = database.affairs().count() + 1; 31 | var affair = getAffair(req.body); 32 | affair.id = id; 33 | database.affairs.insert(affair); 34 | 35 | res.writeHead(200, { 'Content-Type': 'application/json' }); 36 | res.end(JSON.stringify({ success: true })); 37 | }; 38 | 39 | exports.update = function (req, res) { 40 | var affair = getAffair(req.body); 41 | database.affairs.merge(affair, 'id'); 42 | 43 | res.writeHead(200, { 'Content-Type': 'application/json' }); 44 | res.end(JSON.stringify({ success: true })); 45 | }; 46 | 47 | exports.find = function (req, res) { 48 | var affair = database.affairs({ id: +req.query.id }).get()[0]; 49 | 50 | res.writeHead(200, { 'Content-Type': 'application/json' }); 51 | res.end(JSON.stringify(affair)); 52 | }; -------------------------------------------------------------------------------- /demo/chi/server/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express'); 7 | var http = require('http'); 8 | var path = require('path'); 9 | 10 | var app = express(); 11 | 12 | // all environments 13 | app.set('port', process.env.PORT || 8788); 14 | app.use(express.favicon()); 15 | app.use(express.logger('dev')); 16 | app.use(express.bodyParser()); 17 | app.use(express.methodOverride()); 18 | app.use(app.router); 19 | app.use(express.static(path.join(__dirname, '..', '..', '..'))); 20 | 21 | // development only 22 | if ('development' == app.get('env')) { 23 | app.use(express.errorHandler()); 24 | } 25 | app.get( 26 | '/', 27 | function(req, res) { 28 | res.writeHead(302, { 'Location': '/demo/chi/index.htm' }); 29 | res.end(); 30 | } 31 | ); 32 | var member = require('./member'); 33 | app.get('/member/list', member.list); 34 | app.post('/member/save', member.save); 35 | app.post('/member/update', member.update); 36 | app.get('/member/find', member.find); 37 | app.post('/member/remove', member.remove); 38 | var affair = require('./affair'); 39 | app.get('/affair/list', affair.list); 40 | app.post('/affair/save', affair.save); 41 | app.post('/affair/update', affair.update); 42 | app.get('/affair/find', affair.find); 43 | 44 | http.createServer(app).listen(app.get('port'), function (){ 45 | console.log('open http://localhost:' + app.get('port')); 46 | }); 47 | -------------------------------------------------------------------------------- /demo/chi/server/database.js: -------------------------------------------------------------------------------- 1 | var Taffy = require('taffydb').taffy; 2 | 3 | function random(start, end) { 4 | var value = Math.round(Math.random() * (end - start)) + start; 5 | return value; 6 | } 7 | 8 | var members = Taffy(); 9 | members.insert({ id: 1, name: '李享', gender: 0, birthday: '1983-05-28', balance: 233 }); 10 | members.insert({ id: 2, name: '石磊', gender: 0, birthday: '1984-07-22', balance: 1024 }); 11 | members.insert({ id: 3, name: '李义冬', gender: 1, birthday: '1987-08-13', balance: 198 }); 12 | members.insert({ id: 4, name: '刘恺华', gender: 1, birthday: '1986-08-25', balance: -1322 }); 13 | members.insert({ id: 5, name: '沈彬', gender: 1, birthday: '1988-03-14', balance: 4 }); 14 | members.insert({ id: 6, name: '张立理', gender: 1, birthday: '1986-10-05', balance: -130 }); 15 | members.insert({ id: 7, name: '孙金飞', gender: 1, birthday: '1980-03-11', balance: 250 }); 16 | members.insert({ id: 8, name: '叶梦秋', gender: 0, birthday: '1987-11-1', balance: 14 }); 17 | 18 | var affairs = Taffy(); 19 | for (var i = 0; i < 36; i++) { 20 | var member = members({ id: random(1, 8) }).get()[0]; 21 | var item = { 22 | id: i + 1, 23 | time: '2013-' + random(1, 12) + '-' + random(1, 28), 24 | member: { 25 | id: member.id, 26 | name: member.name 27 | }, 28 | type: random(0, 1), 29 | amount: random(0, 43), 30 | balance: random(-990, 988) 31 | }; 32 | affairs.insert(item); 33 | } 34 | 35 | var activities = Taffy(); 36 | 37 | var database = module.exports = { 38 | members: members, 39 | affairs: affairs, 40 | 41 | getPage: function (table, page, pageSize) { 42 | page = page ? +page : 1; 43 | pageSize = pageSize ? +pageSize : 10; 44 | if (typeof table === 'string') { 45 | table = database[table]; 46 | } 47 | table = table(); 48 | 49 | return { 50 | totalCount: table.count(), 51 | page: page, 52 | pageSize: 10, 53 | results: table.start((page - 1) * pageSize).limit(pageSize).get() 54 | }; 55 | } 56 | } -------------------------------------------------------------------------------- /demo/chi/server/member.js: -------------------------------------------------------------------------------- 1 | var database = require('./database'); 2 | 3 | function getMember(context) { 4 | var member = { 5 | id: +context.id, 6 | name: context.name, 7 | gender: +context.gender, 8 | birthday: context.birthday 9 | }; 10 | 11 | return member; 12 | } 13 | 14 | exports.list = function (req, res) { 15 | var page = database.getPage('members', req.body.page, req.body.pageSize); 16 | 17 | res.writeHead(200, { 'Content-Type': 'application/json' }); 18 | res.end(JSON.stringify(page)); 19 | }; 20 | 21 | exports.save = function (req, res) { 22 | var id = database.members().count() + 1; 23 | var member = getMember(req.body); 24 | member.balance = 0; 25 | member.id = id; 26 | database.members.insert(member); 27 | 28 | res.writeHead(200, { 'Content-Type': 'application/json' }); 29 | res.end(JSON.stringify({ success: true })); 30 | }; 31 | 32 | exports.update = function (req, res) { 33 | var member = getMember(req.body); 34 | database.members.merge(member, 'id'); 35 | 36 | res.writeHead(200, { 'Content-Type': 'application/json' }); 37 | res.end(JSON.stringify({ success: true })); 38 | }; 39 | 40 | exports.find = function (req, res) { 41 | var member = database.members({ id: +req.query.id }).get()[0]; 42 | 43 | res.writeHead(200, { 'Content-Type': 'application/json' }); 44 | res.end(JSON.stringify(member)); 45 | }; 46 | 47 | exports.remove = function (req, res) { 48 | var member = database.members({ id: +req.body.id }).remove(); 49 | 50 | res.writeHead(200, { 'Content-Type': 'application/json' }); 51 | res.end(JSON.stringify({ success: true })); 52 | }; -------------------------------------------------------------------------------- /demo/chi/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chi-server", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "3.2.4", 10 | "moment": "*", 11 | "taffydb": "*" 12 | } 13 | } -------------------------------------------------------------------------------- /demo/chi/src/affair/Form.js: -------------------------------------------------------------------------------- 1 | define( 2 | function(require) { 3 | var Action = require('er/Action'); 4 | 5 | function AffairForm() { 6 | Action.apply(this, arguments); 7 | } 8 | 9 | function cancelSubmit() { 10 | this.redirect('/affair/list'); 11 | } 12 | 13 | function submitAffair(e) { 14 | this.model.save(e.affair).then(saveCallBack.bind(this)); 15 | } 16 | 17 | function saveCallBack(response) { 18 | if (response.success === true) { 19 | this.redirect('/affair/list'); 20 | } 21 | else { 22 | alert('出错啦!'); 23 | } 24 | } 25 | 26 | AffairForm.prototype.modelType = require('./FormModel'); 27 | 28 | AffairForm.prototype.viewType = require('./FormView'); 29 | 30 | AffairForm.prototype.initBehavior = function() { 31 | this.view.on('submit', submitAffair.bind(this)); 32 | this.view.on('cancel', cancelSubmit.bind(this)); 33 | }; 34 | 35 | require('er/util').inherits(AffairForm, Action); 36 | 37 | return AffairForm; 38 | } 39 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/FormModel.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var UIModel = require('ef/UIModel'); 4 | var datasource = require('er/datasource'); 5 | 6 | var types = [ 7 | { 8 | text: '充值', 9 | value: 0 10 | }, 11 | { 12 | text: '支出', 13 | value: 1 14 | } 15 | ]; 16 | 17 | function AffairFormModel() { 18 | UIModel.apply(this, arguments); 19 | 20 | var url = this.get('url'); 21 | 22 | this.datasource = { 23 | members: datasource.remote( 24 | '/member/list', 25 | { method: 'GET' } 26 | ), 27 | types: datasource.constant(types) 28 | }; 29 | 30 | if (this.get('formType') === 'update') { 31 | 32 | this.datasource.detail = datasource.remote( 33 | '/affair/find', 34 | { 35 | method: 'GET', 36 | data: { 37 | id: url.getQuery('id'), 38 | } 39 | } 40 | ); 41 | } 42 | else { 43 | var detail = { balance: 0, member: {} }; 44 | this.datasource.detail = datasource.constant(detail); 45 | } 46 | } 47 | 48 | AffairFormModel.prototype.prepare = function () { 49 | var members = this.get('members').results; 50 | var list = members.map( 51 | function (m) { return { text: m.name, value: m.id } }); 52 | this.set('members', list); 53 | } 54 | 55 | AffairFormModel.prototype.save = function(data) { 56 | this.fill(data); 57 | 58 | var postData = this.getPart.apply(this, Object.keys(data)); 59 | // update请求要多个id字段 60 | if (this.get('formType') === 'update') { 61 | var url = this.get('url'); 62 | postData.id = url.getQuery('id'); 63 | } 64 | 65 | var ajax = require('er/ajax'); 66 | var url = this.get('formType') === 'update' 67 | ? '/affair/update' 68 | : '/affair/save'; 69 | return ajax.post(url, postData); 70 | }; 71 | 72 | AffairFormModel.prototype.formatters = { 73 | time: UIModel.formatters.time 74 | }; 75 | 76 | require('er/util').inherits(AffairFormModel, UIModel); 77 | return AffairFormModel; 78 | } 79 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/FormView.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | require('er/tpl!./form.tpl'); 4 | require('esui/Form'); 5 | require('esui/Label'); 6 | require('esui/Button'); 7 | require('esui/TextBox'); 8 | require('esui/Select'); 9 | require('esui/Calendar'); 10 | // css 11 | require('css!./css/form.css'); 12 | 13 | var AffairType = require('./config').AffairType; 14 | 15 | 16 | var UIView = require('ef/UIView'); 17 | 18 | function AffairFormView() { 19 | UIView.apply(this, arguments); 20 | } 21 | 22 | function submit() { 23 | var form = this.get('form'); 24 | var data = form.getData(); 25 | this.fire('submit', {affair: data}); 26 | } 27 | 28 | function cancel() { 29 | this.fire('cancel'); 30 | } 31 | 32 | AffairFormView.prototype.template = 'affairForm'; 33 | 34 | AffairFormView.prototype.enterDocument = function() { 35 | UIView.prototype.enterDocument.apply(this, arguments); 36 | var form = this.get('form'); 37 | form.on('submit', require('er/util').bind(submit, this)); 38 | 39 | var cancelButton = this.get('cancel-button'); 40 | cancelButton.on('click', require('er/util').bind(cancel, this)) 41 | }; 42 | 43 | 44 | require('er/util').inherits(AffairFormView, UIView); 45 | return AffairFormView; 46 | } 47 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/List.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var Action = require('er/Action'); 4 | 5 | function AffairList() { 6 | Action.apply(this, arguments); 7 | } 8 | 9 | AffairList.prototype.modelType = require('./ListModel'); 10 | 11 | AffairList.prototype.viewType = require('./ListView'); 12 | 13 | AffairList.prototype.initBehavior = function () { 14 | var action = this; 15 | this.view.on( 16 | 'modify', 17 | function (e) { 18 | action.redirect('/affair/update~id=' + e.id); 19 | } 20 | ); 21 | this.view.on( 22 | 'create', 23 | this.redirect.bind(this, '/affair/create') 24 | ); 25 | }; 26 | 27 | require('er/util').inherits(AffairList, Action); 28 | return AffairList; 29 | } 30 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/ListModel.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var Model = require('er/Model'); 4 | var datasource = require('er/datasource'); 5 | 6 | function AffairListModel() { 7 | Model.apply(this, arguments); 8 | 9 | var url = this.get('url'); 10 | this.datasource = { 11 | list: datasource.remote( 12 | '/affair/list', 13 | { 14 | method: 'GET', 15 | data: { 16 | page: url.getQuery('page') || 0, 17 | pageSize: url.getQuery('pageSize') || 0 18 | } 19 | } 20 | ) 21 | }; 22 | } 23 | 24 | require('er/util').inherits(AffairListModel, Model); 25 | return AffairListModel; 26 | } 27 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/ListView.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | require('er/tpl!./list.tpl'); 4 | require('esui/Button'); 5 | require('esui/Table'); 6 | require('esui/extension/Command'); 7 | 8 | 9 | var AffairType = require('./config').AffairType; 10 | var tableFields = [ 11 | { 12 | title: '时间', 13 | field: 'time' , 14 | tip :'时间', 15 | width: 50, 16 | content: 'time' 17 | }, 18 | { 19 | title: '成员', 20 | field: 'member' , 21 | tip :'成员姓名', 22 | width: 50, 23 | content: function (item) { 24 | return item.member.name; 25 | } 26 | }, 27 | { 28 | title: '类型', 29 | field: 'type' , 30 | tip :'收支类型', 31 | width: 50, 32 | content: function (item) { 33 | return AffairType[item.type]; 34 | } 35 | }, 36 | { 37 | title: '金额', 38 | field: 'amount' , 39 | tip :'金额', 40 | width: 50, 41 | content: 'amount' 42 | }, 43 | { 44 | title: '余额', 45 | field: 'balance' , 46 | tip :'开支后余额', 47 | width: 50, 48 | content: 'balance' 49 | }, 50 | { 51 | title: '操作', 52 | width: 150, 53 | content: function (item) { 54 | return '编辑'; 56 | } 57 | } 58 | ]; 59 | 60 | var UIView = require('ef/UIView'); 61 | 62 | function AffairListView() { 63 | UIView.apply(this, arguments); 64 | } 65 | 66 | AffairListView.prototype.template = 'affairListPage'; 67 | 68 | AffairListView.prototype.uiProperties = { 69 | list: { 70 | fields: tableFields, 71 | sortable: false, 72 | columnResizable: true, 73 | subrow: false, 74 | followHead: true, 75 | selectMode: 'line' 76 | } 77 | }; 78 | 79 | AffairListView.prototype.enterDocument = function () { 80 | UIView.prototype.enterDocument.apply(this, arguments); 81 | this.get('createButton').on( 82 | 'click', 83 | this.fire.bind(this, 'create') 84 | ); 85 | } 86 | 87 | require('er/util').inherits(AffairListView, UIView); 88 | return AffairListView; 89 | } 90 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/config.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var actions = [ 4 | { 5 | path: '/', 6 | type: 'affair/List' 7 | }, 8 | { 9 | path: '/affair/list', 10 | type: 'affair/List' 11 | }, 12 | { 13 | path: '/affair/create', 14 | type: 'affair/Form', 15 | args: { formType: 'create' } 16 | }, 17 | { 18 | path: '/affair/update', 19 | type: 'affair/Form', 20 | args: { formType: 'update' } 21 | } 22 | ]; 23 | 24 | var controller = require('er/controller'); 25 | actions.forEach(controller.registerAction); 26 | 27 | var config = {}; 28 | config.AffairType = { 29 | 'CONSUMPTION': 0, 30 | 'DEPOSITE': 1, 31 | '0': '充值', 32 | '1': '支出' 33 | }; 34 | 35 | return config; 36 | } 37 | ); -------------------------------------------------------------------------------- /demo/chi/src/affair/css/form.css: -------------------------------------------------------------------------------- 1 | /* 清除浮动 */ 2 | .form-row, 3 | .form-line, 4 | .form-single-line, 5 | .form-value, 6 | .form-value-mini, 7 | .edit-submit { 8 | zoom:1; 9 | } 10 | 11 | .form-row:after, 12 | .form-line:after, 13 | .form-single-line:after, 14 | .form-value:after, 15 | .form-value-mini:after, 16 | .edit-submit:after { 17 | content:"\0020"; 18 | display:block; 19 | height:0; 20 | visibility:hidden; 21 | clear:both; 22 | } 23 | 24 | 25 | /* 表单部分 */ 26 | .edit-form { 27 | background:#FFF; 28 | margin-bottom:10px; 29 | padding: 5px; 30 | } 31 | 32 | /* 表单头部 */ 33 | .edit-form .form-head { 34 | border-width: 1px 1px 0 1px; 35 | border-style: solid; 36 | border-color: #9ecae8; 37 | background: #c7deed; 38 | height:28px; 39 | } 40 | 41 | .edit-form .form-headcntr { 42 | height:27px; 43 | line-height:27px; 44 | color:#1c4a79; 45 | border-top:1px solid #d8e8f2; 46 | } 47 | 48 | .edit-form .form-headcntr h2 { 49 | padding-left:20px; 50 | font-weight:700; 51 | font-size:12px; 52 | } 53 | 54 | 55 | /* 表单行 */ 56 | .form-row { 57 | padding:2px 0; 58 | } 59 | 60 | .form-line, 61 | .form-single-line, 62 | .form-key, 63 | .form-key-mini, 64 | .form-value-mini, 65 | .form-value { 66 | float:left; 67 | display:inline; 68 | line-height:34px; 69 | } 70 | 71 | .form-line, 72 | .form-value { 73 | width: auto; 74 | } 75 | 76 | .form-value-mini { 77 | width:300px; 78 | padding:2px; 79 | } 80 | .ui-dialog-body .form-value { 81 | width:auto; 82 | } 83 | 84 | .form-value-preview { 85 | line-height:0; 86 | } 87 | 88 | /* 表单key */ 89 | .form-key, 90 | .form-key-mini { 91 | float:left; 92 | padding:2px 0 3px 10px; 93 | width:85px; 94 | color:#333333; 95 | } 96 | 97 | .form-key-mini { 98 | padding-left:0; 99 | } 100 | 101 | 102 | /* 表单value */ 103 | .form-value { 104 | padding:2px; 105 | } 106 | .form-value-caution { 107 | float:left; 108 | color:red; 109 | } 110 | 111 | 112 | .form-tip{ 113 | color:#999999; 114 | } 115 | /*有必填标记的表单value*/ 116 | .form-value-required{ 117 | position:relative; 118 | left:-8px; 119 | } 120 | /*必填标记*/ 121 | .required-icon{ 122 | float:left; 123 | margin-right:3px; 124 | color:red; 125 | } 126 | /* 单独占一行 */ 127 | .form-single-line { 128 | padding:2px 0 2px 36px; 129 | width:500px; 130 | clear:both; 131 | } 132 | 133 | .form-line { 134 | clear:both; 135 | } 136 | 137 | /* 表单内控件样式微调 */ 138 | .form-row .ui-checkbox, 139 | .form-row .ui-radiobox { 140 | display: inline; 141 | margin-top:5px; 142 | } 143 | 144 | .form-row .ui-text { 145 | margin-right:8px; 146 | } 147 | 148 | .form-row .ui-checkbox-label, 149 | .form-row .ui-radiobox-label { 150 | display: inline; 151 | line-height: 24px; 152 | margin: 0 8px 0 0px; 153 | } 154 | 155 | /* 表单文字 */ 156 | .form-text { 157 | float:left; 158 | display:inline; 159 | margin-right:8px; 160 | line-height:34px; 161 | } 162 | 163 | /* 表单小文字 */ 164 | .form-text-s { 165 | font-size:10px; 166 | color:#999; 167 | } 168 | .form-text-arrow{ 169 | font-family:simsun; 170 | font-size:12px; 171 | } 172 | /* 表单内容块*/ 173 | .form-content-block { 174 | padding-left:98px; 175 | } 176 | 177 | .form-row .ui-combobox { 178 | float:left; 179 | margin-right:5px; 180 | } 181 | 182 | .form-row .ui-select, 183 | .form-row .ui-combobox, 184 | .form-row .ui-button, 185 | .form-row .ui-text, 186 | .form-row .ui-textarea, 187 | .form-row .ui-cal, 188 | .form-row .ui-buttonmenu, 189 | .form-row .ui-combosearchlistsel { 190 | margin-top: 5px; 191 | } 192 | 193 | .form-row .ui-text { 194 | *margin-top:1px; 195 | vertical-align: middle; 196 | *height: 21px; 197 | *line-height: 21px; 198 | } 199 | 200 | .form-row .ui-textarea{ 201 | margin-right:5px; 202 | } 203 | /* 提交部分 */ 204 | .edit-submit { 205 | /*margin:0 0 0 21px;*/ 206 | zoom:1; 207 | } 208 | 209 | 210 | /* 表单验证提示 */ 211 | .validate-error { 212 | padding:5px; 213 | display:block; 214 | } 215 | .validate { 216 | /* 217 | background:#FEF7DB; 218 | border:1px solid #F0DDA5; 219 | */ 220 | position:relative; 221 | zoom:1; 222 | height: 25px; 223 | } 224 | 225 | .validate-text { 226 | background: none repeat scroll 0 0 #FEDBDC; 227 | border: 1px solid #F0CCCC; 228 | color: #DD6666; 229 | display: block; 230 | height: 25px; 231 | line-height: 25px; 232 | margin-top: 8px; 233 | padding-left: 45px; 234 | } 235 | 236 | .validate-text-nobr { 237 | color: red; 238 | line-height: 34px; 239 | padding-left: 5px; 240 | } 241 | 242 | .validate-icon { 243 | background:#FEDBDC url(../../assets/img/allbgs.png) -23px -208px no-repeat; 244 | position: absolute; 245 | left: 15px; 246 | top: 7px; 247 | width: 14px; 248 | height: 14px; 249 | } 250 | 251 | /* 表单分支链接(支线任务) */ 252 | .form-branch-task { 253 | font-family:STHeiti, SimSun; 254 | position:absolute; 255 | right:20px; 256 | top:20px; 257 | } 258 | 259 | /* 选填 ,灰色*/ 260 | .form-optional,.form-grey { 261 | float:left; 262 | display:inline; 263 | margin-left:5px; 264 | line-height:24px; 265 | color:#CCC; 266 | } 267 | 268 | .ui-richsel-entry .form-optional { 269 | line-height:22px; 270 | } 271 | 272 | .form-grey{float:none;} 273 | .form-grey2{color:#CCC;} 274 | /* 表单提示信息(先放在这里,组件做好再转移) */ 275 | .ui-forminfo { 276 | position:relative; 277 | margin:1px; 278 | background:#EFF7F9; 279 | border:1px solid #C5DFF0; 280 | clear:both; 281 | zoom:1; 282 | } 283 | 284 | .ui-forminfo .ui-link { 285 | text-decoration:underline; 286 | } 287 | 288 | .ui-forminfo-icon { 289 | position:absolute; 290 | width:14px; 291 | height:17px; 292 | background:url(../../assets/img/BlockSprites.png) no-repeat -141px -315px; 293 | left:40px; 294 | top:6px; 295 | } 296 | 297 | .ui-forminfo-text { 298 | padding:4px 10px 4px 64px; 299 | line-height:24px; 300 | color:#8BA9C4; 301 | } 302 | 303 | .ui-forminfo-text strong { 304 | margin:0 3px; 305 | } 306 | 307 | /** 308 | * 表单内的iframe 309 | */ 310 | .form-iframe { 311 | width:850px; 312 | height:450px; 313 | overflow-x:hidden; 314 | border:none; 315 | } 316 | 317 | .form-iframe-wrapper { 318 | border-top:1px solid #DCE3E9; 319 | margin:10px; 320 | padding:5px 0; 321 | width:850px; 322 | } 323 | 324 | /** 325 | * 缩进HACK 326 | */ 327 | .indent-checkbox { 328 | padding-left: 17px; 329 | *padding-left: 23px; 330 | } 331 | /** 332 | * 表单元素组缩进 333 | */ 334 | .form-row-group 335 | { 336 | margin-left: 35px; 337 | } 338 | -------------------------------------------------------------------------------- /demo/chi/src/affair/form.tpl: -------------------------------------------------------------------------------- 1 | 2 |
4 |
5 | 6 |
7 |
当前余额:
8 |
9 | 11 |
12 |
13 | 14 |
15 |
啥时候:
16 |
17 |
19 |
20 |
21 |
22 |
谁:
23 |
24 |
27 |
28 |
29 |
30 |
充值\支出:
31 |
32 |
35 |
36 |
37 |
38 |
金额:
39 |
40 | 47 |
48 |
49 |
50 |
备注:
51 |
52 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
保存并继续
63 |
取消新建
65 |
66 |
67 |
68 |
-------------------------------------------------------------------------------- /demo/chi/src/affair/list.tpl: -------------------------------------------------------------------------------- 1 | 2 |
新建收支
4 | 5 | 6 | 7 |
9 |
-------------------------------------------------------------------------------- /demo/chi/src/common/css/operation.css: -------------------------------------------------------------------------------- 1 | .operation-remove { 2 | color: red; 3 | cursor: pointer; 4 | } 5 | .operation-modify { 6 | color: #666666; 7 | cursor: pointer; 8 | } -------------------------------------------------------------------------------- /demo/chi/src/common/esl-1.3.0.min.js: -------------------------------------------------------------------------------- 1 | var define,require;(function(n){function t(){var n=arguments.length;if(n){for(var t,r,i=arguments[--n];n--;){var o=arguments[n];"string"==typeof o?t=o:B(o)&&(r=o)}var a=window.opera;if(!t&&document.attachEvent&&(!a||"[object Opera]"!=""+a)){var u=_();t=u&&u.getAttribute("data-require-id")}r=r||["require","exports","module"],t?e(t,r,i):W.push({deps:r,factory:i})}}function r(n){var t=[];for(var r in N){var e=N[r];e.state==n&&t.push(e)}return t}function e(n,t,r){if(!v(n)){var e={id:n,deps:t,factory:r,exports:{},hardDeps:[],softDeps:[],state:P};N[n]=e}}function i(){var n=[],t=r(P);F(t,function(t){var r=t.deps.slice(0);t.realDeps=r;var e=t.factory,i=/require\(\s*(['"'])([^'"]+)\1\s*\)/g,o=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm;"function"==typeof e&&(""+e).replace(o,"").replace(i,function(n,t,e){r.push(e)}),F(r,function(r){var e=z(r);e.resource&&n.push(U(e.module,t.id))}),t.state=C}),y(n,function(){o(t)})}function o(n){var t=[];F(n,function(n){var r=n.id,e=n.realDeps,i=n.hardDeps,o=n.softDeps,u={},f=n.deps;F(f,function(n,t){n=U(n,r),f[t]=n,u[n]||g(r,n,"hardDeps")||(i.push(n),u[n]=1)});for(var c=e.length,s={};c--;){var v=U(e[c],r);!v||v in s||v in V?e.splice(c,1):(s[v]=1,e[c]=v,u[v]||o.unshift(v),t.push(v))}n.state=R,a(n)}),y(t)}function a(t){function r(){var n=1;return F(t.hardDeps,function(t){return n=t in V||p(t),!!n}),n&&F(t.softDeps,function(t){return n=t in V||p(t)||g(i,t),!!n}),n&&(t.state=G),n}function e(){if(!p(i)&&r()){var a=[];F(t.deps,function(n,t){a[t]=o[n]||l(n)});try{var f=t.factory,s="function"==typeof f?f.apply(n,a):f;s!==void 0&&(t.exports=s)}catch(v){if(0===v.message.indexOf("[MODULE_MISS]"))return;throw v}t.state=H,c(e),u(i)}}var i=t.id,o={require:O(i),exports:t.exports,module:t};s(e),e()}function u(n){Q++,F(J,function(t){t&&t(n)}),Q--,f()}function f(){1>Q&&(K.sort(function(n,t){return t-n}),F(K,function(n){J.splice(n,1)}),K=[])}function c(n){F(J,function(t,r){n==t&&K.push(r)})}function s(n){J.push(n)}function v(n){return n in N}function p(n){return v(n)&&N[n].state==H}function l(n){return v(n)?N[n].exports:null}function d(n){return N[n]}function h(n,t){N[n]={exports:t||!0,state:H},u(n)}function m(n){var t=W.slice(0);W.length=0,W=[],F(t,function(t){var r=t.id||n;e(r,t.deps,t.factory)}),i()}function g(n,t,r,e){r=r||"realDeps";var i=d(t),o=i&&i[r];if(e=e||{},e[t])return 0;if(e[t]=1,o)for(var a=o.length;a--;){var u=o[a];if(n==u||g(n,u,r,e))return 1}return 0}function y(t,r,e){function i(){function e(n){var t=1;return F(n,function(n){return a[n]||(a[n]=1,V[n])?void 0:p(n)&&e(d(n).realDeps)?void 0:(t=0,!1)}),t}if(!o){var a={};if(e(t)){o=1,c(i);var u=[];F(t,function(n){u.push(V[n]||l(n))}),r.apply(n,u)}}}if(r=r||Function(),e=e||"","string"==typeof t){if(!p(t))throw Error("[MODULE_MISS]"+t+" is not exists!");return l(t)}if(B(t)){if(0===t.length)return r(),void 0;var o=0;s(i),F(t,function(n){n in V||(n.indexOf("!")>0?D:x)(n,e)}),i()}}function x(n){function t(){var t=r.readyState;(t===void 0||/^(loaded|complete)$/.test(t))&&(r.onload=r.onreadystatechange=null,m(n),delete X[n],r=null)}if(!v(n)&&!X[n]){X[n]=1;var r=document.createElement("script");r.setAttribute("data-require-id",n),r.src=w(n)+".js",r.async=!0,r.readyState?r.onreadystatechange=t:r.onload=t,A(r)}}function D(n,t){function r(t){h(n,t)}function e(e){p(n)||e.load(a,O(t),r,{})}var i=z(n),o=i.module,a=i.resource;r.fromText=function(n,t){Function(t)(),m(n)},p(o)?e(l(o)):y([o],e)}function b(n,t){var r=Y[n];if("string"==typeof r)Y[n]=t;else if(B(r))F(t,function(n){r.push(n)});else for(var e in t)r[e]=t[e]}function k(){Y.baseUrl=Y.baseUrl.replace(/\/$/,"")+"/",q(),E(),S()}function S(){Z=[],F(Y.packages,function(n){var t=n;"string"==typeof n&&(t={name:n.split("/")[0],location:n,main:"main"}),t.location=t.location||t.name,t.main=(t.main||"main").replace(/\.js$/i,""),Z.push(t)}),Z.sort(I("name"))}function q(){nt=M(Y.paths),nt.sort(I())}function E(){tt=[],tt=M(Y.map),tt.sort(I()),F(tt,function(n){var t=n.k;n.v=M(n.v),n.v.sort(I()),n.reg="*"==t?/^/:T(t)})}function w(n){if(!it.test(n))return n;var t=n,r=0;return F(nt,function(n){var e=n.k;return T(e).test(t)?(t=t.replace(e,n.v),r=1,!1):void 0}),r||F(Z,function(r){var e=r.name;return T(e).test(n)?(t=t.replace(e,r.location),!1):void 0}),/^([a-z]{2,10}:\/)?\//i.test(t)||(t=Y.baseUrl+t),t}function O(n){function t(t,e){if("string"==typeof t){var i;return(i=r[t])||(i=y(U(t,n),e,n),r[t]=i),i}if(B(t)){var o=[];F(t,function(t){var r=z(t),e=U(r.module,n);r.resource&&!p(e)&&o.push(e)}),y(o,function(){var r=[];F(t,function(t){r.push(U(t,n))}),y(r,e,n)},n)}}var r={};return t.toUrl=function(t){return w(U(t,n))},t}function U(n,t){if(!n)return"";var r=z(n);if(!r)return n;var e=r.resource,i=$(r.module,t);if(F(Z,function(n){var t=n.name,r=t+"/"+n.main;return t==i?(i=i.replace(t,r),!1):void 0}),i=j(i,t),e){var o=l(i);return e=o.normalize?o.normalize(e,function(n){return U(n,t)}):U(e,t),i+"!"+e}return i}function $(n,t){if(/^\.{1,2}/.test(n)){var r=t.split("/"),e=n.split("/"),i=r.length-1,o=e.length,a=0,u=0;n:for(var f=0;o>f;f++){var c=e[f];switch(c){case"..":if(!(i>a))break n;a++,u++;break;case".":u++;break;default:break n}}return r.length=i-a,e=e.slice(u),r.push.apply(r,e),r.join("/")}return n}function z(n){var t=n.split("!");return it.test(t[0])?{module:t[0],resource:t[1]||""}:null}function j(n,t){return F(tt,function(r){return r.reg.test(t)?(F(r.v,function(t){var r=t.k,e=T(r);return e.test(n)?(n=n.replace(r,t.v),!1):void 0}),!1):void 0}),n}function M(n){var t=[];for(var r in n)n.hasOwnProperty(r)&&t.push({k:r,v:n[r]});return t}function _(){if(rt)return rt;if(et&&"interactive"==et.readyState)return et;for(var n=document.getElementsByTagName("script"),t=n.length;t--;){var r=n[t];if("interactive"==r.readyState)return et=r,r}}function A(n){rt=n;var t=document;(t.getElementsByTagName("head")[0]||t.body).appendChild(n),rt=null}function T(n){return RegExp("^"+n+"(/|$)")}function B(n){return n instanceof Array}function F(n,t){if(B(n))for(var r=0,e=n.length;e>r&&t(n[r],r)!==!1;r++);}function I(n){return n=n||"k",function(t,r){var e=t[n],i=r[n];return"*"==i?-1:"*"==e?1:i.length-e.length}}var L=O("");t.amd={};var N={},P=1,C=2,R=3,G=4,H=5,J=[],K=[],Q=0,V={require:L,exports:1,module:1},W=[],X={},Y={baseUrl:"./",paths:{},config:{},map:{},packages:[]};L.config=function(n){for(var t in Y)n.hasOwnProperty(t)&&b(t,n[t]);k()},k();var Z,nt,tt,rt,et,it=/^[-_a-z0-9\.]+(\/[-_a-z0-9\.]+)*$/i;n.define=t,n.require=L})(this); -------------------------------------------------------------------------------- /demo/chi/src/common/esl-css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ESL (Enterprise Standard Loader) 3 | * Copyright 2013 Baidu Inc. All rights reserved. 4 | * 5 | * @file CSS Loader-Plugin 6 | * @author errorrik(errorrik@gmail.com) 7 | */ 8 | 9 | // 构建环境暂不支持分析,为了能合并该plugin到loader里, 10 | // 只能暂时使用具名id 11 | define( 'css', { 12 | load: function ( resourceId, req, load, config ) { 13 | var link = document.createElement( 'link' ); 14 | link.setAttribute( 'rel', 'stylesheet' ); 15 | link.setAttribute( 'type', 'text/css' ); 16 | link.setAttribute( 'href', req.toUrl( resourceId ) ); 17 | 18 | var parent = document.getElementsByTagName( 'head' )[ 0 ] || document.body; 19 | parent.appendChild( link ); 20 | 21 | parent = null; 22 | link = null; 23 | 24 | load( true ); 25 | } 26 | } ); 27 | -------------------------------------------------------------------------------- /demo/chi/src/common/main.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | function init() { 4 | require('er/Deferred').syncModeEnabled = true; 5 | require('../affair/config'); 6 | require('../member/config'); 7 | // rule 8 | require('esui/validator/MaxRule'); 9 | require('esui/validator/MinRule'); 10 | require('esui/validator/MaxLengthRule'); 11 | require('esui/validator/PatternRule'); 12 | require('esui/validator/RequiredRule'); 13 | require('esui/validator/MaxRule'); 14 | 15 | require('er').start(); 16 | } 17 | 18 | return { 19 | init: init 20 | }; 21 | } 22 | ); -------------------------------------------------------------------------------- /demo/chi/src/common/sugar-1.3.9.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Sugar Library vedge 3 | * 4 | * Freely distributable and licensed under the MIT-style license. 5 | * Copyright (c) 2013 Andrew Plummer 6 | * http://sugarjs.com/ 7 | * 8 | * ---------------------------- */ 9 | (function(){var k=true,l=null,n=false;function aa(a){return function(){return a}}var p=Object,q=Array,r=RegExp,s=Date,t=String,u=Number,v=Math,ba=p.prototype.toString,ca=typeof global!=="undefined"?global:this,da={},ea=p.defineProperty&&p.defineProperties,x="Array,Boolean,Date,Function,Number,String,RegExp".split(","),ga=fa(x[0]),ha=fa(x[1]),ia=fa(x[2]),y=fa(x[3]),A=fa(x[4]),B=fa(x[5]),C=fa(x[6]); 10 | function fa(a){var b,c;if(/String|Number|Boolean/.test(a))b=a.toLowerCase();c=a==="Array"&&q.isArray||function(d){if(b&&typeof d===b)return k;return ba.call(d)==="[object "+a+"]"};return da[a]=c}function ja(a){if(!a.SugarMethods){ka(a,"SugarMethods",{});D(a,n,n,{extend:function(b,c,d){D(a,d!==n,c,b)},sugarRestore:function(){return la(a,arguments,function(b,c,d){ka(b,c,d.method)})},sugarRevert:function(){return la(a,arguments,function(b,c,d){if(d.qa)ka(b,c,d.Ba);else delete b[c]})}})}} 11 | function D(a,b,c,d){var e=b?a.prototype:a;ja(a);E(d,function(f,h){var i=e[f],j=F(e,f);if(typeof c==="function")h=ma(e[f],h,c);if(c!==n||!e[f])ka(e,f,h);a.SugarMethods[f]={xa:b,method:h,Ba:i,qa:j}})}function G(a,b,c,d,e){var f={};d=B(d)?d.split(","):d;d.forEach(function(h,i){e(f,h,i)});D(a,b,c,f)}function la(a,b,c){var d=b.length===0,e=H(b),f=n;E(a.SugarMethods,function(h,i){if(d||e.indexOf(h)>-1){f=k;c(i.xa?a.prototype:a,h,i)}});return f} 12 | function ma(a,b,c){return function(){return(a&&(c===k||!c.apply(this,arguments))?a:b).apply(this,arguments)}}function ka(a,b,c){if(ea)p.defineProperty(a,b,{value:c,configurable:k,enumerable:n,writable:k});else a[b]=c}function H(a,b){var c=[],d,e;d=0;for(e=a.length;d=b;){e.push(a);c&&c.call(this,a);a+=d||1}return e}function N(a,b,c){c=v[c||"round"];var d=v.pow(10,v.abs(b||0));if(b<0)d=1/d;return c(a*d)/d}function O(a,b){return N(a,b,"floor")}function P(a,b,c,d){d=v.abs(a).toString(d||10);d=ta(b-d.replace(/\.\d+/,"").length,"0")+d;if(c||a<0)d=(a<0?"-":"+")+d;return d} 15 | function ua(a){if(a>=11&&a<=13)return"th";else switch(a%10){case 1:return"st";case 2:return"nd";case 3:return"rd";default:return"th"}}function va(){return"\t\n\u000b\u000c\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u2028\u2029\u3000\ufeff"}function ta(a,b){return q(v.max(0,I(a)?a:1)+1).join(b||"")}function wa(a,b){var c=a.toString().match(/[^/]*$/)[0];if(b)c=(c+b).split("").sort().join("").replace(/([gimy])\1+/g,"$1");return c} 16 | function R(a){B(a)||(a=t(a));return a.replace(/([\\/'*+?|()\[\]{}.^$])/g,"\\$1")}function xa(a,b){var c=typeof a,d,e,f,h,i,j,g;if(c==="string")return a;f=ba.call(a);d=L(a);e=f==="[object Array]";if(a!=l&&d||e){b||(b=[]);if(b.length>1)for(j=b.length;j--;)if(b[j]===a)return"CYC";b.push(a);d=t(a.constructor);h=e?a:p.keys(a).sort();j=0;for(g=h.length;j>0);if(c<0)c=e+c;if(!f&&c<0||f&&c>=e)c=h;for(;f&&c>=0||!f&&c>>0==e&&e!=4294967295&&e>=c&&d.push(parseInt(e));d.sort().each(function(f){return b.call(a,a[f],f,a)});return a}function Ha(a,b,c,d,e){var f,h;T(a,function(i,j,g){if(Ea(i,b,g,[i,j,g])){f=i;h=j;return n}},c,d);return e?h:f} 27 | function Ia(a,b){var c=[],d={},e;T(a,function(f,h){e=b?S(f,b,a,[f,h,a]):f;Ja(d,e)||c.push(f)});return c}function Ka(a,b,c){var d=[],e={};b.each(function(f){Ja(e,f)});a.each(function(f){var h=xa(f),i=!ya(f);if(La(e,h,f,i)!=c){var j=0;if(i)for(h=e[h];je||i&&g0)c=b}catch(d){}a=a.concat(c)});return a}}); 31 | D(q,k,n,{find:function(a,b,c){return Ha(this,a,b,c)},findAll:function(a,b,c){var d=[];T(this,function(e,f,h){Ea(e,a,h,[e,f,h])&&d.push(e)},b,c);return d},findIndex:function(a,b,c){a=Ha(this,a,b,c,k);return K(a)?-1:a},count:function(a){if(K(a))return this.length;return this.findAll(a).length},removeAt:function(a,b){var c,d;if(K(a))return this;if(K(b))b=a;c=0;for(d=b-a;c<=d;c++)this.splice(a,1);return this},include:function(a,b){return this.clone().add(a,b)},exclude:function(){return q.prototype.remove.apply(this.clone(), 32 | arguments)},clone:function(){return qa([],this)},unique:function(a){return Ia(this,a)},flatten:function(a){return Ma(this,a)},union:function(){return Ia(this.concat(Na(arguments)))},intersect:function(){return Ka(this,Na(arguments),n)},subtract:function(){return Ka(this,Na(arguments),k)},at:function(){return za(this,arguments)},first:function(a){if(K(a))return this[0];if(a<0)a=0;return this.slice(0,a)},last:function(a){if(K(a))return this[this.length-1];return this.slice(this.length-a<0?0:this.length- 33 | a)},from:function(a){return this.slice(a)},to:function(a){if(K(a))a=this.length;return this.slice(0,a)},min:function(a,b){return Oa(this,a,"min",b)},max:function(a,b){return Oa(this,a,"max",b)},least:function(a,b){return Oa(this.groupBy.apply(this,[a]),"length","min",b)},most:function(a,b){return Oa(this.groupBy.apply(this,[a]),"length","max",b)},sum:function(a){a=a?this.map(a):this;return a.length>0?a.reduce(function(b,c){return b+c}):0},average:function(a){a=a?this.map(a):this;return a.length>0? 34 | a.sum()/a.length:0},inGroups:function(a,b){var c=arguments.length>1,d=this,e=[],f=N(this.length/a,void 0,"ceil");sa(0,a-1,function(h){h=h*f;var i=d.slice(h,h+f);c&&i.lengthh?1:0;return f*(b?-1:1)});return c},randomize:function(){for(var a=this.concat(),b=a.length,c,d;b;){c=v.random()*b|0; 36 | d=a[--b];a[b]=a[c];a[c]=d}return a},zip:function(){var a=H(arguments);return this.map(function(b,c){return[b].concat(a.map(function(d){return c in d?d[c]:l}))})},sample:function(a){var b=this.randomize();return arguments.length>0?b.slice(0,a):b[0]},each:function(a,b,c){T(this,a,b,c);return this},add:function(a,b){if(!A(u(b))||isNaN(b))b=this.length;q.prototype.splice.apply(this,[b,0].concat(a));return this},remove:function(){var a,b=this;H(arguments,function(c){for(a=0;a0&&!y(a[0])},"map,every,all,some,any,none,filter",function(a,b){a[b]=function(c){return this[b](function(d,e){return b==="map"?S(d,c,this,[d,e,this]):Ea(d,c,this,[d,e,this])})}})})(); 41 | (function(){q[Va]="A\u00c1\u00c0\u00c2\u00c3\u0104BC\u0106\u010c\u00c7D\u010e\u00d0E\u00c9\u00c8\u011a\u00ca\u00cb\u0118FG\u011eH\u0131I\u00cd\u00cc\u0130\u00ce\u00cfJKL\u0141MN\u0143\u0147\u00d1O\u00d3\u00d2\u00d4PQR\u0158S\u015a\u0160\u015eT\u0164U\u00da\u00d9\u016e\u00db\u00dcVWXY\u00ddZ\u0179\u017b\u017d\u00de\u00c6\u0152\u00d8\u00d5\u00c5\u00c4\u00d6".split("").map(function(b){return b+b.toLowerCase()}).join("");var a={};T("A\u00c1\u00c0\u00c2\u00c3\u00c4,C\u00c7,E\u00c9\u00c8\u00ca\u00cb,I\u00cd\u00cc\u0130\u00ce\u00cf,O\u00d3\u00d2\u00d4\u00d5\u00d6,S\u00df,U\u00da\u00d9\u00db\u00dc".split(","), 42 | function(b){var c=b.charAt(0);T(b.slice(1).split(""),function(d){a[d]=c;a[d.toLowerCase()]=c.toLowerCase()})});q[Qa]=k;q[Ta]=a})();Xa(Ya);Xa(Za,k);Aa($a,ra); 43 | var U,bb,cb=["ampm","hour","minute","second","ampm","utc","offset_sign","offset_hours","offset_minutes","ampm"],db="({t})?\\s*(\\d{1,2}(?:[,.]\\d+)?)(?:{h}([0-5]\\d(?:[,.]\\d+)?)?{m}(?::?([0-5]\\d(?:[,.]\\d+)?){s})?\\s*(?:({t})|(Z)|(?:([+-])(\\d{2,2})(?::?(\\d{2,2}))?)?)?|\\s*({t}))",eb={},fb,gb,hb,ib=[],jb=[{ba:"f{1,4}|ms|milliseconds",format:function(a){return V(a,"Milliseconds")}},{ba:"ss?|seconds",format:function(a){return V(a,"Seconds")}},{ba:"mm?|minutes",format:function(a){return V(a,"Minutes")}}, 44 | {ba:"hh?|hours|12hr",format:function(a){a=V(a,"Hours");return a===0?12:a-O(a/13)*12}},{ba:"HH?|24hr",format:function(a){return V(a,"Hours")}},{ba:"dd?|date|day",format:function(a){return V(a,"Date")}},{ba:"dow|weekday",la:k,format:function(a,b,c){a=V(a,"Day");return b.weekdays[a+(c-1)*7]}},{ba:"MM?",format:function(a){return V(a,"Month")+1}},{ba:"mon|month",la:k,format:function(a,b,c){a=V(a,"Month");return b.months[a+(c-1)*12]}},{ba:"y{2,4}|year",format:function(a){return V(a,"FullYear")}},{ba:"[Tt]{1,2}", 45 | format:function(a,b,c,d){if(b.ampm.length==0)return"";a=V(a,"Hours");b=b.ampm[O(a/12)];if(d.length===1)b=b.slice(0,1);if(d.slice(0,1)==="T")b=b.toUpperCase();return b}},{ba:"z{1,4}|tz|timezone",text:k,format:function(a,b,c,d){a=a.getUTCOffset();if(d=="z"||d=="zz")a=a.replace(/(\d{2})(\d{2})/,function(e,f){return P(f,d.length)});return a}},{ba:"iso(tz|timezone)",format:function(a){return a.getUTCOffset(k)}},{ba:"ord",format:function(a){a=V(a,"Date");return a+ua(a)}}],kb=[{$:"year",method:"FullYear", 46 | ja:k,da:function(a){return(365+(a?a.isLeapYear()?1:0:0.25))*24*60*60*1E3}},{$:"month",method:"Month",ja:k,da:function(a,b){var c=30.4375,d;if(a){d=a.daysInMonth();if(b<=d.days())c=d}return c*24*60*60*1E3},error:0.919},{$:"week",method:"ISOWeek",da:aa(6048E5)},{$:"day",method:"Date",ja:k,da:aa(864E5)},{$:"hour",method:"Hours",da:aa(36E5)},{$:"minute",method:"Minutes",da:aa(6E4)},{$:"second",method:"Seconds",da:aa(1E3)},{$:"millisecond",method:"Milliseconds",da:aa(1)}],lb={}; 47 | function mb(a){qa(this,a);this.ga=ib.concat()} 48 | mb.prototype={getMonth:function(a){return A(a)?a-1:this.months.indexOf(a)%12},getWeekday:function(a){return this.weekdays.indexOf(a)%7},oa:function(a){var b;return A(a)?a:a&&(b=this.numbers.indexOf(a))!==-1?(b+1)%10:1},ua:function(a){var b=this;return a.replace(r(this.num,"g"),function(c){return b.oa(c)||""})},sa:function(a){return U.units[this.units.indexOf(a)%8]},va:function(a){return this.na(a,a[2]>0?"future":"past")},ra:function(a){return this.na(nb(a),"duration")},wa:function(a){a=a||this.code; 49 | return a==="en"||a==="en-US"?k:this.variant},za:function(a){return a===this.ampm[0]},Aa:function(a){return a&&a===this.ampm[1]},na:function(a,b){var c,d,e=a[0],f=a[1],h=a[2],i=this[b]||this.relative;if(y(i))return i.call(this,e,f,h,b);d=this.units[(this.plural&&e>1?1:0)*8+f]||this.units[f];if(this.capitalizeUnit)d=ob(d);c=this.modifiers.filter(function(j){return j.name=="sign"&&j.value==(h>0?1:-1)})[0];return i.replace(/\{(.*?)\}/g,function(j,g){switch(g){case "num":return e;case "unit":return d; 50 | case "sign":return c.src}})},ta:function(){return this.ma?[this.ma].concat(this.ga):this.ga},addFormat:function(a,b,c,d,e){var f=c||[],h=this,i;a=a.replace(/\s+/g,"[-,. ]*");a=a.replace(/\{([^,]+?)\}/g,function(j,g){var m,o,w,z=g.match(/\?$/);w=g.match(/^(\d+)\??$/);var J=g.match(/(\d)(?:-(\d))?/),M=g.replace(/[^a-z]+$/,"");if(w)m=h.tokens[w[1]];else if(h[M])m=h[M];else if(h[M+"s"]){m=h[M+"s"];if(J){o=[];m.forEach(function(Q,Fa){var W=Fa%(h.units?8:m.length);if(W>=J[1]&&W<=(J[2]||J[1]))o.push(Q)}); 51 | m=o}m=pb(m)}if(w)w="(?:"+m+")";else{c||f.push(M);w="("+m+")"}if(z)w+="?";return w});if(b){b=qb(db,h,e);e=["t","[\\s\\u3000]"].concat(h.timeMarker);i=a.match(/\\d\{\d,\d\}\)+\??$/);rb(h,"(?:"+b+")[,\\s\\u3000]+?"+a,cb.concat(f),d);rb(h,a+"(?:[,\\s]*(?:"+e.join("|")+(i?"+":"*")+")"+b+")?",f.concat(cb),d)}else rb(h,a,f,d)}};function sb(a,b){var c;B(a)||(a="");c=lb[a]||lb[a.slice(0,2)];if(b===n&&!c)throw Error("Invalid locale.");return c||bb} 52 | function tb(a,b){function c(g){var m=i[g];if(B(m))i[g]=m.split(",");else m||(i[g]=[])}function d(g,m){g=g.split("+").map(function(o){return o.replace(/(.+):(.+)$/,function(w,z,J){return J.split("|").map(function(M){return z+M}).join("|")})}).join("|");return g.split("|").forEach(m)}function e(g,m,o){var w=[];i[g].forEach(function(z,J){if(m)z+="+"+z.slice(0,3);d(z,function(M,Q){w[Q*o+J]=M.toLowerCase()})});i[g]=w}function f(g,m,o){g="\\d{"+g+","+m+"}";if(o)g+="|(?:"+pb(i.numbers)+")+";return g}function h(g, 53 | m){i[g]=i[g]||m}var i,j;i=new mb(b);c("modifiers");"months,weekdays,units,numbers,articles,tokens,timeMarker,ampm,timeSuffixes,dateParse,timeParse".split(",").forEach(c);j=!i.monthSuffix;e("months",j,12);e("weekdays",j,7);e("units",n,8);e("numbers",n,10);h("code",a);h("date",f(1,2,i.digitDate));h("year","'\\d{2}|"+f(4,4));h("num",function(){var g=["\\d+"].concat(i.articles);if(i.numbers)g=g.concat(i.numbers);return pb(g)}());(function(){var g=[];i.ha={};i.modifiers.push({name:"day",src:"yesterday", 54 | value:-1});i.modifiers.push({name:"day",src:"today",value:0});i.modifiers.push({name:"day",src:"tomorrow",value:1});i.modifiers.forEach(function(m){var o=m.name;d(m.src,function(w){var z=i[o];i.ha[w]=m;g.push({name:o,src:w,value:m.value});i[o]=z?z+"|"+w:w})});i.day+="|"+pb(i.weekdays);i.modifiers=g})();if(i.monthSuffix){i.month=f(1,2);i.months=sa(1,12).map(function(g){return g+i.monthSuffix})}i.full_month=f(1,2)+"|"+pb(i.months);i.timeSuffixes.length>0&&i.addFormat(qb(db,i),n,cb);i.addFormat("{day}", 55 | k);i.addFormat("{month}"+(i.monthSuffix||""));i.addFormat("{year}"+(i.yearSuffix||""));i.timeParse.forEach(function(g){i.addFormat(g,k)});i.dateParse.forEach(function(g){i.addFormat(g)});return lb[a]=i}function rb(a,b,c,d){a.ga.unshift({Da:d,ya:a,Ca:r("^"+b+"$","i"),to:c})}function ob(a){return a.slice(0,1).toUpperCase()+a.slice(1)}function pb(a){return a.filter(function(b){return!!b}).join("|")} 56 | function ub(a,b){var c;if(L(a[0]))return a;else if(A(a[0])&&!A(a[1]))return[a[0]];else if(B(a[0])&&b)return[vb(a[0]),a[1]];c={};gb.forEach(function(d,e){c[d.$]=a[e]});return[c]}function vb(a,b){var c={};if(match=a.match(/^(\d+)?\s?(\w+?)s?$/i)){if(K(b))b=parseInt(match[1])||1;c[match[2].toLowerCase()]=b}return c} 57 | function wb(a,b){var c={},d,e;b.forEach(function(f,h){d=a[h+1];if(!(K(d)||d==="")){if(f==="year")c.Ea=d.replace(/'/,"");e=parseFloat(d.replace(/'/,"").replace(/,/,"."));c[f]=!isNaN(e)?e:d.toLowerCase()}});return c}function xb(a){a=a.trim().replace(/^just (?=now)|\.+$/i,"");return yb(a)} 58 | function yb(a){return a.replace(fb,function(b,c,d){var e=0,f=1,h,i;if(c)return b;d.split("").reverse().forEach(function(j){j=eb[j];var g=j>9;if(g){if(h)e+=f;f*=j/(i||1);i=j}else{if(h===n)f*=10;e+=f*j}h=g});if(h)e+=f;return e})} 59 | function zb(a,b,c,d){var e=new s,f=n,h,i,j,g,m,o,w,z,J;e.utc(d);if(ia(a))e.utc(a.isUTC()).setTime(a.getTime());else if(A(a))e.setTime(a);else if(L(a)){e.set(a,k);g=a}else if(B(a)){h=sb(b);a=xb(a);h&&E(h.ta(),function(M,Q){var Fa=a.match(Q.Ca);if(Fa){j=Q;i=j.ya;g=wb(Fa,j.to,i);g.utc&&e.utc();i.ma=j;if(g.timestamp){g=g.timestamp;return n}if(j.Da&&!B(g.month)&&(B(g.date)||h.wa(b))){z=g.month;g.month=g.date;g.date=z}if(g.year&&g.Ea.length===2)g.year=N(V(new s,"FullYear")/100)*100-N(g.year/100)*100+g.year; 60 | if(g.month){g.month=i.getMonth(g.month);if(g.shift&&!g.unit)g.unit=i.units[7]}if(g.weekday&&g.date)delete g.weekday;else if(g.weekday){g.weekday=i.getWeekday(g.weekday);if(g.shift&&!g.unit)g.unit=i.units[5]}if(g.day&&(z=i.ha[g.day])){g.day=z.value;e.reset();f=k}else if(g.day&&(o=i.getWeekday(g.day))>-1){delete g.day;if(g.num&&g.month){J=function(){var W=e.getWeekday();e.setWeekday(7*(g.num-1)+(W>o?o+7:o))};g.day=1}else g.weekday=o}if(g.date&&!A(g.date))g.date=i.ua(g.date);if(i.Aa(g.ampm)&&g.hour< 61 | 12)g.hour+=12;else if(i.za(g.ampm)&&g.hour===12)g.hour=0;if("offset_hours"in g||"offset_minutes"in g){e.utc();g.offset_minutes=g.offset_minutes||0;g.offset_minutes+=g.offset_hours*60;if(g.offset_sign==="-")g.offset_minutes*=-1;g.minute-=g.offset_minutes}if(g.unit){f=k;w=i.oa(g.num);m=i.sa(g.unit);if(g.shift||g.edge){w*=(z=i.ha[g.shift])?z.value:0;if(m==="month"&&I(g.date)){e.set({day:g.date},k);delete g.date}if(m==="year"&&I(g.month)){e.set({month:g.month,day:g.date},k);delete g.month;delete g.date}}if(g.sign&& 62 | (z=i.ha[g.sign]))w*=z.value;if(I(g.weekday)){e.set({weekday:g.weekday},k);delete g.weekday}g[m]=(g[m]||0)+w}if(g.year_sign==="-")g.year*=-1;hb.slice(1,4).forEach(function(W,fc){var Gb=g[W.$],Hb=Gb%1;if(Hb){g[hb[fc].$]=N(Hb*(W.$==="second"?1E3:60));g[W.$]=O(Gb)}});return n}});if(j)if(f)e.advance(g);else{e._utc&&e.reset();Ab(e,g,k,n,c)}else{if(a!=="now")e=new s(a);d&&e.addMinutes(-e.getTimezoneOffset())}if(g&&g.edge){z=i.ha[g.edge];E(hb.slice(4),function(M,Q){if(I(g[Q.$])){m=Q.$;return n}});if(m=== 63 | "year")g.fa="month";else if(m==="month"||m==="week")g.fa="day";e[(z.value<0?"endOf":"beginningOf")+ob(m)]();z.value===-2&&e.reset()}J&&J();e.utc(n)}return{ea:e,set:g}}function nb(a){var b,c=v.abs(a),d=c,e=0;hb.slice(1).forEach(function(f,h){b=O(N(c/f.da()*10)/10);if(b>=1){d=b;e=h+1}});return[d,e,a]}function Bb(a){var b=nb(a.millisecondsFromNow());if(b[1]===6)b[0]=v.abs(a.monthsFromNow());return b} 64 | function Cb(a,b,c,d){var e,f=sb(d),h=r(/^[A-Z]/);if(a.isValid())if(Date[b])b=Date[b];else{if(y(b)){e=Bb(a);b=b.apply(a,e.concat(f))}}else return"Invalid Date";if(!b&&c){e=e||Bb(a);if(e[1]===0){e[1]=1;e[0]=1}return f.va(e)}b=b||"long";b=f[b]||b;jb.forEach(function(i){b=b.replace(r("\\{("+i.ba+")(\\d)?\\}",i.la?"i":""),function(j,g,m){j=i.format(a,f,m||1,g);m=g.length;var o=g.match(/^(.)\1+$/);if(i.la){if(m===3)j=j.slice(0,3);if(o||g.match(h))j=ob(j)}else if(o&&!i.text)j=(A(j)?P(j,m):j.toString()).slice(-m); 65 | return j})});return b}function Db(a,b,c,d){var e,f,h,i=0,j=0,g=0;e=zb(b,l,l,d);if(c>0){j=g=c;f=k}if(!e.ea.isValid())return n;if(e.set&&e.set.fa){kb.forEach(function(m){if(m.$===e.set.fa)i=m.da(e.ea,a-e.ea)-1});b=ob(e.set.fa);if(e.set.edge||e.set.shift)e.ea["beginningOf"+b]();if(e.set.fa==="month")h=e.ea.clone()["endOf"+b]().getTime();if(!f&&e.set.sign&&e.set.fa!="millisecond"){j=50;g=-50}}f=a.getTime();b=e.ea.getTime();h=h||b+i;h=Eb(a,b,h);return f>=b-j&&f<=h+g} 66 | function Eb(a,b,c){b=new Date(b);a=(new Date(c)).utc(a.isUTC());if(V(a,"Hours")!==23){b=b.getTimezoneOffset();a=a.getTimezoneOffset();if(b!==a)c+=(a-b).minutes()}return c} 67 | function Ab(a,b,c,d,e){function f(g){return I(b[g])?b[g]:b[g+"s"]}function h(g){return I(f(g))}var i,j;if(A(b)&&d)b={milliseconds:b};else if(A(b)){a.setTime(b);return a}if(I(b.date))b.day=b.date;E(hb,function(g,m){var o=m.$==="day";if(h(m.$)||o&&h("weekday")){b.fa=m.$;j=+g;return n}else if(c&&m.$!=="week"&&(!o||!h("week")))Fb(a,m.method,o?1:0)});kb.forEach(function(g){var m=g.$;g=g.method;var o;o=f(m);if(!K(o)){if(d){if(m==="week"){o=(b.day||0)+o*7;g="Date"}o=o*d+V(a,g)}else m==="month"&&h("day")&& 68 | Fb(a,"Date",15);Fb(a,g,o);if(d&&m==="month"){m=o;if(m<0)m=m%12+12;m%12!=V(a,"Month")&&Fb(a,"Date",0)}}});if(!d&&!h("day")&&h("weekday")){i=f("weekday");a.setWeekday(i)}(function(){var g=new s;return e===-1&&a>g||e===1&&a1&&this.addWeeks(a-1);b!==1&&this.advance({days:b-1});return this.getTime()}},getISOWeek:function(){var a=this;a=a.clone();var b=V(a,"Day")||7;a.addDays(4-b).reset();return 1+O(a.daysSince(a.clone().beginningOfYear())/7)},getUTCOffset:function(a){var b= 72 | this._utc?0:this.getTimezoneOffset(),c=a===k?":":"";if(!b&&a)return"Z";return P(O(-b/60),2,k)+c+P(v.abs(b%60),2)},utc:function(a){ka(this,"_utc",a===k||arguments.length===0);return this},isUTC:function(){return!!this._utc||this.getTimezoneOffset()===0},advance:function(){var a=ub(arguments,k);return Ab(this,a[0],a[1],1)},rewind:function(){var a=ub(arguments,k);return Ab(this,a[0],a[1],-1)},isValid:function(){return!isNaN(this.getTime())},isAfter:function(a,b){return this.getTime()>s.create(a).getTime()- 73 | (b||0)},isBefore:function(a,b){return this.getTime()d},isLeapYear:function(){var a=V(this,"FullYear");return a%4===0&&a%100!==0||a%400===0},daysInMonth:function(){return 32-V(new s(V(this,"FullYear"),V(this,"Month"),32),"Date")},format:function(a,b){return Cb(this,a,n,b)},relative:function(a,b){if(B(a)){b=a;a=l}return Cb(this, 74 | a,k,b)},is:function(a,b,c){var d,e;if(this.isValid()){if(B(a)){a=a.trim().toLowerCase();e=this.clone().utc(c);switch(k){case a==="future":return this.getTime()>(new s).getTime();case a==="past":return this.getTime()<(new s).getTime();case a==="weekday":return V(e,"Day")>0&&V(e,"Day")<6;case a==="weekend":return V(e,"Day")===0||V(e,"Day")===6;case (d=U.weekdays.indexOf(a)%7)>-1:return V(e,"Day")===d;case (d=U.months.indexOf(a)%12)>-1:return V(e,"Month")===d}}return Db(this,a,b,c)}},reset:function(a){var b= 75 | {},c;a=a||"hours";if(a==="date")a="days";c=kb.some(function(d){return a===d.$||a===d.$+"s"});b[a]=a.match(/^days?/)?1:0;return c?this.set(b,k):this},clone:function(){var a=new s(this.getTime());a.utc(!!this._utc);return a}});s.extend({iso:function(){return this.toISOString()},getWeekday:s.prototype.getDay,getUTCWeekday:s.prototype.getUTCDay}); 76 | function Ib(a,b){function c(){return N(this*b)}function d(){return X(arguments)[a.ia](this)}function e(){return X(arguments)[a.ia](-this)}var f=a.$,h={};h[f]=c;h[f+"s"]=c;h[f+"Before"]=e;h[f+"sBefore"]=e;h[f+"Ago"]=e;h[f+"sAgo"]=e;h[f+"After"]=d;h[f+"sAfter"]=d;h[f+"FromNow"]=d;h[f+"sFromNow"]=d;u.extend(h)}u.extend({duration:function(a){return sb(a).ra(this)}}); 77 | U=bb=s.addLocale("en",{plural:k,timeMarker:"at",ampm:"am,pm",months:"January,February,March,April,May,June,July,August,September,October,November,December",weekdays:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",units:"millisecond:|s,second:|s,minute:|s,hour:|s,day:|s,week:|s,month:|s,year:|s",numbers:"one,two,three,four,five,six,seven,eight,nine,ten",articles:"a,an,the",tokens:"the,st|nd|rd|th,of","short":"{Month} {d}, {yyyy}","long":"{Month} {d}, {yyyy} {h}:{mm}{tt}",full:"{Weekday} {Month} {d}, {yyyy} {h}:{mm}:{ss}{tt}", 78 | past:"{num} {unit} {sign}",future:"{num} {unit} {sign}",duration:"{num} {unit}",modifiers:[{name:"sign",src:"ago|before",value:-1},{name:"sign",src:"from now|after|from|in|later",value:1},{name:"edge",src:"last day",value:-2},{name:"edge",src:"end",value:-1},{name:"edge",src:"first day|beginning",value:1},{name:"shift",src:"last",value:-1},{name:"shift",src:"the|this",value:0},{name:"shift",src:"next",value:1}],dateParse:["{num} {unit} {sign}","{sign} {num} {unit}","{month} {year}","{shift} {unit=5-7}", 79 | "{0?} {date}{1}","{0?} {edge} of {shift?} {unit=4-7?}{month?}{year?}"],timeParse:["{0} {num}{1} {day} of {month} {year?}","{weekday?} {month} {date}{1?} {year?}","{date} {month} {year}","{date} {month}","{shift} {weekday}","{shift} week {weekday}","{weekday} {2?} {shift} week","{num} {unit=4-5} {sign} {day}","{0?} {date}{1} of {month}","{0?}{month?} {date?}{1?} of {shift} {unit=6-7}"]});hb=kb.concat().reverse();gb=kb.concat();gb.splice(2,1); 80 | G(s,k,n,kb,function(a,b,c){function d(g){g=g/h;var m=g%1,o=b.error||0.999;if(m&&v.abs(m%1)>o)g=N(g);return parseInt(g)}var e=b.$,f=ob(e),h=b.da(),i,j;b.ia="add"+f+"s";i=function(g,m){return d(this.getTime()-s.create(g,m).getTime())};j=function(g,m){return d(s.create(g,m).getTime()-this.getTime())};a[e+"sAgo"]=j;a[e+"sUntil"]=j;a[e+"sSince"]=i;a[e+"sFromNow"]=i;a[b.ia]=function(g,m){var o={};o[e]=g;return this.advance(o,m)};Ib(b,h);c<3&&["Last","This","Next"].forEach(function(g){a["is"+g+f]=function(){return this.is(g+ 81 | " "+e)}});if(c<4){a["beginningOf"+f]=function(){var g={};switch(e){case "year":g.year=V(this,"FullYear");break;case "month":g.month=V(this,"Month");break;case "day":g.day=V(this,"Date");break;case "week":g.weekday=0}return this.set(g,k)};a["endOf"+f]=function(){var g={hours:23,minutes:59,seconds:59,milliseconds:999};switch(e){case "year":g.month=11;g.day=31;break;case "month":g.day=this.daysInMonth();break;case "week":g.weekday=6}return this.set(g,k)}}}); 82 | U.addFormat("([+-])?(\\d{4,4})[-.]?{full_month}[-.]?(\\d{1,2})?",k,["year_sign","year","month","date"],n,k);U.addFormat("(\\d{1,2})[-.\\/]{full_month}(?:[-.\\/](\\d{2,4}))?",k,["date","month","year"],k);U.addFormat("{full_month}[-.](\\d{4,4})",n,["month","year"]);U.addFormat("\\/Date\\((\\d+(?:\\+\\d{4,4})?)\\)\\/",n,["timestamp"]);U.addFormat(qb(db,U),n,cb);ib=U.ga.slice(0,7).reverse();U.ga=U.ga.slice(7).concat(ib);G(s,k,n,"short,long,full",function(a,b){a[b]=function(c){return Cb(this,b,n,c)}}); 83 | "\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07".split("").forEach(function(a,b){if(b>9)b=v.pow(10,b-9);eb[a]=b});"\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19".split("").forEach(function(a,b){eb[a]=b});fb=r("([\u671f\u9031\u5468])?([\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19]+)(?!\u6628)","g"); 84 | (function(){var a="today,yesterday,tomorrow,weekday,weekend,future,past".split(","),b=U.weekdays.slice(0,7),c=U.months.slice(0,12);G(s,k,n,a.concat(b).concat(c),function(d,e){d["is"+ob(e)]=function(f){return this.is(e,0,f)}})})();(function(){s.extend({utc:{create:function(){return X(arguments,0,k)},past:function(){return X(arguments,-1,k)},future:function(){return X(arguments,1,k)}}},n,n)})(); 85 | s.extend({RFC1123:"{Dow}, {dd} {Mon} {yyyy} {HH}:{mm}:{ss} {tz}",RFC1036:"{Weekday}, {dd}-{Mon}-{yy} {HH}:{mm}:{ss} {tz}",ISO8601_DATE:"{yyyy}-{MM}-{dd}",ISO8601_DATETIME:"{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{fff}{isotz}"},n,n); 86 | DateRange=function(a,b){this.start=s.create(a);this.end=s.create(b)};DateRange.prototype.toString=function(){return this.isValid()?this.start.full()+".."+this.end.full():"Invalid DateRange"}; 87 | D(DateRange,k,n,{isValid:function(){return this.start=b.start&&c<=b.end})},every:function(a,b){var c=this.start.clone(),d=[],e=0,f,h;if(B(a)){c.advance(vb(a,0),k);f=vb(a);h=a.toLowerCase()==="day"}else f={milliseconds:a};for(;c<=this.end;){d.push(c);b&&b(c,e);if(h&&V(c,"Hours")===23){c=c.clone();Fb(c, 88 | "Hours",48)}else c=c.clone().advance(f,k);e++}return d},union:function(a){return new DateRange(this.starta.end?this.end:a.end)},intersect:function(a){return new DateRange(this.start>a.start?this.start:a.start,this.endm;)g=Function.prototype.apply.apply(d,e.shift());Jb(c,i,function(){f=n;h()});f=k}};return c},delay:function(a){var b=H(arguments).slice(1);Jb(this,a,this,this,b);return this},throttle:function(a){return this.lazy(a,1)},debounce:function(a){function b(){b.cancel(); 92 | Jb(b,a,c,this,arguments)}var c=this;return b},cancel:function(){if(ga(this.timers))for(;this.timers.length>0;)clearTimeout(this.timers.shift());return this},after:function(a){var b=this,c=0,d=[];if(A(a)){if(a===0){b.call();return b}}else a=1;return function(){var e;d.push(H(arguments));c++;if(c==a){e=b.call(this,d);c=0;d=[];return e}}},once:function(){return this.throttle(Infinity)},fill:function(){var a=this,b=H(arguments);return function(){var c=H(arguments);b.forEach(function(d,e){if(d!=l||e>= 93 | c.length)c.splice(e,0,d)});return a.apply(this,c)}}}); 94 | function Kb(a,b,c,d,e,f){var h=a.toFixed(20),i=h.search(/\./);h=h.search(/[1-9]/);i=i-h;if(i>0)i-=1;e=v.max(v.min((i/3).floor(),e===n?c.length:e),-d);d=c.charAt(e+d-1);if(i<-9){e=-3;b=i.abs()-9;d=c.slice(0,1)}return(a/(f?(2).pow(10*e):(10).pow(e*3))).round(b||0).format()+d.trim()} 95 | D(u,n,n,{random:function(a,b){var c,d;if(arguments.length==1){b=a;a=0}c=v.min(a||0,K(b)?1:b);d=v.max(a||0,K(b)?1:b)+1;return O(v.random()*(d-c)+c)}}); 96 | D(u,k,n,{log:function(a){return v.log(this)/(a?v.log(a):1)},abbr:function(a){return Kb(this,a,"kmbt",0,4)},metric:function(a,b){return Kb(this,a,"n\u03bcm kMGTPE",4,K(b)?1:b)},bytes:function(a,b){return Kb(this,a,"kMGTPE",0,K(b)?4:b,k)+"B"},isInteger:function(){return this%1==0},isOdd:function(){return!isNaN(this)&&!this.isMultipleOf(2)},isEven:function(){return this.isMultipleOf(2)},isMultipleOf:function(a){return this%a===0},format:function(a,b,c){var d,e,f,h="";if(K(b))b=",";if(K(c))c=".";d=(A(a)? 97 | N(this,a||0).toFixed(v.max(a,0)):this.toString()).replace(/^-/,"").split(".");e=d[0];f=d[1];for(d=e.length;d>0;d-=3){if(d2},{startsWith:function(a,b,c){var d=this;if(b)d=d.slice(b);if(K(c))c=k;a=C(a)?a.source.replace("^",""):R(a);return r("^"+a,c?"":"i").test(d)},endsWith:function(a,b,c){var d=this;if(I(b))d=d.slice(0,b);if(K(c))c=k;a=C(a)?a.source.replace("$",""):R(a);return r(a+"$",c?"":"i").test(d)}}); 110 | D(t,k,n,{escapeRegExp:function(){return R(this)},escapeURL:function(a){return a?encodeURIComponent(this):encodeURI(this)},unescapeURL:function(a){return a?decodeURI(this):decodeURIComponent(this)},escapeHTML:function(){return this.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")},unescapeHTML:function(){return this.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(///g, 111 | "/").replace(/&/g,"&")},encodeBase64:function(){return Rb(this)},decodeBase64:function(){return Sb(this)},each:function(a,b){var c,d,e;if(y(a)){b=a;a=/[\s\S]/g}else if(a)if(B(a))a=r(R(a),"gi");else{if(C(a))a=r(a.source,wa(a,"g"))}else a=/[\s\S]/g;c=this.match(a)||[];if(b){d=0;for(e=c.length;d0?"_":"")+a.toLowerCase()}).replace(/([A-Z\d]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").toLowerCase()},camelize:function(a){return this.underscore().replace(/(^|_)([^_]+)/g,function(b,c,d,e){b=d;b=(c=t.Inflector)&&c.acronyms[b];b=B(b)?b:void 0;e=a!==n||e>0;if(b)return e?b:b.toLowerCase();return e?d.capitalize():d})},spacify:function(){return this.underscore().replace(/_/g," ")},stripTags:function(){var a=this;na(arguments.length> 115 | 0?arguments:[""],function(b){a=a.replace(r("]*>","gi"),"")});return a},removeTags:function(){var a=this;na(arguments.length>0?arguments:["\\S+"],function(b){b=r("<("+b+")[^<>]*(?:\\/>|>.*?<\\/\\1>)","gi");a=a.replace(b,"")});return a},truncate:function(a,b,c,d){var e="",f="",h=this.toString(),i="["+va()+"]+",j="[^"+va()+"]*",g=r(i+j+"$");d=K(d)?"...":t(d);if(h.length<=a)return h;switch(c){case "left":a=h.length-a;e=d;h=h.slice(a);g=r("^"+j+i);break;case "middle":a=O(a/2);f=d+h.slice(h.length- 116 | a).trimLeft();h=h.slice(0,a);break;default:a=a;f=d;h=h.slice(0,a)}if(b===n&&this.slice(a,a+1).match(/\S/))h=h.remove(g);return e+h+f},pad:function(a,b){return ta(b,a)+this+ta(b,a)},padLeft:function(a,b){return ta(b,a)+this},padRight:function(a,b){return this+ta(b,a)},first:function(a){if(K(a))a=1;return this.substr(0,a)},last:function(a){if(K(a))a=1;return this.substr(this.length-a<0?0:this.length-a)},repeat:function(a){var b="",c=this;if(!A(a)||a<1)return"";for(;a;){if(a&1)b+=c;if(a>>=1)c+=c}return b}, 117 | toNumber:function(a){var b=this.replace(/,/g,"");return b.match(/\./)?parseFloat(b):parseInt(b,a||10)},capitalize:function(a){var b;return this.toLowerCase().replace(a?/[\s\S]/g:/^\S/,function(c){var d=c.toUpperCase(),e;e=b?c:d;b=d!==c;return e})},assign:function(){var a={};H(arguments,function(b,c){if(L(b))qa(a,b);else a[c+1]=b});return this.replace(/\{([^{]+?)\}/g,function(b,c){return F(a,c)?a[c]:b})}});D(t,k,n,{insert:t.prototype.add}); 118 | (function(a){if(this.btoa){Rb=this.btoa;Sb=this.atob}else{var b=/[^A-Za-z0-9\+\/\=]/g;Rb=function(c){var d="",e,f,h,i,j,g,m=0;do{e=c.charCodeAt(m++);f=c.charCodeAt(m++);h=c.charCodeAt(m++);i=e>>2;e=(e&3)<<4|f>>4;j=(f&15)<<2|h>>6;g=h&63;if(isNaN(f))j=g=64;else if(isNaN(h))g=64;d=d+a.charAt(i)+a.charAt(e)+a.charAt(j)+a.charAt(g)}while(m>4;f=(f&15)<<4|i>>2;h=(i&3)<<6|j;d+=t.fromCharCode(e);if(i!=64)d+=t.fromCharCode(f);if(j!=64)d+=t.fromCharCode(h)}while(g编辑' 51 | + ' | ' 52 | + '删除' 54 | } 55 | } 56 | ]; 57 | 58 | var UIView = require('ef/UIView'); 59 | 60 | function MemberListView() { 61 | UIView.apply(this, arguments); 62 | } 63 | 64 | MemberListView.prototype.template = 'memberListPage'; 65 | 66 | MemberListView.prototype.uiProperties = { 67 | memberList: { 68 | fields: tableFields, 69 | sortable: false, 70 | columnResizable: true, 71 | subrow: false, 72 | followHead: true, 73 | selectMode: 'line' 74 | } 75 | }; 76 | 77 | MemberListView.prototype.uiEvents = { 78 | 'memberList:command': function (e) { 79 | if (e.name === 'modify') { 80 | this.fire('modifyClicked', {id: e.args}); 81 | } 82 | if (e.name === 'remove') { 83 | this.fire('removeClicked', {args: e.args}); 84 | } 85 | }, 86 | 87 | 'createButton:click': function (e) { 88 | this.fire('createNewMember'); 89 | } 90 | }; 91 | 92 | require('er/util').inherits(MemberListView, UIView); 93 | return MemberListView; 94 | } 95 | ); -------------------------------------------------------------------------------- /demo/chi/src/member/config.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var actions = [ 4 | { 5 | path: '/', 6 | type: 'member/List' 7 | }, 8 | { 9 | path: '/member/list', 10 | type: 'member/List' 11 | }, 12 | { 13 | path: '/member/create', 14 | type: 'member/Form', 15 | args: { formType: 'create' } 16 | }, 17 | { 18 | path: '/member/update', 19 | type: 'member/Form', 20 | args: { formType: 'update' } 21 | } 22 | ]; 23 | 24 | var controller = require('er/controller'); 25 | actions.forEach(controller.registerAction); 26 | 27 | var config = {}; 28 | 29 | config.Gender = { 30 | MALE: 0, 31 | FEMALE: 1, 32 | UNKNOWN: 2, 33 | 34 | 0: '女', 35 | 1: '男', 36 | 2: '保密' 37 | }; 38 | 39 | return config; 40 | } 41 | ); -------------------------------------------------------------------------------- /demo/chi/src/member/css/form.css: -------------------------------------------------------------------------------- 1 | /* 清除浮动 */ 2 | .form-row, 3 | .form-line, 4 | .form-single-line, 5 | .form-value, 6 | .form-value-mini, 7 | .edit-submit { 8 | zoom:1; 9 | } 10 | 11 | .form-row:after, 12 | .form-line:after, 13 | .form-single-line:after, 14 | .form-value:after, 15 | .form-value-mini:after, 16 | .edit-submit:after { 17 | content:"\0020"; 18 | display:block; 19 | height:0; 20 | visibility:hidden; 21 | clear:both; 22 | } 23 | 24 | 25 | /* 表单部分 */ 26 | .edit-form { 27 | background:#FFF; 28 | margin-bottom:10px; 29 | padding: 5px; 30 | } 31 | 32 | /* 表单头部 */ 33 | .edit-form .form-head { 34 | border-width: 1px 1px 0 1px; 35 | border-style: solid; 36 | border-color: #9ecae8; 37 | background: #c7deed; 38 | height:28px; 39 | } 40 | 41 | .edit-form .form-headcntr { 42 | height:27px; 43 | line-height:27px; 44 | color:#1c4a79; 45 | border-top:1px solid #d8e8f2; 46 | } 47 | 48 | .edit-form .form-headcntr h2 { 49 | padding-left:20px; 50 | font-weight:700; 51 | font-size:12px; 52 | } 53 | 54 | 55 | /* 表单行 */ 56 | .form-row { 57 | padding:2px 0; 58 | } 59 | 60 | .form-line, 61 | .form-single-line, 62 | .form-key, 63 | .form-key-mini, 64 | .form-value-mini, 65 | .form-value { 66 | float:left; 67 | display:inline; 68 | line-height:34px; 69 | } 70 | 71 | .form-line, 72 | .form-value { 73 | width: auto; 74 | } 75 | 76 | .form-value-mini { 77 | width:300px; 78 | padding:2px; 79 | } 80 | .ui-dialog-body .form-value { 81 | width:auto; 82 | } 83 | 84 | .form-value-preview { 85 | line-height:0; 86 | } 87 | 88 | /* 表单key */ 89 | .form-key, 90 | .form-key-mini { 91 | float:left; 92 | padding:2px 0 3px 10px; 93 | width:85px; 94 | color:#333333; 95 | } 96 | 97 | .form-key-mini { 98 | padding-left:0; 99 | } 100 | 101 | 102 | /* 表单value */ 103 | .form-value { 104 | padding:2px; 105 | } 106 | .form-value-caution { 107 | float:left; 108 | color:red; 109 | } 110 | 111 | 112 | .form-tip{ 113 | color:#999999; 114 | } 115 | /*有必填标记的表单value*/ 116 | .form-value-required{ 117 | position:relative; 118 | left:-8px; 119 | } 120 | /*必填标记*/ 121 | .required-icon{ 122 | float:left; 123 | margin-right:3px; 124 | color:red; 125 | } 126 | /* 单独占一行 */ 127 | .form-single-line { 128 | padding:2px 0 2px 36px; 129 | width:500px; 130 | clear:both; 131 | } 132 | 133 | .form-line { 134 | clear:both; 135 | } 136 | 137 | /* 表单内控件样式微调 */ 138 | .form-row .ui-checkbox, 139 | .form-row .ui-radiobox { 140 | display: inline; 141 | margin-top:5px; 142 | } 143 | 144 | .form-row .ui-text { 145 | margin-right:8px; 146 | } 147 | 148 | .form-row .ui-checkbox-label, 149 | .form-row .ui-radiobox-label { 150 | display: inline; 151 | line-height: 24px; 152 | margin: 0 8px 0 0px; 153 | } 154 | 155 | /* 表单文字 */ 156 | .form-text { 157 | float:left; 158 | display:inline; 159 | margin-right:8px; 160 | line-height:34px; 161 | } 162 | 163 | /* 表单小文字 */ 164 | .form-text-s { 165 | font-size:10px; 166 | color:#999; 167 | } 168 | .form-text-arrow{ 169 | font-family:simsun; 170 | font-size:12px; 171 | } 172 | /* 表单内容块*/ 173 | .form-content-block { 174 | padding-left:98px; 175 | } 176 | 177 | .form-row .ui-combobox { 178 | float:left; 179 | margin-right:5px; 180 | } 181 | 182 | .form-row .ui-select, 183 | .form-row .ui-combobox, 184 | .form-row .ui-button, 185 | .form-row .ui-text, 186 | .form-row .ui-textarea, 187 | .form-row .ui-cal, 188 | .form-row .ui-buttonmenu, 189 | .form-row .ui-combosearchlistsel { 190 | margin-top: 5px; 191 | } 192 | 193 | .form-row .ui-text { 194 | *margin-top:1px; 195 | vertical-align: middle; 196 | *height: 21px; 197 | *line-height: 21px; 198 | } 199 | 200 | .form-row .ui-textarea{ 201 | margin-right:5px; 202 | } 203 | /* 提交部分 */ 204 | .edit-submit { 205 | /*margin:0 0 0 21px;*/ 206 | zoom:1; 207 | } 208 | 209 | 210 | /* 表单验证提示 */ 211 | .validate-error { 212 | padding:5px; 213 | display:block; 214 | } 215 | .validate { 216 | /* 217 | background:#FEF7DB; 218 | border:1px solid #F0DDA5; 219 | */ 220 | position:relative; 221 | zoom:1; 222 | height: 25px; 223 | } 224 | 225 | .validate-text { 226 | background: none repeat scroll 0 0 #FEDBDC; 227 | border: 1px solid #F0CCCC; 228 | color: #DD6666; 229 | display: block; 230 | height: 25px; 231 | line-height: 25px; 232 | margin-top: 8px; 233 | padding-left: 45px; 234 | } 235 | 236 | .validate-text-nobr { 237 | color: red; 238 | line-height: 34px; 239 | padding-left: 5px; 240 | } 241 | 242 | .validate-icon { 243 | background:#FEDBDC url(../../assets/img/allbgs.png) -23px -208px no-repeat; 244 | position: absolute; 245 | left: 15px; 246 | top: 7px; 247 | width: 14px; 248 | height: 14px; 249 | } 250 | 251 | /* 表单分支链接(支线任务) */ 252 | .form-branch-task { 253 | font-family:STHeiti, SimSun; 254 | position:absolute; 255 | right:20px; 256 | top:20px; 257 | } 258 | 259 | /* 选填 ,灰色*/ 260 | .form-optional,.form-grey { 261 | float:left; 262 | display:inline; 263 | margin-left:5px; 264 | line-height:24px; 265 | color:#CCC; 266 | } 267 | 268 | .ui-richsel-entry .form-optional { 269 | line-height:22px; 270 | } 271 | 272 | .form-grey{float:none;} 273 | .form-grey2{color:#CCC;} 274 | /* 表单提示信息(先放在这里,组件做好再转移) */ 275 | .ui-forminfo { 276 | position:relative; 277 | margin:1px; 278 | background:#EFF7F9; 279 | border:1px solid #C5DFF0; 280 | clear:both; 281 | zoom:1; 282 | } 283 | 284 | .ui-forminfo .ui-link { 285 | text-decoration:underline; 286 | } 287 | 288 | .ui-forminfo-icon { 289 | position:absolute; 290 | width:14px; 291 | height:17px; 292 | background:url(../../assets/img/BlockSprites.png) no-repeat -141px -315px; 293 | left:40px; 294 | top:6px; 295 | } 296 | 297 | .ui-forminfo-text { 298 | padding:4px 10px 4px 64px; 299 | line-height:24px; 300 | color:#8BA9C4; 301 | } 302 | 303 | .ui-forminfo-text strong { 304 | margin:0 3px; 305 | } 306 | 307 | /** 308 | * 表单内的iframe 309 | */ 310 | .form-iframe { 311 | width:850px; 312 | height:450px; 313 | overflow-x:hidden; 314 | border:none; 315 | } 316 | 317 | .form-iframe-wrapper { 318 | border-top:1px solid #DCE3E9; 319 | margin:10px; 320 | padding:5px 0; 321 | width:850px; 322 | } 323 | 324 | /** 325 | * 缩进HACK 326 | */ 327 | .indent-checkbox { 328 | padding-left: 17px; 329 | *padding-left: 23px; 330 | } 331 | /** 332 | * 表单元素组缩进 333 | */ 334 | .form-row-group 335 | { 336 | margin-left: 35px; 337 | } 338 | -------------------------------------------------------------------------------- /demo/chi/src/member/form.tpl: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 |
8 |
姓名:
9 |
10 | 11 |
12 |
13 |
14 |
性别:
15 |
16 |
17 |
    18 |
  • 19 | 20 |
  • 21 |
  • 22 | 23 |
  • 24 |
  • 25 | 26 |
  • 27 |
28 |
29 |
30 |
31 |
32 |
生日:
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 | 新建 46 | 47 | 保存 48 | 49 |
50 |
取消
51 |
52 |
53 |
54 |
-------------------------------------------------------------------------------- /demo/chi/src/member/list.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
新建成员
7 | 8 |
9 |
-------------------------------------------------------------------------------- /doc/ActionPanel.md: -------------------------------------------------------------------------------- 1 | # ActionPanel控件 2 | 3 | `ActionPanel`控件渲染一个`Panel`,但在该面板里加载一个子Action。 4 | 5 | ## 继承关系 6 | 7 | - er/Control 8 | - er/Panel 9 | - ActionPanel 10 | 11 | ## 属性 12 | 13 | ### {string} url 14 | 15 | 指定需要加载的子Action对应的地址。 16 | 17 | ### {Object} actionOptions 18 | 19 | 指定加载子Action时传递的额外参数。 20 | 21 | ### {er/Action} action 22 | 23 | 当子Action加载完毕后,可以使用该属性访问到`Action`对象实例。 24 | 25 | 该属性为 **只读** 。 26 | 27 | ## 事件 28 | 29 | ### actionloaded 30 | 31 | 子Action加载完毕时触发。 -------------------------------------------------------------------------------- /doc/UIModel.md: -------------------------------------------------------------------------------- 1 | # UIModel基类 2 | 3 | `UIModel`是对`er/Model`的继承,用于将ER与ESUI结合起来。 4 | 5 | `UIModel`会重写`set`和`fill`方法,提供数据格式化的功能。 6 | 7 | `UIModel`主要用于表单类的模块,`er/Form`控件的`getData()`方法会返回控件的`rawValue`而不是`value`,`rawValue`是一个对象,并不一定是字符串。但前后端交互的HTTP接口一定使用字符串,因此在获得`rawValue`之后,需要一定的方法将之转换为字符串再通过`Model`的相关方法(如`save`或`update`之类)提交,因此`UIModel`提供`formatters`属性用于配置格式化逻辑。 8 | 9 | ## 继承关系 10 | 11 | - er/Observable 12 | - er/Model 13 | - UIModel 14 | 15 | ## 属性 16 | 17 | ### {Object} UIModel.formatters 18 | 19 | 这是`UIModel`上的静态属性,其内置了几个常用的格式化函数: 20 | 21 | - `date`:将`Date`类型格式化为`YYYY-MM-dd`格式的日期字符串。 22 | - `dateRange`:将`{ {Date} begin, {Date} end }`类型格式化为`YYYY-MM-dd,YYYY-MM-dd`格式的字符串。 23 | - `time`:将`Date`类型格式化为`YYYY-MM-dd HH:mm:ss`格式的日期字符串。 24 | - `timeRange`:将`{ {Date} begin, {Date} end }`类型格式化为`YYYY-MM-dd HH:mm:ss,YYYY-MM-dd HH:mm:ss`格式的字符串。 25 | 26 | ### {Object} formatters 27 | 28 | 通过该属性配置格式化函数,当调用`set`或`fill`将值写入当前`Model`时,会通过此配置查找对应的格式化函数,先经过函数的处理再将值写入。 29 | 30 | 每一个格式化函数应该接受一个值,返回字符串。 31 | 32 | `formatters`属性为一个对象,其键为对应属性的名称,值为格式化函数,如: 33 | 34 | CustomModel.prototype.formatters = { 35 | birthday: UIModel.formatters.date, 36 | gender: function (value) { 37 | return ['male', 'female'][value]; 38 | } 39 | }; 40 | 41 | 以上配置设定当写入`birthday`属性时,先将值通过内置的`date`函数进行格式化,而当`gender`属性被写入时,使用自定义的函数进行格式化。 42 | 43 | ## 方法 44 | 45 | ### {Object} getPart({string..} keys) 46 | 47 | 由于一个`Model`中会存在许多属性,并不是每一个属性都要在保存、更新时发送到服务器,因此不能直接使用`valueOf()`得到对象,而是需要取出一部分属性,拼装成一个新的对象,通常就是这样的代码: 48 | 49 | var postData = { 50 | name: model.get('name'), 51 | age: model.get('age'), 52 | birthday: model.get('birthday'), 53 | ... 54 | }; 55 | 56 | 如果属性较多,会比较累。而使用`getPart`方法,则可以: 57 | 58 | var postData = model.getPart('name', 'age', 'birthday', ...); 59 | 60 | 以节省代码量,另一种方法是,根据`form.getData()`返回的键,一次性获取所有的值: 61 | 62 | var formData = form.getData(); 63 | model.fill(formData); // 先写到model中,会经过formatter格式化 64 | var postData = model.getPart(Object.keys(formData)); // 注意Object.keys的兼容性 -------------------------------------------------------------------------------- /doc/UIView.md: -------------------------------------------------------------------------------- 1 | # UIView基类 2 | 3 | `UIView`是对`er/View`的继承,用于将ER与ESUI结合起来。 4 | 5 | `UIView`会重写`enterDocument`方法,使用ESUI对容器进行初始化,以保证控件的渲染。 6 | 7 | ## 继承关系 8 | 9 | - er/Observable 10 | - er/View 11 | - UIView 12 | 13 | ## 属性 14 | 15 | ### {Object} uiProperties 16 | 17 | 通过`uiProperties`属性,可以为控件加上额外的属性。该属性为一个对象,其中的键为对应控件的id,值为控件的额外属性对象,如下代码: 18 | 19 | CustomView.prototype.uiProperties = { 20 | username: { 21 | maxLength: 20, 22 | pattern: '^[a-zA-Z]+$' 23 | } 24 | }; 25 | 26 | 则指定id为 **username** 的控件在实例化时额外传递2个属性,分别为`maxLength`和`pattern`。 27 | 28 | ### {Object} uiEvents 29 | 30 | 通过`uiEvents`属性,可以为控件绑定指定的事件。该属性为一个对象,由2种方式声明: 31 | 32 | - 键为`id:eventName`形式的字符串,值为对应事件的处理函数,如: 33 | 34 | CustomView.prototype.uiEvents = { 35 | 'username:input': function (e) { 36 | if (this.getValue().length > 20) { 37 | warn('已经超出' + (this.getValue().length - 20) + '个字符'; 38 | } 39 | } 40 | } 41 | 42 | - 键为控件的id,值为一个对象。值对象中的键为事件名称,值为处理函数,如: 43 | 44 | CustomView.prototype.uiEvents = { 45 | username: { 46 | input: function (e) { 47 | if (this.getValue().length > 20) { 48 | warn('已经超出' + (this.getValue().length - 20) + '个字符'; 49 | } 50 | } 51 | } 52 | } 53 | 54 | 需要注意的是,在此处声明的事件,运行时的`this`对象均是`View`实例,而非控件的实例。同时,在运行期,`UIView`会克隆该属性,将其中所有的处理函数都进行一次`bind`,将`this`指向自身,因此运行时的`uiEvents`与类声明时的不会相同。 55 | 56 | 如果需要解除某个事件的绑定,可以使用`.on('type', this.uiEvents.xxx)`进行。 57 | 58 | ### {ViewContext} viewContext 59 | 60 | 每一个`UIView`会创建一个单独的`ViewContext`,该视图的所有控件均存放在这个`ViewContext`中。 61 | 62 | 一般情况下没有使用该属性的必要,特殊场景如清空当前视图下的控件,可以使用`this.viewContext.clean();`。 63 | 64 | 在`UIView`销毁时,会同时销毁该`ViewContext`,因此不需要关心控件在离开视图时的销毁工作。 65 | 66 | ## 方法 67 | 68 | ### {Control} get({string} id) 69 | 70 | 该方法即`this.viewContext.get(id)`,用于返回当前视图下指定id的控件实例。 71 | 72 | ### {ViewContext} createViewContext() 73 | 74 | 创建当前`UIView`实例使用的`ViewContext`对象,默认实现是通过`this.name`或者当前构造函数的名字来创建一个`ViewContext`实例,可重写来创建一个`id`稳定的`ViewContext`对象。 75 | 76 | ## 其它 77 | 78 | ### 值替换 79 | 80 | `UIView`为ESUI提供了值替换函数,在HTML中对应`data-ui-*`和`data-ui-extension-*`属性的值,如果以 **@** 为起始,则会替换为`Model`中的对应值,如: 81 | 82 |
83 | 84 | 则在该`Select`控件实例化时,其`datasource`属性的值等于`this.model.get('datasource')`的值,而不是简单的`@users`字符串。 85 | 86 | 以 **@** 为起始的字符串可以是一个深度的属性路径,如`@user.name.first`也是被允许的。 87 | 88 | ### enterDocument 89 | 90 | 如果有需要重写`enterDocument`方法, **必须** 调用`UIView.prototype.enterDocument`,否则ESUI无法正常工作。 -------------------------------------------------------------------------------- /edp-build-config.js: -------------------------------------------------------------------------------- 1 | exports.input = __dirname; 2 | 3 | var path = require( 'path' ); 4 | exports.output = path.resolve( __dirname, 'output' ); 5 | 6 | // var moduleEntries = 'html,htm,phtml,tpl,vm,js'; 7 | // var pageEntries = 'html,htm,phtml,tpl,vm'; 8 | 9 | exports.getProcessors = function () { 10 | var lessProcessor = new LessCompiler(); 11 | var cssProcessor = new CssCompressor(); 12 | var moduleProcessor = new ModuleCompiler(); 13 | var jsProcessor = new JsCompressor(); 14 | var pathMapperProcessor = new PathMapper(); 15 | var addCopyright = new AddCopyright(); 16 | 17 | return { 18 | 'default': [ lessProcessor, moduleProcessor, pathMapperProcessor ], 19 | 'release': [ 20 | lessProcessor, cssProcessor, moduleProcessor, 21 | jsProcessor, pathMapperProcessor, addCopyright 22 | ] 23 | }; 24 | }; 25 | 26 | exports.exclude = [ 27 | 'tool', 28 | 'doc', 29 | 'test', 30 | 'module.conf', 31 | 'dep/packages.manifest', 32 | 'dep/*/*/test', 33 | 'dep/*/*/doc', 34 | 'dep/*/*/demo', 35 | 'dep/*/*/tool', 36 | 'dep/*/*/*.md', 37 | 'dep/*/*/package.json', 38 | 'edp-*', 39 | '.edpproj', 40 | '.svn', 41 | '.git', 42 | '.gitignore', 43 | '.idea', 44 | '.project', 45 | 'Desktop.ini', 46 | 'Thumbs.db', 47 | '.DS_Store', 48 | '*.tmp', 49 | '*.bak', 50 | '*.swp' 51 | ]; 52 | 53 | exports.injectProcessor = function ( processors ) { 54 | for ( var key in processors ) { 55 | global[ key ] = processors[ key ]; 56 | } 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /module.conf: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "src", 3 | "paths": {}, 4 | "packages": [ 5 | { 6 | "name": "eoo", 7 | "location": "../dep/eoo/0.0.5/src", 8 | "main": "oo" 9 | }, 10 | { 11 | "name": "er", 12 | "location": "../dep/er/3.1.0-beta.3/src", 13 | "main": "main" 14 | }, 15 | { 16 | "name": "esui", 17 | "location": "../dep/esui/3.1.0-beta.3/src", 18 | "main": "main" 19 | }, 20 | { 21 | "name": "etpl", 22 | "location": "../dep/etpl/2.1.2/src", 23 | "main": "main" 24 | }, 25 | { 26 | "name": "mini-event", 27 | "location": "../dep/mini-event/1.0.0/src", 28 | "main": "main" 29 | }, 30 | { 31 | "name": "moment", 32 | "location": "../dep/moment/2.7.0/src", 33 | "main": "moment" 34 | }, 35 | { 36 | "name": "underscore", 37 | "location": "../dep/underscore/1.5.2/src", 38 | "main": "underscore" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ef", 3 | "version": "3.1.0-beta.2", 4 | "maintainers": [ 5 | { 6 | "name": "otakustay", 7 | "email": "otakustay@gmail.com" 8 | } 9 | ], 10 | "dependencies": { 11 | "er": "3.1.x", 12 | "esui": "3.1.x", 13 | "underscore": "1.5.x", 14 | "mini-event": "1.x", 15 | "etpl": "2.x", 16 | "moment": "2.x", 17 | "eoo": "0.x" 18 | }, 19 | "main": "main", 20 | "description": "EF是ER与ESUI整合而成的高效率业务系统开发框架。", 21 | "homepage": "https://github.com/ecomfe/ef", 22 | "edp": { 23 | "wwwroot": "/", 24 | "depDir": "dep", 25 | "srcDir": "src", 26 | "loaderAutoConfig": "js,htm,html,tpl,vm,phtml", 27 | "loaderUrl": "http://s1.bdstatic.com/r/www/cache/ecom/esl/1-8-0/esl.js", 28 | "dependencies": { 29 | "er": "3.1.x", 30 | "esui": "3.1.x", 31 | "underscore": "1.5.x", 32 | "mini-event": "1.x", 33 | "etpl": "2.x", 34 | "moment": "2.x", 35 | "eoo": "0.x" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "immed": true, 3 | "latedef": false, 4 | "newcap": true, 5 | "noarg": true, 6 | "noempty": true, 7 | "nonew": true, 8 | "plusplus": false, 9 | "quotmark": "single", 10 | "regexp": false, 11 | "undef": true, 12 | "unused": "vars", 13 | "strict": false, 14 | "trailing": true, 15 | "maxparams": 20, 16 | "maxdepth": 4, 17 | "maxlen": 120, 18 | 19 | "asi": false, 20 | "boss": false, 21 | "debug": false, 22 | "eqnull": true, 23 | "esnext": false, 24 | "evil": false, 25 | "expr": true, 26 | "funcscope": false, 27 | "globalstrict": false, 28 | "iterator": false, 29 | "lastsemic": false, 30 | "laxbreak": true, 31 | "laxcomma": false, 32 | "loopfunc": false, 33 | "multistr": false, 34 | "onecase": false, 35 | "proto": false, 36 | "regexdash": false, 37 | "scripturl": false, 38 | "smarttabs": false, 39 | "shadow": true, 40 | "sub": false, 41 | "supernew": false, 42 | "validthis": true, 43 | 44 | "browser": true, 45 | "couch": false, 46 | "devel": true, 47 | "dojo": false, 48 | "jquery": true, 49 | "mootools": false, 50 | "node": false, 51 | "nonstandard": false, 52 | "prototypejs": false, 53 | "rhino": false, 54 | "wsh": false, 55 | 56 | "nomen": true, 57 | "onevar": false, 58 | "passfail": false, 59 | "white": false, 60 | 61 | "predef": ["define", "URL"] 62 | } 63 | -------------------------------------------------------------------------------- /src/ActionDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ecom Framework 3 | * Copyright 2013 Baidu Inc. All rights reserved. 4 | * 5 | * @ignore 6 | * @file ActionDialog 7 | * @author otakustay 8 | */ 9 | define( 10 | function (require) { 11 | var ui = require('esui/main'); 12 | var Dialog = require('esui/Dialog'); 13 | 14 | require('./ActionPanel'); 15 | 16 | /** 17 | * @class ef.ActionDialog 18 | * 19 | * 用于加载子Action的面板控件 20 | * 21 | * @constructor 22 | * @extends esui/Panel 23 | */ 24 | var exports = {}; 25 | 26 | exports.type = 'ActionDialog'; 27 | exports.styleType = 'Dialog'; 28 | 29 | /** 30 | * 设置HTML内容,`ActionDialog`没有这功能 31 | */ 32 | exports.setContent = function () { 33 | }; 34 | 35 | /** 36 | * 构建对话框主内容和底部内容 37 | * 38 | * @param {"foot" | "body"} type 面板类型 39 | * @param {HTMLElement} mainDOM body或foot主元素 40 | * 41 | * @return {ef.ActionPanel | esui.Panel} panel 42 | * @protected 43 | */ 44 | exports.createBF = function (type, mainDOM) { 45 | if (mainDOM) { 46 | this[type === 'body' ? 'content' : 'foot'] = mainDOM.innerHTML; 47 | } 48 | else { 49 | mainDOM = document.createElement('div'); 50 | this.main.appendChild(mainDOM); 51 | } 52 | 53 | this.helper.addPartClasses(type + '-panel', mainDOM); 54 | var properties = { 55 | main: mainDOM 56 | }; 57 | 58 | var panelType = 'Panel'; 59 | if (type === 'body') { 60 | properties.url = this.url; 61 | properties.actionOptions = this.actionOptions; 62 | panelType = 'ActionPanel'; 63 | } 64 | 65 | var panel = ui.create(panelType, properties); 66 | if (type === 'body') { 67 | panel.on( 68 | 'actionattach', 69 | function () { 70 | this.resize(); 71 | 72 | if (this.autoClose) { 73 | // 当子Action处理完成后对话框也一起销毁 74 | var action = this.get('action'); 75 | if (typeof action.on === 'function') { 76 | // 要阻止默认行为,因为后续整个Action会销毁,有任何进一步的行为(如跳转)都没用 77 | action.on('handlefinish', false); 78 | action.on('handlefinish', this.dispose, this); 79 | } 80 | } 81 | 82 | this.fire('actionattach'); 83 | }, 84 | this 85 | ); 86 | 87 | // 把`ActionPanel`代理的子Action事件再代理出来 88 | panel.on( 89 | '*', 90 | function (e) { 91 | if (e.type.indexOf('action@') === 0) { 92 | // 不像`ActionPanel`,这里不需要修改`type`,所以直接触发 93 | this.fire(e); 94 | } 95 | }, 96 | this 97 | ); 98 | 99 | // 代理`ActionPanel`的相关事件 100 | var Event = require('mini-event'); 101 | Event.delegate(panel, this, 'actionloaded'); 102 | Event.delegate(panel, this, 'actionloadfail'); 103 | Event.delegate(panel, this, 'actionloadabort'); 104 | } 105 | 106 | panel.render(); 107 | this.addChild(panel, type); 108 | 109 | return panel; 110 | }; 111 | 112 | /** 113 | * 重构 114 | * 115 | * @protected 116 | * @override 117 | */ 118 | exports.repaint = require('esui/painters').createRepaint( 119 | Dialog.prototype.repaint, 120 | { 121 | name: ['url', 'actionOptions'], 122 | paint: function (dialog, url, actionOptions) { 123 | // 获取body panel 124 | var body = dialog.getBody(); 125 | var properties = { 126 | url: url, 127 | actionOptions: actionOptions 128 | }; 129 | body.setProperties(properties); 130 | } 131 | } 132 | ); 133 | 134 | /** 135 | * 获取action 136 | * 137 | * @return {er.Action | null} 138 | */ 139 | exports.getAction = function () { 140 | var actionPanel = this.getBody(); 141 | if (actionPanel) { 142 | return actionPanel.get('action'); 143 | } 144 | else { 145 | return null; 146 | } 147 | }; 148 | 149 | 150 | /** 151 | * 重新加载管理的子Action(代理Panel的) 152 | */ 153 | exports.reload = function () { 154 | var actionPanel = this.getBody(); 155 | if (actionPanel) { 156 | actionPanel.reload(); 157 | } 158 | }; 159 | 160 | var ActionDialog = require('eoo').create(Dialog, exports); 161 | require('esui').register(ActionDialog); 162 | return ActionDialog; 163 | } 164 | ); 165 | -------------------------------------------------------------------------------- /src/ActionPanel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ecom Framework 3 | * Copyright 2013 Baidu Inc. All rights reserved. 4 | * 5 | * @ignore 6 | * @file ActionPanel 7 | * @author otakustay 8 | */ 9 | define( 10 | function (require) { 11 | var events = require('er/events'); 12 | var Panel = require('esui/Panel'); 13 | 14 | /** 15 | * @class ef.ActionPanel 16 | * 17 | * 用于加载子Action的面板控件 18 | * 19 | * @extends esui.Panel 20 | * @constructor 21 | */ 22 | var exports = {}; 23 | 24 | exports.type = 'ActionPanel'; 25 | 26 | /** 27 | * 设置HTML内容,`ActionPanel`没有这功能 28 | */ 29 | exports.setContent = function () { 30 | }; 31 | 32 | /** 33 | * 加载的Action的类型 34 | * 35 | * @type {string} 36 | */ 37 | exports.actionType = null; 38 | 39 | /** 40 | * 加载的Action的实例 41 | * 42 | * @type {er.Action | er.meta.Promise} 43 | * @readonly 44 | */ 45 | exports.action = null; 46 | 47 | /** 48 | * 代理子Action的事件 49 | * 50 | * @param {mini-event.Event} e 事件对象 51 | */ 52 | function delegateActionEvent(e) { 53 | var event = require('mini-event').fromEvent(e, { preserveData: true, syncState: true }); 54 | event.type = 'action@' + e.type; 55 | this.fire(event); 56 | } 57 | 58 | /** 59 | * 把已经加载的子Action赋值到控件上 60 | * 61 | * @param {mini-event.Event} e 事件对象 62 | */ 63 | function attachAction(e) { 64 | if (!e.isChildAction || e.container !== this.main.id) { 65 | return; 66 | } 67 | 68 | this.action = e.action; 69 | 70 | // 进入 action 前的处理 71 | this.action.on('enter', this.enterAction, this); 72 | 73 | // 代理所有的子Action的事件 74 | if (typeof this.action.on === 'function') { 75 | this.action.on('*', delegateActionEvent, this); 76 | } 77 | 78 | this.fire('actionattach'); 79 | } 80 | 81 | /** 82 | * 通知子Action加载完毕 83 | * 84 | * @param {mini-event.Event} e 事件对象 85 | */ 86 | function notifyActionLoadComplete(e) { 87 | if (!e.isChildAction || e.container !== this.main.id) { 88 | return; 89 | } 90 | 91 | this.fire('actionloaded'); 92 | } 93 | 94 | /** 95 | * 通知子Action加载失败 96 | * 97 | * @param {mini-event.Event} e 事件对象 98 | * @param {string} e.reason 失败原因 99 | */ 100 | function notifyActionLoadFailed(e) { 101 | if (!e.isChildAction || e.container !== this.main.id) { 102 | return; 103 | } 104 | 105 | this.action = null; 106 | this.fire( 107 | 'actionloadfail', 108 | { failType: e.failType, reason: e.reason } 109 | ); 110 | } 111 | 112 | /** 113 | * 通知子Action加载中断 114 | * 115 | * @param {mini-event.Event} e 事件对象 116 | * @param {string} e.reason 失败原因 117 | * @inner 118 | */ 119 | function notifyActionLoadAborted(e) { 120 | if (!e.isChildAction || e.container !== this.main.id) { 121 | return; 122 | } 123 | 124 | this.fire('actionloadabort'); 125 | } 126 | 127 | /** 128 | * 初始化结构 129 | * 130 | * @protected 131 | * @override 132 | */ 133 | exports.initStructure = function () { 134 | events.on('enteraction', attachAction, this); 135 | events.on('enteractioncomplete', notifyActionLoadComplete, this); 136 | events.on('actionnotfound', notifyActionLoadFailed, this); 137 | events.on('permissiondenied', notifyActionLoadFailed, this); 138 | events.on('actionfail', notifyActionLoadFailed, this); 139 | events.on('enteractionfail', notifyActionLoadFailed, this); 140 | events.on('actionabort', notifyActionLoadAborted, this); 141 | }; 142 | 143 | /** 144 | * 跳转前 hook 145 | */ 146 | exports.enterAction = function () { 147 | this.url = this.action.context.url.toString(); 148 | }; 149 | 150 | /** 151 | * 销毁控件上关联的Action 152 | */ 153 | exports.disposeAction = function () { 154 | var Deferred = require('er/Deferred'); 155 | var action = this.action; 156 | 157 | if (!action) { 158 | return; 159 | } 160 | 161 | // Action正在加载,正确的`renderChildAction`得到的加载器有`abort`方法 162 | if (Deferred.isPromise(action) && typeof action.abort === 'function') { 163 | action.abort(); 164 | } 165 | // 已经加载完的Action,但并不一定会有`leave`或`un`方法 166 | else { 167 | if (typeof action.un === 'function') { 168 | action.un('*', delegateActionEvent, this); 169 | } 170 | if (typeof action.leave === 'function') { 171 | action.leave(); 172 | } 173 | } 174 | 175 | this.action = null; 176 | }; 177 | 178 | /** 179 | * 重构 180 | * 181 | * @override 182 | * @protected 183 | */ 184 | exports.repaint = require('esui/painters').createRepaint( 185 | Panel.prototype.repaint, 186 | { 187 | name: ['url', 'actionOptions'], 188 | paint: function (panel, url, actionOptions) { 189 | panel.disposeAction(); 190 | 191 | if (!url) { 192 | return; 193 | } 194 | 195 | if (panel.lazy && panel.helper.isInStage('INITED')) { 196 | return; 197 | } 198 | 199 | var controller = require('er/controller'); 200 | panel.action = controller.renderChildAction( 201 | url, 202 | panel.main.id, 203 | actionOptions 204 | ); 205 | 206 | // 如果发生错误,因为事件是同步触发的, 207 | // 因此先执行`notifyActionLoadFailed`再赋值,导致没清掉。 208 | // 错误时返回的`Promise`对象是没有`abort`方法的, 209 | // 这种对象我们也不需要,因此直接清掉 210 | if (typeof panel.action.abort !== 'function') { 211 | panel.action = null; 212 | } 213 | } 214 | } 215 | ); 216 | 217 | /** 218 | * 销毁控件 219 | * 220 | * @override 221 | */ 222 | exports.dispose = function () { 223 | this.disposeAction(); 224 | 225 | // 移除注册的一堆方法 226 | events.un('enteraction', attachAction, this); 227 | events.un('enteractioncomplete', notifyActionLoadComplete, this); 228 | events.un('actionnotfound', notifyActionLoadFailed, this); 229 | events.un('permissiondenied', notifyActionLoadFailed, this); 230 | events.un('actionfail', notifyActionLoadFailed, this); 231 | events.un('enteractionfail', notifyActionLoadFailed, this); 232 | events.un('actionabort', notifyActionLoadAborted, this); 233 | 234 | this.$super(arguments); 235 | }; 236 | 237 | /** 238 | * 重新加载管理的子Action 239 | * 240 | * @param {Object} [actionOptions] 子Action的额外数据 241 | */ 242 | exports.reload = function (actionOptions) { 243 | var url = this.url; 244 | this.url = null; 245 | this.setProperties({ url: url, actionOptions: actionOptions }); 246 | }; 247 | 248 | var ActionPanel = require('eoo').create(Panel, exports); 249 | require('esui').register(ActionPanel); 250 | return ActionPanel; 251 | } 252 | ); 253 | -------------------------------------------------------------------------------- /src/ChildView.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ecom Framework 3 | * Copyright 2013 Baidu Inc. All rights reserved. 4 | * 5 | * @ignore 6 | * @file ChildView 7 | * @author otakustay 8 | */ 9 | define( 10 | function (require) { 11 | var u = require('underscore'); 12 | var Control = require('esui/Control'); 13 | 14 | /** 15 | * @class ef.ChildView 16 | * 17 | * 子视图控件,用于加载一个{@link er.View} 18 | * 19 | * @constructor 20 | * @extends esui.Control 21 | */ 22 | var exports = {}; 23 | 24 | /** 25 | * 控件的类型,始终为`"ChildView"` 26 | * 27 | * @type {string} 28 | * @readonly 29 | * @override 30 | */ 31 | exports.type = 'ChildView'; 32 | 33 | /** 34 | * 重绘 35 | * 36 | * @protected 37 | * @override 38 | */ 39 | exports.repaint = require('esui/painters').createRepaint( 40 | Control.prototype.repaint, 41 | { 42 | name: 'viewType', 43 | paint: function (childView, viewType) { 44 | childView.disposeView(); 45 | 46 | var Deferred = require('er/Deferred'); 47 | childView.view = Deferred.require([viewType]); 48 | childView.view.then(u.bind(childView.fire, childView, 'viewloaded')); 49 | childView.view.then(u.bind(childView.renderView, childView)); 50 | } 51 | } 52 | ); 53 | 54 | /** 55 | * 销毁对应的视图 56 | * 57 | * 如果在视图模块加载过程中,调用了此方法是没有效果的,加载完后会继续把视图渲染出来 58 | */ 59 | exports.disposeView = function () { 60 | var view = this.get('view'); 61 | 62 | if (view && typeof view.dispose === 'function') { 63 | view.dispose(); 64 | } 65 | 66 | this.view = null; 67 | }; 68 | 69 | /** 70 | * 代理View的事件 71 | * 72 | * @param {mini-event.Event} e 事件对象 73 | */ 74 | function delegateViewEvents(e) { 75 | var event = require('mini-event').fromEvent(e, { preserveData: true, syncState: true }); 76 | event.type = 'view@' + e.type; 77 | this.fire(event); 78 | } 79 | 80 | /** 81 | * 渲染加载完毕的视图对象 82 | * 83 | * @param {Mixed} View 加载完毕的视图构造函数或对象 84 | * @protected 85 | */ 86 | exports.renderView = function (View) { 87 | // 仅当渲染完成阶段才会对View进行操作,销毁的时候这里不处理 88 | if (this.helper.isInStage('RENDERED')) { 89 | this.loadedViewModule = View; // 存下来,后面还会用到的 90 | var view = this.view = typeof View === 'function' ? new View() : View; 91 | view.model = this.get('model'); 92 | view.container = this.main.id; 93 | view.render(); 94 | 95 | this.fire('viewrendered'); 96 | 97 | view.on('*', delegateViewEvents, this); 98 | } 99 | }; 100 | 101 | /** 102 | * 刷新包含的视图 103 | */ 104 | exports.refresh = function () { 105 | var viewModule = this.get('loadedViewModule'); 106 | if (!viewModule) { 107 | throw new Error('No view module loaded yet'); 108 | } 109 | this.disposeView(); 110 | this.renderView(viewModule); 111 | }; 112 | 113 | var ChildView = require('eoo').create(Control, exports); 114 | require('esui').register(ChildView); 115 | return ChildView; 116 | } 117 | ); 118 | -------------------------------------------------------------------------------- /src/RemoteTreeStrategy.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var lib = require('esui/lib'); 4 | var TreeStrategy = require('esui/TreeStrategy'); 5 | 6 | var exports = {}; 7 | 8 | exports.constructor = function (options) { 9 | if (options.requestMethod) { 10 | options.requestMethod = options.requestMethod.toLowerCase(); 11 | } 12 | TreeStrategy.apply(this, arguments); 13 | this.workingRequests = {}; 14 | }; 15 | 16 | exports.urlTemplate = ''; 17 | 18 | exports.requestMethod = 'get'; 19 | 20 | exports.getRequestURL = function (node) { 21 | return lib.format(this.urlTemplate, node); 22 | }; 23 | 24 | exports.getRequestData = function (node) { 25 | return null; 26 | }; 27 | 28 | exports.requestNodeData = function (node) { 29 | var url = this.getRequestURL(node); 30 | var data = this.getRequestData(node); 31 | var ajax = require('er/ajax'); 32 | 33 | return this.requestMethod === 'get' 34 | ? ajax.getJSON(url, data, this.useCache || false) 35 | : ajax.post(url, data, 'json'); 36 | }; 37 | 38 | function expandNode(tree, strategy, e) { 39 | var node = e.node; 40 | if (node.children) { 41 | tree.expandNode(node.id); 42 | return; 43 | } 44 | 45 | // 如果原来就在请求数据,把原来的断掉 46 | var xhr = tree.workingRequests[node.id]; 47 | if (xhr) { 48 | xhr.abort(); 49 | } 50 | xhr = this.requestNodeData(node); 51 | tree.workingRequests[node.id] = xhr; 52 | xhr.done(lib.bind(tree.expandNode, tree, node.id)); 53 | } 54 | 55 | exports.enableToggleStrategy = function (tree) { 56 | tree.on( 57 | 'expand', 58 | lib.curry(expandNode, tree, this) 59 | ); 60 | tree.on( 61 | 'collapse', 62 | function (e) { 63 | this.collapseNode(e.node.id, false); 64 | } 65 | ); 66 | }; 67 | 68 | var RemoteTreeStrategy = require('eoo').create(TreeStrategy, exports); 69 | lib.inherits(RemoteTreeStrategy, TreeStrategy); 70 | return RemoteTreeStrategy; 71 | } 72 | ); -------------------------------------------------------------------------------- /src/UIModel.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var Model = require('er/Model'); 4 | 5 | /** 6 | * @class ef.UIModel 7 | * 8 | * 处理ESUI场景的`Model`实现 9 | * 10 | * @constructor 11 | * @extends er.Model 12 | */ 13 | var exports = {}; 14 | 15 | /** 16 | * 补0 17 | * 18 | * @param {string|number} s 输入的数字或字符串 19 | * @return {string} 不足2位的补个0 20 | */ 21 | function pad(s) { 22 | s = s + ''; 23 | return s.length === 1 ? '0' + s : s; 24 | } 25 | 26 | /** 27 | * 内置的格式化函数 28 | * 29 | * @type {Object} 30 | */ 31 | var formatters = { 32 | /** 33 | * 格式化日期 34 | * 35 | * @param {Date} date 输入的日期 36 | * @return {string} YYYY-MM-dd格式的字符串 37 | */ 38 | date: function (date) { 39 | return date.getFullYear() + '-' 40 | + pad(date.getMonth() + 1) + '-' 41 | + pad(date.getDate()); 42 | }, 43 | /** 44 | * 格式化日期范围 45 | * 46 | * @param {Object} range 输入的日期范围 47 | * @return {string} 逗号分隔2个日期,均为YYYY-MM-dd格式 48 | */ 49 | dateRange: function (range) { 50 | return formatters.date(range.begin) 51 | + ',' + formatters.date(range.end); 52 | }, 53 | /** 54 | * 格式化时间 55 | * 56 | * @param {Date} time 输入的时间 57 | * @return {string} YYYY-MM-dd HH:mm:ss格式的字符串 58 | */ 59 | time: function (time) { 60 | return formatters.date(time) + ' ' 61 | + pad(time.getHours()) + ':' 62 | + pad(time.getMinutes()) + ':' 63 | + pad(time.getSeconds()); 64 | }, 65 | /** 66 | * 格式化时间范围 67 | * 68 | * @param {Object} range 输入的时间范围 69 | * @return {string} 逗号分隔2个时间,均为YYYY-MM-dd HH:mm:ss格式 70 | */ 71 | timeRange: function (range) { 72 | return formatters.time(range.begin) 73 | + ',' + formatters.time(range.end); 74 | } 75 | }; 76 | 77 | /** 78 | * 配置值的格式化函数,键为属性名称,值为格式化函数, 79 | * 设置该属性时,值将先经过格式化函数处理 80 | * 81 | * @type {Object} 82 | * @public 83 | */ 84 | exports.formatters = {}; 85 | 86 | /** 87 | * 设置值 88 | * 89 | * @param {string} name 属性名 90 | * @param {*} value 对应的值 91 | * @param {Object=} options 相关选项 92 | * @param {boolean=} options.silent 如果该值为true则不触发`change`事件 93 | * @public 94 | */ 95 | exports.set = function (name, value, options) { 96 | if (this.formatters.hasOwnProperty(name)) { 97 | value = this.formatters[name](value); 98 | } 99 | this.$super([name, value, options]); 100 | }; 101 | 102 | /** 103 | * 批量设置值 104 | * 105 | * @param {Object} extension 批量值的存放对象 106 | * @param {Object=} options 相关选项 107 | * @param {boolean=} options.silent 如果该值为true则不触发`change`事件 108 | * @public 109 | */ 110 | exports.fill = function (extension, options) { 111 | for (var name in extension) { 112 | if (extension.hasOwnProperty(name) 113 | && this.formatters.hasOwnProperty(name) 114 | ) { 115 | var formatter = this.formatters[name]; 116 | var value = extension[name]; 117 | extension[name] = formatter(value); 118 | } 119 | } 120 | 121 | this.$super(arguments); 122 | }; 123 | 124 | /** 125 | * 根据传入的属性名获取一个组装后的对象 126 | * 127 | * @param {Array. | string...} names 需要的属性名列表 128 | * @return {Object} 包含`names`参数指定的属性的对象 129 | */ 130 | exports.getPart = function (names) { 131 | if (Object.prototype.toString.call(names) !== '[object Array]') { 132 | names = [].slice.call(arguments); 133 | } 134 | 135 | var part = {}; 136 | for (var i = 0; i < names.length; i++) { 137 | var name = names[i]; 138 | part[name] = this.get(name); 139 | } 140 | return part; 141 | }; 142 | 143 | var UIModel = require('eoo').create(Model, UIModel); 144 | 145 | UIModel.formatters = formatters; 146 | 147 | return UIModel; 148 | } 149 | ); -------------------------------------------------------------------------------- /src/UIView.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | var u = require('underscore'); 4 | var View = require('er/View'); 5 | 6 | require('ef/ActionDialog'); 7 | 8 | /** 9 | * @class ef.UIView 10 | * 11 | * 与ESUI结合的`View`基类 12 | * 13 | * @constructor 14 | * @extends er.View 15 | */ 16 | var exports = {}; 17 | 18 | function getProperty(target, path) { 19 | var value = target; 20 | for (var i = 0; i < path.length; i++) { 21 | value = value[path[i]]; 22 | } 23 | 24 | return value; 25 | } 26 | 27 | /** 28 | * 替换元素属性中的特殊值 29 | * 30 | * @param {string} value 需要处理的值 31 | * @return {*} 处理完的值 32 | * @public 33 | */ 34 | exports.replaceValue = function (value) { 35 | if (typeof value !== 'string') { 36 | return value; 37 | } 38 | 39 | if (value === '@@' || value === '**') { 40 | return this.model; 41 | } 42 | 43 | var prefix = value.charAt(0); 44 | var actualValue = value.substring(1); 45 | 46 | if (prefix === '@' || prefix === '*') { 47 | var path = actualValue.split('.'); 48 | var value = this.model.get(path[0]); 49 | return path.length > 1 50 | ? getProperty(value, path.slice(1)) 51 | : value; 52 | } 53 | else { 54 | return value; 55 | } 56 | }; 57 | 58 | /** 59 | * 根据id获取当前视图下的控件 60 | * 61 | * @param {string} id 控件的id 62 | * @return {Control=} 对应的控件 63 | * @protected 64 | */ 65 | exports.get = function (id) { 66 | return this.viewContext.get(id); 67 | }; 68 | 69 | /** 70 | * 根据id获取控件实例,如无相关实例则返回`esui.SafeWrapper` 71 | * 72 | * @param {string} id 控件id 73 | * @return {Control} 根据id获取的控件 74 | */ 75 | exports.getSafely = function (id) { 76 | return this.viewContext.getSafely(id); 77 | }; 78 | 79 | /** 80 | * 根据name获取当前视图下的控件组 81 | * 82 | * @param {string} name 控件组的名称 83 | * @return {ControlGroup} 对应的控件组 84 | * @protected 85 | */ 86 | exports.getGroup = function (name) { 87 | return this.viewContext.getGroup(name); 88 | }; 89 | 90 | /** 91 | * 创建一个控件实例 92 | * 93 | * @param {string} type 控件的类型 94 | * @param {Obejct=} options 创建控件时的选项 95 | * @return {Control} 96 | * @proceted 97 | */ 98 | exports.create = function (type, options) { 99 | options = options || {}; 100 | if (!options.viewContext) { 101 | options.viewContext = this.viewContext; 102 | } 103 | return require('esui').create(type, options); 104 | }; 105 | 106 | /** 107 | * 显示一条提示信息 108 | * 109 | * @param {string | Object} content 提示的内容或完整的配置项 110 | * @param {string=} title 提示框的标题,如`content`提供配置项则无此参数 111 | * @return {esui/Dialog} 112 | * @protected 113 | */ 114 | exports.alert = function (content, title) { 115 | var options = typeof content === 'string' 116 | ? { title: title || document.title, content: content } 117 | : u.clone(content); 118 | if (!options.viewContext) { 119 | options.viewContext = this.viewContext; 120 | } 121 | 122 | var Dialog = require('esui/Dialog'); 123 | return Dialog.alert(options); 124 | }; 125 | 126 | /** 127 | * 显示一条确认信息 128 | * 129 | * @param {string | Object} content 提示的内容或完整的配置项 130 | * @param {string=} title 提示框的标题,如`content`提供配置项则无此参数 131 | * @return {esui/Dialog} 132 | * @protected 133 | */ 134 | exports.confirm = function (content, title) { 135 | var options = typeof content === 'string' 136 | ? { title: title || document.title, content: content } 137 | : u.clone(content); 138 | if (!options.viewContext) { 139 | options.viewContext = this.viewContext; 140 | } 141 | 142 | var Dialog = require('esui/Dialog'); 143 | return Dialog.confirm(options); 144 | }; 145 | 146 | /** 147 | * 显示ActionDialog 148 | * 149 | * @param {Object} options 参数 150 | * @return {esui/Dialog} 151 | * @protected 152 | */ 153 | exports.popActionDialog = function (options) { 154 | //创建main 155 | var main = document.createElement('div'); 156 | document.body.appendChild(main); 157 | 158 | var defaults = { 159 | width: 600, 160 | needFoot: false, 161 | draggable: true, 162 | closeOnHide: true, 163 | autoClose: true, 164 | main: main, 165 | viewContext: this.viewContext 166 | }; 167 | options = u.defaults({}, options, defaults); 168 | 169 | var ui = require('esui/main'); 170 | var dialog = ui.create('ActionDialog', options); 171 | 172 | dialog.render(); 173 | dialog.show(); 174 | return dialog; 175 | }; 176 | 177 | /* 178 | * 声明控件的事件。该属性有2种方式: 179 | * 180 | * - 以`id:eventName`为键,以处理函数为值。 181 | * - 以`id`为键,值为一个对象,对象中以`eventName`为键,处理函数为值。 182 | * 183 | * 在此处声明的事件,运行时的`this`对象均是`View`实例,而非控件的实例。 184 | * 185 | * 同时,在运行期,`UIView`会克隆该属性,将其中所有的处理函数都进行一次`bind`, 186 | * 将`this`指向自身,因此运行时的`uiEvents`与类声明时的不会相同。 187 | * 188 | * 如果需要解除某个事件的绑定,可以使用`.on('type', this.uiEvents.xxx)`进行。 189 | * 190 | * @type {Object} 191 | * @public 192 | */ 193 | exports.uiEvents = null; 194 | 195 | /* 196 | * 获取当前视图关联的控件事件声明。参考`uiEvents`属性 197 | * 198 | * @return {Object} 199 | * @public 200 | */ 201 | exports.getUIEvents = function () { 202 | return this.uiEvents || {}; 203 | }; 204 | 205 | /** 206 | * 声明控件的额外属性。 207 | * 208 | * 这个属性以控件的id为键,以一个对象为值。对象表示要额外附加到控件上的属性。 209 | * 当控件实例化时,会把DOM中声明的属性与此处声明的合并在一起,此处声明的为优先。 210 | * 211 | * @type {Object} 212 | * @public 213 | */ 214 | exports.uiProperties = null; 215 | 216 | /** 217 | * 声明当前视图关联的控件的额外属性,参考`uiProperties`属性 218 | * 219 | * @return {Object} 220 | */ 221 | exports.getUIProperties = function () { 222 | return this.uiProperties; 223 | }; 224 | 225 | /** 226 | * 给指定的控件绑定事件 227 | * 228 | * @param {UIView} view View对象实例 229 | * @param {string} id 控件的id 230 | * @param {string} eventName 事件名称 231 | * @param {function | string} handler 事件处理函数,或者对应的方法名 232 | * @return {function} 绑定到控件上的事件处理函数,不等于`handler`参数 233 | * @inner 234 | */ 235 | function bindEventToControl(view, id, eventName, handler) { 236 | if (typeof handler === 'string') { 237 | handler = view[handler]; 238 | } 239 | 240 | // TODO: mini-event后续会支持`false`作为处理函数,要考虑下 241 | if (typeof handler !== 'function') { 242 | return handler; 243 | } 244 | 245 | var control = view.get(id); 246 | if (control) { 247 | control.on(eventName, handler, view); 248 | } 249 | 250 | return handler; 251 | } 252 | 253 | /** 254 | * 绑定控件的事件。 255 | * 256 | * @override 257 | * @protected 258 | */ 259 | exports.bindEvents = function () { 260 | var events = this.getUIEvents(); 261 | if (!events) { 262 | return; 263 | } 264 | 265 | for (var key in events) { 266 | if (!events.hasOwnProperty(key)) { 267 | // 下面逻辑太长了,在这里先中断 268 | continue; 269 | } 270 | 271 | // 可以用`submit:click`的形式在指定控件上绑定指定类型的控件 272 | var segments = key.split(':'); 273 | if (segments.length > 1) { 274 | var id = segments[0]; 275 | var type = segments[1]; 276 | var handler = events[key]; 277 | bindEventToControl(this, id, type, handler); 278 | } 279 | // 也可以是一个控件的id,值是对象,里面每一项都是一个事件类型 280 | else { 281 | var map = events[key]; 282 | 283 | if (typeof map !== 'object') { 284 | return; 285 | } 286 | 287 | for (var type in map) { 288 | if (map.hasOwnProperty(type)) { 289 | var handler = map[type]; 290 | bindEventToControl(this, key, type, handler); 291 | } 292 | } 293 | } 294 | } 295 | }; 296 | 297 | var counter = 0x861005; 298 | function getGUID() { 299 | return 'ef-' + counter++; 300 | } 301 | 302 | /** 303 | * 获取当前视图的名称,通常用于生成`ViewContext` 304 | * 305 | * @return {string} 306 | * @protected 307 | */ 308 | exports.getViewName = function () { 309 | if (this.name) { 310 | return this.name; 311 | } 312 | 313 | // 从构造函数把名字猜出来 314 | var name = this.constructor && this.constructor.name; 315 | if (!name && this.constructor) { 316 | // 用正则猜名字 317 | var functionString = this.constructor.toString(); 318 | var match = /function\s+([^\(]*)/.exec(functionString); 319 | // 去除函数名后面的空格 320 | name = match && match[1].replace(/\s+$/g, ''); 321 | } 322 | // 再不行用计数 323 | if (!name) { 324 | name = getGUID(); 325 | } 326 | 327 | // 以下代码是一个洁癖和强近症患者所写: 328 | 329 | // 如果名字是XxxView,把最后的View字样去掉 330 | name = name.replace(/View$/, ''); 331 | // 从PascalCase转为横线分隔,这里需要注意,连续的大写字母不应该连续分隔 332 | name = name.replace( 333 | /[A-Z]{2,}/g, 334 | function (match) { 335 | // 这里把ABCD这种连续的大写,转成AbcD这种形式。 336 | // 如果`encodeURIComponent`,会变成`encodeUriComponent`, 337 | // 然后加横线后就是`encode-uri-component`得到正确的结果 338 | return match.charAt(0) 339 | + match.slice(1, -1).toLowerCase() 340 | + match.charAt(match.length - 1); 341 | } 342 | ); 343 | name = name.replace( 344 | /[A-Z]/g, 345 | function (match) { return '-' + match.toLowerCase(); } 346 | ); 347 | if (name.charAt(0) === '-') { 348 | name = name.substring(1); 349 | } 350 | 351 | return name; 352 | }; 353 | 354 | /** 355 | * 创建当前`UIView`使用的`ViewContext`对象 356 | * 357 | * @return {ViewContext} 358 | * @public 359 | */ 360 | exports.createViewContext = function () { 361 | var ViewContext = require('esui/ViewContext'); 362 | var name = this.getViewName(); 363 | 364 | return new ViewContext(name || null); 365 | }; 366 | 367 | /** 368 | * 当容器渲染完毕后触发,用于控制元素可见性及绑定事件等DOM操作 369 | * 370 | * @override 371 | * @protected 372 | */ 373 | exports.enterDocument = function () { 374 | this.viewContext = this.createViewContext(); 375 | 376 | var container = this.getContainerElement(); 377 | var options = { 378 | viewContext: this.viewContext, 379 | properties: this.getUIProperties(), 380 | valueReplacer: u.bind(this.replaceValue, this) 381 | }; 382 | try { 383 | require('esui').init(container, options); 384 | } 385 | catch (ex) { 386 | var error = new Error( 387 | 'ESUI initialization error on view ' 388 | + 'because: ' + ex.message 389 | ); 390 | error.actualError = ex; 391 | throw error; 392 | } 393 | 394 | 395 | this.bindEvents(); 396 | }; 397 | 398 | /** 399 | * 销毁当前视图 400 | * 401 | * @override 402 | * @protected 403 | */ 404 | exports.dispose = function () { 405 | if (this.viewContext) { 406 | this.viewContext.dispose(); 407 | this.viewContext = null; 408 | } 409 | this.$super(arguments); 410 | }; 411 | 412 | var UIView = require('eoo').create(View, exports); 413 | return UIView; 414 | } 415 | ); 416 | -------------------------------------------------------------------------------- /test/UIView.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | var UIView = require('ef/UIView'); 3 | var template = require('er/template'); 4 | 5 | require('er/tpl!./tpl/plain.tpl'); 6 | 7 | describe('UIView', function () { 8 | it('should be a constructor', function () { 9 | expect(UIView).toBeOfType('function'); 10 | }); 11 | 12 | it('should be instantiable', function () { 13 | expect(new UIView()).toBeOfType('object'); 14 | }); 15 | 16 | describe('when rendered', function () { 17 | var view = new UIView(); 18 | view.container = 'container'; 19 | view.template = 'plain'; 20 | view.render(); 21 | 22 | it ('should merge the template specified with `template` property', function () { 23 | expect(container.innerHTML).toContain('\nabc'); 24 | view.dispose(); 25 | }); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/asset/css/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /test/asset/js/async.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var withoutAsync = {}; 3 | 4 | ["it", "beforeEach", "afterEach"].forEach(function(jasmineFunction) { 5 | withoutAsync[jasmineFunction] = jasmine.Env.prototype[jasmineFunction]; 6 | return jasmine.Env.prototype[jasmineFunction] = function() { 7 | var args = Array.prototype.slice.call(arguments, 0); 8 | var timeout = null; 9 | if (isLastArgumentATimeout(args)) { 10 | timeout = args.pop(); 11 | // The changes to the jasmine test runner causes undef to be passed when 12 | // calling all it()'s now. If the last argument isn't a timeout and the 13 | // last argument IS undefined, let's just pop it off. Since out of bounds 14 | // items are undefined anyways, *hopefully* removing an undef item won't 15 | // hurt. 16 | } else if (args[args.length-1] == undefined) { 17 | args.pop(); 18 | } 19 | if (isLastArgumentAnAsyncSpecFunction(args)) 20 | { 21 | var specFunction = args.pop(); 22 | args.push(function() { 23 | return asyncSpec(specFunction, this, timeout); 24 | }); 25 | } 26 | return withoutAsync[jasmineFunction].apply(this, args); 27 | }; 28 | }); 29 | 30 | function isLastArgumentATimeout(args) 31 | { 32 | return args.length > 0 && (typeof args[args.length-1]) === "number"; 33 | } 34 | 35 | function isLastArgumentAnAsyncSpecFunction(args) 36 | { 37 | return args.length > 0 && (typeof args[args.length-1]) === "function" && args[args.length-1].length > 0; 38 | } 39 | 40 | function asyncSpec(specFunction, spec, timeout) { 41 | if (timeout == null) timeout = jasmine.DEFAULT_TIMEOUT_INTERVAL || 1000; 42 | var done = false; 43 | spec.runs(function() { 44 | try { 45 | return specFunction.call(spec, function(error) { 46 | done = true; 47 | if (error != null) return spec.fail(error); 48 | }); 49 | } catch (e) { 50 | done = true; 51 | throw e; 52 | } 53 | }); 54 | return spec.waitsFor(function() { 55 | if (done === true) { 56 | return true; 57 | } 58 | }, "spec to complete", timeout); 59 | }; 60 | 61 | }).call(this); -------------------------------------------------------------------------------- /test/asset/js/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | setExceptionHandling(); 82 | 83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 84 | reporterView.addSpecs(specs, self.specFilter); 85 | }; 86 | 87 | self.reportRunnerResults = function(runner) { 88 | reporterView && reporterView.complete(); 89 | }; 90 | 91 | self.reportSuiteResults = function(suite) { 92 | reporterView.suiteComplete(suite); 93 | }; 94 | 95 | self.reportSpecStarting = function(spec) { 96 | if (self.logRunningSpecs) { 97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 98 | } 99 | }; 100 | 101 | self.reportSpecResults = function(spec) { 102 | reporterView.specComplete(spec); 103 | }; 104 | 105 | self.log = function() { 106 | var console = jasmine.getGlobal().console; 107 | if (console && console.log) { 108 | if (console.log.apply) { 109 | console.log.apply(console, arguments); 110 | } else { 111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 112 | } 113 | } 114 | }; 115 | 116 | self.specFilter = function(spec) { 117 | if (!focusedSpecName()) { 118 | return true; 119 | } 120 | 121 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 122 | }; 123 | 124 | return self; 125 | 126 | function focusedSpecName() { 127 | var specName; 128 | 129 | (function memoizeFocusedSpec() { 130 | if (specName) { 131 | return; 132 | } 133 | 134 | var paramMap = []; 135 | var params = jasmine.HtmlReporter.parameters(doc); 136 | 137 | for (var i = 0; i < params.length; i++) { 138 | var p = params[i].split('='); 139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 140 | } 141 | 142 | specName = paramMap.spec; 143 | })(); 144 | 145 | return specName; 146 | } 147 | 148 | function createReporterDom(version) { 149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 150 | dom.banner = self.createDom('div', { className: 'banner' }, 151 | self.createDom('span', { className: 'title' }, "Jasmine "), 152 | self.createDom('span', { className: 'version' }, version)), 153 | 154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 155 | dom.alert = self.createDom('div', {className: 'alert'}, 156 | self.createDom('span', { className: 'exceptions' }, 157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), 158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), 159 | dom.results = self.createDom('div', {className: 'results'}, 160 | dom.summary = self.createDom('div', { className: 'summary' }), 161 | dom.details = self.createDom('div', { id: 'details' })) 162 | ); 163 | } 164 | 165 | function noTryCatch() { 166 | return window.location.search.match(/catch=false/); 167 | } 168 | 169 | function searchWithCatch() { 170 | var params = jasmine.HtmlReporter.parameters(window.document); 171 | var removed = false; 172 | var i = 0; 173 | 174 | while (!removed && i < params.length) { 175 | if (params[i].match(/catch=/)) { 176 | params.splice(i, 1); 177 | removed = true; 178 | } 179 | i++; 180 | } 181 | if (jasmine.CATCH_EXCEPTIONS) { 182 | params.push("catch=false"); 183 | } 184 | 185 | return params.join("&"); 186 | } 187 | 188 | function setExceptionHandling() { 189 | var chxCatch = document.getElementById('no_try_catch'); 190 | 191 | if (noTryCatch()) { 192 | chxCatch.setAttribute('checked', true); 193 | jasmine.CATCH_EXCEPTIONS = false; 194 | } 195 | chxCatch.onclick = function() { 196 | window.location.search = searchWithCatch(); 197 | }; 198 | } 199 | }; 200 | jasmine.HtmlReporter.parameters = function(doc) { 201 | var paramStr = doc.location.search.substring(1); 202 | var params = []; 203 | 204 | if (paramStr.length > 0) { 205 | params = paramStr.split('&'); 206 | } 207 | return params; 208 | } 209 | jasmine.HtmlReporter.sectionLink = function(sectionName) { 210 | var link = '?'; 211 | var params = []; 212 | 213 | if (sectionName) { 214 | params.push('spec=' + encodeURIComponent(sectionName)); 215 | } 216 | if (!jasmine.CATCH_EXCEPTIONS) { 217 | params.push("catch=false"); 218 | } 219 | if (params.length > 0) { 220 | link += params.join("&"); 221 | } 222 | 223 | return link; 224 | }; 225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 226 | jasmine.HtmlReporter.ReporterView = function(dom) { 227 | this.startedAt = new Date(); 228 | this.runningSpecCount = 0; 229 | this.completeSpecCount = 0; 230 | this.passedCount = 0; 231 | this.failedCount = 0; 232 | this.skippedCount = 0; 233 | 234 | this.createResultsMenu = function() { 235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 237 | ' | ', 238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 239 | 240 | this.summaryMenuItem.onclick = function() { 241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 242 | }; 243 | 244 | this.detailsMenuItem.onclick = function() { 245 | showDetails(); 246 | }; 247 | }; 248 | 249 | this.addSpecs = function(specs, specFilter) { 250 | this.totalSpecCount = specs.length; 251 | 252 | this.views = { 253 | specs: {}, 254 | suites: {} 255 | }; 256 | 257 | for (var i = 0; i < specs.length; i++) { 258 | var spec = specs[i]; 259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 260 | if (specFilter(spec)) { 261 | this.runningSpecCount++; 262 | } 263 | } 264 | }; 265 | 266 | this.specComplete = function(spec) { 267 | this.completeSpecCount++; 268 | 269 | if (isUndefined(this.views.specs[spec.id])) { 270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 271 | } 272 | 273 | var specView = this.views.specs[spec.id]; 274 | 275 | switch (specView.status()) { 276 | case 'passed': 277 | this.passedCount++; 278 | break; 279 | 280 | case 'failed': 281 | this.failedCount++; 282 | break; 283 | 284 | case 'skipped': 285 | this.skippedCount++; 286 | break; 287 | } 288 | 289 | specView.refresh(); 290 | this.refresh(); 291 | }; 292 | 293 | this.suiteComplete = function(suite) { 294 | var suiteView = this.views.suites[suite.id]; 295 | if (isUndefined(suiteView)) { 296 | return; 297 | } 298 | suiteView.refresh(); 299 | }; 300 | 301 | this.refresh = function() { 302 | 303 | if (isUndefined(this.resultsMenu)) { 304 | this.createResultsMenu(); 305 | } 306 | 307 | // currently running UI 308 | if (isUndefined(this.runningAlert)) { 309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); 310 | dom.alert.appendChild(this.runningAlert); 311 | } 312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 313 | 314 | // skipped specs UI 315 | if (isUndefined(this.skippedAlert)) { 316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); 317 | } 318 | 319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 320 | 321 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 322 | dom.alert.appendChild(this.skippedAlert); 323 | } 324 | 325 | // passing specs UI 326 | if (isUndefined(this.passedAlert)) { 327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); 328 | } 329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 330 | 331 | // failing specs UI 332 | if (isUndefined(this.failedAlert)) { 333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 334 | } 335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 336 | 337 | if (this.failedCount === 1 && isDefined(dom.alert)) { 338 | dom.alert.appendChild(this.failedAlert); 339 | dom.alert.appendChild(this.resultsMenu); 340 | } 341 | 342 | // summary info 343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 345 | }; 346 | 347 | this.complete = function() { 348 | dom.alert.removeChild(this.runningAlert); 349 | 350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 351 | 352 | if (this.failedCount === 0) { 353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 354 | } else { 355 | showDetails(); 356 | } 357 | 358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 359 | }; 360 | 361 | return this; 362 | 363 | function showDetails() { 364 | if (dom.reporter.className.search(/showDetails/) === -1) { 365 | dom.reporter.className += " showDetails"; 366 | } 367 | } 368 | 369 | function isUndefined(obj) { 370 | return typeof obj === 'undefined'; 371 | } 372 | 373 | function isDefined(obj) { 374 | return !isUndefined(obj); 375 | } 376 | 377 | function specPluralizedFor(count) { 378 | var str = count + " spec"; 379 | if (count > 1) { 380 | str += "s" 381 | } 382 | return str; 383 | } 384 | 385 | }; 386 | 387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 388 | 389 | 390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 391 | this.spec = spec; 392 | this.dom = dom; 393 | this.views = views; 394 | 395 | this.symbol = this.createDom('li', { className: 'pending' }); 396 | this.dom.symbolSummary.appendChild(this.symbol); 397 | 398 | this.summary = this.createDom('div', { className: 'specSummary' }, 399 | this.createDom('a', { 400 | className: 'description', 401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), 402 | title: this.spec.getFullName() 403 | }, this.spec.description) 404 | ); 405 | 406 | this.detail = this.createDom('div', { className: 'specDetail' }, 407 | this.createDom('a', { 408 | className: 'description', 409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 410 | title: this.spec.getFullName() 411 | }, this.spec.getFullName()) 412 | ); 413 | }; 414 | 415 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 416 | return this.getSpecStatus(this.spec); 417 | }; 418 | 419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 420 | this.symbol.className = this.status(); 421 | 422 | switch (this.status()) { 423 | case 'skipped': 424 | break; 425 | 426 | case 'passed': 427 | this.appendSummaryToSuiteDiv(); 428 | break; 429 | 430 | case 'failed': 431 | this.appendSummaryToSuiteDiv(); 432 | this.appendFailureDetail(); 433 | break; 434 | } 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 438 | this.summary.className += ' ' + this.status(); 439 | this.appendToSummary(this.spec, this.summary); 440 | }; 441 | 442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 443 | this.detail.className += ' ' + this.status(); 444 | 445 | var resultItems = this.spec.results().getItems(); 446 | var messagesDiv = this.createDom('div', { className: 'messages' }); 447 | 448 | for (var i = 0; i < resultItems.length; i++) { 449 | var result = resultItems[i]; 450 | 451 | if (result.type == 'log') { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 453 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 455 | 456 | if (result.trace.stack) { 457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 458 | } 459 | } 460 | } 461 | 462 | if (messagesDiv.childNodes.length > 0) { 463 | this.detail.appendChild(messagesDiv); 464 | this.dom.details.appendChild(this.detail); 465 | } 466 | }; 467 | 468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 469 | this.suite = suite; 470 | this.dom = dom; 471 | this.views = views; 472 | 473 | this.element = this.createDom('div', { className: 'suite' }, 474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) 475 | ); 476 | 477 | this.appendToSummary(this.suite, this.element); 478 | }; 479 | 480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 481 | return this.getSpecStatus(this.suite); 482 | }; 483 | 484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 485 | this.element.className += " " + this.status(); 486 | }; 487 | 488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 489 | 490 | /* @deprecated Use jasmine.HtmlReporter instead 491 | */ 492 | jasmine.TrivialReporter = function(doc) { 493 | this.document = doc || document; 494 | this.suiteDivs = {}; 495 | this.logRunningSpecs = false; 496 | }; 497 | 498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 499 | var el = document.createElement(type); 500 | 501 | for (var i = 2; i < arguments.length; i++) { 502 | var child = arguments[i]; 503 | 504 | if (typeof child === 'string') { 505 | el.appendChild(document.createTextNode(child)); 506 | } else { 507 | if (child) { el.appendChild(child); } 508 | } 509 | } 510 | 511 | for (var attr in attrs) { 512 | if (attr == "className") { 513 | el[attr] = attrs[attr]; 514 | } else { 515 | el.setAttribute(attr, attrs[attr]); 516 | } 517 | } 518 | 519 | return el; 520 | }; 521 | 522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 523 | var showPassed, showSkipped; 524 | 525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 526 | this.createDom('div', { className: 'banner' }, 527 | this.createDom('div', { className: 'logo' }, 528 | this.createDom('span', { className: 'title' }, "Jasmine"), 529 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 530 | this.createDom('div', { className: 'options' }, 531 | "Show ", 532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 536 | ) 537 | ), 538 | 539 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 543 | ); 544 | 545 | this.document.body.appendChild(this.outerDiv); 546 | 547 | var suites = runner.suites(); 548 | for (var i = 0; i < suites.length; i++) { 549 | var suite = suites[i]; 550 | var suiteDiv = this.createDom('div', { className: 'suite' }, 551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 553 | this.suiteDivs[suite.id] = suiteDiv; 554 | var parentDiv = this.outerDiv; 555 | if (suite.parentSuite) { 556 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 557 | } 558 | parentDiv.appendChild(suiteDiv); 559 | } 560 | 561 | this.startedAt = new Date(); 562 | 563 | var self = this; 564 | showPassed.onclick = function(evt) { 565 | if (showPassed.checked) { 566 | self.outerDiv.className += ' show-passed'; 567 | } else { 568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 569 | } 570 | }; 571 | 572 | showSkipped.onclick = function(evt) { 573 | if (showSkipped.checked) { 574 | self.outerDiv.className += ' show-skipped'; 575 | } else { 576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 577 | } 578 | }; 579 | }; 580 | 581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 582 | var results = runner.results(); 583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 584 | this.runnerDiv.setAttribute("class", className); 585 | //do it twice for IE 586 | this.runnerDiv.setAttribute("className", className); 587 | var specs = runner.specs(); 588 | var specCount = 0; 589 | for (var i = 0; i < specs.length; i++) { 590 | if (this.specFilter(specs[i])) { 591 | specCount++; 592 | } 593 | } 594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 597 | 598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 599 | }; 600 | 601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 602 | var results = suite.results(); 603 | var status = results.passed() ? 'passed' : 'failed'; 604 | if (results.totalCount === 0) { // todo: change this to check results.skipped 605 | status = 'skipped'; 606 | } 607 | this.suiteDivs[suite.id].className += " " + status; 608 | }; 609 | 610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 611 | if (this.logRunningSpecs) { 612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 613 | } 614 | }; 615 | 616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 617 | var results = spec.results(); 618 | var status = results.passed() ? 'passed' : 'failed'; 619 | if (results.skipped) { 620 | status = 'skipped'; 621 | } 622 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 624 | this.createDom('a', { 625 | className: 'description', 626 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 627 | title: spec.getFullName() 628 | }, spec.description)); 629 | 630 | 631 | var resultItems = results.getItems(); 632 | var messagesDiv = this.createDom('div', { className: 'messages' }); 633 | for (var i = 0; i < resultItems.length; i++) { 634 | var result = resultItems[i]; 635 | 636 | if (result.type == 'log') { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 638 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 640 | 641 | if (result.trace.stack) { 642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 643 | } 644 | } 645 | } 646 | 647 | if (messagesDiv.childNodes.length > 0) { 648 | specDiv.appendChild(messagesDiv); 649 | } 650 | 651 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 652 | }; 653 | 654 | jasmine.TrivialReporter.prototype.log = function() { 655 | var console = jasmine.getGlobal().console; 656 | if (console && console.log) { 657 | if (console.log.apply) { 658 | console.log.apply(console, arguments); 659 | } else { 660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 661 | } 662 | } 663 | }; 664 | 665 | jasmine.TrivialReporter.prototype.getLocation = function() { 666 | return this.document.location; 667 | }; 668 | 669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 670 | var paramMap = {}; 671 | var params = this.getLocation().search.substring(1).split('&'); 672 | for (var i = 0; i < params.length; i++) { 673 | var p = params[i].split('='); 674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 675 | } 676 | 677 | if (!paramMap.spec) { 678 | return true; 679 | } 680 | return spec.getFullName().indexOf(paramMap.spec) === 0; 681 | }; 682 | -------------------------------------------------------------------------------- /test/matchers.js: -------------------------------------------------------------------------------- 1 | beforeEach(function() { 2 | this.addMatchers({ 3 | toBeOfType: function(type) { 4 | return {}.toString.call(this.actual).slice(8, -1).toUpperCase() === type.toUpperCase(); 5 | } 6 | }); 7 | }); -------------------------------------------------------------------------------- /test/run.htm: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 62 | 63 | 64 |
65 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /test/tpl/plain.tpl: -------------------------------------------------------------------------------- 1 | 2 | abc --------------------------------------------------------------------------------