(对不起,如果我的问题标题不是很好,我就不会想到更好的问题。请随意提出更好的选择。)
我试图创建一个可重复使用的属性网格"在Angular中,可以将对象绑定到网格,但是这样可以在某种程度上定制对象的呈现。
这是指令模板的样子(form-element
对我的问题并不重要,所以我将其排除在外:
<div ng-repeat="prop in propertyData({object: propertyObject})">
<div ng-switch on="prop.type">
<div ng-switch-when="text">
<form-element type="text"
label-translation-key="{{prop.key}}"
label="{{prop.key}}"
name="{{prop.key}}"
model="propertyObject[prop.key]"
focus-events-enabled="false">
</form-element>
</div>
</div>
</div>
和指令代码:
angular.module("app.shared").directive('propertyGrid', ['$log', function($log) {
return {
restrict: 'E',
scope: {
propertyObject: '=',
propertyData: '&'
}
templateUrl: 'views/propertyGrid.html'
};
}]);
以下是一个示例用法:
<property-grid edit-mode="true"
property-object="selectedSite"
property-data="getSitePropertyData(object)">
</property-grid>
与之相关的getSitePropertyData()
函数:
var lastSite;
var lastSitePropertyData;
$scope.getSitePropertyData = function (site) {
if (site == undefined) return null;
if (site == lastSite)
return lastSitePropertyData;
lastSite = site;
lastSitePropertyData = [
{key:"SiteName", value:site.SiteName, editable: true, type:"text"},
//{key:"Company.CompanyName", value:site.Company.CompanyName, editable: false, type:"text"},
{key:"Address1", value:site.Address1, editable: true, type:"text"},
{key:"Address2", value:site.Address2, editable: true, type:"text"},
{key:"PostalCode", value:site.PostalCode, editable: true, type:"text"},
{key:"City", value:site.City, editable: true, type:"text"},
{key:"Country", value:site.Country, editable: true, type:"text"},
{key:"ContactName", value:site.ContactName, editable: true, type:"text"},
{key: "ContactEmail", value: site.ContactEmail, editable: true, type:"email"},
{key: "ContactPhone", value: site.ContactPhone, editable: true, type:"text"},
{key: "Info", value: site.Info, editable: true, type:"text"}
];
return lastSitePropertyData;
};
我正在浏览这样一个&#34;属性数据&#34;函数,而不仅仅是直接绑定到对象的属性是我需要控制属性的顺序,以及它们是否应该显示给用户,以及它是什么类型的属性(文本,电子邮件) ,数量,日期等)为了介绍。
首先,正如您可以从value
函数中的getSitePropertyData()
属性残差所知,我首先尝试直接从此函数提供值,但这不会绑定到对象,因此在对象中进行更改或形成属性网格不会来回同步。接下来,我正在使用key
这个想法,我可以这样做:propertyObject[prop.key]
- 这对于直接属性非常有用,但正如您所看到的,我必须注释掉&#34;公司&#34;字段,因为它是属性的属性,而propertyObject["a.b"]
不起作用。
我正努力想弄清楚在这做什么。我需要绑定才能工作,我需要能够在绑定中使用任意深度属性。我知道这种事情在理论上是可行的;我已经看到它在例如UI Grid中完成了,但是这样的项目有很多代码,我可能花了几天时间来了解它们是如何做到的。
我是否已接近,或者我是否认为这一切都错了?
答案 0 :(得分:2)
您想在对象上运行任意Angular表达式。这正是$parse
(ref)的目的。这个服务很好...解析一个Angular表达式并返回一个getter和setter。以下示例是formElement
指令的过度简化实现,演示了$parse
的使用:
app.directive('formElement', ['$parse', function($parse) {
return {
restrict: 'E',
scope: {
label: '@',
name: '@',
rootObj: '=',
path: '@'
},
template:
'<label>{{ label }}</label>' +
'<input type="text" ng-model="data.model" />',
link: function(scope) {
var getModel = $parse(scope.path);
var setModel = getModel.assign;
scope.data = {};
Object.defineProperty(scope.data, 'model', {
get: function() {
return getModel(scope.rootObj);
},
set: function(value) {
setModel(scope.rootObj, value);
}
});
}
};
}]);
我稍微改变了指令的使用方式,希望不改变语义:
<form-element type="text"
label-translation-key="{{prop.key}}"
label="{{prop.key}}"
name="{{prop.key}}"
root-obj="propertyObject"
path="{{prop.key}}"
focus-events-enabled="false">
其中root-obj
是模型的顶部,path
是达到实际数据的表达式。
如您所见,$parse
为任何根对象创建给定表达式的getter和setter函数。在model.data
属性中,将$parse
创建的访问器函数应用于根对象。整个Object.defineProperty
构造可以被watch替换,但这只会增加摘要周期的开销。
这是一个工作小提琴:https://jsfiddle.net/zb6cfk6y/
顺便说一句,写入get / set的另一种(更简洁和惯用)方式是:
Object.defineProperty(scope.data, 'model', {
get: getModel.bind(null, scope.rootObj),
set: setModel.bind(null, scope.rootObj)
});
答案 1 :(得分:1)
如果您使用的是lodash
,则可以使用_.get
功能来实现此目的。
您可以将_.get
存储在property-grid
的控制器中,然后使用
model="get(propertyObject,prop.key)"
在你的模板中。如果您在应用程序的多个位置(而不仅仅是property-grid
)中需要此功能,则可以为此编写filter
。
问题在于您无法以这种方式绑定模型,因此您无法编辑这些值。您可以使用_.set
函数和带有getter和setter的对象来实现此功能。
vm.modelize = function(obj, path) {
return {
get value(){return _.get(obj, path)},
set value(v){_.set(obj, path,v)}
};
}
然后您可以在模板中使用该功能:
<div ng-repeat="prop in propertyData({object: propertyObject})">
<input type="text"
ng-model="ctrl.modelize(propertyObject,prop.key).value"
ng-model-options="{ getterSetter: true }"></input>
</div>
有关简化示例,请参阅此Plunker。
如果您不使用lodash
,则可以使用我从_.get
中提取的lodash
函数的简化版本。
function getPath(object, path) {
path = path.split('.')
var index = 0
var length = path.length;
while (object != null && index < length) {
object = object[path[index++]];
}
return (index && index == length) ? object : undefined;
}
此功能可确保您不会收到任何Cannot read property 'foo' of undefined
错误。如果您有可能存在未定义值的长链属性,这将非常有用。如果您希望能够使用更高级的路径(例如foo.bar[0]
),则必须使用_.get
中的完整lodash
功能。
以下是_.set
的简化版本,也是lodash
提取的形式:
function setPath(object, path, value) {
path = path.split(".")
var index = -1,
length = path.length,
lastIndex = length - 1,
nested = object;
while (nested != null && ++index < length) {
var key = path[index]
if (typeof nested === 'object') {
var newValue = value;
if (index != lastIndex) {
var objValue = nested[key];
newValue = objValue == null ?
((typeof path[index + 1] === 'number') ? [] : {}) :
objValue;
}
if (!(hasOwnProperty.call(nested, key) && (nested[key] === value)) ||
(value === undefined && !(key in nested))) {
nested[key] = newValue;
}
}
nested = nested[key];
}
return object;
}
请记住,这些提取的函数会忽略lodash
处理的一些边缘情况。但它们应该在大多数情况下起作用。
答案 2 :(得分:0)
创建lastSitePropertyData时,您可以通过这种方式创建对象而不对其进行硬编码
function createObject (){
for(var key in site){
lastSitePropertyData.push({key:key, value:site[key], editable: true, type:"text"});
} }
后来使用函数来获取类似这样的数据
function getKey(prop){
if(typeof prop.value === 'object'){
return prop.value.key; //can run loop create a go deep reccursive method - thats upto u
}
else return prop.key;
}
function getValue(prop){
if(typeof prop === 'object'){
return prop.value.value; //have tp run loop get value from deep reccursive method - thats upto u
}
else return prop.value;
}
可以在html {{getKey(prop)}}
和{{getValue(prop}}
中使用
有关工作演示,请查看此链接 - https://jsfiddle.net/718px9c2/4/
注意:它只是想以更好的方式访问json数据,我没有在演示中使用angular。
答案 3 :(得分:0)
另一个想法就是像这样做。 如果你不想避免使object.proto脏(这总是好主意)只需将此功能移动到另一个模块。
(function () {
'use strict';
if (Object.hasOwnProperty('getDeep')) {
console.error('object prototype already has prop function');
return false;
}
function getDeep(propPath) {
if (!propPath || typeof propPath === 'function') {
return this;
}
var props = propPath.split('.');
var result = this;
props.forEach(function queryProp(propName) {
result = result[propName];
});
return result;
}
Object.defineProperty(Object.prototype, 'getDeep', {
value: getDeep,
writable: true,
configurable: true,
enumerable: false
});
}());
答案 4 :(得分:0)
我有类似的东西用于在网格中显示数据,这些网格可能显示相同的对象但没有相同的列。但是我不能一气呵成。