2016-06-09 14 views
7

(Tôi xin lỗi nếu tiêu đề câu hỏi của tôi không tốt lắm, tôi không thể nghĩ ra một câu hỏi hay hơn. Vui lòng đề xuất các tùy chọn tốt hơn.)Ràng buộc các thuộc tính sâu tùy ý trên một đối tượng dựa trên các quy tắc

Tôi đang cố gắng tạo ra một "lưới điện bất động sản" có thể tái sử dụng trong Angular, nơi mà người ta có thể ràng buộc một đối tượng vào lưới điện, nhưng theo cách mà trình bày của đối tượng có thể được tùy chỉnh một chút.

Đây là những gì các mẫu chỉ trông giống như (những form-element là không quan trọng đối với câu hỏi của tôi, vì vậy tôi sẽ để nó ra):

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

và mã chỉ thị:

angular.module("app.shared").directive('propertyGrid', ['$log', function($log) { 
    return { 
     restrict: 'E', 
     scope: { 
      propertyObject: '=', 
      propertyData: '&' 
     } 
     templateUrl: 'views/propertyGrid.html' 
    }; 
}]); 

Dưới đây là một cách sử dụng ví dụ:

<property-grid edit-mode="true" 
       property-object="selectedSite" 
       property-data="getSitePropertyData(object)"> 
</property-grid> 

getSitePropertyData() chức năng tha t đi với nó:

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

Lý do tôi đang trải qua một chức năng "dữ liệu bất động sản" như vậy và không chỉ ràng buộc trực tiếp đến tài sản trên đối tượng là tôi cần phải kiểm soát thứ tự của các thuộc tính, cũng cho dù họ có được hiển thị cho người dùng chút nào hay không và cũng là loại tài sản gì (văn bản, email, số, ngày, v.v.) vì mục đích trình bày.

Lúc đầu, khi bạn có thể biết số dư tài sản value trong hàm getSitePropertyData(), trước tiên tôi đã thử cung cấp các giá trị trực tiếp từ hàm này, nhưng điều đó sẽ không ràng buộc với đối tượng, do đó thay đổi trong đối tượng hoặc biểu mẫu lưới thuộc tính không đồng bộ qua lại. Tiếp theo, sau đó, đã sử dụng ý tưởng key, cho phép tôi thực hiện điều này: propertyObject[prop.key] —các tác phẩm tuyệt vời cho thuộc tính trực tiếp, nhưng như bạn thấy, tôi phải nhận xét trường "Công ty" vì đó là thuộc tính của một thuộc tính và propertyObject["a.b"] không hoạt động.

Tôi đang cố gắng tìm ra những việc cần làm ở đây. Tôi cần các ràng buộc để làm việc, và tôi cần để có thể sử dụng các thuộc tính sâu tùy ý trong các ràng buộc của tôi. Tôi biết loại điều này về mặt lý thuyết là có thể; Tôi đã nhìn thấy nó được thực hiện ví dụ trong UI Grid, nhưng các dự án như vậy có rất nhiều mã mà tôi có lẽ sẽ dành nhiều ngày tìm hiểu cách họ làm điều đó.

Tôi có gần gũi không, hoặc tôi đang đi về điều này tất cả sai?

Trả lời

2

Bạn muốn chạy biểu thức góc tùy ý trên một đối tượng. Đó chính xác là mục đích của $parse (ref). Dịch vụ này cũng có thể ...phân tích một biểu thức Góc và trả về một getter và setter. Ví dụ sau đây là một thực hiện đơn giản đi của chỉ thị formElement của bạn, thể hiện việc sử dụng $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); 
     } 
     }); 
    } 
    }; 
}]); 

Tôi đã thay đổi một chút cách chỉ thị được sử dụng, hy vọng mà không thay đổi ngữ nghĩa:

<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"> 

đâu root-obj là đầu mô hình và path là biểu thức để tiếp cận dữ liệu thực tế.

Như bạn thấy, $parse tạo hàm getter và setter cho biểu thức đã cho, cho bất kỳ đối tượng gốc nào. Trong thuộc tính model.data, bạn áp dụng các hàm truy cập được tạo bởi $parse đối tượng gốc. Toàn bộ cấu trúc Object.defineProperty có thể được thay thế bằng đồng hồ, nhưng điều đó sẽ chỉ thêm phí vào chu trình tiêu hóa.

Đây là một fiddle làm việc: https://jsfiddle.net/zb6cfk6y/


Bằng cách này, một cách khác (ngắn gọn và nhiều thành ngữ) để viết get/set sẽ là:

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

Khi bạn tạo lastSitePropertyData bạn có thể tạo ra các đối tượng theo cách này để không hardcode nó

