How to use AngularJS to lazy load content in a collapsible panel - ajax

I am building an application that uses the Bootstrap Collapse component to render a sequence of panels, all of which will initially be in the collapsed state.
Since the page may contain many such panels and each of them may contain a large amount of content, it seems appropriate to populate these panels on demand, by executing an AJAX call when the user expands any panel.
The dynamic content of the page (including the markup for the panels) is rendered using AngularJS, and I assume it's possible to configure Angular to bind to an event on the panel elements, that results in their content being lazy loaded when they expand.
Unfortunately, after looking at the AngularJS docs and the available tutorials, I can't see how best to tackle this. Can anyone throw any light on it?
Thanks in advance,
Tim

This is way old, but the question might still come up now and then. I now find this to be the most suitable solution without polluting your controllers:
(myDirective loading its content via AJAX right after its creation.)
<accordion>
<accordion-group
heading=""
ng-repeat="foo in bar"
ng-init="status = {load: false}"
ng-click="status.load = true">
<myDirective ng-if="status.load"></myDirective>
</accordion-group>
</accordion>
each element created by ng-repeat gets its own $scope, so clicking ab accordion-group will result in only the respective directive being loaded.
edit:
depending on latency and the size of the data that's to be lazy loaded, you might consider using ng-mouseover instead of ng-click. That way loading starts some 100ms before the user opens the accordion which can reduce 'sluggishness' of your UI. Obviously there's the downside of occasionally loading content of groups that are never actually clicked.

#Tim Coulter, I've created something following the idea of #Stewie.
It can definitely be improved, but I guess it's a good starting point.
I've created a small directive to bind the click event of the accordion's panel. When the click event is fired, I passed the panel template via the panel-template= attribute and it updates the main-template which is used inside the panel.
It makes reference to 2 html files (panel1.html and panel2.html) that contains the content of the each panel.
I would recommend to create a service to fetch these files via AJAX - just the way you wanted.
On the code below I created a service called dataService for this purpose and you should bind it to the click event - so files are loaded on demand when the user clicks on it.
Note the the mainTemplate is a common panel to all accordions, so when it changes the all the accordions will have the same content, BUT I am assuming you want to display only one panel at time, right ?!
Anyway as I said before the logic can be improved to fix these little 'gotchas', but I believe the core functionality is there to start with. :)
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<script>
var myApp = angular.module('myApp', []);
myApp.controller('AccordionDemoCtrl', ['$scope', 'dataService', function ($scope, dataService) {
$scope.oneAtATime = true;
$scope.mainTemplate = '';
$scope.groups = [
{
id: "001",
title: "Dynamic Group Header - 1",
content: "Dynamic Group Body - 1",
template: "panel1.html"
},
{
id: "002",
title: "Dynamic Group Header - 2",
content: "Dynamic Group Body - 2",
template: "panel2.html"
}
];
}]);
myApp.factory('dataService', [ '$http', function($http){
return {
getData: function() {
return // you AJAX content data here;
}
}
}]);
myApp.directive('accordionToggle', [function () {
return {
restrict: 'C',
scope: {
mainTemplate: '=',
panelTemplate: '#'
},
link: function (scope, element, iAttrs) {
element.bind('click', function(e){
scope.mainTemplate = scope.panelTemplate;
scope.$apply();
});
}
};
}]);
</script>
</head>
<body ng-controller="AccordionDemoCtrl">
<div class="accordion" id="accordionParent">
<div class="accordion-group" ng-repeat="group in groups" >
<div class="accordion-heading">
<a class="accordion-toggle" main-template="$parent.mainTemplate" panel-template="{{ group.template }}" data-toggle="collapse" data-parent="#accordionParent" href="#collapse{{ $parent.group.id }}">
Collapsible Group Item {{ $parent.group.id }}
</a>
</div>
<div id="collapse{{ group.id }}" class="accordion-body collapse">
<div class="accordion-inner">
<div class="include-example" ng-include="mainTemplate"></div>
</div>
</div>
</div>
</div>
</body>
</html>

Related

Grails remoteLink ajax issue populating JQuery accordion

