How to make Dajax callback into scoped object - ajax

I cant seem to find a way to make django-dajaxice have its callback inside same scoped object from which made the initial call.
MyViewport = Ext.extend(MyViewportUi, {
initComponent: function() {
MyViewport.superclass.initComponent.call(this);
},
LoadRecordsCallback: function(data){
if(data!='DAJAXICE_EXCEPTION')
{ alert(data); }
else
{ alert('DAJAXICE_EXCEPTION'); }
},
LoadRecords: function(){
Dajaxice.Console.GetUserRecords(this.LoadRecordsCallback);
}
});
var blah = new MyViewport();
blah.LoadRecords();
I'm on django, and like the calling syntax to django-dajaxice. I'm using Extjs 3.2 and tried passing a Ext.createCallback but Dajax's returning eval seems to only want a string for the callback.

BozoJoe, this should work.
MyViewport = Ext.extend(MyViewportUi, {
initComponent: function() {
MyViewport.superclass.initComponent.call(this);
},
LoadRecordsCallback: function(data){
if(data!='DAJAXICE_EXCEPTION')
{ alert(data); }
else
{ alert('DAJAXICE_EXCEPTION'); }
},
LoadRecords: function(){
Dajaxice.Console.GetUserRecords('blah.LoadRecordsCallback');
}
});
var blah = new MyViewport();
blah.LoadRecords();

I'm not familiar with django at all, but I think I understand the problem.
It seems that the API mandates that you pass a string which will be eval'd as a function call, so you must pass the name of the function, rather than the function itself.
This in turn means that it must be a name that is meaningful at the window scope - either a function defined outside of an Ext class (e.g. "myGlobalFunction"), or a member function of an Ext class that is accessible as a variable (e.g. "window.blah.LoadRecordsCallback")

Related

Pass DataTable reference to the callback function on load

My current code is:
var CommissionLogs = $("#CommissionLogs").DataTable({
ajax: {
url: ajaxurl + '?action=pos&post_action=get_commissions'
},
'initComplete': function (settings, json){
//possible to access 'this'
this.api().columns(1);
}
});
I improved the code above as below with help :
var CommissionLogs = $("#CommissionLogs").DataTable({
ajax: {
url: ajaxurl + '?action=pos&post_action=get_commissions'
},
'initComplete': function(settings, json){
callbackFunction(settings);
}
});
function callbackFunction(settings){
var api = new $.fn.dataTable.Api( settings );
// api is accessible here.
}
Update :
Now I can access api from callback function. But I want use same callback with load() as below code.
CommissionLogs.ajax.url( newAjaxURL ).load( callbackFunction(), true);
But settings param is not accessible in load function.
I can clear and destroy datatable and re initialize always. But what will be the right way.
I think you need settings:
https://datatables.net/reference/type/DataTables.Settings
$('#example').dataTable( {
"initComplete": function(settings, json) {
myFunction(settings);
}
});
function myFunction(settings){
var api = new $.fn.dataTable.Api( settings );
// Output the data for the visible rows to the browser's console
// You might do something more useful with it!
console.log( api.rows( {page:'current'} ).data() );
}
Other option is re-use your var CommissionLogs variable throughout the code without using this, I recommend strongly this last option.
The dataTable.ajax.url().load() has not access to settings.
So can not call a callback function with settings.
But possible to use callback function without settings.
So here is an alternative way to use settings.
CommissionLogs.clear();// clear the table
CommissionLogs.destroy();// destroy the table
CommissionLogs = $("#CommissionLogs").DataTable({
ajax: {
url: newAjaxUrl
},
'initComplete': function (settings, json){
callbackDatatableFunciton(settings);
}
});

How to set mandatory route parameters

