AngularJS with jQuery DataTables intermittently working - javascript

I'm adding AngularJS to a project that is heavily dependent on jQuery's datatables plugin.
In my angular view, I have the datatables code (which only loads when the view is loaded).
Ex:
$('#datatable').DataTable();
In my layout, which is rendered by the server (everything else is rendered by the client), I have <script> tags for each of the JavaScript files.
However, when I load the page, all of the data comes in without the pagination and when I click a sorter (up or down), I see my Angular templating i.e. {{record.name}} and {{record.time}} in the first row and my data disappears.
The only table that is working with jQuery datatables is a table with a very small amount of records (6). Even then, it works intermittently.
This has lead me to believe that it's a problem with some data loading before/after (depending on the amount of data) the scripts do.
I know there are alternatives and directives out there, but I have yet to find one that works properly and does everything the native plugin does. So please, do not suggest any of those to me.
If it is a problem with the order of the data/scripts loading and one needs to load before another, I'm fine with adding a delay until everything has finished loading.
Does this seem like it's the problem? How can I test this?
Edit:
Another note to take: when everything has finished loading, and I enter in the console $('#datatable').DataTable(); it applies all of the datatables features.
The datatables code in my angular view is surrounded in a $(function() { //code });
This is how I get the data (for every table):
app.controller('PaymentsCtrl', function($scope, Payments) {
Payments.get()
.success(function(data) {
$scope.payments = data.payments;
});
});
I then run an ng-repeat on the table's <tr>

Directives are the correct way to wrap external libraries, so that's how you would want to invoke your DataTable. I haven't used jquery DataTables with Angular before, since it's a bit of an anti-pattern (though I understand it's unavoidable here), but if it's merely a case of invoking $().DataTables() at the correct time, then this directive should do it - keep in mind that jquery and datatables' tags should be included BEFORE angular.js so that Angular replaces jqlite with jquery internally - this way, the element param here is automatically wrapping the element with jquery for you, and should allow you to call the DataTable function.
angular.module('yourModule').directive('datatable', function() {
return {
restrict: 'AC',
link: function(scope, element, attrs) {
element.DataTable();
}
}
});
With that directive, rather than giving your table an ID #datatable, just add a "datatable" class or attribute and let the directive handle it from there!

Related

Correctly binding multiple knockout viewmodels with Turbolinks?

Currently, the website I'm working on uses knockout for our frontend framework. I want to incorporate Turbolinks with multiple knockout viewmodels across different pages. The problem I'm running into is that since turbolinks doesn't actually change the page, bindings get applied multiple times which results in an error.
So far here is what I have tried.
I found this article which describes exactly what I want to do but doesn't necessarily work.
His solution is to replace the ko.applyBindings with
window.applyTurboBindings = function(node, viewModelAccessor) {
return $(document).one("page:change", function() {
return ko.applyBindings(viewModelAccessor(), node);
});
};
and use it with this
applyTurboBindings(document.getElementById('my-node'), function() {
return new MyViewModel();
});
page:change is no longer a method used with turbolinks according to their documentation. So I tried it using turbolinks:load (which fires once after the initial page load, and again after every Turbolinks visit) and turbolinks:visit (fires immediately after a visit starts). That wasn't working either. The event never gets triggered when I call the method applyTurboBindings so no bindings get applied.
Here is a fiddle with a basic setup of what I'm doing. fiddle
When I run this through my debugger, the applyTurboBindings never catches the event (turbolinks:load) that is firing.
One idea I had was to use something like this:
$(document).one("turbolinks:load", function () {
// execute all js for page here
});
instead of the typical
$(function(){
// execute all js for page here
});
But I couldn't get that working either because bindings were still getting applied multiple times.
So my question is how on earth do I get this working for multiple viewmodels so the bindings don't break? Should I be organizing my js differently? Is there a simple solution I'm not seeing or is this just not a good idea use both together?
This is my first question on Stack Overflow by the way so if I'm missing anything or I didn't make my question clear enough please let me know. Thanks for your help.

re-binding jquery selectors in angular

I have a very simple angular application that has one service which uses a http request to pull down a web page. I then want to display that page within my application using ng-bind-html.
template:
<div class="container" ng-bind-html="data"></div>
controller:
this.service.getHoliday().then(function(data) {
$scope.data = $sce.trustAsHtml(data);
});
this works from a visual perspective, however it would appear the javascript/jquery files are loaded but not used. I feel like this is because the elements are not available when it is run.
I feel like the best option to resolve this is to somehow rebind all the elements, is it possible to rebind the entire page without caring about each individual selector.
I've tried using a $apply() method, wrapping the above like so:
this.service.getHoliday().then(function(data) {
$scope.$apply(function() {
$scope.data = $sce.trustAsHtml(data);
});
});
but this stops the page from loading the html completely, i understand that this is vague but hopefully someone has come up against a similar issue that can help.

How does document.ready work with angular element directives?

In my current project I'm using angular directives to create custom html elements. See example below.
banner.js
app.directive('banner', function () {
return {
restrict: 'E',
replace: true,
templateUrl: './common/banner/banner.html'
};
});
Banner.html
<div>
<div class="banner-image"></div>
</div>
Issue: There is a javascript file that adds additional properties to elements with the banner-image class after document.ready. This worked perfectly before using angular element directives, but the properties are no longer being added.
Question: Does the document.ready callback occur prior to angular element directives being rendered in the dom? If so, that could explain why my javascript file is no longer making the necessary changes to the html elements.
Thank you
This is less a "directives" question, and more an Angular sequence of events question.
Angular itself (if not manually started with .bootstrap) will defer its loading until .onready.
At that point in time, Angular expects that all JS it needs to run will be there and registered.
Then Angular starts up.
Then after Angular starts up, Angular parses the DOM to find a root element to attach to (the one with the ng-app directive).
Then it recursively goes down the line, injecting controllers and building out directives and interpolating nodes as it goes.
Now we are way past any code that would have fired on DOMReady.

angularjs - After changing views using routing the jquery elements doesn`t get rendered

My issue is that after changing views using routing, the jquery components in my page doesn´t get rendered. I have customized ui components like dropdowns that are not being processed. I saw this answer: Angular.js App with routing.. Cannot get jquery ui layout plugin working on 2nd view
But I can´t figure out how to make it work in my case. It seems that when angular renders the template, any but angularjs code is processed.
Thanks so much in advance.
Code: http://plnkr.co/1mwMcsqMxWGTheoQmJ22
In the example you've provided, you're not actually loading the file with the jQuery code in it (jqcode.js).
The real problem however, as you can see in this version of your example, is that the document ready event is executed before your template has been loaded and rendered. This means that the element your jQuery code is attempting to target does't exist when you try to manipulate it.
You should really look into Angular directives which is where you are advised to place any DOM manipulation logic within an Angular project:
If you have to perform your own manual DOM manipulation, encapsulate
the presentation logic in directives.
From the Angular documentation for controllers.

Angular JS/Dojo widget parsing

I have an Angular JS application, in which I would like (actually I MUST) use dojo widgets. In the HTML template for the controller, I can return HTML that has dojo markup in it. However, the dojo parsing doesn't happen automatically when a view gets re-rendered. The only way I have been able to trigger reparsing is by manually calling the parser after a slight delay by doing something like this in the controller and then calling it when I know the model has changed.
refreshDojo = function() {
setTimeout(function() {
require(["dojo/parser"], function(parser) {
parser.parse()
});
}, 10);
}
This isn't really feasible for two reasons:
Having to do anything after a timeout is bound to cause trouble by
either happening to quickly before the html has been processed or
two slowly, causing a flash of content before the widgets are
created.
Secondly, if I am not mistaken, parser.parse() parses the whole DOM
which is not very efficient if I only updated a single div.
Is there a way to know for certain when the DOM has stabilized so that I can be sure to trigger this at the right time? And is there a way to access the root element of the controller (It appears that you can no longer inject $element)?
You should decorate the $compile service, so before angular compiles anything it will let dojo compile it.
Here's a small example:
http://plnkr.co/edit/9iJJFLWDqGtyqLV5Mbe3?p=preview
Documentation on decorators: http://docs.angularjs.org/api/AUTO.$provide#decorator
You could create an attribute directive dojoType that requires the named widget module (and mixins if needed) and instantiates them instead of the dojo parser.
For example, if you've loaded a part of the DOM ('ajax'), then you would first create a new scope for it, compile and link it against the scope (the dojoType directive gets applied), then you would call the dojo parser and specify the rootNode.
dojoParser.parse({
rootNode: dom.byId(domid)
}).then(/* access the widgets or whatever */);

Categories

Resources