Following the simple example from Jquery: Accordion Example
Using a remoteLink function from grails (AJAX) the information is pulled back from the controller and sent back to the GSP, which works fine. I do however want this data to be placed within a Accordion container... click here for screenshot of current functionality.
(Event Create Page rendering form template) _form - GSP:
<g:remoteLink controller="event" action="showContacts" method="GET" update="divContactList">Show Contacts!</g:remoteLink>
<div id="divContactList">
<g:render template="contactListAjax" model="[contactList: contactList]" />
</div>
<script type="text/javascript">
$(document).ready(function()
{
alert("Checking if I'm ready :)");
$( "#accordion" ).accordion({
header: 'h3',
collapsible: true
});
});
</script>
_contactListAjax - GSP Template
<div id="accordion">
<g:each in="${contactList}" status = "i" var="contact">
<h3>${contact.contactForename}</h3>
<div><p>${contact.email}</p></div>
</g:each>
</div>
Not quite sure what I'm doing wrong here, as I'm encasing the data with the div with an id accordion, yet doesn't load. Please refer to screenshot link above to see what is currently happening.
UPDATE
Event (Standard Generated CRUD, only showing where the relivant imports are) create - GSP
<link rel="stylesheet" href="${resource(dir: 'css', file: 'jquery.ui.accordion.css')}"/>
<g:javascript src="jquery-1.7.1.min.js"></g:javascript>
<g:javascript src="jquery-ui.min.js"></g:javascript>
Try replacing:
$(function() {
$( "#accordion" ).accordion({
collapsible: true
});
})
with
$( "#accordion" ).accordion({
collapsible: true
});
Edit:
You can't use an <r:script /> tag after the request is finished. The server has already laid out all of the resources. Change it to a standard javascript tag. Also the inclusion of your javascript and css files should be elsewhere on the page.
I solved this... (well Hack & Slash for now... not the best, but only viable solution for now)
var whatever is the stored HTML generated by the AJAX and placed into the divContactList on the update function. The Accordion is deleted and then rebuilt (not the best approach I realise)
var whatever = $('#divContactList').html();
$('#accordion').append(whatever)
.accordion('destroy').accordion({
collapsible: true,
active: false
});

Dynamically inserting content within same page rather then going to a new page

EDIT italics = more detailed explanation added to the question. Thanks.
I'm building a jQuery Mobile site which has a Gallery section.
The gallery has a series of thumbnails on the top of the screen.
Users click on the thumbnail to load in new content, that being a larger image, text, and potentially audio on some of them.
It's at this point that I'm not sure what to do: the way jQuery Mobile works, it's geared towards loading new pages, or views. But I just want to inject new content in a container on the current page.
To be clear, when the user clicks on another thumbnail, a new image replaces the content of the container with new content.
I have two questions:
I'm not sure how to structure the dynamic content. I was thinking i'd create an html file for each item, which as a rule always contains a title, information and sometimes, audio.
I'm not sure how to script this functionality in jQuery Mobile. It's obviously Ajax, but I'm not familiar with it yet, especially since jQuery Mobile has it's own methods in place already which seems to redefine behaviors in a way that's contradictory to this approach described here.
Here is a code explanation of what i'm trying to do:
<!-- Galleries -->
<div data-role="page" id="galleries">
<div data-role="content" role="main">
This is the Selection UI, if i click on thumb2.jpg, it'd
fill #content-holder with the whatever html is in content2.php
<div id="thumb-carousel">
<img src="thumb1.jpg">
<img src="thumb2.jpg">
<img src="thumb3.jpg">
<img src="thumb4.jpg">
<img src="thumb5.jpg">
<img src="thumb6.jpg">
<img src="thumb7.jpg">
<img src="thumb8.jpg">
<img src="thumb9.jpg">
</div>
<!-- This is the container, currently it's filled
with the kinda stuff i need to put in it. -->
<div id="content-holder">
<img src="myimage1.jpg"/>
<p>Artwork Title</p>
<p>Caption</p>
<audio>//mp3</audio>
</div>
</div>
</div>
//remember to use event delegation because you never know when the page will be in the DOM
$(document).delegate('#galleries', 'pageinit', function () {
//bind a `click` event handler to each thumbnail link
$('#thumb-carousel').children().bind('click', function () {
$.ajax({
url : $(this).attr('href'),
success : function (serverResponse) {
//select the container,
//then fade it out,
//change it's HTML to the response from the AJAX request,
//and fade it back in
$('#content-holder').fadeOut(500, function () {
$(this).html(serverResponse).fadeIn(500);
});
},
error : function (jqXHR, textStatus, errorThrown) {
//remember to handle errors so your page doesn't seem broken to the user
}
});
//prevent the default behavior of the link, which is to navigate to the `href` attribute
return false;
});
});
This expects your server-response to be valid HTML markup that is ready to inject into the DOM, meaning no <html> or <body> tags, just what you want to add to the DOM:
<img src="..." />
<span>TITLE</span>
<audio src="..."></audio>
Here are some docs for ya:
$.ajax(): http://api.jquery.com/jquery.ajax
.closest(): http://api.jquery.com/closest
.fadeIn(): http://api.jquery.com/fadein

