I'm getting some values from webapi using knockout.js and then result of that (holded in span) I'm trying to use in the other place (input in table row). Result I'm showing this way:
<h3 data-bind="foreach: book">
<span data-bind="text: Hotel" class="label label-info"/>
<span data-bind="text: Номер" class="label label-info"/>
<span class="label label-info" data-bind=" text: Фамилия"/>
<span class="label label-info ad" data-bind=" text: Колчел"/>
<span class="label label-info ch" data-bind=" text: Дети"/>
</h3>
and this is knockout code:
<script>
function BookViewModel(baseUri) {
var self = this;
self.Номер = ko.observable("");
self.Колчел = ko.observable("");
self.Дети = ko.observable("");
self.Фамилия = ko.observable("");
self.Hotel = ko.observable("");
var book = {
Номер: self.Номер,
Колчел: self.Колчел,
Дети: self.Дети,
Фамилия: self.Фамилия,
Hotel: self.Hotel
};
self.book = ko.observable();
self.books = ko.observableArray();
$.getJSON(baseUri, self.book);
}
$(document).ready(function () {
var url = location.href.split("/")
var baseUri;
if (url[4].toString = 'x') {
baseUri = '/api/xTourist/' + url[5];
}
else if (url[4].toString = 'y') {
baseUri = '/api/yTourist/' + url[5];
}
ko.applyBindings(new BookViewModel(baseUri));
//This is how I'm trying to read result and use this result in input field in the other table.
var ad = $("span.ad").val();
var ch = $("span.ch").val();
$("#gvOrders tr input.pax_ad").each(function () {
$(this).val(ad);
});
$("#gvOrders tr input.pax_ch").each(function () {
$(this).val();
});
});
</script>
Unfortunately this var is undefrined. I'm really don't understand why values cannot be readed after ko already apply binding.
The knockout documentation is really good. I suggest you start there to help get a better understanding of how knockout works. It'll really save you some time and frustration.
As for the $.getJSON call, the documentation has lots of information and examples.
Related
I am altering an observableArray, modifying some data in a subscribe event. First I am converting the ObservableArray using ko.toJS(), mapping trough the data, and altering. At the end I call self.menuCategories(jsArray) to set the observableArray again.
It seems like I lose the "connection" to the observableArray in some way, since the foreach statement in my code suddenly breaks.
Either there is a very much easier way to handle this, or I am not handling the observables correctly.
CODE :
var MenuWizardModel = function() {
var self = this;
self.menuCategories = ko.observableArray();
self.commonDiscount = ko.observable(0);
// Handling adding items to menuCategories.
self.addNewSubMenuItem = function () {
var newSubMenuItem = new SubMenuItemViewModel(self.newSubMenuItemName(), []);
self.menuCategories.push(newSubMenuItem);
self.newSubMenuItemName(null);
self.createNewSubMenu(false);
}
function SubMenuItemViewModel(name, foodItemList) {
var self = this;
self.name = ko.observable(name);
self.foodItemList = ko.observableArray(foodItemList);
}
self.commonDiscount.subscribe(function(val) {
var discount = parseInt(val) / 100;
var jsArray = ko.toJS(self.menuCategories);
console.log(jsArray)
jsArray = ko.toJS(jsonArray[0].foodItemList.map(item => {
item.price = parseInt(item.price) - (parseInt(item.price) * discount);
return item;
}));
self.menuCategories(jsArray);
});
MARKUP :
<div data-bind="foreach: menuCategories">
<h4 data-bind="text: name"></h4>
<div data-bind="foreach: foodItemList" class="list-group">
...
DATA :
I think the best way to handle this type of thing is to add a computed observable to the fooditem that captures the global discount and calculates the discounted price.
something like the following.
var MenuWizardModel = function() {
var self = this;
self.menuCategories = ko.observableArray([{
name: 'Main Meals'
}]);
self.commonDiscount = ko.observable(0);
self.newSubMenuItemName = ko.observable();
// Handling adding items to menuCategories.
self.addNewSubMenuItem = function() {
var newSubMenuItem = new SubMenuItemViewModel(self.newSubMenuItemName(), [{name: 'Oranges', price: 3.99}]);
self.menuCategories.push(newSubMenuItem);
self.newSubMenuItemName(null);
//self.createNewSubMenu(false);
}
function mapFoodItem(item){
item.discountedPrice= ko.pureComputed(function(){
var discount = parseInt(self.commonDiscount()) / 100
return parseInt(item.price) - (parseInt(item.price) * discount);
});
return item;
}
function SubMenuItemViewModel(name, foodItemList) {
var self = this;
self.name = ko.observable(name);
self.foodItemList = ko.observableArray(foodItemList.map(mapFoodItem));
}
};
ko.applyBindings(new MenuWizardModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>Discount <input data-bind="value: commonDiscount"></label>
<label>Sub Menu Name: <input data-bind="value: newSubMenuItemName" /></label>
<button data-bind="click: addNewSubMenuItem">Add Sub Menu</button>
<div data-bind="foreach: {data: menuCategories, as: 'menu' }">
<h4 data-bind="text: menu.name"></h4>
<div data-bind="foreach: {data: menu.foodItemList, as: 'food'}" class="list-group">
<div class="list-group-item">
Name: <span data-bind="text: food.name"></span>
Price: <span data-bind="text: food.price"></span>
Discounted Price: <span data-bind="text: food.discountedPrice"></span>
</div>
</div>
</div>
I have the following button:
<button class="btn actionButtonIcon" id="DashboardEdit" data-bind="click: changeButtonText">
<figure>
<img src="../../../Images/NotesPink.png" />
<figcaption data-bind="text: $data.ProcurementbuttonText() ? 'Save': 'Edit'"></figcaption>
</figure>
</button>
I want to only show it in this specific url
http://localhost:5595/#scorecard/ec5aa8ed-2798-4e71-b13d-f3e525994538/dashboard/PrefDashBoard
Bearing in mind that ec5aa8ed-2798-4e71-b13d-f3e525994538 is an id, thats always changing but i want it to show with all ids as well for example the button should show here as well
http://localhost:5595/#scorecard/2356789-234-234d-g3g3-reg456452/dashboard/PrefDashBoard
and i want to hide it where this isnt the url.
I tried the following code but it doesnt seem to work:
<script>
$(document).ready(function(){
if(window.location.pathname.match(/\/dashboard/PrefDashBoard))
{
$(".DashboardEdit").show();
}
else
{
$(".DashboardEdit").hide();
}
});
</script>
Here is the JS of that button:
self.ProcurementbuttonText = ko.observable(false);
self.changeButtonText = function(){
self.ProcurementbuttonText(!self.ProcurementbuttonText())
if (!self.ProcurementbuttonText()){
var data = {
'ScorecardId':ko.observable(localStorage.getItem('scorecardId'))(),
'DashboardConfig':ko.observable(localStorage.getItem('ElementDataWidget'))()
};
PreferentialProcurementDashboardApi.Save(data);
}
}
self.DashboardEdit = ko.computed({
read: function () {
var text = 'Customise your dashboard';
if (!self.EnableScorecardFeatures()) {
text = 'This feature is currently unavailable for this scorecard type';
} else {
if (!self.HasDocumentsRole()) {
text = 'You do not have sufficient rights to access the Supporting Documents view';
}
}
return text;
}
});
i think you can take advantage of the visible binding to show/hide the button based on your criteria
function PageViewModel() {
var self = this;
self.buttonVisible = ko.observable(true);
self.changeButtonText = function() {
self.ProcurementbuttonText(!self.ProcurementbuttonText());
}
self.ProcurementbuttonText = ko.observable(false);
self.buttonText = ko.pureComputed(function() {
return self.ProcurementbuttonText() ? "Save" : "Edit";
});
self.isButtonVisible = ko.computed(function() {
//do some your logic here. just need to return a true or false value;
return self.buttonVisible();
});
self.labelText = ko.pureComputed(function() {
var messageStart = "click to ";
var state = self.buttonVisible() ? 'hide' : 'show';
var messageEnd = " button";
return messageStart + state + messageEnd;
});
}
ko.applyBindings(new PageViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<button class="btn actionButtonIcon" id="DashboardEdit" data-bind="click: changeButtonText, visible: isButtonVisible, text: buttonText">
Click me.
</button>
<br/>
<br/>
<label><span data-bind="text: labelText " ></span>
<input type="checkbox" data-bind="checked: buttonVisible" />
</label>
If you have Durandal's router plugin installed and configured, you can also use the activeInstruction() observable to get info about the current route. You can then use this in your computed to check if the current fragment matches your page route.
More info here: http://durandaljs.com/documentation/api#class/Router/property/activeInstruction
I currently have a list of students and a list of classes in a school object. I would like each class object to be able to display a filtered list of students based on the class id property.
I have tried to access the parent object via custom binding but have not had any success.
Perhaps I am looking at the problem the wrong way? I have spent a couple days on this and whichever way I tackle it I always need to access a value on a parent object.
Are there any methods of accessing what I need? I am beginning to think that it is not possible to access a global style variable.
function School()
{
var self = this;
self.ClassVMs = ko.observableArray([]).indexed('Number');
self.ChildVMs = ko.observableArray([]).indexed('Number');
}
function ClassVM(classId, text)
{
var self = this;
self.Number = ko.observable();
self.Text = ko.observable(text);
self.ClassId = ko.observable(classId);
}
function ChildVM(classId, text)
{
var self = this;
self.Number = ko.observable();
self.ClassId = ko.observable(classId);
self.Text = ko.observable(text);
}
I have a Fiddle with my setup. Any and all guidance is appreciated. Thanks
You do not need global variables to solve this. Knockout has $root and $parent to step slightly outside of the scope you're in inside a foreach. In addition, if really needed, you can always make sure the view models get another type of view model as its dependency. In fact, if one view model has a list of sub view models it already has such a dependency.
What you need to think about is what your UI/UX is going to be like. Are you designing your view models to support a view where the user is "viewing" a student and enrolls him/her in classes? Or is the app user viewing a class and adding students one by one?
Here's a variant that shows a little bit of both:
function School(classes) {
var self = this;
self.classes = ko.observableArray(classes);
self.students = ko.observableArray([]);
self.enroll = function(child, someClass) {
if (self.students().indexOf(child) < 0) {
self.students.push(child);
}
if (someClass.students().indexOf(child) < 0) {
someClass.students.push(child);
}
};
self.enrollNewChild = function(someClass) {
if (!!someClass.childToBeEnrolled()) {
self.enroll(someClass.childToBeEnrolled(), someClass);
someClass.childToBeEnrolled(null);
}
};
self.enrollInClass = function(child) {
if (!!child.classToBeEnrolledIn()) {
self.enroll(child, child.classToBeEnrolledIn());
child.classToBeEnrolledIn(null);
}
};
}
function Class(id, txt) {
var self = this;
self.id = ko.observable(id);
self.txt = ko.observable(txt);
self.students = ko.observableArray([]);
self.studentsCsv = ko.computed(function() {
return self.students().map(function(s) { return s.txt(); }).join(", ");
});
self.childToBeEnrolled = ko.observable(null);
}
function Child(id, txt) {
var self = this;
self.id = ko.observable(id);
self.txt = ko.observable(txt);
self.classToBeEnrolledIn = ko.observable(null);
}
var english = new Class(1, "English 1");
var math1 = new Class(2, "Mathematics 1");
var math2 = new Class(2, "Mathematics 2");
var john = new Child(1, "John Doe");
var mary = new Child(1, "Mary Roe");
var rick = new Child(1, "Rick Roll");
var marc = new Child(1, "Marcus Aurelius");
var school = new School([english, math1, math2]);
ko.applyBindings(school);
// Method 1:
school.enroll(john, english);
school.enroll(john, math2);
school.enroll(marc, english);
school.enroll(mary, math2);
school.enroll(mary, english);
school.enroll(rick, english);
school.enroll(rick, math1);
td { background-color: #eee; padding: 2px 10px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<h3>Classes:</h3>
<table>
<tbody data-bind="foreach: classes">
<tr>
<td><span data-bind="text: txt"></span></td>
<td><span data-bind="text: studentsCsv"></span></td>
<td>
Add student
<select data-bind="options: $root.students, value: childToBeEnrolled, optionsText: 'txt', optionsCaption: 'Choose...'"></select>
<button data-bind="click: $root.enrollNewChild">enroll now</button>
</td>
</tr>
</tbody>
</table>
<h3>School Students</h3>
<table>
<tbody data-bind="foreach: students">
<tr>
<td><span data-bind="text: txt"></span></td>
<td>
Enroll in:
<select data-bind="options: $root.classes, optionsText: 'txt', value: classToBeEnrolledIn, optionsCaption: 'Choose...'"></select>
<button data-bind="click: $root.enrollInClass">enroll now</button>
</td>
</tr>
</tbody>
</table>
It does not answer your question directly ("display a filtered list of students based on the class id property") because I think that's an XY-problem, and you're better off trying to find a solution like the above where you have proper references instead of having to use id and some kind of lookup mechanism.
Yes I can see how it could be considered a bit of an XY problem. You both made look at it differently which was a great help.
The way I originally would have liked wasn't looking feasible. My compromise was to add a computed to the root view model and display the list this way.
function School()
{
var self = this;
self.ClassVMs = ko.observableArray([]).indexed('Number');
self.ChildVMs = ko.observableArray([]).indexed('Number');
self.DisplayClassId = ko.observable(1);
self.Display = function(x)
{
console.log(x);
self.DisplayClassId(x);
}
}
var viewModel = new School();
viewModel.filteredItems = ko.computed(function () {
var filter = viewModel.DisplayClassId();
if (!filter) {
return viewModel.ChildVMs();
} else {
var filtered = ko.utils.arrayFilter(viewModel.ChildVMs(), function (item) {
return (item.ClassId() === filter);
});
return filtered;
}
})
Fiddle for reference
Here is a fiddle
I have this html:
<div class="margin:0px; padding:0px; outline:0; border:0;" data-bind="with: notesViewModel">
<table class="table table-striped table-hover" data-bind="with: notes">
<thead><tr><th>Date Logged</th><th>Content</th><th>Logged By</th><th></th></tr>
</thead>
<tbody data-bind="foreach: allNotes">
<tr>
<td data-bind="text: date"></td>
<td data-bind="text: compressedContent"></td>
<td data-bind="text: logged"></td>
<td><img src="/images/detail.png" data-bind="click: $root.goToNote.bind($data, $index())" width="20" alt="Details"/></td>
</tr>
</tbody>
</table>
<div class="noteView" data-bind="with: chosenNote">
<div class="info">
<p><label>Date:</label><span data-bind="text: date"></span></p>
<p><label>Logged:</label><span data-bind="text: logged"></span></p>
</div>
<p class="message" data-bind="html: content"></p>
<button class="btn btn-default" data-bind="click: $root.toNotes">Back to Notes</button>
</div>
<div class="editor-label" style="margin-top:10px">
Notes
</div>
<div class="editor-field">
<textarea id="contact_note" rows="5" class="form-control" data-bind="value: $root.noteContent"></textarea>
<p data-bind="text: $root.characterCounter"></p>
<button class="btn btn-info" data-bind="click: $root.saveNotes">Save</button>
<div data-bind="html: $root.status">
</div>
</div>
</div>
And this JavaScript using knockout:
var notesViewModel = function () {
var self = this;
self.notes = ko.observable(null);
self.chosenNote = ko.observable();
self.allNotes = new Array();
self.user = "user1";
// behaviours
self.goToNote = function (noteIndex) {
self.notes(null);
self.chosenNote(new note(self.allNotes[noteIndex]));
};
self.toNotes = function () {
self.chosenNote(null);
self.notes({ allNotes: $.map(self.allNotes, function (item) { return new note(item); }) });
console.log(self.notes());
}
self.noteContent = ko.observable();
self.saveNotes = function () {
var request = $.ajax({
url: "EnquiryManagement/Contact/SaveNotes",
type: "GET",
dataType: "json",
data: { id: "1322dsa142d2131we2", content: self.noteContent() }
});
request.done(function (result, message) {
var mess = "";
var err = false;
var imgSrc = "";
if (message = "success") {
if (result.success) {
mess = "Successfully Updated";
imgSrc = "/images/tick.png";
self.allNotes.push({ date: new Date().toUTCString(), content: self.noteContent(), logged: self.user });
self.toNotes();
} else {
mess = "Server Error";
imgSrc = "/images/redcross.png";
err = true;
}
} else {
mess = "Ajax Client Error";
imgSrc = "/images/redcross.png";
err = true;
}
self.status(CRTBL.CreateMessageOutput(err, mess, imgSrc));
self.noteContent(null);
setTimeout(function () {
self.status(null);
}, 4000);
});
};
self.status = ko.observable();
self.characterCounter = ko.computed(function () {
return self.noteContent() == undefined ? 0 : self.noteContent().length;
});
};
var note = function (data) {
var self = this;
console.log(data.date);
self.date = CRTBL.FormatIsoDate(data.date);
self.content = data.content;
self.compressedContent = data.content == null ? "" : data.content.length < 25 ? data.content : data.content.substring(0, 25) + " ...";
self.logged = data.logged;
console.log(this);
};
ko.applyBindings(new notesViewModel());
When I first load the page it says:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: notes is not defined;
Bindings value: with: notes
However, I pass it null, so it shouldn't show anything, because when I do the function goToNote then do goToNotes it sets the notes observable to null
So why can't I start off with this null value?
The problem is where you have:
<div data-bind="with: notesViewModel">
That makes it look for a property "notesViewModel" within your notesViewModel, which does not exist.
If you only have one view model you can just remove that data binding and it will work fine.
If, however, you wish to apply your view model to just that div specifically and not the entire page, give it an ID or some other form of accessor, and add it as the second parameter in applyBindings, as follows:
HTML:
<div id="myDiv">
JS:
ko.applyBindings(new notesViewModel(), document.getElementById('myDiv'));
This is generally only necessary where you have multiple view models in the same page.
Like what bcmcfc has put, however, due to my scenario being a multi-viewModel scenario I don't think his solution is quite the right one.
In order to achieve the correct results, first of all I extrapolated out the self.notes = ko.observable(null); into a viewModel which makes doing the table binding far easier.
Then to fix the binding issues instead of setting an element for the bind to take place, I merely did this:
ko.applyBindings({
mainViewModel: new mainViewModel(),
notesViewModel: new notesViewModel()
});
In my original code I have two viewModels which is why I was getting this error. With this method the key is:
I don't create dependancies!
Instead of tieing the viewModel to a certain dom element which can change quite easily and cause having to go and changes things with ko, plus if I add more viewModels then it can get more complicated. I simply do:
data-bind="with: viewModel"
That way I can bind to any DOM object and I can have has many as I like.
This is the solution that solved my post.
Here is the jsfiddle
I am a newbee to knockout, I'm trying to move from the MVC ViewModel binding.
I have a complex model:
SearchStudentsModel which has 2 properties
Collection of Students (Subset of students)
Number of Students overall
Note that the length of the collection isn't equal to the number overall.
I need to implement a search functionality
Student will have all the regular properties plus IsActive indicator.
I use ul and li tags to data-bind the details.
The search screen should facilitate the user in marking the active flag with an indicator (on and off) and immediately data should be saved in the database.
All the examples I referred to talk about only one level of model. I have a SearchStudent model and within that I have a collection of students.
How should the binding be for this hierarchy of models?
I have refactored your jsFiddle. Hoping you can now understand knockoutJS better. It is not your whole page/Knockout, but I think with this snippet your problem can be solved.
the markup:
<button id="searchEmployees" type="button" data-bind="click: search">Search</button>
<li data-bind="foreach: Employees">
ID: <span data-bind="text: Id"></span><br/>
Name: <span data-bind="text: Name"></span><br/>
Active: <span data-bind="click: ToggleActivation, text: IsActive"></span> <-- click<br/>
</li>
<span data-bind="text: Employees().length"></span> of
<span data-bind="text: AllEmployees().length"></span>
the js/viewmodel
function Employee(id, name, isactive){
var self = this;
self.IsActive = ko.observable(isactive);
self.Id = ko.observable(id);
self.Name = ko.observable(name);
self.ToggleActivation = function () {
if(self.IsActive() === true)
self.IsActive(false);
else
self.IsActive(true);
};
}
var searchEmployeeViewModel = function () {
var self = this;
self.Employees = ko.observableArray([]);
self.AllEmployees = ko.observableArray([]);
self.search = function () {
//Ajax call to populate Employees - foreach on onsuccess
var employee1 = new Employee(2, "Jane Doe", true);
var employee2 = new Employee(3, "Kyle Doe", false);
var employee3 = new Employee(4, "Tyra Doe", false);
var employee = new Employee(1, "John Doe", true);
self.AllEmployees.push(employee);
self.AllEmployees.push(employee1);
self.AllEmployees.push(employee2);
self.AllEmployees.push(employee3);
self.Employees.push(employee);
}
}
$(document).ready(function () {
ko.applyBindings(new searchEmployeeViewModel());
});
or you can simply use my jsFiddle if you do not like reading my code here ;)