I've seen a few long winded answers to how to load templates for rendering in respect of the _underscore templates utility and the like. What I've got below works:
$.ajax({
url: 'template.tmp',
type: 'get',
success: function(data) {
$('html').append(data);
}
});
The code sits after jquery is loaded but before any scripts using the templates. Would this be ok or is there a reason why it wouldn't be a good idea to load templates like this? The templates.tmp file has templates within
<script type="text/template" id="tmpId"></script>
tags. They seem to get loaded into the DOM pretty much instantly. I'm prepared to be criticised for this implementation, but I'd just like to know why. The only thing I can think of is perhaps some processing if "error:" gets called instead of "success:". Thx.
I decided to produce my own OO based solution. This is the constructor:
var TemplateLoader = function(templatePath) {
this.template = templatePath;
this.loaded = false;
this.error = false;
}
And these are the methods:
TemplateLoader.prototype.setReady = function (state) {
this.loaded = state;
}
TemplateLoader.prototype.setError = function (state) {
this.error = state;
}
TemplateLoader.prototype.isReady = function () {
return this.loaded;
}
TemplateLoader.prototype.isError = function () {
return this.error;
}
TemplateLoader.prototype.loadTemplate = function() {
templateLoader = this;
$.ajax({
url: this.template,
type: 'get',
success: function(data) {
$('html').append(data);
templateLoader.setReady(true);
},
error: function() {
templateLoader.setError(true);
}
});
}
And this is how to use it:
templateLoader = new TemplateLoader('template.tmpl');
templateLoader.loadTemplate();
And to check the template has loaded:
templateLoader.isReady() //true for loaded
templateLoader.isError() //true for error loading template, eg. template doesn't exist
Again, I'd appreciate any feedback on issues anyone could see with this code. How about checking for the DOM object appended to the HTML. Worthwhile?
Related
I have a simple code that involves asynchronous tasks:
// The NewsFeed Class
function NewsFeed() {
this.loadFeed = function() {
$.ajax({
url: "http://www.example.com",
success: function() {
// doSomething here, and call onload.
}
});
}
// Need to implement onload here somehow
this.onload = ?;
this.loadFeed();
return this;
}
NewsFeed.constructor = NewsFeed;
// In main JS file
var newsFeed = new NewsFeed();
$(function() {
// do something
newsFeed.onload = function() { // do something when news feed is loaded };
}
My requirement is that, onload of NewsFeed needed to be executed in both case:
If the loadFeed's ajax is finished, run it immediately.
If the loadFeed's ajax is not done yet, run after it.
There's really no need to use new or constructor when you don't need new instances, all you really need is to run a simple ajax function that gets the result from cache if it hasn't changed.
function newsFeed() {
return $.ajax({
url : "http://www.example.com",
cache : true // let the browser handle caching for you
});
}
// In main JS file
$(function() {
newsFeed().then(function() {
// do something when news feed is loaded
});
});
The new pattern instead of callback is using Promises
see:
https://github.com/kriskowal/q
With jquery you can use:
https://api.jquery.com/category/deferred-object/
now the code:
function NewsFeed() {
function loadFeed() {
var deferred = $.Deferred();
$.ajax({
url: "http://www.example.com",
success: function(data) {
deferred.resolve(data);
},
error: function(data) {
deferred.reject(data);
}
});
return deferred.promise();
}
this.loadFeed = loadFeed;
return this;
}
NewsFeed.constructor = NewsFeed;
// In main JS file
var newsFeed = new NewsFeed();
newsFeed.loadFeed().done(function(data){
//data loaded successfully
})
.fail(function(data){
//ajax request failed
})
.always(function(){
//finally:
});
I am trying to figure out what is wrong with the following code:
$(".ReportScore").click(function () {
$.ajax({
type: "GET",
url: "/events/Tournaments/ReportScore",
success: function() {
location.reload();
},
error: function() {
alert("The scores were not recorded");
}
});
});
When I type the url in the bar, it works without problems, however when I try to do an ajax call I get 404 Page not found error.
To clarify, when I click on the button I Get a popup saying "The scores were not recorded" and on developer tools I get a script error saying Page not found.
I also have a breakpoint in visusal studio on the method itself, but the point is never hit as the method is never called.
Server Side Code:
public async Task<ActionResult> ReportScore()
{
var a = "abc"
}
var a line is never hit.
EDIT:
I have another ajax call from the same script that works without problems:
$("#InvitedMember").autocomplete({
source: function (request, response) {
$.ajax({
type: "POST",
url: "/events/Teams/Members",
data: { id: $("#InvitedMember").val() },
success: function (data) {
response($.map(data, function (item) {
return {
label: item.CustomUrl, value: item.CustomUrl
};
}));
}
});
},
create: function () {
$(this).data('ui-autocomplete')._renderItem = function (ul, item) {
return $('<li>')
.append("<a><div>" + item.label + "</div></a>")
.appendTo(ul);
};
},
select: function (event, ui) {
//you can access ui.item to get the selected item object.
$("#InvitedMember").val(ui.item.value);
return false;
}
});
It is not a good idea to hardcode your url's like that. You should always use the Url.Action or Url.RouteUrl html helper methods to build the relative url to the action methods/endpoints you are accessing. These helper methods will take care of correctly building the url regardless of your current page/path.
Also, from your comment,it seems like events is the name of your virtual directory/application name in your IIS. You should not use those in your code to build the urls as it might change based on your deployment. What if you want a a copy of your code deployed to "http://staging.yourSite.com" ?
As long as you use the Url.Action helper method,it will build the correct relative url to your app and you do not need to worry about your IIS virutal directory/application name.
var url = "#Url.Action("ReportScore","Tournaments")";
$.ajax({
type: "GET",
url:url,
success: function (res) {
alert('success happened');
//location.reload();
},
error: function () {
alert("The scores were not recorded");
}
});
The above code will work if you have it in a razor view. But if your ajax call code is in external js file, You may build the relative url to the app root and pass that to your js file and use that to build the url. You can use the #Url.Content("~") to build the url to app root. If you want, you can build the url to specific action method itself.
<script>
var myApp = myApp || {};
myApp.Urls = myApp.Urls || {};
myApp.Urls.baseUrl = '#Url.Content("~")';
myApp.Urls.reportScoreUrl= '#Url.Action("ReportScore","Tournaments")';
</script>
<script src="~/Scripts/PageSpecificExternalJsFile.js"></script>
And in your PageSpecificExternalJsFile.js file, you can read it like
var myUrlToUser = myApp.Urls.reportScoreUrl;
alert(myUrlToUser);
or build using the base url.
var myUrlToUser= myApp.Urls.baseUrl+"Tournaments/ReportScore";
alert(myUrlToUser);
The problem seems not in your javascript code but your controller action method (if it is indeed how it is written)
public async Task<ActionResult> ReportScore()
{
var a = "abc"
}
Your code block doesn't show any return statement, make sure your return value is awaited.
i.e.
return await "abc"
I have just recently be tasked with creating an SPA. So, I created a new project and selected SPA and found that it loaded all the files I needed including this knockout.js.
I am new to knockout.js so I watched a few videos and I get the idea, but the SPA project just doesn't seem to compute to me as it isn't a Single Page Application because you have to go to a new URL to login, register, authorise, manage account, etc (you get the idea).
So, looking at the code for the index page I can see a view model for the homeView. It looks like this:
function HomeViewModel(app, dataModel) {
var self = this;
self.myHometown = ko.observable("");
Sammy(function () {
this.get('#home', function () {
// Make a call to the protected Web API by passing in a Bearer Authorization Header
$.ajax({
method: 'get',
url: app.dataModel.userInfoUrl,
contentType: "application/json; charset=utf-8",
headers: {
'Authorization': 'Bearer ' + app.dataModel.getAccessToken()
},
success: function (data) {
self.myHometown('Your Hometown is : ' + data.hometown);
}
});
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
return self;
}
app.addViewModel({
name: "Home",
bindingMemberName: "home",
factory: HomeViewModel
});
and the HTML looks like this:
<!-- ko with: home -->
<!-- removed HTML to make it concise -->
<!-- /ko -->
now, from the look of this (correct me if I am wrong) the with handle states that if there is a variable called home, then display it (I assume this is what the bindingMembername is).
So, seeing that I can guess that if I added another partial page and included it. I could created a view model like this:
function DrawViewModel(app, dataModel) {
var self = this;
Sammy(function () {
this.get('#draw', function () {
app.home = null;
});
});
return self;
}
app.addViewModel({
name: "Draw",
bindingMemberName: "draw",
factory: DrawViewModel
});
so, in theory because this sets the app.home to null whenever anyone navigates to #draw, then the home partial will not be displayed, similarly I could added app.draw = null to the sammy route for the homeViewModel to hide the draw partial.
My issue with this, is that it will get massively complicated the more viewModels I create. So, is there something I am missing? Is there an easier way of doing this?
My ultimate goal is to move all the pages to be SPA (including the login/register pages).
Cheers in advance,
/r3plica
Well, after a bit of messing around I found out how to do this.
Basically I rewrote the AddView method and made it look like this:
// Other operations
self.addViewModel = function (options) {
var viewItem = new options.factory(self, dataModel),
navigator;
// Add view to AppViewModel.Views enum (for example, app.Views.Home).
self.Views[options.name] = viewItem;
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
if (typeof (options.navigatorFactory) !== "undefined") {
navigator = options.navigatorFactory(self, dataModel);
} else {
navigator = function () {
self.view(viewItem);
};
}
// Add navigation member to AppViewModel (for example, app.NavigateToHome());
self["navigateTo" + options.name] = navigator;
};
are you can see, if check to see if the current held view is different to the one I am adding. If it is, then I return null (which is how I get it to hide any views I am not using).
To answer my question further, I needed a way of working out how to direct to the login page if the user was not logged in.
Again in app.viewmodel.js I added a few observable properties:
// UI state
self.user = ko.observable(null);
self.loggedIn = ko.computed(function () {
return self.user() !== null;
});
and in my new login.viewmodel.js I added this function:
// Operations
self.login = function () {
self.loggingIn(true);
dataModel.login({
grant_type: "password",
username: self.userName(),
password: self.password()
}).done(function (data) {
if (data.userName && data.access_token) {
app.navigateToLoggedIn(data.userName, data.access_token, self.rememberMe());
} else {
//self.errors.push("An unknown error occurred.");
}
}).fail(function (jqXHR, textStatus, error) {
dataModel.displayError(jqXHR);
}).always(function () {
self.loggingIn(false);
});
};
the important bit here is the app.naviateToLoggedIn method. This is located in the app.viewmodel.js and looks like this:
// UI operations
self.navigateToLoggedIn = function (userName, accessToken, persistent) {
if (accessToken) {
dataModel.setAccessToken(accessToken, persistent)
}
self.user(new UserViewModel(self, userName, dataModel));
self.navigateToHome();
};
the userViewModel is dead simple:
function UserViewModel(app, name, dataModel) {
var self = this;
// Data
self.name = ko.observable(name);
// Operations
self.logOff = function () {
dataModel.logout().done(function () {
app.navigateToLoggedOff();
}).fail(function (jqHXR) {
dataModel.displayError(jqHXR);
});
};
return self;
}
and finally, to get our initial load right, in the home.viewmodel.js js file, I have this sammy declaration:
Sammy(function () {
this.get('#home', function () {
if (app.loggedIn()) {
app.navigateToHome();
} else {
window.location.hash = "login";
}
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
I'm trying to work with two XML files. I use the second highlighted answer in this thread [1] as a base script.
This is what I got:
jQuery.extend({
getValues: function(url) {
var result = null;
$.ajax({
url: url,
type: 'get',
dataType: 'xml',
async: false,
success: function(data) {
result = data;
}
});
return result;
}
});
var party1 = $.getValues('http://data.riksdagen.se/voteringlista/?rm=2010%2F11&bet=&punkt=parti=M&valkrets=&rost=&iid=&sz=500&utformat=xml&gruppering=bet')
var party2 = $.getValues('http://data.riksdagen.se/voteringlista/?rm=2010%2F11&bet=&punkt=&parti=S&valkrets=&rost=&iid=&sz=500&utformat=xml&gruppering=bet')
$(party1).find('votering').each(function(){
var id = $(this).find("forslagspunkt").text()
partyTwo(id)
//-------------------------------------
//HERE I RUN A FEW SIMPLE IF STATEMENTS
//------------------------------------
})
function partyTwo(id) {
$(party2).find('votering').filter(function() {
return $(this).find("forslagspunkt").text() == id;
}).each(function () {
//-------------------------------------
// AGAIN, A FEW SIMPLE IF STATEMENTS
//------------------------------------
return vote
})
}
This leaves me with two problems:
1) partyTwo(id) returns 'undefined', but works fine if I manually insert an id outside.
2) The whole script runs very slow (+5 sec to load).
Any thoughts?
[1] JQuery - Storing ajax response into global variable
I've inherited JavaScript code where the success callback of an Ajax handler initiates another Ajax call where the success callback may or may not initiate another Ajax call. This leads to deeply nested anonymous functions. Maybe there is a clever programming pattern that avoids the deep-nesting and is more DRY. Also, there is the problem of inner variables myVar1 and myVar2 that are used throughout the functions.
jQuery.extend(Application.Model.prototype, {
process: function() {
var myVar1;
// processing using myVar1;
jQuery.ajax({
url:myurl1,
dataType:'json',
success:function(data) {
var myVar2;
// process data using myVar1, set state of myVar2,
// then send it back
jQuery.ajax({
url:myurl2,
dataType:'json',
success:function(data) {
// do stuff with myVar1 and myVar2
if(!data.ok) {
jQuery.ajax({
url:myurl2,
dataType:'json',
success:mycallback
});
}
else {
mycallback(data);
}
}
});
}
});
}
});
There's no need for all the callbacks to be anonymous and defined inline, you can declare them elsewhere and just use the function name when specifying the callback.
Thanks to the chaining hint and this comment, I have come to the following solution. I have tested it and it works. There are probably some scope issues and you could refactor a general ChainAjax class out of it. But for the time being, this is ok.
jQuery.extend(MyApplication.Model.prototype, {
process: function() {
// private class for executing the Ajax calls
var myAjaxCalls = function(options) {
this.options = options;
this.myVar1 = null;
this.myVar2 =null;
}
jQuery.extend(myAjaxCalls.prototype, {
process1:function(data) {
// processsing using this.myVar1
this.myVar1 = 5;
return true;
},
process2:function(data) {
this.myVar2 = 6;
if(data.ok) {
mycallback(data);
}
else {
return true;
}
},
process3:function(data) {
// Process this.myVar1 and this.myVar
mycallback(data);
return false;
},
chainAjax:function() {
if(this.options.length > 0) {
var opt = this.options.shift();
var that = this;
jQuery.ajax({
url:opt.url,
success:function(data) {
if(that[opt.callback](data)) {
that.chainAjax();
}
}
});
}
}
});
// End private class
var calls = new myAjaxCalls([
{url:'http://localhost/', callback:'process1'},
{url:'http://localhost/', callback:'process2'},
{url:'http://localhost/', callback:'process3'}
]);
calls.chainAjax();
}
});
Update: I found this nice presentation that also deals with useful programming patterns and best practices.
Update 2012: In the meantime there are several libraries for simulating a synchronous flow with asynchronous functions: q, stratified.js and streamline.js
I would suggest creating a little tool called "chain ajax". You give it what you want to happen in what order, and then fire. It will chain ajax on success until all the logic runs out. It will help you stop repeating yourself and just represent the logical model of what you want done vs grunt-coding.
You could do this with Frame.js like this:
jQuery.extend(Application.Model.prototype, {
process: function() {
var myVar1;
// processing using myVar1;
Frame(function(done){
jQuery.ajax({
url:myurl1,
dataType:'json',
success: done
});
});
Frame(function(done, data) {
var myVar2;
// process data using myVar1, set state of myVar2,
// then send it back
jQuery.ajax({
url:myurl2,
dataType:'json',
success: done
});
});
Frame(function(done, data) {
// do stuff with myVar1 and myVar2
if(!data.ok) {
jQuery.ajax({
url:myurl2,
dataType:'json',
success:done
});
}
else {
done(data);
}
});
Frame(function(done, data){
mycallback(data);
});
Frame.start();
}
});