Stringing KnockOut conditionals together - javascript

In the aspx of my page I have the following Knockout conditional:
<tbody id="resultsTable" data-bind="foreach: get_contacts()">
<!-- ko if: get_xPos() == 0 -->
<tr>
<td>
<span data-bind="text: get_lname()">
</span>
,
<span data-bind="text:get_fname()">
</span>
<br />
<span data-bind="text: get_email()">
</span>
<br />
<span data-bind="text: get_phone()">
</span>
<br />
<span data-bind="text: get_office()">
</span>
</td>
</tr>
<!-- /ko -->
</tbody>
This works by itself. If I add another Knockout conditional immediately after the first (directly before the </tbody>), Knockout throws an error Cannot find closing comment tag to match: ko ifnot: get_xPos() == 0:
<!-- ko ifnot: get_xPos() == 0 -->
<td>
<span data-bind="text: get_lname()">
</span>
,
<span data-bind="text:get_fname()">
</span>
<br />
<span data-bind="text: get_email()">
</span>
<br />
<span data-bind="text: get_phone()">
</span>
<br />
<span data-bind="text: get_office()">
</span>
</td>
<!-- /ko -->
The intention here is to conditionally create a new row. If my element has an xPosition of 0, then I make a new row and cell. Otherwise I just create a new cell in my table.
Can anyone point out what's wrong with my code?

From the comments:
So you want to create table with 5 cells in each row?
Right.
Reflect that circumstance in your view model. That's what it's for. The view should not do this.
Computed observables are the way to solve this. In your view model add:
self.contactRows = ko.computed(function () {
var rows = [],
i,
contacts = self.get_contacts();
for (i = 0; i < contacts.length; i += 5) {
rows.push( contacts.slice(i, i + 5) );
}
return rows;
});
and in your view use it:
<tbody id="resultsTable" data-bind="foreach: contactRows">
<tr data-bind="foreach: $data">
<td>
<span data-bind="text: get_lname()"></span>,
<span data-bind="text: get_fname()"></span><br />
<span data-bind="text: get_email()"></span><br />
<span data-bind="text: get_phone()"></span><br />
<span data-bind="text: get_office()"></span>
</td>
</tr>
</tbody>
Think if you can find a nicer markup for what you are trying to to.
Maybe that's a better approach:
Semantically you are trying to display a (nested) list. Using ul / li with appropriate CSS would result in more meaningful markup than using a table. And you wouldn't have to fill in meaningless empty cells just to make the table look right.
You could even drop the idea of nesting altogether and make a single list that you style to allow a maximum of 5 "contact" list items per row (fixed dimension items floated left in a container that's 5 times wider than a single item).
That way you could use one simple foreach binding in your view and you'd not need a computed observable at all. Displaying 6 or 4 items per row would amount to one trivial CSS change.

Related

Add styles to function-returned data

I'm trying to add CSS styles to a string computed in Javascript. It goes through a series of transformation functions:
var fieldSetTransformation = setFieldTransformation(iteration);
fieldSetTransformation = stylePropertyName(fieldSetTransformation);
This value then is passed to a table generated in a directive through AngularJS's ng-repeat:
<tbody class="tableBody">
<tr ng-repeat="line in linesArray">
<td ng-repeat="column in ::columns" width="{{::column.width}}" ng-class="{
'center-text' : (!line[column.key] || line[column.key].length === 0)
}">{{line[column.key] !== undefined ? line[column.key] : '--'}}<span ng-if="column.key == 'user'">
<i id="plus-icon-styling"
class="fa fa-plus-circle action-icon">
</i>
</span>
</td>
</tr>
</tbody>
So I'm struggling to append it to an existing container.
What I have tried so far?
Injecting the HTML directly into the returned value:
var htmledField = [
'<span class="propertyNameHighlight">' + fieldSetTransformation,
'<span>'].join("\n");
];
No use, since this does not seem to be accepted anymore by current navigators (correct me if wrong) since it's a security issue.
The thrown result is just <span class="propertyNameHighlight">000000</span>
appearing in the table.
Creating the element, then appending it in the view
Also a no-go:
function stylePropertyName(data){
var newSpan = document.createElement('span');
newSpan.setAttribute('class', 'propertyNameHighlight');
document.getElementsByClassName("tableBody").appendChild(newSpan);
newSpan.innerHTML = data;
return data;
}
This returns a null function exception.
I have also checked this question, which seemed the closest to my query, but in my case there is no clear container neither to wrap up the resulting string.
TL;DR: What I'm trying to achieve?
This green text over here:
That represents a cell. The data is not directly passed, it's generated dynamically through a series of functions and ng-repeats.
Any help or related disregarded question that I could get is greatly appreciated. Thanks!
As suggested by commenter Stanislav Kvitash (thanks!), I solved this by using ngBindHtml :
<tbody class="tableBody">
<tr ng-repeat="line in linesArray">
<td ng-repeat="column in ::columns" width="{{::column.width}}" ng-class="{'center-text' : (!line[column.key] || line[column.key].length === 0)}">
<span ng-bind-html="line[column.key] !== undefined ? line[column.key] : '--'"></span>
<span ng-if="column.key == 'user'">
<i id="plus-icon-styling"
class="fa fa-plus-circle action-icon">
</i>
</span>
</td>
</tr>
</tbody>