I want to make a route with has a mandatory parameter. If not, it should fall into
$urlRouterProvider.otherwise("/home");
Current route:
function router($stateProvider) {
$stateProvider.state("settings", {
url: "^/settings/{id:int}",
views: {
main: {
controller: "SettingsController",
templateUrl: "settings.html"
}
}
});
}
Currently both the routes below are valid:
http://myapp/settings //Should be invalid route
http://myapp/settings/123
Any ideas?
Use a state change start listener to check if params were passed:
$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState, fromParams) {
if(toState.name==="settings")
{
event.preventDefault(); //stop state change
if (toParams.id===undefined)
$state.go("home");
else
$state.go(toState, toParams);
}
});
The following solution is valid for ui-router 1.0.0:
.config(($stateProvider, $transitionsProvider) => {
//Define state
$stateProvider.state('verifyEmail', {
parent: 'portal',
url: '/email/verify/:token/:optional',
component: 'verifyEmail',
params: {
token: {
type: 'string',
},
optional: {
value: null,
squash: true,
},
},
});
//Transition hooks
$transitionsProvider.onBefore({
to: 'verifyEmail',
}, transition => {
//Get params
const params = transition.params();
//Must have token param
if (!params.token) {
return transition.router.stateService.target('error', {
type: 'page-not-found',
});
}
});
})
The above will make the :token parameter mandatory and the :optional parameter optional. If you try to browse to the page without the token parameter it will fail the transition and redirect to your error page. If you omit the :optional parameter however, it will use the default value (null).
Remember to use squash: true on the trailing optional parameters, because otherwise you'll also get a 404 if you omit the trailing / in the URL.
Note: the hook is required, because if you browse to email/verify/ with a trailing slash, ui-router will think the token parameter is an empty string. So you need the additional handling in the transition hook to capture those cases.
In my app I had to make required parameters for a lot of routes. So I needed a reusable and DRY way to do it.
I define a constants area in my app to access global code. I use for other things as well.
I run this notFoundHandler at app config time. This is setting up a router state for handling errors. It is setting the otherwise route to this error route. You could define a different route for when a required parameter is missing, but for us this was defined as being the same as a 404 experience.
Now at app run time I also define a stateChangeErrorHandler which will look for a rejected route resolve with the 'required-param' string.
angular.module('app')
.constant('constants', constants)
.config(notFoundHandler)
.run(stateChangeErrorHandler);
// use for a route resolve when a param is required
function requiredParam(paramName) {
return ['$stateParams', '$q', function($stateParams, $q) {
// note this is just a truthy check. if you have a required param that could be 0 or false then additional logic would be necessary here
if (!$stateParams[paramName]) {
// $q.reject will trigger the $stateChangeError
return $q.reject('required-param');
}
}];
}
var constants = {
requiredParam: requiredParam,
// define other constants or globals here that are used by your app
};
// define an error state, and redirect to it if no other route matches
notFoundHandler.$inject = ['$stateProvider', '$urlRouterProvider'];
function notFoundHandler($stateProvider, $urlRouterProvider) {
$stateProvider
//abstract state so that we can hold all our ingredient stuff here
.state('404', {
url: '/page-not-found',
views: {
'': {
templateUrl: "/app/error/error.tpl.html",
}
},
resolve: {
$title: function () { return 'Page Not Found'; }
}
});
// redirect to 404 if no route found
$urlRouterProvider.otherwise('/page-not-found');
}
// if an error happens in changing state go to the 404 page
stateChangeErrorHandler.$inject = ['$rootScope', '$state'];
function stateChangeErrorHandler($rootScope, $state) {
$rootScope.$on('$stateChangeError', function(evt, toState, toParams, fromState, fromParams, error) {
if (error && error === 'required-param') {
// need location: 'replace' here or back button won't work on error page
$state.go('404', null, {
location: 'replace'
});
}
});
}
Now, elsewhere in the app, when I have a route defined, I can make it have a required parameter with this route resolve:
angular.module('app')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider', 'constants'];
function routeConfig($stateProvider, constants) {
$stateProvider.state('app.myobject.edit', {
url: "/:id/edit",
views: {
'': {
template: 'sometemplate.html',
controller: 'SomeController',
controllerAs: 'vm',
}
},
resolve: {
$title: function() { return 'Edit MyObject'; },
// this makes the id param required
requiredParam: constants.requiredParam('id')
}
});
}
I'd like to point out that there shouldn't be any problem with accessing the /settings path, since it doesn't correspond to any state, unless you've used inherited states (see below).
The actual issue should happen when accessing the /settings/ path, because it will assign the empty string ("") to the id parameter.
If you didn't use inherited states
Here's a solution in plunker for the following problem:
accessing the /state_name/ path, when there's a state with url /state_name/:id
Solution explanation
It works through the onBefore hook (UI router 1.x or above) of the Transition service, which prevents transitioning to states with missing required parameters.
In order to declare which parameters are required for a state, I use the data hash like this:
.state('settings', {
url: '/settings/:id',
data: {
requiredParams: ['id']
}
});
Then in app.run I add the onBefore hook:
transitionService.onBefore({}, function(transition) {
var toState = transition.to();
var params = transition.params();
var requiredParams = (toState.data||{}).requiredParams || [];
var $state = transition.router.stateService;
var missingParams = requiredParams.filter(function(paramName) {
return !params[paramName];
});
if (missingParams.length) {
/* returning a target state from a hook
issues a transition redirect to that state */
return $state.target("home", {alert: "Missing params: " + missingParams});
}
});
If you used inherited states
You could implement the same logic via inherited states:
function router($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings'
})
.state('settings.show", {
url: '/:id'
});
}
then you'd need to add the abstract property to the parent declaration, in order to make /settings path inaccessible.
Solution explanation
Here's what the documentation says about the abstract states:
An abstract state can never be directly activated. Use an abstract state to provide inherited properties (url, resolve, data, etc) to children states.
The solution:
function router($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings',
abstract: true
})
.state('settings.show", {
url: '/:id'
});
}
Note: that this only solves the issue with /settings path and you still need to use the onBefore hook solution in order to also limit the access to /settings/.
it is not very well documented, but you can have required and optional parameters, and also parameters with default values.
Here is how you can set required params:
function router($stateProvider) {
$stateProvider.state("settings", {
url: "^/settings/{id:int}",
params: {
id: {}
},
views: {
main: {
controller: "SettingsController",
templateUrl: "settings.html"
}
}
});
}
I never used params with curly brackets, just with the semicolon, like this url: "^/settings/:id", but from what I read, those are equivalent.
For other types of parameters, please see the other half of my answer here: AngularJS UI Router - change url without reloading state
Please note that when I added that answer, I had to build ui-router from source, but I read that functionality has been added to the official release by now.