function createObject(){ 
    for(var key in site){ 
     lastSitePropertyData.push({key:key, value:site[key], editable: true, type:"text"}); 
} } 

Và sử dụng chức năng sau để có được một cái gì đó dữ liệu như thế này

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

Bằng cách đó có thể được sử dụng trong html {{getKey(prop)}}{{getValue(prop}} Để làm bản trình diễn, vui lòng xem liên kết này - https://jsfiddle.net/718px9c2/4/

Lưu ý: Ý tưởng chỉ để truy cập dữ liệu json theo cách tốt hơn, tôi không sử dụng góc trong bản trình diễn.

+0

Tôi nghĩ rằng bỏ lỡ câu trả lời của bạn hai điểm quan trọng nhất trong câu hỏi của tôi. Đầu tiên, tôi nói "Lý do tôi đi qua một hàm" dữ liệu thuộc tính "và không chỉ ràng buộc trực tiếp đến các thuộc tính trên đối tượng là tôi cần kiểm soát thứ tự của các thuộc tính." Sử dụng vòng lặp for sẽ không cho phép điều khiển như vậy. Thứ hai, tôi nói "Lần đầu tiên tôi thử cung cấp các giá trị trực tiếp từ hàm này, nhưng điều đó sẽ không liên kết với đối tượng, do đó thay đổi trong đối tượng hoặc biểu mẫu lưới thuộc tính không đồng bộ qua lại". Giải pháp của bạn sẽ không cho phép ràng buộc hai chiều bắt buộc này. – Alex

0

Một ý tưởng khác là làm như thế này. Nếu bạn không muốn làm cho object.proto bẩn (điều này luôn luôn là ý tưởng tốt) chỉ cần di chuyển chức năng này vào module khác.

(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

Nhưng điều này sẽ không ràng buộc đối tượng với mô hình góc, phải không? Vì vậy, bạn không thể chỉnh sửa các giá trị trong biểu mẫu. –

0

Tôi có thứ tương tự được sử dụng để hiển thị dữ liệu trong lưới, những lưới này có thể hiển thị cùng một đối tượng nhưng không có cùng cột. tuy nhiên tôi không xử lý nó trong một lần.

  1. Tôi có một loại dịch vụ mà tôi tuyên bố loại của tôi và một số cấu hình mặc định
  2. Tôi có một dịch vụ lưới mà tạo ra các tùy chọn định nghĩa lưới theo những gì tôi đã chỉ định.
  3. Trong bộ điều khiển, tôi khởi tạo lưới bằng dịch vụ lưới, chỉ định thứ tự của các cột và một số cấu hình cụ thể, ghi đè lên các cột mặc định. Bản thân dịch vụ lưới tạo ra cấu hình thích hợp để lọc, sắp xếp theo định nghĩa kiểu của các trường.
1

Nếu bạn đang sử dụng lodash bạn có thể sử dụng chức năng _.get để đạt được điều này.

Bạn có thể lưu trữ _.get trong bộ điều khiển của property-grid của bạn và sau đó sử dụng

model="get(propertyObject,prop.key)" 

trong mẫu của bạn. Nếu bạn cần chức năng này ở nhiều nơi trong ứng dụng của bạn (và không chỉ trong số property-grid), bạn có thể viết filter cho việc này.


Vấn đề với điều này là bạn không thể ràng buộc mô hình theo cách này và do đó bạn không thể chỉnh sửa giá trị. Bạn có thể sử dụng chức năng _.set và một đối tượng có bộ thu thập và trình cài đặt để thực hiện công việc này.

vm.modelize = function(obj, path) { 
    return { 
     get value(){return _.get(obj, path)}, 
     set value(v){_.set(obj, path,v)} 
    }; 
} 

Sau đó bạn có thể sử dụng chức năng trong mẫu:

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

Đối với một ví dụ giảm thấy Plunker này.


Nếu bạn không sử dụng lodash bạn có thể sử dụng phiên bản này đơn giản hóa của _.get chức năng mà tôi rút ra từ 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; 
} 

Chức năng này đảm bảo rằng bạn sẽ không nhận được bất kỳ lỗi nào Cannot read property 'foo' of undefined. Điều này hữu ích đặc biệt là nếu bạn có các chuỗi thuộc tính dài nơi có thể có một giá trị không xác định. Nếu bạn muốn có thể sử dụng nhiều đường dẫn nâng cao hơn (như foo.bar[0]), bạn phải sử dụng chức năng đầy đủ _.get từ lodash.

Và đây là một phiên bản đơn giản của _.set hình thức cũng trích 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; 
} 

Hãy ghi nhớ rằng các chức năng chiết xuất bỏ qua một số trường hợp cạnh đó lodash tay cầm. Nhưng họ nên làm việc trong hầu hết các trường hợp.

Các vấn đề liên quan