I am using Google Cloud Channels in Android's WebView, but the same problem probably occurs as well when using any socket in a similar way.
Problem: The argument is not passed on by the handler, possibly because functions are called in a different scope.
Here is my code:
<html>
<head>
<script src='{{ channelurl }}jsapi'></script>
</head>
<body>
<script type="text/javascript">
onMessage = function(message) {
};
var token = '{{ token }}';
var channel = new goog.appengine.Channel(token);
var handler = {
'onmessage': onMessage,
};
var socket = channel.open(handler);
socket.onmessage = onMessage;
</script>
</body>
</html>
onMessage has a single argument (string) and my onMessage function is properly called, but the argument is 'undefined', probably because it is in a different scope.
This question might be a duplicate of these or other similar questions, and I tried to apply the recipes given there, but no success.
How do I pass arguments to an event handler?
How to pass event as argument to an inline event handler in JavaScript?
I actually applied the code from here
http://2cor214.blogspot.com/2010/08/passing-arguments-to-event-handler-in.html
and tried to play with things like this:
socket.onmessage = (function (message) {return onMessage})(message)
in many variants, but couldn't get it to work.
I admit I am not normally developing JavaScript and I don't exactly understand what JavaScript does here, but it seems to me that the argument needs to be extracted the function wrapped somehow.
Can anybody shed light please.
--
I removed parts of my code for brevity.
The problem was not a JavaScript issue, as I assumed. The assumption that the same issue would occur with any socket type was also incorrect.
The problem had to do with how Google Cloud Channels hands over the message in onMessage().
As I could see from code that Google posted for their Tic Tac Toe example here http://code.google.com/p/channel-tac-toe/source/browse/trunk/index.html, line 175ff, they hand over the string as an event with the variable "data", but called the argument "m" (as in message).
onOpened = function() {
sendMessage('/opened');
};
onMessage = function(m) {
newState = JSON.parse(m.data);
state.board = newState.board || state.board;
state.userX = newState.userX || state.userX;
state.userO = newState.userO || state.userO;
state.moveX = newState.moveX;
state.winner = newState.winner || "";
state.winningBoard = newState.winningBoard || "";
updateGame();
}
openChannel = function() {
var token = '{{ token }}';
var channel = new goog.appengine.Channel(token);
var handler = {
'onopen': onOpened,
'onmessage': onMessage,
'onerror': function() {},
'onclose': function() {}
};
var socket = channel.open(handler);
socket.onopen = onOpened;
socket.onmessage = onMessage;
}
Kind of confusing and undocumented here https://cloud.google.com/appengine/docs/java/channel/?csw=1 and here https://cloud.google.com/appengine/docs/java/channel/javascript, but when I changed the message signature to
onMessage = function(message) {
ChannelListener.onMessage(message.data); // message
};
it worked perfectly fine.
Related
I am developing my app, and one of the features will be messaging within the application. What I did, is I've developed 'send message' window, where user can send message to other user. The logic behind it is as following:
1. User A sends message to User B.
2. Firebase creates following nodes in 'Messaging':
"Messaging"->"User A"->"User B"->"Date & Time"->"UserA: Message"
"Messaging"->"User B"->"User A"->"Date & Time"->"UserA: Message"
Here is the code that I am using for sending messages:
sendMsg: function(receiver, content) {
var user = Auth.getUser();
var sender = user.facebook.id;
var receiverId = receiver;
var receiverRef = $firebase(XXX.firebase.child("Messaging").child(receiverId).child(sender).child(Date()));
var senderRef = $firebase(XXX.firebase.child("Messaging").child(sender).child(receiverId).child(Date()));
receiverRef.$set(sender,content);
senderRef.$set(sender,content);
},
(picture 1 in imgur album)
At the moment, I am trying to read the messages from the database, and sort them in according to date. What I've accomplished so far, is that I have stored the content of "Messaging/UserA/" in form of an Object. The object could be seen in the picture I've attached (picture 2).
http://imgur.com/a/3zQ0o
Code for data receiving:
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
Messages.on("value", function (snapshot) {
var messagesObj = snapshot.val();
return messagesObj;
}, function (errorObject) {
console.log("Error code: " + errorObject.code);
});
}
My question is: how can I read the object's messages? I would like to sort the according to the date, get the message and get the Id of user who has sent the message.
Thank you so much!
You seem to be falling for the asynchronous loading trap when you're reading the messages:
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
Messages.on("value", function (snapshot) {
var messagesObj = snapshot.val();
return messagesObj;
}, function (errorObject) {
console.log("Error code: " + errorObject.code);
});
}
That return statement that you have in the Messages.on("value" callback doesn't return that value to anyone.
It's often a bit easier to see what is going on, if we split the callback off into a separate function:
onMessagesChanged(snapshot) {
// when we get here, either the messages have initially loaded
// OR there has been a change in the messages
console.log('Inside on-value listener');
var messagesObj = snapshot.val();
return messagesObj;
},
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
console.log('Before adding on-value listener');
Messages.on("value", onMessagesChanged);
console.log('After adding on-value listener');
}
If you run the snippet like this, you will see that the console logs:
Before adding on-value listener
After adding on-value listener
Inside on-value listener
This is probably not what you expected and is caused by the fact that Firebase has to retrieve the messages from its servers, which could potentially take a long time. Instead of making the user wait, the browser continues executing the code and calls your so-called callback function whenever the data is available.
In the case of Firebase your function may actually be called many times, whenever a users changes or adds a message. So the output more likely will be:
Before adding on-value listener
After adding on-value listener
Inside on-value listener
Inside on-value listener
Inside on-value listener
...
Because the callback function is triggered asynchronously, you cannot return a value to the original function from it. The simplest way to work around this problem is to perform the update of your screens inside the callback. So say you want to log the messages, you'd do:
onMessagesChanged(snapshot) {
// when we get here, either the messages have initially loaded
// OR there has been a change in the messages
console.log('Inside on-value listener');
var i = 0;
snapshot.forEach(function(messageSnapshot) {
console.log((i++)+': '+messageSnapshot.val());
});
},
Note that this problem is the same no matter what API you use to access Firebase. But the different libraries handle it in different ways. For example: AngularFire shields you from a lot of these complexities, by notifying AngularJS of the data changes for you when it gets back.
Also see: Asynchronous access to an array in Firebase
I'm trying to send a message to my custom receiver for ChromeCast. I use the following code in my Android app to send a code to the receiver;
Cast.CastApi.sendMessage(mApiClient, "urn:x-cast:move", "TEST");
On the receiving side I have the following code;
window.mediaElement = document.getElementById('media');
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
window.castReceiverManager.start();
window.castReceiverManager.onSenderConnected = function(event) {
//This is called
}
window.customMessageBus = window.castReceiverManager.getCastMessageBus('urn:x-cast:move', cast.receiver.CastMessageBus.MessageType.STRING);
var defaultFunction = window.customMessageBus.onMessage;
window.customMessageBus.onMessage = function(event) {
//This is not called
defaultFunction(event);
};
As I pointed out in the code, the 'onSenderConnected' is called, so I know it connected to the app. But when I try to send a message over the custom messagebus, it doesnt give me anything. I'm completely new to android and cast, so I could be doing a thousand things wrong. Can anybody help me solve this?
I solved it myself. What I did wrong was starting the castReceiverManager before adding the custom namespace. So the correct code for the receiver compared to what I posted in the question would be;
window.mediaElement = document.getElementById('media');
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
//Removed the start here
window.castReceiverManager.onSenderConnected = function(event) {
//OnConnect
}
window.customMessageBus = window.castReceiverManager.getCastMessageBus('urn:x-cast:move', cast.receiver.CastMessageBus.MessageType.STRING);
var defaultFunction = window.customMessageBus.onMessage;
window.customMessageBus.onMessage = function(event) {
//OnMessage
defaultFunction(event);
};
//Start at the end
window.castReceiverManager.start();
I can return a value if I send a sync message:
// frame script
var chromeBtnText = sendSyncMessage("getChromeToolbarButtonText");
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
// chrome script
messageManager.addMessageListener("getChromeToolbarButtonText", listener);
function listener(message) {
return document.getElementById('myChromeToolbarButton').label.value;
}
How do I achieve this with a callback with sendAsyncMessage?
I was hoping to do something like:
// frame script
function myCallback(val) {
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
var chromeBtnText = sendAsyncMessage("getChromeToolbarButtonText", null, myCallback);
There is no callback for replies. In fact, there is no reply at all. The return value from the chrome message listener is simply ignored for async messages.
To do fully async communication, you'd have to send another message containing the reply.
Frame script
addMessageListener("getChromeToolbarButtonTextReply", function(message) {
alert(message.data.btnText);
});
sendAsyncMessage("getChromeToolbarButtonText");
Chrome
messageManager.addMessageListener("getChromeToolbarButtonText", function(message) {
var btnText = document.getElementById('myChromeToolbarButton').label.value;
// Only send message to the frame script/message manager
// that actually asked for it.
message.target.messageManager.sendAsyncMessage(
"getChromeToolbarButtonTextReply",
{btnText: btnText}
);
});
PS: All messages share a namespace. So to avoid conflicts when another piece of code wants to use the same name getChromeToolbarButtonText, you better choose a more unique name in the first place, like prefixing your messages with your add-on name my-unique-addoon:getChromeToolbarButtonText or something like that. ;)
I was also hoping to do something similar:
messageManager.sendAsyncMessage("my-addon-framescript-message", null, myCallback);
I'm going the other direction so the myCallback would be in chrome but it's exactly the same principle.
I'd used similar approaches to #Noitidart and #nmaier before but in this new case I wanted to bind to some local data so myCallback can behave differently based on the application state at the time the first message was sent rather than at the time the callback is executed, all while allowing for the possibility of multiple message round-trips being in progress concurrently.
Chrome:
let someLocalState = { "hello": "world" };
let callbackName = "my-addon-somethingUnique"; // based on current state or maybe generate a UUID
let myCallback = function(message) {
messageManager.removeMessageListener(callbackName, myCallback);
//message.data.foo == "bar"
//someLocalState.hello == "world"
}.bind(this); // .bind(this) is optional but useful if the local state is attached to the current object
messageManager.addMessageListener(callbackName, myCallback);
messageManager.sendAsyncMessage("my-addon-framescript-message", { callbackName: callbackName } );
Framescript:
let messageHandler = function(message) {
let responseData = { foo: "bar" };
sendAsyncMessage(message.data.callbackName, responseData);
};
addMessageListener("my-addon-framescript-message", messageHandler);
There's a real-world example here: https://github.com/luckyrat/KeeFox/commit/c50f99033d2d07068140438816f8cc5e5e290da9
It should be possible for Firefox to be improved to encapsulate this functionality in the built-in messageManager one day but in the mean-time this approach works well and with a surprisingly small amount of boiler-plate code.
in this snippet below. i add the callback before sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarbuttonText'... as i know it will send back. Then I remove it after callback executes. I know I don't have to but just to kind of make it act like real callback, just to kind of show people, maybe it helps someone understand.
Frame:
/////// frame script
function CALLBACK_getChromeToolbarButtonText(val) {
removeMessageListner('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //remove the callback
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
addMessageListener('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //add the callback
var chromeBtnText = sendAsyncMessage("my-addon-id#jetpack:getChromeToolbarButtonText", null);
Chrome:
////// chrome script
messageManager.addMessageListener("my-addon-id#jetpack:getChromeToolbarButtonText", listener);
function listener() {
var val = document.getElementById('myChromeToolbarButton').label.value;
sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage',val);
}
This question already has answers here:
How does JavaScript .prototype work?
(26 answers)
Closed 9 years ago.
I am making tools with js API named vLine,
but it is basic quesion of javascript so I post here.
I want to attache chat system on sample code.
What I have adde tis between ///chat function
but
this.prototype.onMessage_
shows error like
Uncaught TypeError: Cannot set property 'onMessage_' of undefined
I have made a few javascript programings though,I am not good at this.
So I think I didnt understand ,something very basic javascript object oriented.
please help me.
<script>
var vlineClient = (function(){
if('{{vlineData.serviceId}}' == 'YOUR_SERVICE_ID' || '{{vlineData.serviceId}}' == 'YOUR_SERVICE_ID'){
alert('Please make sure you have created a vLine service and that you have properly set the $serviceID and $apiSecret variables in classes/Vline.php file.');
}
var client, vlinesession,
authToken = '{{ vlineData.authToken }}',
serviceId = '{{ vlineData.serviceId }}',
profile = {"displayName": '{{ vlineData.displayName }}', "id": '{{ vlineData.id }}'};
// Create vLine client
window.vlineClient = this.client_ = vline.Client.create({"serviceId": serviceId, "ui": true});
// Add login event handler
this.client_.on('login', onLogin);
this.client_.login(serviceId, profile, authToken).done(this.init_,this);
// Do login
**/////chat function**
this.prototype.onMessage_ = function(event) {
var msg = event.message,
sender = msg.getSender();
console.log('get message');
this.showAlert(sender.getDisplayName(),
sender.getThumbnailUrl(),
msg.getBody());
};
this.client_.on('recv:im', this.onMessage_, this);
**/////chat function**
function onLogin(event) {
vlinesession = event.target;
// Find and init call buttons and init them
$(".callbutton").each(function(index, element) {
initCallButton($(this));
});
}
// add event handlers for call button
function initCallButton(button) {
var userId = button.attr('data-userid');
// fetch person object associated with username
vlinesession.getPerson(userId).done(function(person) {
// update button state with presence
function onPresenceChange() {
if(person.getPresenceState() == 'online'){
button.removeClass().addClass('active');
}else{
button.removeClass().addClass('disabled');
}
button.attr('data-presence', person.getPresenceState());
}
// set current presence
onPresenceChange();
// handle presence changes
person.on('change:presenceState', onPresenceChange);
// start a call when button is clicked
button.click(function() {
if (person.getId() == vlinesession.getLocalPersonId()) {
alert('You cannot call yourself. Login as another user in an incognito window');
return;
}
if(button.hasClass('active'))
**/////chat function**
person.postMessage("Hello there");
console.log("send message");
**////chat function**
person.startMedia();
});
});
}
return client;
})();
$(window).unload(function() {
vlineClient.logout();
});
Can not undestand whole lots of things that you have written. But the problem is ver clear. "this", you are expecting to be your method, but you have to very careful of this as it changes context based upon where you are executing it.
If I simplyfy your code, its an example of module pattern, which should be as following.
var moduleExample = (function () {
// private variables and functions
var privateVar = 'bar';
// constructor
var moduleExample = function () {
};
moduleExample.prototype.chat = function(){
alert('hello');
};
// return module
return moduleExample;
})();
var my_module = new moduleExample();
my_module.chat();
Notice how the code above has avoided the use of "this".
Also notice, how a new object is created by using "new"
I'm building a debugging tool for my web app and I need to show console errors in a div. I know I can use my own made console like object and use it, but for future use I need to send all console errors to window. Actually I want to catch console events.
To keep the console working:
if (typeof console != "undefined")
if (typeof console.log != 'undefined')
console.olog = console.log;
else
console.olog = function() {};
console.log = function(message) {
console.olog(message);
$('#debugDiv').append('<p>' + message + '</p>');
};
console.error = console.debug = console.info = console.log
Here's a way using closure, containing the old console log function in the scope of the new one.
console.log = (function (old_function, div_log) {
return function (text) {
old_function(text);
div_log.value += text;
};
} (console.log.bind(console), document.getElementById("error-log")));
None of the answers here consider console messages that get passed multiple parameters. E.g. console.log("Error:", "error details")).
The function that replaces the default log function better regards all function arguments (e.g. by using the arguments object). Here is an example:
console.log = function() {
log.textContent += Array.prototype.slice.call(arguments).join(' ');
}
(The Array.prototype.slice.call(...) simply converts the arguments object to an array, so it can be concatenated easily with join().)
When the original log should be kept working as well:
console.log = (function (old_log, log) {
return function () {
log.textContent += Array.prototype.slice.call(arguments).join(' ');
old_log.apply(console, arguments);
};
} (console.log.bind(console), document.querySelector('#log')));
A complete solution:
var log = document.querySelector('#log');
['log','debug','info','warn','error'].forEach(function (verb) {
console[verb] = (function (method, verb, log) {
return function () {
method.apply(console, arguments);
var msg = document.createElement('div');
msg.classList.add(verb);
msg.textContent = verb + ': ' + Array.prototype.slice.call(arguments).join(' ');
log.appendChild(msg);
};
})(console[verb], verb, log);
});
(An example of a framework that emits messages with multiple parameters is Video.js. But there is certainly many others.)
Edit: Another use of multiple parameters is the formatting capabilities of the console (e.g. console.log("Status code: %d", code).
About errors that are not shown
(Update Dec. 2021)
If any code crashes with an uncaught error, in might not show up in the div. One solution could be, if possible, to wrap all code in a try block to catch such errors and log them manually to the div.
try {
// Code that might throw errors...
} catch(err) {
// Pass the error to the overridden error log handler
console.error(err);
}
Else, if you were concerned at keeping log, warn and error separate from one another, you could do something like this (adapted from MST's answer):
var log = document.querySelector('#log');
['log','warn','error'].forEach(function (verb) {
console[verb] = (function (method, verb, log) {
return function (text) {
method(text);
// handle distinguishing between methods any way you'd like
var msg = document.createElement('code');
msg.classList.add(verb);
msg.textContent = verb + ': ' + text;
log.appendChild(msg);
};
})(console[verb].bind(console), verb, log);
});
where #log is your HTML element. The variable verb is one of 'log', 'warn', or 'error'. You can then use CSS to style the text in a distinguishable way. Note that a lot of this code isn't compatible with old versions of IE.
How about something as simple as:
console.log = function(message) {$('#debugDiv').append('<p>' + message + '</p>');};
console.error = console.debug = console.info = console.log
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<div id="logger" class="web_console"></div>
<script type="text/javascript">
// Overriding console object
var console = {};
// Getting div to insert logs
var logger = document.getElementById("logger");
// Adding log method from our console object
console.log = function(text)
{
var element = document.createElement("div");
var txt = document.createTextNode(text);
element.appendChild(txt);
logger.appendChild(element);
}
// testing
console.log("Hello World...");
console.log("WOW");
/**
console.log prints the message in the page instead browser console, useful to programming and debugging JS using a Android phone
*/
</script>
</body>
</html>
I created a zero-dependency npm module for this case: console-events (surely if you're okay to use nodejs :P)
You can add event listener like that:
const { console } = require('console-events');
console.addEventListener('log', (e) => {
e.preventDefault(); //if you need to prevent normal behaviour e.g. output to devtools console
$('#debugDiv').append('<p>' + message + '</p>');
})