re-binding jquery selectors in angular - javascript

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.

Related

Stimulus.js way to load a partial?

We are investigating Stimulus.js for use with our Rails6 app and keep hitting conceptual walls in moving our thinking from jQuery to Stimulus.
For example:
On one part of the page we have a button and when that button is clicked we want to load content into a div on another part of the page. In jQuery this is simple - respond to click event, load in the part from the rails backend into that div.
In Stimulus, how to do this? It looks like everything needs to be in one big controller so that the button can see the 'target div'. So essentially we are writing 'page' controllers, which seems a lot of overhead. Also, it messes up the way we are breaking down the page into partials because now those partials need to share a Stimulus controller.
You definitely don't need to be writing 'page' controllers. The idea is that you write smaller controllers for interactive elements. If you post more about your use case perhaps we can help you split apart your controllers. You are correct though that the controller's element must contain any element registered as a target. Just remember you can access elements outside that context (e.g. in a completely different part of the page) using regular Javascript (in other words document.getElementById can give you access just as easily as using Stimulus targets)
To answer your question though, you can do something like this:
show() {
fetch("/path/to/partial")
.then((res) => res.text())
.then((html) => {
const fragment = document
.createRange()
.createContextualFragment(html);
this.element.appendChild(fragment);
// OR document.getElementById("testy_element").appendChild(fragment)
});
}
What this does is fetch an HTML response from the Rails server and insert it inside of the main controller's element (this.element). For example:
<div class="container" data-controller="test">
<button data-action="test#show">Do the Thing</button>
<!-- partial would appear here -->
</div>
What you are looking for is Stimulus’s sibling library Turbo, made to handle HTML navigation through turbo-frames and partially updating the page through turbo-streams.

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.

AngularJS with jQuery DataTables intermittently working

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!

AngularJS :: html5Mode + normal link behavior

I am quite new to angular and currently facing a problem on a website, where we need html5Mode(true) while at the same time keep normal links working like they would do with html5Mode(false).
We have our ng-app in the html-tag - so the whole page is under controll of a module. On some pages we need to load new content via ajax and a change of the underlying model in the specific controller. This should change the URL to a new (absolute) one. Achieving this is kind of simple by $locationProvider.html5Mode(true); in the module-config.
Now the Problem: as we activate html5Mode, normal links would not work as expected any more. Is there any way to achieve the desired behavior without fragmenting the page into multiple modules that kind of leave the "normal links" out of the scope of angular and the html5Mode-setting of the module?
I had the idea to simply use history.pushState() in the controller-function, that handles the click-event for the content-reload, but this does throw a lot of exceptions on the one hand and does not even work properly on the other hand.
Any idea is very much appreciated :-)
Best regards, SubnetOne
Finally I found the solution:
Module.directive('a', function () {
return {
restrict: 'E',
link: function(scope, element, attrs) {
element.attr("target", "_self");
}
};
});
This ads a target="_self" to all links (anchor-tags) on the page. Due to angulars hijacking of links depends on
!elm.attr('target') = true
links WITH a target will behave like intended.
Thanks to Niks answer to
angular.js link behaviour - disable deep linking for specific URLs
and Sebastians answer to
Conditionally add target="_blank" to links with Angular JS I finaly solved the issue :-)

Applying Knockout binding to a specific part of the page: What am I doing wrong?

The Code
You can find the JSFiddle in question at: http://jsfiddle.net/SeanKilleen/A3QtJ/
Background / What I'm Trying to Accomplish
I'd like a Feedback button to place on our web site, likely in the Site.Master file (it's an asp.net web site)
When the feedback link is clicked, I'd like to show a modal dialog
I'd like to bind the link and the elements inside of the modal to a specific knockout viewmodel
I'd like to properly namespace it so that it doesn't interfere with any other scripts that might come up on other pages
I'd like to apply the Knockout bindings only to this portion of the code, because other subsequent pages, etc. might also have bindings.
To do this, I have the following main toolset: Knockout, jQuery, and jQueryUI (jQueryUI isn't my particular choice but that ship has sailed).
The Problem
In the JSFiddle link, the following code currently works:
$(document).ready(function () {
vm = new FeedbackNamespace.ViewModel();
ko.applyBindings(vm);
});
However, when I change ko.applyBindings(vm) to:
ko.applyBindings(vm, document.getElementById('FeedbackArea'));
The link part of the binding (that is mound to a viewmodel function to show the dialog) still works. However, none of the bindings inside the modal dialog still work.
Question(s)
How can I properly apply the viewModel to only a section of the site in this case?
Is this method of doing things still going to cause problems with child pages that might load their own knockout viewmodels and apply them?
Are there other examples of this sort of thing being done? I've been looking but unable to find them.
Thanks in advance for any help you can give!
The problem is here:
self.Start();
this sets up the modal, removing it from the FeedbackArea div. This happens in the process of creating the viewmodel, such that when this newly created vm is actually applied to the div a moment later, that modal is now gone, which is why nothing inside of it responds like it does when you apply the VM to the entire page.
I would make sure that Start method is called AFTER you apply bindings.
LIKE THIS

Categories

Resources