Loading Partial Page With Angular and Compile The Controller - javascript

In large scale application, our web application might be organize into separate partial page in order to increase the modularity of our application. In some case compiling a partial page loaded through XHR or Ajax request either using Angular $http.get or JQuery $.load will introduce an error.
Using my scenario as example, exactly I'm using Kohana PHP framework so i can control the modularity of my web application on the server level. As usual all template and page been separate into view, leaving all HTML, JS and CSS together on presentation layer.
This will giving an great flexibility for me to implement the Javascript MVW/MVC stack on client side processing as my web app are heavily depend on AJAX request to fetch data from back end application. In my scenario im using AngularJS and below is a simple pseudo on how the data from Model presented to client.
Kohana Model > Kohana Controller > Kohana View > XHR > JQuery\Angular > DOM
One of my part in my application that really give me bump and get me drink few bottles of metabolism drink to solve the application. Is where i have a Modal dialog and the partial page are load through XHR from server and attached it to selected DOM.
The problem is when Angular try to compile the partial page, when it found the ng-controller directive it will be looking for the function referring to the processed directive. Error were produce where the controller is not found as it not yet evaluated by DOM parser. But when you pre-delare the function somewhere in your application just before your load the partial page, everything is OK. Below is the example on how i setup a Dialog service that will be called from link directive when i clicked the said link.
var dialogService = angular.module('dialog.service', []);
dialogService.factory('Dialog', function($http,$compile){
var dialogService = {};
dialogService.load = function(url, scope){
$("#dialog:ui-dialog").dialog( "destroy" );
$("#dialog").attr('title','Atlantis');
$http.get(url).success(function (data) {
html = $compile(data)(scope);
$('#dialog-content').html(html);
$("#dialog").dialog({
width: '600px',
buttons: {
"Ok": function() {
$( this ).dialog( "close" );
return true;
},
},
close: function(){
if (typeof (onClose) == 'function') { onClose(); }
},
});
});
}
return dialogService;
});
After some research i have found some solution and sharing it with fellas on my answer for others beginner like me. (sorry for my English).

There's nothing wrong on AngularJS on this setup, others JS guru out there might already know the solution and very busy to share with us while inventing another cool web development tools or framework. That's OK keep doing that. This might not be a cool or ultimatum solution, please share any improvement or tips with us!
To overcome this problem we need s strategy to setup, let me start with an example code so our brain will digest while the information flowing through. Below code is the placeholder where i create the the modal dialog using JQuery and the Ajax content will be insert.
<div ng-app="asng" id="dialog" title="" style="display:none">
<div id="dialog-content"></div>
</div>
As a basic knowledge, we have to understand on how DOM parser are working. We might think that DOMP (DOM Parser) is a multi-threaded and that's the reason we can load multiple external resource in parallel. Actually DOMP are single threaded while parsing the DOM elements index from top to bottom. Below is the example on the partial page that I'm gonna load into #dialog-content DIV element.
<script language="JavaScript" type="text/javascript">
function Transaction ($scope,$http){
$scope.items = [{"country":"VN","quantity":"100"}];
$scope.country_name = $scope.items;
}
</script>
<style>
</style>
<div id="transaction-panel" class="user" data-ng-controller="Transaction">
<form id="{{ form_name }}" action="">
Country : [[ items.country ]] </br>
Total : [[ items.quantity ]]
</form>
</div>
Actually these partial still giving an error, although we have put the script block just before the element with ng-controller directive. Actually that's not really the case, the part that we need to tackle is how AngularJS compile service are compiling the partial DOM. Let go back on my question part above and inspect where the line that we do the compile thing.
html = $compile(data)(scope);
$('#dialog-content').html(html);
First line above will compile DOM in data variable, and insert into the root DOM unfortunately first line will shout an error : Controller Transaction not found.
This happen because, the Script Block in your partial page is not yet evaluate by DOMP parser because is not inserted into the root DOMP. Now you see the light OK, so we have to change the compiling strategy a bit, by inserting the new DOM and then we will parse back the inserted DOM look example below:-
html = $('#dialog-content').html(data);
$compile(html)(scope);
Slim and simple solution, it took me few head banging morning to solve this problem just because ignoring the simple concept on DOM parsing.

