(function(){ // jquery find performance bad, use querySelectorAll instead if($.browser.msie && $.browser.version <= 8){ $.fn.findAll = function(selector){ var list = []; this.each(function() { var queryList = this.querySelectorAll(selector); list.merge(queryList); }); return $(list); }; }else{ $.fn.findAll = $.fn.find; }
$('<div id="loading-block" style="display: none;"></div><div id="loading" style="display: none;"></div>').appendTo($(document.body));
})(); // file ng.config.js (function(angular){ // ajax filter in $HttpBackendProvider (not $HttpProvider - responseInterceptors) if(!angular.completeRequestInterceptor){ angular.completeRequestInterceptor = function(url, status, response, headersString){ window.console.log('Request filter status : ' + status);
var headers = Consts.requestGetHeader(headersString);
if('200' != status){
window.console.log('Error when request filter : not 200 response!');
if(Consts.requestFilterStatus && !Consts.requestFilterStatus(status, response, headers)){
return false;
}
}
if('200' == status && response){
// save token
var tokenHeader = headers[Consts.tokenHeaderKey];
if(tokenHeader){
TokenLocal.setDataByUrl(Consts.getPathWithoutApp(url), tokenHeader);
}
var r;
if(angular.isString(response)){
// need to skip ng-include
var firstChar = response.substring(0, 1);
// json, if not json it may be html code or jsonp javascript expression, just return true and continue
if(firstChar == '{' || firstChar == '['){
try{
r = JSON.parse(response);
}catch(e){
window.console.log('Error when request filter json parse : ' + response);
return false;
}
}else if(response.contains('script_umlogin')){
// um relogin TODO
PortalAuth.login();
return false;
}
}else if(angular.isObject(response)){
r = response;
}
if(Consts.requestFilter){
return Consts.requestFilter(r);
}
}
return true;
};
} // /completeRequestInterceptor
if(!angular.preSendRequestInterceptor){
angular.preSendRequestInterceptor = function(config, reqHeaders){
// check session
if(Consts.sendRequestFilter && !Consts.sendRequestFilter(config, reqHeaders)){
return false;
}
// add token
var url = config.url;
var token = TokenLocal.getToken(Consts.getPathWithoutApp(url),
config.data || config.params);
if(token){
reqHeaders[Consts.tokenHeaderKey] = token;
}else{
window.console.log('No token get : ' + url);
}
return true;
};
} // /preSendRequestInterceptor
var moduleName = 'ng.config';
window.console.log('Begin init module ' + moduleName);
if(angular.isModuleExists && angular.isModuleExists(moduleName)){
window.console.log('Module already exists!' + moduleName);
return;
}
// define this angular module as a simple javascript object, add configurations
var conf = {};
// ... it sucks when using absolute url path
// copy properties from Consts so that you only need to change one file
var keys = ['appname', 'contextPre', 'context',
'logLevel', 'logQueueFlushSize', 'logPostMsg', 'logServerUrl',
'useToken', 'tokenHeaderKey'];
var i = 0, len = keys.length;
for(; i < len; i++){
var key = keys[i];
conf[key] = Consts[key];
}
// add servlet container context path to url
conf.url = function(url){
return this.appname + url;
};
conf.validClass = 'ng-valid';
conf.invalidClass = 'ng-invalid';
conf.dirtyClass = 'ng-dirty';
conf.pristineClass = 'ng-pristine';
// jquery / plugins parameters
// tipsStyle = 'poshytip' => change tips style
conf.tipsStyle = 'default';
conf.tipsXoffset = -12;
conf.tipsYoffset = 8;
conf.tipOptions = {
showOn: 'focus',
alignTo: 'target',
alignX: 'left',
alignY: 'center',
offsetX: 5
};
conf.date = {
duration: 'fast',
dateFormat: 'yy-mm-dd',
changeMonth: true,
changeYear: true,
showMonthAfterYear: false,
yearSuffix: ''};
conf.autocomplete = {minChars: 3, maxItemsToShow: 10, finishOnBlur: true};
conf.defaultPagiBtnClass = 'btn';
conf.defaultPagiCurrentBtnClass = 'btn btn-blue';
// dialog
conf.dialogDraggable = {
opacity: 0.6,
containment: 'document'
};
conf.contextMenu = {
contextMenuClass: 'contextMenuPlugin',
gutterLineClass: 'gutterLine',
headerClass: 'header',
seperatorClass: 'divider',
title: ''
};
conf.dropdownOptions = {
valueField: 'code',
labelField: 'label',
widthDiff: 16,
widthMultipleInput: 150,
zIndex: 1010
};
// user can overwrite
for(var keyConsts in Consts){
if(keyConsts.startsWith('conf_')){
conf[keyConsts.substr('conf_'.length)] = Consts[keyConsts];
}
}
var md = angular.module(moduleName, []).config(function($sceProvider){
$sceProvider.enabled(false);
});
md.value('ng.config', conf);
})(angular);
// file ng.filter.js (function(angular){ var moduleName = 'ng.filter'; window.console.log('Begin init module ' + moduleName); if(angular.isModuleExists && angular.isModuleExists(moduleName)){ window.console.log('Module already exists!' + moduleName); return; }
var md = angular.module(moduleName, []).config(function($sceProvider){
$sceProvider.enabled(false);
});
// filter function will be called twice, refer :
// http://stackoverflow.com/questions/11676901/is-this-normal-for-angularjs-filtering
// use ngClassEven/Odd instead
md.filter('trBg', function(){
return function(index, oddStyleSuf) {
var pre = index % 2 == 0 ? 'odd' : 'even';
if(oddStyleSuf)
pre += oddStyleSuf;
return pre;
};
});
})(angular);
// file ng.service.js (function(angular){ var moduleName = 'ng.service'; window.console.log('Begin init module ' + moduleName); if(angular.isModuleExists && angular.isModuleExists(moduleName)){ window.console.log('Module already exists!' + moduleName); return; }
// change plugin settings
if($.dialog){
$.dialog.setting.path = Consts.context + 'images/lhgdialog/';
$.dialog.setting.zIndex = 900;
$.dialog.setting.padding = '2px';
}
var md = angular.module(moduleName, ['ng.config']).config(function($sceProvider){
$sceProvider.enabled(false);
});
md.factory('safeApply', function($rootScope){
return function(scope, fn){
var phase = scope.$root.$$phase;
if(phase == '$apply' || phase == '$digest'){
if(fn && (typeof (fn) === 'function')){
fn();
}
}else{
scope.$apply(fn);
}
}
});
md.factory('uiPortalUtils', ['$window', 'uiLog', function(win, log){
return {
openTab: function(tabId, url, title, opts){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
// open new window if not in portal
if(opts && opts.openForce){
win.open(url, tabId);
}
return;
}else{
win.parent.PortalTab.open(tabId, encodeURI(url), title, opts);
}
},
openTabByPost: function(tabId, url, title, opts, data){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
// open new window if not in portal
if(opts && opts.openForce){
win.open(url, tabId);
}
return;
}else{
win.parent.PortalTab.openByPost(tabId, url, title, opts, data);
}
},
removeCurrent: function(donotShowTip){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
win.close();
}else{
win.parent.PortalTab.removeCurrent(donotShowTip);
}
},
removeAll: function(){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
win.close();
}else{
win.parent.PortalTab.removeAll();
}
},
removeTabByTabId: function(tabId, donotShowTip){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
}else{
win.parent.PortalTab.removeTabByTabId(tabId, donotShowTip);
}
},
openNav: function(){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
}else{
win.parent.PortalTab.openNav();
}
},
collapseNav: function(){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
}else{
win.parent.PortalTab.collapseNav();
}
},
getIframe: function(id){
if(!win.parent || !win.parent.PortalTab){
log.w('No PortalTab defination!');
}else{
return win.parent.PortalTab.getIframe(id);
}
}
};
}]);
// log
md.factory('uiLog', ['ng.config', '$window', function(conf, win){
var logQueue = [];
var levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
return {
// format to string
getMsg: function(msg, level){
if(!angular.isString(msg))
msg = JSON.stringify(msg);
level = level || 'INFO';
var dat = new Date().format();
return '[' + dat + '][' + level + ']' + msg;
},
// send to server
postMsg: function(){
if(!conf.logPostMsg)
return;
var url = conf.logServerUrl;
var _this = this;
$.ajax({
url: url,
type: 'POST',
async: false,
data: 'msg=' + JSON.stringify(logQueue),
error: function(xhr){
_this.e('POST log failed : ' + url);
},
success: function(){
_this.i('POST log ok : ' + url);
logQueue.clear();
}
});
},
isLevelEnabled: function(level){
return levels.indexOf(conf.logLevel) <= levels.indexOf(level);
},
log: function(msg, level){
if(win.console && win.console.log && this.isLevelEnabled(level)){
win.console.log(this.getMsg(msg, level));
}
},
d: function(msg){
this.log(msg, 'DEBUG');
},
i: function(msg){
this.log(msg, 'INFO');
},
w: function(msg){
this.log(msg, 'WARN');
},
e: function(msg){
this.log(msg, 'ERROR');
},
audit: function(msg){
var str = this.getMsg(msg);
if(win.console && win.console.log){
win.console.log(str);
}
logQueue.push(str);
if(logQueue.length > conf.logQueueFlushSize){
this.postMsg();
}
}
};
}]);
// local storage adaptor
md.factory('uiStorage', function(){
return Storage.newInstance();
});
// use jquery ajax when do synchronized requests
// pre filter after filter
md.factory('jqueryAjax', ['$rootScope', 'safeApply', 'uiLog', function($rootScope, safeApply, uiLog){
var isSuccess = function(status){
return 200 <= status && status < 300;
};
var jqueryAjax = function(config){
var method = config.method ? angular.uppercase(config.method) : 'GET';
var reqData = config.params || config.data;
var reqHeaders = config.headers || {};
// token
var preFilterResult = true;
if(angular.preSendRequestInterceptor){
preFilterResult = angular.preSendRequestInterceptor(config, reqHeaders);
}
if(!preFilterResult){
// just return
return {
success: function(fn){
// skip
return this;
},
error: function(fn){
fn(data, -1);
safeApply($rootScope);
return this;
}
};
}
var data;
var headersString;
var status = 200;
var props = {
url: config.url,
type: method,
data: reqData,
async: false,
success: function(txt, status, jqXHR){
headersString = jqXHR.getAllResponseHeaders();
try{
data = angular.isObject(txt) ? txt : JSON.parse(txt);
}catch(e){
data = txt;
}
},
error: function(jqXHR, error, msg){
status = jqXHR.status;
headersString = jqXHR.getAllResponseHeaders();
data = jqXHR.responseText || msg;
}
};
if(reqHeaders.contentType)
props.contentType = reqHeaders.contentType;
if('post' === method.toLowerCase()){
props.contentType = 'application/json';
props.data = JSON.stringify(props.data);
}
$.ajax(props);
var filterResult = true;
if(angular.completeRequestInterceptor){
filterResult = angular.completeRequestInterceptor(config.url, status, data, headersString);
}
return {
success: function(fn){
if(isSuccess(status)){
fn(data, status);
safeApply($rootScope);
}
return this;
},
error: function(fn){
if(isSuccess(status))
return;
fn(data, status);
safeApply($rootScope);
return this;
}
};
};
jqueryAjax.get = function(url, params){
return jqueryAjax({method: 'get', url: url, params: params});
};
jqueryAjax.post = function(url, data){
return jqueryAjax({method: 'post', url: url, data: JSON.stringify(data),
headers: {contentType: 'application/json'}});
};
return jqueryAjax;
}]);
// pagination model helper
md.factory('uiPager', ['ng.config', 'uiLog', function(conf, log){
return {
gen: function(pager, opts){
var pagi = {};
if(!pager){
pagi.totalPageLl = [];
pagi.totalPage = 0;
pagi.totalCount = 0;
pagi.pageNum = 0;
pagi.pageSize = 0;
pagi.style = {};
pagi.style.btnClass = conf.defaultPagiBtnClass;
// first previous next last / buttons disabled
pagi.ctrl = {};
pagi.ctrl.isChoosePageDisabled = true;
pagi.ctrl.isFirstPageDisabled = true;
pagi.ctrl.isPrevPageDisabled = true;
pagi.ctrl.isNextPageDisabled = true;
pagi.ctrl.isLastPageDisabled = true;
return pagi;
}
// current page
pagi.pageNum = pager.pageNum || 0;
// number per page
pagi.pageSize = pager.pageSize || 10;
pagi.totalCount = pager.totalCount || 0;
// no records
if(!pagi.totalCount)
pagi.pageNum = 0;
pagi.totalPage = this.getTotalPage(pagi.totalCount, pagi.pageSize);
pagi.totalPageLl = this.getTotalPageLl(pagi.totalPage, pagi.pageNum, opts);
pagi.targetPageChoosed = _.find(pagi.totalPageLl, function(it){
return it.pageNum == pagi.pageNum;
});
pagi.style = {};
pagi.style.btnClass = conf.defaultPagiBtnClass;
// first previous next last
pagi.ctrl = {};
pagi.ctrl.isFirstPageDisabled = pagi.totalCount == 0 || pagi.pageNum == 1;
pagi.ctrl.isPrevPageDisabled = pagi.pageNum <= 1;
pagi.ctrl.isNextPageDisabled = pagi.pageNum == pagi.totalPage;
pagi.ctrl.isLastPageDisabled = pagi.totalPage <= 1 || pagi.pageNum == pagi.totalPage;
pagi.ctrl.isChoosePageDisabled = pagi.totalPage <= 1;
return pagi;
},
refresh: function(pagi){
pagi.totalPage = this.getTotalPage(pagi.totalCount, pagi.pageSize);
pagi.totalPageLl = this.getTotalPageLl(pagi.totalPage, pagi.pageNum);
},
getTotalPageLl: function(totalPage, pageNum, opts){
return _.map(_.range(1, totalPage + 1), function(it){
var pagiBtnClass = conf.defaultPagiBtnClass;
if(opts && opts.defaultBtnClass)
pagiBtnClass = opts.defaultBtnClass;
// model with button style
// use ng-repeat to render different buttons
var one = {};
one.btnClass = pagiBtnClass;
one.pageNum = it;
if(pageNum == it){
one.btnClass = conf.defaultPagiCurrentBtnClass;
if(opts && opts.currentBtnClass)
one.btnClass = opts.currentBtnClass;
}
return one;
});
},
getTotalPage: function(totalCount, pageSize){
var r = totalCount % pageSize;
var r2 = totalCount / pageSize;
var result = r == 0 ? r2 : r2 + 1;
return Math.floor(result);
}
};
}]);
// filter ajax request parameters
md.factory('uiRequest', ['$http', 'uiLog', 'uiTips', 'uiStorage', function($http, log, uiTips, uiStorage){
return {
// parameters flatten
// eg. {list: [{name: 'kerry']} -> {'list[0].name': 'kerry'}
pairs: function(obj, name){
if(obj == null)
return null;
if(!name)
name = '';
var sub, pairs = {};
var type = Object.prototype.toString.call(obj);
if(type == "[object Array]"){
var i = 0, len = obj.length;
for(; i < len; i++){
var item = obj[i];
sub = this.pairs(item, name + '[' + i + ']');
_.extend(pairs, sub);
}
return pairs;
}else if(type == "[object Object]"){
for(key in obj){
var subName = name == '' ? key : name + '.' + key;
sub = this.pairs(obj[key], subName);
_.extend(pairs, sub);
}
return pairs;
}else{
pairs[name] = obj;
return pairs;
}
},
// url query parameters
params: function(url){
return Utils.params(url);
},
genKey: function(data){
var str = '';
if(!data)
return str;
var sp = '_';
for(key in data){
str += key + sp + data[key] + sp;
}
return str;
},
req: function(url, params, msg, callback, method){
method = method || 'get';
var key = url + '__' + this.genKey(params);
var saved = uiStorage.get(key);
if(saved){
callback(saved);
return;
}
uiTips.loadingFn(function(){
$http[method](Consts.getAppPath(url), params).success(function(data){
uiStorage.set(key, data);
callback(data);
uiTips.unloading();
});
}, msg);
}
};
}]);
// tips
md.factory('uiTips', ['ng.config', 'uiLog', function(conf, log){
return {
filterClass: function(elm, invalid){
if(invalid){
elm.removeClass(conf.validClass).removeClass(conf.pristineClass).addClass(conf.invalidClass).addClass(conf.dirtyClass);
}else{
elm.removeClass(conf.invalidClass).addClass(conf.validClass);
}
},
on: function(el, msg, notHoverShow){
log.d('tips on...');
// check if already executed uiTips.on
var lastTip = el.data('last-tip');
if(lastTip && lastTip === msg){
return;
}
el.data('last-tip', msg);
// select2 rebuild dom
var select2Container = el.prev('.select2-container:first');
if(select2Container.length){
this.filterClass(el, true);
el = select2Container;
}
// dropdown rebuild dom
var dropdownContainer = el.closest('.pui-dropdown');
if(dropdownContainer.length){
this.filterClass(el, true);
el = dropdownContainer;
}
var dropdownMultipleContainer = el.prev('.pui-autocomplete-multiple');
if(dropdownMultipleContainer.length){
this.filterClass(el, true);
el = dropdownMultipleContainer;
}
this.filterClass(el, true);
var id = el.attr('ui-valid-id');
if(!id){
id = Math.guid();
el.attr('ui-valid-id', id);
}
if(id.contains('.')){
id = id.replace(/\./g, '_');
}
if(notHoverShow){
if('poshytip' == conf.tipsStyle){
el.poshytip('destroy');
}else{
$("#vtip_" + id).remove();
el.unbind('mouseenter mouseleave');
}
return;
}
var genTips = function(){
// already exists, change css style
var _tip = $("#vtip_" + id);
if(_tip.length){
_tip.html('<img class="vtip_arrow " src="' + conf.context + 'images/vtip_arrow.png" />' + msg)
.css({"display": "none"});
}else{
// generate new and append
var html = '<p id="vtip_' + id + '" class="vtip"><img class="vtip_arrow" src="' + conf.context + 'images/vtip_arrow.png" />' + msg + '</p>';
$(html).css({"display": "none"}).appendTo($('body'));
}
};
var bindTipsShow = function(){
genTips();
el.unbind('mouseenter mouseleave').bind('mouseenter', _.throttle(function(e){
var _tip = $("#vtip_" + id);
_tip.css({left: (e.pageX + conf.tipsXoffset) + 'px', top: (e.pageY + conf.tipsYoffset) + 'px'});
if(_tip.is(':hidden'))
_tip.show();
}, 100)).bind('mouseleave', function(){
$("#vtip_" + id).hide();
});
};
if('poshytip' == conf.tipsStyle){
// destroy first
el.poshytip('destroy');
el.poshytip({content: msg});
}else{
bindTipsShow();
}
},
off: function(el){
el.data('last-tip', '');
// select2 rebuild dom
var select2Container = el.prev('.select2-container:first');
if(select2Container.length){
this.filterClass(el);
el = select2Container;
}
// dropdown rebuild dom
var dropdownContainer = el.closest('.pui-dropdown');
if(dropdownContainer.length){
this.filterClass(el, true);
el = dropdownContainer;
}
var dropdownMultipleContainer = el.prev('.pui-autocomplete-multiple');
if(dropdownMultipleContainer.length){
this.filterClass(el, true);
el = dropdownMultipleContainer;
}
this.filterClass(el);
var id = el.attr('ui-valid-id');
if(!id){
log.w('No ui-valid-id when call tips off!');
return;
}
if(id.contains('.')){
id = id.replace(/\./g, '_');
}
if('poshytip' == conf.tipsStyle){
el.poshytip('destroy');
}else{
$("#vtip_" + id).remove();
el.unbind('mouseenter mouseleave');
}
},
// remove all tips div in a speicfic jQuery context
// TIPS other tips style TODO
offInContext: function(_context){
if(!_context || !_context.length){
return;
}
_context.findAll('[ui-valid]').each(function(){
var validId = $(this).attr('ui-valid-id');
if(validId){
$('#vtip_' + validId).hide();
}
});
},
// *** loading block
unloading: function(){
$('#loading, #loading-block').hide();
},
loadingFn: function(fn, msg, sync){
$('#loading').text(msg);
$('#loading, #loading-block').show();
if(sync){
setTimeout(fn, 50);
}else{
fn();
}
},
alert: function(msg, fn){
if(!$.dialog)
return;
$.dialog.alert(msg, fn);
},
confirm: function(msg, fn, fn2){
if(!$.dialog)
return;
$.dialog.confirm(msg, fn, fn2);
},
prompt: function(msg, fn){
if(!$.dialog)
return;
$.dialog.prompt(msg, fn);
},
tips: function(msg, delay, img, fn){
if(!$.dialog)
return;
$.dialog.tips(msg, delay, img, fn);
}
};
}]);
// valid
md.factory('uiValid', ['ng.config', 'uiLog', 'uiTips', '$parse', function(conf, log, tips, $parse){
return {
setElementInvalid: function(el){
el.removeClass(conf.validClass).addClass(conf.invalidClass);
},
setElementValid: function(el){
el.removeClass(conf.invalidClass).addClass(conf.validClass);
},
checkForm: function($form){
return this.checkFormWithVal($form, false);
},
// call this method before submit your form or do a ajax request
// because angular directive donot trigger auto
checkFormWithVal: function($form, returnRequiredModel, $index){
var formName = $form.$name;
var _context = $('form[name="{0}"],[ng-form="{0}"],[data-ng-form="{0}"]'.format(formName));
// ng-repeat create forms with same name, use one
if($index != null)
_context = _context.eq($index);
var _elLl = _context.findAll('[ui-valid]');
if(!_elLl.length)
return true;
// angular unshift value="?" option
var isSelectNull = function(one, val){
return '?' == val && one.is('select');
};
var _this = this;
var flags = [];
// no break -> show all tips of form inputs that require value
_elLl.each(function(){
var _el = $(this);
var val = _el.val();
if(angular.isString(val))
val = val.trim();
if(val && !isSelectNull(_el, val))
return;
var rules = _el.attr('ui-valid');
if(!rules)
return;
var arr = rules.split(' ');
// 'r' means required
if(arr.contains('r')){
var modelName = _el.attr('ng-model') || _el.attr('data-ng-model');
flags.push(modelName);
tips.on(_el, _el.attr('ui-valid-tips') || _this.getMsg('r'));
}
});
return returnRequiredModel ? flags : !flags.length;
},
// 根据排除特定模型、规则后,获取$form的是否验证成功
validForm: function($form, skipedList, ruleList, index){
// 用于保存去掉ruleList后各个model对应的违背规则列表,用于调节样式
// 行转列,之前是规则对应false or NgModelController列表
// 转换后,变成modelName对应的规则列表
var modelRuleItems = {};
// 当$form是$dirty false时候,用dom校验必填项
var requiredFlags = this.checkFormWithVal($form, true, index);
var requiredFlagsTarget = requiredFlags;
// 排除skipedList的必填项规则
if(ruleList.contains('r')){
requiredFlagsTarget = _.difference(requiredFlags, skipedList);
_.each(_.intersection(skipedList, requiredFlags), function(modelName){
if(!modelRuleItems[modelName])
modelRuleItems[modelName] = [];
modelRuleItems[modelName].push('r');
});
}
// 排除之后还有违背必填规则的模型,required就未被校验通过
var isRequiredFlag = !requiredFlagsTarget.length;
// 看下是不是因为modelList非必填的导致的$valid是false
var isValidForm = $form.$valid;
if(!isValidForm){
var errorToken = [];
var errors = $form.$error;
for(var key in errors){
if(!errors.hasOwnProperty(key) || !key.contains('__')){
continue;
}
var arr = key.split(/__/);
var modelName = arr[0];
var rule = arr[1];
// === false就是校验通过
if(errors[key] === false){
continue;
}
if(!modelRuleItems[modelName]){
modelRuleItems[modelName] = [];
}
modelRuleItems[modelName].push(rule);
var needSkip = ruleList.contains(rule) && skipedList.contains(modelName);
if(needSkip){
continue;
}
// 如果有非skipedList的model产生的error,就验证不通过
errorToken.push(key);
}
isValidForm = errorToken.length === 0;
}
this.filterTipsOff(modelRuleItems, skipedList, ruleList, index);
return isRequiredFlag && isValidForm;
},
// 去掉tips
filterTipsOff: function(modelRuleItems, skipedList, ruleList, index){
for(var modelName in modelRuleItems){
if(!skipedList.contains(modelName)){
continue;
}
// 违反规则去掉skip的rule list
var rules = modelRuleItems[modelName];
var rulesSliced = _.difference(rules, ruleList);
if(!rulesSliced.length){
var targetEl = $('[ng-model="{0}"],[data-ng-model="{0}"]'.format(modelName));
if(index != null)
targetEl = targetEl.eq(index);
tips.off(targetEl);
}
}
},
// check if val fit these valid rules
check: function(val, rules, $scope, defaultTips, extendParam){
// no rules
if(!rules)
return {flag: true};
var arr = rules.split(' ');
// 'r' means required
// multiple select blank array == '' -> true, use string to compare
var isBlank = val === null || val === undefined || val === '' || ('' + val === '');
if(!arr.contains('r') && isBlank)
return {flag: true};
var i = 0, len = arr.length;
for(; i < len; i++){
var rule = arr[i];
if(!rule)
continue;
var flag = true;
if('r' == rule){
// multiple select blank array == '' -> true
// so return false
flag = !isBlank;
}else if(rule.contains(':')){
// rules that is complex
flag = this.checkRule(val, rule.split(/:/), $scope, extendParam);
}else{
var pat = this.pats[rule];
if(pat instanceof RegExp){
if(angular.isString(val)){
flag = this.mat(val, pat);
}
}else if(angular.isFunction(pat)){
flag = pat(val);
}else{
// only support regexp and function
flag = false;
}
}
// if get string means valid failed, just show
if(angular.isString(flag)){
return {flag: false, msg: flag, rule: rule};
}
if(flag === false){
var msg = this.getMsg(rule, defaultTips) || this.getMsg('tips.valid');
return {flag: false, msg: msg, rule: rule};
}
}
return {flag: true};
},
// eg. "fn:checkTarget" -> customized valid function
// eg. "num:range:target_id:+100" -> return true when val - model val(target_id) < 100
// eg. "date:range:target_id:+2" -> return true when val - model val(target_id) < 2
// eg. "date:rangeout:target_id:+2" -> return true when val - model val(target_id) > 2
// eg. "minlen:char:3"
// eg. "maxval:float:3.23"
checkRule: function(val, ruleArr, $scope, extendParam){
var len = ruleArr.length;
var pre = ruleArr[0];
// customized valid function defined in controller $scope
var getter, targetVal, rangeVal;
if('fn' == pre){
var fnName = ruleArr[1];
getter = $parse(fnName);
var fn = getter($scope);
if(!fn){
return true;
}
// execute's context is current scope
return fn.call($scope, val, extendParam);
}else if('num' == pre){
if(len != 4){
log.i('Invalid rules : ' + ruleArr);
return false;
}
// val targetVal is string, usually generated by user's input
getter = $parse(ruleArr[2]);
targetVal = getter($scope);
if(!targetVal)
return false;
var currentVal = parseFloat(val);
var targetNumVal = parseFloat(targetVal);
rangeVal = parseInt(ruleArr[3], 10);
if(ruleArr[1] == 'range' && currentVal > targetNumVal + rangeVal)
return false;
if(ruleArr[1] == 'rangeout' && currentVal < targetNumVal + rangeVal)
return false;
return true;
}else if('date' == pre){
if(len != 4){
log.i('Invalid rules : ' + ruleArr);
return false;
}
// val targetVal is better as a Date object, but it's much more complex
// here targetVal is a string
getter = $parse(ruleArr[2]);
targetVal = getter($scope);
if(!targetVal)
return false;
rangeVal = parseInt(ruleArr[3], 10);
if(ruleArr[1] == 'range' && Date.parse2(val) > Date.parse2(targetVal).add(rangeVal))
return false;
if(ruleArr[1] == 'rangeout' && Date.parse2(val) < Date.parse2(targetVal).add(rangeVal))
return false;
return true;
}else if('minlen' == pre || 'maxlen' == pre){
if(len != 3){
log.i('Invalid rules : ' + ruleArr);
return false;
}
var lenVal = parseInt(ruleArr[2], 10);
if(ruleArr[0] == 'minlen' &&
(('byte' == ruleArr[1] && val.length < lenVal) ||
('char' == ruleArr[1] && val.charlen() < lenVal)))
return false;
if(ruleArr[0] == 'maxlen' &&
(('byte' == ruleArr[1] && val.length > lenVal) ||
('char' == ruleArr[1] && val.charlen() > lenVal)))
return false;
return true;
}else if('minval' == pre || 'maxval' == pre){
if(len != 3){
log.i('Invalid rules : ' + ruleArr);
return false;
}
targetVal = 'float' == ruleArr[1] ? parseFloat(ruleArr[2]) : parseInt(ruleArr[2], 10);
var currentVal = 'float' == ruleArr[1] ? parseFloat(val) : parseInt(val, 10);
if(pre == 'minval' && currentVal < targetVal)
return false;
if(pre == 'maxval' && currentVal > targetVal)
return false;
return true;
}else if('ac' == pre){
// autocomplete valid check
if(len != 2 && len != 3){
log.i('Invalid rules : ' + ruleArr);
return false;
}
getter = $parse(ruleArr[1]);
targetVal = getter($scope);
// tips: label-value (format)
var spliter = len == 3 ? ruleArr[2] : '-';
return targetVal && val.split(spliter)[0] == targetVal;
}else{
return true;
}
},
mat: function(val, pat){
if(!pat)
return true;
return pat.test(val);
},
getMsg: function(rule, tips){
// if develeper giving tips (ui-valid-tips) when using this directive, return giving tips
// if ui-valid-tips="label:Your model label", prepend 'Your model label' to tips and return
tips = tips || '';
if(tips && !tips.contains(':')){
return tips;
}
var msg = this.msgs[rule];
if(rule.contains(':')){
var ruleFirst = rule.split(':')[0];
if(['ac', 'maxval', 'minval', 'maxlen', 'minlen'].contains(ruleFirst)){
msg = this.msgs[ruleFirst];
}
}
if(msg){
var params0 = tips.contains(':') ? tips.split(/:/)[1] : '';
var params1 = '';
if(rule.startsWith('min') || rule.startsWith('max')){
var ruleArr = rule.split(/:/);
// eg. rule = "maxval:float:3.23" -> show tips with 3.23
params1 = ruleArr[ruleArr.length - 1];
}
return msg.format(params0, params1);
}else{
log.w('No tips for : ' + rule);
return tips;
}
},
// add your valid function using this
/*
eg.
var myModule = angular.module('myModule', ['ng.service']);
myModule.run(['uiValid', function(uiValid){
uiValid.regPat('rule.test', /^\d{2,3}$/, '数字两三个');
}]);
*/
regPat: function(code, pat, msg){
if(this.pats[code])
return;
this.pats[code] = pat;
this.msgs[code] = msg;
},
// default rule / tips defination
msgs: {
'r': '{0}不能为空',
'date': '{0}不正确的日期格式格式,日期格式应该为yyyy-MM-dd',
'time': '{0}不正确的时间格式,时间格式应该为hh:mm',
'datetime': '{0}不正确的时间格式,时间格式应该为yyyy-MM-dd hh:mm:ss',
'mobile': '{0}手机号码格式不正确',
'phone': '{0}不正确的固定电话格式,固定电话格式应该为:区号-固定电话号码',
'int': '{0}必须为整数',
'posint': '{0}必须为正整数',
'float': '{0}必须为数字',
'float1': '{0}格式不正确(最多1位小数)',
'float2': '{0}格式不正确(最多2位小数)',
'idno': '{0}身份证号码为15或18位,18位除最后一位可为英文字符“X”外其它位数均为数字',
'email': '{0}电子邮件只允许字母、数字、“-”、“_”、“.”、@,且所有数字、字母、符号都为半角,字母可以大小写,有且仅包含一个@,字符长度不少于6位',
'carcode': '{0}车牌号必须是以汉字+大写字母开头,数字部分为4-8位',
'policy.no': '{0}不是有效的保单号码',
'report.no': '{0}不是有效的报案号码',
'claim.no': '{0}不是有效的赔案号码',
'mac': '{0}请输入正确MAC地址格式,包含数字0-9、字母A-F,如00-33-2D-7B-37-FE',
'minlen': '{0}字符数不到规定长度{1}',
'maxlen': '{0}字符数超过规定长度{1}',
'maxval': '{0}值超过上限{1}',
'minval': '{0}值小于下限{1}',
'ac': '请选择自动填充内容',
'tips.valid': '校验不通过'
},
// default rule -> regex/function defination
pats: {
'date': function(val){
return Date.isDateValid(val);
},
'time': function(val){
return Date.isTimeValid(val);
},
'datetime': function(val){
return Date.isDateTimeValid(val);
},
// 'mobile': /^0?(13[0-9]|15[012356789]|18[0236789]|14[57]|17[0-9])[0-9]{8}$/,
'mobile': /^1(3|4|5|7|8)\d{9}$/,
'phone': /^([\d]{3,4}(-|\/))?[\d]{6,8}(-[\d]{1,6})?$/,
'int': /^[\-\+]?([0-9]+)$/,
'posint': /^\d+$/,
'float': /^[\-\+]?([0-9]+\.?([0-9]+)?)$/,
'float1': /^[\-\+]?([0-9]+(\.[0-9]{1})?)$/,
'float2': /^[\-\+]?([0-9]+(\.[0-9]{2})?)$/,
'idno': /^\d{6}(((19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])\d{3}([0-9]|x|X))|(\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])\d{3}))$/,
'email': /^([a-zA-Z\d\-\._]+)@([a-zA-Z\d\-\._]+)$/,
'carcode': /^(([^\x00-\xff]|[A-Za-z0-9]){2}([^\x00-\xff]|[A-Za-z0-9]){2,6})|([*]-[*])$/,
'policy.no': /^\d{19,20}$/,
'report.no': /^\d{19,20}$/,
'claim.no': /^\d{19,20}$/,
'mac': /^([0-9A-F]{2})(([-][0-9A-F]{2}){5})$/
}
};
}]);
})(angular);
// file ng.ui.js (function(ag){ var moduleName = 'ng.ui';
window.console.log('Begin init module ' + moduleName);
if(ag.isModuleExists && ag.isModuleExists(moduleName)){
window.console.log('Module already exists!' + moduleName);
return;
}
var md = ag.module(moduleName, ['ng.config', 'ng.service', 'ng.filter']);
// tips use poshytips
md.directive('uiTip', ['ng.config', 'uiLog', function(conf, log){
var options = {};
if(ag.isObject(conf.tipOptions)){
ag.extend(options, conf.tipOptions);
}
return {
restrict: 'A',
link: function(scope, el, attrs){
if(!attrs.uiTip){
log.i('Skip tips');
return;
}
var opts = ag.extend(ag.copy(options), scope.$eval(attrs.uiTipOptions));
// use asychronized function better
opts.content = attrs.uiTip;
el.poshytip(opts);
}
};
}]);
// datepicker
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
md.directive('uiDate', ['ng.config', 'uiLog', function(conf, log){
'use strict';
var options = {};
if(ag.isObject(conf.date)){
ag.extend(options, conf.date);
}
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attrs, ctrl){
var getOptions = function(){
return ag.extend(ag.copy(options), scope.$eval(attrs.uiDate));
};
var init = function(){
var opts = getOptions();
log.i('Init datepicker : ' + attrs.ngModel);
log.i(opts);
opts.onSelect = function(value, picker){
scope.$apply(function(){
ctrl.$setViewValue(el.val());
});
};
el.datepicker('destroy');
el.addClass('date');
if(opts.timeFormat){
el.datetimepicker(opts);
}else{
el.datepicker(opts);
}
};
// change format auto
// add strikethrough or add 0
var format = function(){
var val = el.val();
if(!val)
return;
var arr = val.split(' ');
var ymd = arr[0];
var ymdNew = ymd;
// add 0 to month/day
if(ymd.contains('-')){
var subArr = ymd.split('-');
if(subArr.length != 3)
return;
ymdNew = subArr[0] +
'-' +
(subArr[1].length == 1 ? '0' + subArr[1] : subArr[1]) +
'-' +
(subArr[2].length == 1 ? '0' + subArr[2] : subArr[2]);
if(arr.length > 1)
ymdNew += ' ' + arr[1];
}else{
// if(!ymd.match(/^\d{8}$/)) // return; // ymdNew = ymd.substr(0, 4) + '-' + ymd.substr(4, 2) + '-' + ymd.substr(6, 2); ymdNew = new Date().format('yyyy-MM-dd'); if(arr.length > 1) ymdNew += ' ' + arr[1]; }
if(ymdNew != val){
el.val(ymdNew);
scope.$apply(function(){
ctrl.$setViewValue(ymdNew);
});
}
};
el.blur(format);
// return
el.keyup(function(e){
if(e.keyCode == 13)
format();
});
// Watch for changes to the directives options
// under one condition it's datepicker, another it's datetimepicker
scope.$watch(getOptions, init, true);
}
};
}]);
// autocomplete, use dropdown better
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
md.directive('uiAutocomplete', ['ng.config', 'uiLog', '$parse', function(conf, log, $parse){
'use strict';
var options = {};
if(ag.isObject(conf.autocomplete)){
ag.extend(options, conf.autocomplete);
}
$(document).click(function(e) {
var target = $(e.target);
var shouldHideAc = $('.autoComplete:visible');
if(shouldHideAc.length && target.is('input[ui-autocomplete]')){
var extClassNameThis = target.attr('ac-result-class');
shouldHideAc = shouldHideAc.not('.' + extClassNameThis);
}
shouldHideAc.each(function(){
var extClassName = $(this).attr('class').split(' ')[2];
$('[ac-result-class="' + extClassName + '"]').trigger('ac-finish');
});
});
var cc = 0;
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attrs, ctrl){
var getOptions = function(){
return ag.extend(ag.copy(options), scope.$eval(attrs.uiAutocomplete));
};
var extClassName = 'auto-complete' + (++cc);
el.attr('ac-result-class', extClassName);
var init = function(){
var opts = getOptions();
log.i('Init autocomplete : ' + attrs.ngModel);
log.i(opts);
if(!opts.url){
log.i('Init autocomplete fail : url required : ' + attrs.ngModel);
return;
}
ctrl.$render = function(){
// show value for input
var showLabel = ctrl.$modelValue;
if(showLabel){
el.val(showLabel);
}
if(!showLabel && opts.targetModel){
var showValue = $parse(opts.targetModel)(scope);
if(showValue){
el.val(showValue);
}
}
};
var labelKey = opts.labelKey;
var valueKey = opts.valueKey;
// json :
// [{data: {result: i}, value: 'Col' + i}]
var props = {
url: opts.url,
minChars: opts.minChars,
maxItemsToShow: opts.maxItemsToShow,
finishOnBlur: opts.finishOnBlur,
remoteDataType: 'json',
useCache: false,
resultsClass: 'acResults autoComplete ' + extClassName,
// do not request when value contains '-'
fetchRemoteDataFilter: function(val){
return val && val.indexOf('-') === -1;
},
processData: function(data) {
var i, r = [], len = data.length;
for(i = 0; i < len; i++){
var item = data[i];
// change here
var valueShow = valueKey ? item[valueKey] : item;
var labelShow = labelKey ? (valueShow + (opts.spliter || '-') + item[labelKey]) : valueShow;
r.push({data: {result: valueShow}, value: labelShow});
}
return r;
},
onItemSelect: function(item){
var val = item.data.result;
var showLabel = item.value;
var targetModel = opts.targetModel;
var setter;
if(targetModel){
var getter = $parse(targetModel);
setter = getter.assign;
}
scope.$apply(function(){
if(setter){
setter(scope, val);
}
ctrl.$setViewValue(showLabel);
});
}
};
// IE8 finishOnBlur fail... because scrollbar trigger blur
if($.browser.msie){
props.finishOnBlur = false;
}
el.autocomplete(props);
ctrl.$render();
};
// Watch for changes to the directives options
scope.$watch(getOptions, init, true);
}
};
}]);
// select2, use dropdown better as performance is bad
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
md.directive('uiSelect2', ['ng.config', 'uiLog', function(conf, log){
'use strict';
var options = {};
if(ag.isObject(conf.select2)){
ag.extend(options, conf.select2);
}
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attrs, ctrl){
var isMultiple = (attrs.multiple !== undefined);
var isSelect = el.is('select');
var opts = ag.extend(ag.copy(options), scope.$eval(attrs.uiSelect2));
log.i('Init select2 : ');
log.i(opts);
var blankText = '';
if(isSelect){
delete opts.multiple;
delete opts.initSelection;
blankText = el.findAll('option[value=""]').eq(0).text();
}else if(isMultiple){
opts.multiple = true;
}
var getNgOptionsModelValue = function(){
return null;
};
var tmp = attrs.ngOptions || attrs.uiOptions;
if(tmp){
var pat = /^.+in (.+)$/;
var mat = pat.exec(tmp);
var ngOptionsModelName = mat[1];
getNgOptionsModelValue = function(){
return ngOptionsModelName ? scope.$eval(ngOptionsModelName) : null;
};
}
var model2val = function(modelVal, newVal){
var valueKey = opts.valueKey || 'v';
// not use ng-options
if(!newVal){
newVal = el.findAll('option').map(function(){
var item = {};
item[valueKey] = $(this).val();
return item;
}).get();
}
// because ng-options use index but not value,
// so get index by target value (format: [{v: 'yourModelValue'}]) first
// eg. ng-options="one.v as one.l for one in optsTest"
var i = 0, len = newVal.length;
if(isMultiple && ag.isArray(modelVal)){
var indexLl = [];
if(!ctrl.$viewValue)
return indexLl;
for(; i < len; i++){
if(ctrl.$viewValue.contains(newVal[i][valueKey])){
indexLl.push(i);
}
}
return indexLl;
}else{
var index = -1;
for(; i < len; i++){
if(newVal[i][valueKey] == ctrl.$viewValue){
index = i;
break;
}
}
return index;
} // end if
};
ctrl.$render = function(){
if(!isSelect)
return;
var select2index = model2val(ctrl.$modelValue, getNgOptionsModelValue());
// IE8 need setTimeout
if($.browser.msie){
setTimeout(function(){
el.select2('val', select2index);
if(attrs.uiOptions && select2index == -1){
el.prev('.select2-container:first').findAll('.select2-chosen').text(blankText);
}
}, 0);
}else{
el.select2('val', select2index);
}
};
if(ngOptionsModelName){
// watch ng-option model
scope.$watch(ngOptionsModelName, function(newVal, oldVal){
if(!newVal)
return;
// isArray -> multiple
if(!ctrl.$viewValue){
el.select2();
return;
}
var index = model2val(ctrl.$viewValue, newVal);
if((ag.isArray(index) && index.length) || (ag.isNumber(index) && index != -1)){
el.select2('val', index);
}else{
el.select2();
}
}, true); // end $watch
}
attrs.$observe('disabled', function(val){
el.select2(val && 'disable' || 'enable');
});
// IE8 select val bug
if($.browser.msie){
el.bind('select2-selected', function(e){
if('' == e.val){
$(this).attr('value', '');
}
});
}
el.select2(opts);
}
};
}]);
md.service('uiDropdownHelper', function(){
// panel begin zindex
this.zindex = 1000;
this.defaultPanelScrollHeight = 150;
this.highlightClass = 'ui-state-highlight';
this.setPanelPosition = function(panel, el, opts){
var offset = el.offset();
panel.css({
zindex: this.zindex++,
width: el.width(),
top: offset.top + el.height(),
left: offset.left
});
var height = (opts.height || this.defaultPanelScrollHeight) + 'px';
panel.findAll('.pui-dropdown-items-wrapper').css({height: height});
};
this.bindHoverEvent = function(el, selector){
var selector = 'li';
el.delegate(selector, 'mouseenter', function(e){
var hovered = $(e.target);
if(!hovered.is(selector))
hovered = hovered.closest(selector);
el.findAll(selector + '.ui-state-hover').removeClass('ui-state-hover');
if(!hovered.is('.ui-state-active') && !hovered.is('.ui-state-disabled'))
hovered.addClass('ui-state-hover');
}).on('mouseleave', function(){
el.findAll(selector).removeClass('ui-state-hover');
});
};
this.bindDelegateDocumentEvent = function(attrId){
$(document).click(function(e){
var target = $(e.target);
$('.pui-dropdown-panel').each(function(){
var panel = $(this);
var panelDropdownId = panel.attr(attrId);
if(target.is('.pui-dropdown-filter') || target.is('.ui-dropdown-multiple-input')){
// hide others panel
var targetDropdownId = target.attr(attrId);
if(targetDropdownId !== panelDropdownId)
panel.triggerHandler('uiDropdownHide');
}else{
panel.triggerHandler('uiDropdownHide');
}
});
// multiple choosed label delete span
// is a span <ul><li><span></span></li></ul>
if(target.is('[data-pui-ac-close-span]')){
var acId = target.attr('data-pui-ac-close-span');
$('.pui-dropdown-panel').each(function(){
var panel = $(this);
var panelAcId = panel.attr(attrId);
if(acId === panelAcId){
var targetVal = target.parent().attr('data-raw-value');
panel.triggerHandler('puiAcDelVal', [targetVal]);
}
});
target.parent().remove();
}
});
};
this.wrapMultipleLi = function(targetVal, targetLabel, acId){
return '<li data-raw-value="' + targetVal +
'" class="pui-autocomplete-token ui-state-active ui-corner-all ui-helper-hidden" ' +
'style="display: list-item;"><span data-pui-ac-close-span="' + acId +
'" class="pui-autocomplete-token-icon ui-icon ui-icon-close"></span>' +
'<span class="pui-autocomplete-token-label" title="' + targetLabel + '">' + targetLabel + '</span></li>';
};
});
md.directive('uiDropdownPanel', ['$http', 'ng.config', 'uiLog', 'uiDropdownHelper', function($http, conf, log, uiDropdownHelper){
var tplLi = '<li data-id="{0}" class="pui-dropdown-item pui-dropdown-list-item ui-corner-all">{1}</li>';
return {
scope: {
list: '=',
valueField: '@',
labelField: '@',
blankLabel: '@',
queryUrl: '@',
modelVal: '=',
labelWithVal: '@'
},
link: function(scope, el, attrs){
var isLabelWithVal = 'true' == scope.labelWithVal;
var filterList = function(list, targetVal, cb, match){
if(!list)
return cb([]);
if(match && !targetVal)
return cb(list);
var filteredList = _.filter(list, function(it){
var val = '' + it[scope.valueField];
var label = '' + it[scope.labelField];
if(match && !(val.contains(targetVal) ||
val.toLowerCase().contains(targetVal.toLowerCase()) ||
label.contains(targetVal) ||
label.toLowerCase().contains(targetVal.toLowerCase())
)){
return false;
}
var isChoosedAlready = angular.isArray(scope.modelVal) ?
scope.modelVal.contains(val) : scope.modelVal == val;
return !isChoosedAlready;
});
cb(filteredList);
};
// use dom
el.on('uiDropdownUnique', function(e, modelVal){
e.preventDefault();
e.stopPropagation();
renderList({list: scope.list, queryUrl: scope.queryUrl, querySearch: scope.querySearch});
});
var renderList = function(obj, match, triggerReady){
var list = obj.list;
var querySearch = obj.querySearch;
var queryUrl = obj.queryUrl;
var tpl = scope.blankLabel ? tplLi.format('', scope.blankLabel) : '';
var cb = function(ll){
var i = 0, len = ll.length, one;
for(; i < len; i++){
one = ll[i];
tpl += tplLi.format(one[scope.valueField],
isLabelWithVal ? one[scope.valueField] + '-' + one[scope.labelField] : one[scope.labelField]);
}
el.findAll('ul').html(tpl);
if(triggerReady)
el.triggerHandler('uiDropdownListReady');
};
if(queryUrl){
if(!querySearch){
cb([]);
}else{
$http.get(Consts.getAppPath(queryUrl) + '?q=' + encodeURI(querySearch)).success(function(data){
cb(data);
});
}
}else{
filterList(list, querySearch, cb, match);
}
};
scope.$watch(function(){
return {list: scope.list, queryUrl: scope.queryUrl, querySearch: scope.querySearch};
}, function(obj){
renderList(obj, true, true);
}, true);
var focusByCalIndex = function(calFn){
var liList = el.findAll('li');
var liCurrent = liList.filter('.ui-state-hover');
liCurrent.removeClass('ui-state-hover');
var index = liList.index(liCurrent);
var targetIndex = calFn(index, liList.length);
liList.eq(targetIndex).addClass('ui-state-hover');
};
var focusPrev = function(){
focusByCalIndex(function(index, len){
return index <= 0 ? len - 1 : index - 1;
});
};
var focusNext = function(){
focusByCalIndex(function(index, len){
return index < len - 1 ? index + 1 : 0;
});
};
var chooseCurrent = function(){
var li = el.findAll('li.ui-state-hover');
// no hover, choose first by default
if(!li.length)
li = el.findAll('li').eq(0);
if(!li.length)
return;
var targetId = li.attr('data-id');
var targetLabel = li.text();
el.triggerHandler('uiDropdownChoose', [targetId, targetLabel]);
};
var hidePanel = function(){
el.triggerHandler('uiDropdownHide');
};
el.findAll('.pui-dropdown-filter').keyup(function(e){
e.stopPropagation();
var keyCode = e.keyCode;
switch(keyCode){
// up
case 38:
focusPrev();
break;
// down
case 40:
focusNext();
break;
// return
case 13:
chooseCurrent();
break;
// esc
case 27:
hidePanel();
break;
default:
break;
}
});
}
};
}]);
// dropdown
md.directive('uiDropdown', ['$parse', '$compile', 'ng.config', 'uiLog', 'uiDropdownHelper', function($parse, $compile, conf, log, uiDropdownHelper){
var countNum = 0;
var attrId = 'data-dropdown-id';
var dropdownPaneTpl ='<div ui-dropdown-panel="" label-with-val="{6}" model-val="{5}" query-url="{4}" blank-label="{3}" label-field="{2}" value-field="{1}" list="{0}" ' +
'class="pui-dropdown-panel ui-widget-content ui-corner-all ui-helper-hidden pui-shadow">' +
' <div class="pui-dropdown-filter-container">' +
' <input type="text" ng-model="querySearch" class="pui-dropdown-filter pui-inputtext ui-widget ui-state-default ui-corner-all" />' +
' <span class="ui-icon ui-icon-search"></span>' +
' </div>' +
' <div class="pui-dropdown-items-wrapper">' +
' <ul class="pui-dropdown-items pui-dropdown-list ui-widget-content ui-widget ui-corner-all ui-helper-reset">' +
' </ul>' +
' </div>' +
'</div>';
uiDropdownHelper.bindDelegateDocumentEvent(attrId);
return {
restrict: 'A',
require: 'ngModel',
transclude: true,
compile: function(el, attrs, transcludeFn){
return function(scope, el, attrs, ctrl){
var opts = scope.$eval(attrs.uiDropdown) || {};
opts = angular.extend(angular.copy(conf.dropdownOptions), opts);
var listModel = opts.list;
if(!listModel && !opts.queryUrl){
log.w('No listModel or queryUrl given!');
return;
}
var getList = function(){
return $parse(listModel)(scope);
};
var isMultiple = !!opts.multiple;
var isEditable = !!opts.editable;
var cc = countNum++;
if(isEditable){
attrs.$observe('uiEditable', function(val){
if(val === undefined)
return;
if('true' === val){
el.parent().addClass('ui-helper-editable');
}else{
renderLabel(el.val());
el.parent().removeClass('ui-helper-editable');
}
});
}
attrs.$observe('disabled', function(val){
if(val === undefined)
return;
// disabled -> true
if(val){
hidePanel();
if(isMultiple){
var input = el.prev('.pui-autocomplete-multiple').findAll('.ui-dropdown-multiple-input');
input.attr('disabled', true);
var ul = input.parent().parent();
ul.findAll('.ui-icon-close').hide();
ul.findAll('.pui-autocomplete-token-label').addClass('ui-state-disabled');
}else{
el.parent().parent().addClass('ui-state-disabled');
}
}else{
if(isMultiple){
var input = el.prev('.pui-autocomplete-multiple').findAll('.ui-dropdown-multiple-input');
input.removeAttr('disabled');
var ul = input.parent().parent();
ul.findAll('.ui-icon-close').show();
ul.findAll('.pui-autocomplete-token-label').removeClass('ui-state-disabled');
}else{
el.parent().parent().removeClass('ui-state-disabled');
}
}
});
var tplPanel = dropdownPaneTpl.format(listModel, opts.valueField, opts.labelField,
opts.blankLabel || '', opts.queryUrl || '', attrs.ngModel, opts.labelWithVal);
var panel = $compile(tplPanel)(scope);
panel.attr(attrId, cc).css('z-index', opts.zIndex);
panel.findAll('.pui-dropdown-filter').attr(attrId, cc);
panel.appendTo($(document.body));
// use enter/tab trigger
panel.on('uiDropdownChoose', function(e, targetVal, targetLabel){
e.preventDefault();
e.stopPropagation();
chooseCurrent(targetVal, targetLabel);
});
// when set list after model set
panel.on('uiDropdownListReady', function(e){
e.preventDefault();
e.stopPropagation();
ctrl.$render();
});
panel.on('uiDropdownHide', function(e){
e.preventDefault();
e.stopPropagation();
if(isActive)
hidePanel();
});
if(isMultiple){
panel.on('puiAcDelVal', function(e, targetVal){
var input = el.prev('.pui-autocomplete-multiple').findAll('.ui-dropdown-multiple-input');
// remove one from array
var targetValList = ctrl.$modelValue || [];
var index = targetValList.indexOf(targetVal);
if(index >= 0){
targetValList.splice(index, 1);
el.val(targetValList.toString());
scope.$apply(function(){
ctrl.$setViewValue(targetValList);
if(attrs.uiChange){
scope.$eval(attrs.uiChange);
}
});
}
});
}
uiDropdownHelper.bindHoverEvent(panel, 'li');
panel.delegate('li', 'click', function(e){
e.preventDefault();
e.stopPropagation();
var li = $(e.target);
var targetVal = li.attr('data-id');
var targetLabel = li.text();
chooseCurrent(targetVal, targetLabel);
});
if(isMultiple){
transcludeFn(scope, function(clone){
el.hide();
// help input
var input = $('<input type="text" class="pui-textfield ui-dropdown-multiple-input" />')
.attr(attrId, cc).width(opts.widthMultipleInput + 'px');
el.before(input);
input.wrap('<li class="pui-autocomplete-input-token"></li>');
input.parent().wrap('<ul class="pui-autocomplete-multiple ui-widget pui-inputtext ui-state-default ui-corner-all"></ul>');
if(opts.widthWrapper){
input.parent().parent().width(opts.widthWrapper + 'px');
}
});
}else if(isEditable){
transcludeFn(scope, function(clone){
el.wrap('<div class="ui-helper-hidden-accessible ui-helper-editable"></div>');
var elParent = el.parent();
opts.width = el.width();
elParent.wrap('<div class="pui-dropdown ui-widget ui-state-default ui-corner-all ui-helper-clearfix" style="width: ' + opts.width + 'px;"></div>');
elParent.after('<div class="pui-dropdown-trigger ui-state-default ui-corner-right"><span class="ui-icon ui-icon-triangle-1-s"></span></div>');
elParent.after('<label class="pui-dropdown-label pui-inputtext ui-corner-all" style="width: ' + (opts.width - opts.widthDiff) + 'px;">' + (opts.blankLabel || '--/--') + '</label>');
el.css({border: 'none', 'background-color': '#fff', height: (conf.inputHeight || 27) + 'px'});
});
}else{
transcludeFn(scope, function(clone){
el.wrap('<div class="ui-helper-hidden-accessible"></div>');
var elParent = el.parent();
opts.width = el.width();
elParent.wrap('<div class="pui-dropdown ui-widget ui-state-default ui-corner-all ui-helper-clearfix" style="width: ' + opts.width + 'px;"></div>');
elParent.after('<div class="pui-dropdown-trigger ui-state-default ui-corner-right"><span class="ui-icon ui-icon-triangle-1-s"></span></div>');
elParent.after('<label class="pui-dropdown-label pui-inputtext ui-corner-all" style="width: ' + (opts.width - opts.widthDiff) + 'px;">' + (opts.blankLabel || '--/--') + '</label>');
});
}
var renderLabelMultiple = function(valList, labelList, clear){
var input = el.prev('.pui-autocomplete-multiple').findAll('.ui-dropdown-multiple-input');
var li = input.parent();
if(clear){
li.siblings().remove();
}
valList = valList || [];
el.val(valList.toString());
if(!valList.length)
return;
labelList = labelList || [];
_.each(valList, function(targetVal, i){
var targetLabel = labelList[i];
if(!targetLabel){
var item = _.find(getList(), function(it){
return it[opts.valueField] == targetVal;
});
targetLabel = item ? item[opts.labelField] : targetVal;
if(opts.labelWithVal && item){
targetLabel = item[opts.valueField] + '-' + targetLabel;
}
}
li.before(uiDropdownHelper.wrapMultipleLi(targetVal, targetLabel, cc));
});
// if disabled
if(attrs.disabled){
var ul = li.parent();
ul.findAll('.ui-icon-close').hide();
ul.findAll('.pui-autocomplete-token-label').addClass('ui-state-disabled');
}
};
var renderLabel = function(modelVal, labelVal){
el.val(modelVal || '');
var label = opts.blankLabel || '';
if(labelVal){
label = labelVal;
}else if(modelVal){
if(isEditable){
label = modelVal;
}else{
// not ===
var item = _.find(getList(), function(it){
return it[opts.valueField] == modelVal;
});
label = item ? item[opts.labelField] : modelVal;
if(opts.labelWithVal && item){
label = item[opts.valueField] + '-' + label;
}
}
}
el.parent().parent().findAll('.pui-dropdown-label').attr('title', label).text(label);
};
var chooseCurrent = function(targetVal, targetLabel){
hidePanel();
// reset input for next time choose from panel
panel.findAll('.pui-dropdown-filter').val('');
if(isMultiple){
var targetValList = ctrl.$modelValue || [];
// if not blank
if(targetVal && !targetValList.contains(targetVal)){
renderLabelMultiple([targetVal], [targetLabel]);
targetValList.push(targetVal);
scope.$apply(function(){
ctrl.$setViewValue(targetValList);
if(attrs.uiChange){
scope.$eval(attrs.uiChange);
}
});
}
}else{
renderLabel(targetVal, targetLabel);
scope.$apply(function(){
ctrl.$setViewValue(targetVal);
if(attrs.uiChange){
scope.$eval(attrs.uiChange);
}
});
}
};
ctrl.$render = function(){
isMultiple ? renderLabelMultiple(ctrl.$modelValue, null, true) : renderLabel(ctrl.$modelValue);
};
var isActive = false;
var hidePanel = function(){
panel.hide();
isActive = false;
};
var showPanel = function(){
var relativeEl = isMultiple ? el.prev('.pui-autocomplete-multiple').findAll('.ui-dropdown-multiple-input') :
el.parent().parent();
uiDropdownHelper.setPanelPosition(panel, relativeEl, opts);
panel.show();
panel.findAll('.pui-dropdown-filter').focus();
panel.triggerHandler('uiDropdownUnique', [ctrl.$modelValue]);
isActive = true;
};
if(isMultiple){
el.prev('.pui-autocomplete-multiple').findAll('.ui-dropdown-multiple-input').on('focus', function(e){
e.stopPropagation();
showPanel();
});
}else{
el.parent().parent().click(function(e){
e.stopPropagation();
if(attrs.disabled)
return;
// editable
if(isEditable){
var target = $(e.target);
if(!target.is('input')){
isActive ? hidePanel() : showPanel();
}
}else{
isActive ? hidePanel() : showPanel();
}
});
}
}; // end return link
}
};
}]);
// lhgdialog style
// use uiDialog2 instead
md.directive('uiDialog', ['ng.config', 'uiLog', function(conf, log){
'use strict';
return {
restrict: 'A',
templateUrl: conf.context + 'tpl/lhgdialog.html',
replace: true,
transclude: true,
scope: {
// titler not title, or raw browser will make it title like
titler: '@',
visible: '@',
// using parent method
onOk: '&',
onCancel: '&'
},
compile: function(el, attrs, transclude){
return {
post: function(scope, el, attrs, ctrl){
var opts = scope.$eval(attrs.uiDialog) || {};
log.i('Compile dialog ui : ');
log.i(opts);
el.findAll('.ui_min').click(function(e){
el.find('.ui_icon, .ui_main, .ui_buttons').hide();
el.find('.ui_res').css('display', 'inline-block');
$(this).hide();
return false;
});
el.findAll('.ui_res').click(function(e){
el.find('.ui_icon, .ui_main, .ui_buttons').show();
el.find('.ui_min').css('display', 'inline-block');
$(this).hide();
return false;
});
// jquery ui drag
// locate center, after binding width changed, so need location center again
// use jquery to get document.width as IE fails
var innerTbl = el.findAll('table').eq(0);
// for div not with parent body, location center by tblWidth/tblHeight parameter
var tblW = opts.tblWidth || innerTbl.width();
var tblH = opts.tblHeight || innerTbl.height();
var left = Math.floor($(window).width() - tblW) / 2;
var top = Math.floor($(window).height() - tblH) / 2;
// if targetElementModel assign, location bellow that element
if(opts.targetElementModel){
var targetEl = $('[ng-model="{0}"],[data-ng-model="{0}"]'.format(opts.targetElementModel));
if(targetEl.length){
var leftFix = 20;
var topFix = 20;
var offsetEl = targetEl.eq(0).offset();
left = offsetEl.left + leftFix;
top = offsetEl.top + topFix;
}
}
if(left <= 0)
left = 10;
if(top <= 0)
top = 10;
var pos = {left: left + 'px', top: top + 'px'};
el.findAll('div.ng-ui-dialog-wrapper').css(pos);
// need lock, add block div
if(opts.lock === true){
var tplBlock = '<div class="ng-ui-dialog-block"></div>';
el.prepend(tplBlock);
}
if(opts.draggable === true){
var daggableOpts = {handle: '.ui_title_bar'};
if(ag.isObject(conf.dialogDraggable)){
ag.extend(daggableOpts, conf.dialogDraggable);
}
ag.extend(daggableOpts, opts);
el.findAll('div.ng-ui-dialog-wrapper').draggable(daggableOpts);
}
// min-height
if(opts.height || opts.width){
var props = {overflow: 'auto'};
if(ag.isNumber(opts.height))
props.height = '' + opts.height + 'px';
if(ag.isNumber(opts.width))
props.width = '' + opts.width + 'px';
el.findAll('.ui_content').css(props);
}
} // /post
};
} // /compile
};
}]);
// scope: true better
md.directive('uiDialog2', ['$parse', '$compile', 'ng.config', 'uiLog', 'safeApply', function($parse, $compile, conf, log, safeApply){
'use strict';
var cc = 0;
var fixCenter = function(dialog, fixDelay){
// setTimeout -> locate center after $digest -> dom rebuild
// donot use $timeout as need not $digest again
setTimeout(function(){
var wrap = dialog.DOM.wrap[0];
var left = ($(window).width() - wrap.offsetWidth) / 2;
var top = ($(window).height() - wrap.offsetHeight) / 2;
dialog.position(left, top);
}, fixDelay || 200);
};
return {
restrict: 'A',
link: function(scope, el, attrs){
var opts = scope.$eval(attrs.uiDialog2) || {};
log.i('Compile dialog2 ui : ');
log.i(opts);
if(!opts.showModel){
log.w('No show model given!');
return;
}
// one page has more than one dialogs with same dialog id
opts.dialogId = (opts.dialogId || '') + '_' + (++cc);
var subScope;
// lhgdialog properties
var props = {};
if(ag.isObject(conf.dialog2)){
ag.extend(props, conf.dialog2);
}
props.id = opts.dialogId;
props.title = opts.titleModel ? ('{{' + opts.titleModel + '}}'): opts.title;
props.content = el.html();
props.init = function(){
var targetScope = scope;
if(opts.closeForce)
subScope = targetScope = scope.$new();
// in watch
$compile(this.DOM.wrap.findAll('.ui_dialog'))(targetScope);
if(opts.fixPosition){
var that = this;
fixCenter(that, opts.fixDelay);
}
};
// a flag that make sure lhgdialog close only once
// because model true -> false trigger close again
var isInClose = false;
props.close = function(){
isInClose = true;
// use close in dialog toolbar will execute twice
// use button in dialog user defined will execute once which trigger by watch list
var getter = $parse(opts.showModel);
var isShow = getter(scope);
if(isShow){
var setter = getter.assign;
// trigger watch again
safeApply(scope, function(){
setter(scope, false);
if(opts.closeSettings){
var key = opts.closeSettings.key;
var val = opts.closeSettings.value;
$parse(key).assign(scope, val);
}
if(opts.closeFn){
var fnTarget = $parse(opts.closeFn)(scope);
if(ag.isFunction(fnTarget)){
fnTarget();
}
}
});
};
isInClose = false;
if(opts.closeForce && subScope){
subScope.$destroy();
subScope = null;
}
// not really close
return opts.closeForce ? true : false;
};
// @depricated, use ext instead
_.each(['lock', 'drag', 'fixed', 'resize'], function(it){
if(angular.isDefined(opts[it]))
props[it] = opts[it];
});
_.each(['width', 'height', 'left', 'top'], function(it){
if(opts[it])
props[it] = opts[it];
});
// overwrite other properties
if(opts.ext)
ag.extend(props, opts.ext);
scope.$watch(opts.showModel, function(val){
// show
if(val){
var target = $.dialog.list[opts.dialogId];
if(target){
if(target.config.lock){
target.lock();
}else{
target.zindex();
}
if(opts.fixPosition){
fixCenter(target);
}
target.show();
}else{
$.dialog(angular.copy(props));
}
}else{
// hide
var target = $.dialog.list[opts.dialogId];
if(target){
if(opts.closeForce){
if(!isInClose)
target.close();
}else{
target.hide();
}
}
}
}); // end $watch showModel
} // end link
};
}]);
// use template better, the angular way
// jquery dom way -> support compile template lazy
md.directive('uiTabs', ['$compile', '$parse', 'safeApply', 'uiLog', 'uiTips',
function($compile, $parse, safeApply, log, tips){
'use strict';
return {
restrict: 'A',
link: function(scope, el, attrs){
var opts = scope.$eval(attrs.uiTabs) || {};
// new style based on bootstrap
var contentsNews = el.siblings('.tabs');
var isStyleNew = !!contentsNews.length;
var navs, contents;
if(isStyleNew){
contents = contentsNews;
navs = el.findAll('li');
}else{
navs = el.findAll('.ng-ui-tabs').eq(0).findAll('li');
contents = el.findAll('.ng-ui-tab-content').eq(0).findAll('.ng-ui-tab-pane');
}
if(!navs.length || !contents.length || navs.length != contents.length){
log.i('Compile ui-tabs failed : tabs length not match!');
return;
}
navs.findAll('a').click(function(e){
e.preventDefault();
e.stopPropagation();
var navLinkLl = navs.findAll('a');
var index = navLinkLl.index(this);
var triggerIndex = navs.index(navs.filter('.active'));
var flag = true;
if(opts.beforeFn){
var fnTarget = $parse(opts.beforeFn)(scope);
if(fnTarget){
if(opts.digest){
safeApply(scope, function(){
flag = fnTarget(index, triggerIndex);
});
}else{
flag = fnTarget(index, triggerIndex);
}
}
}
if(!flag)
return;
navLinkLl.not(':eq(' + index + ')').parent().removeClass('active');
navLinkLl.eq(index).parent().addClass('active');
// tips off
var lastVisitedContent = contents.not(':eq(' + index + ')').filter('.active');
tips.offInContext(lastVisitedContent);
contents.not(':eq(' + index + ')').removeClass('active');
var targetPane = contents.eq(index);
targetPane.addClass('active');
// if link delay
var isLinkDelay = targetPane.attr('is-link');
if(isLinkDelay){
(function(){
var tplEl = targetPane.findAll('script').eq(0);
if(!tplEl.length)
return;
var inner = targetPane.findAll('.tpl');
// compile only once
if(isLinkDelay !== 'repeat' && inner.length)
return;
// empty div first
inner.remove();
// compile and link
var compiledEl = $compile(tplEl.html())(scope);
compiledEl.addClass('tpl').appendTo(targetPane);
})();
}
safeApply(scope, function(){
scope.$broadcast('TabFocus', index);
});
return false;
});
// trigger first
navs.findAll('a').eq(opts.targetIndex || 0).trigger('click');
}
};
}]);
// context menu
md.directive('uiContextMenu', ['$parse', 'ng.config', 'uiLog', function($parse, conf, log){
'use strict';
$(document).click(function(e){
$('.contextMenuPlugin').hide();
});
var attrId = 'ui-context-menu-id';
var options = {};
if(ag.isObject(conf.contextMenu)){
ag.extend(options, conf.contextMenu);
}
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attrs, ctrl){
var opts = ag.extend(ag.copy(options), scope.$eval(attrs.uiContextMenu));
log.i('Create context menu...' + JSON.stringify(opts));
if(!ctrl)
return;
// add uuid
if(!el.attr(attrId)){
el.attr(attrId, Math.guid());
}
var createMenu = function(){
if(!ctrl.$modelValue || !ag.isArray(ctrl.$modelValue)){
log.w('Context menu model must be an array!');
return null;
}
var menuTpl = '<ul id="{2}" class="{0}"><div class="{1}"></div></ul>';
var inner = menuTpl.format(opts.contextMenuClass, opts.gutterLineClass, el.attr(attrId));
var menu = $(inner).appendTo(document.body);
if(opts.title){
$('<li class="{0}"></li>'.format(opts.headerClass)).text(opts.title).appendTo(menu);
}
var itemTpl = '<li><a href="javascript:void(0);" ui-context-item-id="{0}"><span></span></a></li>';
var i = 0;
for(; i < ctrl.$modelValue.length; i++){
var item = ctrl.$modelValue[i];
if(item){
var row = $(itemTpl.format(item.id || ''));
var rowSpan = row.findAll('span');
rowSpan.text(item.label);
if(item.icon){
var icon = $('<img>');
icon.attr('src', conf.contextPre + '/' + item.icon);
icon.insertBefore(rowSpan);
}
row.appendTo(menu);
}else{
$('<li class="{0}"></li>'.format(opts.seperatorClass)).appendTo(menu);
}
}
menu.bind('contextmenu', function(){return false;});
return menu;
};
ctrl.$render = function(){
var contextMenuId = el.attr(attrId);
if(!contextMenuId){
log.w('No context menu id!');
return;
}
// dom already exists, remove first
$('#' + contextMenuId).remove();
el.unbind('contextmenu').bind('contextmenu', function(e){
var thisContextMenu = $('#' + contextMenuId);
var isAlreadyExists = !!thisContextMenu.length;
var menu = isAlreadyExists ? thisContextMenu : createMenu();
if(!menu){
log.w('No context menu model!');
return;
}
var left = e.pageX + 5;
var top = e.pageY;
if (top + menu.height() >= $(window).height()){
top -= menu.height();
}
if (left + menu.width() >= $(window).width()){
left -= menu.width();
}
menu.css({zIndex: opts.zIndex || 1000001, left: left, top: top}).show();
if(!isAlreadyExists){
// Cover rest of page with invisible div that when clicked will cancel the popup.
var bg = $('<div></div>')
.css({left: 0, top: 0, width: '100%', height: '100%', position: 'absolute', zIndex: 1000000})
.appendTo(document.body)
.bind('contextmenu click', function(){
bg.remove();
menu.remove();
return false;
});
menu.findAll('a').click(function(e){
bg.remove();
menu.remove();
// call back, id as parameter
var itemId = $(this).attr('ui-context-item-id');
if(itemId && opts.fn){
var getter = $parse(opts.fn);
var fnTarget = getter(scope);
if(fnTarget){
scope.$apply(function(){
fnTarget(itemId);
});
}
}
return false;
});
}
// cancel browser context menu
return false;
});
};// \ctrl.render
}// \link
};
}]);
// progress bar
md.directive('uiProgressBar', ['uiLog', function(log){
'use strict';
return {
require: 'ngModel',
restrict: 'A',
replace: true,
template: '<div class="box ng-ui-progressbar">' +
'<div class="ng-ui-progressbar-text"></div>' +
'<div class="ng-ui-progressbar-value"></div>' +
'</div>',
link: function(scope, el, attrs, ctrl){
var opts = scope.$eval(attrs.uiProgressBar) || {};
var textPre = opts.textPre || 'Completed';
ctrl.$render = function(){
if(!ag.isNumber(ctrl.$modelValue) || ctrl.$modelValue < 0 || ctrl.$modelValue > 100){
log.w('Progress bar model must be a number between 0-100!');
return;
}
var textDiv = el.findAll('.ng-ui-progressbar-text');
var valDiv = el.findAll('.ng-ui-progressbar-value');
textDiv.text(textPre + ctrl.$modelValue + '%');
valDiv.css({width: ctrl.$modelValue + '%'});
};
}// \link
};
}]);
// key enter
md.directive('uiEnter', function(){
return {
restrict: 'A',
link: function(scope, el, attrs){
el.keyup(function(e){
// return
if(13 != e.keyCode)
return;
scope.$apply(function(){
scope.$eval(attrs.uiEnter);
});
});
}
};
});
// raw event when do dom operation only, no $digest
md.directive('uiRawBind', function(){
return {
restrict: 'A',
link: function(scope, el, attrs){
el.on(attrs.bindType || 'click', function(e){
scope.$eval(attrs.uiRawBind)
});
}
};
});
// shortcuts
md.directive('uiShortkey', ['safeApply', 'uiLog', function(safeApply, log){
'use strict';
return {
restrict: 'A',
link: function(scope, el, attrs, ctrl){
if(!attrs.uiShortkey){
return;
}
log.i('Init shortkey : ');
log.i(attrs.uiShortkey);
$.hotkeys.add(attrs.uiShortkey, function(){
safeApply(scope, function(){
scope.$eval(attrs.uiShortkeyFn);
});
});
}// \link
};
}]);
// placeholder for IE9-, not prefer
md.directive('uiPlaceholder', ['uiLog', function(log){
'use strict';
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attrs){
// only ie
if(!$.browser.msie){
log.i('No IE not neccessory!');
return;
}
if('INPUT' != el[0].nodeName){
log.w('Init extPlaceholder failed : not a INPUT element!');
return;
}
var placeholderTxt = el.attr('placeholder');
if(!placeholderTxt){
log.w('No placeholder set!');
return;
}
var opts = scope.$eval(attrs.extPlaceholder) || {};
log.i('Init extPlaceholder ' + placeholderTxt + ' - ' + JSON.stringify(opts));
// bind click/blur/change
el.bind({
click: function(e){
var val = $(this).val().trim();
if(val == placeholderTxt)
el.val('').removeClass('ng-blur-placeholder');
},
focus: function(e){
var val = $(this).val().trim();
if(val == placeholderTxt)
el.val('').removeClass('ng-blur-placeholder');
},
blur: function(e){
var val = $(this).val().trim();
if(val == '')
el.val(placeholderTxt).addClass('ng-blur-placeholder');
},
change: function(e){
var val = $(this).val().trim();
if(val == '')
el.val(placeholderTxt).addClass('ng-blur-placeholder');
},
keyup: function(e){
var val = $(this).val().trim();
if(!val || val == placeholderTxt)
el.val(placeholderTxt).addClass('ng-blur-placeholder');
}
});
// add watch when model is not undefined
scope.$watch(attrs.ngModel, function(val){
if(!val && !el.val()){
el.val(placeholderTxt).addClass('ng-blur-placeholder');
}else{
el.removeClass('ng-blur-placeholder');
}
});
}// \link
};
}]);
// list watch
// use ng-init/ng-change work with ng-repeat better
// this $watch support edit form with model assigned, fire $digest, ng-change do not as user not trigger browser event yet
md.directive('uiListWatch', ['$parse', 'uiLog', function($parse, log){
'use strict';
return {
restrict: 'A',
require: 'ngModel',
// begin link ***
link: function(scope, el, attrs, ctrl){
var opts = scope.$eval(attrs.uiListWatch) || {};
if(!opts.targetProperty || !opts.fn){
log.w('Directive list watch targetProperty/fn required!');
return;
}
var getter = $parse(opts.fn);
var fnTarget = getter(scope);
if(!fnTarget || !ag.isFunction(fnTarget)){
log.w('Directive list watch fn should be a function!');
return;
}
scope.$watch(attrs.ngModel, function(val){
var getter = $parse(opts.targetProperty);
var setter = getter.assign;
setter(scope, fnTarget(val));
});
}
// end link ***
}; // end return
}]);
// validation -> donot watch $validity/$required (binding ng-show etc.), use tips instead
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
md.directive('uiValid', ['$parse', 'ng.config', 'uiLog', 'uiValid', 'uiTips', function($parse, conf, log, valid, tips){
'use strict';
var uiValidAttrIdName = 'ui-valid-id';
var uiValidRefered = {};
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, el, attrs, ctrl){
// add guid to this element
var validId = el.attr(uiValidAttrIdName);
if(!validId){
validId = Math.guid();
el.attr(uiValidAttrIdName, validId);
}
var getRules = function(){
return attrs.uiValid;
};
// require not show tips
var notHoverShow = 'true' == attrs.uiValidNotHover;
var lastOldRules;
var validFn = function(value, oldRules){
var sp = '__';
var rules = getRules();
var r = valid.check(value, rules, scope, attrs.uiValidTips, {thisModel: attrs.ngModel});
if(lastOldRules && !oldRules)
oldRules = lastOldRules;
if(r.flag && oldRules){
rules = rules ? rules + ' ' + oldRules : oldRules;
}
if(rules){
// set form $error
var arrInner = rules.split(' ').unique();
var i = 0;
for(; i < arrInner.length; i++){
var oneRule = arrInner[i];
if(!oneRule.trim())
continue;
ctrl.$setValidity(attrs.ngModel + sp + oneRule, r.flag ? true : oneRule != r.rule);
}
}
if(!r.flag){
tips.on(el, r.msg, notHoverShow && 'r' == r.rule);
}else{
tips.off(el);
}
return r.flag;
};
var init = function(){
var rules = getRules();
log.i('Init valid : ' + attrs.ngModel);
log.i(rules);
if(!rules)
return;
// clear ctrl.$parsers, use uiTips.on/off instead $watch form's $error, as ng-show effect layout
// donot use angluar valid function (in $parse array)
// tips: donot use email/url directives provided by angular
if(ctrl.$parsers && ctrl.$parsers.length > 0){
ctrl.$parsers.clear();
}
if(ctrl.$formatters && ctrl.$formatters.length > 0){
ctrl.$formatters.clear();
}
ctrl.$parsers.unshift(function(value){
return validFn(value) ? value : undefined;
});
// set model value directly need not validate again unless ctrl.$invalid === true
ctrl.$formatters.unshift(function(value){
if(value !== undefined &&
(ctrl.$invalid || el.hasClass(conf.invalidClass))){
validFn(value);
}
return value;
});
};
// validation relative to other model
// if rules is dynamical, make sure that rules first set has target model declaration
// because bellow block only run once
var rules = getRules();
if(rules){
var arr = rules.split(' ');
var watchedLl = [];
// it sucks...
var i = 0;
for(; i < arr.length; i++){
if(!arr[i].contains(':'))
continue;
var ruleArr = arr[i].split(':');
if(!['num', 'date', 'watch'].contains(ruleArr[0]))
continue;
// eg. num:range:targetModelName/date:range:targetModelName/watch:targetModelName1,targetModelName2
var modelName = ruleArr['watch' == ruleArr[0] ? 1 : 2];
var modelArr = modelName.split(/,/);
var j = 0;
for(; j < modelArr.length; j++){
var targetModelName = modelArr[j];
// already watched
if(watchedLl.contains(targetModelName))
continue;
log.i('Add watch for valid check : ' + targetModelName);
scope.$watch(targetModelName, function(){
/*
if you donot want to valid if it's not dirty, add function bellow:
valid.filterWatchValid = function(ctrl){
return ctrl.$dirty;
};
*/
if((valid.filterWatchValid && valid.filterWatchValid(ctrl, attrs)) ||
!valid.filterWatchValid){
// valid again
ctrl.$setViewValue(ctrl.$viewValue);
}
}, true);
watchedLl.push(targetModelName);
uiValidRefered[attrs.ngModel] = targetModelName + '|' + ruleArr[0];
}// \for inner
}// \for outer
}
// Watch this model change and check if last bind failed (ctrl.$invalid === true)
// eg.
/*
<select ng-model="optionVal" ui-valid="r"
ng-options="one.code as one.name for one in optionList">
<option value="">--to be choosed--</option>
</select>
var MyCtrl = function($scope){
$scope.optionList = [];
$scope.changeOptionListAndVal = function(){
$scope.optionList = [{code: 'A', name: 'A'}];
$scope.optionVal = 'A';
// tips div should be removed
};
};
*/
// donot use $formaters as always show raw value, even $modelValue valid failed
// change on 2014-08-26.. $watch make performance bad
// scope.$watch(attrs.ngModel, function(){ // if(ctrl.$modelValue !== undefined && // (ctrl.$invalid || el.hasClass(conf.invalidClass))){ // validFn(ctrl.$modelValue); // } // });
// Watch for changes to the directives options
// if validation rules change, initialize again
scope.$watch(getRules, function(newRules, oldRules){
init();
oldRules = oldRules || '';
if(lastOldRules)
oldRules += ' ' + lastOldRules;
lastOldRules = oldRules;
// not bind yet (validate failed or first initialization) include ngModelController initialize value : NaN
if(ctrl.$modelValue === undefined ||
ctrl.$modelValue === null ||
ctrl.$modelValue !== ctrl.$modelValue){
// bind failed
// check tips has showed
var needValid = false;
if(el.hasClass(conf.invalidClass)){
needValid = true;
}
if(!needValid){
// NaN need not valid
// null need valid (ctrl.$invalid || ctrl.$viewValue === null)
var isValNaN = ctrl.$viewValue !== ctrl.$viewValue;
if(ctrl.$invalid ||
(ctrl.$viewValue !== undefined && !isValNaN)){
needValid = true;
}
}
if(needValid){
ctrl.$setViewValue(ctrl.$viewValue);
}
}else{
if(!ctrl.$dirty && attrs.dirtyCheck){
log.i('Skip valid if need not check when undirty...');
}else{
validFn(ctrl.$modelValue, oldRules);
}
}
}, true);
}
};
}]);
// layout tips : most of time you donot need this directive
// because it uses some class defined to add width to td/th
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
md.directive('uiLayoutCol', ['ng.config', 'uiLog', function(conf, log){
'use strict';
return {
restrict: 'A',
link: function(scope, el, attrs, ctrl){
if('TR' != el[0].nodeName){
log.w('Init uiLayoutCol failed : not a TR element!');
return;
}
log.i('Relayout...');
var _tds = el.children('td');
if(_tds.length == 2){
_tds.filter(':first').addClass('l');
_tds.filter(':last').addClass('r');
}else if(_tds.length == 4){
_tds.filter(':even').addClass('l2');
_tds.filter(':odd').addClass('r2');
}else if(_tds.length == 6){
_tds.eq(0).addClass('l3');
_tds.eq(1).addClass('r3');
_tds.eq(2).addClass('l3');
_tds.eq(3).addClass('r3');
_tds.eq(4).addClass('l3');
_tds.eq(5).addClass('r3last');
}
// siblings tr set td text-align to right if exists label
_tds = el.siblings('tr').children('td');
_tds.filter(function(){
return $(this).findAll('label').length > 0 && !$(this).hasClass('al');
}).addClass('ar');
_tds.filter(function(){
return $(this).findAll('label').length == 0;
}).addClass('al p_left5');
}
};
}]);
// pagination view
md.directive('uiPagi', ['ng.config', 'uiPager', function(conf, pager){
return {
restrict: 'A',
// templateUrl: conf.context + 'tpl/pagi.html', templateUrl: conf.context + 'tpl/pagisel.html', replace: false,
scope: {
pager: '=',
onChangePage: '&'
},
link: function(scope, el, attrs){
var opts = scope.$eval(attrs.uiPagi) || {};
scope.$watch('pager', function(it){
scope.pagi = pager.gen(it, opts);
}, true);
}
};
}]);
// change new style base on bootstrap, use this instead uiPagi
// you can also merge two in one, using template cache, compile different template
md.directive('uiPagiNew', ['ng.config', 'uiPager', function(conf, pager){
return {
restrict: 'A',
templateUrl: conf.context + 'tpl/pagisel_new.html',
replace: false,
scope: {
pager: '=',
onChangePage: '&'
},
link: function(scope, el, attrs){
var opts = scope.$eval(attrs.uiPagi) || {};
scope.$watch('pager', function(it){
scope.pagi = pager.gen(it, opts);
}, true);
}
};
}]);
// change new style base on bootstrap, use this instead uiPagi
// you can also merge two in one, using template cache, compile different template
md.directive('uiPagiNews', ['ng.config', 'uiPager', function(conf, pager){
return {
restrict: 'A',
templateUrl: conf.context + 'tpl/pagiNew.html',
replace: false,
scope: {
pager: '=',
onChangePage: '&'
},
link: function(scope, el, attrs){
var opts = scope.$eval(attrs.uiPagi) || {};
scope.$watch('pager', function(it){
scope.pagi = pager.gen(it, opts);
}, true);
}
};
}]);
// sort
md.directive('uiSort', ['ng.config', 'uiLog', '$parse', function(conf, log, $parse){
'use strict';
return {
restrict: 'A',
link: function(scope, el, attrs, ctrl){
var nodeName = el[0].nodeName;
if('TD' != nodeName && 'TH' != nodeName){
log.w('Sort bind failed : not a TD/TH element!');
return;
}
var opts = scope.$eval(attrs.uiSort) || {};
log.i('Init sort : ');
log.i(opts);
if(!opts.targetModel){
log.w('Init sort fail : targetModel required!');
return;
}
el.addClass('ng-ui-sort-all');
var sortModel = function(isUp){
var targetModel = opts.targetModel;
var getter = $parse(targetModel);
var model = getter(scope);
if(!model || !ag.isArray(model)){
log.w('Event trigger sort fail : targetModel required and must be a list!');
return;
}
var fnCompareCallback;
if(opts.fnCompare){
var getterCompare = $parse(opts.fnCompare);
fnCompareCallback = getterCompare(scope);
}
var sortedModel;
// use string localeCompare
if(opts.sortLocale){
var fnSortLocale = function(a, b){
if(!opts.field)
return 0;
if(!a)
return -1;
if(!b)
return 1;
var val1 = a[opts.field];
var val2 = b[opts.field];
if(!ag.isString(val1))
val1 = '' + val1;
if(!ag.isString(val2))
val2 = '' + val2;
return val1.localeCompare(val2);
};
model.sort(function(a, b){
return isUp ? fnSortLocale(a, b) : fnSortLocale(b, a);
});
sortedModel = model;
}else{
sortedModel = _.sortBy(model, function(it, index){
if(fnCompareCallback){
return fnCompareCallback(it, index, isUp);
}else{
if(!opts.field){
return 0;
}else{
var val = it[opts.field];
if(!val){
return 0;
}else if(ag.isDate(val)){
return isUp ? val.getTime() : (0 - val.getTime());
}else if(ag.isNumber(val)){
return isUp ? val : (0 - val);
}else if(ag.isString(val)){
try{
var intVal = parseFloat(val);
return isUp ? intVal : (0 - intVal);
}catch(e){
log.e(e);
return 0;
}
}else{
return 0;
}
}
}
});
}
scope.$apply(function(){
var setter = getter.assign;
setter(scope, sortedModel);
});
};
var resetSortedClass = function(element, suf1, suf2, addedSuf3){
var pre = 'ng-ui-sort-';
element.removeClass(pre + suf1).removeClass(pre + suf2).addClass(pre + addedSuf3);
};
var eventTriggerType = opts.eventTriggerType || 'click';
el.unbind(eventTriggerType).bind(eventTriggerType, function(e){
e.preventDefault();
var isUp = !$(this).hasClass('ng-ui-sort-down');
sortModel(isUp);
resetSortedClass(el, 'all', isUp ? 'up' : 'down', isUp ? 'down' : 'up');
// reset others' style
var others = el.siblings('td,th').filter('.ng-ui-sort-down,.ng-ui-sort-up');
resetSortedClass(others, 'up', 'down', 'all');
return false;
});
}
};
}]);
// PortalTab integration, open a new iframe tab when alinks/button click triggered
md.directive('uiTab', ['ng.config', 'uiLog', 'uiPortalUtils', function(conf, log, PortalUtils){
'use strict';
return {
restrict: 'A',
link: function(scope, el, attrs, ctrl){
if('A' != el[0].nodeName){
log.w('Rebind open tab failed : not a A element!');
return;
}
// need tabId
var opts = scope.$eval(attrs.uiTab) || {};
log.i('Rebind open tab : ');
log.i(opts);
el.click(function(e){
e.preventDefault();
e.stopPropagation();
var tabId = el.attr('tab-id') || opts.tabId;
if(!tabId){
log.w('Skip open tab as no tabId given!');
return false;
}
var url = el.attr('href');
var title = el.attr('title');
PortalUtils.openTab(tabId, url, title, opts);
return false;
});
}
};
}]);
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
// delegate dom event binding
md.directive('uiDelegateBind', ['$parse', 'uiLog', function($parse, log){
'use strict';
// link
return {
restrict: 'A',
link: function(scope, el, attrs){
var opts = scope.$eval(attrs.uiDelegateBind) || {};
log.i('Init delegate bind : ');
log.i(opts);
if(!opts.tag || !opts.fn){
log.w('Init delegate bind fail : tag/fn required!');
return;
}
el.delegate(opts.tag, opts.eventType || 'click', function(){
var getter = $parse(opts.fn);
var fnTarget = getter(scope);
if(fnTarget){
// pass attribute info as parameter
var attrMap = {};
var i = 0;
for(; i < this.attributes.length; i++){
var attr = this.attributes[i];
attrMap[attr.name] = attr.value;
};
scope.$apply(function(){
fnTarget(attrMap);
});
}
});
}// \link
};
}]); // end directive
// *** *** *** *** *** *** *** *** *** ***
// *** *** *** *** *** *** *** *** *** ***
// donot use select directive... data-raw="true" hack angular
// not the angular way but performance better
md.directive('uiOptions', ['$parse', 'uiLog', function($parse, log){
'use strict';
var UI_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+([^\.]+)\s+in\s+(\S+)$/;
// link
return {
restrict: 'A',
require: 'ngModel',
priority: 8000,
link: function(scope, el, attrs, ctrl){
var optionsExp = attrs.uiOptions;
log.i('Init options : ' + optionsExp);
var match = optionsExp.match(UI_OPTIONS_REGEXP);
if(!match){
log.w('Not options regexp match!');
return;
}
var isMulti = attrs.multiple;
var valueKey = match[1];
var displayKey = match[2];
var tmpVal = match[3];
var listVal = match[4];
if(!displayKey && valueKey){
displayKey = valueKey;
valueKey = null;
}
if(!displayKey && !valueKey){
log.w('No display / value key given!');
return;
}
var pre = tmpVal + '.';
if(displayKey && displayKey.startsWith(pre))
displayKey = displayKey.substring(pre.length);
else
displayKey = null;
if(valueKey && valueKey.startsWith(pre))
valueKey = valueKey.substring(pre.length);
else
valueKey = null;
var rawHtml = el.html().trim();
// comment this just make sure user will not set $modelValue -> null / not in options list
if(!rawHtml)
rawHtml = '<option value="" selected></option>';
scope.$watch(listVal, function(list){
if(!list){
el.empty();
el.html(rawHtml);
return;
}else{
var pendHtml = '';
// reuse option node
var sel = el[0];
var optionList = sel.getElementsByTagName('option');
var i = 0, len = list.length;
for(; i < len; i++){
var it = list[i];
var title = displayKey ? it[displayKey] : it;
var value = i;
if(attrs.linkValue && valueKey)
title = it[valueKey] + attrs.linkValue + title;
if(optionList.length > i){
var one = optionList[i];
one.value = value;
one.text = title;
one.title = title;
}else{
pendHtml += '<option title="' + title + '" value="' + value + '">' + title + '</option>';
}
}
if(optionList.length > len){
while(optionList.length > len)
sel.remove(len);
}
el.prepend(rawHtml);
el.append(pendHtml);
if(ctrl.$modelValue){
el.val(val2view(ctrl.$modelValue));
}else{
// el.val('') will not make first option selected
el.findAll('option[value=""]').eq(0).attr('selected', true);
}
}
resetTitle();
}, true);
var resetTitle = function(){
var option = el.find('option:selected:first');
el.attr('title', option.length ? option.text() : '');
};
ctrl.$render = function(){
if(el.data('norender'))
return;
var targetVal = val2view(ctrl.$modelValue);
if(targetVal == -1){
el.findAll('option[value=""]').eq(0).attr('selected', true);
}else{
el.val(targetVal);
}
resetTitle();
};
// model to view (select index)
var getIndexByKey = function(list, val, key){
var indexLl = getIndexListByKey(list, [val], key);
return indexLl.length ? indexLl[0] : -1;
};
var getIndexListByKey = function(list, valList, key){
var indexLl = [];
if(!list || !list.length || !valList || !valList.length)
return indexLl;
var i = 0;
for(; i < list.length; i++){
var one = list[i];
if((key && valList.contains(one[key])) || (!key && valList.contains(one)))
indexLl.push(i);
}
return indexLl;
};
// view to model
var getValueByIndex = function(list, index, key){
var valueLl = getValueListByIndex(list, [index], key);
return valueLl.length ? valueLl[0] : null;
};
var getValueListByIndex = function(list, indexList, key){
var valList = [];
if(!list || !list.length || !indexList || !indexList.length)
return valList;
var i = 0;
for(; i < indexList.length; i++){
var index = indexList[i];
// '0' is string -> true
if(!index)
continue;
var one = list[index];
if(one){
valList.push(key ? one[key] : one);
}
}
return valList;
};
var val2view = function(modelVal){
var targetVal;
var listModelVal = $parse(listVal)(scope);
if(isMulti){
targetVal = getIndexListByKey(listModelVal, modelVal, valueKey);
}else{
targetVal = getIndexByKey(listModelVal, modelVal, valueKey);
}
return targetVal;
};
var val2model = function(selectedVal){
var modelVal;
var listModelVal = $parse(listVal)(scope);
if(isMulti){
modelVal = getValueListByIndex(listModelVal, selectedVal, valueKey);
}else{
modelVal = getValueByIndex(listModelVal, selectedVal, valueKey);
}
return modelVal;
};
el.change(function(e){
var selectedVal = el.val();
var modelVal = val2model(selectedVal);
el.data('norender', 'true');
scope.$apply(function(){
ctrl.$setViewValue(modelVal);
});
el.removeData('norender');
resetTitle();
});
}// \link
};
}]); // end directive
})(angular);
// cmd adaptor for pawa old version define('ng/ng.config', {init: function(){}}); define('ng/ng.service', {init: function(){}}); define('ng/ng.ui', {init: function(){}});