Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.

Join them; it only takes a minute:

Sign up
Join the Stack Overflow community to:
  1. Ask programming questions
  2. Answer and help your peers
  3. Get recognized for your expertise

Given an array of objects (such as would represent a selection of items, for example), and an input, how do you go about binding the input value so that it represents a given property of all the objects?

The input should display state in the manner:

  • if all values for this property are the same on all objects, display that value
  • if at least one value is not the same, set the input.value to 'multiple'
  • if all values are undefined, set the input.value to 'none'

I have the function that aggregates the values for a given property exposed on the scope:

// Return value (if all values are equal) or undefined (if not)
scope.getSelection('property')

I also have the function that sets a value on all the objects:

scope.setSelection('property', value)

I can't find a combination ng-value, ng-model and ng-change that allows me to both get from .getSelection() and set to .setSelection() automatically, so I'm assuming I have to write a new directive.

What's the idiomatic way to solve this problem?

share|improve this question
    
do you plan to select a property from a select then checkCondition and return a result? do you have one input or input per property? – Poyraz Yilmaz Mar 26 '14 at 11:49
    
If your application will run only on fairly modern browsers, maybe getters/setters will help you: Documentation – Nikos Paraskevopoulos Mar 26 '14 at 11:53
    
@wickY26 If you mean a <select>, then no, I am not using a select. I have one input per property. – stephband Mar 26 '14 at 11:53
    
@Nikos Paraskevopoulos Perhaps. i don't think that changes much. I still have to make a controller for each input in order to declare a new scope with the getter / setter on it, and even then the set will have to take place inside a $scope.$apply or angular won't know to propagate updates... – stephband Mar 26 '14 at 11:58
    
I am not sure I understand your concept correctly. Is this what you want? – Nikos Paraskevopoulos Mar 26 '14 at 12:11
up vote 2 down vote accepted

For the sake of future reference, let me write a full answer:

A way to accomplish this in fairly modern browsers is using property getters/setters (spec). An example, proof-of-concept implementation would be:

Let's say the $scope contains the following collection:

$scope.items = [
    {id: 1, prop: "a"},
    {id: 2, prop: "a"},
    {id: 3, prop: "a"}
];

And we want to manipulate the aggregate of the item.prop property. We define another object as:

$scope.form = {
    get aggregate() {
        var res, i;
        for( i=0; i < $scope.items.length; i++ ) {
            if( typeof(res) === "undefined" ) {
                res = $scope.items[i].prop;
            }
            else if( $scope.items[i].prop !== res ) {
                return "(multiple)";
            }
        }
        return res;
    },
    set aggregate(val) {
        var i;
        for( i=0; i < $scope.items.length; i++ ) {
            $scope.items[i].prop = val;
        }
    }
};

The form.aggregate property now has a getter and setter. These function handle their values by iterating over $scope.items. The getter compares the values and returns the common one, if all are the same or "(multiple)" if at least one is different. The setter just sets the given value to all properties.

A fiddle: http://jsfiddle.net/52HE6/

And an improved (IMO) version, using a placeholder instead of the literal "(multiple)": http://jsfiddle.net/52HE6/1/

This pattern can probably be generalized/parameterized (i.e. no fixed name prop), e.g. as (WARNING: UNTESTED):

function aggregatorFactory($scope, collectionName, propName) {
    return {
        get aggregate() {
            var res, i;
            for( i=0; i < $scope[collectionName].length; i++ ) {
                if( typeof(res) === "undefined" ) {
                    res = $scope[collectionName][i][propName];
                }
                else if( $scope[collectionName][i][propName] !== res ) {
                    return "(multiple)";
                }
            }
            return res;
        },
        set aggregate(val) {
            var i;
            for( i=0; i < $scope[collectionName].length; i++ ) {
                $scope[collectionName][i][propName] = val;
            }
        }
    };
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.