Multiple view models break knockout.js - javascript

I'm trying to use multiple view models as suggested in the documentation and in this other answer.
I'm getting an error in the console complaining about a variable not being defined:
Uncaught ReferenceError: Unable to process binding "foreach: function (){return seals }"
Message: seals is not defined
Reproduction online
HTML
<!-- ko foreach: seals -->
<div class="form-group">
<label for="seal" class="col-xs-2 control-label" data-bind="text: 'Seal ' + name"></label>
<div class="col-xs-8">
<input type="text" class="form-control" data-bind="attr: {name: 'seal' + formName}" />
</div>
</div>
<!-- /ko -->
JS
ko.applyBindings(demo, document.body);
ko.applyBindings(addEquipmentModel, document.getElementById('whatever'));

The problem is here:
ko.applyBindings(demo, document.body);
You are applying a model to document.body, so it's going to try and parse and bind the whole document. When it gets to the part with:
<!-- ko foreach: seals -->
You get an error because the demo model doesn't have a seals property.
In practice, you don't want the elements that you are binding to overlap. In other words, don't bind one model to a child element of an element that is bound to another model. They should be siblings, or cousins. Not direct descendants.

If you need several viewmodels nested in your view, or even in a child-parent relation, you should consider using Knockout components for that. Another possibility aside from that is to use apply(this) in your main viewmodel to the other viewmodels 'class' so your main viewmodel sort of inherits the functionality and properties of the referred model. This will, though, lead to problems if you have name concurrencies in your viewmodels.

Related

EmberJS template component suddenly not rendering on page

