2016-06-09 78 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手柄。但他們應該在大多數情況下工作。