I want to send an email with a content related to my data such as in following piece of code I found on Datasource script of Google AppMaker Project Tracker template. But I don't understand how it works. How that data.modifiedBy reflect to the record in my datasource?
Any help from the floors? Thanks ..
Look at the Notifications server side script in the template.
It has method notifyAboutItemChanges_ which is passing the data to this record.
function notifyAboutItemChanges_(changes) {
var settings = getAppSettingsRecord_()[0];
if (!settings.EnableEmailNotifications) {
return;
}
var data = {
appUrl: settings.AppUrl,
itemType: changes[0].Type,
itemKey: changes[0]._key,
itemName: changes[0].Name,
modifiedBy: changes[0].ModifiedBy,
changes: changes
};
// Email subject.
var subjectTemplate =
HtmlService.createTemplate(settings.NotificationEmailSubject);
}
This function is passing this data to your settings record.
So no magic here :) You need to pass the data to your record which will be replaced at run time with the values.
For more details on Email refer this sample app.
Related
I have tried to get the client ID with custom javascript but it cannot return the value. Below is the code is tried. Would like to seek help from all experts. Thanks.
function () {
return function () {
try {
var trackers = ga.getAll();
trackers.forEach(function(tracker) {
var cid = tracker.get('clientId');
tracker.set('dimension1', cid);
});
} catch (e) {}
}
}
It cannot return a normal client ID
Your custom variable returns a function, not a value (since the function is never actually executed).
A better way to get the clientId for each current tracker is to use a custom task in Google Analytics (tasks are basically individual steps in the tracker lifecycle, from checking if a client id exists to assemble the payload to actually sending the data). A task is a Javascript function that is added to the GA tag via the "set fields" configuration. Tasks have access to the tracker data model and can add, remove or modify values from the payload.
The only task you can use via GTM is the customTask, which as the name suggests, adds custom capabilities to the tracker.
If you create a custom javascript variable called e.g. "getClientId" with the following code:
function() {
// Modify customDimensionIndex to match the index number you want to send the data to
var customDimensionIndex = 5;
return function(model) {
model.set('dimension' + customDimensionIndex, model.get('clientId'));
}
}
then go to your GA settings tag, and in the "set field" configuration set the field name "customTask" with the variable as value, the clientId will be extracted from the data model and added to the payload as custom dimension.
Better than my explanation is Simo Ahavas GTM tip for setting the client id via custom tasks.
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.
I have a page that lets you edit user data. I'm using FlowRouter for the routing and it can be found on the route /employees/:id.
I need to update the detail form when data changes on the server and leave the route if it was deleted by other client.
I decided to use Tracker.autorun which informs me whenever the data changes. The previous user info is stored on the template so it's easy to tell if the record was deleted.
Template.UpdateEmployee.onCreated(function () {
const self = this;
self.subscribe('user', FlowRouter.getParam('id'));
self.autorun(function () {
const _id = FlowRouter.getParam('id');
const user = Meteor.users.findOne({_id});
if(!user && self.user)
FlowRouter.go('/employees');
self.user = user;
if(!user)
return;
user.email = user.emails[0].address;
$('.ui.form').form('set values',user);
});
});
And lastly in the onRendered callback I'm checking if the data was set on template as I believe not doing so could lead to data being available before the template is rendered and hence values wouldn't get set properly. Is this correct?
Template.UpdateEmployee.onRendered(function () {
if(this.user){
user.email = user.emails[0].address;
$('.ui.form').form('set values',user);
}
});
Are there any pitfalls to this solution?
I can see a couple drawbacks inherently. The first one is doing a find query on the client. Typically you would want to return data from the server using Meteor publish and subscribe.
The second is you are passing the key to find the data over the URL. This can be spoofed by other users for them to find that users data.
Lastly if you are doing a find on the user object, I assume you might be storing data there. This is generally bad practice. If you need to store user data with their profile, it's best to create a new collection and publish/subscribe what you need.
I'm in the process of learning meteor. I followed the tutorial to create microscope. If some one submits a post meteor will re render the template for all users. This could be very annoying if there are hundreds of posts then the user will come back to the top of the page and loose track of where he was. I want to implement something similar to what facebook has. When a new post is submitted template isn't rendered rather, a button or link will appear. Clicking it will cause the template to re-render and show the new posts.
I was thinking of using observeChanges on the collection to detect any changes and it does stop the page from showing new posts but only way to show them is to reload the page.
Meteor.publish('posts', function(options) {
var self = this, postHandle = null;
var initializing = true;
postHandle = Posts.find({}, options).observeChanges({
added: function(id, post) {
if (initializing){
self.added('posts', id, post);
}
},
changed: function(id, fields) {
self.changed('posts', id, fields);
}
});
self.ready();
initializing = false;
self.onStop(function() { postHandle.stop(); });
});
Is this the right path to take? If yes, how do I alert the user of new posts? Else, what would be a better way to implement this?
Thank you
This is a tricky question but also valuable as it pertains to a design pattern that is applicable in many instances. One of the key aspects is wanting to know that there is new data but not wanting to show it (yet) to the user. We can also assume that when the user does want to see the data, they probably don't want to wait for it to be loaded into the client (just like Facebook). This means that the client still needs to cache the data as it arrives, just not display it immediately.
Therefore, you probably don't want to restrict the data displayed in the publication - because this won't send the data to the client. Rather, you want to send all the (relevant) data to the client and cache it there until it is ready.
The easiest way involves having a timestamp in your data to work from. You can then couple this with a Reactive Variable to only add new documents to your displayed set when that Reactive Variable changes. Something like this (code will probably be in different files):
// Within the template where you want to show your data
Template.myTemplate.onCreated(function() {
var self = this;
var options = null; // Define non-time options
// Subscribe to the data so everything is loaded into the client
// Include relevant options to limit data but exclude timestamps
self.subscribe("posts", options);
// Create and initialise a reactive variable with the current date
self.loadedTime = new ReactiveVar(new Date());
// Create a reactive variable to see when new data is available
// Create an autorun for whenever the subscription changes ready() state
// Ignore the first run as ready() should be false
// Subsequent false values indicate new data is arriving
self.newData = new ReactiveVar(false);
self.autorun(function(computation) {
if(!computation.firstRun) {
if(!self.subscriptionsReady()) {
self.newData.set(true);
}
}
});
});
// Fetch the relevant data from that subscribed (cached) within the client
// Assume this will be within the template helper
// Use the value (get()) of the Reactive Variable
Template.myTemplate.helpers({
displayedPosts = function() {
return Posts.find({timestamp: {$lt: Template.instance().loadedTime.get()}});
},
// Second helper to determine whether or not new data is available
// Can be used in the template to notify the user
newData = function() {
return Template.instance().newData.get();
});
// Update the Reactive Variable to the current time
// Assume this takes place within the template helper
// Assume you have button (or similar) with a "reload" class
Template.myTemplate.events({
'click .reLoad' = function(event, template) {
template.loadedTime.set(new Date());
}
});
I think this is the simplest pattern to cover all of the points you raise. It gets more complicated if you don't have a timestamp, you have multiple subscriptions (then need to use the subscription handles) etc. Hope this helps!
As Duncan said in his answer, ReactiveVar is the way to go. I've actually implemented a simple facebook feed page with meteor where I display the public posts from a certain page. I use infinite scroll to keep adding posts to the bottom of the page and store them in a ReactiveVar. Check the sources on github here and the live demo here. Hope it helps!
Here is the problem :
I am currently programming a chatapp based on what i found on github (https://github.com/sasikanth513/chatDemo)
I am refactoring it with iron-router.
When I go to the page (clicking on the link) I get an existing chatroom (that's what I want)
When I refresh the page (F5) I get a new created chatroom ! (what i want is getting the existing chatroom ...)
Here is the code in ironrouter :
Router.route('/chatroom', {
name: 'chatroom',
data: function() {
var currentId = Session.get('currentId'); //id of the other person
var res=ChatRooms.findOne({chatIds:{$all:[currentId,Meteor.userId()]}});
console.log(res);
if(res){
Session.set("roomid",res._id);
}
else{
var newRoom= ChatRooms.insert({chatIds:[currentId, Meteor.userId()],messages:[]});
Session.set('roomid',newRoom);
}
}
});
You can find my github repo with the whole project : https://github.com/balibou/textr
Thanx a lot !
Your route data depends on Session variables which will be erased after a refresh. You have a few options but the easiest would be to put the room id directly into the route: '/chatroom/:_id'. Then you can use this.params._id to fetch the appropriate ChatRooms document. Note that you could still keep '/chatroom' for cases where the room doesn't exist, however you'd need to redirect to '/chatroom/:_id' after the insert.
In meteor, the Session object is empty when the client starts, and loading/refreshing the page via HTTP "restarts" the client. To deal with this issue, you could persist the user's correspondent id in a Meteor.user attribute, so that you could easily do:
Router.route('/chatroom', {
name: 'chatroom',
data: function() {
var currentId = Meteor.user().profile.correspondentId;
var res=ChatRooms.findOne({chatIds:{$all:[currentId,Meteor.userId()]}});
console.log(res);
if(res){
Session.set("roomid",res._id);
}
else{
var newRoom= ChatRooms.insert({chatIds:[currentId, Meteor.userId()],messages:[]});
Session.set('roomid',newRoom);
}
}
});
This would work, with the proper permissions, but I would recommend not allowing the direct update of that value on the client (I don't know if you want users to be able to override their correspondentId). So if you want to secure this process, replace all that code with a server method call, where your updates are safer.
Another (and more common case) solution was given by David Weldon, if you don't mind having ids in your URL (and therefore not a single url)