基于规则绑定到对象的任意深度属性

(如果我的问题标题不是很好,我很抱歉,我想不出更好的选择。请随时提出更好的选择。)

我正在尝试在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;
};

我经历这样一个“属性数据”函数,而不仅仅是直接绑定到对象的属性的原因是,我需要控制属性的顺序,以及它们是否应该显示给用户,以及为了演示的目的,它是什么样的属性(文本,电子邮件,数字,日期等)。

起初,你可以从getSitePropertyData()函数中的value属性getSitePropertyData() ,我首先尝试直接从这个函数中提供值,但不会绑定到对象,所以要么改变对象,要么改变属性的属性电网没有来回同步。 接下来是使用key思想,它让我做到这一点: propertyObject[prop.key] - 这对于直接属性非常propertyObject[prop.key] ,但正如您所看到的,我必须将“Company”字段注释掉,因为它是属性的属性和propertyObject["ab"]不起作用。

我正在努力弄清楚在这里做什么。 我需要绑定才能工作,并且我需要能够在绑定中使用任意深度的属性。 我知道这种事情在理论上是可能的, 我已经看到它在UI Grid中完成,但是这样的项目有很多代码,我可能会花几天的时间找出它们是如何实现的。

我正在接近,还是我对这一切都做错了?


你想在一个对象上运行一个任意的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结构可以被手表取代,但这只会增加摘要循环的开销。

这是一个工作小提琴:https://jsfiddle.net/zb6cfk6y/


顺便说一句,另一种(更简洁和习惯的)写get / set的方法是:

  Object.defineProperty(scope.data, 'model', {
    get: getModel.bind(null, scope.rootObj),
    set: setModel.bind(null, scope.rootObj)
  });

如果您使用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你可以使用我从lodash提取的_.get函数的简化版本。

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错误的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手柄。 但他们应该在大多数情况下工作。


当你创建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。

链接地址: http://www.djcxy.com/p/34513.html

上一篇: Binding to arbitrarily deep properties on an object based on rules

下一篇: Watching generation lists during a program run