Scope problem: moved function now doesn't find variable - javascript

I am working on a hybrid app (just for my own) with Framework7 and almost everything is working fine. I want to tidy up the code and move some repeating code into a function. But now I get the error
ReferenceError: Can't find variable: resolve
I am just learning JS and had the last couple days learned more about scope and context, but I can't figure out why I get this error. As I understand, as the resolve() callback function is defined in outer scope, I must be able to call it from my custom function?
When I put the app.request() where my getJson() function is, it works. However, the request still works from within getJson() and I can see the data logged, but the resolve function won't work as expected.
routes = [
{
path: '/person/abc/',
async: function (routeTo, routeFrom, resolve, reject) {
var router = this;
var app = router.app;
var jwt = store.get('jwt');
app.preloader.show();
getJson(
jwt,
{document:'person'},
'Test',
'-/-'
);
},
},
];
function getJson(jwt, data, title, note) {
app.request({
url: 'https://example.com/json/',
method: 'GET',
dataType: 'json',
crossDomain: true,
data: { data },
headers: { Authorization: 'Bearer ' + jwt },
success: function(data, textStatus){
if (data.status === 'ALLOW') {
console.log('Data received');
resolve(
{
componentUrl: './pages/person.html',
},
{
context: {
title: title, note: note, person: data.person,
}
}
);
} else {
app.dialog.alert('Error loading data');
}
app.preloader.hide();
return;
},
error: function(xhr, textStatus, errorThrown){
console.log('Error', xhr.response);
app.preloader.hide();
app.dialog.alert('Error on request');
return;
}
});
}

Related

Why does my jQuery getJSON call (within Office.js (Excel js add-in)) return an error?

This javascript within an Office js add-in always fails when making a get request:
function update() {
Excel.run(function (ctx) {
return ctx.sync()
.then(function () {
var company = $('#company').val();
var environment = $('#environment').val();
var apiUrl = "https://localhost:44321/api/Country";
var params = "company=" + company + "&environment=" + environment;
$.getJSON(apiUrl, params)
.done(function (result) {
showNotification(result.length);
})
.fail(function (xhr, status, error) {
//showNotification(error.statusText);
showNotification(error.length);
});
});
}).catch(function (error) {
showNotification(error);
});
}
I can see the json being returned successfully within Fiddler and JSONLint says the json is valid. Here it is:
[{"country":"UK","countryLongName":"United Kingdom","currency":"GBP"}]
I'm running on localhost with https, and have the following AppDomains in my manifest (belt and braces):
<AppDomain>https://localhost:44321/api/Country</AppDomain>
<AppDomain>https://localhost:44321</AppDomain>
<AppDomain>https://localhost</AppDomain>
However, getJSON.fail() is always invoked, with the following parameters:
xhr.responseJSON: undefined
xhr.statusText: "error"
status: "error"
error: ""
Why does getJSON always fail?
Further edit
I've tried this js instead...
$.ajax({
url: apiUrl,
dataType: 'json',
async: false,
data: params,
success: function (data) {
showNotification(data);
},
error: function (msg) {
showNotification('error : ' + msg.d);
}
});
...and now the error function is being invoked with the following parameters:
msg.responseJSON: undefined
msg.status: 0
msg.statusText: "NetworkError"
It's all to do with CORS, I found the solution in rick-strahl's post:
https://weblog.west-wind.com/posts/2016/Sep/26/ASPNET-Core-and-CORS-Gotchas#ApplythePolicy
Specifically,
"UseCors() has to be called before UseMvc()
Make sure you declare the CORS functionality before MVC so the middleware fires before the MVC pipeline gets control and terminates the request."

AngularJS Toaster does not show on callback

I'm using AngularJS Toaster to show toast messages. But i can't seem to get it working with a result from a callback, and i REALLY don't know why.
I've created a service for Toaster:
service.factory('$toasterservice', ['toaster',
function (toaster) {
return {
'show': function (type, title, text) {
return toaster.pop({
type: type,
title: title,
body: text,
showCloseButton: false
});
}
};
}]);
This piece of code is in my controller:
$scope.submit = function (db) {
var params = {
username: db.credentials.username,
password: db.credentials.password,
database: db.name,
hostname: db.host,
port: db.port
};
$dbservice.testConnection(params, function (response) {
if (response.error)
{
console.log(response.error)
}
else
{
console.log('correct!');
}
});
};
whenever i put this line of code:
$toasterservice.show('error', 'TITLE', 'BODY');
within controller level scope, it works perfectly fine.
When ever i try to use it in:
$dbservice.testConnection(params, function (response) {
//HERE $toasterservice.show('error', 'TITLE', 'BODY');
});
it doesn't work, how can i solve this? i can't seem to understand why this is happening.
$dbservice :
service.factory('$dbservice', ['$constants',
function ($constants) {
return {
'testConnection': function (params, callback) {
$.ajax({
url: $constants.URL_TEST_CONNECTION,
dataType: 'json',
type: 'post',
contentType: 'application/x-www-form-urlencoded',
data: params,
success: callback,
error: callback
});
}
};
}]);
The problem is using $.ajax and you should switch to using $http.
Any events that occur outside of angular core that change the scope need to notify angular so it can run digest in view.
You can call $scope.$apply() in the callback of your $.ajax to run digest

