ok so i have my view model
function viewModel(calendarData) {
var self = this;
self.Calendars = ko.mapping.fromJS(calendarData);
self.ViewedCalendar = {};//what can/should i set this to be on initial creation?
self.DisplayCalendar = function (calendar) {
self.ViewedCalendar = calendar;
};
};
I then have my html:
<div data-bind="visible: Calendars().length > 0">
<h2>You have <span data-bind="text: Calendars().length"></span> calendars</h2>
<table class="table">
<thead>
<tr>
<th>Calendars</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Calendars">
<tr>
<td data-bind="text: Name"></td>
<td><span data-bind="text: Events().length"></span> events</td>
<td><a href='#' data-bind='click: $root.DisplayCalendar'>View</a></td>
</tr>
</tbody>
</table>
</div>
<div data-bind="visible: ViewedCalendar() !== null">
Your are viewing <span data-bind="text: ViewedCalendar.Name"></span><br />
</div>
What I am trying to achieve is when the user clicks View for a given calendar, that via DisplayCalendar() I set the property ViewedCalendar to be set to the given calendar.
This then shows my div that contains the label stating what calendar is being viewed.
This is all rough code at the minute just to get the basic functionality in place but I'm new to knockout so could use some help.
I'm getting TypeError: ViewedCalendar is not a function or ViewedCalendar is undefined.
Any help would be much appreciated.
The ViewedCalendar property needs to be an observable for knockout to reach to changes in it's value. You defined it like this:
self.ViewedCalendar = {};
Which is an (empty) object literal and not a function (as the error message correctly stated). What you need is:
self.ViewedCalendar = ko.observable(); // empty () give you an empty observable - no calendar selected yet
And then you can update it in your click handler with:
self.ViewedCalendar(calendar);
Here's a working full example:
function viewModel(calendarData) {
var self = this;
self.Calendars = ko.mapping.fromJS(calendarData);
self.ViewedCalendar = ko.observable();
self.DisplayCalendar = function (calendar) {
self.ViewedCalendar(calendar);
};
};
ko.applyBindings(new viewModel([{Name:'some calendar', Events: []}, {Name:'another calendar', Events: []}]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div data-bind="visible: Calendars().length > 0">
<h2>You have <span data-bind="text: Calendars().length"></span> calendars</h2>
<table class="table">
<thead>
<tr>
<th>Calendars</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Calendars">
<tr>
<td data-bind="text: Name"></td>
<td><span data-bind="text: Events().length"></span> events</td>
<td><a href='#' data-bind='click: $root.DisplayCalendar'>View</a></td>
</tr>
</tbody>
</table>
</div>
<div data-bind="with: ViewedCalendar">
Your are viewing <span style="font-weight: bold" data-bind="text: Name"></span><br />
</div>
Related
I have been working on a sample Knockout.js application where there is a list of offices and each office is associated with 1 or more sets of opening hours.
When I click on select in the table the opening hours are shown underneath for the first set of opening hours.
I want to be able to cycle through the array by clicking on the button to show the opening hours for Rota A, then B, then C and back to Rota A again.
With my current solution, if I press on the >> button currently the index is incremented but the opening hours table is not updated until I re-press the select button.
The jsfiddle is here https://jsfiddle.net/58p57dv3/
How, do I make the opening hours in the table update automatically when I press the >>> button
HTML
<div id="container">
<div id="officeInfo">
<table>
<thead>
<tr>
<th>Office ID</th>
<th>Office Name</th>
</tr>
</thead>
<tbody data-bind="foreach: offices">
<tr>
<td data-bind="text: OfficeID"></td>
<td data-bind="text: OfficeName"></td>
<td>Select</td>
</tr>
</tbody>
</table>
</div>
<div id="openingHours" data-bind="with: selectedOffice">
<input type="button" value=">>>" data-bind="click: $parent.nextOpeningHourType">
<table>
<thead>
<tr>
<th>Day</th>
<th>Opening Time</th>
<th>Closing Time</th>
</tr>
</thead>
<tbody data-bind="with: OpeningHours()[$parent.index]">
<tr><td>Monday</td><td data-bind="text: MondayStartTime"></td><td data-bind="text: MondayEndTime"></tr>
<tr><td>Tueday</td><td data-bind="text: TuesdayStartTime"></td><td data-bind="text: TuesdayEndTime"></tr>
<tr><td>Wednesday</td><td data-bind="text: WednesdayStartTime"></td><td data-bind="text: WednesdayEndTime"></tr>
<tr><td>Thursday</td><td data-bind="text: ThursdayStartTime"></td><td data-bind="text: ThursdayEndTime"></tr>
<tr><td>Friday</td><td data-bind="text: FridayStartTime"></td><td data-bind="text: FridayEndTime"></tr>
<tr><td>Saturday</td><td data-bind="text: SaturdayStartTime"></td><td data-bind="text: SaturdayEndTime"></tr>
<tr><td>Sunday</td><td data-bind="text: SundayStartTime"></td><td data-bind="text: SundayEndTime"></tr>
</tbody>
</table>
</div>
</div>
JS
var OfficeViewModel = function() {
var self = this;
self.offices = officeList;
self.selectedOffice = ko.observable();
self.index = ko.observable();
self.index = 0;
self.nextOpeningHourType = function() {
if (self.index < 2) {
self.index++;
} else {
self.index = 0;
}
}
self.selectOffice = function (office) {
self.selectedOffice(office);
}
}
I guess it will work fine if you just make index an observable
self.index = ko.observable(0);
self.nextOpeningHourType = function() {
if (self.index() < 2) {
self.index(self.index()+1);
} else {
self.index(0);
}
}
self.selectOffice = function (office) {
self.index(0);
self.selectedOffice(office);
}
and change your markup as
<tbody data-bind="with: OpeningHours()[$parent.index()]">
JsFiddle: https://jsfiddle.net/newuserjs/58p57dv3/2/
I have the following in my View:
<div data-bind='foreach: providers'>
<div data-bind='text: name'></div>
<button data-bind='click: model.addToCart'>Show</button>
<table class="table">
<thead>
<th style="width: 300px">Name</th>
<th>Price</th>
<th></th>
</thead>
<tbody data-bind='foreach: items'>
<tr>
<td data-bind='text: name'></td>
<td data-bind='text: price'></td>
<td>
<input type="button" data-bind='click: model.add' value="Add">
</td>
</tr>
</tbody>
</table>
</div>
And the following in my model:
function model() {
var self = this;
self.providers = data; //some random array data
self.addToCart = function(place) {
console.log(place);
}
};
I have used the normal click binding in the Knockout.js.
When I run the code, it seems that the addToCart function is not even being called. I get no output from console.log() for any value.
What have I missed? I'm a beginner. Please help out.
Once inside a foreach loop, your scope is the object you're currently iterating on. So, to refer to a function in the parent or root scope you're gonna have to use $root (although $parent is the same in this case):
<button data-bind='click: $root.addToCart'>Show</button>
See Documentation
your parent View Model should have
var self = this;
self.providers = ko.observableArray();
self.model = new Model();
then in your foreach loop you can use
$root.model.addToCart()
I have created a table/grid using jsViews. Each row has an edit button which when clicked selects the row and shows input controls instead of text values.
If I show/hide the inputs using data-link="visible{:#parent.parent.data.selectedIndex!==#index}" then it works fine.
However, I was trying a different approach using {^{if #parent.parent.data.selectedIndex===#index}}...{{else}}...{{/if}} to show/hide the inputs and this doesn't work when selectedIndex changes on my data object.
I also tried with {^{if ~root.selectedIndex===#index}} but that didn't work either. Is it possible to do this with {{if}}? The reason I am trying this over the first method that worked was to avoid rendering lots of select boxes which will just be hidden anyway.
My data object looks like this:
app = {
data: [...],
selectedIndex: null,
select: function select(index) {
if (this.selectedIndex !== index) {
$.observable(this).setProperty("selectedIndex", index);
}
}
};
I link the template like this:
$.templates("#myTemplate").link("#divHolder", app)
.on("click", ".data .editButton", function() {
app.select($.view(this).index);
})
.on("click", ".data .saveButton", function() {
// save details
})
.on("click", ".transmittals .cancelButton", function() {
// reset values
app.select(null);
});
My template is like this:
<script id="myTemplate" type="text/x-jsrender">
<table id="tblData" class="data">
<thead>
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
{^{for data}}
<tr class="item">
<td>
{{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem">
<button class="cancelButton">Cancel</button></span>
{{else}}
<span class="viewItem">
<button class="editButton">Edit</button></span>
{{/if}}
</td>
<td>
{^{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem"><input type="text" data-link="B" /></span>
{{else}}
<span class="viewItem" data-link="B"></span>
{{/if}}
</td>
<td>
{^{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem"><input type="text" data-link="C" /></span>
{{else}}
<span class="viewItem" data-link="C"></span>
{{/if}}
</td>
</tr>
{{/for}}
</tbody>
</table>
</script>
When you add an {{if}} block, it is a nested view, so the button click is not getting you the item view with the index. You need to use $.view(this).parent.index - or, simpler, $.view(this).getIndex() - which automatically steps up through nested views (if any) to the item view and gets its index.
app.select($.view(this).getIndex());
(See discussion here: https://github.com/BorisMoore/jsrender/issues/173#issuecomment-11058106)
BTW here is a modified form of your sample, just to give you some ideas. It uses <button data-link="{on ~root.select #getIndex()}">Edit</button> to hook up the click handler on the button and call the select method directly, passing it the index:
<script id="myTemplate" type="text/x-jsrender">
<table id="tblData" class="data">
<thead>
...
</thead>
<tbody>
{^{for data}}
<tr class="item">
{^{if ~root.selectedIndex===#index}}
<td><button class="cancelButton" data-link="{on ~root.select null}">Cancel</button></td>
<td><input data-link="A" /></td>
<td><input data-link="B" /></td>
{{else}}
<td><button class="editButton" data-link="{on ~root.select #getIndex()}">Edit</button></td>
<td data-link="A"></td>
<td data-link="B"></td>
{{/if}}
</tr>
{{/for}}
</tbody>
</table>
</script>
<div id="divHolder"></div>
<script>
var app = {
data: [{A:"aa", B: "bb"},{A:"aa2", B: "bb2"}],
selectedIndex: null,
select: function(index) {
if (this.selectedIndex !== index) {
$.observable(this).setProperty("selectedIndex", index);
}
}
};
$.templates("#myTemplate").link("#divHolder", app);
</script>
I have a html table that has an <ul> in it. The table is bound by a knockout view model.
When I click on the TEST text I would like remove the <tr> that the clicked TEST text is in, from the table. I have it now so when I click on REMOVE it removes the <tr> but I need it so when I click on the TEST it removes the <tr>. Help is greatly appreciated!
FIDDLE with it, and/or see code snippet below.
VIEW MODEL
var rowModel = function(id, list) {
this.uid = ko.observable(id);
this.myList = ko.observableArray(list);
};
function myViewModel() {
var self = this;
self.myArr = ko.observableArray([
new rowModel(1, ["str1", "str2", "str3"]),
new rowModel(2, ["str1", "str2"]),
new rowModel(3, ["str1"])
]);
//this.myList = ko.observableArray();
self.removeItem = function(rowItem) {
self.myArr.remove(rowItem);
}
}
ko.applyBindings(new myViewModel());
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="table table-striped">
<thead>
<tr>
<td>Col 1</td>
<td>Col 2</td>
</tr>
</thead>
<tbody data-bind="foreach: myArr">
<tr>
<td data-bind="text: uid"></td>
<td>
<ul data-bind="foreach: myList">
<li><span data-bind="text: $data"></span> TEST
</li>
</ul>
</td>
<td>REMOVE
</td>
</tr>
</tbody>
</table>
The problem is that the context of the data for the TEST link is a myList item, not a myArr item. Your removeItem() function is expecting a myList item. You need to pass the data from the (in this case) parent context. You can use $parentContext.$data or simply $parent.
<ul data-bind="foreach: myList">
<li>
<span data-bind="text: $data"></span>
TEST
</li>
</ul>
Uncaught ReferenceError: Unable to process binding "foreach: function (){return Educations }"
Uncaught ReferenceError: Unable to process binding "foreach: function (){return WorkExperience }"
I couldn't figure out why the binding is failing.
i have the following two tables one for Education and other for Work Experience, They give the error when i'm trying to bind the both table in one view, If i remove the binding (JS + HTML code) it works fine
HTML:
<div id=divtable1 class="widget widget-simple widget-table">
<table id="Table1" class="table table-striped table-content table-condensed boo-table table-hover">
<thead>
<tr id="Tr1" class="filter">
<th>University<span class="required">*</span></th>
<th>Location <span class="required">*</span></th>
<th></th>
</tr>
</thead>
<tbody data-bind='foreach: Educations'>
<tr>
<td><input type="text" class='span11 required' data-bind="value: SchoolName" /></td>
<td><input type="text" class='span11 required' data-bind="value: Location" /></td>
<td><a href='#' data-bind='click: $root.removeEducation'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: $root.addEducation' class="btn btn-blue">Add Education</button>
</div>
<div id="divtable2">
<table id="Table2">
<thead>
<tr id="Tr2" class="filter">
<th>Employer Name<span class="required">*</span></th>
<th>EmployerAddress <span class="required">*</span></th>
<th></th>
</tr>
</thead>
<tbody data-bind='foreach: WorkExperience'>
<tr>
<td><input type="text" class='span11 required' data-bind="value: EmployerName" /></td>
<td><input type="text" class='span11 required' data-bind="value: EmployerAddress" /></td>
<td><a href='#' data-bind='click: $root.removeWorkExperience'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: $root.addWorkExperience' class="btn btn-blue">Add Work Experience</button>
</div>
Java Script:
<script type="text/javascript">
var Educations = function (educations) {
var self = this;
self.Educations = ko.mapping.fromJS(educations);
self.addEducation = function () {
self.Educations.push({"SchoolName": ko.observable(""), "Location": ko.observable("")});
};
self.removeEducation = function (education) {
self.Educations.remove(education);
};
};
var viewModel = new Educations(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Educations)));
ko.applyBindings(viewModel);
</script>
<script type="text/javascript">
var WorkExperience = function (workexperiences) {
var self = this;
self.WorkExperience = ko.mapping.fromJS(workexperiences);
self.addWorkExperience = function () {
self.WorkExperience.push({ "EmployerName": ko.observable(""), "EmployerAddress": ko.observable("")});
};
self.removeWorkExperience = function (workexperience) {
self.WorkExperience.remove(workexperience);
};
};
var viewModel = new WorkExperience(#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.WorkExperience)));
ko.applyBindings(viewModel);
</script>
Also I tried to bind Table1 but it didn't work
ko.applyBindings(viewModel, $('#Table1')[0]);
try adding this <pre data-bind="text: ko.toJSON($data, null, 2)"></pre> to your view. it will output the data that knockout contains in the current context.
Also you have one view and two view models that are trying to bind to it. create one viewmodel with both Educations and WorkExperience as properties.
something like
var vm = {
Educations : educationViewModel,
WorkExperience: workExperienceViewModel
}
ko.applyBindings(vm);
If you want to bind two view-models separately, you must define which section of your view to bind to. You do this by providing the element to ko.applyBindings as the second parameter.
ko.applyBindings(viewModel, document.getElementById("divtable1"));