I am working in a client development environment and have to adhere to their coding standards. I have the following JS and HTML. My observableArray is coming as not defined. I am not able to get it working. Even the console.logs are printing the correct values.
Please don't worry about ko.applyBindings. It is taken care of.
My JS Code:
define(
['knockout'],
function (ko) {
"use strict";
return {
onLoad: function (widget) {
widget.getDetails= function (prod) {
var abc = prod.DetailsNumbers();
console.log(abc);
var someArray= [];
someArray= abc.split(',');
//console.log(someArray);
var anotherObservableArray = ko.observableArray();
for (var i = 0; i < someArray.length; i++) {
var temp = {
"prodName": ko.observable(someArray[i])
};
anotherObservableArray.push(temp);
}
console.log(anotherObservableArray());
};
}
}
}
);
My HTML Code:
<div id="someId">
<table>
<tbody>
<tr>
<td>Button Here</td>
<td><button data-bind="click: getDetails(product())">Click me</button></td>
</tr>// this here is working fine
</tbody>
</table>
<ul data-bind="foreach: anotherObservableArray"> // this doesnt work
<li>
<span data-bind="text: prodName"> </span>
</li>
</ul>
</div>
anotherObservableArray is not defined
You don't expose anotherObservableArray outside the function scope you declare it in. Basically your code is of this format:
{
onLoad: function (widget) {
widget.getDetails = function (prod) {
var anotherObservableArray = ko.observableArray();
// push some items into the array
console.log(anotherObservableArray());
};
}
}
You somehow need to expose the anotherObservableArray outside the function. For example:
{
onLoad: function (widget) {
widget.getDetails = function (prod) {
var anotherObservableArray = ko.observableArray();
// push some items into the array
console.log(anotherObservableArray());
this.anotherObservableArray = anotherObservableArray; // Expose it on the function
};
}
}
Move var anotherObservableArray = ko.observableArray(); to your VM definition and ensure it's exposed (i.e. "public"). I am imagining you do have something like this:
var vm = {
// ...
// most likely you are exposing getDetails() already
// ....
anotherObservableArray: ko.observableArray()
};
// ...
ko.applyBindings(vm);
Related
I am trying to save the data from the text-boxes to the localStorage using knockout JS! However I am new and not able to figure out this particular scenario. The field has same observable name! Please find my code below.
HTML Code:
<form data-bind="foreach: trialData">
<input type="text" name="name" data-bind="textInput: myData"><br>
</form>
JS Code:
var dataModel = {
myData: ko.observable('new'),
dataTemplate: function (myData) {
var self = this;
self.myData = ko.observable(myData);
}
};
dataModel.collectedNotes = function () {
var self = this;
self.trialData = ko.observableArray([]);
for (var i=0; i<5; i++) {
self.trialData.push (new dataModel.dataTemplate());
}
};
dataModel.collectedNotes();
ko.applyBindings(dataModel);
Traget: The data entered inside the text-boxes should be available in localStorage.
You need to define a Handler function to read the data from the Textboxes and save it to the localstorage. You need to reference the Data which is bound to the click event, which can be accessed using the first parameter. Knockout passes the data and event information as 2 arguments to the click handler function. So, you can add the event handler to your viewModel using the click binding and then unwrap the value and save it to localStorage.
saveToLocalStorage : function(data){
var datatoStore = JSON.stringify(data.trialData().map(x=>x.myData()));
console.log(datatoStore);
localStorage.setItem("TextBoxValue", datatoStore);
}
Complete Code: Please note since this is a sandboxed environment (Running this js Snippet on StackOverflow), localStorage wouldn't work, but it should work in your code. I have added a line in console to get the value to Store.
var dataModel = {
myData: ko.observable('new'),
dataTemplate: function (myData) {
var self = this;
self.myData = ko.observable(myData);
},
saveToLocalStorage : function(data){
var datatoStore = JSON.stringify(data.trialData().map(x=>x.myData()));
console.log(datatoStore);
localStorage.setItem("TextBoxValue", datatoStore);
}
};
dataModel.collectedNotes = function () {
var self = this;
self.trialData = ko.observableArray([]);
for (var i=0; i<5; i++) {
self.trialData.push (new dataModel.dataTemplate());
}
};
dataModel.collectedNotes();
ko.applyBindings(dataModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<form data-bind="foreach: trialData">
<input type="text" name="name" data-bind="textInput: myData"><br>
</form>
<button data-bind="click:saveToLocalStorage">Save To local storage</button>
I am new to knockout. For my problem, I am trying to make it so that for each project, there is a button and textarea. The textarea will be hidden upon page load. If I click the button, it will show the textarea (toggle). Currently, if I click the button, ALL textareas on the page will show, rather than just the corresponding textarea.
I'm hoping the fix for this isn't too dramatic and involving a complete reworking of my code as by some magic, every other functionality has been working thus far. I added the {attr id: guid} (guid is a unique identifier of a project retrieved from the database) statement in an attempt to establish a unique ID so that the right controls were triggered...although that did not work.
Sorry I do not have a working jfiddle to show the issue... I tried to create one but it does not demonstrate the issue.
JS:
//if a cookie exists, extract the data and bind the page with cookie data
if (getCookie('filterCookie')) {
filterCookie = getCookie('filterCookie');
var cookieArray = filterCookie.split(",");
console.log(cookieArray);
$(function () {
var checkboxes = new Array();
for (var i = 0; i < cookieArray.length; i++) {
console.log(i + cookieArray[i]);
checkboxes.push(getCheckboxByValue(cookieArray[i]));
//checkboxes.push(document.querySelectorAll('input[value="' + cookieArray[i] + '"]'));
console.log(checkboxes);
checkboxes[i].checked = true;
}
})
filterCookie = getCookie('filterResultsCookie');
cookieArray = filterCookie.split(",");
filterCookieObj = {};
filterCookieObj.action = "updateProjects";
filterCookieObj.list = cookieArray;
$.ajax("/api/project/", {
type: "POST",
data: JSON.stringify(filterCookieObj)
}).done(function (response) {
proj = response;
ko.cleanNode(c2[0]);
c2.html(original);
ko.applyBindings(new ProjectViewModel(proj), c2[0]);
});
}
//if the cookie doesn't exist, just bind the page
else {
$.ajax("/api/project/", {
type: "POST",
data: JSON.stringify({
action: "getProjects"
})
}).done(function (response) {
proj = response;
ko.cleanNode(c2[0]);
c2.html(original);
ko.applyBindings(new ProjectViewModel(proj), c2[0]);
});
}
View Model:
function ProjectViewModel(proj) {
//console.log(proj);
var self = this;
self.projects = ko.observableArray(proj);
self.show = ko.observable(false);
self.toggleTextArea = function () {
self.show(!self.show());
};
};
HTML:
<!-- ko foreach: projects -->
<div id="eachOppyProject" style="border-bottom: 1px solid #eee;">
<table>
<tbody>
<tr>
<td><a data-bind="attr: { href: '/tools/oppy/' + guid }" style="font-size: 25px;"><span class="link" data-bind=" value: guid, text: name"></span></a></td>
</tr>
<tr data-bind="text: projectDescription"></tr>
<%-- <tr data-bind="text: guid"></tr>--%>
</tbody>
</table>
<span class="forminputtitle">Have you done project this before?</span> <input type="button" value="Yes" data-bind="click: $parent.toggleTextArea" class="btnOppy"/>
<textarea placeholder="Tell us a little of what you've done." data-bind="visible: $parent.show, attr: {'id': guid }" class="form-control newSessionAnalyst" style="height:75px; " /><br />
<span> <input type="checkbox" name="oppyDoProjectAgain" style="padding-top:10px; padding-right:20px;">I'm thinking about doing this again. </span>
<br />
</div><br />
<!-- /ko -->
Spencer:
function ProjectViewModel(proj) {
//console.log(proj);
var self = this;
self.projects = ko.observableArray(proj);
self.projects().forEach(function() { //also tried proj.forEach(function())
self.projects().showComments = ko.observable(false);
self.projects().toggleComments = function () {
self.showComments(!self.showComments());
};
})
};
It's weird that
data-bind="visible: show"
doesn't provide any binding error because context of binding inside ko foreach: project is project not the ProjectViewModel.
Anyway, this solution should solve your problem:
function ViewModel() {
var self = this;
var wrappedProjects = proj.map(function(p) {
return new Project(p);
});
self.projects = ko.observableArray(wrappedProjects);
}
function Project(proj) {
var self = proj;
self.show = ko.observable(false);
self.toggleTextArea = function () {
self.show(!self.show());
}
return self;
}
The problem is that the show observable needs to be defined in the projects array. Currently all the textareas are looking at the same observable. This means you'll have to move the function showTextArea into the projects array as well.
Also you may want to consider renaming your function or getting rid of it entirely. Function names which imply they drive a change directly to the view fly in the face of the MVVM pattern. I'd recommend a name like "toggleComments" as it doesn't reference a view control.
EDIT:
As an example:
function ProjectViewModel(proj) {
//console.log(proj);
var self = this;
self.projects = ko.observableArray(proj);
foreach(var project in self.projects()) {
project.showComments = ko.observable(false);
project.toggleComments = function () {
self.showComments(!self.showComments());
};
}
};
There is probably a much cleaner way to implement this in your project I just wanted to demonstrate my meaning without making a ton of changes to the code you provided.
In my javascript file i have mix of knockout and jquery which contains two different view models and i am having trouble displaying the results:
Javascript:
POSITION ViewModel
var positionViewModel = function (data) {
var _self = this;
_self.PositionName = ko.observable(data.PositionName);
_self.PositionRank = ko.observable(data.PositionRank);
_self.ContentRole = ko.observable(data.ContentRole);
}
positionViewModel.AddPositions = function (data) {
$.each(data, function (index, value) {
positionViewModel.PushPosition(value);
});
};
positionViewModel.PushPosition = function (postion) {
viewModel.PositionTypes.push(new positionViewModel(position));
};
USER ViewModel
// the ViewModel for a single User
var userViewModel = function (data) {
var _self = this;
_self.ID = ko.observable(data.ID);
_self.Name = ko.observable(data.Name);
_self.Email = ko.observable(data.Email);
_self.ContentRole = ko.observable(data.ContentRole);
};
userViewModel.AddUsers = function (data) {
$.each(data, function (index, value) {
userViewModel.PushUser(value);
});
};
userViewModel.PushUser = function (user) {
viewModel.Users.push(new userViewModel(user));
};
Positions and Users
ko.utils.arrayForEach(viewModel.PositionTypes(), function(position){
var usersInPosition = ko.utils.arrayFilter(viewModel.Users(), function(user){
return user.ContentRole() == position.ContentRole();
});
ko.utils.arrayForEach(usersInPosition, function(user){
});
});
Binding
// Binds the main ViewModel
var bindModel = function (data) {
var _self = viewModel;
viewModel.TotalUser = ko.computed(function () {
return _self.Users().length;
});
userViewModel.AddUsers(data);
ko.applyBindings(viewModel, $('#UserView')[0]);
};
View Page
<ul data-bind="foreach:PositionTypes">
<li>
<div>
<span data-bind="text:PositionName"></span>
</div>
<ul data-bind="template: { name: 'grid', foreach: Users}">
</ul>
</li>
</ul>
Result example:
CEO
James
Vice President
John
Workers
Amy
Betsy
How can i alter my view to properly display results from javascript file?
So your architecture was wrong to start out. In your example you are showing a list of type Position and each Position has another list of type User. I have whipped up a fiddle with the correct architecture for you to be able to add on whatever functionality you need. I would seriously look into the knockout documentation as well as design a little bit before you start coding.
http://jsfiddle.net/zBmSN/1/
Consider this fiddle.
I want to add ko.computeds to a ko.observableArray dynamically:
self.items.push(ko.computed({
read: function () {
return items[i];
},
write: function (value) {
//some write action
alert(value);
}
}));
I need to manage the write function from the ko.computed into the array.
With this code, read works great, but knockout is not calling the write function, so alert is not being called.
Am I missing something? Is there a workaround for this?
I resolved it creating another ViewModel:
function item(value) {
var self = this;
self.value = ko.observable(value);
self.computed = ko.computed({
read: self.value,
write: function (value) {
alert(value);
self.value(value);
}
});
}
function header(items) {
var self = this;
self.items = ko.observableArray();
for (var i = 0; i < items.length; i++) {
self.items.push(new item(items[i]));
}
}
The HTML:
<ul data-bind="foreach: items">
<li>
<input type="text" data-bind="value: computed" />
</li>
</ul>
And the fiddle working.
I'm trying to bind a 1-many mapping using KnockoutJS, where 1 zip code can have many 'agents'. I have the following classes:
function CaseAssignmentZipCode(zipcode, agent) {
var self = this;
self.zipcode = ko.observable(zipcode);
self.agent = ko.observable(agent);
}
function Agent(id, name) {
var self = this;
self.id = id;
self.name = name;
}
function ZipcodeAgentsViewModel() {
var self = this;
self.caseAssignmentZipCodes = ko.observableArray([]);
self.agents = ko.observableArray([]);
jdata = $.parseJSON($('#Agents').val());
var mappedAgents = $.map(jdata, function (a) { return new Agent(a.Id, a.Name) });
self.agents(mappedAgents);
var dictAgents = {};
$.each(mappedAgents, function (index, element) {
dictAgents[element.id] = element;
});
var jdata = $.parseJSON($('#CaseAssignmentZipCodes').val());
var mappedZipcodeAgents = $.map(jdata, function (za) { return new CaseAssignmentZipCode(za.ZipCode, dictAgents[za.UserId], false) });
self.caseAssignmentZipCodes(mappedZipcodeAgents);
}
var vm = new ZipcodeAgentsViewModel()
ko.applyBindings(vm);
My bindings look like this:
<table>
<thead><tr><th>Zipcode Agents</th></tr></thead>
<tbody data-bind="foreach: caseAssignmentZipCodes">
<tr>
<td><input data-bind="value: zipcode"></td>
<td><select data-bind="options: $root.agents, value: agent, optionsText: 'name'"></select></td>
<td>Remove</td>
</tr>
</tbody>
</table>
Everything binds fine the first time, with the table and select fields appearing properly. However, nothing happens when I change the selected value on any of the select elements. I have bound other elements to them and these don't update, and I've tried using .subscribe() to listen for the update event, but this doesn't fire either.
I expect there's something wrong with the way I'm setting up/binding these relationships, but I can't figure it out to save my life.
Thanks!
I think you need to add
self.agents = ko.observableArray([]);
at the top of ZipcodeUsersViewModel