I am using the reveal module pattern for my javascript and I am having the darndest time getting the css binding to work correctly with knockout.
My JS
my.js.module = (function ($) {
"use strict";
var my = {
testUrl: null
},
testModel= {
stuff: [{
"testOne": null
}]
},
testViewModel = null,
testId: null;
my.bindStuff = function () {
testViewModel = ko.mapping.fromJS(testModel);
ko.applyBindings(testViewModel, $(my.testId).get(0));
$.getJSON(my.testUrl,
{},
function (data) {
var testModelData = {
stuff: data
};
ko.mapping.fromJS(testModelData, testViewModel);
});
};
return my;
}(jQuery));
and in my cshtml I have
<tbody data-bind="foreach: stuff">
<tr>
<td data-bind="text: testOne"></td>
</tr>
</tbody>
Now I want to use the css binding via knock out to get a css value based on the value of what testOne is, it can be one of three things. I know it will be a ko,computed function but i cant quite figure out how to get each particular instance of stuff to look at testOne and get the correct value to determine what to return via the ko.computed function.
if someone could help me out i would be greatly appreciative.
This fiddle shows how to use mapping with an array and set the class of an element via knockout, its not an exact match to your code but should help:
http://jsfiddle.net/davidoleary/UHaVV/
var newData = {
test:"this is a test",
stuff:[
{"testOne":"Event1"},
{"testOne":"Event2"},
{"testOne":"Event3"}
]
};
var viewModel = ko.mapping.fromJS( newData );
ko.applyBindings(viewModel);
<div data-bind="text:test"></div>
<ul data-bind="foreach: stuff">
<li><span data-bind="text: testOne, attr:{class: testOne}"></span></li>
</ul>
Also have a look at this question to see two ways of setting the class:
Knockout binding css class to an observed model property
You can use attr or the new css binding both are refereed to in the question above.
Related
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
});
Let's say I have a
<button type="button" data-bind="click: actions.remove">×</button>
and a handler
var actions = {
remove: function(item) {
?array?.remove(item); // ?array? is a containing array, accessed somehow
}
}
How do I find ?array? so I can use the same button in any foreach binding?
Clarification:
I know how to do that if I put remove into the view model. However the view model contains hierarchical arrays and I do not really want to go through it all just to get methods in the right places. View model is also updated from server occasionally with the help of ko.mapping, but that does not add any methods to the new data. That is why I implemented the handlers separately.
You try something like this.
<div data-bind="foreach: someArray">
<button type="button" data-bind="click: $parent.actions.remove">x</button>
</div>
//Inside your viewmodel.
var self = this;
self.someArray = ko.observableArray();
self.actions = {
remove: function() {
self.someArray.remove(this); // ?array? is a containing array, accessed somehow
}
}
edit: Sorry, I misread what you meant. You can try something like this to make it work for any foreach binding.
<div data-bind="foreach: someArray">
<button type="button" data-bind="click: function() {$parent.actions.remove($parent.someArray(), $data}">x</button>
</div>
//Inside your viewmodel.
var self = this;
self.someArray = ko.observableArray();
self.actions = {
remove: function(arr, item) {
arr.remove(item); // ?array? is a containing array, accessed somehow
}
}
It is not currently possible.
I raised a new knockout issue for that (currently open):
Allow access to the current array through the binding context.
Also relevant: Support for $last in 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.
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.
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>