If I understand what you are trying to do, here's a simple example.
I wanted to post via AJAX to a Django form and then replace the form content in the page with the returned markup. The returned markup includes an ng-controller, which I need to execute when it loads:
.controller('MyForm', function($element, $compile, $scope){
var scope = $scope;
var $theForm = $element;
var $formBlock = $element.find('.the_form'); // is replaced by the form response
$element.find('.submit_the_form').click(function(){
// submit the form and replace contents of $formBlock
$.post($theForm.attr('action'), $theForm.serialize(), function(response){
var newstuff = $formBlock.html(response);
$compile(newstuff)(scope); // loads the angular stuff in the new markup
});
});
})
I think the line you're interested in is $compile(newstuff)(scope);
EDIT:
Crikey, tried this with some other markup this morning and did not work, for no reason I could figure out. Turned out that if I did not have a field with ng-model assigned, in the new markup, then the $compile does not execute. Added:
<input type="hidden" name="dummy" value="0" ng-model="dummy"/>
...and now it compiles.

Related

Lazy-load a javascript script?

Is there a way I can wrap an external JS script embed with lazy-load behavior to only execute when the embed is in the viewport?
Context: I have an external javascript embed that when run, generates an iframe with a scheduling widget. Works pretty well, except that when the script executes, it steals focus and scrolls you down to the widget when it’s done executing. The vendor has been looking at a fix for a couple weeks, but it’s messing up my pages. I otherwise like the vendor.
Javascript embed call:
<a href=https://10to8.com/book/zgdmlguizqqyrsxvzo/ id="TTE-871dab0c-4011-4293-bee3-7aabab857cfd" target="_blank">See
Online Booking Page</a>
<script src=https://d3saea0ftg7bjt.cloudfront.net/embed/js/embed.min.js> </script> <script>
window.TTE.init({
targetDivId: "TTE-871dab0c-4011-4293-bee3-7aabab857cfd",
uuid: "871dab0c-4011-4293-bee3-7aabab857cfd",
service: 1158717
});
</script>
While I'm waiting for the vendor to fix their js, I wondered if lazy-loading the JS embed may practically eliminate the poor user experience. Warning: I'm a JS/webdev noob, so probably can't do anything complicated. A timer-based workaround is not ideal because users may still be looking at other parts of the page when the timer runs out. Here are the things I’ve tried and what happens:
I tried:
What happened:
Add async to one or both of the script declarations above
Either only shows the link or keeps stealing focus.
Adding type=”module” to one or both script declarations above
Only rendered the link.
Wrapping the above code in an iframe with the appropriate lazy-loading tags
When I tried, it rendered a blank space.
Also, I realize it's basically the same question as this, but it didn't get any workable answers.
I actually also speak french but I'll reply in english for everybody.
Your question was quite interesting because I also wanted to try out some lazy loading so I had a play on Codepen with your example (using your booking id).
I used the appear.js library because I didn't really want to spend time trying some other APIs (perhaps lighter so to take in consideration).
The main JS part I wrote is like this:
// The code to init the appear.js lib and add our logic for the booking links.
(function(){
// Perhaps these constants could be put in the generated HTML. I don't really know
// where they come from but they seem to be related to an account.
const VENDOR_LIB_SRC = "https://d3saea0ftg7bjt.cloudfront.net/embed/js/embed.min.js";
const UUID = "871dab0c-4011-4293-bee3-7aabab857cfd";
const SERVICE = 1158717;
let vendorLibLoaded = false; // Just to avoid loading several times the vendor's lib.
appear({
elements: function() {
return document.querySelectorAll('a.booking-link');
},
appear: function(bookingLink) {
console.log('booking link is visible', bookingLink);
/**
* A function which we'll be able to execute once the vendor's
* script has been loaded or later when we see other booking links
* in the page.
*/
function initBookingLink(bookingLink) {
window.TTE.init({
targetDivId: bookingLink.getAttribute('id'),
uuid: UUID,
service: SERVICE
});
}
if (!vendorLibLoaded) {
// Load the vendor's JS and once it's loaded then init the link.
let script = document.createElement('script');
script.onload = function() {
vendorLibLoaded = true;
initBookingLink(bookingLink);
};
script.src = VENDOR_LIB_SRC;
document.head.appendChild(script);
} else {
initBookingLink(bookingLink);
}
},
reappear: false
});
})();
I let you try my codepen here: https://codepen.io/patacra/pen/gOmaKev?editors=1111
Tell me when to delete it if it contains sensitive data!
Kind regards,
Patrick
This method will Lazy Load HTML Elements only when it is visible to User, If the Element is not scrolled into viewport it will not be loaded, it works like Lazy Loading an Image.
Add LazyHTML script to Head.
<script async src="https://cdn.jsdelivr.net/npm/lazyhtml#1.0.0/dist/lazyhtml.min.js" crossorigin="anonymous" debug></script>
Wrap Element in LazyHTML Wrapper.
<div class="lazyhtml" data-lazyhtml onvisible>
<script type="text/lazyhtml">
<!--
<a href=https://10to8.com/book/zgdmlguizqqyrsxvzo/ id="TTE-871dab0c-4011-4293-bee3-7aabab857cfd" target="_blank">See
Online Booking Page</a>
<script src=https://d3saea0ftg7bjt.cloudfront.net/embed/js/embed.min.js>
</script>
<script>
window.TTE.init({
targetDivId: "TTE-871dab0c-4011-4293-bee3-7aabab857cfd",
uuid: "871dab0c-4011-4293-bee3-7aabab857cfd",
service: 1158717
});
</script>
-->
</script>
</div>

