tabs with angular: loading tab content on click only with $http - ajax

I have big forms with lots of data,
so I'd like tabs with chunks of data for each tab.
I'd like tab content to be lazy loaded on click of the tab title, and it then doesn't need to be reloaded again when selected again later.
I think this example goes into the direction of what I want:
angular-ui tabs loading template in tab-content
but this seems to load a static template:
<tabs>
<pane active="pane.active"
heading="{{pane.title}}"
ng-repeat="pane in panes">
<div ng-include="pane.content"></div>
</pane>
</tabs>
How can I load the pane's content dynamically with $http.get()?
Note: this is already a page loaded via ng-view routing, so I can't do nested routing.
EDIT: The content is quite different for every tab, so ideally I'd provide a function and a template for every tab or something like that...
I guess angular-ui is a good way to go about this?

Was curious myself how to make tabs load via ajax. Here's a little demo I worked out.
Tabs have a select attribute that triggers when selected. So I used following for a tab:
<tab heading="{{tabs[0].title}}" select="getContent(0)">
<div ng-hide="!tabs[0].isLoaded">
<h1>Content 1</h1>
<div ng-repeat="item in tabs[0].content">
{{item}}
</div>
</div>
<div ng-hide="tabs[0].isLoaded"><h3>Loading...</h3></div>
</tab>
Controller:
$scope.tabs = [
{ title:"AJAX Tab 1", content:[] , isLoaded:false , active:true},
{ title:"Another AJAX tab", content:[] , isLoaded:false }
];
$scope.getContent=function(tabIndex){
/* see if we have data already */
if($scope.tabs[tabIndex].isLoaded){
return
}
/* or make request for data , delayed to show Loading... vs cache */
$timeout(function(){
var jsonFile='data'+(tabIndex +1)+'.json'
$http.get(jsonFile).then(function(res){
$scope.tabs[tabIndex].content=res.data;
$scope.tabs[tabIndex].isLoaded=true;
});
}, 2000)
}
Would move the cache to a service so if user switches views, and returns, data will still be in service cache
DEMO

Another approach is to use dynamic ng-include:
<tabset>
<tab ng-repeat="tab in tabs"
heading="{{tab.heading}}"
select="setTabContent(tab.content)">
</tab>
</tabset>
<ng-include src="tabContentUrl"></ng-include>
Then the controller has this:
$scope.tabs = [
{ heading: 'Dashboard', content: 'dashboard' },
{ heading: 'All Nodes', content: 'nodes' },
{ heading: 'Details', content: 'details' },
{ heading: 'Dependencies', content: 'dependencies' }
];
$scope.setTabContent = function(name) {
$scope.tabContentUrl = "view/" + name + "/index.html";
}

Related

Ajax.ActionLink inside target div to update target div

