最近一直研究angularjs,其双向绑定的确很强大,看中文社区,官方文档,参考github的ui示例,大概摸索下angularjs的开发方式——和传统的dom编程完全不同,jquery再锋利在angularjs的设计理念中,只能作为幕后者。AMD/CMD虽然能和angularjs一起使用,但angularjs自己的module设计,尤其是自动注入特性,比amd/cmd优越。和jquery plugin的扩展方式一样,amd/cmd期望以另一个非官方标准来积累js扩展,angularjs也是如此,开发者要作出选择,或组合或取舍。
言归正传:贴下一个简单以cmd形式扩展angularjs指令实现结合jquery/plugin的代码,需要的童鞋可以参考下,照此思路可以把jquery的有用的功能移植到angularjs的应用中来
// file ng.config.js define(function(require){ return { init: function(){ // 默认配置 var conf = {}; conf.date = {dateFormat: 'yy-mm-dd'}; conf.autocomplete = {minChars: 3, maxItemsToShow: 20}; var md = angular.module('ng.config', []); md.value('ng.config', conf); } }; }); // file ng.filter.js define(function(require){ return { init: function(){ var md = angular.module('ng.filter', []); // 过滤器方法都会执行两次。。。吐血 // http://stackoverflow.com/questions/11676901/is-this-normal-for-angularjs-filtering // 日期格式转换 md.filter('formatDate', function(){ return function(value, format){ if (!value) return value; return value.format(format || 'yyyy-MM-dd'); }; }); } }; }); // file ng.service.js define(function(require){ return { init: function(){ var md = angular.module('ng.service', []); // 注册服务 // 全局变量获取,一种约定 md.factory('uiGetPageData', ['$window', function(win){ return function(key){ var pageData = win.pageData; return pageData ? pageData[key] : null; }; }]); // 日志 md.factory('uiLog', ['$window', function(win){ return function(msg, level){ if(typeof(msg) != 'string') msg = JSON.stringify(msg); level = level || 'INFO'; if(win.console && win.console.log) win.console.log('[' + level + ']' + msg); }; }]); // 服务请求过滤 md.factory('uiRequest', ['uiLog', function(log){ return { filter: function(params, conf, skipLl){ if(!conf) conf = {dateFormat: 'yyyy-MM-dd'}; var r = {}; if(params){ for(key in params){ if(skipLl && skipLl.contains(key)) continue; var val = params[key]; if(angular.isDate(val)){ r[key] = val.format(conf.dateFormat); }else{ r[key] = val; } } } return r; } }; }]); // 验证服务 md.factory('uiValid', function(){ return { check: function(val, rule){ } }; }); } }; }); // file ng.ui.js define(function(require){ require('ng/ng.config').init(); require('ng/ng.service').init(); require('ng/ng.filter').init(); require('jquery.autocomplete'); require('jquery.bgiframe'); require('jquery.datepicker'); require('jquery.hotkeys'); var ag = window.angular; return { init: function(){ var md = ag.module('ng.ui', ['ng.config', 'ng.service', 'ng.filter']); // 日期选择器 // *** *** *** *** *** *** *** *** *** *** // *** *** *** *** *** *** *** *** *** *** 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(options, scope.$eval(attrs.uiDate)); }; var init = function(){ var opts = getOptions(); log('Init datepicker : '); log(opts); if(ctrl){ // update model when datepicker value changes var updateModel = function(){ scope.$apply(function(){ var date = el.datepicker("getDate"); ctrl.$setViewValue(date); }); }; if(opts.onSelect){ var userHandler = opts.onSelect; opts.onSelect = function(value, picker){ updateModel(); return userHandler(value, picker); }; }else{ opts.onSelect = function(value, picker){ updateModel(); }; } // datepicker后无法按键了 // el.bind('change', updateModel); // Update the date picker when the model changes ctrl.$render = function(){ var date = ctrl.$viewValue; if (ag.isDefined(date) && date !== null && !ag.isDate(date)){ throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string'); } el.datepicker("setDate", date); }; } // If we don't destroy the old one it doesn't update properly when the config changes el.datepicker('destroy'); // Create the new datepicker widget el.datepicker(opts); // Force a render to override whatever is in the input text box ctrl.$render(); }; // Watch for changes to the directives options scope.$watch(getOptions, init, true); } }; }]); // 自动填充 // *** *** *** *** *** *** *** *** *** *** // *** *** *** *** *** *** *** *** *** *** md.directive('uiAutocomplete', ['ng.config', 'uiLog', function(conf, log){ 'use strict'; var options = {}; if(ag.isObject(conf.autocomplete)){ ag.extend(options, conf.autocomplete); } return { restrict: 'A', require: 'ngModel', link: function(scope, el, attrs, ctrl){ var getOptions = function(){ return ag.extend(options, scope.$eval(attrs.uiAutocomplete)); }; var init = function(){ var opts = getOptions(); log('Init autocomplete : '); log(opts); if(!opts.url || !opts.targetModel){ log('Init autocomplete fail : url/targetModel required!'); return; } if(ctrl){ ctrl.$render = function(){ // 自动填充如果没有填充的显示值,就默认取填充值 var showLabel = ctrl.$viewValue; if(!showLabel){ var targetModel = opts.targetModel; var showValue; if(targetModel.contains('.')){ var arr = targetModel.split(/\./); var i = 0; var targetScope = scope; for(; i < arr.length; i++){ var key = arr[i]; if(i == arr.length - 1){ showValue = targetScope[key]; }else{ if(!targetScope[key]) break; targetScope = targetScope[key]; } } }else{ showValue = scope[targetModel]; } if(showValue){ ctrl.$setViewValue(showValue); el.val(showValue); } } }; } // json : // [{data: {result: i}, value: 'Col' + i}] el.autocomplete({ url: opts.url, minChars: opts.minChars, maxItemsToShow: opts.maxItemsToShow, remoteDataType: 'json', useCache: false, processData: function(data) { var i, r = []; for(i = 0; i < data.length; i++){ var item = data[i]; r.push({data: {result: item.v}, value: item.v + '-' + item.l}); } return r; }, onItemSelect: function(item){ var val = item.data.result; var showLabel = item.value; var targetModel = opts.targetModel; if(targetModel.contains('.')){ var arr = targetModel.split(/\./); var i = 0; var targetScope = scope; for(; i < arr.length; i++){ var key = arr[i]; if(i == arr.length - 1){ targetScope[key] = val; }else{ if(!targetScope[key]) targetScope[key] = {}; targetScope = targetScope[key]; } } }else{ scope[targetModel] = val; } scope.$apply(function(){ ctrl.$setViewValue(showLabel); }); } }); ctrl.$render(); }; // Watch for changes to the directives options scope.$watch(getOptions, init, true); } }; }]); // 快捷键 // *** *** *** *** *** *** *** *** *** *** // *** *** *** *** *** *** *** *** *** *** md.directive('uiShortkey', ['ng.config', 'uiLog', function(conf, log){ 'use strict'; var options = {}; return { restrict: 'A', link: function(scope, el, attrs, ctrl){ var getOptions = function(){ return ag.extend(options, scope.$eval(attrs.uiShortkey)); }; var init = function(){ var opts = getOptions(); log('Init shortkey : '); log(opts); if(!opts.key || !opts.method){ log('Init shortkey fail : key/method required!'); return; } $.hotkeys.add(opts.key, function(){ var fn = scope[opts.method]; if(fn) fn.call(); }); }; // Watch for changes to the directives options scope.$watch(getOptions, init, true); } }; }]); // 布局相关 // *** *** *** *** *** *** *** *** *** *** // *** *** *** *** *** *** *** *** *** *** 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) return; log('Relayout...'); var _tds = el.children('td'); if(_tds.size() == 2){ _tds.filter(':first').addClass('l'); _tds.filter(':last').addClass('r'); }else if(_tds.size() == 4){ _tds.filter(':even').addClass('l2'); _tds.filter(':odd').addClass('r2'); }else if(_tds.size() == 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 el.siblings('tr').children('td').filter(function(){ return $(this).find('label').size() > 0; }).addClass('ar'); // set vertical-align = middle for label // el.siblings('tr').children('td').each(function(){ // var _td = $(this); // if(_td.find('label').size() > 0){ // _td.addClass('ar'); // _td.find('label').addClass('vm'); // } // }); } }; }]); } }; });