Knockout bindings from JSON file issues

I am using Typescript to define my Knockout ViewModel.
I have a JSON file, the structure of which can be seen here (Github gist as It's a bit large to paste here).
The structure is basically:
OrderLine (the root) -> milestones -> factory_date
Or orally: (many) order lines have (many) milestones, which each have (one) factory date.
I am trying to build a ViewMOdel with the following:
var FactoryAppViewModel = (function () {
function FactoryAppViewModel(seasonID) {
var self = this;
self.seasonID = seasonID;
self.orderlines = ko.observableArray([]);
this.buildViewModel();
}
FactoryAppViewModel.prototype.buildViewModel = function () {
var self = this;
var getOrderLines = HTTP.get("/season/" + self.seasonID + "/orderlines").done(function (data) {
self.orderlines(JSON.parse(data));
}).fail(function () {
error("Could not get orderlines");
});
};
As far as I know, the JSON.parse on the data here will apply the values to the orderlines ko.observableArray([]), However I need to apply a ko.observable to the order lines children (milestones), and to a milestones child (factory_date) as well. And I don't know how to do this. Least of all from JSON.
I have read this but it didn't seem to help me.
I know that the observable isn't applied, because when i change a factory_date in the view, it doesn't update the viewmodel.
Any help would be appreciated. The javascript above is the compiled TypeScript.
EDIT:
Here is an example of the way I'm accessing the code in the view:
<tbody data-bind="foreach: orderlines">
<tr>
<td data-bind="text: factory.name"></td>
<!-- ko foreach: milestones -->
<!-- ko if: factory_date == null -->
<td>
<span>TBA</span>
</td>
<!-- /ko -->
<!-- ko if: factory_date !== null -->
<td>
<div class="wrapper-wrapper">
<div class="btn btn-primary dateChanger">
<span data-bind="text: moment(factory_date.milestone_date).format('DD-MM-YYYY')"></span>
</div>
<div class="date-update-wrapper text-center">
<input type="text" data-bind="attr: {value: moment(factory_date.milestone_date).format('DD-MM-YYYY')}" class="form-control datetimepicker">
<a class="save-date btn btn-success" data-bind="click: function(){$root.saveDate(factory_date, $parent)}"><i class="fa fa-check"></i></a>
<a class="cancel-date btn btn-danger"><i class="fa fa-times"></i></a>
</div>
</div>
</td>
<!-- /ko -->
<!-- /ko -->
</tr>
</tbody>
The part that made me aware I had an issue was this part:
data-bind="click: function(){$root.saveDate(factory_date, $parent)}"
I made a simple saveDate method, which was console.log(factory_date.milestone_date), and it returned the default JSON data, despite me editing it in the view (using the datepicker).
Knockout does not map children in lists to observables by default. There is a plugin called ko.mapping that can help you achieve what you are looking for.
You can set up a "children" mapping, or loop through your children, manually making them observable (In this case from JavaScript):
var mappedChildren = [];
var children = someObj.children();
for (var i = 0; i < children.length; i++) {
var mappedChild = ko.mapping.fromJS(children[i], mappingFunction);
mappedChildren.push(mappedChild);
}
someObj.children(mappedChildren);

Knockout observable used in text binding not updating ui

This has been driving me nuts for hours now. I think I have tried everything on the javascript side. In the javascript side I have this value: resultItem.standingNo which is a knockout observable. This one works fine, it updates like it should. I've used subscribers and everything I can think of to see that the value is updated and also tried the same to update other observables to use instead of this one.
What happens. The initial value is rendered in the html when I create the observable. But the html bit never receives the updates. I have tried with and without .extend({ notify: 'always' }), I have tried observable, computed and pureComputed, none updates the ui. But all gets the new value. I have other observables in the same class which update fine. But they are not located in the same place in the html.
resultItem.shortName just below is not an observable and doesn't need to be updated.
So my question is, is there some limitation with the html that prevents some observables from updating? Because I can't see any problem with my code between the two "...". Where those are there is code that get observable updates with no problems. Any ideas?
<!-- ko foreach: { data: resultLists, as: 'resultList' } -->
<!-- ko if: resultList.visible -->
...
<!-- ko foreach: { data: resultList.resultItems, as: 'resultItem', afterRender: $parent.renderedHandler } -->
<!-- Competitor Info Row Start-->
<tr class="rowDetail" data-bind="toggleCompetitorDetails: resultItem.competitorSelected">
<td class="selected-competitor-content" colspan="100%">
<div class="selected-competitor-container" >
<div class="selected-competitor-image">
<div class="img" data-bind="attr: { style: 'background-image: url(' + resultList.getRandomImage() + ');' }"></div>
<div class="competitor-dot competitor-star" data-bind="attr: {class: resultItem.isTopCompetitor()}, style: { backgroundColor: resultItem.color.toString(), color: resultItem.color.toString() }"></div>
</div>
DOES NOTE UPDATE --> <div class="selected-competitor-position" data-bind="text: resultItem.standingNo"></div> <-- DOES NOTE UPDATE
<div class="selected-competitor-info">
<p class="selected-competitor-name" data-bind="text: resultItem.shortName"></p>
<p class="selected-competitor-captain" data-bind="text: resultItem.name"></p>
</div>
</div>
</td>
</tr>
<!-- Competitor Info Row End -->
...
<!-- /ko -->
<!-- /ko --><!-- ResultList visible end -->
<!-- /ko --><!-- ResultLists foreach End -->

How to restrict the table row using js or jquery with out using style tag and class attribute for that row

I want to restrict the displaying the records of table.
I can able to restrict the table rows with style property of row.But when I am using that style it is giving the UI problems like on mouse over is missing for entire row.
Now mouse over should be there in this table on every table record.
I am using below code.
<tr style="display: block;"></tr>
Is there any way to hide the rows other than the above code.
I want to restrict the table row with out using the style property.
<c:forEach var="article" items="${vp_kb_articleList}" varStatus="loopStatus">
<tr id="<%=rowId++%>" class="myrow">
<td class="vp_kb_article" > see this code <a class="detaillist" href="${vp_kb_articlePageUrl}?articleId=${article.id}"> ${article.title}<br>
<span class="vp_kb_details">${article.description}</span>
<span class="vp_kb_article_id">${article.id}</span> </a>
</td>
</tr>
</c:forEach>
<c:forEach var="article" items="${vp_kb_articleList}" varStatus="loopStatus">
<tr id="<%=rowId++%>" class="myrow">
<td class="vp_kb_article" > see this code <a class="detaillist" href="${vp_kb_articlePageUrl}?articleId=${article.id}"> ${article.title}<br>
<span class="vp_kb_details">${article.description}</span>
<span class="vp_kb_article_id">${article.id}</span> </a>
</td>
</tr>
</c:forEach>
JS
$(document).ready(function() {
$(".myrow").hide();
});

knockout.js doesn't fire click event when click description in loop

I have next model code
<!-- ko foreach: {data: userAdminView.viewRoles, as: 'rrole'} -->
<tr>
<td class="userRolesRoleTitle"><b data-bind="text: rrole.role.name"></b><br/><i data-bind="text: rrole.role.description"></i></td>
<td class="userRolesRoleGroups">
<!-- ko foreach: {data: rrole.role.groups, as: 'group'} -->
<div class="usersGroupElement" data-bind="html: group.viewName"></div>
<!-- /ko -->
<a class="btn emb green" data-bind="click: userAdminView.addNewGroup,visible:(rrole.role.isNewGroupAccessible) , attr: { value: rrole }"><i class="icon16 plus"></i>add</a>
</td>
</tr>
<!-- /ko -->
and model event
function userAdminView(user) {
//some code
self.addNewGroup = function(data, event){};
//some code
}
all working fine but except userAdminView.addNewGroup event, it never fired when described in loop.
Why does it happens ?
Thanks
change this part data-bind="click: userAdminView.addNewGroup"
into this data-bind="click: $parent.addNewGroup"
check this out custom-bindings-controlling-descendant-bindings
Bindings such as with and foreach create extra levels in the binding
context hierarchy. This means that their descendants can access data
at outer levels by using $parent, $parents, $root, or $parentContext.

Categories

Resources