Knockout css data-bind doesn't apply class - css

So I'm having a very, very strange issue, so here's a code snippet where the problem is happening:
<td class="editable ageCell" data-source="Age" data-bind="css: {'modified': true}"></td>
Basically what is going wrong is that it does not add the modified class. At all. Strangely, when I added a text binding, like modified:true, text: 'testatest', it does apply it.
What is going on? I'm not even close to a web developer expert, but I even asked my two senior project leads and they can't figure it out.

Inspect your web page in browser console and check the class modified present or not.
If it is there, then probably the problem is with your text content inside the td element. it may be null or empty. Also make sure that there is no script error in browser console.
Try and understand this:
HTML
<div class="editable ageCell" data-source="Age" data-bind="css: {'modified': false}, text:text"></div>
<div class="editable ageCell" data-source="Age" data-bind="css: {'modified': true}, text:text"></div>
JavaScript
var model = function()
{
var self=this;
self.modified = ko.observable(false);
self.text = ko.observable('Sample Text');
};
ko.applyBindings(new model());
Here is the Demo

So it turns out that in some framework that was being used, there was a function that served no purpose but to remove classes from cells when it was called, and it was being called right after my modified adder, so that wraps up this problem.

There's another reason why a class might not be applied I ran into: It gets overridden by a subsequent css-declaration:
<td class="editable ageCell" data-source="Age" data-bind="css: {'modified': true}, css: unrelatedcssclass"></td>
In this case 'modified' will never appear, but 'unrelatedcssclass' will.
To fix that, merge both declarations to
<td class="editable ageCell" data-source="Age" data-bind="css: {'modified': true, 'unrelatedcssclass' : true}"></td>

Related

AngularJS events with ASP.NET UpdatePanel

I'm trying to use AngularJS within an ASP UpdatePanel, very much like in this question: AngularJS with ASP.NET Updatepanel partial update
That solution also helped a lot, at least regarding the static parts, ie everything showed upon initialization.
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function (sender, args) {
var elem = angular.element(document.getElementById("myDiv"));
var newElem = $compile(elem)($scope);
$scope.$apply();
elem.replaceWith(newElem.html());
});
Properties setup within add_endRequest() are shown properly. My problem is that all interaction is dead.
For example if I put
<div id="myDiv">
{{testtext}}
<div ng-init="testtext='hello world'"/>
</div>
it'll print out the string as expected. But when adding ie a click event nothing happens.
<div ng-click="testtext='hello world'">Click here</div>
Any ideas why? As I understand it the angular $compile and $scope.$apply() should apply to all angularjs functionality, but it seems not.
Figured it out with a little help from my friends. The problem was that only the element with my AngularJS list <div id="myDiv"> was recompiled in the add_endRequest() method. When changing this to the div holding the angularjs controller, surrounding the entire UpdatePanel section...
<div ng-controller="UpdatePanelController" id="UpdatePanelController">
<asp:UpdatePanel ID="UpdatePanel" UpdateMode="Conditional" runat="server">
...
</asp:UpdatePanel>
</div>
and then compiling its children...
OneViewApp.controller("UpdatePanelController", function ($scope, $compile) {
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function () {
var elem = angular.element(document.getElementById("UpdatePanelController"));
$compile(elem.children())($scope);
$scope.$apply();
});
});
everything worked fine. Since UpdatePanel replaces the html, the entire controller needs to be recompiled in order for it to work properly.
This can of course also be achieved by (re)bootstraping the angular app for every step, as suggested in another answer, but the above solution is closer to the original code and thus simpler in this case.
I had the same issue.
I have an Angular page that have an iframe, that contains an old asp.net application (with user controls and an update panel).
I've tried to add an Angular directive to the .NET part, and had the same issue. The directive had an empty template (no HTML content under the directive's div).
And after applying the solution you've mentioned:
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function (sender, args) {
var elem = angular.element(document.getElementById("myDiv"));
var newElem = $compile(elem)($scope);
$scope.$apply();
elem.replaceWith(newElem.html());
});
I saw the html content but without Angular bindings, like ng-repeat or ng-click.
The solution was found here:
http://blog.travisgosselin.com/integrating-angularjs-in-a-tight-spot/
You need to manually initialize your module in the add_endRequest event:
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function (sender, args) {
angular.bootstrap($('#myDiv'), ['myNgApp']);
});
This solution was enough, and I removed the solution that you've mentioned with the $compile.
You can read about angular.bootstrap in the documentation:
https://docs.angularjs.org/guide/bootstrap

