ng-grid not binding with latest data after async XMLHttpRequest's response - javascript

I have a AngularJS web application, I'm trying to upload a file to a server and while the upload is complete, I have to update ng-grid with the last uploaded file's entry. The following is my grid html,
<div class="gridholder" data-ng-grid="viewmodel.gridOptions">
</div>
The following is my controller logic.
vm.gridOptions = {
data: 'gridData',
enableColumnResize: true,
enablePaging: true,
columnDefs: [
{ field: 'FileName', displayName: 'File Name', width: 250 }
{ field: 'UploadedDate', displayName: 'Uploaded Date'}
],
multiSelect: false,
enableSorting: true,
showFooter: true,
};
The requirement is that I show the progress of file upload and the entire application to be responsive when upload is in progress, I have achieved this but my ng-grid not is updating in a particular scenario.
If I remain in the same page until the file is uploaded and the response comes, the grid is refreshing but when I move to another page of my application and come back to the file upload page, and the response comes after, my grid is not getting refreshed.
This is my file upload js code,
var data = new FormData();
data.append('file', file);
var xhrRequest = Factory.uploadFileRequest('UploadFile');
xhrRequest.upload.addEventListener("progress", progressHandler, false);
xhrRequest.onreadystatechange = function (e) {
};
xhrRequest.onload = function (e) {
if (JSON.parse(e.currentTarget.responseText).Success == true) {
$timeout(function () {
$scope.LoadGrid();
//showing success message here
}, 2000);
}
else
{
//showing error message here
}
};
xhrRequest.onerror = function (e) {
//showing error message here
};
xhrRequest.send(data);
$scope.LoadGrid = function () {
Factory.callGet("Files").then(function (d) {
$scope.gridData = d.data;
}
$scope.totalItems = $scope.gridData.length;
}, function error(err) {
//Error Message
});
}
gridData is my data-ng-grid value. I'm calling my LoadGrid method inside a $timeout already but still the grid is not refreshing with latest data. Any help would be much appreciated.