Script that handles a tag that is within a angular template file does not work

I have this giant code, which is not necessary show here, but he will manipulate this div:
<div data-calendar="true"></div>
Finding like this:
var calendar = $('[data-calendar]');
The script works if the tag is within my main angular layout file, but when the tag is within a angular template file, the script does not work at all.
How fix that? I already put this script after the angular scripts, in order to make the angular scripts run before, but didn't work at all.
Including the script in the template is never going to work, you need to be able to control when it runs so that it will run any time that div is added, replaced, etc which could happen any time a digest happens.
The best way to do that in this case would be with a directive.
<div calendar></div>
.directive('calendar', function() {
return {
link: function (scope, element) {
element.jqCalendar(); // jQuery plugin should be avail here, if you included jQuery and the plugin before angular.
}
};
});

Design pattern for loading view in front-end JavaScript

I am relatively new to web-programming and am looking for a simple pattern to show a "loading"/waiting view for the web. Before I say anything else, I am sending only the minimum amount of data from the server, and start sending the JS/HTML resources to client while the client is using AJAX to request more data from the server (this might be suboptimal but bear with me). So basically, in theory this should mean the web view pops up earlier initially, but spends more time loading some of the data and corresponding subviews. Thus the need for a loading view.
So we have the standard jQuery function .ready()
fetchSomeDataAsynchronously(); //self-explanatory
$(document).ready(function () {
//should I load waiting view here or can I initialize it earlier??
window.mainUserHomeView = new MainUserHomeView({el: $("#user-home-main-div")});
window.mainUserHomeView.render();
window.userHomeMainTableView = new UserHomeMainTableView({el: $("#user-home-main-table-div")});
window.userHomeMainTableView.render();
fetchTeamSnapTeams(); //fetch more data asynchronously
});
Maybe my question is simply - can I show a loading screen before .ready() fires and what does that look like?
Yes you can. Just include the script to show the view in your HTML's head or at the beginning of the body. Make sure that you put it after you've loaded your required resources (e.g. jQuery) though.
Simplified example:
<html>
<head>
<script type="text/javascript">
function showLoadingView() {
// ...
}
showLoadingView();
</script>
</head>
<body>
<!-- ... -->
</body>
</html>