Reloading everything but one div on a web page

I'm trying to set up a basic web page, and it has a small music player on it (niftyPlayer). The people I'm doing this for want the player in the footer, and to continue playing through a song when the user navigates to a different part of the site.
Is there anyway I can do this without using frames? There are some tutorials around on changing part of a page using ajax and innerHTML, but I'm having trouble wrapping my head aroung getting everything BUT the music player to reload.
Thank you in advance,
--Adam
Wrap the content in a div, and wrap the player in a separate div. Load the content into the content div.
You'd have something like this:
<div id='content'>
</div>
<div id='player'>
</div>
If you're using a framework, this is easy: $('#content').html(newContent).
EDIT:
This syntax works with jQuery and ender.js. I prefer ender, but to each his own. I think MooTools is similar, but it's been a while since I used it.
Code for the ajax:
$.ajax({
'method': 'get',
'url': '/newContentUrl',
'success': function (data) {
// do something with the data here
}
});
You might need to declare what type of data you're expecting. I usually send json and then create the DOM elements in the browser.
EDIT:
You didn't mention your webserver/server-side scripting language, so I can't give any code examples for the server-side stuff. It's pretty simple most of time. You just need to decide on a format (again, I highly recommend JSON, as it's native to JS).
I suppose what you could do is have to div's.. one for your footer with the player in it and one with everything else; lets call it the 'container', both of course within your body. Then upon navigating in the site, just have the click reload the page's content within the container with a ajax call:
$('a').click(function(){
var page = $(this).attr('page');
// Using the href attribute will make the page reload, so just make a custom one named 'page'
$('#container').load(page);
});
HTML
<a page="page.php">Test</a>
The problem you then face though, is that you wouldnt really be reloading a page, so the URL also doesnt get update; but you can also fix this with some javascript, and use hashtags to load specific content in the container.
Use jQuery like this:
<script>
$("#generate").click(function(){
$("#content").load("script.php");
});
</script>
<div id="content">Content</div>
<input type="submit" id="generate" value="Generate!">
<div id="player">...player code...</div>
What you're looking for is called the 'single page interface' pattern. It's pretty common among sites like Facebook, where things like chat are required to be persistent across various pages. To be honest, it's kind of hard to program something like this yourself - so I would recommend standing on top of an existing framework that does some of the leg work for you. I've had success using backbone.js with this pattern:
http://andyet.net/blog/2010/oct/29/building-a-single-page-app-with-backbonejs-undersc/
You can reload desired DIVs via jQuery.ajax() and JSON:
For example:
index.php
<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript" src="ajax.js"></script>
<a href='one.php' class='ajax'>Page 1</a>
<a href='two.php' class='ajax'>Page 2</a>
<div id='player'>Player Code</div>
<div id='workspace'>workspace</div>
one.php
<?php
$arr = array ( "workspace" => "This is Page 1" );
echo json_encode($arr);
?>
two.php
<?php
$arr = array( 'workspace' => "This is Page 2" );
echo json_encode($arr);
?>
ajax.js
jQuery(document).ready(function(){
jQuery('.ajax').click(function(event) {
event.preventDefault();
// load the href attribute of the link that was clicked
jQuery.getJSON(this.href, function(snippets) {
for(var id in snippets) {
// updated to deal with any type of HTML
jQuery('#' + id).html(snippets[id]);
}
});
});
});

Using Jquery in Controller Page-ASP.NET MVC-3

