I have a meteor template helper that is being called multiple times. The helper has session variables and I guess helper is executed when each session object is changed.
This is my helper:
Template.profile.helpers({
listOfUsers: function (param1,param2,param3) {
if(*if param 1/2/3 are not passed as parameters*)
{
var param1 = Session.get('param1');
var param2 = Session.get('param2');
var param3 = Session.get('param3');
}
/*This is the api call to get the data.
Meteor.call('apiMethpod', param1, param2, param3, function (error, result) {
if (error) {
console.log("Error occurred on receiving checks data on server. ", error);
Session.set('apiData', "");
} else {
console.log("Checks data received on client side ");
Session.set('apiData', result);
}
});
return Session.get('apiData');
}
});
This helper is also being called from an event :
Template.checkImages.events({
'click .search': function(event){
/*Getting the values for params
var param1 = Session.get('param1');
var param2 = Session.get('param2');
var param3 = "Some value not from session object";
Template.profile.__helpers.get("listOfUsers")(param1,param2,param3);
}
});
When the event is triggered, parameters are getting passed to the helper and I am getting api response as expected for the first time. But the helper gets exceuted multiple times and now as it does not see the parameters it takes the values from session object.
I somehow wan the helper to execute only once when it is being called from the event.
Does your HTML refer to {{#each listOfUsers}} anywhere? That would cause multiple updates and reactive updates based on changes in your 3 Session variables. You may wish to create a single session variable that's an object with Session.set('myParams',{param1: value1, param2: value2, param3: value3}).
Related
In general I want to export data from asp.net mvc application to Google Sheets for example list of people. I've already set up connection and authenticated app with my Google account (trough OAuth2) but now I'm trying to send my list of objects to api and then handle it in script (by putting all data in new file) and couldn't get my head around this.
Here is some sample code in my app that sends the request.
public async Task<ActionResult> SendTestData()
{
var result = new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(CancellationToken.None).Result;
if (result.Credential != null)
{
string scriptId = "MY_SCRIPT_ID";
var service = new ScriptService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "Test"
});
IList<object> parameters = new List<object>();
var people= new List<Person>(); // next i'm selecting data from db.Person to this variable
parameters.Add(people);
ExecutionRequest req = new ExecutionRequest();
req.Function = "testFunction";
req.Parameters = parameters;
ScriptsResource.RunRequest runReq = service.Scripts.Run(req, scriptId);
try
{
Operation op = runReq.Execute();
if (op.Error != null)
{
// The API executed, but the script returned an error.
// Extract the first (and only) set of error details
// as a IDictionary. The values of this dictionary are
// the script's 'errorMessage' and 'errorType', and an
// array of stack trace elements. Casting the array as
// a JSON JArray allows the trace elements to be accessed
// directly.
IDictionary<string, object> error = op.Error.Details[0];
if (error["scriptStackTraceElements"] != null)
{
// There may not be a stacktrace if the script didn't
// start executing.
Newtonsoft.Json.Linq.JArray st =
(Newtonsoft.Json.Linq.JArray)error["scriptStackTraceElements"];
}
}
else
{
// The result provided by the API needs to be cast into
// the correct type, based upon what types the Apps
// Script function returns. Here, the function returns
// an Apps Script Object with String keys and values.
// It is most convenient to cast the return value as a JSON
// JObject (folderSet).
Newtonsoft.Json.Linq.JObject folderSet =
(Newtonsoft.Json.Linq.JObject)op.Response["result"];
}
}
catch (Google.GoogleApiException e)
{
// The API encountered a problem before the script
// started executing.
AddAlert(Severity.error, e.Message);
}
return RedirectToAction("Index", "Controller");
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
The next is how to handle this data in scripts - are they serialized to JSON there?
The execution API calls are essentially REST calls so the payload should be serialized as per that. Stringified JSON is typically fine. Your GAS function should then parse that payload to consume the encoded lists
var data = JSON.parse(payload);
Here is my server side code
Meteor.methods({
addSupportRequest: function (support) {
Support.insert(support, function (err, id) {
if (err)
throw new Meteor.Error(404, "Oops! Network Error. Please submit help request again. ");
console.log("Support resuest added: " + id);
return id;
});
} // End addSupportRequest
});
Here is the client side code
App.Util = {
call: function (method, params, callback) {
NProgress.start();
Meteor.apply(method, params, function (error, result) {
NProgress.done();
console.log(error);
console.log(result);
callback(error, result);
});
} // end Call
};
Please help me find out why meteor method call is not returning error or result. console.log() showing new record Id on server but showing undefined on client.
One issue that is preventing your method from returning a result is that return id; is in the function scope of the insert callback, and not the scope of the meteor method. So it will return from the callback and then there is no return in the meteor method function which is implicitly a return undefined.
You should add a return to the method's scope like this:
Meteor.methods({
addSupportRequest: function (support) {
return Support.insert(support, function (err, id) {
if (err)
throw new Meteor.Error(404, "Oops! Network Error. Please submit help request again. ");
console.log("Support resuest added: " + id);
return id;
});
} // End addSupportRequest
As for the error, I am not sure why it isn't surfacing as it should traverse up the call stack (doesn't matter that it is inside an inner function like the return) and since it is a Meteor.Error it should get sent to the client as well.
Dsyko's answer was somewhat on the right track. However, the asynchronous callback will never pass its result to the scope of the original function, which has ended.
What you want is to run the Support.insert operation synchronously, i.e. having the current fiber yield while I/O is happening. This is what the Meteor._wrapAsync function is for. Luckily for you, there is no need to do this manually because if you just take out the callback from the insert operation, it will run synchronously:
Meteor.methods({
addSupportRequest: function (support) {
var id = Support.insert(support);
// An error may be thrown here, but it certainly won't be a network error because the request will have already reached the server.
console.log("Support request added: " + id);
return id;
});
}
I always use methods to insert, update and remove. This is the way my code look just now:
Client side
Template.createClient.events({
'submit form': function(event, tmpl) {
e.preventDefault();
var client = {
name: event.target.name.value,
// .... more fields
}
var validatedData = Clients.validate(client);
if (validatedData.errors) {
// Display validation errors
return;
}
Meteor.call('createClient', validatedData.client, function(error) {
if (error)
// Display error
});
}
});
Client and server side:
Clients = new Mongo.Collection("clients");
Clients.validate = function(client) {
// ---- Clean data ----
client.name = _.str.trim(client.name);
// .... more fields clean
// ---- Validate data ---
var errors = [];
if (!client.name)
errors.push("The name is required.");
// .... more fields validation
// Return and object with errors and cleaned data
return { errors: _.isEmpty(errors) ? undefined : errors, client: client };
}
Meteor.methods({
'createClient': function (client) {
// --- Validate user permisions ---
// If server, validate data again
if (Meteor.isServer) {
var validatedData = Clients.validate(client);
if (validatedData.errors)
// There is no need to send a detailed error, because data was validated on client before
throw new Meteor.Error(500, "Invalid client.");
client = validatedData.client;
}
check(client, {
name: String,
// .... more fields
});
return Clients.insert(client);
}
});
Meteor.call is executed on client and server side, but Meteor doesn't have a way stop the running on the server side if the validation on the client side fails (or at least, I don't know how). With this pattern, I avoid sending data to the server with Meteor.call if validation fail.
I want to start using Collection2, but I can't figure how to get the same pattern. All the examples I found involve the usage of direct Insert and Update on client side and Allow/Deny to manage security, but I want to stick with Meteor.call.
I found on documentation that I can validate before insert or update, but I don't know how to get this to work:
Books.simpleSchema().namedContext().validate({title: "Ulysses", author: "James Joyce"}, {modifier: false});
I know the autoform package, but I want to avoid that package for now.
How can I validate with Collection2 on the client side before sending data to the server side with Meteor.call? Is my pattern wrong or incompatible with Collection2 and I need to do it in another way?
In under 30 lines you can write your very own, full-featured validation package for Collection2. Let's walk through an example:
"use strict"; //keep it clean
var simplyValid = window.simplyValid = {}; //OK, not that clean (global object)
simplyValid.RD = new ReactiveDict(); //store error messages here
/**
*
* #param data is an object with the collection name, index (if storing an array), and field name, as stored in the schema (e.g. 'foo.$.bar')
* #param value is the user-inputted value
* #returns {boolean} true if it's valid
*/
simplyValid.validateField = function (data, value) {
var schema = R.C[data.collection]._c2._simpleSchema; //access the schema from the local collection, 'R.C' is where I store all my collections
var field = data.field;
var fieldVal = field.replace('$', data.idx); //make a seperate key for each array val
var objToValidate = {};
var dbValue = schema._schema[field].dbValue; //custom conversion (standard to metric, dollars to cents, etc.) IGNORE
if (dbValue && value) value = dbValue.call({value: value}); //IGNORE
objToValidate[field] = value; //create a doc to clean
schema.clean(objToValidate, {removeEmptyStrings: false}); //clean the data (trim, etc.)
var isValid = schema.namedContext().validateOne(objToValidate, field, {extendedCustomContext: true}); //FINALLY, we validate
if (isValid) {
simplyValid.RD.set(fieldVal, undefined); //The RD stores error messages, if it's valid, it won't have one
return true;
}
var errorType = schema.namedContext()._getInvalidKeyObject(field).type; //get the error type
var errorMessage = schema.messageForError(errorType, field); //get the message for the given error type
simplyValid.RD.set(fieldVal, errorMessage); //set the error message. it's important to validate on error message because changing an input could get rid of an error message & produce another one
return false;
};
simplyValid.isFieldValid = function (field) {
return simplyValid.RD.equals(field, undefined); //a very cheap function to get the valid state
};
Feel free to hack out the pieces you need and shoot me any questions you might have.
You can send the schema to the client and validate before sending to the server. If you want to use Collection" you need to attach the schema to the collection and use the insert which is something that you don't want. So the best option, for your scenario, is sending the schema to the client and use it to validate.
Also reconsider using mini-mongo instead of using Methods for everything, it will save you lots of time and don't think your app is secure jut because you're using Methods.
I have this angular js code here:
$http.post('/reports/', JSON.stringify($scope.user));
and its hitting my Reports Controller Post method:
[HttpPost]
public dynamic Post(Array data){
//do something
}
but when I check the data in my Post method when it hits in my breakpoint it appears as null :( how do I pass the data from $scope.user to my Controller. I did a console.log of $scope.user and the data is there, it is an object but trying to pass it in as JSON.
I found this:
public HttpResponseMessage Post([FromBody]Customer cust)
{
var newCust = _Repository.InsertCustomer(cust);
if (newCust != null)
{
var msg = new HttpResponseMessage(HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + newCust.ID.ToString());
return msg;
}
throw new HttpResponseException(HttpStatusCode.Conflict);
}
would I have to put [FromBody] Reports report instead of Array data
Just do this simple as possible, you are missing the parameter name:
$http.post('/reports/', {data: $scope.user});
Make sure that $scope.user is an Array, else change the type.
I am trying to call an action method by jQuery, and got help with passing parameters here: Calling action method in MVC with jQuery and parameters not working . However, even though the parameters are sent correctly now, in the action method I need to redirect (passing on those same parameters) to another action method. In the debugger I can see that the redirect is carried out, and the parameters are still there, but the View returned doesn't update accordingly...
Is there some problem with calling an action method that redirects by using jQuery?
Action method called by jQuery:
public ActionResult SelectDate(string date)
{
DateTime dateObject = DateTime.Parse(date);
MyCalendar calendar = new MyCalendar();
string number = calendar.GetWeekNumber(dateObject).ToString();
string year = dateObject.Year.ToString();
return RedirectToAction("Edit", new { number = number, year = year });
}
Action method redirected to:
public ActionResult Edit(string number, string year) //Why string here???
{
int n;
if (string.IsNullOrEmpty(number))
n = myCalendar.GetWeekNumber(DateTime.Today);
else
n = Int32.Parse(number);
int y;
if (string.IsNullOrEmpty(year))
y = DateTime.Today.Year;
else
y = Int32.Parse(year);
List<Customer> customers = _repository.Customers;
ViewData["Customers"] = new SelectList(customers, "CustomerId", "CustomerName");
ViewData["WeekNumber"] = n;
ViewData["Year"] = y;
return View();
}
Or is jQuery get() not the proper way to call an action method to load a new page? Note that doing the same thing with an ActionLink works fine, it's just that I need to call it by jQuery because I'm using the datepicker plugin and also a button as the way to call the action.
What am I doing wrong?
UPDATE:
Actually, the RedirectToAction doesn't seem to be the problem, I modified the code so that the jQuery now calls the final action method directly (by doing the job of the first action method directly in the jQuery, i.e. convert date to week and year). But I still don't get the new page with the new week and year.
Here's the new jQuery:
function selectWeek() {
$('#selectWeekButton').click(function (event) {
var date = $('#selectWeekId').val();
var selectedDate = new Date(date);
var year = selectedDate.getFullYear();
var week = selectedDate.getWeekOfYear();
var url = '<%: Url.Action("Edit") %>';
$.get(url, { number: week, year: year }, function (data) {
// alert('Test');
});
});
}
Again, is it using the get that is incorrect? I do not wish to load content in an html tag, I just want to use jQuery to do the exact same thing as an actionlink, i.e. call an action method to get the Edit View...
For instance, this actionlink works fine, calling an action method called Next that redirects to Edit and shows the new week and year View:
<a href="<%=Url.Action("Next", new { number=ViewData["WeekNumber"], year = ViewData["Year"] })%>">
>></a>
But I want to do that (or Edit directly) in jQuery...
jquery AJAX automatically follows redirect meaning that in the success callback you will get the result of the Edit action you have redirected to. In this callback you will get the partial HTML returned by the controller action. So if you want to update some part of the DOM you need to explicitly instruct it so:
$.ajax({
url: '<%= Url.Action("SelectDate") %>',
data: { date: '123' },
success: function(result) {
$('#someresultDiv').html(result);
}
});
or simply use the .load() method:
$('#someresultDiv').load('<%= Url.Action("SelectDate") %>', { date: '123' });