0

I have a model that i am using in knockout that contains different observables like so.

self.myModel = { 
FirstName: ko.observable(), 
LastName: ko.observable(),
MiddleName : ko.observable(),
CatName: ko.observable(),
DogName: ko.observable()
... approx 80 properties
}

I would like to create an html text input for each property in my model.

like so

<!--ko foreach: myModel-->
<span data-bind="text: myModel.item"></span>
<!--/ko-->

Here is my Fiddle

How can i create an Html Element for each observable property using a foreach loop?

0

2 Answers 2

1

As you've got it, you could make this work using Object.keys...

<!-- ko foreach: { data: Object.keys(myModel), as: 'key' } -->
  <p>
    <input data-bind="textInput: $parent.myModel[key]"/>
    <span data-bind="text: key"></span>
  </p>
<!--/ko-->

You could then go one step further and create a custom binding for iterating objects, such as this...

ko.bindingHandlers.foreachProperty = {
  init(el, valueAccessor) {
    const keyValuePairs = ko.pureComputed(() => {
      const raw = ko.unwrap(valueAccessor());
      return Object.keys(raw).map((key) => ({
        key,
        value: raw[key]
      }));
    });

    ko.applyBindingsToNode(el, {
      foreach: keyValuePairs
    });

    return { controlsDescendantBindings: true };
  }
}

ko.virtualElements.allowedBindings.foreachProperty = true;

<!-- ko foreachProperty: myModel -->
  <p>
   <input data-bind="textInput: value"/>
   <span data-bind="text: key"></span>
  </p>
<!--/ko-->

Here is your fiddle with a custom binding, https://jsfiddle.net/p1vfe6fu/9/

Please note though, what you've got is an object, not an array.

Sign up to request clarification or add additional context in comments.

3 Comments

I really like this solution but for some reason visual studio is giving me a weird error that doesnt allow me to build. Implicit property must be identifier.
Probably something to do with the ES2015 syntax. What line does the error occur on?
i had to change it from init(el) to init: function(el)... It works perfectly now.
1

here is a solution. of interest I used the rawData binding to keep the observable in sync with the span after it.

function ViewModel(){
var self = this;

self.myModel = { 
    FirstName: ko.observable("Bert"), 
    LastName: ko.observable("Anderson"),
    MiddleName : ko.observable(),
    CatName: ko.observable(),
    DogName: ko.observable(),
    };
 
self.labels= ko.computed(function() {
    var array = $.map(self.myModel, function(value, key) {
    return [key];
    });
    return array
}, this);
    
self.myArray= ko.computed(function() {
    var array = $.map(self.myModel, function(value, index) {
    return [value];
    });
    return array
}, this);
}

var viewModel = new ViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>



<!--ko foreach: myArray-->
<p>
 <strong data-bind="text: $root.labels()[$index()]"></strong> 
 <input data-bind="textInput: $rawData"/>
 <span data-bind="text: $data"></span>
</p>
<!--/ko-->

all that being said not sure it this is a great approach you may want to look into templates and components, and the knockout mapping plugin.

1 Comment

That is a really neat function, what would it look like if you wanted to display the index as a label and the value as an input?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.