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>
Related
I am trying to implement collapse and expand panels in a ngRepeat. I have tried this:
<tbody ng-repeat="item in Items">
<tr data-toggle="collapse" class="accordion-toggle">
<td>{{item.name}}</td>
<td>
<a class="dropdown-toggle" ng-click="isCollapsed = !isCollapsed;getDetails(item.id);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="caret"></span></a>
</td></tr>
<tr>
<td class="hiddenRow">
<div class="accordian-body collapse" collapse="isCollapsed">
<table class="table display" style="border-collapse:collapse;">
<thead><tr>
<th>Student Name</th><th>Roll Number</th></tr></thead>
<tbody>
<tr>
<td>{{studentRecord.patientName}}</td><td>{{studentRecord.rollNumber}}</td>
</tr></tbody></table>
</div>
</td>
</tr>
</tbody>
Script :
$scope.getDetails = function (sId)
{
var studentDetails = DetailService.getDetail("studentsDetails", sId);
studentDetails.then(function(data) {
$scope.studentRecord = data;
});
}
When I click on single caret button in ngRepeat it is working fine. But, when I click multiple items, repeater creates a scope for every item. So, all records in ngRepeat gets same scope values. How to resolve it.
U should make property in item like
item.studentRecord
then use
getDetails(item)
$scope.getDetails = function (item)
{
var studentDetails = DetailService.getDetail("studentsDetails", item.id);
studentDetails.then(function(data) {
item.studentRecord = data;
});
}
<td>{{item.studentRecord.patientName}}</td>
<td>{{item.studentRecord.rollNumber}}</td>
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 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>
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>
I slimmed down my actual code but I can't get this work. I am using knockoutjs and bootstrap with inline knockout templates. I use to just put a bunch of input's inside a div but I changed it to a table for alignment reasons. I know the property names are correct and the javascript console doesn't show any errors at all for bad variables or binding issues. I am putting the foreach in a TR tag instead of the TBODY tag because I don't know how many checkboxes I will have every time and I don't want them in rows exactly, just one TR element and a bunch of TD cells inside that one TR tag for now. How can I make this work??
<div id="Counties" class="well well-large checkbox inline">
<table class="table table-condensed">
<tbody>
<tr data-bind="foreach: { data: counties
}">
<td><input type="checkbox" data-bind="attr: { value: $data.sid }" />$data.name
</td>
</tr>
</tbody>
</table>
</div>
Here are my viewModels :
function searchVm() {
var self = this;
self.counties = ko.observableArray([]); //array of jurisItem
}
function jurisItem(name, sid) {
var self = this;
self.name = name;
self.sid = sid;
}
Edit :
I also tried this based on knockoutjs documentation and it doesn't work. I know I can do this in other ways using jquery but I would prefer knockout template syntax...
<table class="table table-condensed">
<tbody>
<tr>
<!-- ko foreach: $root.counties -->
<td>
<input type="checkbox" data-bind="attr: { value: $data.sid }" />$data.name
</td>
<!-- /ko -->
</tr>
</tbody>
</table>
I am not sure what are You trying to do. I made some sample.
html:
<div id="Counties" class="well well-large checkbox inline">
<table class="table table-condensed">
<tbody>
<tr data-bind="foreach: counties">
<td>
<input type="checkbox" data-bind="attr: { value: sid }" />
<span data-bind="text: name" />
</td>
</tr>
</tbody>
</table>
</div>
javascript:
$(function () {
var SearchVm = function () {
var self = this;
self.counties = ko.observableArray([]);
};
var JurisItem = function (name, sid) {
var self = this;
self.name = name;
self.sid = sid;
}
var item1 = new JurisItem("TestName1", "TestSid1");
var item2 = new JurisItem("TestName2", "TestSid2");
var searchViewModel = new SearchVm();
searchViewModel.counties.push(item1);
searchViewModel.counties.push(item2);
ko.applyBindings(searchViewModel);
})
Does this work for You:
http://jsfiddle.net/PVMjy/41/