最后的小提琴
Angularjs 链式指令替换元素
我的目标是开发一个通用指令来使用 Angularjs 为 Activiti 引擎任务呈现表单元素。为此,我开发了一个指令(比如 dir1),它基于表单元素的某些属性将呈现适当类型的 html 元素(输入(文本、复选框)、选择或跨度)替换 dir1 元素。
采集 Activiti 表单的控制器由以下代码模拟
功能 MyCtrlr($scope) {
$scope.v = [{value: 'init0'},
{value: 'init1'},
{value: 'init2'},
{value: 'init3'}
];
$scope.formVals = {
vals: [{
id: 'one',
type: 'string',
value: 'xyz'
}, {
id: 'two',
type: 'enum',
value: '2',
writable:true,
enumValues: [{
'id': 1,
'name': 'ek'
}, {
'id': 2,
'name': 'don'
}]
}, {
id: 'three',
type: 'enum',
value: 'abc',
writable:true,
enumValues: [{
'id': 3,
'name': 'tin'
}, {
'id': 4,
'name': 'chaar'
}]
}, {
id: 'four',
type: 'enum',
value: 'abc',
writable:true,
enumValues: [{
'id': 5,
'name': 'paach'
}, {
'id': 6,
'name': 'sahaa'
}]
},
{id:'five',
type:'string',
value:'test',
writable:true
}
]
};
//$scope.formVals.vals[0].varRef = $scope.v[0];
//$scope.formVals.vals[1].varRef = $scope.v[1];
$scope.formVals.vals[2].varRef = $scope.v[2];
$scope.formVals.vals[3].varRef = $scope.v[3];
$scope.verify = function () {
alert($scope.v[0].value + '...' + $scope.v[1].value + '...' + $scope.v[2].value + '...' + $scope.v[3].value);
};
}
指令 dir1 如下
app.directive('dir1', function ($compile) {
var getTemplate = function(fld, fvarnm, debug) {
value = ' value="' + fld.value + '"';
nm = ' name="' + fld.id + '"';
ngmodel = ' ng-model="' + fvarnm + '.varRef.value"';
disabled = fld.writable?'':' disabled=disabled';
switch(fld.type) {
case 'activitiUser':
case 'enum':
template = '<select '
+ nm + disabled
+ (fld.varRef != null?ngmodel:'');
template += '<option></option>';
for (e in fld.enumValues) {
selected = '';
ev = fld.enumValues[e];
if ((fld.varRef == null && (fld.value == ev.id)) || (fld.varRef != null) && (fld.varRef.value == ev.id))
selected = ' SELECTED ';
template += '<option value="' + ev.id + '"' + selected + '>' + ev.name + '</option>';
}
template += '</select>';
break;
case 'boolean':
template = '<input type="checkbox"'
+ nm + disabled
+ (fld.varRef != null?ngmodel:value)
+ (fld.value?' CHECKED':'')
+ '></input>';
break;
default:
template = '<input type="text"'
+ nm + disabled
+ (fld.varRef != null?ngmodel:value)
+ ' value-format="' + fld.type + ' '
+ fld.datePattern + '"'
+ '></input>';
}
if (fld.varRef != null && typeof(debug) != 'undefined' && debug.toLowerCase() == 'true') {
template = '<div>' + template
+ '<span ng-bind="' + fvarnm
+ '.varRef.value"></span>' + '</div>';
}
return template;
};
return {
restrict: 'E',
replace: true,
scope : {
field : '='
},
link : function(scope, element, attrs) {
html = getTemplate(scope.field, attrs.field, attrs.debug);
element.replaceWith($compile(html)(scope.$parent));
}
};
});
然而,当 Activiti 之上的应用程序的细微差别出现时,我决定让开发人员能够使用 dir1 来满足他的一般要求,并允许他开发自己的指令链接到 dir1 来处理这些细微差别。关于细微差别——基于表单元素的属性,应用程序开发人员要么使用 dir1 提供的通用渲染,要么将 dir2 元素替换为适当的 html 元素。
我添加了 dir2 如下 -
app.directive('dir2', function ($compile) {
var getTemplate2 = function(scope, el, attrs) {
html2 = "<dir1 field='" + attrs.field + "'></dir1>";
if (scope.field.id == 'five') {
html2 = '<span style="font-weight:bold" ';
if (typeof(scope.field.varRef) != 'undefined' && scope.field.varRef) {
html2 += ' ng-bind="f.varRef.value" ';
} else {
html2 += ' ng-bind="f.value" ';
}
html2 += '></span> ';
}
return html2;
};
return {
restrict: 'E',
replace : true,
scope : {
field : '='
},
link: function (scope, el, attrs) {
var html2 = getTemplate2(scope, el, attrs);
el.replaceWith($compile(html2)(scope.$parent));
}
};
});
但是,我在 dir1 中的 replaceWith 调用中开始出现 null 父级错误。经过大量迷失方向的思考和控制台记录后,我意识到在 el.replaceWith($compile(html2)(scope.$parent)) 语句中编译 html2 的那一刻,只要 html2 是 dir1 元素,就会触发 dir1 链接函数。此时 dir1 元素没有任何 parentNode。因此,我想出了以下安排。在gettemplate2函数中html2的默认值变成了html2="",即传递parent属性。在 dir1 链接函数中,我进行了以下更改 html = getTemplate(scope.field, attrs.field, attrs.debug); scope.dir1el = $compile(html)(scope); if (typeof(attrs.parent) == 'undefined') { element.replaceWith(scope.dir1el); } 从而防止在 dir1 中替换。dir2 的互补变化是
var html2 = getTemplate2(scope, el, attrs);
if (html2 == null) {
$compile("<dir1 parent='true' field='" + attrs.field + "'></dir1>")(scope.$parent);
ne = scope.$$nextSibling.dir1el;
} else {
ne = $compile(html2)(scope.$parent);
}
el.replaceWith(ne);
由于 dir1 和 dir2 是同级指令,我必须使用 $$nextSibling 访问 dir1 范围。因此,我可以根据需要将 dir2 中的元素替换为 dir1 或 dir2 生成的元素。
我还使用属性指令 dir3 开发了一个替代解决方案,其中 dir3 将成为 dir1 的属性。这里 dir1 作用域成为 dir3 的父作用域。并且 dir3 中的定制元素是 replaces element 替换由 dir1 创建的元素。因此,该解决方案涉及双 DOM 替换。