简体   繁体   中英

How to bind click handlers to templates in knockoutjs without having a global viewModel?

I'm very new to KnockoutJs so I'm hoping that there is a well known best practice for this kind of situation that I just haven't been able to find.

I have a view model that contains an array of items. I want to display these items using a template. I also want each item to to be able to toggle between view and edit modes in place. I think what fits best with Knockout is to create the relevant function on either the main view model or (probably better) on each item in the array and then bind this function in the template. So I have created this code on my page:

<ul data-bind="template: {name: testTemplate, foreach: items}"></ul>

<script id="testTemplate" type="text/x-jquery-tmpl">
  <li>
    <img src="icon.png" data-bind="click: displayEditView"  />
    <span data-bind="text: GBPAmount"></span>
    <input type="text" data-bind="value: GBPAmount" />
  </li>
</script>

<script>
(function() {
  var viewModel = new TestViewModel(myItems);
  ko.applyBindings(viewModel);
})();
</script>

And this in a separate file:

function TestViewModel(itemsJson) {

  this.items = ko.mapping.fromJS(itemsJson);

  for(i = 0; i < this.items.length; ++i) {
    this.items[i].displayEditView = function () {
      alert("payment function called");
    }
  }

  this.displayEditView = function () {
    alert("viewmodel function called");
  }
};

Due to the environment my JS is running in I can't add anything to the global namespace, hence the annonymous function to create and set up the view model. (There is a namespace that I can add things to if it is necessary.) This restriction seems to break all the examples I've found, which seem to rely on a global viewModel variable.

PS If there's an approach that fits better with knockoutJS than what I am trying to do please feel free to suggest it!

When your viewModel is not accessible globally, there are a couple of options.

First, you can pass any relevant methods using the templateOptions parameter to the template binding.

It would look like (also note that a static template name should be in quotes):

data-bind="template: {name: 'testTemplate', foreach: items, templateOptions: { vmMethod: methodFromMainViewModel } }"

Then, inside of the template vmMethod would be available as $item.vmMethod . If you are using templateOptions as the last parameter, then make sure that there is a space between your braces { { or jQuery templates tries to parse it as its own.

So, you can bind to it like:

<img src="icon.png" data-bind="click: $item.vmMethod"  />

The other option is to put a method and a reference to anything relevant from the view model on each item. It looks like you were exploring that option.

Finally, in KO 1.3 (hopefully out in September and in beta soon) there will be a nice way to use something like jQuery's live/delegate functionality and connect it with your viewModel (like in this sample: http://jsfiddle.net/rniemeyer/5wAYY/ )

Also, the "Avoiding anonymous functions in event bindings" section of this post might be helpful to you as well. If you are looking for a sample of editing in place using a dynamically chosen template, then this post might help.

This is for those asking how to pass variable methods (functions) to Knockout Template. One of the core features of Templating is the consuming of variable data, which can be String or function . In KO these variables can be embedded in data or foreach properties for the Template to render. Objects embedded in data or foreach , be it String , function etc, can be accessed at this context using $data .

You can look at this code and see if it can help you to pass functions to Knockout Template.

function ViewModel() {
 this.employees = [
     { fullName: 'Franklin Obi', url: 'employee_Franklin_Obi', action: methodOne },
     { fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo }
 ],
this.methodOne = function(){ alert('I can see you'); },
this.methodTwo = function(){ alert('I can touch you'); }
}

ko.applyBindings(new ViewModel());


<ul  data-bind="template: { name: employeeTemplate, foreach: employees }" ></ul>
     <script type="text/html" id="employeeTemplate">
         <li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
    </script>

If you want to serve multiple Template constructs you can introduce a switch method to your ViewModel like this, and use as property to introduce alias for each item (employee). Make sure you add the switch key, linkable , to the item object.

...
this.employees = [
     { fullName: 'Franklin Obi',  linkable : false },
     { fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo, linkable : true }
 ],
this.methodLinkTemplate = function(employee){return employee.linkable ? "link" : "noLink"; } //this is a two way switch, many way switch is applicable.
...

Then the id of the Template forms will be named thus;

<ul  data-bind="template: { name: employeeTemplate, foreach: employees, as: 'employee' }" ></ul>
  <script type="text/html" id="noLink">
     <li data-bind="text: fullName"></li>
  </script>
  <script type="text/html" id="link">
     <li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
  </script>

I have not ran this codes but I believe the idea can save someones time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM