Is Backbone.js suitable for getting HTML from server? - ajax

As far as I can tell, Backbone.js view represents DOM element. I take it from existing DOM or create it on the fly in el attribute.
In my case, I want to take it from server with AJAX request because I'm using Django templates and don't want to rewrite everything to JavaScript templates.
So I define el function that performs AJAX request.
el: function() {
model.fetch().success(function(response) {
return response.template
})
}
Of course, it does NOT work because AJAX request is executed asynchronous.
This means that I don't have el attribute and events does NOT work neither. Can I fix it?
Maybe the Backbone.js framework isn't the right tool for my needs? The reason I want to use that was to have some structure for the code.
P.S. I'm new to Backbone.js.

Do your ajax request from another view, or directly after the page load using jquery directly, and after you've downloaded your template, THEN instantiate your backbone view class with the proper id/el or whatever (depending on where you stored your ajax fetched template). Depending on your use-case, this may or may not be a sensible approach.
Another, perhaps more typical approach, would be to set up your view with some placeholder element (saying "loading" or whatever), then fire off the ajax, and after the updated template has been retrieved, then update your view accordingly (replace the placeholder with the actual template you requested).
When/if you update your view with new/other DOM elements, you need to call the view's delegateEvents method to rebind your events to the new elements, see:
http://backbonejs.org/#View-delegateEvents

I came across a similar requirement. In my instance, I was running asp.net and wanted to pull my templates from user controls. The first thing I would recommend is looking into Marionette because it will save you from writing a lot of boiler plate code in Backbone. The next step is to override how your templates are loaded. In this case I created a function that uses Ajax to retrieve the HTML from the server. I found an example of this function where they were using it to pull down html pages so I did a little modification so I can make MVC type requests. I can't remember where I found the idea from; otherwise, I would give the link here.
function JackTemplateLoader(params) {
if (typeof params === 'undefined') params = {};
var TEMPLATE_DIR = params.dir || '';
var file_cache = {};
function get_filename(name) {
if (name.indexOf('-') > -1) name = name.substring(0, name.indexOf('-'));
return TEMPLATE_DIR + name;
}
this.get_template = function (name) {
var template;
var file = get_filename(name);
var file_content;
var result;
if (!(file_content = file_cache[name])) {
$.ajax({
url: file,
async: false,
success: function (data) {
file_content = data; // wrap top-level templates for selection
file_cache[name] = file_content;
}
});
}
//return file_content.find('#' + name).html();
return file_content;
}
this.clear_cache = function () {
template_cache = {};
};
}
The third step would be to override Marionette's method to load templates. I did this in the app.addInitializer method. Here I am initializing my template loader and setting it's directory to a route handler. So when I want to load a template, I just set the template: "templatename" in my view and Backbone will load the template from api/ApplicationScreens/templatename. I am also overriding my template compiling to use Handlebars because ASP.net is not impressed with the <%= %> syntax.
app.JackTemplateLoader = new JackTemplateLoader({ dir: "/api/ApplicationScreens/", ext: '' });
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (name) {
if (name == undefined) {
return "";
} else {
var template = app.JackTemplateLoader.get_template(name);
return template;
}
};
// compiling
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function (rawTemplate) {
var compiled = Handlebars.compile(rawTemplate);
return compiled;
};
// rendering
Backbone.Marionette.Renderer.render = function (template, data) {
var template = Marionette.TemplateCache.get(template);
return template(data);
}
Hopefully this helps. I've been working on a large dynamic website and it is coming along very nicely. I am constantly being surprised by the overall functionality and flow of using Marionette and Backbone.js.

Related

Knockout SPA Ajax loaded templates

I am lost between the possibilities offered to handle this case: let's say we have the following constraints:
Knockout
SPA with Sammy.js - Html loaded via Ajax
My page:
+-------------------------------+
| #navigation |
+---------+---------------------+
| #sidebar| #content |
| | |
| | |
| | |
+---------+---------------------+
Currently, I have one appViewModel which handle the data-bind for all the shared elements of my website: #navigation and #sidebar. This appViewModel has observable used on every pages of my website.
appViewModel = function () {
var self = this;
self.sidebarItemArray = ko.observableArray([x, y, z]);
self.currentRoute = ko.observable();
...
self.updateView = function(path, currentRoute) {
return $.get(path, function( data ) {
var $data = $(data);
// Updates #content, TITLE and update the currentRoute observable.
$( '#content' ).replaceWith($data.find('#content'));
document.title = $data.filter('title').text();
self.currentRoute(currentRoute);
}, 'html');
}
Sammy(function() {
this.get(':link'', function() {
self.updateView(this.path, this.params.link);
});
}).run();
}
ko.applyBindings(new appViewModel());
Now, let's say that #content is a piece of DOM loaded through an Ajax Call. Each time a user click a link inside #navigation or #sidebar, Sammy.js intercept it and then update #content. The problem is that the new DOM inside #content has data-bindings itself.
1) First, should I use the html data-bind on #content, replaceWith(as above) or the template binding with custom function to get the template?
(http://knockoutjs.com/documentation/template-binding.html#note-5-dynamically-choosing-which-template-is-used)? What is the best practice here?
2) Should Sammy necessary lives inside the appViewModel as in the documentation or elsewhere is just fine?
3) Once the updateView method is completed, how would you bind the new DOM? Like below? Isn't there a risk of rebinding some DOM because ko.applyBindings has already been called without second argument?
ko.applyBindings(new routeSpecificViewModel() , document.getElementById("content"));
I am thankful for your help.
One simple solution is to make the page's viewmodel an observable, and load it ondemand. Use a variable to record if ko.applyBindings has been called. Example from the knockout-spa framework:
/*! knockout-spa (https://github.com/onlyurei/knockout-spa) * Copyright 2015-2016 Cheng Fan * MIT Licensed (https://raw.githubusercontent.com/onlyurei/knockout-spa/master/LICENSE) */
define(['app/shared/root-bindings', 'framework/page-disposer', 'ko', 'sugar'], function (
RootBindings, PageDisposer, ko) {
var initialRun = true;
var Page = {
init: function (name, data, controller, path) {
Page.loading(false);
name = name.toLowerCase();
if ((Page.page().name == name) && (Page.page().data == data)) { // if the requested page is the same page, immediately call controller without going further
if (controller) {
controller(data);
}
document.title = Page.title();
if (Page.initExtra) {
Page.initExtra(name, data, controller);
}
return data;
}
var autoDispose = (Page.page().data.dispose && Page.page().data.dispose(Page)) || true; // if the requested page is not the same page, dispose current page first before swap to the new page
if (autoDispose !== false) {
// auto-dispose page's exposed observables and primitive properties to initial values. if not desired, return
// false in dispose function to suppress auto-disposal for all public properties of the page, or make the
// particular properties private
PageDisposer.dispose(Page.page().data);
}
PageDisposer.init(data); //store initial observable and primitive properties values of the page
var initialized = (data.init && data.init(Page)) || true; // init view model and call controller (optional) before template is swapped-in
if (initialized === false) {
return false; // stop initialization if page's init function return false (access control, etc.)
}
if (controller) {
controller(data);
}
Page.pageClass([name, ('ontouchstart' in document.documentElement) ? 'touch' : 'no-touch'].join(' '));
Page.page({
name: name,
data: data,
path: path
}); // to test if template finished rendering, use afterRender binding in the template binding
document.title = Page.title();
if (Page.initExtra) {
Page.initExtra(name, data, controller); // useful for common init tasks for all pages such as anaylitics page view tracking, can be set in RootBindings
}
if (initialRun) {
ko.applyBindings(Page, document.getElementsByTagName('html')[0]); // apply binding at root node to be able to bind to anywhere
initialRun = false;
}
return data;
},
page: ko.observable({
name: '', // name of the page - auto-set by the framework, no need to worry
data: {
init: function () {}, // preparation before the page's template is rendered, such as checking access control, init/instantiate modules used by the page, etc.
dispose: function () {} // properly dispose the page to prevent memory leaks and UI leftovers (important for SPA since page doesn't refresh between page views) - remove DOM element event listeners, dispose knockout manual subscriptions, etc.
}
}),
pageClass: ko.observable(''),
loading: ko.observable(false),
title: function () {
return Page.page().name.titleize(); // override in RootBindings as needed
}
};
Object.merge(Page, RootBindings); // additional root bindings as needed by the app
return Page;
});
A mini but full-fledged SPA framework built on top of Knockout, Require, Director, jQuery, Sugar.
https://github.com/onlyurei/knockout-spa
Live Demo: https://knockout-spa.mybluemix.net

Whats different between AngularJS “Responsive” calls vs good old AJAX calls?

