Anno
Anno

Reputation: 77

KnockoutJS Add and Loop Observables

Hi guy I've been trying to get this right for a while now but Im a complete noob at KnockoutJS. The idea is you add a input and it then presents its own settings next like name attr placeholder, required etc But Im running into a couple of problems.

I've managed to get some working but for some reason required is always true. The other problem I have is adding more - am I right by saying that then I need to add more observables in in js ? Can I not do some sort of loop. Here is my code please help.

<div class="input-row">
  <div class="input-item">            
     <input type="text" data-bind="attr: { name: itemName, placeholder: itemPlaceholder, value : itemValue, required : itemRequired }" />         
  </div>
  <div class="input-settings">
    name:
    <input type="text" data-bind="value: itemNameSetting">
    <br/>

    placehoder:
    <input type="text" data-bind="value: itemPlaceholderSetting">
    <br/>

    required:
    <select data-bind="value: itemRequiredSetting">
      <option value="true">true</option>
      <option value="false">false</option>
    </select>
    <br/>   

    maxlength:
    <br/>

    defaultvalue:
    <input type="text" data-bind="value: itemValueSetting">
    <br/>
  </div>
</div>
<button>+ ADD MORE INPUTS</button>

The JS

var ViewModel = function() {
    this.itemNameSetting        = ko.observable(); 
    this.itemPlaceholderSetting = ko.observable();
    this.itemRequiredSetting    = ko.observable();
    this.itemValueSetting       = ko.observable();

    this.itemName = ko.pureComputed(function() {            
        return this.itemNameSetting();
    }, this);

    this.itemPlaceholder = ko.pureComputed(function() {           
        return this.itemPlaceholderSetting();
    }, this);

    this.itemRequired = ko.pureComputed(function() {
      return this.itemRequiredSetting();         
    }, this);

    this.itemValue = ko.pureComputed(function() {           
        return this.itemValueSetting();
    }, this);
};

ko.applyBindings(new ViewModel());

Upvotes: 1

Views: 164

Answers (1)

janfoeh
janfoeh

Reputation: 10328

You have this thing that has has multiple properties - a name, a placeholder, a value and so on:

{
  name: 'foo',
  placeholder: 'foo goes here',
  value: 'bar'
}

But you need many of them. In Javascript, a good way to do that is to build a constructor:

var InputItem = function InputItem(name, placeholder, value) {
  this.name        = name;
  this.placeholder = placeholder;
  this.value       = value;
}

Now we can make as many of them as we like:

var item1 = new InputItem('foo', 'foo goes here', 'bar');
var item2 = new InputItem('bar', 'bar goes here', 'baz');

item1.placeholder
// returns 'foo goes here'
item2.name
// returns 'bar'
item2.value = 'something else'
// item 2's value is now changed to 'something else'

But since this is Knockout, we want our properties to be observable:

var InputItem = function InputItem(name, placeholder, value) {
  this.name        = ko.observable(name);
  this.placeholder = ko.observable(placeholder);
  this.value       = ko.observable(value);
}

var item1 = new InputItem('foo', 'foo goes here', 'bar');
item1.name()
// returns 'foo'
item1.placeholder('kittens')
// item 1's placeholder is now changed to 'kittens'

What you have here is a data model - something that holds all the data required for a single 'thing', an input in your case. Now we need a view model that holds all our data models, and a way for the user to add more of them:

var ViewModel = function ViewModel() {
  var that = this;

  this.inputItems = ko.observableArray([]);

  this.addInput = function addInput() {
    that.inputItems.push( new InputItem() );
  };
}

ko.applyBindings( new ViewModel() );

In our markup, we iterate over all the InputItems in our observable inputItems array with foreach:

<div data-bind="foreach: inputItems">
  <!-- everything inside here is rendered once for every InputItem -->
  <input type="text" data-bind="value: name">
  <input type="text" data-bind="value: placeholder">
</div>

<button data-bind="click: addInput">Add another input</button>

Try this interactive demo:

var InputItem = function InputItem(name, placeholder, value) {
  this.name        = ko.observable(name);
  this.placeholder = ko.observable(placeholder);
  this.value       = ko.observable(value);
}

var ViewModel = function ViewModel() {
  var that = this;

  this.inputItems = ko.observableArray([]);

  this.addInput = function addInput() {
    that.inputItems.push(new InputItem());
  };
}

ko.applyBindings(new ViewModel());
.console {
  background-color: lightgrey;
  padding: 1rem;
  margin-top: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<ul data-bind="foreach: inputItems">
  <li>
    <!-- everything inside here is rendered once for every InputItem -->
    <input type="text" data-bind="value: name" placeholder="input name">
    <input type="text" data-bind="value: placeholder" placeholder="input placeholder">
  </li>
</ul>

<button data-bind="click: addInput">Add another input</button>

<div class="console">
  <h1>You have added <span data-bind="text: inputItems().length"></span> input items</h1>
  <ul data-bind="foreach: inputItems">
    <li>
      Name: <span data-bind="text: name"></span> with placeholder <span data-bind="text: placeholder"></span>
    </li>
  </ul>
</div>

Upvotes: 1

Related Questions