Add a class to parent element when clicked with Knockout.js

I have a div with a close button on it. The close button has a function fired via Knockout.js that I would like to add a class to the parent of this button, i.e. the encapsulating div. However, in my JS file (see below) the function firing is linked to an object in an array.
HTML
<div>
<button data-bind="click: $parent.myFunc">
</div>
JS file
this.myFunc = function(e) {
// this.addClass('boo'); does not work
}
I can fire a console.log off in this function, but can't seem to manipulate this element through standard jQuery.
Knockout way of doing it would be to add a css binding to the parent and then manipulate it within your function fired by click event:
<div data-bind="css: someClass">
<button data-bind="click: myFunc">
</div>
And within your JS file:
this.someClass = ko.observable("");
this.myFunc = function(e) {
this.someClass("boo");
}
since you tagged jQuery, I assume you can use it, so:
$('button').click(function(){
$(this).parent().addClass('boo');
});
This is my first answer on here but how about looking into jQuery's .parent() api? http://api.jquery.com/parent/
I'm not familiar with Knockout.js but perhaps something like this could work..
$('button').data('bind','click: $parent.myFunc').click(function(){
$(this).parent().addClass('boo');
});

What is the 'angular' way of displaying a tooltip / lightbox?

I've been looking around and have not been quite able to get a clear path to the 'angular' way of accomplishing the following. What I'm trying to achieve is displaying a tooltip with information when hovering over a link within an ng-repeat loop. Based on my research, I understood that this is part of the view, and so I should probably handle this in a directive. So, I created an attribute directive called providertooltip. The html declaration is below:
<table>
<tr id="r1" ng-repeat="doc in providers">
<td>
<a providertooltip href="#{{doc.Id}}" ng-mouseover="mouseOverDoc(doc)" ng-mouseleave="mouseLeave()">{{doc.FirstName}} {{doc.LastName}}</a>
</td>
</tr>
</table
<div id="docViewer" style="display:hidden">
<span>{{currentDoc.FirstName}} {{currentDoc.LastName}}</span>
</div>
In the module, I declare my directive, and declare my mouseOver and mouseLeave functions in the directive scope. I also 'emit' an event since this anchor is a child scope of the controller scope for the page. On the controller function (docTable ) which is passed as a controller to a router, I listen for the event. Partial implementation is seen below:
app.directive("providertooltip", function() {
return {
restrict : 'A',
link: function link(scope, element, attrs) {
//hover handler
scope.mouseOverDoc = function(doc){
scope.currentDoc = doc;
scope.$emit('onCurrentDocChange');
element.attr('title',angular.element('#docViewer').html());
element.tooltipster('show');
//docViewer
};
scope.mouseLeave = function() {
element.tooltipster('hide');
}
}
}});
function docTable(docFactory, $scope, $filter, $routeParams) {
$scope.$on('onCurrentDocChange',function(event){
$scope.currentDoc = event.targetScope.currentDoc;
event.stopPropagation();
});
}
Ok, so here is my question. All of the works as expected; Actually, the tooltip doesn't really work so if someone knows a good tooltip library that easily displays div data, please let me know. But, what I'm really confused about is the binding. I have been able to get the tooltip above to work by setting the title ( default tooltip behavior ), but I can see that the binding has not yet occured the first time I hover of a link. I assume that the onCurrentDocChange is not synchronous, so the binding occurs after the tooltip is displayed. If I hover over another link, I see the previous info because as I mentioned the binding occurs in an asynchronous fashion, i.e., calling scope.$emit('onCurrentDocChange') doesn't mean the the parent scope binds by the time the next line is called which shows the tooltip. I have to imagine that this pattern has to occur often out there. One scope does something which should trigger binding on some other part of the page, not necessarily in the same scope. Can someone validate first that the way I'm sending the data from one scope to the other is a valid? Moreover, how do we wait until something is 'bound' before affecting the view. This would be easier if I let the controller mingle with the view, but that is not correct. So, I need the controller to bind data to the scope, then I need the view to 'display a tooltip' for an element with the data. Comments?
To go the angular way correctly start your directive like:
...
directive('showonhover',function() {
return {
link : function(scope, element, attrs) {
element.parent().bind('mouseenter', function() {
element.show();
});
element.parent().bind('mouseleave', function() {
element.hide();
});
}
...
Or start with http://angular-ui.github.io/ link to go the angular-way UI. Look into the bootstrap-ui module - pure angular bootstrap widgets implemented as directives. You can get a clue how the tooltip binding implemented directly from the source of the module - https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js
Also here is another example - (having jQuery and bootstrap scripts included) - use the ui-utils module Jquery passthrough directive ui-jq'. It allows to bind Jquery plugins ( style of $.fn ) directly as angular directive.
Here is their example for binding twitter bootstrap tooltip.
<a title="Easiest. Binding. Ever!" ui-jq="tooltip">
Hover over me for static Tooltip</a>
<a data-original-title="{{tooltip}}" ui-jq="tooltip">Fill the input for a dynamic Tooltip:</a>
<input type="text" ng-model="tooltip" placeholder="Tooltip Content">
<script>
myModule.value('uiJqConfig', {
// The Tooltip namespace
tooltip: {
// Tooltip options. This object will be used as the defaults
placement: 'right'
}
});
</script>
Also look into the official angular documentation for writing directives examples,
and have a happy coding time with Angular!

Binding to nested properties where observables in the chain can be null

I just read about KnockoutJS and when I try to bind to sub-properties on objects that can be null I get binding errors, e.g.:
<div data-bind="text: selectedAccount().DocumentList().length"></div>
So once you call ko.applyBindings it tries to evaluate the above expression and if selectedAccount is null (which it is by default) it throws an error. I know that I can create a dependentObservable like so:
viewModel.docLength = ko.dependentObservable(function () {
return selectedAccount() ? selectedAccount().DocumentList().length : null;
})
But I was wondering if there's a solution other than putting properties in the ViewModel simply because I get a binding error.
A couple thoughts:
If you don't want to bother with the dependentObservable, then you can place your statement directly in the text binding like:
<div data-bind="text: selectedAccount() ? selectedAccount().DocumentList().length : null"></div>
or even shorter:
<div data-bind="text: selectedAccount() && selectedAccount().DocumentList().length"></div>
Depending on your scenario, you can also use the template binding to your advantage when dealing with potentially null values. It would be like this:
<div data-bind="template: { name: 'accountTmpl', data: selectedAccount }"></div>
<script id="accountTmpl" type="text/html">
${DocumentList().length}
</script>
Additionally, in the 1.3 release of Knockout there will be some control flow bindings that could be helpful to you. Particularly the "if" or "with" bindings would work for this situation. They are described here: https://groups.google.com/d/topic/knockoutjs/pa0cPkckvE8/discussion

jQuery.click(): Can I get a reference to the synthetic event object passed to event handlers?

I have an <a> inside a <TD>, and I'm trying to have clicks on the <TD>, but outside the <A>, act like they were clicks on the <a>. I'm almost there:
HTML:
<TD class="somethingPretty">
Text
</td>
JS:
$('.anchor').click(function(ev){return confirm("go ahead?");});
$('somethingPretty').click(function(ev){
if($('.anchor').click()){
document.location = $('.anchor').attr('href');
}
}
The problem with this is that jQuery.click returns undefined, and I can't see how to get at the event object that's passed to the click handlers so I can interrogate it with isPropagationStopped and isDefaultPrevented. What's the right way to solve this problem?
Sometimes asking the question clearly is the best way to find an answer. Some strategic poking around the jQuery source led me to the following solution(using the markup above):
$('.somethingPretty').click(function(ev){
var syntheticClick = new $.Event("click");
syntheticClick.stopPropagation();
$('.anchor').trigger(syntheticClick);
if(syntheticClick.isDefaultPrevented()) return;
document.location = $('.anchor').attr('href');
}
This works for all event handlers except live-bound ones (those don't execute; my users will have to learn to click the anchor itself for them!). The tricky part here is that trigger takes a jQuery.Event in addition to the documented string.
How about this?
var a = $('.somethingPretty .anchor');
var td = $('.somethingPretty');
a.click( function(ev) { return confirm("go ahead?"); } );
td.click( function() { a.click(); } );
Did you try something like:
$("td.outer").add("td.outer a").click(function() {
// do stuff
});
You're going to want to find some way to ensure that whatever is in the function runs only once, since a click on the <a> will count both as a click on the <td> and the <a>.

Resources