Possible Problem
You implemented upload logic inside the controller. When you switch to another view, angularjs destroys your controller and therefore no one listens on file upload response.
Possible solution:
1) Use a service (or Factory) kind of singleton to manage upload process there.
For example MyService.upload(data).then(function (response) {/**/});
2) By default MyService.upload(data) returns a promise on a regular basis but also stores the result inside the Service, for example, upload_results:
app.service('MyService',['$q',function ($q) {
var self = this;
var upload_results = [];
self.upload = function (_data) {
return // <YOUR_PROMISE>
.then(function (response) {
upload_results.push({
id: new Date().getTime(),
data: response.data
})
}
, function (error) {
console.error(error);
return $q.reject(error);
});
};
self.getResults() = function(){
return upload_results;
}
self.resetResults() = function(){
upload_results = [];
}
}
When you initialize the controller on start or go back to the previous controller, you ask the service if it has something for you:
var results = MyService.getResults();
if(results.length > 0){
$scope.gridData = results[0].data; // or use timestamp to manage it
MyService.resetResults();
}
Hope it will give you some insight,

Related

How to disable toast message for Newsletter module on Odoo (v14) website

For the website I develop, I use Newsletter module to create a mailing list. It's quite enough for basic needs. When you insert an e-mail and click to subscribe button, it shows (replace) "Thanks" message and hide the "Subscribe" button. It also shows a toast message: "Thanks for subscribing!" on the top right side of the page.
I don't want to show toast messages for newsletter subscriptions. Unfortunately, there is no option to enable/disable it.
If I disable/remove that part below from website_mass_mailing.js file it doesn't show the toast message.
self.displayNotification({
type: toastType,
title: toastType === 'success' ? _t('Success') : _t('Error'),
message: result.toast_content,
sticky: true,
});
I don't want to touch this file (website_mass_mailing.js) but instead, inherit it and remove that part but I couldn't succeed. Any suggestion on how to do it?
You should create a new module which depends on website_mass_mailing and extends mass_mailing.website_integration via a dedicated javascript module.
For example:
odoo.define('my_module.mass_mailing_website_integration', function (require) {
var website_integration = require('mass_mailing.website_integration');
website_integration.extend({
// Your Logic Here
});
}
Find mass_mailing method who's calling displayNotification and override it.
Unfortunately i see no alternative to copy-pasting it entirely from source and then removing desired behaviours.
Do not forget to include your javascript in web_assets template.
After suggestions of #icra I've tried to figure it out and here is the code that worked for me.
Thanks to Cybrosys Techno Solutions Pvt.Ltd as well to achieve the solution.
Here is the code:
odoo.define('your_module.name', function (require){
var publicWidget = require('web.public.widget');
var _t = core._t;
publicWidget.registry.subscribe.include({
_onSubscribeClick: async function () {
var self = this;
var $email = this.$(".js_subscribe_email:visible");
if ($email.length && !$email.val().match(/.+#.+/)) {
this.$target.addClass('o_has_error').find('.form-control').addClass('is-invalid');
return false;
}
this.$target.removeClass('o_has_error').find('.form-control').removeClass('is-invalid');
let tokenObj = null;
if (this._recaptcha) {
tokenObj = await this._recaptcha.getToken('website_mass_mailing_subscribe');
if (tokenObj.error) {
self.displayNotification({
type: 'danger',
title: _t("Error"),
message: tokenObj.error,
sticky: true,
});
return false;
}
}
const params = {
'list_id': this.$target.data('list-id'),
'email': $email.length ? $email.val() : false,
};
if (this._recaptcha) {
params['recaptcha_token_response'] = tokenObj.token;
}
this._rpc({
route: '/website_mass_mailing/subscribe',
params: params,
}).then(function (result) {
let toastType = result.toast_type;
if (toastType === 'success') {
self.$(".js_subscribe_btn").addClass('d-none');
self.$(".js_subscribed_btn").removeClass('d-none');
self.$('input.js_subscribe_email').prop('disabled', !!result);
if (self.$popup.length) {
self.$popup.modal('hide');
}
}
// make the changes you need accordingly or comment out the below code.
self.displayNotification({
type: toastType,
title: toastType === 'success' ? _t('Success') : _t('Error'),
message: result.toast_content,
sticky: true,
});
});
},
})
})

How to wait for promise to complete with returned value using angularjs

I’m having an issue with my project. In my angularjs controller a function is being executed and then my function to make a call to my database to update a record is executing without waiting for the first function to complete and therefore sending over an undefined result variable.
Below you can find my code snippets with my attempts so far.
Submit button function:
$scope.submitNewStarters = function () {
// result is returning as undefined <<<<< Issue
var result = $scope.sendNewStarterDetailsToApi();
$scope.updateArchivedImportFlag(result);
};
Controller function handling the logic:
$scope.sendNewStarterDetailsToApi = function () {
swal({
title: "Confirmation",
text: "Are you sure you want to import the new starter details?",
icon: "info",
dangerMode: true,
buttons: ["No", "Yes"]
}).then(function (approve) {
if (approve) {
// Get each of the new starter details that have been set to true for import.
var newStartsToImport = $scope.tableParams.data.filter(x => x.imported == true);
for (let i = 0; i < newStartsToImport.length; i++) {
// Parses the current new starter object into a stringified object to be sent to the api.
$scope.newStartsToImport = $scope.createApiObject(newStartsToImport[i]);
// A check to ensure that nothing has went wrong and that the stringify object has worked.
if ($scope.newStartsToImport !== "") {
apiFactory.postNewStarterDetailsToApi($scope.newStartsToImport).then(function (response) {
var isSuccessful = response.data.d.WasSuccessful;
if (isSuccessful)
toastr.success("New starter details successfully sent to API.", "Success!");
else {
var errorMessage = response.data.d.ErrorMessage;
toastr.error("New starter details were unsuccessfully sent to API. Please try again. \n" + errorMessage, "Error!");
}
});
}
else {
toastr("An error has occurred when attempting to create the data object to be sent to API. The process has stopped!", "Error!");
break;
}
}
return newStartsToImport;
}
else
toastr.info("No new starter details were sent to API", "Information!");
});
};
Factory function for API call:
postNewStarterDetailsToApi: function (data) {
return $http({
url: "https://www.example.com/services/service.svc/Import",
method: "POST",
data: data,
headers: {
'Content-Type': 'application/json; charset=utf-8',
}
}).then(function successCallbwack(response) {
// this callback will be called asynchronously
// when the response is available
return response;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
console.log('An error has occured during the function call postNewStarterDetailsToApi(): ', response);
});
}
So with the concept of promises how am I able to execute the sendNewStarterDetailsToApi function, wait for it to complete and then return the populated array? Once the populated array (result) is returned then execute the updateArchivedImportFlag function.
Below I've added an illustration of what I'd like to achieve:
The approach I am using is , save all the promises in an array .
Use any promise library or es6 Promise, and use .all function to wait for all promises to execute
The syntax i wrote is not totally correct. Since you are using angular js , you can use $q.all
$scope.sendNewStarterDetailsToApi = function () {
swal({
title: "Confirmation",
text: "Are you sure you want to import the new starter details?",
icon: "info",
dangerMode: true,
buttons: ["No", "Yes"]
}).then(function (approve) {
var res = [];
if (approve) {
// Get each of the new starter details that have been set to true for import.
var newStartsToImport = $scope.tableParams.data.filter(x => x.imported == true);
for (let i = 0; i < newStartsToImport.length; i++) {
// Parses the current new starter object into a stringified object to be sent to the api.
$scope.newStartsToImport = $scope.createApiObject(newStartsToImport[i]);
// A check to ensure that nothing has went wrong and that the stringify object has worked.
if ($scope.newStartsToImport !== "") {
res.push(apiFactory.postNewStarterDetailsToApi($scope.newStartsToImport))
}
else {
toastr("An error has occurred when attempting to create the data object to be sent to API. The process has stopped!", "Error!");
break;
}
}
return Promise.all(res);
}
else
toastr.info("No new starter details were sent to API", "Information!");
}).then(function (data) {
data.forEach((response) => {
var isSuccessful = response.data.d.WasSuccessful;
if (isSuccessful)
toastr.success("New starter details successfully sent to API.", "Success!");
else {
var errorMessage = response.data.d.ErrorMessage;
toastr.error("New starter details were unsuccessfully sent to API. Please try again. \n" + errorMessage, "Error!");
}
})
}).then((res) => {
//call Submit new starters
})
};

Meteor.user() is undefined in client-side controller

In the client-side of a METEORJS application, i have a controller that display some users.
I have a problem with Meteor.user() function, the error is : Meteor.user(...) is undefined.
Here is my code :
this.AdminUsersController = RouteController.extend({
template: "Admin",
yieldTemplates: {
'AdminUsers': { to: 'AdminSubcontent'}
},
onBeforeAction: function() {
var permissions = Meteor.user().profile.permissions;
if (permissions && permissions.indexOf('Users') != -1)
this.next();
else this.redirect('/admin/dashboard');
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("Admin"); this.render("loading", { to: "AdminSubcontent" });}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("users")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
var data = {
params: this.params || {},
users: Users.find({labo_id: Meteor.user().profile.labo_id}, {sort: {createdAt:-1}})
};
return data;
},
onAfterAction: function() {
}});
It's in the data function.
I try to retrieve all users that are connected to the logged in user and got the same labo_id field...
I don't know why it give me that, because in the onBeforeAction function, i can access to Meteor.user(), and specially his profile...
Someone know what can i do to make it run ?
Thanks for your future answers :)
This is a timing issue. Meteor.user() does not necessarily return data, if it hasn't been loaded yet. Meteor.userId() however will return the _id of the user record (if they are logged in). If you can change your query to rely on that _id, then it can work.
Depending on which router you are using, you can add a resolve: entry to ensure that the route waits for the user record to be loaded before activating it, and then your query will work.

Async operations in modal callback

I'm using the semantic-ui modal to allow users to insert data. It has an onApprove callback which allows you to return false to keep the modal open if there are any problems. My data is inserted into a DB, which returns false if there's any error. What's the best way of keeping the modal open if there's an error during this async operation?
Here's my code (coffeescript):
$('#verification-modal')
.modal('setting', {
detachable: false,
onApprove: validateVerificationForm
closable: false
})
validateVerificationForm = () ->
formData = $('.form').serializeArray()
formatted = format($formData);
ID_Details.insert(formatted, (errs, id) ->
if errs
false
else
true
Obviously the anonymous function is returning true/false into the context of the function. What's the best way to return it to the modal?
You can use a local reactive variable:
var data = new ReactiveDict();
Template.modalTemplate.created = function() {
data.set('isError', false);
};
Template.modalTemplate.helpers({
isError: function() {
return data.get('isError');
},
});
var yourMethodWithAsync = function() {
...
async(..., function(error) {
if(error) {
data.set('isError', true);
}
...
});
};

AngularJS redirection after ng-click

I have a REST API that read/save data from a MongoDB database.
The application I use retrieves a form and create an object (a job) from it, then save it to the DB. After the form, I have a button which click event triggers the saving function of my controller, then redirects to another url.
Once I click on the button, I am said that the job has well been added to the DB but the application is jammed and the redirection is never called. However, if I reload my application, I can see that the new "job" has well been added to the DB. What's wrong with this ??? Thanks !
Here is my code:
Sample html(jade) code:
button.btn.btn-large.btn-primary(type='submit', ng:click="save()") Create
Controller of the angular module:
function myJobOfferListCtrl($scope, $location, myJobs) {
$scope.save = function() {
var newJob = new myJobs($scope.job);
newJob.$save(function(err) {
if(err)
console.log('Impossible to create new job');
else {
console.log('Ready to redirect');
$location.path('/offers');
}
});
};
}
Configuration of the angular module:
var myApp = angular.module('appProfile', ['ngResource']);
myApp.factory('myJobs',['$resource', function($resource) {
return $resource('/api/allMyPostedJobs',
{},
{
save: {
method: 'POST'
}
});
}]);
The routing in my nodejs application :
app.post('/job', pass.ensureAuthenticated, jobOffers_routes.create);
And finally the controller of my REST API:
exports.create = function(req, res) {
var user = req.user;
var job = new Job({ user: user,
title: req.body.title,
description: req.body.description,
salary: req.body.salary,
dueDate: new Date(req.body.dueDate),
category: req.body.category});
job.save(function(err) {
if(err) {
console.log(err);
res.redirect('/home');
}
else {
console.log('New job for user: ' + user.username + " has been posted."); //<--- Message displayed in the log
//res.redirect('/offers'); //<---- triggered but never render
res.send(JSON.stringify(job));
}
});
};
I finally found the solution ! The issue was somewhere 18inches behind the screen....
I modified the angular application controller like this :
$scope.save = function() {
var newJob = new myJobs($scope.job);
newJob.$save(function(job) {
if(!job) {
$log.log('Impossible to create new job');
}
else {
$window.location.href = '/offers';
}
});
};
The trick is that my REST api returned the created job as a json object, and I was dealing with it like it were an error ! So, each time I created a job object, I was returned a json object, and as it was non null, the log message was triggered and I was never redirected.
Furthermore, I now use the $window.location.href property to fully reload the page.

Categories

Resources