I have a small, simple view & viewmodel that displays some numbers and a chart. I was asked to provide a "side by side" view whereby up to 6 instances of this would be shown side by side.
Somewhat naively, it would appear, I just did this in my view:
<div class="container acd">
<div class="row">
<div class="col-md-2">
<div data-bind="compose: { model: acd1}"></div>
</div>
<div class="col-md-2">
<div data-bind="compose: { model: acd2 }"></div>
</div>
<div class="col-md-2">
<div data-bind="compose: { model: acd3 }"></div>
</div>
<div class="col-md-2">
<div data-bind="compose: { model: acd4 }"></div>
</div>
<div class="col-md-2">
<div data-bind="compose: { model: acd5 }"></div>
</div>
<div class="col-md-2">
<div data-bind="compose: { model: acd6 }"></div>
</div>
</div>
</div>
and this in the viewmodel:
var acd1 = new acd({ header: 'Article 1'});
var acd2 = new acd({ header: 'Article 2'});
var acd3 = new acd({ header: 'Article 3'});
var acd4 = new acd({ header: 'Article 4'});
var acd5 = new acd({ header: 'Article 5'});
var acd6 = new acd({ header: 'Article 6'});
...where "acd" is the reference "required" in my viewmodel that links to the standalone view & viewmodel.
This seemed to work at first and I had 6 instances side by side on the page with different headers as set above.
However when I came to actually fire off some loading events whereby a section is data-bound to an observable that controls whether it's visible or not, it seems that there is not enough separation of instances as changing a dropdown in the first instance actually reveals the section in the 6th section!
When I inspect the elements on the page through chrome dev tools, all the div IDs are the same, so I imagine that there's a fundamental problem with what I'm trying to achieve here? I thought that by creating "new" instances in the master page it would create standalone instances that were self-aware so that when passed parameters/updated, they would know to refer to their own instances of IDs but it would appear not.
Anyone got any pointers to try and help me out here please?
The master page viewmodel:
define(['services/datacontext', 'viewmodels/articleComparisonDetail'], function (datacontext, acd) {
var acd1 = new acd({ header: 'Article 1'});
var acd2 = new acd({ header: 'Article 2'});
var acd3 = new acd({ header: 'Article 3'});
var acd4 = new acd({ header: 'Article 4'});
var acd5 = new acd({ header: 'Article 5'});
var acd6 = new acd({ header: 'Article 6'});
var acdMaster = {
acd1: acd1,
acd2: acd2,
acd3: acd3,
acd4: acd4,
acd5: acd5,
acd6: acd6
};
return acdMaster;
});
To add more info, I've put a debug stop point on the "var acdMaster =..." line and at that moment, acd1-6 appear to be separate and independent viewmodels with the "header" property set to the 6 separate values I used. Somehow though the input boxes in the individual details views don't seem to be linked up to the specific instance.
The acd "detail" view (simplified):
<div id="acdWrapper">
<section id="acd">
<div class="acdArticleCode" data-bind="text: header"></div>
<div class="formGrid">
<input autocomplete="off" data-bind="typeahead: { name: 'sectionNames', highlight: true, source: articleList}, value: artCode""/>
</div>
<!-- ko if: articleLoaded() == true -->
<div id="articleSelected" data-bind="visible: articleLoaded()">
<div class="formGrid resultsTable">
<div class="formRow">
<div class="formCell">
<label>Article Weight:</label>
</div>
<div class="formCell text-right">
<span data-bind="numericText: loadedArticle().totalArticleWeight, precision: 2"></span>
</div>
</div>
...
</div>
<!-- /ko -->
<div id="articleNotSelected" data-bind="visible: !articleLoaded()">
<p>Type a minimum of 2 characters into text box to see list of articles</p>
</div>
</section>
...and viewmodel (also simplified)
define(['plugins/dialog', 'knockout', 'config', 'services/datacontext'], function (dialog, ko, config, datacontext) {
var acdvm = function (params) {
var self = this;
self.content = ko.observable();
self.header = ko.observable(params.header);
self.loadedArticle = ko.observable(); //contains entity object for display
self.articleLoaded = ko.observable(false); //is article loaded true/false
self.articleList = ko.observableArray([]); //holds list of matching articles from search
self.selectedRow = ko.observable('e1');
self.chartID = ko.computed(function () {
var str = self.header() + "_chart";
return str.replace(" ","");
}, this, { deferEvaluation: true });
self.displayPrecision = ko.observable(6);
self.artCode = ko.observable('');
};
return acdvm;
});
EDIT 2 So I investigated further. I added:
function externalActivate(fakeself) {
if (fakeself.header() == "Article 2") { fakeself.blah("I blow") };
}
var acdvm = function (args) {
var self = this;
self.blah = ko.observable('blah');
self.activate = function () {
externalActivate(self);
if(self.header()=="Article 5") {self.blah("Really Sucks")};
};
... rest unchanged
}
return acdvm;
And altered the view to display blah on each of the 6 views I instantiate. In my master viewmodel I added:
acd3.blah("sucks");
to the module's "activate" method and all of it worked exactly as expected.
On the 6 columns on screen I get "blah", "I blow", "sucks", "blah", "Really sucks", "blah" which is what I need. Yet if I use the input field on the first column/viewmodel to select something then as soon as the viewmodel loads up data and changes the observables, triggering the html in that view to be "filled in" then it appears in the 6th column, not the first!
This is insane... I truly have no idea how this can be happening.
this.acd1 = new acd({...});
– GôTôvar acdvm = function(args) {... return acdvm
code IS returning a constructor? I'm confused... (as usual) – TheMook