I was watching a free interactive course published at angularjs.org to learn Angular, Shaping up with Angular js.
On this course at the very first chapter they say, one of the main reasons to use AngularJS is, it can build “Responsive” web apps. (I know about "Responsive Design" but that's totally a different thing), and explaining it saying that, with Angular you don’t need to refresh your web page to update it with the data getting from the web server (They tell you this, like this is brand new tech!).
I think isn’t that the same thing we did for last 10 years using Ajax? Or is this something totally different?
Please help me to understand this coz I'm new to AngularJS.
From my view “Responsive” web apps. means type of application that updates View regards to model change (MVC).
Angular application UI is full of watchers. For each variable wrapped by {{}} in HTML, Angular creates new watcher and when we update during code running this value, Angular, by using digest cycle updates view respectively. Or ng-repeat directive that creates separate scope per list item and adds watcher as well.
On other hand in pure Javascript I need find my element by id and update it manually.
Consider following example in Fiddle
HTML
<ul>
<li ng-click="loadGeo()">click 1</li>
</ul>
<ul> <pre>
data: {{data|json}}
</pre>
</ul>
JS
var app = angular.module('myModule', ['ngResource']);
app.controller('fessCntrl', function ($scope, Data) {
$scope.data = false;
$scope.loadGeo = function () {
Data.query()
.then(function (result) {
$scope.data = result.data.results[0];
}, function (result) {
alert("Error: No data returned");
});
}
});
app.factory('Data', ['$http', '$q', function ($http, $q) {
var address = 'Singapore, SG, Singapore, 153 Bukit Batok Street 1';
var URL = 'http://maps.googleapis.com/maps/api/geocode/json?address=' + address + '&sensor=true';
var factory = {
query: function () {
var data = $http({
method: 'GET',
url: URL
});
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
}
}
return factory;
}]);
On start we have empty data: $scope.data = false;
We click on button, we get Geo data from factory and populate data with Geo output. Our GUI updates without any additional code.
This approach I would call “Responsive” web app
I suggest you to read this great post written by Josh David Miller:
how-do-i-think-in-angularjs-if-i-have-a-jquery-background

jquery mobile ajax sends both GET and POST requests

Here is the problem:
By default jQuery Mobile is using GET requests for all links in the application, so I got this small script to remove it from each link.
$('a').each(function () {
$(this).attr("data-ajax", "false");
});
But I have a pager in which I actually want to use AJAX. The pager link uses HttpPost request for a controller action. So I commented the above jQuery code so that I can actually use AJAX.
The problem is that when I click on the link there are two requests sent out, one is HttpGet - which is the jQuery Mobile AJAX default (which I don't want), and the second one is the HttpPost that I actually want to work. When I have the above jQuery code working, AJAX is turned off completely and it just goes to the URL and reloads the window.
I am using asp.net MVC 3. Thank you
Instead of disabling AJAX-linking, you can hijack clicks on the links and decide whether or not to use $.post():
$(document).delegate('a', 'click', function (event) {
//prevent the default click behavior from occuring
event.preventDefault();
//cache this link and it's href attribute
var $this = $(this),
href = $this.attr('href');
//check to see if this link has the `ajax-post` class
if ($this.hasClass('ajax-post')) {
//split the href attribute by the question mark to get just the query string, then iterate over all the key => value pairs and add them to an object to be added to the `$.post` request
var data = {};
if (href.indexOf('?') > -1) {
var tmp = href.split('?')[1].split('&'),
itmp = [];
for (var i = 0, len = tmp.length; i < len; i++) {
itmp = tmp[i].split('=');
data.[itmp[0]] = itmp[1];
}
}
//send POST request and show loading message
$.mobile.showPageLoadingMsg();
$.post(href, data, function (serverResponse) {
//append the server response to the `body` element (assuming your server-side script is outputting the proper HTML to append to the `body` element)
$('body').append(serverResponse);
//now change to the newly added page and remove the loading message
$.mobile.changePage($('#page-id'));
$.mobile.hidePageLoadingMsg();
});
} else {
$.mobile.changePage(href);
}
});
The above code expects you to add the ajax-post class to any link you want to use the $.post() method.
On a general note, event.preventDefault() is useful to stop any other handling of an event so you can do what you want with the event. If you use event.preventDefault() you must declare event as an argument for the function it's in.
Also .each() isn't necessary in your code:
$('a').attr("data-ajax", "false");
will work just fine.
You can also turn off AJAX-linking globally by binding to the mobileinit event like this:
$(document).bind("mobileinit", function(){
$.mobile.ajaxEnabled = false;
});
Source: http://jquerymobile.com/demos/1.0/docs/api/globalconfig.html

HTML data from multiple ajax requests to javascript array

I'm trying to pre-load some html content using AJAX and jQuery. The AJAX callback function adds the data to an associative array. I'm fine if I do each request individually:
var contentArray = new Object();
var urlA = "includes/contentA.php";
var urlB = "includes/contentB.php";
var urlC = "includes/contentC.php";
$.get(urlA, function(htmlA) {
contentArray["A"] = htmlA;
});
$.get(urlB, function(htmlB) {
contentArray["B"] = htmlB;
});
$.get(urlC, function(htmlC) {
contentArray["C"] = htmlC;
});
Since I am likely to have a few of these (more than three), I tried to do it a for loop:
var contentArray = new Object();
var pages = new Object();
pages["A"] = "includes/contentA.php";
pages["B"] = "includes/contentB.php";
pages["C"] = "includes/contentC.php";
for (var key in pages) {
var URL = pages[key];
$.get(URL, function(html) {
contentArray[key] = html;
});
}
However, this doesn't work. contentArray only has one property containing html data, rather than three. I'm knew to jQuery, particularly the AJAX stuff, so both explanations and solutions (similar or different-method-same-result) are welome.
By the way, I'm aware that one larger AJAX request is preferable to multiple small ones, but I'm trying to retain compatibility for users without JS enabled, and the current php includes are convenient. Any suggestions as how I might satisfy both these requirements are also very welcome.
Thanks.
The callback function for an AJAX request doesn't run until the request returns. In your case each callback function will use key as it exists in the current context, and since there's no key variable in it's local scope it will use the nearest it can find, the key in your for loop.
The problem is by the time the AJAX requests return, the for loop has been fully iterated over and key is equal to the last key in the array. Thus each of the callback functions will receive the same key, overwriting the previous value in your contentArray.
If you're using jQuery 1.5.1 or above a quick and dirty solution (one that doesn't involve changing the current structure of your PHP files) might be to try the following:
for (var key in pages) {
var URL = pages[key];
$.ajax({
url: URL,
xhrFields: {
'customData': key
},
success: function(html, statusText, jqXHR) {
contentArray[jqXHR.customData] = html;
}
});
}
I haven't tested that but according to the documentation page it should work. All you're doing is using the request object created by jQuery to pass your variable along to the callback function.
Hope that helps

MVC2 Client-Side Validation for injected Ajax content

I am making an Ajax call and adding content to a form inside a MVC2 app.
I need to update the Client Validation Metadata with the validation for my new content.
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"
...
</script>
Is there a way to generate this metadata for a partial view ?
Thanks in advance.
I was banging my head against a wall for a few days on this too and was going to go down the route of removing the form tag, but have just got it working in a slightly less hacky way if you are still interested. My scenario was similar in that I have a form with a collection of elements to validate initially, but users can dynamically add new rows via ajax.
I'll break it down so hopefully it'll be easier to see what is going on. Looking at the MVC source code, the form and validation works roughly as so:
Html.BeginForm() outputs the opening form tag then creates and returns a new instance of MvcForm, which doesn't outwardly do much except make the scope of the form easier to manage for you.
It does however create a new FormContext and stores this within ViewContext.FormContext. It is this FormContext that tracks the client validation.
The last thing Html.BeginForm() does is set the FormId property of the new FormContext, using the id of the form tag. This is required so the client script can match up forms and validation rules.
Html.EndForm() disposes the MvcForm. This Dispose method outputs the form closing tag and then calls ViewContext.OutputClientValidation() which is resposible for outputting the javascript. Lastly it removes the current FormContext and sets it back to the parent FormContext or null if there isn't one.
So to not output the form tag we somehow need to take some of the FormContext management out of the MvcForm constructor/destructor.
So within my Partial View I did the following:
At the top I check if the ViewContext.FormContext has a value. If so we we are in the initial load so no need to mess around. If not, it is an ajax call, so I enable client validation, create a new MvcForm directly (not with BeginForm) - this causes a FormContext to be created - and set the FormContext.FormId to the same as my parent page
At the end of the view, I check if I have a form instance and if so, call ViewContext.OutputClientValidation() and reset the ViewContext.FormContext to null. I do not Dispose() the MvcForm as this would output the closing tag and MvcForm does not contain disposable objects.
The skeleton of the view looks as so:
<%
MvcForm dummyForm = null;
if (this.ViewContext.FormContext == null)
{
Html.EnableClientValidation();
dummyForm = new MvcForm(this.ViewContext);
this.ViewContext.FormContext.FormId = "mainform";
}
%>
// standard partial view markup goes here
<%
if (dummyForm != null)
{
this.ViewContext.OutputClientValidation();
this.ViewContext.FormContext = null;
}
%>
You could quite easily wrap this up into an extension method
Phil
Finally got it to work.
The answer is simple: don't waist time with MicrosoftMvcValidation.js. It is generated with Script# which makes it difficult to extend.
Switch to xVal and jQuery Validation.
It doesn't need a form to generate the client validation metadata.
Also in order to load validation for a AJAX request all you have to do is to call the following after you have the new Html:
lForm.find("#placeholder").empty();
lForm.valid();
lForm.find("#placeholder").html(responseHtml);
That does it. First you remove the old content. Than re-run validation to get rid of potentially obsolete validation errors. Than add the new content. Works like a cham.
Also jQuery Validation makes it really easy to enable or disable validation for a certain field (conditional validation).
I have the same problem and resolve using the Future files, and in MicrosoftMvcJQueryValidation.js I change the and of file, this:
$(document).ready(function () {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
});
for:
function chargeValidation() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
}
and in content after close form using I call the 'chargeValidation()', this resolve for me the problem I have using $.get(action) containing a form validation.
I hope to help you!
Finally found it.
After content is loaded in dynamically you will need to register the new form.
Since I am using Facebox, I added it to the facebox code, however you can add it wherever you need, or in a callback if your modal or whatever you are loading into has an afterLoaded event.
I wrapped them in a try/catch just in case i ever use facebox without the validation stuff.
Just run these two lines AFTER your content has been loaded:
try {
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
} catch (err) {/* MVC Clientside framework is likely not loaded*/ }
I made some progress but I am not quite happy.
Problem #1: The client validation metadata is not generated unless you have a Html.BeginForm() in your partial. Which in my case is false because I do not update the entire form, I update portions of it.
Solution for Problem #1: Add a form in the partial view, let MVC generate the client validation MetaData and remove the form tags with a action filter. Let's call this Hack #1.
public class RemoveFormFilterAttribute : ActionFilterAttribute
{
private static readonly MethodInfo SwitchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);
private TextWriter _OriginalWriter;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_OriginalWriter = (TextWriter)SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {new HtmlTextWriter(new StringWriter())});
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (_OriginalWriter != null)
{
HtmlTextWriter lTextWriter =(HtmlTextWriter) SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {_OriginalWriter});
string lOriginalHTML = lTextWriter.InnerWriter.ToString();
string lNewHTML = RemoveFormTags(lOriginalHTML);
filterContext.HttpContext.Response.Write(lNewHTML);
}
}
Problem #2: The initial client validation metadata for the page is gone by the time I have the metaData for the new content.
Solution for Problem #2: Store the initial metadata (hard copy) and update it with the new fieds, than call the methods you mentioned to let MVC know new stuff arrived. Let's call this Hack #2.
<script type="text/javascript">
var pageMvcClientValidationMetadata;
$(document).ready(function() {
$("input[name='PaymentTypeName']").change(PaymentTypeChanged);
//create a back-up of the ValidationMetadata
pageMvcClientValidationMetadata = JSON.parse(JSON.stringify(window.mvcClientValidationMetadata));
});
function PaymentTypeChanged() {
var selectedPaymentType = $("input[name='PaymentTypeName']:checked").val();
$.ajax(
{
url: 'PersonalData/GetPaymentTypeHtml?&paymentType=' + selectedPaymentType,
type: "GET",
cache: false,
success: GetPaymentTypeHtml_Success
});
}
function GetPaymentTypeHtml_Success(result) {
$('#divPaymentTypeDetails').html(result);
UpdateValidationMetaData();
}
function UpdateValidationMetaData() {
//update the ValidationMetadata
for (i = 0; i < window.mvcClientValidationMetadata[0].Fields.length; i++) {
pageMvcClientValidationMetadata[0].Fields.push(window.mvcClientValidationMetadata[0].Fields[i]);
}
//restore the ValidationMetadata
window.mvcClientValidationMetadata = JSON.parse(JSON.stringify(pageMvcClientValidationMetadata));
//Notify the Validation Framework that new Metadata exists
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
}
Now. Any improvements would be appreciated.
Hack #1: How can I generate the client validation metadata without having an actual form ?
HAck #2: How can I appent to the page validation metadata ?

Resources