WHY does it initialize this Knockout.js component in random order? - javascript

I am beyond confused...
I am creating a list using Knockout.js components, templates, and custom elements. For some reason, the steps I create in my Viewmodel are being initialized in random order within the custom element definition! And it is completely randomized so that it is different each time!
To help better illustrate this, it is best to look at the JSFiddle. I put alert("break") after each step initialization. Load it once, and then click "run" again to see the demo properly. Look in the output window and you can see that other than step 1 being written first, the steps always appear randomly (though they maintain their order in the end).
https://jsfiddle.net/uu4hzc41/8/
I need to have these in the correct order because I will add certain attributes from my model into an array. When they are random I can't access the array elements properly.
HTML:
<ul>
<sidebar-step params="vm: sidebarStepModel0"></sidebar-step>
<sidebar-step params="vm: sidebarStepModel1"></sidebar-step>
<sidebar-step params="vm: sidebarStepModel2"></sidebar-step>
<sidebar-step params="vm: sidebarStepModel3"></sidebar-step>
<sidebar-step params="vm: sidebarStepModel4"></sidebar-step>
</ul>
JS/Knockout:
//custom element <sidebar-step>
ko.components.register("sidebar-step", {
viewModel: function (params) {
this.vm = params.vm;
alert("break");
},
template: "<li data-bind='text: vm.message'>vm.onChangeElement</li>"
});
// model
var SidebarStepModel = function () {
this.message = ko.observable("step description");
};
// viewmodel
var OrderGuideViewModel = function () {
this.sidebarStepModel0 = new SidebarStepModel();
this.sidebarStepModel0.message("step 1");
this.sidebarStepModel1 = new SidebarStepModel();
this.sidebarStepModel1.message("step 2");
this.sidebarStepModel2 = new SidebarStepModel();
this.sidebarStepModel2.message("step 3");
this.sidebarStepModel3 = new SidebarStepModel();
this.sidebarStepModel3.message("step 4");
this.sidebarStepModel4 = new SidebarStepModel();
this.sidebarStepModel4.message("step 5");
};
ko.applyBindings(new OrderGuideViewModel());

By default knockout components load asynchronously. In version 3.3 an option was added to allow the component to load synchronously.
Add synchronous:true when registering to get the behavior you want.
Example:
ko.components.register("sidebar-step", {
viewModel: function (params) {
this.vm = params.vm;
alert("break");
},
template: "<li data-bind='text: vm.message'>vm.onChangeElement</li>",
synchronous: true
});

Related

selection not working on WinJS ListView in click mode

