rlcrews
rlcrews

Reputation: 3562

how to populate an observable array from two different arrays in knockout.js

Within my app I have a section that allows users to add a filter for search criteria the filter is an observable array that contains 3 drop down menus and input fields for manually adding values. Two of the drop down menus are static, the third is populated based upon selected items located elsewhere in the page. Where I am having trouble is in the binding between the two array's. I have all the drop downs populating but I cannot figure out how to pass the value from the SelectedAttributes() array to be inserted in the attribure property of my filter list

html

<input type="button" value="Add Filter" title="Add Filter" data-bind="click: $root.addFilter, enable: myfilters().length < 10" />
<table>
 <tbody data-bind="foreach: myfilters">
  <tr>
  <td>
    <!-- ko with: $root.iqReport -->
    <select data-bind="options: SelectedAttributes(), optionsText: function(SelectedAttributes){ return SelectedAttributes.NameHierarchy() + '.' + SelectedAttributes.LabelName() },  optionsCaption:'Select a Field...'">
    </select>
    <!-- /ko -->
  </td>
  <td>
    <select data-bind="options: $root.filterOperators, value:operator, optionsText: 'operatorName'">
    </select>
  </td>
  <td>
    <input data-bind="value: criteria1" />
  </td>
  <td>
    <input data-bind="value: criteria2" />
  </td>
  <td>
    <select data-bind="options: $root.joinOperators, value:joinOperator, optionsText: 'joinName'">
    </select>
 </td>
 <td>
    <a class="attributeLink" data-bind="click: $root.removeFilter">Remove</a>
 </td>
 </tr>

knockoutJS

function FilterList(JoiningOperator, AttributeHierarchy, AttributeIDHierarchy, Operator, FilterCriteria, FilterCriteriaRange) {
    var self = this;

    self.joinOperator = ko.observable(JoiningOperator);
    self.attribute = ko.observable(AttributeHierarchy);
    self.attributeID = ko.observable(AttributeIDHierarchy);
    self.operator = ko.observable(Operator);
    self.criteria1 = ko.observable(FilterCriteria);
    self.criteria2 = ko.observable(FilterCriteriaRange);
}


var viewmodel function(){

        //non-editable operators
        self.filterOperators = [
        { operatorName: "EQUALS", operatorValue: "=" },
        { operatorName: "GREATER THAN", operatorValue: ">" },
        { operatorName: "GREATER THAN OR EQUAL TO", operatorValue: ">=" },
        { operatorName: "LESS THAN", operatorValue: "<" },
        { operatorName: "LESS THAN OR EQUAL TO", operatorValue: "<=" },
        { operatorName: "NOT EQUAL TO", operatorValue: "<>" },
        { operatorName: "NOT LESS THAN", operatorValue: "!>" },
        { operatorName: "NOT GREATER THAN", operatorValue: "!<" },
        { operatorName: "BETWEEN", operatorValue: "BETWEEN" },
        { operatorName: "LIKE", operatorValue: "LIKE" }
        ];

        //non-editable joins
        self.joinOperators = [
        {joinName: "AND"},
        {joinName: "OR"}
        ];


        //define filter collection
        self.myfilters = ko.observableArray([]);


        //add new filter row
        self.addFilter = function () {
            self.myfilters.push(new FilterList(self.joinOperators[0], "", "", self.filterOperators[0], "", ""));
        };

        FilterList.attribute.subscribe(function () {

        }, FilterList);


        self.removeFilter = function (row) {
            self.myfilters.remove(row);
        };
}

The collection iqReports is populated from another array independent of myfilters[] hence the virtual binding in the html.

What I would like to learn is when a user selects a value from the Selected attributes drop down how do I go about having that value populate/update the observable property self.attribute of myfilters

Update

the iqReports collection is an observable array consisting of additional observable arrays.This is created server side, and passed back to the client. The SelectedAttributes class contains the following properties

