I am using Firebase to develop an HTML5 mobile messaging app. I encountered an issue that I am unable to resolve. The app has multiple channels (chat rooms). When a message is added for the first time to a channel it works as expected but when I go to a different channel and post an new message to that channel then I return to the previous channel and post another message I get duplicates of the last posted message. When I reload the page the duplicates are gone but I'd prefer not to have duplicates showing at all. Below is my code:
function loadChatMessages(channelID) {
$('#chatMessages').html('');
var msgObj = {};
var channelRef = globals.channelsBase + '/' + channelID + '/messages';
var channelMessages = new Firebase(channelRef);
channelMessages.on('child_added', function (snapshot) {
msgObj = snapshot.val();
var id = snapshot.name().toString();
var messageTime = application.Functions.renderTime(msgObj.messageTime);
var tails = '<div class="message-tails-wrap"><div class="message-tails"></div></div>';
var html = '<li class="chatEl ' + sentByClass + '" id="'+id+'">';
html += tails;
html += msgObj.message;
html += '<span class="sender"> ' + by + ' </span> <span class="tmp-recipient"> ' + msgObj.recipient + ' </span>';
html += '<span class="time-stamp msg-time" >';
html += messageTime;
html += '</span></li>';
$(html).appendTo('#chatMessages');
/// TODO: TEMP solution!
var prevID = 0;
$('#chatMessages li').each(function(n) {
var id = $(this).attr('id');
if(id == prevID){
// console.log(id + ' is a duplicate. Remove it');
this.remove(); // the necessary evil....
}
prevID = id;
});
});
}
It sounds like you run loadChatMessages() every time you enter a room, and as your users move around rooms, you're seeing duplicate calls to your on('child_added' callback.
This is because you're adding a new callback every time you re-enter a room. To resolve this, do some clean up when your users leave a room. In that clean up function, make sure you remove the old listener using .off("child_added").
The event :
YOURREF.limitToLast(1).on('child_added', function (OBJECTADDED){
console.log(OBJECTADDED.key(), OBJECTADDED.val());
});
You may think the above code will e fired one time, this is not true. The above console.log will be fired twice, once with OBJECTADDED as the last object in the reference YOURREF before adding the new one, and another with the new one.
Related
I am trying to setup an email notification and my hope is to have a simple list of all the items in a datasource (separated into their fields).
Ex:
ItemName01, Cost01, Quantity01
ItemName02, Cost02, Quantity02
ItemName03, Cost03, Quantity03
Doing a projection of each (#datasources.Datasource.items..ItemName + #datasources.Datasource.items..Cost + #datasources.Datasource.items..Quantity) gives me everything, but not organized correctly.
Ex. [ItemName01,ItemName02,ItemName03],[Cost01,Cost02,Cost03],[Quantity01,Quantity02,Quantity03]
Any help/thoughts are appreciated.
Thanks!
I would recommend to use server script for this:
// query records
var records = app.models.Item.newQuery().run();
// generate email HTML body
var emailBody = records.reduce(function(str, item) {
str += '<p>' + item.Name + ', ' + item.Cost + ', ' + item.Quantity + '</p>'
});
// hand off generated HTML to other function
// that will actually send email
sendEmail(emailBody);
You can call this Server Script from Model Events or explicitly from client using google.script.run. You also can pass some filters to narrow records set to be sent.
Given this code:
var Container = CRM.GetBlock("Container");
var CustomCommunicationDetailBox = CRM.GetBlock("CustomCommunicationDetailBox");
Container.AddBlock(CustomCommunicationDetailBox);
if(!Defined(Request.Form)){
CRM.Mode=Edit;
}else{
CRM.Mode=Save;
}
CRM.AddContent(Container.Execute());
var sHTML=CRM.GetPageNoFrameset();
Response.Write(sHTML);
Im calling this .asp page with this parameters but does not seems to work
popupscreeens.asp?SID=33185868154102&Key0=1&Key1=68&Key2=82&J=syncromurano%2Ftabs%2FCompany%2FCalendarioCitas%2Fcalendariocitas.asp&T=Company&Capt=Calendario%2Bcitas&CLk=T&PopupWin=Y&Key6=1443Act=512
Note the Key6=Comm_Id and Act=512??? which i believe it is when editing?
How can i achieve to fill the screen's field with entity dada?
In this case it is a communication entity
In order to populate a custom screen with data, you need to pass the data to the screen.
First, you need to get the Id value. In this case, we're getting it from the URL:
var CommId = Request.QueryString("Key6") + '';
We're going to put a few other checks in though. These are mainly to handle scenarios that have come up in different versions or from different user actions.
// check we have a value and get the Id from context if we don't
if(CommId == 'undefined'){
CommId = CRM.GetContextInfo("Communication","comm_communicationid");
}
// if CommId is still undefined, set it to zero to check later
// otherwise, make sure the URL only contains one CommId
if(CommId == 'undefined'){
CommId = 0;
} else if(CommId.indexOf(",") > -1){
CommId = CommId.substr(0,CommId.indexOf(","));
}
Certain user actions can make the URL hold multiple Ids in the same attribute. In these cases, those Ids are separated by commas. So, if the Id is not defined, we check if there is a comma in it. If there is, we take the 1st Id.
After we have the Id, we need to load the record. At this point, you should have already checked you have a valid id (E.g. not zero) and put some error handling in. In some pages you may want to display an error, in others you may want to create a new, blank record. This gets the record:
var CommRecord = CRM.FindRecord("communication","comm_communicationid = " + CommId);
After that, you need to apply the record to the screen. Using your example above:
CustomCommunicationDetailBox.ArgObj = CommRecord;
Adding all that to your script, you get:
var CommId = Request.QueryString("Key6") + '';
// check we have a value and get the Id from context if we don't
if(CommId == 'undefined'){
CommId = CRM.GetContextInfo("Communication","comm_communicationid");
}
// if CommId is still undefined, set it to zero to check later
// otherwise, make sure the URL only contains one CommId
if(CommId == 'undefined'){
CommId = 0;
} else if(CommId.indexOf(",") > -1){
CommId = CommId.substr(0,CommId.indexOf(","));
}
// add some error checking here
// get the communication record
var CommRecord = CRM.FindRecord("communication","comm_communicationid = " + CommId);
// get the container and the detail box
var Container = CRM.GetBlock("Container");
var CustomCommunicationDetailBox = CRM.GetBlock("CustomCommunicationDetailBox");
// apply the communication record to the detail box
CustomCommunicationDetailBox.ArgObj = CommRecord;
// add the box to the container
Container.AddBlock(CustomCommunicationDetailBox);
// set the moder
if(!Defined(Request.Form)){
CRM.Mode=Edit;
} else {
CRM.Mode=Save;
}
// output
CRM.AddContent(Container.Execute());
var sHTML=CRM.GetPageNoFrameset();
Response.Write(sHTML);
However, we would advise putting in more error/exception handling. If the user is saving the record, you will also need to add a redirect in after the page is written.
Six Ticks Support
This is further information from a previous submission but I thought it would be clearer if I posted this separately.
A helper is returning a collection query:
Template.clientGrid.helpers({
'programs': function () {
var fullNameP = Session.get('clientName');
return Programs.find({FullName: fullNameP});
}
});
In the template it's printing out properties from 'programs'. For example:
...
{{#each programs}}
<p>{{formatCampYear CampYear}}: {{formatNotes Notes}}</p>
{{/each}}
....
Nothing special going on. So, if the FullName is Jane Doe, and she's got 6 documents in the programs collection, it will print the six properties in the template. But the page is getting caught in a while-loop inside Tracker (see line 449 the while-loop 'recompute all pending computations') after the properties finish printing. The CPU is tied up and prevents certain page operations. If any of you harder-core guys and gals have any clue as to what this means, perhaps I can sleuth out the problem. Here's a copy of the while loop itself (just in isolation):
// recompute all pending computations
while (pendingComputations.length) {
var comp = pendingComputations.shift();
comp._recompute();
if (comp._needsRecompute()) {
pendingComputations.unshift(comp);
}
if (! options.finishSynchronously && ++recomputedCount > 1000) {
finishedTry = true;
return;
}
}
EDIT: Here's the event map that setting the session. There doesn't seem to be anything suspicious. Since I'm pre-production, I'm not doing any updates to the collection. It's pretty much just static at this point.
Template.clientSearchButton.events({
'click #client-search-button': function(event) {
event.preventDefault();
var clientFullName = document.getElementById('full-name').value.toUpperCase();
Session.set('clientName', clientFullName);
mapAddress = Demographic.find({ "FullName": clientFullName }).map(function (a) { return (a.Address + " " + a.City + " " + a.State + " " + a.Country); });
Meteor.myFunctions.initialize();
}
});
I'm trying to add options to a selectmenu in jqueryMobile. I read the values that I would like to add from a sqlite database. The reading from the database works (the console log output tells me it has found 16 rows) and the variable that holds the new options gets filled correctly too. But if I would like to add the options to the selectmenu, I get the error "cannot call methods on selectmenu prior to initialization: attempted to call method 'refresh'. I tried to run the selectmenu() method before the refresh but it did not work either. Here is my code:
HTML:
<select name="subject" class="subjectDropdown" data-native-menu="false">
<option>%tx_subject%</option>
</select>
Javascript:
// Read all subjects from the database
fillSubjectsDropdown: function(){
sz.db.container.transaction(function(tx){
tx.executeSql("SELECT * FROM SUBJECTS ORDER BY sj_name", [], sz.db.selectSubjectsSuccess, sz.db.errorCB);
}, sz.db.errorCB, sz.db.successCB);
},
// Read subjects Callback
selectSubjectsSuccess: function(tx, results){
var len = results.rows.length;
console.log('### szlog: Subjects found: ' + len);
var subjects = '<option value="">' + sz.langdata['subject'] + '</option>';
for (var i = 0; i < len; i++){
subjects += '<option value="' + results.rows.item(i).sj_id + '">' + results.rows.item(i).sj_name + '</option>';
}
$(".subjectDropdown").html(subjects).selectmenu('refresh', true);
},
I have searched and found some articles but none of them could really help me. Any help would be much appreciated.
I use jqueryMobile 1.2.0, jquery 1.8.3 and phonegap 2.3.0
Thanks
In case of this error:
cannot call methods on selectmenu prior to initialization: attempted
to call method 'refresh'.
no matter is it a button, a select box or a listview, that element must be initialized before merkup enhancement with a refresh function can begun.
Change your code like this:
$(".subjectDropdown").html(subjects).selectmenu().selectmenu('refresh', true);
First .selectmenu() will initialize it and second .selectmenu('refresh', true); will style it.
I have a website which is using Google Analytics newer asynchronous tracking method (_gaq). The problem I've run into is that I want to institute some specific link tracking and am worried that I will be creating a race condition.
Basically, it's a news website so it has headlines which link to stories all over the place. A headline for a story might appear in 3 different places on a page, and appear on hundreds of other pages. Thus, in order to understand how our audience is interacting with the site we have to track how each specific headline block is used, and not just the destination. Because of those two stipulations tracking individual pages, nor tracking referred pages won't be enough, we have to track individual links.
So if I have a link.
Here
Because _gaq.push() is an asynchronous call, isn't it possible that the page change will occur prior to Google's completion of the click tracking? If so is there a way to prevent that, or do I have a misunderstanding about the way that Google Analytics Async functions (http://code.google.com/apis/analytics/docs/tracking/asyncUsageGuide.html).
You're right. If the browser leaves the page before it sends the GA tracking beacon (gif hit) for the event, the event will not be recorded. This is not new to the async code however, because the process of sending the tracking beacon is asynchronous; the old code worked the same way in that respect. If tracking is really that important, you could do something like this:
function track(link) {
if (!_gat) return true;
_gaq.push(['_trackEvent', 'stuff']);
setTimeout(function() {location.href=link.href'}, 200);
return false;
}
...
This will stop the browser from going to the next page when the link is clicked if GA has been loaded already (it's probably best to not make the user wait that long). Then it sends the event and waits 200 milliseconds to send the user to the href of the link they clicked on. This increases the likelihood that the event will be recorded. You can increase the likelihood even more by making the timeout longer, but that also may be hurting user-experience in the process. It's a balance you'll have to experiment with.
I've got this problem too, and am determined to find a real solution.
What about pushing the function into the queue?
// Log a pageview to GA for a conversion
_gaq.push(['_trackPageview', url]);
// Push the redirect to make sure it happens AFTER we track the pageview
_gaq.push(function() { document.location = url; });
From Google's documentation for universal analytics (new version since most other answers for this question). You can now easily specify a callback.
var trackOutboundLink = function(url) {
ga('send', 'event', 'outbound', 'click', url, {'hitCallback':
function () {
document.location = url;
}
});
}
For clarity I'd recommend using this syntax, which makes it clearer which properties you're sending and easier to add more :
ga('send', 'event', {
'eventCategory': 'Homepage',
'eventAction': 'Video Play',
'eventLabel': label,
'eventValue': null,
'hitCallback': function()
{
// redirect here
},
'transport': 'beacon',
'nonInteraction': (interactive || true ? 0 : 1)
});
[Here's a complete list of parameters for all possible ga calls.]
In addition I've added the transport parameter set to beacon (not actually needed because it's automatically set if appropriate):
This specifies the transport mechanism with which hits will be sent.
The options are 'beacon', 'xhr', or 'image'. By default, analytics.js
will try to figure out the best method based on the hit size and
browser capabilities. If you specify 'beacon' and the user's browser
does not support the navigator.sendBeacon method, it will fall back
to 'image' or 'xhr' depending on hit size.
So when using navigator.beacon the navigation won't interrupt the tracking . Unfortunately Microsoft's support for beacon is non existent so you should still put the redirect in a callback.
In event handler you should setup hit callback:
_gaq.push(['_set', 'hitCallback', function(){
document.location = ...
}]);
send you data
_gaq.push(['_trackEvent'
and stop event event processing
e.preventDefault();
e.stopPropagation();
I'm trying out a new approach where we build the URL for utm.gif ourselves, and request it, then only once we've received the response (the gif) we send the user on their way:
Usage:
trackPageview(url, function() { document.location = url; });
Code (CrumbleCookie from: http://www.dannytalk.com/read-google-analytics-cookie-script/)
/**
* Use this to log a pageview to google and make sure it gets through
* See: http://www.google.com/support/forum/p/Google%20Analytics/thread?tid=5f11a529100f1d47&hl=en
*/
function trackPageview(url, fn) {
var utmaCookie = crumbleCookie('__utma');
var utmzCookie = crumbleCookie('__utmz');
var cookies = '__utma=' + utmaCookie + ';__utmz=' + utmzCookie;
var requestId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
var hId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
var utmUrl = 'http://www.google-analytics.com/__utm.gif';
utmUrl += '?utmwv=4.8.9';
utmUrl += '&utmn=' + requestId;
utmUrl += '&utmhn=' + encodeURIComponent(window.location.hostname);
utmUrl += '&utmhid=' + hId;
utmUrl += '&utmr=-';
utmUrl += '&utmp=' + encodeURIComponent(url);
utmUrl += '&utmac=' + encodeURIComponent(_gaProfileId);
utmUrl += '&utmcc=' + encodeURIComponent(cookies);
var image = new Image();
image.onload = function() { fn(); };
image.src = utmUrl;
}
/**
* #author: Danny Ng (http://www.dannytalk.com/read-google-analytics-cookie-script/)
* #modified: 19/08/10
* #notes: Free to use and distribute without altering this comment. Would appreciate a link back :)
*/
// Strip leading and trailing white-space
String.prototype.trim = function() { return this.replace(/^\s*|\s*$/g, ''); }
// Check if string is empty
String.prototype.empty = function() {
if (this.length == 0)
return true;
else if (this.length > 0)
return /^\s*$/.test(this);
}
// Breaks cookie into an object of keypair cookie values
function crumbleCookie(c)
{
var cookie_array = document.cookie.split(';');
var keyvaluepair = {};
for (var cookie = 0; cookie < cookie_array.length; cookie++)
{
var key = cookie_array[cookie].substring(0, cookie_array[cookie].indexOf('=')).trim();
var value = cookie_array[cookie].substring(cookie_array[cookie].indexOf('=')+1, cookie_array[cookie].length).trim();
keyvaluepair[key] = value;
}
if (c)
return keyvaluepair[c] ? keyvaluepair[c] : null;
return keyvaluepair;
}
Using onmousedown instead of onclick may also help. It doesn't eliminate the race condition, but it gives GA a head start. There's also the concern of someone clicking on a link and dragging away before letting go of the mouse button, but that's probably a negligible case.