2016-06-09 80 views
7

(对不起,如果我的问题标题不是很好,我想不出一个更好的。随时提出更好的选择。)绑定到任意深度的属性

我想在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()功能THA t趋于吧:

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; 
}; 

我要通过这样的“属性数据”功能,而不仅仅是直接绑定到属性的对象上的原因是,我需要控制属性的顺序,以及因为它们是否应该甚至向用户展示,以及为了演示的目的它是什么样的属性(文本,电子邮件,数字,日期等)。

起初,您可以从函数中的value属性残余中知道,我首先尝试直接从该函数提供值,但不会绑定到该对象,因此可以更改对象或窗体物业网格不会来回同步。接下来是使用key的想法,它可以让我这样做:propertyObject[prop.key]-这对于直接属性很好,但正如你所看到的,我必须注释掉“公司”字段,因为它是属性的属性,并且propertyObject["a.b"]不起作用。

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

我靠近了吗?还是我在谈论这一切都是错误的?

回答

2

您想要在对象上运行任意的Angular表达式。这正是$parseref)的目的。这项服务可以...解析一个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) 
    }); 
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。

+0

我认为你的回答失误在我的问题中最重要的两点。首先,我说“我之所以要通过这样的”属性数据“函数,而不是直接绑定到对象的属性上,是因为我需要控制属性的顺序。”使用for循环将不允许这种控制。其次,我说:“我首先尝试直接从这个函数提供值,但不会绑定到对象,所以更改对象或形成属性网格不会来回同步。”您的解决方案不会允许这种所需的双向绑定。 – Alex

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 
    }); 


}()); 
+0

但是这不会将对象绑定到角度模型,对吧?所以你不能编辑表单中的值。 –

0

我也有类似的用来显示网格数据的东西,这些网格可以显示相同的对象还没有相同的列。但是我不能一劳永逸。

  1. 我有一个类型的服务,我宣布我的类型和一些默认配置
  2. 我有根据我指定的内容生成网格定义选项的网格服务。
  3. 在控制器中,我使用网格服务实例化网格,指定列的排序和一些特定的配置,这些配置将覆盖默认的配置。网格服务本身为过滤生成适当的配置,使用字段的类型定义进行排序。
1

如果您正在使用lodash可以使用_.get功能来实现这一目标。

您可以存储_.getproperty-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错误。如果您有可能存在未定义值的长链属性,这非常有用。如果您希望能够使用更高级的路径(如foo.bar[0]),则必须使用lodash中的完整_.get功能。

这里是_.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手柄。但他们应该在大多数情况下工作。