how to wait for model initialize before starting view

I am making a basic trello clone. Except instead of signing in, projects have a slug(i.e. 'www.example.com/1d754b6c')
If a user visits the root, a new slug is created on the back end. The user is then routed to www..com/1d754b6c, which sends another ajax call to get the projects ID. A view is then started. However my view is getting started before the slug -> ID ajax call is finished. Whats the best way to fix this? (I currently have a setTimeout as a temporary patch, I know that is not a good way to accomplish this)
router.js
Buckets.Routers.PageRouter = Backbone.Router.extend({
routes: {
'': 'newProject',
':token': 'displayProject'
},
newProject: function () {
new Buckets.Models.Project({});
},
displayProject: function (token) {
var that = this;
var project = new Buckets.Models.Project({token: token});
setTimeout(function(){
new Buckets.Views.showProject({
model: project
});
}, 500);
}
});
project.js
Buckets.Models.Project = Backbone.Model.extend({
url: function() {
return Buckets.BASE_URL + '/api/projects/' + (this.id)
},
initialize: function(options) {
var that = this;
if (options && options.token) {
that.token = options.token
$.ajax({
url: Buckets.BASE_URL + '/' + that.token,
dataType: 'json',
success: function( data, status ){
that.id = data;
},
error: function(xhr, textStatus, err) {
console.log(xhr);
}
});
} else {
$.ajax({
url: Buckets.BASE_URL + '/api/projects/new',
dataType: 'json',
success: function( data, status ){
that.token = data.token;
that.id = data.id;
Buckets.Routers.router.navigate('/' + that.token, true);
},
error: function(xhr, textStatus, err) {
console.log(xhr);
}
});
}
return this;
},
});
Try to use Backbone.Model.sync. .sync() returns a Promise so you can take full advantage of the Deffered/Promises standard.
When I want to pass variable urls to the fetch I override the model.fetch(). For your implemenation I'd first scrap the $.ajax in initialize() and override fetch like this
Buckets.Models.Project = Backbone.Model.extend({
fetch: function(options) {
var that = this;
if (options && options.token) {
this.url = Buckets.BASE_URL + '/' + that.token;
else
this.url = Buckets.BASE_URL + '/api/projects/new';
return Backbone.Model.prototype.fetch.call(this, options);
}
.fetch() eventually returns the result of sync() which is a Promise. That means that in your Router you'd do this:
displayProject: function (token) {
var that = this;
var project = new Buckets.Models.Project();
$.when(project.fetch({token: token})
// deffered.done() replaces the success callback in your $.ajax
.done(function() {
project.id = data;
new Buckets.Views.showProject({ model: project });
})
// deffered.fail() replaces the error callback in your $.ajax
.fail(function( jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
});
}
And for completeness, you'd reweite newProject() similarly,
newProject: function () {
var project = new Buckets.Models.Project();
$.when(project.fetch({token: token})
.done(function( data, textStatus, jqXHR ) {
project.token = data.token;
project.id = data.id;
new Buckets.Views.showProject({ model: project });
})
.fail(function( jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
});
}
Try it out. I started using this method of fetching when it was recommended to me by a major contributor to MarionetteJS, one of the premier opinionated Backbone frameworks. This method is easy to maintain and very responsive.

Variable Scope in Nested AJAX Calls

I have a set of custom user data that I want to make an ajax call to, and in the event that there is no user data, make another ajax call to retrieve a default set of data, and then execute a function after parsing the data. Here's an example:
var oData = [],
exampleUrl = 'example.php';
$.ajax({
url: exampleUrl + '?query=getUserData',
contentType: 'application/json;odata=verbose',
headers: {
'accept': 'application/json;odata=verbose'
},
success : function(data, request){
// Request succeeded
// Check the results
if(data.length){
// There are custom user results!
// Parse the results
oData = data;
}
else{
// There were no custom user results...
// Run another query to retrieve default values
$.ajax({
url: examplUrl + '?query=getDefaultData',
contentType: 'application/json;odata=verbose',
headers: {
'accept': 'application/json;odata=verbose'
},
success : function(data, request){
// Request succeeded
// Check the results
if(data.length){
// There was some default data!
// Parse the results
oData = data;
}
else{
// No data was found...
// Attempt to be helpful
console.log('No Default data was found!');
}
},
error : function(data, request){
// There was an error with the request
// Attempt to be helpful
console.log('Error retrieving data:');
console.log(data);
console.log(request);
}
});
}
},
error : function(data, request){
// There was an error with the request
// Attempt to be helpful
console.log('Error retrieving Custom User data:');
console.log(data);
console.log(request);
},
complete : function(){
// Do something with the data
index.displayData(oData);
}
});
The issue is that if the second ajax call is run, oData doesn't contain any data at all when it's passed to index.displayData(). I'm guessing it has something to do with the asyncronous nature of ajax calls, but shouldn't 'complete' run after everything inside of 'success' runs?
I also know I probably shouldn't be using the ajax "Pyramid of Doom" and should be using promises, but I've tried them and keep getting the same results.
Thank you for your assistance!
As pointed out by Violent Crayon, you could try calling "complete" yourself instead of relying on JQuery's implicit control flow:
function getData(exampleUrl, onComplete){
$.ajax({
success : function(data, request){
if(data.length){
onConplete(data);
}else{
$.ajax({
success : function(data, request){
if(data.length){
onComplete(data);
}else{
console.log('No Default data was found!');
}
},
error : function(data, request){
console.log('Error retrieving data:');
}
});
}
},
error : function(data, request){
console.log('Error retrieving Custom User data:');
}
});
}
var oData = [];
getData('example.php', function(data){
oData = data;
index.displayData(oData);
}
BTW, note how you can have your async functions receive their own return and error callbacks. This can help reduce the pyramid of doom problem without needing to use promises and without needing to hardcode the return callback.
By working with promises, you can avoid the need to pass a callback into your function, and by defining a utility function you can avoid repetition of code.
//reusable utility function, which returns either a resolved or a rejected promise
function fetchData(queryString, cache) {
return $.ajax({
url: 'example.php',
data: { query: queryString },
type: 'JSON',//assumed
cache: cache,
contentType: 'application/json;odata=verbose',
headers: { 'accept': 'application/json;odata=verbose' }
}).then(function(data, textStatus, jqXHR) {
if (data && data.length) {
return data;
} else {
return $.Deferred().reject(jqXHR, 'no data returned').promise();//emulate a jQuery ajax failure
}
});
}
This allows promise methods to be used for a control structure, which :
is concise
uses chaining, not nesting
gives meaningful error messages.
//control structure
fetchData('getUserData', false).then(null, function(jqXHR, textStatus) {
console.log('Error retrieving Custom User data: ' + textStatus);
return fetchData('getDefaultData', true);
}).then(index.displayData, function(jqXHR, textStatus) {
console.log('Error retrieving default data: ' + textStatus);
});
Notes :
the null in .then(null, function(){...}) allows a successful response to drop straight through to the second .then(index.displayData, ...)
default data is cached while the user data is not. This is not necessary to make things work but will be faster next time the default data is required.
in the world of promises, this or something similar is the way to go.

make this jQuery Ajax async=true with closure

I wrote this object and I run it into the page I have:
var dataPage = {
getData: function() {
return $.ajax({
url: '/my_url/',
data: {
product: 'some_product',
state: 'some_state'
},
type: 'POST',
async: true,
success: function (data) {
//console.log(data);
dataPage.results = data;
},
error: function (xhr, ajaxOptions, thrownError) {
alert('Error:' + xhr.status);
alert(thrownError);
}
});
}
,returnData: function(){
var xhr = this.getData();
//console.log(xhr);
xhr.done(function() {
//console.log(xhr.responseText);
this.results = xhr.responseText;
$('#JSON').html(this.results);
});
}
}
var results = dataPage.returnData()
console.log(results)
It works perfectly as the attribute async set to false: the data is loaded into the div with id="JSON". The two console.log()s return the data and everything works fine.
Now I would like to switch to async: true but I don't know how to apply closure to make the function pass the resulting data correctly, avoiding the xhr.responseText to be undefined because of the asynchronous nature of the getData() call.
EDITED
I edited the code above, added the returnData() function, but the last console.log() still return undefined. Adding the .done() didn't solve the problem of taking out to the global scope the results if async: true...
Use the done() callback:
var jqXHR = dataPage.getData();
jqXHR.done(function(result) {
$('#JSON').html(result);
});

Categories