Below is my WinJS.UI.ListView definition. However, onselectionchanged is never called when right clicking or doing Ctrl+Click. I have setup my ListView identically to the samples(which work). Am I missing something? Or could something be interfering, just with the click selection?
this.m_listView = new WinJS.UI.ListView(this.domNode,
{
layout : {type: WinJS.UI.GridLayout},
itemTemplate : this.itemTemplate.bind(this),
selectionMode: 'multi',
tapBehavior: 'invokeOnly',
swipeBehavior: 'select'
});
this.m_listView.oniteminvoked = this.itemInvoked.bind(this);
this.m_listView.onselectionchanged = this.selectionChanged.bind(this);
EDIT: I Assign my datasource in a separate function with these lines
var itemList = new WinJS.Binding.List(this.m_nodes);
this.m_listView.itemDataSource = itemList.dataSource;
This ListView is wrapped in a javascript class. So my template function is a prototype of my EntriesList class. This is that function(I pulled out the real content for simplicity, I still have this issue with the content though):
EntriesList.prototype.itemTemplate = function(itemPromise)
{
return itemPromise.then
(
function (item)
{
var entry = document.createElement("div");
entry.className = "tile";
entry.style.height = "120px";
entry.style.width = "340px";
return entry;
}
);
The issue was something in our internal API was blocking pointer events. We were able to resolve the problem. The code/configuration in the question works.

Is it possible to defer a computed observable until certain UI elements become visible?

I have a computed observable in my model which looks like this:
this.TrainingPlanTemplates = ko.computed(function ()
{
var workgroups = model.WorkgroupsImpacted();
var areas = model.AreasImpacted();
var plans = model.PrescribedPlan();
$(plans).each(function (i, v)
{
// A bunch of stuff that really slows everything down
});
// ...
}
I then have a UI template:
<table>
<!-- ko foreach: TrainingPlanTemplates -->
<tr> ... Various columns bound to TrainingPlanTemplates properties ... </tr>
<!-- /ko -->
</table>
The issue is, the HTML template above contains various custom binding handlers and potentially has a large amount of data. Rendering this table is somewhat slow (like 5 seconds or so). This UI uses the jQuery UI tabs control, so I don't even show the data when the page loads. Most users will never even switch to that tab, meaning I'm usually wasting my time binding that data.
Question: Is there a way to defer the binding of a computed observable until I say so, for example, until a certain jQuery tab becomes active?
Ideas:
I got a few ideas from this page. There does exist a deferEvaluation property, however this will only defer the property until something accesses it, which will happen immediately as a hidden HTML table is still bound to the data.
One idea would be to create a new observable property called TrainingPlanTemplatesLoaded, and set that to true when the tab becomes active. Then, create a dependency between TrainingPlanTemplates and TrainingPlanTemplatesLoaded so that when TrainingPlanTemplatesLoaded changes, TrainingPlanTemplates actually loads in the real data.
Any other ideas on the best way to accomplish this?
Yes, just making another observable that you check before doing your computation:
// set to true when you want the computation to run
this.TrainingPlanTemplatesLoaded = ko.observable(false);
this.TrainingPlanTemplates = ko.computed(function ()
{
if (this.TrainingPlanTemplatesLoaded()) {
var workgroups = model.WorkgroupsImpacted();
var areas = model.AreasImpacted();
var plans = model.PrescribedPlan();
$(plans).each(function (i, v)
{
// A bunch of stuff that really slows everything down
});
// ...
}
}, this);
Sure its possible, see my example:
function VM(){
var self = this;
self.FirstName = ko.observable("John");
self.LastName = ko.observable("Smith");
self.canShow = ko.observable(false);
self.FullName = ko.computed(function(){
if (self.canShow()){
return self.FirstName() + " " + self.LastName();
}
});
}
myVm = new VM();
ko.applyBindings(myVm);
// Represents that at some point
// Some function make something happen
setTimeout(function(){
// Let's say we check if an element was visible
// or check anything that we want to know has happened, then:
myVm.canShow(true);
}, 4000);
<p data-bind="text: FirstName"></p>
<p data-bind="text: LastName"></p>
<p data-bind="text: FullName"></p>

Creating Bootstrap tabs using Knockout.js foreach

I'm trying to create some tabs, one per profile the user chooses to save. Each profile is a ViewModel. So I thought I'd just create another ViewModel that contains an observableArray of objects of type: {name: profile_name, model: model_converted_to_json}.
I followed this example to create my code - but I get nothing bound, for some reason.
Here's my code:
-ViewModel (I use Requirejs, that explains the external wrapper):
"use strict";
// profiles viewmodel class
define(["knockout"], function(ko) {
return function() {
var self = this;
this.profilesArray = ko.observableArray();
this.selected = ko.observable();
this.addProfile = function(profile) {
var found = -1;
for(var i = 0; i < self.profilesArray().length; i++) {
if(self.profilesArray()[i].name == profile.name) {
self.profilesArray()[i].model = profile.model;
found = i;
}
}
if(found == -1) {
self.profilesArray().push(profile);
}
};
};
});
-The JS code (excerpt of larger file):
var profiles = new profilesViewMode();
ko.applyBindings(profiles, $("#profileTabs")[0]);
$("#keepProfile").on("click", function() {
var profile = {
name: $("#firstName").text(),
model: ko.toJSON(model)
};
profiles.addProfile(profile);
profiles.selected(profile);
console.log(profile);
$("#profileTabs").show();
});
-The HTML (Thanks Sara for correcting my HTML markup)
<section id="profileTabs">
<div>
<ul class="nav nav-tabs" data-bind="foreach: profilesArray">
<li data-bind="css: { active: $root.selected() === $data }">
</li>
</ul>
</div>
</section>
I have verified that the observableArray does get new, correct value on button click - it just doesn't get rendered. I hope it's a small thing that I'm missing in my Knockout data-bind syntax.
Thanks for your time!
You will want to call push directly on the observableArray, which will both push to the underlying array and notify any subscribers. So:
self.profilesArray.push(profile);
You are setting name using name: $('#firstName').text(); you may need to change that to .val() if this is referencing an input field (which I assumed here).
You are using .push() on the underlying array which bypasses ko's subscribers (the binding in this case)
Here is a working jsfiddle based on your code. I took some liberties with model since that wasn't included.

How do I correctly reference an attribute of the current element passed into knockout.JS?

Working on a little feedback form and I'm new at the Knockout/jQuery game so I'm sure this is a syntax error.
Goal / Background
I have a feedback form, part of which includes a list with feedback types. The actual text of the feedback type I'd like to use is stored in the "Title" attribute of the LI tags.
I'd like to pass an onclick from each of a set of LI tags denoting the type of feedback.
I would like knockout to receive this onclick event with the calling element
I'd like the ViewModel function to update the ViewModel's feedback type based on the content of the LI's title attribute
I'd then like to remove a class from all the list and apply it to the selected element.
I already have jQuery that does this; just want to incorporate it into the model change.
What I Have So Far
The relevant part of the HTML Feedback Form (the UL list):
<ul class="thumbnails" id="feedbackList">
<li class="feedbackItem" id="feedbackItemPraise" title="Praise" data-bind="click: updateFeedbackType"><i class="icon-thumbs-up"></i>Praise</li>
<li class="feedbackItem" id="feedbackItemCriticism" title="Criticism" data-bind="click: updateFeedbackType"><i class="icon-thumbs-down"></i>Criticism</li>
<li class="feedbackItem" id="feedbackItemProblem" title="Problem" data-bind="click: updateFeedbackType"><i class="icon-warning-sign"></i>Problem</li>
<li class="feedbackItem" id="feedbackItemQuestion" title="Question" data-bind="click: updateFeedbackType"><i class="icon-question-sign"></i>Question</li>
</ul>
The ViewModel so far (including some irrelevant parts):
var FeedbackViewModel = function () {
var self = this;
self.manualEMailAddress = "MyEmail#MyProvider.com";
self.manualApplicationName = "MyApplication";
self.username = ko.observable($("#feedbackUsernameFromServer").val());
self.feedbackType = ko.observable("Praise");
self.wantsFollowUp = ko.observable(true);
self.enteredName = ko.observable("");
self.feedbackText = ko.observable("");
self.userNameCaptured = ko.computed(function () { return self.username().length > 3; }, self);
self.mailToLink = ko.computed(function () { return "mailto:" + self.manualEMailAddress + "?subject=" + encodeURIComponent(self.feedbackType()) + encodeURIComponent(" for ") + encodeURIComponent(self.manualApplicationName) + "&body=" + encodeURIComponent(self.feedbackText()) }, self);
};
var feedbackViewModel = new FeedbackViewModel();
ko.applyBindings(feedbackViewModel, document.getElementById("feedbackModal"));
The current jQuery to change the style (not linked to the model yet):
$("#feedbackList li").click(function () {
$("#feedbackList li.feedbackItem-Highlighted").removeClass("feedbackItem-Highlighted");
$(this).addClass("feedbackItem-Highlighted");
});
What I think I need to add to the ViewModel, but doesn't quite work:
self.updateFeedbackType = function (elementToChangeTo) {
self.feedbackType($(elementToChangeTo).attr("title"));
$("#feedbackList li.feedbackItem-Highlighted").removeClass("feedbackItem-Highlighted");
$(elementToChangeTo).addClass("feedbackItem-Highlighted");
};
This results in feedbackType being turned into an undefined and the visual change not happening.
Where am I going wrong? Thanks for any help!
I think you just needed that function in the definition of the vm.
Here's a jsfiddle that seems to work:
http://jsfiddle.net/gN3HV/
Update: Here's a fiddle which better leverages knockout and properly accomplishes the goal:
http://jsfiddle.net/gN3HV/7/
elementToChangeTo returns the FeedbackViewModel (same as this) and not the element clicked on--the behavior is a bit different than jQuery.
The second argument passed into updateFeedbackType will be an event, so you could use $(event.target) to get a reference to the clicked element.
self.updateFeedbackType = function (view, event) {
var $elementToChangeTo = $(event.target);
self.feedbackType($elementToChangeTo.attr("title"));
$("#feedbackList li.feedbackItem-Highlighted").removeClass("feedbackItem-Highlighted");
$elementToChangeTo.addClass("feedbackItem-Highlighted");
};
However, #daedalus28 has addressed the larger problem, which is that you're not utilizing knockout.js's strengths and are over-complicating the process. You don't really need both to solve this simplistic condition.

How can i access index of foreach when iterating through ko.computed in knockout.js

i'm building a paged list on the client side with knockout.js and im trying to output the page index with the below code so i get clickable links with numbers so people can switch page.
<ul data-bind="foreach:Paging">
<li>
</li>
</ul>
In my viewmodel
this.Paging = ko.computed(function ()
{
return ko.utils.range(1, this.TotalPages);
});
Everything works, tried just outputtung text:test and it writes test for each page but i want numbers. So the easiest way is of course to access current index in the foreach and + 1.
How would i be able to do this?
The problem could be with your computed ko. You have not bound it to this. So instead of:
this.Paging = ko.computed(function ()
{
return ko.utils.range(1, this.TotalPages);
});
.. try ...
this.Paging = ko.computed(function ()
{
return ko.utils.range(1, this.TotalPages);
}, this);
You can then try ColinE suggestion of text: this
When you use this in bindings, it will be referencing the window object. You ought to be using $data like this:
I tested it using this markup and it worked as expected:
<!-- returns 12345678910 -->
<div data-bind="foreach: ko.utils.range(1,10)"><span data-bind="text: $data"></span></div>

Categories

Resources