I'm following this Ember tutorial and I've suddenly run into an issue where my rental-listing.hbs template component stops rendering. It started when I began implementing the map service.
I don't understand where this could be happening. I've copied the code from parts of the GitHub repository that I thought were relevant but to no avail.
I have a rental.hbs template that looks like so:
<div class="jumbo">
<div class="right tomster"></div>
<h2>Welcome!</h2>
<p>We hope you find exactly what you're looking for in a place to stay.</p>
{{#link-to "about" class="button"}}
About Us
{{/link-to}}
</div>
{{outlet}}
Which in turn has a template component called rental-listing.hbs:
<article class="listing">
<a
onclick={{action "toggleImageSize"}}
class="image {{if this.isWide "wide"}}"
role="button"
>
<img src={{this.rental.image}} alt="">
<small>View Larger</small>
</a>
<div class="details">
<h3>{{link-to this.rental.title "rentals.show" this.rental class=this.rental.id}}</h3>
<div class="detail owner">
<span>Owner:</span> {{this.rental.owner}}
</div>
<div class="detail type">
<span>Type:</span> {{rental-property-type this.rental.category}} - {{this.rental.category}}
</div>
<div class="detail location">
<span>Location:</span> {{this.rental.city}}
</div>
<div class="detail bedrooms">
<span>Number of bedrooms:</span> {{this.rental.bedrooms}}
</div>
</div>
<LocationMap #location={{this.rental.city}}/>
</article>
The only thing I have added to the above is the line <LocationMap #location={{this.rental.city}}/> but it still doesn't work if I remove it.
The console shows me no errors and I can actually see I am getting the three dummy objects I want from Mirage:
So I'm definitely getting the objects and from what I see I'm doing everything necessary in the templates to render it but they aren't. Should I be looking somewhere else?
Are you able to provide a link to your example? By having each piece of the ember application you mention it would be best to answer definitely. I can give a general answer with strategies for debugging the template.
The conventions behind ember.js make understanding the "whys" frustrating at first and possibly opaque. Ember's handlebars implementation governs how values are populated and accessed within templates using very specific rules. Ember treats templates (handlebars files) differently depending on whether it is for a route or component. Component's have an isolated context and must receive values by explicit passing in or dependency injection. Then, you can use such values in a component's template by accessing those properties with {{this.somePassedInValue}}.
In the super-rentals app, it appears the rental index route invokes the components responsible for displaying the individual units. I found this in app/templates/rentals/index.hbs.
<li><RentalListing #rental={{rentalUnit}} /></li>
The route template iterates over the list of filteredResults. Each of these is the rentalUnit. A good first step would be to use the {{log}} helper to print out that the value of rentalUnit to ensure it is what you expect.
Alternatively, you could try cloning https://github.com/ember-learn/super-rentals and applying the changes you want to make step by step from the master branch. By doing so, you could easily undo a single change to see what caused something to not show up as expected.
<LocationMap #location={{this.rental.city}}/>
to be written as below
<LocationMap #location={{this.rentals.city}}/>
may be typo error.
also repeat this for there place in that template.
Because the model name in the console is rentals not rental

Kendo UI - dynamically load template to kendo window with observable view model

Kendo UI for JQuery question incoming.
I have a partial view with a kendo template, a kendo window and an observable view model. What I am trying to achieve is put the template inside of the window and then bind it to the view model like:
var kendoDialog = kendo.template($("#window-template").html());
window.center().open();
window.content(kendoDialog);
kendo.bind($("#window-container"), viewModel);
The window is created correctly and the model is populated correctly but before the binding occurs this line:
window.content(kendoDialog);
Throws description is not defined.
In the template its just:
<div class="col-sm-10">
#: description #
</div>
How would I achieve what I am trying to do?
I have prepared a dojo for you to see the problem:
https://dojo.telerik.com/OsANOcox
The issue is that when you are loading the template it is expecting to some form of a model being passed in which currently you aren't so effectively there is a null model to bind.
so in your code changing:
window.content(kendoDialog);
to
window.content(kendoDialog(viewModel));
will load the data into the template.
But if you are looking to bind it via MVVM as you currently appear to want to then you need to change the template so it is binding aware from:
<div class="col-sm-10">
#= data.description #
</div>
to
<div class="col-sm-10">
<span data-bind="html:description"></span>
</div>
Hope this helps.

Knockout containerless 'with' binding not working

This containerless with binding doesn't set the bindingContext as I would expect; it's still set to the containing parent of ladder.
<!-- ko with:ladder -->
<table>
//Context here is the $root object, not $root.ladder
//some foreach binding here
</table>
Add 4 pages
<!-- /ko -->
This containerful method works fine though.
<table class="ladder-table" data-bind="with:ladder">
//the context is correctly set to ladder in this instance
//some foreach binding here
</table>
<br />
Add 4 pages
Anyone know what's up with that? Google didn't give any results.
The problem is with Durandal, not Knockout, as this answer explains:
containerless statements of knockoutjs is not working in hottowel SPA?
In short, Durandal allows only one root element per view.
goodView.html
<div>
<--ko foreach:stuff-->
//stuff
<--/ko-->
</div>
badView.html
<div>
<stuff/>
</div>
<--ko foreach:stuff--> //these elements are stripped out
//stuff
<--/ko-->
Thanks #nemesv

KO cannot find template with ID

I've used Knockout templates before, so I'm not sure why this isn't working for me.
I tried two different styles of ko markup, neither work.
<!-- more nesting levels -->
<div class="cal-day-tps" data-bind="foreach: timePeriods">
<div class="cal-day-tp-cont">
<div data-bind="template: { name: 'tp-ed-templ', data: $data }"></div>
//both of these methods fail
<!-- ko template: { name: 'tp-ed-templ', data: $data } -->
<!-- /ko -->
</div>
</div>
<!-- /more nesting levels -->
<script type="text/html" id="tp-ed-templ">
<!-- bunch of markup -->
</script>
I just get the error "Cannot find template with ID tp-ed-templ".
Probably just a typo, but I haven't been able to find it.
I'm using KO in the context of Durandal, though this shouldn't make a difference.
Tried declaring the template before usage, didn't help.
Someone else ran into the same thing with no solution either
It seems to be an issue with Durandal, not Knockout.
I tried some extremely simple cases in vanilla durandal setups, and it still does the same thing. Even tried putting the script in the same nested location as the binding, no dice.
The short answer: You can't currently use Knockout templates inside of Durandal.
However, as nemesv pointed out, if you put your named template outside of Durandal, ko is able to find them. For example, anywhere outside of the <div id="applicationHost"></div> element.
The other workarounds are to either use Durandal's compose functionality, or just inline the templates as anonymous.
Knockout templates will probably be supported in the near future.
I finally dug these answers up on the Durandal google group,
Mixing knockout templates with durandal compose
knockout can't find templates inside of views
The issue is that the KO template element must exist in the DOM before the Durandal view is bound. This is because the view is bound before it is inserted into the DOM so any contained templates cannot be resolved by ID.
Using a function that returns an observable can be used to later re-trigger a template binding .. it works, but is wonky. (An if binding could be used for similar effect.)
// bind to this in markup:
// <div data-bind="template: {name: $root.templateName, .. }">
vm.templateName = function () {
return vm.TemplateId();
};
// Changing this will trigger an observable in the KO template binding;
// don't ask me why we have to pass in a function to 'name' ..
vm.TemplateId = ko.observable("dummy-template-id-that-exists");
// After the view is attached the correct template element is in the DOM
// so we can trigger the template to (re-)bind and it will find it.
function viewAttached () {
vm.TemplateId("the-real-template-id");
}

knockout JS bind to properties of object

I have a need to bind some HTML to an object, but my issue is that I don't know the properties of the object at development time.
I have a selectedItem property in my main view model which I have bound to a section in my HTML:
<div data-bind="with: selectedItem">
</div>
Now I want to generate a table based on the property name and property values:
<div data-bind="foreach: [WHAT DO I PUT HERE?]">
<label class="control-label"><span data-bind="text: [OR HERE?]" /></label>
</div>
I have really no idea how to do this. Any help is greatly appreciated.
Also, just slightly extending this, I would like to handle the properties of the bound object differently, such as, if the property is just a primitive type, just output it, but if its another object/array, then handle it specially.
Can this be done?
If anyone else is looking to bind a simple object's properties. You can do it like this...
<table>
<tbody data-bind="foreach: arrayOfObjects">
<tr data-bind="foreach: Object.keys($data)">
<td data-bind="text: $parent[$data]"></td>
</tr>
</tbody>
</table>
note: object.keys is not supported in older browsers, but you can use this to add backwards compatability http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
Here is a working example using computed observable to select at runtime the data to show. Dynamically selected templates are also used to render the data according to the type of data to render (array or scalar).

Categories