diff --git a/CHANGELOG.md b/CHANGELOG.md
index c161308..325f3c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+* 0.2.1
+ - 优化了样式
+ - 增加了 `Sidebar` 的默认处理
+ - 增加了 NMP 权限模块的支持
+ - 增加了列表页的局部刷新功能和 `AuthPanel` 控件
+ - 补充 `system/user` 模块的接口
+ - 修复 `TableTip` 的一些问题
+ - `navigator` 升级,修复子 Tab 的权限问题导致的跳转错误
+ - 系统初始化时增加自定义接口的读取
+
* 0.2.0
- 修复 merge 错误
- 跳升版本
diff --git a/package.json b/package.json
index 19b7831..57d633c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "bat-ria",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "RIA extension for Brand Ads Team",
"main": "main.js",
"scripts": {
diff --git a/src/css/common.less b/src/css/common.less
index a936698..c373347 100644
--- a/src/css/common.less
+++ b/src/css/common.less
@@ -4,20 +4,23 @@
// Basic status
@bat-success-color: #008000;
-@bat-warning-color: #FF9A00;
-@bat-alert-color: #FF0000;
-@bat-aux-color: #9C9A9C;
+@bat-warning-color: #ff9a00;
+@bat-alert-color: #ff0000;
+@bat-aux-color: #9c9a9c;
-// Link colors
-@bat-link-color: #3377AA;
-@bat-link-hover-color: #FF6600;
+// link colors
+@bat-link-color: #3377aa;
+@bat-link-hover-color: #ff6600;
-// Background & border color
-@bat-border-blue: #BAD5F1;
-@bat-border-light-blue: #DBEBFA;
-@bat-bg-dark-blue: #B6D2F1;
-@bat-bg-blue: #E7F1FB;
-@bat-bg-light-blue: #F1F6FC;
+// background & border color
+@bat-border-blue: #bad5f1;
+@bat-border-light-blue: #dbebfa;
+@bat-bg-dark-blue: #b6d2f1;
+@bat-bg-blue: #e7f1fb;
+@bat-bg-light-blue: #f1f6fc;
+
+// text colors
+@bat-text-blue: #0c3ca0;
/**
@@ -221,11 +224,23 @@ hr {
&.nav-sub-current {
display: block;
}
+
+ & > .nav-sub-separate:last-child {
+ display: none;
+ }
}
@sys-userinfo-color: #FFFFFF;
.user-info {
- color: darken(@sys-userinfo-color, 10);
+ color: darken(@sys-userinfo-color, 20);
+
+ span {
+ color: #FFF;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ }
}
/**
@@ -234,3 +249,34 @@ hr {
.copyright {
text-align: center;
}
+
+
+/**
+ * Forbidden / Not Found
+ */
+.forbidden-message,
+.not-found-message {
+ width: 540px;
+ height: 131px;
+ margin: 100px auto;
+ padding-left: 110px;
+ font-size: 14px;
+ .clearfix();
+
+ .message-text {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ .font-family(heading);
+ font-size: 32px;
+ line-height: 48px;
+ color: @bat-text-blue;
+ }
+}
+
+.forbidden-message {
+ background: url(../img/coupui.png) no-repeat 0 -265px;
+}
+
+.not-found-message {
+ background: url(../img/coupui.png) no-repeat 0 -133px;
+}
diff --git a/src/css/form.less b/src/css/form.less
index ee5125e..02a5d9a 100644
--- a/src/css/form.less
+++ b/src/css/form.less
@@ -6,8 +6,9 @@
.form-header {
background-color: @bat-bg-light-blue;
padding: 8px 10px;
+ line-height: 26px;
font-weight: 700;
-
+
& + .form-body {
border-top: 1px solid @bat-border-blue;
}
@@ -73,4 +74,4 @@
.form-segment + & {
padding-left: 0;
}
-}
\ No newline at end of file
+}
diff --git a/src/css/list.less b/src/css/list.less
index aea433c..953b754 100644
--- a/src/css/list.less
+++ b/src/css/list.less
@@ -5,6 +5,11 @@
.list-header {
border-bottom: none;
+
+ h3 {
+ line-height: 26px;
+ font-weight: 700;
+ }
}
/* 列表头部 > 摘要数据区域 */
@@ -59,10 +64,27 @@
/* 列表头部 > 列表操作列区域 */
.list-operation {
+ position: relative;
+ margin-left: 12px;
.bat-linkify();
+
+ &:before {
+ content: "|";
+ display: inline;
+ position: absolute;
+ left: -8px;
+ color: @bat-aux-color;
+ }
+
+ &:first-child {
+ margin-left: 0;
+ &:before {
+ display: none;
+ }
+ }
}
.list-operation-separator {
margin: 0 5px;
color: @bat-aux-color;
-}
\ No newline at end of file
+}
diff --git a/src/css/main.less b/src/css/main.less
index f6fb0b9..b58903b 100644
--- a/src/css/main.less
+++ b/src/css/main.less
@@ -1,7 +1,6 @@
@import "./mixins.less";
@import "./common.less";
+@import "../ui/css/ui.less";
@import "./ui.less";
@import "./list.less";
@import "./form.less";
-
-@import "../ui/css/ui.less";
\ No newline at end of file
diff --git a/src/css/mixins.less b/src/css/mixins.less
index c2ab4ce..ef2c971 100644
--- a/src/css/mixins.less
+++ b/src/css/mixins.less
@@ -8,4 +8,9 @@
&:hover {
color: @bat-link-hover-color;
}
-}
\ No newline at end of file
+}
+
+.bat-disabled() {
+ color: @bat-aux-color;
+ cursor: not-allowed;
+}
diff --git a/src/css/ui.less b/src/css/ui.less
index 8b0be66..fcf7009 100644
--- a/src/css/ui.less
+++ b/src/css/ui.less
@@ -53,10 +53,12 @@
}
}
+// esui/Pager
.ui-pager-main li {
font-family: inherit;
}
+// esui/Table
.ui-table-cell-editable {
.ellipsis();
}
@@ -74,6 +76,7 @@
margin: 0 auto;
}
+// esui/Dialog
.ui-dialog-title {
font-size: 12px;
}
@@ -94,6 +97,7 @@
padding: 10px 0 10px 6px
}
+// esui/Button
.skin-spring-button {
height: 26px;
line-height: 26px;
@@ -112,6 +116,15 @@ button.skin-link-button:hover {
height: 26px;
}
+.skin-spring-button-disabled {
+ &,
+ &:hover,
+ &:active {
+ .linear-gradient-background(top, #a9d796, #a9d796);
+ border-color: #8ac971;
+ }
+}
+
.skin-spring-add-disabled {
&,
&:hover,
@@ -133,20 +146,12 @@ button.skin-link-button:hover {
top, #f6f6f6, #FFF, "../img/esui-btn-download.png", 9px, center, #f6f6f6);
}
+// esui/RangeCalendar
.ui-rangecalendar {
.linear-gradient-background (top, #ffffff, #f6f6f6, #ffffff);
}
-.ui-uploader {
- display: inline-block;
- *width: 150px;
- white-space: normal;
-}
-
-.ui-uploader-input-container {
- *float: left;
-}
-
+// esui/BoxGroup
.ui-checkbox,
.ui-radio,
.ui-boxgroup-checkbox,
@@ -156,6 +161,7 @@ button.skin-link-button:hover {
}
}
+// esui/Textbox
.ui-textbox {
&,
& input,
@@ -164,6 +170,7 @@ button.skin-link-button:hover {
}
}
+// esui/Tab
.ui-tab-horizontal {
.ui-tab-navigator {
.clearfix();
@@ -190,6 +197,7 @@ button.skin-link-button:hover {
}
}
+// esui/Toast
.ui-toast-content {
padding: 0 10px;
font-size: 12px;
@@ -215,6 +223,7 @@ button.skin-link-button:hover {
}
}
+// esui/Select
.ui-select-validity-label-invalid {
display: inline-block;
margin: 0;
@@ -224,6 +233,7 @@ button.skin-link-button:hover {
color: #C00;
}
+// esui/Wizard
.skin-default-wizard {
.ui-wizard-node {
padding-right: 8px;
@@ -267,5 +277,57 @@ button.skin-link-button:hover {
.ui-wizard-node-done span {
background-color: @bat-bg-blue;
- }
-}
\ No newline at end of file
+ }
+}
+
+// esui/Crumb
+.ui-crumb-node-last {
+ font-weight: 700;
+}
+
+
+// bat-ria/Uploader
+.ui-uploader {
+ display: inline-block;
+ *width: 150px;
+ white-space: normal;
+}
+
+.ui-uploader-input-container {
+ *float: left;
+}
+
+// bat-ria/RichSelector inside esui/Sidebar
+.ui-sidebar {
+ .ui-richselector-empty-text {
+ padding: 0 10px;
+ line-height: 30px;
+ }
+
+ .ui-tree-richselector {
+
+ .ui-tree-node-indicator-level-0 {
+ display: none;
+ }
+
+ .ui-richselector-query-list {
+ .ui-tree-item-content {
+ color: @bat-link-color;
+ }
+
+ .ui-tree-content-wrapper-selected {
+ .ui-tree-item-content {
+ color: @bat-link-color;
+ font-weight: 700;
+ }
+ }
+ }
+
+ .ui-richselector-query-list {
+ .ui-tree-content-wrapper-selected,
+ .ui-tree-content-wrapper:hover {
+ background: @bat-bg-light-blue;
+ }
+ }
+ }
+}
diff --git a/src/extension/hooks.js b/src/extension/hooks.js
index 2169215..f00e37a 100644
--- a/src/extension/hooks.js
+++ b/src/extension/hooks.js
@@ -10,9 +10,7 @@ define(function (require) {
function getAderArgMap() {
var user = require('../system/user');
- var aderId = user.ader && user.ader.id
- || URI.parseQuery(document.location.search).aderId
- || user.visitor && user.visitor.id;
+ var aderId = user.getAderId();
return aderId ? { aderId: aderId } : {};
}
diff --git a/src/extension/ui.js b/src/extension/ui.js
index 8d8fb5c..c213a8a 100644
--- a/src/extension/ui.js
+++ b/src/extension/ui.js
@@ -10,6 +10,7 @@ define(
function (require) {
var u = require('underscore');
var lib = require('esui/lib');
+ var Control = require('esui/Control');
/**
* 加载并配置验证规则
@@ -18,8 +19,10 @@ define(
*/
function initializeValidationRules() {
// 加载所有验证规则
- var MaxLengthRule = require('esui/validator/MaxLengthRule');
- var MinLengthRule = require('esui/validator/MinLengthRule');
+ require('esui/validator/MaxByteLengthRule');
+ require('esui/validator/MinByteLengthRule');
+ require('esui/validator/MaxLengthRule');
+ require('esui/validator/MinLengthRule');
var RequiredRule = require('esui/validator/RequiredRule');
var PatternRule = require('esui/validator/PatternRule');
var MaxRule = require('esui/validator/MaxRule');
@@ -67,68 +70,32 @@ define(
}
var Rule = require('esui/validator/Rule');
+ var defaultGetErrorMessage = Rule.prototype.getErrorMessage;
- MaxLengthRule.prototype.getErrorMessage = function (control) {
- if (control.get('maxErrorMessage')) {
- var getErrorMessage = Rule.prototype.getErrorMessage;
- getErrorMessage.apply(this, arguments);
+ function getErrorMessage(control) {
+ if (control.get(this.type + 'ErrorMessage')) {
+ return defaultGetErrorMessage.apply(this, arguments);
}
var rangeErrorMessage = getRangeErrorMessage(control);
if (rangeErrorMessage) {
return rangeErrorMessage;
}
- return Rule.prototype.getErrorMessage.apply(this, arguments);
- };
- MinLengthRule.prototype.getErrorMessage = function (control) {
- if (control.get('maxErrorMessage')) {
- var getErrorMessage = Rule.prototype.getErrorMessage;
- getErrorMessage.apply(this, arguments);
- }
- var rangeErrorMessage = getRangeErrorMessage(control);
- if (rangeErrorMessage) {
- return rangeErrorMessage;
- }
- return Rule.prototype.getErrorMessage.apply(this, arguments);
- };
-
- MaxRule.prototype.getErrorMessage = function (control) {
- if (control.get('maxErrorMessage')) {
- var getErrorMessage = Rule.prototype.getErrorMessage;
- getErrorMessage.apply(this, arguments);
- }
- var rangeErrorMessage = getRangeErrorMessage(control);
- if (rangeErrorMessage) {
- return rangeErrorMessage;
- }
- return Rule.prototype.getErrorMessage.apply(this, arguments);
- };
-
- MinRule.prototype.getErrorMessage = function (control) {
- if (control.get('maxErrorMessage')) {
- var getErrorMessage = Rule.prototype.getErrorMessage;
- getErrorMessage.apply(this, arguments);
- }
- var rangeErrorMessage = getRangeErrorMessage(control);
- if (rangeErrorMessage) {
- return rangeErrorMessage;
- }
- return Rule.prototype.getErrorMessage.apply(this, arguments);
- };
+ return defaultGetErrorMessage.apply(this, arguments);
+ }
+ MaxRule.prototype.getErrorMessage = getErrorMessage;
+ MinRule.prototype.getErrorMessage = getErrorMessage;
PatternRule.prototype.getErrorMessage = function (control) {
var pattern = control.get('pattern') + '';
- if (control.get('patternErrorMessage')
- || !NUMBER_REGEX.hasOwnProperty(pattern)
- ) {
- var getErrorMessage = Rule.prototype.getErrorMessage;
- getErrorMessage.apply(this, arguments);
+ if (control.get('patternErrorMessage') || !NUMBER_REGEX.hasOwnProperty(pattern)) {
+ return defaultGetErrorMessage.apply(this, arguments);
}
var rangeErrorMessage = getRangeErrorMessage(control);
if (rangeErrorMessage) {
return rangeErrorMessage;
}
- return Rule.prototype.getErrorMessage.apply(this, arguments);
+ return defaultGetErrorMessage.apply(this, arguments);
};
}
@@ -179,7 +146,7 @@ define(
function initializeGlobalExtensions() {
var ui = require('esui');
var globalExtensions = [
- { type: 'CustomData', options: {} }
+ // { type: 'CustomData', options: {} }
];
u.each(globalExtensions, function (extension) {
@@ -264,12 +231,47 @@ define(
};
}
+ function addTreeNodeTitle() {
+ var Tree = require('esui/Tree');
+
+ Tree.prototype.itemTemplate = '${text}';
+ }
+
+ function fixSidebarHide() {
+ var Sidebar = require('esui/Sidebar');
+
+ /**
+ * 隐藏控件
+ *
+ * @return {boolean}
+ */
+ Sidebar.prototype.hide = function () {
+ Control.prototype.hide.call(this);
+
+ var mat = lib.g(this.helper.getId('mat'));
+ if (mat) {
+ mat.style.display = 'none';
+ }
+
+ // 隐藏主区域
+ this.main.style.display = 'none';
+
+ // minibar
+ var miniBar = lib.g(this.helper.getId('minibar'));
+ if (miniBar) {
+ miniBar.style.display = 'none';
+ }
+ };
+ }
+
function activate() {
initializeValidationRules();
addControlLinkMode();
initializeGlobalExtensions();
addRegionExtension();
addCrumbGlobalRedirect();
+ addTreeNodeTitle();
+ fixSidebarHide();
}
return {
diff --git a/src/img/coupui.png b/src/img/coupui.png
new file mode 100644
index 0000000..60c1e3f
Binary files /dev/null and b/src/img/coupui.png differ
diff --git a/src/io/serverIO.js b/src/io/serverIO.js
index 3f135c6..614803d 100644
--- a/src/io/serverIO.js
+++ b/src/io/serverIO.js
@@ -81,7 +81,8 @@ define(function (require) {
if (status === 'SUCCESS') {
return {
success: true,
- result: data.result
+ message: data.message,
+ result: data.result || data.page
};
}
else {
@@ -207,7 +208,8 @@ define(function (require) {
var defaults = {
url: url,
data: data,
- dataType: 'json'
+ dataType: 'json',
+ charset: 'utf-8'
};
options = options
diff --git a/src/main.js b/src/main.js
index e95951a..7cc84f4 100644
--- a/src/main.js
+++ b/src/main.js
@@ -9,6 +9,7 @@ define(
var config = {};
var u = require('underscore');
var util = require('./util');
+ var Deferred = require('er/Deferred');
/**
* 初始化API请求器
@@ -28,14 +29,27 @@ define(
/**
* 初始化系统启动
*
+ * @param {Array} [extra] 额外的请求发送器
+ *
* @ignore
*/
- function loadData() {
- var Deferred = require('er/Deferred');
-
- return Deferred.all(
- Deferred.when(config.api.user()),
- Deferred.when(config.api.constants())
+ function loadData(extra) {
+ extra = extra ? u.map(extra, function (api) {
+ if (typeof api === 'string') {
+ return util.genRequesters(api);
+ }
+ else {
+ return api;
+ }
+ }) : [];
+
+ var requests = [ config.api.user, config.api.constants ].concat(extra || []);
+
+ return Deferred.all.apply(
+ Deferred,
+ u.map(requests, function (requester) {
+ return Deferred.when(requester());
+ })
);
}
@@ -53,6 +67,15 @@ define(
var consts = require('./system/constants');
var localConstants = require('common/constants');
consts.init(u.extend(localConstants, constants));
+
+ // 返回其余请求结果
+ var extra = [].slice.call(arguments).slice(2);
+ return Deferred.all.apply(
+ Deferred,
+ u.map(extra, function (result) {
+ return Deferred.resolved(result);
+ })
+ );
}
/**
@@ -71,9 +94,12 @@ define(
/**
* RIA启动入口
*
+ * @param {Object} riaConfig RIA配置
+ * @param {Array} requesters 初始化数据需要的请求发送器
+ * @param {Function} callback 初始化请求返回后的回调函数
* @ignore
*/
- function start(riaConfig) {
+ function start(riaConfig, requesters, callback) {
config = riaConfig;
@@ -95,8 +121,9 @@ define(
initApiConfig();
// 读取必要信息后初始化系统
- return loadData()
+ return loadData(requesters)
.then(initData)
+ .then(callback)
.then(init);
}
diff --git a/src/mvc/BaseModel.js b/src/mvc/BaseModel.js
index b73af58..794152f 100644
--- a/src/mvc/BaseModel.js
+++ b/src/mvc/BaseModel.js
@@ -22,8 +22,8 @@ define(function (require) {
util.inherits(BaseModel, UIModel);
/**
- * 合并默认数据源
- */
+ * 合并默认数据源
+ */
BaseModel.prototype.mergeDefaultDatasource = function() {
if (!this.datasource) {
this.datasource = this.defaultDatasource;
diff --git a/src/mvc/BaseView.js b/src/mvc/BaseView.js
index ee72b41..1f7ef4d 100644
--- a/src/mvc/BaseView.js
+++ b/src/mvc/BaseView.js
@@ -189,7 +189,7 @@ define(function (require) {
* 2. showDialog(dialog, options)
*/
BaseView.prototype.showDialog = function (dialog, options) {
- if (!dialog instanceof Dialog) {
+ if (!(dialog instanceof Dialog)) {
options = dialog;
dialog = this.popDialog.call(this, options);
}
@@ -289,7 +289,7 @@ define(function (require) {
*/
BaseView.prototype.waitActionDialog = function () {
var dialog = this.popActionDialog.apply(this, arguments);
-
+
var deferred = new Deferred();
dialog.on('actionloaded', deferred.resolver.resolve);
@@ -299,5 +299,15 @@ define(function (require) {
return deferred.promise;
};
+ /**
+ * 刷新权限设置,在Action加载过新内容时使用
+ */
+ BaseView.prototype.refreshAuth = function () {
+ var authPanel = this.get('authPanel');
+ if (authPanel) {
+ authPanel.initAuth();
+ }
+ };
+
return BaseView;
});
diff --git a/src/mvc/ForbiddenAction.js b/src/mvc/ForbiddenAction.js
new file mode 100644
index 0000000..5fb1db2
--- /dev/null
+++ b/src/mvc/ForbiddenAction.js
@@ -0,0 +1,27 @@
+/**
+ * @file 403页 Action
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+ var util = require('er/util');
+ var Action = require('er/Action');
+
+ /**
+ * 403页 Action
+ *
+ * @extends er/Action
+ * @constructor
+ */
+ function ForbiddenAction() {
+ Action.apply(this, arguments);
+ }
+
+ util.inherits(ForbiddenAction, Action);
+
+ ForbiddenAction.prototype.modelType = require('./ForbiddenModel');
+ ForbiddenAction.prototype.viewType = require('./ForbiddenView');
+
+ return ForbiddenAction;
+});
+
diff --git a/src/mvc/ForbiddenModel.js b/src/mvc/ForbiddenModel.js
new file mode 100644
index 0000000..8b22e93
--- /dev/null
+++ b/src/mvc/ForbiddenModel.js
@@ -0,0 +1,24 @@
+/**
+ * @file 403页 Model
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+ var util = require('er/util');
+ var Model = require('er/Model');
+
+ /**
+ * 403页 Model
+ *
+ * @param {Object=} context 初始化时的数据
+ *
+ * @constructor
+ * @extends er/Model
+ */
+ function ForbiddenModel(context) {
+ Model.call(this, context);
+ }
+ util.inherits(ForbiddenModel, Model);
+
+ return ForbiddenModel;
+});
diff --git a/src/mvc/ForbiddenView.js b/src/mvc/ForbiddenView.js
new file mode 100644
index 0000000..c93bf8e
--- /dev/null
+++ b/src/mvc/ForbiddenView.js
@@ -0,0 +1,31 @@
+/**
+ * @file 403页 View
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+ // require template
+ require('../tpl!../tpl/forbidden.tpl.html');
+
+ var util = require('er/util');
+ var View = require('er/View');
+
+ /**
+ * 403页 View
+ *
+ * @extends er.View
+ * @constructor
+ */
+ function ForbiddenView() {
+ View.apply(this, arguments);
+ }
+
+ util.inherits(ForbiddenView, View);
+
+ /**
+ * @inheritDoc
+ */
+ ForbiddenView.prototype.template = 'TPL_forbidden';
+
+ return ForbiddenView;
+});
diff --git a/src/mvc/FormAction.js b/src/mvc/FormAction.js
index 681e8be..8f89df1 100644
--- a/src/mvc/FormAction.js
+++ b/src/mvc/FormAction.js
@@ -219,7 +219,7 @@ define(function (require) {
* @return {meta.Promise}
*/
FormAction.prototype.submit = function (submitData) {
- var handleBeforeSubmit = this.fire('beforesubmit');
+ var handleBeforeSubmit = this.fire('beforesubmit', { submitData: submitData });
if (!handleBeforeSubmit.isDefaultPrevented()) {
try {
var submitRequester = this.model.submitRequester;
@@ -286,7 +286,7 @@ define(function (require) {
* beforeValidate ->
* validate ->
* afterValidate ->
- * sumbmit ->
+ * submit ->
* enableSubmit
*
* 可针对业务需求扩展beforeValidate、afterValidate
@@ -299,9 +299,9 @@ define(function (require) {
require('er/Deferred')
.when(this.beforeValidate(submitData))
- .then(u.bind(u.partial(this.validate, submitData), this))
- .then(u.bind(u.partial(this.afterValidate, submitData), this))
- .then(u.bind(u.partial(this.submit, submitData), this))
+ .then(u.bind(this.validate, this, submitData))
+ .then(u.bind(this.afterValidate, this, submitData))
+ .then(u.bind(this.submit, this, submitData))
.ensure(u.bind(this.view.enableSubmit, this.view));
};
diff --git a/src/mvc/FormView.js b/src/mvc/FormView.js
index b2076fa..58a0ce3 100644
--- a/src/mvc/FormView.js
+++ b/src/mvc/FormView.js
@@ -172,7 +172,7 @@ define(function (require) {
function scrollTo(element) {
var offset = lib.getOffset(element);
if (lib.page.getScrollTop() > offset.top) {
- document.body.scrollTop = document.documentElement.scrollTop = offset.top - 10;
+ element.scrollIntoView(true);
}
}
@@ -186,7 +186,7 @@ define(function (require) {
FormView.prototype.handleValidateInvalid = function () {
var me = this;
var form = this.get('form');
- u.some(form.getInputControls(), function (input, index) {
+ u.some(form.getInputControls(), function (input) {
if (input.hasState('validity-invalid')) {
var e = me.fire('scrolltofirsterror', { firstErrValidity: input });
if (!e.isDefaultPrevented()) {
diff --git a/src/mvc/ListAction.js b/src/mvc/ListAction.js
index fa99444..8ff62ed 100644
--- a/src/mvc/ListAction.js
+++ b/src/mvc/ListAction.js
@@ -21,6 +21,14 @@ define(function (require) {
util.inherits(ListAction, BaseAction);
+
+ /**
+ * 在搜索、翻页等操作后选择触发跳转还是仅刷新列表
+ *
+ * @type {boolean}
+ */
+ ListAction.prototype.redirectAfterChange = true;
+
/**
* 进行查询
*
@@ -31,8 +39,8 @@ define(function (require) {
var defaultArgs = this.model.getDefaultArgs();
var extraArgs = this.model.getExtraQuery();
args = u.chain(args)
- .purify(defaultArgs)
.extend(extraArgs)
+ .purify(defaultArgs)
.value();
var event = this.fire('search', { args: args });
@@ -49,17 +57,17 @@ define(function (require) {
ListAction.prototype.redirectForSearch = function (args) {
var path = this.model.get('url').getPath();
var url = URL.withQuery(path, args);
- this.redirect(url, { force: true });
+ this.loadList(url);
};
/**
* 获取指定页码的跳转URL
*
* @param {number} pageNo 指定的页码
- * @return {string}
+ * @return {er/URL} 生成的分页URL对象
*/
ListAction.prototype.getURLForPage = function (pageNo) {
- var url = this.context.url;
+ var url = this.model.get('url');
var path = url.getPath();
var query = url.getQuery();
query.pageNo = pageNo;
@@ -69,7 +77,7 @@ define(function (require) {
query = u.omit(query, 'pageNo');
}
- return require('er/URL').withQuery(path, query).toString();
+ return require('er/URL').withQuery(path, query);
};
/**
@@ -93,10 +101,31 @@ define(function (require) {
var event = this.fire('pagechange', { page: e.page });
if (!event.isDefaultPrevented()) {
var url = this.getURLForPage(e.page);
- this.redirect(url);
+ this.loadList(url);
}
}
+ /**
+ * 根据新的URL参数刷新列表
+ *
+ * @param {er/URL} [url] 新的URL对象,没有时按当前URL刷新
+ * @return {er/Promise=} 返回请求的Promise对象
+ */
+ ListAction.prototype.loadList = function (url) {
+ if (this.redirectAfterChange) {
+ this.redirect(url, { force: true });
+ }
+ else {
+ var me = this;
+ url = url || me.model.get('url');
+
+ return me.model.loadData(url).then(function () {
+ me.redirect(url, { silent: true });
+ me.view.refresh();
+ });
+ }
+ };
+
/**
* 初始化交互行为
*
@@ -106,16 +135,45 @@ define(function (require) {
ListAction.prototype.initBehavior = function () {
BaseAction.prototype.initBehavior.apply(this, arguments);
this.view.on('search', search, this);
- this.view.on('pagesizechange', search, this);
this.view.on('pagechange', forwardToPage, this);
};
+ /**
+ * 初始化交互行为
+ *
+ * @protected
+ * @override
+ */
+ ListAction.prototype.reload = function () {
+ if (this.redirectAfterChange) {
+ BaseAction.prototype.reload.call(this);
+ }
+ else {
+ this.loadList();
+ }
+ };
+
/**
* 根据布局变化重新调整自身布局
*/
ListAction.prototype.adjustLayout = function () {
this.view.adjustLayout();
};
-
+
+ // /**
+ // * @inheritDoc
+ // *
+ // * @protected
+ // * @override
+ // */
+ // ListAction.prototype.filterRedirect = function (url) {
+ // if (url.getPath() !== this.model.get('url').getPath()
+ // || this.redirectAfterChange) {
+ // return true;
+ // }
+ // this.loadList(url);
+ // return false;
+ // };
+
return ListAction;
});
diff --git a/src/mvc/ListModel.js b/src/mvc/ListModel.js
index 97a4147..91bbcea 100644
--- a/src/mvc/ListModel.js
+++ b/src/mvc/ListModel.js
@@ -32,9 +32,9 @@ define(function (require) {
/**
* 配置默认查询参数
- *
+ *
* 如果某个参数与这里的值相同,则不会加到URL中
- *
+ *
* 创建`Model`时,如果某个参数不存在,则会自动补上这里的值
*
* @type {Object}
@@ -54,6 +54,20 @@ define(function (require) {
return u.defaults(this.defaultArgs || {}, { pageNo: 1 });
};
+ /**
+ * 转换列表数据
+ * 将返回值中的`result`字段改为`tableData`来放到Model中
+ *
+ * @param {Object} data 列表请求接口返回的数据
+ * @return {Object} 转换完毕的数据
+ */
+ function adaptData(data) {
+ var page = data;
+ page.tableData = page.result;
+ delete page.result;
+ return page;
+ }
+
/**
* @inheritDoc
*/
@@ -61,12 +75,7 @@ define(function (require) {
listPage: {
retrieve: function (model) {
return model.listRequester(model.getQuery())
- .then(function(data) {
- var page = data;
- page.tableData = page.result;
- delete page.result;
- return page;
- });
+ .then(adaptData);
},
dump: true
},
@@ -122,7 +131,7 @@ define(function (require) {
ListModel.prototype.defaultTimeRange = null;
/**
- * 默认选择的时间范围
+ * 生成默认的后端请求参数
*
* @return {Object}
* @protected
@@ -145,17 +154,16 @@ define(function (require) {
}
// 合并默认参数、附加参数,最后再统一处理一下输出
- query = u.chain(query)
+ u.chain(query)
.defaults(this.getDefaultArgs())
.defaults(range)
- .extend(this.getExtraQuery())
- .value();
+ .extend(this.getExtraQuery());
- return this.filterQuery(query);
+ return this.prepareQuery(query);
};
/**
- * 获取附加的请求参数
+ * 获取除列表本身参数外附加的请求参数
*
* @return {Object}
* @protected
@@ -167,13 +175,44 @@ define(function (require) {
/**
* 对合并好的请求参数进行统一的后续处理
*
+ * @deprecated since v0.2.1 名字起得不好,后面使用`prepareQuery`替代
* @param {Object} query 需要处理的参数对象
* @return {Object}
* @protected
*/
- ListModel.prototype.filterQuery = function(query) {
+ ListModel.prototype.filterQuery = function (query) {
return query;
};
+ /**
+ * 对合并好的请求参数进行统一的后续处理
+ *
+ * @param {Object} query 需要处理的参数对象
+ * @return {Object}
+ * @protected
+ */
+ ListModel.prototype.prepareQuery = function (query) {
+ return this.filterQuery(query);
+ };
+
+ /**
+ * 重新读取列表数据
+ *
+ * @param {er/URL} url 根据指定URL读取数据
+ * @return {er/Promise} 返回异步请求的Promise对象
+ * @protected
+ */
+ ListModel.prototype.loadData = function (url) {
+ var me = this;
+ var urlQuery = url.getQuery();
+ me.set('url', url);
+ me.fill(urlQuery);
+
+ return me.listRequester(me.getQuery())
+ .then(function(data) {
+ me.fill(adaptData(data));
+ });
+ };
+
return ListModel;
});
diff --git a/src/mvc/ListView.js b/src/mvc/ListView.js
index 8dfb29b..b2aa3f2 100644
--- a/src/mvc/ListView.js
+++ b/src/mvc/ListView.js
@@ -10,7 +10,7 @@ define(function (require) {
var BaseView = require('./BaseView');
var u = require('underscore');
var moment = require('moment');
-
+
/**
* 列表`View`基类
*
@@ -20,7 +20,7 @@ define(function (require) {
function ListView() {
BaseView.apply(this, arguments);
}
-
+
/**
* @inheritDoc
*/
@@ -45,15 +45,11 @@ define(function (require) {
args.order = e.order;
}
- // 总是带上每页显示数
- args.pageSize = this.get('pager').get('pageSize');
-
-
this.fire('search', { args: args });
};
/**
- * 获取查询参数,默认是取`filter`表单的所有数据,加上表格的排序字段
+ * 获取查询参数,默认是取`filter`表单的所有数据,加上表格的排序字段和每页显示条目数
*
* @return {Object}
*/
@@ -61,6 +57,7 @@ define(function (require) {
// 获取表单的字段
var form = this.get('filter');
var args = form ? form.getData() : {};
+
// 加上原本的排序方向和排序字段名
args.order = this.model.get('order');
args.orderBy = this.model.get('orderBy');
@@ -70,7 +67,7 @@ define(function (require) {
// 关键词去空格
args.keyword = u.trim(keyword.getValue());
}
-
+
// 日期是独立的
var range = this.get('range');
if (range) {
@@ -78,6 +75,13 @@ define(function (require) {
args.startTime = moment(range[0]).format('YYYYMMDDHHmmss');
args.endTime = moment(range[1]).format('YYYYMMDDHHmmss');
}
+
+ // 带上每页显示数
+ var pager = this.get('pager');
+ if (pager) {
+ args.pageSize = pager.get('pageSize');
+ }
+
return args;
};
@@ -99,6 +103,7 @@ define(function (require) {
var items = this.getSelectedItems();
this.getGroup('batch').set('disabled', u.isEmpty(items));
+ this.getGroup('batch').set('readOnly', u.isEmpty(items));
};
/**
@@ -120,12 +125,36 @@ define(function (require) {
function batchModify(e) {
var args = {
// 批量操作的类型
- type: e.target.getData('type')
+ action: e.target.getData('type'),
+ selectedItems: this.get('table').getSelectedItems()
};
this.fire('batchmodify', args);
}
+ /**
+ * 侧边栏模式改变时要调整整体布局
+ *
+ * @param {Object} e 模式切换事件对象
+ */
+ function sidebarModeChange(e) {
+ // Sidebar目前只提供了操作DOM的方式更新布局,需要对ESUI做优化后再升级这块
+ var neighbor = document.getElementById('neighbor');
+
+ if (!neighbor) {
+ return;
+ }
+
+ if (e.mode === 'fixed') {
+ neighbor.className = 'ui-sidebar-neighbor';
+ }
+ else {
+ neighbor.className = 'ui-sidebar-neighbor-hide';
+ }
+
+ this.adjustLayout();
+ }
+
/**
* @inheritDoc
*/
@@ -151,11 +180,18 @@ define(function (require) {
filter.on('submit', this.submitSearch, this);
}
+ var sidebar = this.get('sidebar');
+ if (sidebar) {
+ sidebar.on('modechange', sidebarModeChange, this);
+ }
+
u.each(
this.getGroup('batch'),
function (button) {
- // 批量更新
- button.on('click', batchModify, this);
+ if (button instanceof require('esui/Button')) {
+ // 批量更新
+ button.on('click', batchModify, this);
+ }
},
this
);
@@ -171,6 +207,52 @@ define(function (require) {
ListView.prototype.enterDocument = function () {
BaseView.prototype.enterDocument.apply(this, arguments);
this.updateBatchButtonStatus();
+
+ this.adjustLayout();
+ };
+
+ /**
+ * 根据布局变化重新调整自身布局
+ */
+ ListView.prototype.adjustLayout = function () {
+ var table = this.get('table');
+ if (table) {
+ table.adjustWidth();
+ }
+ };
+
+ /**
+ * 根据Model数据重新渲染页面
+ */
+ ListView.prototype.refresh = function () {
+ // 刷新列表
+ this.refreshList();
+
+ // 最后刷新权限显示
+ this.refreshAuth();
+ };
+
+ /**
+ * 根据Model数据重新渲染列表
+ */
+ ListView.prototype.refreshList = function () {
+ var model = this.model;
+ var table = this.get('table');
+ if (table) {
+ table.setDatasource(model.get('tableData'));
+ }
+
+ var pager = this.get('pager');
+ if (pager) {
+ pager.setProperties(
+ {
+ count: model.get('totalCount'),
+ page: model.get('pageNo'),
+ pageSize: model.get('pageSize')
+ },
+ { silent: true }
+ );
+ }
};
require('er/util').inherits(ListView, BaseView);
diff --git a/src/mvc/NotFoundAction.js b/src/mvc/NotFoundAction.js
new file mode 100644
index 0000000..50dacd9
--- /dev/null
+++ b/src/mvc/NotFoundAction.js
@@ -0,0 +1,27 @@
+/**
+ * @file 404页 Action
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+ var util = require('er/util');
+ var Action = require('er/Action');
+
+ /**
+ * 404页 Action
+ *
+ * @extends er/Action
+ * @constructor
+ */
+ function NotFoundAction() {
+ Action.apply(this, arguments);
+ }
+
+ util.inherits(NotFoundAction, Action);
+
+ NotFoundAction.prototype.modelType = require('./NotFoundModel');
+ NotFoundAction.prototype.viewType = require('./NotFoundView');
+
+ return NotFoundAction;
+});
+
diff --git a/src/mvc/NotFoundModel.js b/src/mvc/NotFoundModel.js
new file mode 100644
index 0000000..8cce2d1
--- /dev/null
+++ b/src/mvc/NotFoundModel.js
@@ -0,0 +1,24 @@
+/**
+ * @file 404页 Model
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+ var util = require('er/util');
+ var Model = require('er/Model');
+
+ /**
+ * 404页 Model
+ *
+ * @param {Object=} context 初始化时的数据
+ *
+ * @constructor
+ * @extends er/Model
+ */
+ function NotFoundModel(context) {
+ Model.call(this, context);
+ }
+ util.inherits(NotFoundModel, Model);
+
+ return NotFoundModel;
+});
diff --git a/src/mvc/NotFoundView.js b/src/mvc/NotFoundView.js
new file mode 100644
index 0000000..45f3fa4
--- /dev/null
+++ b/src/mvc/NotFoundView.js
@@ -0,0 +1,31 @@
+/**
+ * @file 404页 View
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+ // require template
+ require('../tpl!../tpl/not_found.tpl.html');
+
+ var util = require('er/util');
+ var View = require('er/View');
+
+ /**
+ * 404页 View
+ *
+ * @extends er.View
+ * @constructor
+ */
+ function NotFoundView() {
+ View.apply(this, arguments);
+ }
+
+ util.inherits(NotFoundView, View);
+
+ /**
+ * @inheritDoc
+ */
+ NotFoundView.prototype.template = 'TPL_not_found';
+
+ return NotFoundView;
+});
diff --git a/src/system/auth.js b/src/system/auth.js
new file mode 100644
index 0000000..3f970fd
--- /dev/null
+++ b/src/system/auth.js
@@ -0,0 +1,205 @@
+/**
+ * @file 权限验证相关功能
+ * @author Justineo(justice360@gmail.com)
+ */
+
+define(function (require) {
+
+ var u = require('underscore');
+
+ var auth = {};
+
+ /**
+ * 权限类型
+ *
+ * @enum {string}
+ */
+ var AuthType = {
+ NONE: 'none',
+ EDITABLE: 'editable',
+ READONLY: 'readonly',
+ MIXED: '-'
+ };
+
+ auth.AuthType = AuthType;
+
+ /**
+ * 获取某个权限模块在某个权限集合中的权限类型
+ * @param {string} authName 权限模块的名字
+ * @param {Object} authMap 权限模块集合
+ * @return {AuthType} 返回权限类型
+ */
+ auth.get = function(authName, authMap) {
+
+ var filter;
+ if (/^\(.+\)$/.test(authName)) {
+ return auth.get(authName.substr(1, authName.length - 2), authMap);
+ }
+ var authNames = [];
+ if (/^(\w+):/.test(authName)) {
+ var authPattern = /^(?:(\w+):)(.+)$/;
+ var arr = authPattern.exec(authName);
+ if (!arr) {
+ throw 'Auth error: [' + authName + '] is not a valid auth expression.';
+ }
+ filter = arr[1];
+ var paramStr = arr[2].replace(/^[\s,|]+|[\s,|]+$/g, ''); // trim
+ var cache = [];
+ var bracketCount = 0;
+ for (var i = 0; i < paramStr.length; i++) {
+ var ch = paramStr.charAt(i);
+ if (ch === '(') {
+ if (bracketCount === 0 && cache.length) { // 括号前有非分隔字符,非法
+ throw 'Auth error: [' + authName + '] is not a valid auth expression.';
+ }
+ if (++bracketCount === 1) { // 最外层第一个括号丢弃
+ continue;
+ }
+ }
+ else if (ch === ')') {
+ // 最外层括号结束,取出整个括号内的内容递归调用
+ if (--bracketCount <= 0) {
+ var brachketContent = cache.join('');
+ authNames.push(auth.get(brachketContent, authMap).toUpperCase());
+ cache = [];
+ continue;
+ }
+ }
+ else if (bracketCount <= 0 // 不在括号内的内容,碰到分隔字符,将cache里的字符作为一个完整的authName
+ && (ch === ','
+ || ch === '|'
+ || /\s/.test(ch)
+ )
+ ) {
+ if (cache.length) {
+ authNames.push(cache.join(''));
+ cache = [];
+ }
+ continue;
+ }
+ cache.push(ch);
+ }
+ if (cache.length) {
+ authNames.push(cache.join(''));
+ }
+ if (bracketCount > 0) {
+ throw 'Auth error: [' + authName + '] brackets doesn\'t match.';
+ }
+ }
+ else {
+ authNames = [u.trim(authName)];
+ }
+
+ var types = [];
+ u.each(authNames, function(name) {
+
+ // 如果是权限类型关键字作为直接量,不需要查询模块
+ if (u.contains(u.keys(AuthType), name)) {
+ types.push(AuthType[name]);
+ return true;
+ }
+
+ var nameSpaces = name.split('.');
+
+ do {
+ var type = authMap[nameSpaces.join('.')];
+ if (type) {
+ types.push(type);
+ return true;
+ }
+ nameSpaces.pop();
+ } while (nameSpaces.length);
+
+ throw 'Auth error: [' + name + '] not found.';
+ });
+
+ if (filter) {
+ return filters[filter].call(
+ null, types.length > 1 ? types : types[0]
+ );
+ }
+ else {
+ return types[0];
+ }
+ };
+
+ /**
+ * 获取某个权限模块在某个权限集合中是否有基本权限
+ * @param {string} authName 权限模块的名字
+ * @param {Object} authMap 权限模块集合
+ * @return {boolean} 是否可见
+ */
+ auth.permit = function(authName, authMap) {
+ return auth.get(authName, authMap) !== AuthType.NONE;
+ };
+
+ /**
+ * 已经注册的权限类型转换器
+ * @type {Object.}
+ * @private
+ */
+ var filters = {
+ rtoe: function(type) {
+ return type === AuthType.READONLY
+ ? AuthType.EDITABLE
+ : type;
+ },
+
+ rton: function(type) {
+ return type === AuthType.READONLY
+ ? AuthType.NONE
+ : type;
+ },
+
+ ntor: function(type) {
+ return type === AuthType.NONE
+ ? AuthType.READONLY
+ : type;
+ },
+
+ up: function(type) {
+ return type === AuthType.NONE
+ ? AuthType.READONLY
+ : AuthType.EDITABLE;
+ },
+
+ max: function(types) {
+ var max = AuthType.NONE;
+ u.each(types, function(type) {
+ if (type === AuthType.EDITABLE) {
+ max = type;
+ return false;
+ }
+ else if (type === AuthType.READONLY) {
+ max = type;
+ }
+ });
+ return max;
+ },
+
+ min: function(types) {
+ var min = AuthType.EDITABLE;
+ u.each(types, function(type) {
+ if (type === AuthType.NONE) {
+ min = type;
+ return false;
+ }
+ else if (type === AuthType.READONLY) {
+ min = type;
+ }
+ });
+ return min;
+ }
+ };
+
+ /**
+ * 注册新的权限类型转换器
+ * @param {string} name 转换器名字
+ * @param {function(AuthType)} filter 转换器处理函数
+ */
+ auth.registerFilter = function(name, filter) {
+ filters[name] = filter;
+ };
+
+ return auth;
+});
diff --git a/src/system/user.js b/src/system/user.js
index 4c336ac..404f4b9 100644
--- a/src/system/user.js
+++ b/src/system/user.js
@@ -7,12 +7,14 @@ define(function (require) {
var u = require('underscore');
var permission = require('er/permission');
+ var URI = require('urijs');
+ var auth = require('./auth');
/**
* 用户信息模块
*/
var exports = {
- init: function(session) {
+ init: function (session) {
if (session.visitor) {
this.visitor = session.visitor;
}
@@ -30,6 +32,46 @@ define(function (require) {
return value !== 'none';
}));
}
+ },
+
+ getVisitor: function () {
+ return this.visitor || null;
+ },
+
+ getVisitorId: function () {
+ return this.visitor && this.visitor.id;
+ },
+
+ getAder: function () {
+ return this.ader || null;
+ },
+
+ getAderId: function () {
+ return this.ader && this.ader.id
+ || URI.parseQuery(document.location.search).aderId
+ || this.visitor && this.visitor.id;
+ },
+
+ getAuthMap: function () {
+ var authMap = this.visitor && this.visitor.auth;
+
+ return authMap || null;
+ },
+
+ getAuthType: function (authId) {
+ return auth.get(authId, this.getAuthMap());
+ },
+
+ getAuth: function (authId) {
+ var authType = this.getAuthType(authId);
+ return {
+ type: authType,
+ id: authId,
+ isReadOnly: authType === auth.AuthType.READONLY,
+ isEditable: authType === auth.AuthType.EDITABLE,
+ isVisible: authType !== auth.AuthType.NONE,
+ isNone: authType === auth.AuthType.NONE
+ };
}
};
diff --git a/src/tpl.js b/src/tpl.js
index 80585ef..df369b2 100644
--- a/src/tpl.js
+++ b/src/tpl.js
@@ -45,9 +45,10 @@ define(
ActionDialog: 'ef',
SelectorTreeStrategy: './ui',
TreeRichSelector: './ui',
- tableRichSelector: './ui',
+ TableRichSelector: './ui',
RichSelector: './ui',
- ToggleButton: './ui'
+ ToggleButton: './ui',
+ AuthPanel: './ui'
};
var extensionModulePrefix = {
diff --git a/src/tpl/forbidden.tpl.html b/src/tpl/forbidden.tpl.html
new file mode 100644
index 0000000..9e53cdc
--- /dev/null
+++ b/src/tpl/forbidden.tpl.html
@@ -0,0 +1,5 @@
+
+
diff --git a/src/tpl/not_found.tpl.html b/src/tpl/not_found.tpl.html
new file mode 100644
index 0000000..94a3a7c
--- /dev/null
+++ b/src/tpl/not_found.tpl.html
@@ -0,0 +1,5 @@
+
+
diff --git a/src/ui/AuthPanel.js b/src/ui/AuthPanel.js
new file mode 100644
index 0000000..7d7c4e6
--- /dev/null
+++ b/src/ui/AuthPanel.js
@@ -0,0 +1,160 @@
+/**
+ * JN 2.0
+ * Copyright 2014 Baidu Inc. All rights reserved.
+ *
+ * @ignore
+ * @file 锦囊进行权限验证的基础容器
+ * @author Justineo(justice360@gmail.com)
+ */
+define(function (require) {
+ var u = require('underscore');
+ var lib = require('esui/lib');
+ var Panel = require('esui/Panel');
+ var auth = require('../system/auth');
+
+ /**
+ * 锦囊权限编辑器权限节点
+ *
+ * @param {Object=} options 初始化参数
+ * @extends esui/Panel
+ * @constructor
+ * @public
+ */
+ function AuthPanel(options) {
+ Panel.apply(this, arguments);
+ }
+
+ AuthPanel.prototype.type = 'AuthPanel';
+
+ /**
+ * 权限类型
+ *
+ * @type {Object}
+ */
+ var AuthType = auth.AuthType;
+
+ /**
+ * 默认属性
+ *
+ * @type {Object}
+ * @public
+ */
+ AuthPanel.defaultProperties = {
+ auth: AuthType.EDITABLE
+ };
+
+ /**
+ * @inheritDoc
+ */
+ AuthPanel.prototype.initOptions = function (options) {
+ var properties = {};
+ u.extend(properties, AuthPanel.defaultProperties, options);
+
+ this.setProperties(properties);
+ };
+
+ /**
+ * 对有auth属性的元素根据指定权限集合进行权限初始化
+ *
+ * @param {HTMLElement=} root 根元素,如果为空,则为控件主元素
+ */
+ AuthPanel.prototype.initAuth = function (root) {
+ var me = this;
+ var root = root || me.main;
+
+ // 可以通过设置disabled属性禁用的控件
+ // 见 http://www.w3.org/html/wg/drafts/html/master/disabled-elements.html
+ var disableableTagNames = [
+ 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA',
+ 'OPTGROUP', 'OPTION', 'COMMAND', 'FIELDSET'
+ ];
+
+ u.each(root.querySelectorAll('[data-auth]'), function(elem) {
+ if (elem.getAttribute('data-auth-done')) {
+ return true;
+ }
+
+ var authId = elem.getAttribute('data-auth');
+
+ // 如果元素的auth属性值包含inherit,层层查找其DOM父节点直到找到
+ // 有auth属性但不包含inherit的元素,并使用该属性值替换掉inherit
+ // 如果找不到,一定是发生错误了
+ if (/\binherit\b/.test(authId)) {
+ var parent = elem;
+ while (parent = parent.parentNode) {
+ var parentAuth = parent.getAttribute('data-auth');
+ if (parentAuth && !/\binherit\b/.test(parentAuth)) {
+ authId = authId.replace('inherit', '(' + parentAuth + ')');
+ elem.setAttribute('data-auth', authId);
+ break;
+ }
+ else if (parent === document.body) {
+ throw 'Auth error: [inherit] source not found.';
+ }
+ }
+ }
+
+ var authType = require('../system/user').getAuthType(authId);
+ var controlId = elem.getAttribute('data-ctrl-id');
+ var control = controlId ? me.viewContext.get(controlId) : null;
+ if (authType === AuthType.NONE) {
+ if (control) { // UI控件调用自己的hide方法,就不删除了
+ control.hide();
+ }
+ else {
+ elem.parentNode.removeChild(elem); // 直接删除内容
+ }
+ }
+ else if (authType === AuthType.READONLY) {
+ if (control) { // UI控件
+ if (control.setReadOnly) {
+ control.setReadOnly(true);
+ }
+ else {
+ control.disable();
+ }
+ }
+ else { // 普通DOM元素
+ var tagName = elem.tagName.toUpperCase();
+
+ // 当元素是某种文本输入框时,可以直接设置readonly
+ if (tagName === 'INPUT'
+ && (elem.type === 'text' || elem.type === 'password')
+ || tagName === 'TEXTAREA') {
+ elem.readonly = true;
+ }
+ // 否则,对于可以通过disabled属性禁用的元素,设置disabled
+ else if (u.contains(disableableTagNames, tagName)) {
+ elem.disabled = true;
+ }
+ // 其他类型的元素,劫持onclick
+ else {
+ lib.un(elem, 'click');
+ elem.onclick = function(e) {
+ lib.event.stopPropagation(e);
+ lib.event.preventDefault(e);
+ return false;
+ };
+ }
+
+ // 不管哪种情况,都给元素增加class
+ lib.addClass(elem, 'auth-disabled');
+ }
+ }
+ elem.setAttribute('data-auth-done', 'auth-done');
+ });
+ };
+
+ /**
+ * @inheritDoc
+ */
+ AuthPanel.prototype.repaint = function () {
+ Panel.prototype.repaint.call(this);
+
+ this.initAuth();
+ };
+
+ lib.inherits(AuthPanel, Panel);
+ require('esui').register(AuthPanel);
+ return AuthPanel;
+});
diff --git a/src/ui/RichSelector.js b/src/ui/RichSelector.js
index f9a88a1..a6da910 100644
--- a/src/ui/RichSelector.js
+++ b/src/ui/RichSelector.js
@@ -88,7 +88,7 @@ define(
}
lib.extend(properties, options);
- properties.width = Math.max(200, properties.width);
+ // properties.width = Math.max(200, properties.width);
this.setProperties(properties);
};
@@ -465,10 +465,10 @@ define(
contentHeight -= 30;
}
-
+
content.style.height = contentHeight + 'px';
}
-
+
};
RichSelector.prototype.adaptData = function () {};
diff --git a/src/ui/SelectorTreeStrategy.js b/src/ui/SelectorTreeStrategy.js
index da3d49d..46d2781 100644
--- a/src/ui/SelectorTreeStrategy.js
+++ b/src/ui/SelectorTreeStrategy.js
@@ -74,11 +74,6 @@ define(
canSelect = false;
}
}
- else {
- if (!isLeafNode) {
- canSelect = false;
- }
- }
if (canSelect) {
this.selectNode(e.node.id);
diff --git a/src/ui/TreeRichSelector.js b/src/ui/TreeRichSelector.js
index 859615c..bfa3e31 100644
--- a/src/ui/TreeRichSelector.js
+++ b/src/ui/TreeRichSelector.js
@@ -100,6 +100,11 @@ define(
// 其中包含父节点信息,以及节点选择状态
var indexData = {};
if (this.allData && this.allData.children) {
+ indexData[this.allData.id] = {
+ parentId: null,
+ node: this.allData,
+ isSelected: false
+ };
walkTree(
this.allData,
this.allData.children,
@@ -121,8 +126,8 @@ define(
*/
TreeRichSelector.prototype.refreshContent = function () {
var treeData = this.isQuery() ? this.queriedData : this.allData;
- if (!treeData
- || !treeData.children
+ if (!treeData
+ || !treeData.children
|| !treeData.children.length) {
this.addState('empty');
}
@@ -270,7 +275,7 @@ define(
/**
* 撤销选择当前项
- * @param {ui.TreeRichSelector} control 类实例
+ * @param {ui.TreeRichSelector} control 类实例
* @ignore
*/
function unselectCurrent(control) {
diff --git a/src/ui/css/TreeRichSelector.less b/src/ui/css/TreeRichSelector.less
index f8ec7b7..e8a3470 100644
--- a/src/ui/css/TreeRichSelector.less
+++ b/src/ui/css/TreeRichSelector.less
@@ -4,7 +4,7 @@
.ui-tree-content-wrapper {
border-left: medium none;
padding: 0 5px;
- line-height: 35px;
+ line-height: 24px;
cursor: pointer;
.clearfix();
&:hover {
diff --git a/src/ui/css/wizard.less b/src/ui/css/wizard.less
index 9a9af75..9881fde 100644
--- a/src/ui/css/wizard.less
+++ b/src/ui/css/wizard.less
@@ -1,95 +1,93 @@
-.skin-default-wizard {
- @commonColor: #E7F0FA;
- @doneColor: #E7F0FA;
- @activeColor: #B6D2F1;
-
- @arrowHeight: 24px;
-
- @arrowGap: 4px;
- .ui-wizard-node {
- color: #999999;
- font-size: 12px;
- font-weight: normal;
- height: @arrowHeight;
- line-height: @arrowHeight;
- padding: 0 15px 0 0;
- *padding: 0;
- text-align: center;
- position: relative;
- background: #fff;
-
- span {
- margin-right: @arrowGap;
- color: #999999;
- height: @arrowHeight;
- float: left;
- position: relative;
- text-align: center;
- width: auto;
- padding: 0 10px;
- background-color: @commonColor;
-
- &:after {
- border-bottom: (@arrowHeight/2) solid transparent;
- border-left: (@arrowHeight/2) solid @commonColor;
- border-top: (@arrowHeight/2) solid transparent;
- content: " ";
- height: 0;
- position: absolute;
- right: -(@arrowHeight/2);
- top: 0;
- width: 0;
- }
- &:before {
- border-bottom: (@arrowHeight/2) solid @commonColor;
- border-left: (@arrowHeight/2) solid transparent;
- border-top: (@arrowHeight/2) solid @commonColor;
- content: "";
- height: 0;
- left: -(@arrowHeight/2);
- position: absolute;
- top: 0;
- width: 0;
- }
- }
- }
-
- .ui-wizard-node-done {
- span {
- background-color: @doneColor;
- color: #999999;
-
- &:after {
- border-left: (@arrowHeight/2) solid @doneColor;
- }
- &:before {
- border-bottom: (@arrowHeight/2) solid @doneColor;
- border-top: (@arrowHeight/2) solid @doneColor;
- }
- }
- }
-
- .ui-wizard-node-last {
- background: none;
- }
-
- .ui-wizard-node-active-prev {
- }
-
- .ui-wizard-node-active {
- span {
- background-color: @activeColor;
- color: #000;
- &:after {
- border-left: (@arrowHeight/2) solid @activeColor;
- }
- &:before {
- border-bottom: (@arrowHeight/2) solid @activeColor;
- border-top: (@arrowHeight/2) solid @activeColor;
- }
- }
-
- }
-
-
+.skin-default-wizard {
+ @commonColor: #E7F0FA;
+ @doneColor: #E7F0FA;
+ @activeColor: #B6D2F1;
+
+ @arrowHeight: 24px;
+
+ @arrowGap: 4px;
+ .ui-wizard-node {
+ color: #999999;
+ font-size: 12px;
+ font-weight: normal;
+ height: @arrowHeight;
+ line-height: @arrowHeight;
+ padding: 0 15px 0 0;
+ *padding: 0;
+ text-align: center;
+ position: relative;
+ background: #fff;
+
+ span {
+ margin-right: @arrowGap;
+ color: #999999;
+ height: @arrowHeight;
+ float: left;
+ position: relative;
+ text-align: center;
+ width: auto;
+ padding: 0 10px;
+ background-color: @commonColor;
+
+ &:after {
+ border-bottom: (@arrowHeight/2) solid transparent;
+ border-left: (@arrowHeight/2) solid @commonColor;
+ border-top: (@arrowHeight/2) solid transparent;
+ content: " ";
+ height: 0;
+ position: absolute;
+ right: -(@arrowHeight/2);
+ top: 0;
+ width: 0;
+ }
+ &:before {
+ border-bottom: (@arrowHeight/2) solid @commonColor;
+ border-left: (@arrowHeight/2) solid transparent;
+ border-top: (@arrowHeight/2) solid @commonColor;
+ content: "";
+ height: 0;
+ left: -(@arrowHeight/2);
+ position: absolute;
+ top: 0;
+ width: 0;
+ }
+ }
+ }
+
+ .ui-wizard-node-done {
+ span {
+ background-color: @doneColor;
+ color: #999999;
+
+ &:after {
+ border-left: (@arrowHeight/2) solid @doneColor;
+ }
+ &:before {
+ border-bottom: (@arrowHeight/2) solid @doneColor;
+ border-top: (@arrowHeight/2) solid @doneColor;
+ }
+ }
+ }
+
+ .ui-wizard-node-last {
+ background: none;
+ }
+
+ .ui-wizard-node-active-prev {
+ }
+
+ .ui-wizard-node-active {
+ span {
+ background-color: @activeColor;
+ color: #000;
+ &:after {
+ border-left: (@arrowHeight/2) solid @activeColor;
+ }
+ &:before {
+ border-bottom: (@arrowHeight/2) solid @activeColor;
+ border-top: (@arrowHeight/2) solid @activeColor;
+ }
+ }
+
+ }
}
\ No newline at end of file
diff --git a/src/ui/extension/TableTip.js b/src/ui/extension/TableTip.js
index f330daf..947fdfb 100644
--- a/src/ui/extension/TableTip.js
+++ b/src/ui/extension/TableTip.js
@@ -77,8 +77,8 @@ define(
showMode: 'over',
delayTime: 200,
positionOpt: {
- bottom: 'bottom',
- left: 'left'
+ top: 'top',
+ right: 'left'
}
};
tip.attachTo(options);
@@ -94,7 +94,8 @@ define(
return;
}
- var elements = document.querySelectorAll('.table-operation');
+ // 根据Table主元素来限定范围
+ var elements = this.target.main.querySelectorAll('.table-operation');
u.chain(elements)
.groupBy(getTipType)
@@ -109,7 +110,7 @@ define(
TableTip.prototype.activate = function () {
Extension.prototype.activate.apply(this, arguments);
- this.target.on('afterrender', this.initTips, this);
+ this.target.on('bodyChange', this.initTips, this);
};
/**
@@ -118,7 +119,7 @@ define(
* @override
*/
TableTip.prototype.inactivate = function () {
- this.target.un('afterrender', this.initTips, this);
+ this.target.un('bodyChange', this.initTips, this);
Extension.prototype.inactivate.apply(this, arguments);
};
@@ -128,4 +129,4 @@ define(
return TableTip;
}
-);
+);
diff --git a/src/ui/loading.js b/src/ui/loading.js
index 7c0a6e7..74b426d 100644
--- a/src/ui/loading.js
+++ b/src/ui/loading.js
@@ -38,7 +38,9 @@ define(function (require) {
properties = u.extend(properties, options);
globalLoading.setProperties(properties);
globalLoading.show();
+ globalLoading.main.style.zIndex = parseInt(globalLoading.main.style.zIndex, 10) + 1;
loadingCount++;
+ loadingTimer && clearTimeout(loadingTimer);
return globalLoading;
}
function hideLoading() {
diff --git a/src/ui/navigator.js b/src/ui/navigator.js
index 60f0ff0..ce22b01 100644
--- a/src/ui/navigator.js
+++ b/src/ui/navigator.js
@@ -1,6 +1,6 @@
/**
* Copyright 2014 Baidu Inc. All rights reserved.
- *
+ *
* @file Navigator
* @author chestnutchen(chenli11@baidu.com)
* @date $DATE$
@@ -49,8 +49,7 @@ define(function (require) {
* 这里的逻辑`exclude`优先级比`include`高,但是也不要两个都配同个规则吧。。
* @cfg {Array} [config.tabs] 子导航,结构和一级导航config中每一项保持一致
* @cfg {string} [config.auth] 与er.permission对应的权限控制
- * TODO:二级导航暂未支持权限
- *
+ *
* @sample:
* nav: {
* navId: 'nav',
@@ -112,7 +111,7 @@ define(function (require) {
*/
function toggleNav(type) {
this.main.style.cssText = 'display:' + type + ';';
- u.some(this.subNavs, function (subNav, index) {
+ u.some(this.subNavs, function (subNav) {
if (u.isObject(subNav)) {
if (lib.hasClass(subNav.nav, 'nav-sub-item-current')) {
subNav.nav.cssText = 'display:' + type + ';';
@@ -172,10 +171,10 @@ define(function (require) {
/**
* 高亮当前导航元素
*
- * @navItems {object} 缓存导航的集合,传入来记录当前高亮的索引
- * @element {object} 要高亮的元素
- * @index {string} 要高亮的导航索引
- * @className {string} 通过添加和移除current className来更换高亮样式
+ * @param {object} navItems 缓存导航的集合,传入来记录当前高亮的索引
+ * @param {object} element 要高亮的元素
+ * @param {string} index 要高亮的导航索引
+ * @param {string} className 通过添加和移除current className来更换高亮样式
*/
function activateNavElement(navItems, element, index, className) {
if (!u.isNumber(navItems.activeIndex)) {
@@ -192,9 +191,9 @@ define(function (require) {
/**
* 展示或隐藏二级导航
*
- * @navItems {object} 缓存导航的集合,传入来记录当前高亮的索引
- * @element {object} 要高亮的元素,不传表示隐藏所有
- * @index {string} 要高亮的导航索引
+ * @param {object} navItems 缓存导航的集合,传入来记录当前高亮的索引
+ * @param {object} element 要展示的子tab,不传表示隐藏所有
+ * @param {string} index 要展示的导航索引
*/
function toggleSubNav(navItems, element, index) {
var className = 'nav-sub-current';
@@ -218,22 +217,62 @@ define(function (require) {
/**
* 创建nav元素
- *
- * @config {object} globalconfig
- * @navItems {object} 缓存nav元素的对象
- * @nav {object} nav父元素
- * @isSub {string} '' 或 'sub-',主导航或二级子导航
+ *
+ * @param {object} config globalconfig
+ * @param {object} navItems 缓存nav元素的对象
+ * @param {object} nav nav父元素
+ * @param {string} isSub {''|'sub-'},主导航或二级子导航
*/
function createNavElements(config, navItems, nav, isSub) {
var isSub = isSub || '';
u.each(config, function (item, index) {
if (!item.auth || permission.isAllow(item.auth)) {
- var url = item.externalUrl || ('#' + item.url);
+ var internalUrl = fixErUrl(item.url || '');
+ var url = item.externalUrl || internalUrl;
+ var target = (item.externalUrl ? ' target="_blank"' : '');
var element = document.createElement('li');
var separate = '';
+ // 处理父tab和子tab权限耦合的部分
+ if (item.tabs) {
+ // 看看默认要跳转的地址所在的子tab有没有权限,没有的话得找备胎
+ var hasDefaultTabAuth = u.every(item.tabs, function (subItem) {
+ if (isActive(item.url.replace(/^#/, ''), subItem)) {
+ if (!subItem.auth || permission.isAllow(subItem.auth)) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ });
+
+ // 如果没有权限就得找到第一个有权限的子tab,把父tab跳转链接改成这个
+ if (!hasDefaultTabAuth) {
+ // 做最坏的打算,所有不外跳的子tab都没权限,那这个父tab只能直接往外跳了
+ var externalUrlBackUp = '';
+ var isInternalUrlFound = u.some(item.tabs, function (subItem) {
+ if (!subItem.auth || permission.isAllow(subItem.auth)) {
+ if (subItem.url) {
+ url = fixErUrl(subItem.url);
+ return true;
+ }
+ else if (subItem.externalUrl && !externalUrlBackUp) {
+ externalUrlBackUp = subItem.externalUrl;
+ }
+ }
+ });
+
+ if (!isInternalUrlFound) {
+ // 如果找不到内跳和外跳的备胎
+ // 就说明所有子tab都没权限,那就用原来父tab配的url就可以了
+ url = externalUrlBackUp || url;
+ }
+ }
+ }
+
element.className = 'nav-' + isSub + 'item';
- element.innerHTML = ''
+ element.innerHTML = ''
+ '' + u.escape(item.text) + ''
+ '';
@@ -253,11 +292,11 @@ define(function (require) {
/**
* 创建二级nav元素,如果存在,直接展示
*
- * @config {object} config.tabs,某个主导航的二级导航配置
- * @navItems {object} 缓存一级导航的对象,用来计算位置
- * @subNavs {object} 缓存二级导航的对象
- * @parentNav {object} 一级导航容器
- * @index {string} 一级导航navElement的index
+ * @param {object} config config.tabs,某个主导航的二级导航配置
+ * @param {object} navItems 缓存一级导航的对象,用来计算位置
+ * @param {object} subNavs 缓存二级导航的对象
+ * @param {object} main 一级导航容器
+ * @param {string} index 一级导航navElement的index
*/
function createOrShowSubNav(config, navItems, subNavs, main, index) {
var ul = subNavs[index].nav;
@@ -294,8 +333,10 @@ define(function (require) {
/**
* 验证url是否匹配
*
- * @url {string} 当前页面的er path
- * @patterns {RegExp | string} 验证的表达式或path
+ * @param {string} url 某个path
+ * @param {RegExp | string} patterns 验证的表达式或path
+ *
+ * @return {boolean}
*/
function testUrlIn(url, patterns) {
return u.some(patterns, function(pattern) {
@@ -308,11 +349,26 @@ define(function (require) {
});
}
+ /**
+ * 将传入的path格式化为er path
+ *
+ * @param {string} url 某个path
+ * @return {string}
+ */
+ function fixErUrl(url) {
+ if (!url) {
+ return '';
+ }
+ return (url.indexOf('#') === 0 ? url : ('#' + url));
+ }
+
/**
* 验证url是否命中某个导航元素config的规则
*
- * @url {string} 当前页面的er path
- * @patterns {RegExp | string} 验证的表达式或path
+ * @param {string} url 某个path,不带#
+ * @param {RegExp | string} item 一个tab的配置信息
+ *
+ * @return {boolean}
*/
function isActive(url, item) {
return !testUrlIn(url, item.exclude || []) && testUrlIn(url, item.include || []);
@@ -325,7 +381,7 @@ define(function (require) {
};
}
- var commonNavigator = new Navigator();
+ var commonNavigator = new Navigator();
return {
init: u.bind(commonNavigator.init, commonNavigator),
diff --git a/src/util.js b/src/util.js
index 117d0d3..1f1d2d3 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,6 +1,6 @@
/**
* Copyright 2014 Baidu Inc. All rights reserved.
- *
+ *
* @ignore
* @file BAT工具模块
* @author Justineo
@@ -33,7 +33,7 @@ define(function (require) {
// 过滤掉不需要生成的URL
isRequester = isRequester || function (path) {
- // 默认跳过以`/download`和`/upload`结尾的路径
+ // 默认跳过以`/download`和`/upload`结尾的路径
return !/\/(?:up|down)load$/.test(path);
};
@@ -253,8 +253,8 @@ define(function (require) {
'data-command': command.type
};
- if (u.typeOf(command.args) === 'String') {
- attrs['data-command-args'] = command.args;
+ if (typeof command.args !== 'undefined') {
+ attrs['data-command-args'] = '' + command.args;
}
if (u.typeOf(command.extra) === 'Object') {
@@ -282,19 +282,19 @@ define(function (require) {
*/
util.genListOperations = function (operations, config) {
config = config || {};
- var html = u.map(
- operations,
- function (operation) {
+ var html = u.chain(operations)
+ .map(function (operation) {
if (operation.url) {
return util.genListLink(operation);
}
else {
return util.genListCommand(operation);
}
- }
- );
+ })
+ .compact()
+ .value();
- return html.join(config.separator || '|');
+ return html.join(config.separator || '');
};
/**