public class ReportEntity
    {
        public int EntityId { get; set; 
        public string Name { get; set; }
        public string IconPath { get; set; }
        public List<ReportAttribute> PrimaryAttributes { get; set; }
        public List<ReportAttribute> SecondaryAttributes { get; set; }
        public List<ReportAssociatedEntity> AssociatedEntities { get; set; }
        public string Hierarchy { get; set; }
        public string NameHierarchy { get; set; }
        public ReportEntity() 
        {
            // Initialize the attributes lists.
            PrimaryAttributes = new List<ReportAttribute>();
            SecondaryAttributes = new List<ReportAttribute>();

            // Initialize Associated Entities list.
            AssociatedEntities = new List<ReportAssociatedEntity>();
        }
    }


public class ReportAttribute
    {
        public string FieldName { get; set; }
        public string LabelName { get; set; }
        public bool IsPrimary { get; set; }
        public int Position { get; set; }
        public bool IsSelected { get; set; }
        public bool IsSortedBy { get; set; }
        public int SortPosition { get; set; }
        public bool SortAscending { get; set; }
        public bool IsGroupedBy { get; set; }
        public string NameHierarchy { get; set; }
        public string Hierarchy { get; set; }
        public ReportAttribute() { }
    }

Once back in the client you can navigate to the values using Firebug in this manner self.iqReport().SelectedAttributes()[0]().FieldName I think where the reference is getting lost is because I have it nested inside of another observable array (myfilters) which is why I have the virtual binding wrapped around the section that pertains to iqReports. Removing that with statement breaks the foreach because it cannot resolve the property SelectedAttributes() trying to explicitly path it using $root does not work resulting in none of the drop downs being populated. Firebug at this point would state that it cannot find the property SelectedAttributes().

The workaround I introduced was to move the iqReports reference out of the foreach so instead I now have this in the html

<!-- ko with: $root.iqReport -->
     <select data-bind="options: SelectedAttributes(), value: $root.selectedItem,  optionsText: function(SelectedAttributes){ return SelectedAttributes.NameHierarchy() + '.' + SelectedAttributes.LabelName() },  optionsCaption:'Select a Field...'">
      </select>
<!-- /ko -->
<input type="button" value="Add Filter" title="Add Filter" data-bind="click: $root.addFilter, enable: myfilters().length < 10" />
<!--<pre data-bind="text: ko.toJSON(iqReport, null, 2)"></pre>-->
<table>
     <tbody data-bind="foreach: myfilters">
       <tr>
          <td>
          <b><span data-bind="text: filterattribute" /></b>
          </td>
          <td>
           <select data-bind="options: $root.filterOperators, value:operator, optionsText: 'operatorName'">
           </select>
          </td>
           <td>
             <input data-bind="value: criteria1" />
           </td>
           <td>
             <input data-bind="value: criteria2" />
           </td>
           <td>
             <select data-bind="options: $root.joinOperators, value:joinOperator, optionsText: 'joinName'">
             </select>
           </td>
           <td>
            <a class="attributeLink" data-bind="click: $root.removeFilter">Remove</a>
            </td>
     </tr>
    </tbody>
</table>

Then within the view model I added a new observable and setup the mapping for the table as follows

 self.selectedItem = ko.observable();

  //define filter collection
 self.myfilters = ko.observableArray([]);


//add new filter row
//Here I navigated to the individual field properties using the selectedItem object
self.addFilter = function () {
            self.myfilters.push(new FilterList(self.joinOperators[0], self.selectedItem().NameHierarchy() + "." + self.selectedItem().FieldName(), "", self.filterOperators[0], "", ""));
        };

While this is not my initial preferred approach this does provide a workaround. Using the virtual binding allowed me to populate the select element but I was never able to pass the value of that select back to the myfilters.attribute() observable. I suspect it was due to the foreach but I have no idea how to work around it

-Cheers

Upvotes: 2

Views: 1589

Answers (1)

CodeThug
CodeThug

Reputation: 3202

Use the value binding and bind it to self.attribute, and remove the with binding so that the value binding can find the self.attribute.

So instead of this:

<!-- ko with: $root.iqReport -->
    <select data-bind="
        options: SelectedAttributes(), 
        optionsText: function(SelectedAttributes){ 
            return SelectedAttributes.NameHierarchy() + '.' + SelectedAttributes.LabelName() },
        optionsCaption:'Select a  Field...'
    ">
    </select>
<!-- /ko -->

Do this:

<select data-bind="
    options: $root.iqReport.SelectedAttributes(), 
    optionsText: function(SelectedAttributes){ 
        return SelectedAttributes.NameHierarchy() + '.' + SelectedAttributes.LabelName() 
    },  
    optionsCaption:'Select a Field...',
    value: attribute 
">

Upvotes: 1

Related Questions