managing common jquery script files in asp.net project

I'm looking for best practices for using javascript/jQuery snippets in an asp.net project. I know that it is best to put all the scripts in a separate file rather than inline. That's good. It is easy to move these script functions to a common file (may be a couple of different ones to even out the performance of loading a single large file for small functions).
But there is some jQuery stuff that needs to happen on document.Ready on each page. How will I move this to a common .js file? I would like to avoid one script per page as it would be just too many.
For example, say Page1 has a need to manipulate a few radio buttons on load and has the following script inline. (just for illustration)
<script>
$(document).ready(function() {
//check checkboxes
if(true)
call function1();
});
</script>
Same with Page2 but for some other condition, calling different function function2.
I can move the function1 and function2 to a common .js file but how about the document ready sections. Should that stay inline? I assume so because otherwise I'm not sure how the common.js will differentiate between document.ready for different pages.
Then does it defeat the purpose of not having inline javascript? If anyone can throw some light into this, it is much appreciated.
I did some research, but probably due to incorrect keywords, so far I haven't been able to find any good information along the same lines. Unobtrusive JavaScript seems promising in the comments below.
You should specify what behaviors should exist within the HTML using data-* attributes.
You can then use a single universal piece of Javascript code to read these attributes and apply behaviors.
For example:
<div data-fancy-trick="trick-3">...</div>
In the JS file, you can write something like
$('[data-fancy-trick]'.each(function() {
var trickName = $(this).data('fancy-trick');
switch (trickName) {
...
}
});
For real-life examples of this technique, look at Bootstrap's Javascript components.
You can simply have separate js files per page and include them in relevant pages. For shared script code, have a common js file. Following your example:
common.js
var myCommonVar = {};
function myCommonFunction(...){
...
}
page1.js
$(document).ready(function() {
...
function1();
...
});
page2.js
$(document).ready(function() {
...
function2();
...
});
page1.html
...
<script src='/js/common/js'></script>
<script src='/js/page1.js'></script>
...
page2.html
...
<script src='/js/common/js'></script>
<script src='/js/page2.js'></script>
...
Consider the usage of AMD (Asynchronous Module Definiton) design pattern. Put your JavaScript code into modules and on each page use just those you really need to. For example requirejs does a great job and I've been using it with success. If you have a bigger project you can split your modules into namespaces. This approach will keep excellent code maintainability and it's reliable. You simply put the "starter" javascript file on each page and load only those required modules you need to work with per each page.
There are many ways to deal with this problem, either using a JavaScript Framework that is aiming to treat your website as a 'Webapp' (Angular and Ember among the popular), or using your own custom script that will do just that - invoking the appropriate JavaScript per loaded page.
Basically, a custom script that will be able to handle it, will have to make use of (pseudo) 'Namespaces' to separate modules/pages code sections.
Assuming you have 2 hypothetical pages, Home and Browse, Simplified code sample may look like this:
HTML:
<body data-page="Home">
Global.js:
var MyApp = {}; // global namespace
$(document).ready(function()
{
var pageName = $('body').data('page');
if (pageName && MyApp[pageName] && MyApp[pageName].Ready)
MyApp[pageName].Ready();
});
Home.js:
MyApp.Home = MyApp.Home || {}; // 'Home' namespace
MyApp.Home.Ready = function()
{
// here comes your 'Home' document.ready()
};
Browse.js:
MyApp.Browse = MyApp.Browse || {}; // 'Browse' namespace
MyApp.Browse.Ready = function()
{
// here comes your 'Browse' document.ready()
};
MyApp.Browse.AnotherUtilFunc = function()
{
// you could have the rest of your page-specific functions as well
}
Also, since you're using ASP.NET MVC, sometimes your Controller name may fit as the qualified page name, you can set it automatically in your Layout.cshtml (if you have one):
<html>
<head>
</head>
<body data-page="#ViewContext.RouteData.Values["Controller"].ToString()">
#RenderBody()
</body>
</html>
I think its not worth stuffing up everything in a single file and separating them with conditional statements, just to avoid adding a reference on the respective file.
If you have code that can be called on 2,3 or more pages, then we can opt for having them in a common file. But if its going to be called on a single page then we must write code on the respective page only. This will also increase the overhead of declaring the functions that are not going to be called on the current page
And when you are using the common js file, then you don't need to worry about the $(document).ready(); event, you can use a single ready event in the common file and separate the code by using conditional statements.
The new versions of the script manager will combine everything into one blob of a script. In theory it makes fewer round trips and things run faster. In practice you could end up with several large scripts that are nearly identical and each page needs its own blob of a script. If your making one of those never change the url website pages then this is the way to go.
I came up with these best practices when I was working with jquery on ASP.Net
Load Jquery in your master page above the first script manager. Jquery is now available on every page. The browser will only get it once and cache it.
If bandwidth is an issue use a jquery loader like googleload or MS content delivery network
Document.load is always at the bottom of the page to guarantee that everything needed is already loaded.
From my blog that I haven't updated in years...Google Load with ASP.Net
One common way to address this problem would be to have your common script include followed by a per-page script element:
<!-- In 'shoppingcart.html' -->
<script src="main.js"></script>
<script>
// Let there be a onDomReady JS object inside main.js
// that defines the document.ready logic on a per-page basis
$(document).ready(onDomReady.shoppingCart);
</script>
Great question, I have been dealing with the same thing.
Here is what I have been doing:
Have your $(document).ready() call different init functions (if they exist), where each .js file has its own init which adds event listeners and loads functions, messes with css, etc.. Each .js file is separated out into different pieces of functionality.
This way you have one document ready that calls all of your initializers. So each page would include the .js functionality it needs. This way you can separate out what is different.
ex:
ready.js:
$(document).ready(function(){
if (typeof menuNavInit == 'function'){
menuNavInit();
}
if (typeof menuNavDifferentInit == 'function'){
menuNavDifferentInit();
}
//other .js functionality
});
menuNav.js
function menuNavInit(){
$("#menu").on('click', menuNavClick)
}
function menuNavClick(){
//do something
}
menuNavDifferent.js
function menuNavDifferentInit(){
$("#menu").on('click', menuNavDifferentClick)
}
function menuNavDifferentClick(){
//do something else
}
page1.html
...
<script src='scripts/ready.js'></script>
<script src='scripts/menuNav.js'></script>
...
page2.html
...
<script src='scripts/ready.js'></script>
<script src='scripts/menuNavDifferent.js'></script>
...

JQuery load() will break facebook like button/comment box. How to workaround?

I am coding a big website but I have cut down my problem into the following tiny html file:
http://dl.dropbox.com/u/3224566/test.html
The problem is that if I (re)load with JQuery a content that features a facebook code, the latter won't appear, even if I reload the script (leading to a duplication of that all.js script, which is another issue).
How can I fix this?
Regards,
Quentin
Use the FB.XFBML.parse() docs after you load the new content
function loadPage() {
$('#test').load('test.html #test', function() {
FB.XFBML.parse( );
}).fadeOut('slow').fadeIn('slow');
}
Note, that loading a fragment with id test in a div with id test will create multiple (two) elements with the same id (nested in each other) in the page, which should never happen as it is invalid.
To avoid this use the more verbose $.get method
$.get('test.html',
function(data) {
var temp = $('<div>').html(data).find('#test');
$('#test').html(temp.html());
}
);

Categories