Scoured a bunch of articles and questions here but can't get to the bottom of this.
I have a page that opens a file manager inside a jqueryUI dialog. This file manager simply displays some thumbnails and a list of sub folders within the main assets folder. The idea is the user clicks a sub folder and gets the thumbs within that folder and a list of sub folders .. I'm sure you get the idea. But, when clicking the folder link the whole page is updated not just the dialog.
In the parent page I am referencing
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
and the jquery functions to prepare and open the dialog:
$("#scopeManagerDialog").dialog({
autoOpen: false,
width: 400,
resizable: false,
title: "File Manager",
modal: true,
buttons: {
"Close": function () {
$(this).dialog("close");
}
}
});
$("#openScopeManager").click(function() {
$("#scopeManagerDialog").load("#Url.Action("Index", "ScopeManager")",
function(response, status, xhr) {
$("#scopeManagerDialog").dialog("open");
});
return false;
});
and the div it loads into:
<div id="scopeManagerDialog" title="Scope Manager" style="overflow: hidden">
</div>
the controller action that returns the partial
public ActionResult Index(string path)
{
if (path==null)
{
path = ConfigurationManager.AppSettings["assetRoot"];
}
List<AssetVM> assets = ScopeManager.GetAllAssets(Server.MapPath(path));
List<FolderVM> folders = ScopeManager.GetAllFolders(Server.MapPath(path));
AssetSetVM model = new AssetSetVM()
{
Path = path,
Assets = assets,
Folders = folders
};
return PartialView("ScopeManager", model);
}
and finally the partial itsself. Note that the ActionLink refers to the containing div (I'm wondering if this is the issue .. I hope not and my gut says this should be possible).
<div id="scopeManagerContainer">
<div id="folderList">
<ul>
#foreach (FolderVM folder in Model.Folders)
{
<li>#Ajax.ActionLink(folder.Name,"Index","ScopeManager",new AjaxOptions {UpdateTargetId = "scopeManagerContainer"})</li>
}
</ul>
</div>
<div id="fileList">
#foreach (AssetVM asset in Model.Assets)
{
<img src='#Url.Action("GetThumbnail", new { path = string.Format("{0}/{1}", Model.Path, asset.FileName), width = 100, height = 100 })' />
}
</div>
</div>
Please let me know if anything else is required. As I say the first load works. Everything is appearing correctly, just that when I click the folder link it re-renders at the page level instead of updating the contents of the dialog.
Thanks.
My error .. I should have been setting the target to scopeManagerDialog not scopeManagerContainer. The question really is a bit pointless now but not sure what the policy is on deleting questions. I'll leave it for now for a mod to decide what to do with it.

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

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>

jquery cookie setup

I am a newbie at jquery. I've been researching how to set cookies for a jquery function using the cookie plugin.
I have this simple hide and show function for a div but want the class states to persist after links to other pages and refreshing.
The JS looks like this
<script type="text/javascript">
$(document).ready(function(){
$("div.toggle_search").hide();
$("h2.trigger_up").click(function() {
$(this).toggleClass("active").prev().slideToggle(250);
if ($.cookie('more_search','1')) {
$("#criteria").attr('class', $.cookie('more_search'));
} else {
$("#criteria").attr('class', 'active');
}
$.cookie('more_search', $(".trigger_up").attr('class'));
return false;
});
});
</script>
HTML
<div id="criteria">
<div class="toggle_search">
<div class='left'>
Stuff goes here
</div>
</div>
<h2 class="trigger_up">See More Search Criteria</h2>
<div class="clear"></div>
</div>
Any help would be greatly appreciated. !
Check the cookie before you show or hide the div. In this snippet, the div with id="moreButton" (not an actual button) has text saying "More" or "Less" for showing and hiding the div with id="moreOptions":
$(document).ready(function() {
if ($.cookie("show") == "show") {
$("#moreButton").html("Less «");
$("#moreButton").attr("title", "Hide the extra search parameters.");
$("#moreOptions").show();
}
else {
$("#moreButton").html("More »");
$("#moreButton").attr("title", "See more search options.");
}
$("#moreButton").click(function() {
$("#moreOptions").animate({ "height": "toggle" }, { duration: 60 });
if ($("#moreButton").html() == "More »") {
$("#moreButton").html("Less «");
$("#moreButton").attr("title", "Hide the extra search parameters.");
$.cookie("show", "show", { path: '/' })
}
else {
$("#moreButton").html("More »");
$("#moreButton").attr("title", "See more search options.");
$.cookie("show", "", { path: '/' })
};
});
}
);
Have you included the reference to the jQuery-cookie library?
See the documentation found here at the plugin page it looks like you are using or trying to use, https://github.com/carhartl/jquery-cookie/
By setting the cookie to expire in the future, it should persist until it hits the expiration date.
Ex: $.cookie('more_search', $(".trigger_up").attr('class'), { expires: 7 });
//Would expire in a week.
Also notice you have two classes when you get $(".trigger_up").attr('class') trigger_up and active (when the link is clicked for the first time), you might want to parse that the cookie value is set to "active"

dynamically add jquery tab in view

I am very new to MVC and jQuery and I have a problem adding a new tab to a jQuery tab panel.
I have a ASP.NET MVC3 View that contains two partial views. The first one is a search form and the second one displays the search results.
Now I need to put the search results in a tab of a tab panel. At a later point in this project it should work like this: The user searches for some keywords, and for each new search a new tab is added to the tab panel. This way it should be possible for the user to switch to a previous search. But I am not this far yet.
What I tried first was to add a static tab panel to the page with a single tab that contains the search results. This was rather easy and I had no problems. What I tried to do next, was to add a new tab with static content ("Hello World") to the tab panel each time the user clicks the submit button of the search form. But this doesn't really work:
I can see that the new tab is added to the tab panel. But only for < 1 sec. The new tab disappears as soon as the search results are rendered. It seems like rendering the partial view overwrites the changes made by jQuery/JavaScript.
This is the view (_SearchInput is the partial view for the search form, _SearchResults is the partial view used to display search results):
<div class="roundedBorder">
#Html.Partial("_SearchInput")
</div>
<div id="tabs" style="margin-top:7px;">
<ul>
<li>Test 1</li>
</ul>
<div id="contentcontainer">
<div id="fragment-1">#Html.Partial("_SearchResults")</div>
</div>
</div>
In _SearchInput I add the tabs when the document is ready and call searchClick when the submit button of the form is clicked:
<script>
$(document).ready(function () {
/* show tabs */
$('#tabs').tabs();
});
function searchClick() {
var keyword = $("#searchTextInput").val().trim();
if (keyword == null || keyword == "") {
return false;
}
var title = keyword.substring(0, 10);
$('#contentcontainer').append("<div id='fragment-2'>hello world</div>");
$('#tabs').tabs("add", "#fragment-2", title);
}
</script>
How are you submitting the form? If your not posting an ajaxy type of result this would be expected as your page will be refreshing on form submit.
Rather, than submit the form, submit the form with jquery and in your success callback perform your post form submission tasks.
EG:
$('form').submit(function(evt) {
evt.preventDefault();
$.ajax({
type: "POST",
url: "formsubmiturl",
data: dataposted,
success: function() {
// add jquery tab stuff here
}
});

Rendering Django forms inside an EXTJS tab

I was able to successfully render my first django form inside an extjs tab. The form data displayed properly and the form validation appears to be working properly.
My problem is that django wants to render the whole new page, not just push the results back into the same tab. I think my app will function better if I can keep all this inside a single tab without complete page rendering.
Background: I used the EXTJS ajax tab example to get this working. Then only problem is that the example didn't have multiple get/post calls getting rendered into the same tab, so I'm not sure how to do that.
Question: How do I keep the results of the POST data inside the EXTJS tab? Also, from experts who develop a lot of these apps, am I using the correct pattern here?
Here's my basic layout:
File: grid.js - Builds an EXTJS grid, user clicks 'edit' icon which does a call to django to grab the edit form.
var createColModel = function (finish, start) {
var columns = [{
dataIndex: 'pk',
header: 'Student ID',
filterable: true
//,filter: {type: 'numeric'}
}, {
// ... More column data here
},{
header: 'Actions',
id: 'actions',
xtype: 'actioncolumn',
width: 50,
items: [{
icon : '/site_media/icons/application_edit.png',
tooltip: 'Edit Record',
handler: function(grid, rowIndex, colIndex) {
var rec = studentStore.getAt(rowIndex);
mainTabPnl.add({
title: rec.get('fields.first_name') + ', ' + rec.get('fields.last_name'),
iconCls: 'tabs',
autoLoad: {url: '/app/edit_student/' + rec.get('pk')},
closable:true
}).show();
}
}]
}];
File: views.py
def edit_student_view(request, sid):
print "Edit Student: " + sid
student = Student.objects.get(pk=sid)
if request.method == 'POST':
form = StudentProfileForm(request.POST, instance=student)
if form.is_valid():
student=form.save()
message="Edit successful"
c = {'form' : form, 'student':student, 'message':message}
return render_to_response('app/edit_student.html', c, context_instance=RequestContext(request))
else:
message = "The form contains errors."
c = {'form':form, 'student':student, 'message':message}
// Problem: This is now rendered as the whole page, not inside the tab
return render_to_response('app/edit_student.html', c, context_instance=RequestContext(request))
else:
form = StudentProfileForm(instance=student)
c = {'form':form, 'student':student}
//Initial GET: renders in correct EXTJS window.
return render_to_response('app/edit_student.html', c, context_instance=RequestContext(request))
File: edit_student.html - renders the django form
{% block mainpanel %}
{% if form.errors %}
<p>The Registration form had errors. Please try again.</p>
{% endif %}
{% if form %}
<form action="/app/edit_student/{{student.student_id}}/" method="post">
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit" />
</form>
{%endif%}
{% endblock %}
If you want to keep it within the tab then you'll need to forgo the standard HTML form mechanisms and instead set the onclick event on a button to perform a POST via AJAX.

Resources