Could any one give an example, how to use Jquery in Controller Page. MVC3 -ASP.NET(How To put various tags like )
I want to show a simple alert before rendering a view in Controller.
Thank you.
Hari Gillala
Normally scripts are part of the views. Controllers shouldn't be tied to javascript. So inside a view you use the <script> tag where you put javascript. So for example if you wanted to show an alert just before rendering a view you could put the following in the <head> section of this view:
<script type="text/javascript">
alert('simple alert');
</script>
As far as jQuery is concerned, it usually is used to manipulate the DOM so you would wrap all DOM manipulation functions in a document.ready (unless you include this script tag at the end, just before closing the <body>):
<script type="text/javascript">
$(function() {
// ... put your jQuery code here
});
</script>
If you are talking about rendering partial views with AJAX that's another matter. You could have a link on some page that is pointing to a controller action:
#Html.ActionLink("click me", "someAction", null, new { id = "mylink" })
and a div container somewhere on the page:
<div id="result"></div>
Now you could unobtrusively AJAXify this link and inject the resulting HTML into the div:
$(function() {
$('#mylink').click(function() {
$('#result').load(this.href, function() {
alert('AJAX request finished => displaying results in the div');
});
return false;
});
});

JQuery Accordion doesn't 'accordionize' new panels loaded via AJAX

I have to load accordion panels into one of my pages using AJAX and I'm finding that JQuery is 'accordionizing' all of the panels defined in the HTML file, but none of the panels loaded via Javascript. One other little quirk: I'm doing this on a nested accordion - the accordion within an accordion. It's accordion inception, if you will.
I checked other Stack Overflow questions and the JQuery Forum and I found that most of them are about resizing panels after loading data. The closest question I found was here, but it doesn't answer my question because trying to destroy and then re-accordion-ize my JS-loaded panels does not work.
This is the relevant section of my html head section:
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/flick/jquery-ui.css" type="text/css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$(".accordion").accordion({
collapsible: true,
icons: false,
autoHeight: false,
active: false
});
});
</script>
This is the relevant section of my html body section:
<div class="accordion">
<h3 id="acc1">First panel title</h3>
<div>First panel content</div>
<h3 id="acc2">Second panel title</h3>
<div class="accordion" id="ajaxresults-aliaslist">This is where the nested accordion goes</div>
<h3 id="acc3">Third panel title</h3>
<div>Thirdpanel content</div>
</div>
<script type="text/javascript">
$("#ajaxresults-aliaslist").load("/loadaliaslist/");
</script>
When javascript loads "/loadaliaslist/", it received a message with the following content:
<h3>1st panel within a panel title</h3>
<div>1st panel within a panel content</div>
<h3>2nd panel within a panel title</h3>
<div>2nd panel within a panel content</div>
<h3>3rd panel within a panel title</h3>
<div>3rd panel within a panel content</div>
I know that the content above is being passed to the div because the content appears un-accordionized when I load the page. Instead of an accordion within an accordion, I just get a bunch of boring content sitting at the first level of the dream sequence. I've got to go down a level... Wait, where was I?
Right, one more thing: I have tried two things that did not work:
- I tried putting both the load and the accordion scripts at the bottom of the page (load first), hoping that the order would matter. (noobish? not sure...)
- I tried adding a script at the end to destroy and recreate the panels like so:
$("#ajaxresults-aliaslist").accordion('destroy').accordion();
That's all. I really hope someone out there is the Leonardo DiCaprio of accordions, and can help rescue me from my predicament. Much appreciated!
Untested but try something like this...
The issue is most likely due to the fact that the event handlers aren't being attached to the inserted content (via Ajax).
$(function() {
var config = {
collapsible: true,
icons: false,
autoHeight: false,
active: false
}
$(".accordion").live('load', function(){
$(this).accordion(config);
}).accordion(config);
});
EDIT: Changed the code a bit (still untested).
EDIT FROM OP: SOLUTION FOUND
After playing around more, I found a way to solve this problem as long as AJAX is loading the new data immediately on pageload. Solution was to call the accordion function, only once, immediately after the load. As in the solution below:
<script type="text/javascript">
$("#ajaxresults-aliaslist").load("/loadaliaslist/",
function(response, status, xhr) {
if (status == "error") {
var msg = "Sorry but there was an <strong>error</strong>: ";
$("#error").html(msg + xhr.status + " " + xhr.statusText);
}
/* All panels are accordionized immediately after loading the content */
$(".accordion").accordion({
collapsible: true,
icons: false,
autoHeight: false,
active: false
});
});
</script>
A word of caution: This will only work if all of the accordion content is loaded immediately. If there are pieces of content loaded after the first accor
I suppose that you want to reset the accordion control. Try to do this:
$('#accordion')[0].innerHTML = "";
After that fill the control with your HTML using the append.

Resources