Form link send and later submit to sender in Meteor - meteor

A Meteor web app being used by a logged in user "merchant" who needs to create a link and sms/email it to his customer. The link opens a form. The customer fills up the form and submits it so that the data gets inserted with a property merchantId, since many merchants can send to many customers.
This single page app is not using a Router but able to sms/email. How can linking a form between a merchant and a customer be accomplished elegantly so that the data from the correct customer gets "linked" to the correct merchant? Thanks

Merchant Part
You can trigger after a successful send of the email/sms a meteor method, that stores a record of the sent email/sms in a collection (in this example named Record). This could be the schema for it:
Record Collection Schema (Server/Client)
{
merchantId:String, // the id of the sender
customer:String, //id or name or address or phone num of receiver
opened:Boolean, //when the link is opened can be set to true
link:String, // the link to be send,
type:Number, //0=sms, 1=email,
expires:Date, //in case the link is not opened
}
You can for example create a Meteor method to insert the record after send:
Insert Record (Server)
Meteor.methods({
insertRecord:function(recordData) {
//... check recordData by schmema and permissions here
return Records.insert(recordData);
}
})
Sending the Email/SMS
So the merchant part of the app sends the link via sms/email and calls the insertRecord method to store the record of the saved .
Save Record (Client or Server)
const callback=function(err, res) {
if (res) { // assume your sent was successful here
Meteor.call("insertRecord", {
//... your record credentials
});
}
}
// a little bit of pseudocode
if (sms)
sms.send(form, callback);
else
email.send(form, callback);
Customer Part
When the customer opens the link it triggers a templatethat will render your form. You can initially run a meteor method to check the Record collection for a document that matches the link url.
Get Record by Url Method (Server)
Meteor.methods({
getRecordByUrl:function(url) {
//... do your input checks here
return Records.findOne({link:url});
},
});
Template for Form (Client)
Template.customerForm.onCreated(function(){
const instance = this;
instance.state = new ReactiveDict();
instance.state.set("record", null);
instance.autorun(function(){
// if no record loaded yet
if (!instance.state.get("record")) {
Meteor.call("getRecordByUrl", window.location.href, function(err, res) {
if (err || !res) {
//...handle err
}
this.state.set("record", res);
}.bind(instance));
}
});
});
Template.customerForm.helpers({
getRecord() {
return Template.instance().state.get("record");
},
getMerchantId() {
const record = Template.instance().state.get("record");
return record.merchantId;
}
});
You can then use this document to add the merchantId to the form either as a hidden input or via html data attribute.
{{#if getRecord}}
<form id="...">
<input type="hidden" name="merchantId" value="{{getMerchantId}}" />
<!-- other inputs here -->
</form>
{{/if}}
The examples can of course be optimized but I think this way it clearer to understand.

Related

Where is spreadsheet with data

Is it possible to access the Google drive table as which contains the data for an app maker model? I mean - is it possible to open it as a Spreadsheet?
Yes. You can use a spreadsheet as a datasource. I used a simple spreadsheet just to collect 3 items. I was playing with the Email Sender tutorial and I wanted to collect a list of the emails I'd already sent. I created the fields and went into the datasources tab and added this code to the queryRecords() function.
var ss=SpreadsheetApp.openById('id');
var sh=ss.getSheetByName('RecentEmails');
var rg=sh.getRange(2,1,sh.getLastRow(),sh.getLastColumn());
var vA=rg.getValues();
var reRecords=[];
for(var i=0;i<vA.length;i++){
var reRecord=app.models.RecentEmails.newRecord();
reRecord.Recipient=vA[i][0];
reRecord.Date=vA[i][1].toString();
reRecord.Message=vA[i][2];
reRecords.push(reRecord);
}
return reRecords;
The above function loads the datasource.
Then I connected a table upto the datasource and the data will update whenever the page is loaded.
I loaded the data into the table with a function like this:
function archiveSentEmails(to,when,what)
{
var ss=SpreadsheetApp.openById('id');
var sh=ss.getSheetByName('RecentEmails');
sh.appendRow([to,when,what]);
}
It gets placed inside the serverside script where the MailApp.sendMail function is located.
When you push the Send EMail button it calls this clientside function which calls the serverside function via google.script.run.
function sendMessage(to, subject, msg){
var status = app.pages.Email.descendants.EmailStatus;
google.script.run
.withFailureHandler(function(error) {
// An error occurred, so display an error message.
status.text = error.message;
})
.withSuccessHandler(function(result) {
// Report that the email was sent.
status.text = 'Email sent...';
clearEmailForm();
loadDebugElements();
app.datasources.RecentEmails.load();//this lines refreshes the widgets attached to the datasource
})
.sendEmailMessage(to, subject, msg);
}
and I placed the command app.datasources.RecentEmails.load in the withSuccessHandler so that the table of recent emails will update everytime it sends an email and that way you don't have to have a button to initiate updating the table after every email is sent.

EmberFire: Getting property generated by Cloud Function when saving record completes

I use a Cloud Function to generate a short unique URL on a record on the 'onWrite' event, and save it. This works well, but when I save a record from my Ember app using EmberFire, I do get a model back as an argument to a callback, but the URL of this model is undefined. Is there a way to return this back to the client? Or do I need to query the record to get the generated URL?
This is how my Cloud Function code looks:
exports.generateUrl = functions.database.ref('/tournaments/{tid}')
.onWrite(event => {
if (event.data.previous.exists()) {
return;
}
if (!event.data.exists()) {
return;
}
const url = shortid.generate();
return event.data.ref.update({ url });
});
Here is my component that saves data through form submission. I'm using an add-on called ember-changeset to handle some validations, but this shouldn't be related to the issue.
export default Ember.Component.extend({
submit(e) {
e.preventDefault();
let snapshot = this.changeset.snapshot();
return this.changeset
.cast(Object.keys(this.get('schema')))
.validate()
.then(() => {
if (this.changeset.get('isValid')) {
return this.changeset
.save()
.then((result) => {
// Here, result.get('url') is undefined.
})
}
})
}
});
If you have a function that writes new data back to a location in the database after a write, you'll have to keep listening to that location on the client in order to get that data back. Don't use a one-time read (once()), use a persistent listener (on()), and in that listener, make sure you're getting the URL or whatever you expect to be generated by the function. Then remove that listener if you don't need it any more.
(Sorry, I don't know Ember or what abstractions it provides around Realtime Database - I'm giving you the plain JavaScript API methods you'd use on a reference.)

how to create a global variable for every entrance to the website?

I have global array that works just fine and stores the URL's of the chosen images from the user after i click submit in the form.
the problem is when i want to submit another form, the global array will still have the URL's of the previous submission.
what i want to do is to create an array for every user to store his URL's, one he click submit, the array will be dropped or deleted. so if there were multiple users using the same function, every one of them will have his own array to store his URL's
How do i do this?
this is what i have tried but when i click on submit on the form page, nothing happens
first, this is the method that returns the url of the chosen image by the user, the method exists in both folder (both/file.js)
storeUrlInDatabaseSS: function( url ) {
check( url, String );
Modules.both.checkUrlValidity( url );
try {
return url;
} catch( exception ) {
return exception;
}
}
then i created the session variables in the client side (client/file.js)
Session.set("screenshots", []);
Session.set("i", 0);
var screenshots = Session.get("screenshots");
var i = Session.get("i");
and here i store the url in the array
let _addUrlToDatabaseSS = ( url ) => {
screenshots[i++] = url;
Session.set("screenshots", screenshots);
};
and am using Meteor Collection Hook Package
and i added these two lines of code which should be excited after the user press submit, they exist inside "client/files.js" directory
Products.before.insert(function (userId, doc) {
doc.screenShots = Session.get("screenshots");
});
now whenever i click submit nothing happens, i think the problem is because nothing is actually stored inside the screenShots attribute in the collection here
screenShots: {
type: [String]
},
when i set the screenShots attribute to an empty array by default like the code below, the submit button works
screenShots: {
type: [String],
autoValue: function() {
return [];
}
},
I tried to use the other way of using AutoForm.hooks
AutoForm.hooks({
submitPostForm: {
before: {
insert: function(doc) {
doc.$set.screenShots = Session.get("screenshots");
}
}
}
});
the is my form in the .html file
{{> quickForm collection="Products" id="submitPostForm"
type="method" meteormethod="submitPost" omitFields="createdAt, previewImage, screenShots, sourceCode, userID"}}
and this is the method triggered once the user submit the form, it exist in the server side.
submitPost: function (app) {
// Console.log('new App:', app);
check(app, {
title: String,
description: String,
category: String,
price: Number
});
Products.insert(app);
}
for some reason my before hook isn't working and i can't see why!
what am i doing wrong here?
One of the ways to create a global array per user is to use Session. This way it is also possible to persist the data across the app (only client-side).
Simple way to use Session is thus:
Create an array in Session called url_list:
Session.set("url_list", []);
Retrieve the array from Session:
var url_list = Session.get("url_list");
Make changes to url_list:
url_list.push(someData);
Store url_list in the Session again:
Session.set("url_list", url_list);
Note: Session can only be used on client-side and all related code should be on the client-side.
More about Session.
PERSISTING DATA TO SERVER-SIDE:
The best way to persist the url_list to the server, would be to insert a new document into the database collection containing the Session data.
insertToDB = function() {
var url_list = Session.get('url_list');
Products.insert({
'url_list': url_list
});
Session.set('url_list', []); // To empty the client-side list
}

Reactive subscription on user collection

I am trying to subscribe to profdle information of a different user than the logged in user, but I am facing issues as mentioned below
I am using angular-material and my code looks like below:
//publish user info upon following user
Meteor.publish("getUserInfo", function (userId) {
return (Meteor.users.find({_id: userId}, {fields: {profile: 1}}));
});
//subscribe
$scope.$meteorSubscribe("getUserInfo", askLikeController.$root.askLike[0].userId).then(function (subscriptionHandle) {
//Second element in the userProfile array will have the profile of required user
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
});
Issues:
1. In the variable askLikeController.$root.usersProfile, I am getting both the loggedIn user and the desired userinfo having userId, I was expecting userinfo of only desired userId, why is this?
2. The subscription "getUserInfo" is not reactive, and even the subscription is lost after processing few blocks of code and then in the askLikeController.$root.usersProfile I am left with only user profile of logged in user, my guess is that my subscription is being replaced by inbuilt Meteor subscription for user.
How do I solve the issues?
Regards,
Chidan
First, make sure you have removed autopublish:
> meteor remove autopublish
To get reactivity in angular-meteor you need $meteor.autorun and $scope.getReactively. Here's an example:
// we need the requested id in a scope variable
// anytime the scope var changes, $scope.getReactively will
// ... react!
$scope.reqId = askLikeController.$root.askLike[0].userId;
$meteor.autorun($scope, function() {
$scope.$meteorSubscribe('getUserInfo', $scope.getReactively('reqId')));
}).then(function(){
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
})
Getting only the user you selected: NOTICE- the logged in users is always published. So you need to specify which user you want to look at on the client side, just like you did on the publish method. So, in the subscribe method:
askLikeController.$root.usersProfile = $meteor.collection(function() {
return Meteor.Users.find({_id: $scope.getReactively('reqId')})
}, false);
At this point you might be better off changing it to an object rather than a collection:
askLikeController.$root.usersProfile = $scope.$meteorObject(Meteor.Users, {_id: $scope.getReactively('reqId')});

Retrieve user email given userId

This is more or less a follow up to this question.
I am trying to display "friends", I have a list of friends I sent a request to (called sent):
{{#each sent}}
<p>{{find_user _id}}</p>
{{/each}}
Sent is generated like so:
Template.friends.sent = function () {
return Notifications.find({from: Meteor.userId(), // to, and from are now userIds and not the user like in the original question.
type: 'friendship'});
}
And a query for the count gives a number of seven. My find_user template is defined as such:
Template.friends.find_user = function (id) {
return Meteor.users.find({_id: id});
}
How can I get the email from the a user id? Doing something like:
{{(find_user _id).emails.[0].address}}
fails, with:
Expected IDENTIFIER.
So first it appears you are iterating over a cursor from a Notifications collection and then calling the find_user method on the template with the _id of a Notification record. You'll need to use the from field of the document as it's the field that contains the userId.
Next you'll want to at least rewrite your find_user method so that it doesn't take a parameter. You can access the same data from within the helper because this is set to the current data context.
Template.friends.find_user = function () {
return Meteor.users.find({_id: this.from}); //note the this.from
}
Then you should be able to access the email address with via your template helper as long as you are publishing that data for the current user.
{{from_user.emails.0.address}}
Personally I like to use Meteor's collection transforms to extend my models with prototypes that can be used just like template helpers.
//first we create our collection and add a transform option
Notifications = new Meteor.Collection("notifications", {
transform: function(document){
return new Notification(document);
}
});
//next we create our constructor
Notification = function(document){
_(this).extend(document);
};
//Then add some prototypal methods that we can use in our templates.
Notification.prototype = {
fromUser: function(){
return Meteor.users.findOne(this.from);
}
};
Now we can use this in our templates like this:
{{fromUser.emails.0.address}}
We can also take this one really great step farther by using the users _transform property to set a function that transforms user documents as well and then add methods to them as well.
//transform each user document into a new User instance
Meteor.users._transform = function(document){
return new User(document);
};
//User constructor
User = function(document){
_(this).extend(document);
};
//and finally the User prototype with methods
User.prototype = {
defaultEmail: function(){
return this.emails && this.emails[0].address;
}
};
Now as a final result you can use it like this:
{{#each sent}
<p>{{fromUser.defaultEmail}}</p>
{{/each}}

Resources