Backbone fetching data using a callback cursor

How would I use Backbones fetch to deal with callback results that contain a cursor? I'm going to use this simple example of a book that is fetching pages.
var Book = Backbone.Collection.extend({
model: Page,
recursiveFetch: function(cursor) {
this.fetch({
url: 'book/pages',
data: {
cursor: {cursor here};
},
success: function(response) {
if (response.cursor) {
this.recursiveFetch(response.cursor);
}
}
});
}
})
I need to be able to use fetch to keep fetching until the response doesn't contain a cursor. It should keep adding page models, but not replacing and overwriting them. It needs to do something like the example above, though I'm not sure of the best way to implement it.
I think that all you need to do is add in a {remove: false} into your fetch options. Its also worth mentioning that the this context of your success function may not be the collection, so you might want to pass it into the success function as a parameter. The end result would be:
recursiveFetch: function(cursor) {
this.fetch({
remove:false, // prevents removal of existing models
url: 'book/pages',
success: function(collection, response) {
if (response.cursor) {
collection.recursiveFetch(response.cursor);
}
}
});
}
The fix is very simple: add cursor to the parameters only when it's present. In other cases (i.e. the first time) make a normal request with the rest of the parameters.
var CursorCollection = Backbone.Collection.extend({
fetchAll: function(cursor) {
var params = {
// if you have some other request parameters...
};
// if we have a cursor from the previous call, add it to the parameters
if (cursor) { params.cursor = cursor; }
this.fetch({
remove: false,
data: params,
success: function(collection, response) {
if (response.cursor) {
return collection.fetchAll(response.cursor);
}
}
});
}
});
Then the first time you call it collection.fetchAll() and it recurses until it gets a response without a cursor.
Note, that the remove: false parameter is very important to accumulate the results as pointed out by #dcarson.

Attach the events ajaxStart() and ajaxStop() only to the current backbone view

I'm using backbone for an app that I'm building. In this app, I have a master view which render a template with 2 other views inside. One header view and another one with some content. The header view is just used to interact with the content view and has specific functions too.
In the header template and content template I have the same piece of code, an hidden DIV with a loader image that is displayed when an ajax call is made. The problem I have is that when I load the app for the first time (or when I refresh the content view), the content view is loading some data from an ajax request, but the loader is showing up in both the header and the content template (like if the ajaxStart() was a global event not attached to the view.
Here is the content view setup:
App.View.Content = Backbone.View.extend({
type:'test',
template: twig({
href: '/js/app/Template/Content.html.twig',
async: false
}),
block:{
test:twig({
href: '/js/app/Template/block/test.html.twig',
async: false
})
},
list:[],
showLoader: function(el){
console.log('loader: ', $('#ajax_loader', el));
$('#ajax_loader', el).show();
console.log('Ajax request started...');
},
hideLoader: function(el){
$('#ajax_loader', el).hide();
console.log('Ajax request ended...');
},
initialize: function(params)
{
this.el = params.el;
this.type = params.type || this.type;
var self = this;
this.el
.ajaxStart(function(){self.showLoader(self.el);})
.ajaxStop(function(){self.hideLoader(self.el);});
this.render(function(){
self.list = new App.Collection.ListCollection();
self.refresh(1, 10);
});
},
refresh:function(page, limit)
{
var self = this;
console.log('Refreshing...');
$('#id-list-content').fadeOut('fast', function(){
$(this).html('');
});
this.list.type = this.type;
this.list.page = page || 1;
this.list.limit = limit || 10;
this.list.fetch({
success: function(data){
//console.log(data.toJSON());
$.each(data.toJSON(), function(){
//console.log(this.type);
var tpl_block = self.block[this.type];
if (tpl_block != undefined) {
var block = tpl_block.render({
test: this
});
$(block).appendTo('#id-list-content');
}
});
$('#id-list-content').fadeIn('fast');
}
});
},
render: function(callback)
{
console.log('Rendering list...');
this.el.html(this.template.render({
}));
if (undefined != callback) {
callback();
}
}
});
As you can see I'm using an ugly piece of code to attach the ajaxStart / ajaxStop event:
this.el
.ajaxStart(function(){self.showLoader(self.el);})
.ajaxStop(function(){self.hideLoader(self.el);});
I use to have it like this:
this.el
.ajaxStart(self.showLoader())
.ajaxStop(self.hideLoader());
But for whatever reason that still undefined on my end, this.el was not defined in the showLoader() and hideLoader().
I was thinking that ajaxStart() and ajaxStop() was attached to the this.el DOM, and that only this view would be able to listen to it. But my headerView which has exactly the same setup (except for the twig template loaded) apparently receive the event and show the loader.
To be sure of this behavior, I've commented out the showLoader() in the content view, and the loader still show up in the header view.
I don't know what I'm doing wrong :(
EDIT (after answer from "mu is too short"):
my content view does now looks like this:
showLoader: function(){
//this.$('#ajax_loader').show();
console.log('Ajax request started...');
},
hideLoader: function(){
this.$('#ajax_loader').hide();
console.log('Ajax request ended...');
},
initialize: function(params)
{
var self = this;
console.log(this.el);
_.bindAll(this, 'showLoader', 'hideLoader');
this.$el
.ajaxStart(this.showLoader)
.ajaxStop(this.hideLoader);
this.render(function(){
self.list = new App.Collection.List();
self.refresh(1, 10);
});
},
...
render: function(callback)
{
console.log('Rendering post by page...');
this.$el.html(this.template.render({
}));
if (undefined != callback) {
callback();
}
}
and my header view:
...
showLoader: function(){
this.$('#ajax_loader').show();
//console.log('Ajax request started...');
},
hideLoader: function(el){
this.$('#ajax_loader').hide();
console.log('Ajax request ended...');
},
initialize: function(params)
{
var self = this;
_.bindAll(this, 'showLoader', 'hideLoader');
this.$el
.ajaxStart(this.showLoader)
.ajaxStop(this.hideLoader);
this.models.Link = new App.Model.Link();
this.render();
},
render: function(callback)
{
this.$el.html(this.template.render({
data: []
}));
if (undefined != callback) {
callback();
}
}
...
But the loader still showing up in the header view template
PS: this.showLoader() was not a typo as I wanted to call the function within the current backbone view.
The context (AKA this) for a JavaScript function depends on how the function is called, not on the context in which the function is defined. Given something like this:
var f = o.m;
f();
When you call o.m through the plain function f, this inside o.m will usually be the global context (window in a browser). You can also use apply and call to choose a different this so this:
f.call(o);
would make this the o that you'd expect it to be. I should mention that you can force your choice of this using bind in most JavaScript environments but I don't want to get too sidetracked.
The point is that this:
this.el
.ajaxStart(this.showLoader)
.ajaxStop(this.hideLoader);
isn't enough to ensure that showLoader and hideLoader will run in the right context; I'm also assuming that the parentheses you had at the end of showLoader and hideLoader were just typos.
The most common way to force a context in a Backbone application is to use _.bindAll in your initialize:
initialize: function(params) {
_.bindAll(this, 'showLoader', 'hideLoader');
//...
That essentially replaces this.showLoader and this.hideLoader with something that's, more or less, equivalent to your wrappers:
function() { self.showLoader(self.el) }
Once you have that _.bindAll in place, this:
this.el
.ajaxStart(this.showLoader)
.ajaxStop(this.hideLoader);
will work fine.
BTW, you don't need to do this:
this.el = params.el;
in your initialize, Backbone does that for you:
constructor / initialize new View([options])
[...] There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.
And you don't need to do things like this:
$('#ajax_loader', el).show();
either, Backbone gives you a $ method in your view that does the same thing without hiding the el at the end of the argument list; doing it like this:
this.$('#ajax_loader').show();
is more idiomatic in Backbone.
Furthermore, this.el won't necessarily be a jQuery object so don't do this:
this.el.html(this.template.render({ ... }));
in your render, use the cached this.$el instead:
this.$el.html(this.template.render({ ... }));

multiple xhr.get s with dojo

how do I do two xhr.gets one after the other using dojo ?
I have ....
require(["dojo/_base/xhr", "dojo/dom", "dojo/domReady!"],
function(xhr, dom) {
// Using xhr.get, as very little information is being sent
xhr.get({
// The URL of the request
url: "inc/etl2json.php?item=Execs",
// The success callback with result from server
load: function(execContent) {
dom.byId("Execs").innerHTML = execContent;
},
// The error handler
error: function() {
// Do nothing -- keep old content there
}
});
});
I would like to do another xhr.get to "inc/etl2json.php?item=Execs" and assign it to dom.byId("Elapsed").innerHTML = elapsedContent;
just call again xhr.get() inside the load function, well that if the content is supposed to change, else you could just use the same data retrieved the first time:
xhr.get({
load:function(data){
//use the first data you retrieved
xhr.get({
load: function(data2){
//do what you like with the nuew data
}
});
}
});
Although nesting is a straightforward solution it almost always leads to unreadable code, so I would do the same as #Ricardo did, but use the advantage of Dojo's Deferred (+ here) and employ chaining:
var requestUrl = "inc/etl2json.php?item=Execs";
xhr.get({ url: requestUrl})
.then(function(results) {
dom.byId("execs").innerHTML = results;
})
.then(function(results) {
return xhr.get({ url: requestUrl});
})
.then(function(results) {
dom.byId("elapsed").innerHTML = results;
})
See it in action at jsFiddle: http://jsfiddle.net/phusick/73X88/
I think you should add another xhr call for the elapsedContent. I don't see any relation between the two calls so you should make them separate. Nesting one in another is not necessary.
just add
xhr.get({
// The URL of the request
url: "inc/etl2json.php?item=Execs",
// The success callback with result from server
load: function(elapsedContent) {
dom.byId("Elapsed").innerHTML = elapsedContent;
},
// The error handler
error: function() {
// Do nothing -- keep old content there
}
});

Resources