Using Pusher to add markers on a map? - ruby-on-rails-3

I am working to create a map to show based upon IP's of the people on my site. I was thinking a cool map showing all the places around the globe that were hitting the map. But I have come into a little issue. I have the ip's being tracked, and then from their they are processed in a Geo query, and finally I'm using pusherapp to post the data to the map. However, now I can make a unordered list, by appending the datato my site, but I can't figure out how to add it to my google map, nor have that map redraw with the data. Can someone help me here?
So basically on the server side:
var bounds = new google.maps.LatLngBounds();
var latlng = new google.maps.LatLng( #{#a.lat}, #{#a.lng});
var marker = new google.maps.Marker({
animation: google.maps.Animation.DROP,
icon: 'images/marker.png',
map: map,
position: latlng,
title: 'Here'
});
bounds.extend(latlng);
map.fitBounds(bounds);
followed by on the client side:
channel.bind('latitude', function(data){
$('#list').append(data);
});
But even if I do get it to plop into the script section is that google map going to know how to update?
I would really like to keep this all in Rails, and for the service I'm right now using pusher
Thanks!

thanks for using our service.
By the sounds of it you should be using our presence functionality so that you can keep track of users leaving and joining your site.
Part of the presence functionality is that when a user subscribes to a channel an AJAX call is made to your server. Within that call you can do your IP lookup and then return the details (I would suggest just the lat and long values) of that lookup as the user_info to the presence request. See Authenticating Users for more information on this. These docs also provide a Rails example of authenticating a user and providing user_info for that user.
When any user subscribes the the presence channel in the client they will get an initial list of connected users. You can loop through that user list and get the info for each user and add a marker to the Google map. When new users join you will get member_added events on the channel and you can add a new marker. When users leave you will get a member_removed event and you can remove a marker.
An example of the client code might be:
var channel = pusher.subscribe('presence-site-map-channel');
// Initial list of users on the site
channel.bind('pusher:subscription_succeeded', function(members) {
members.each(function(member) {
addMember(member);
});
});
channel.bind('pusher:member_added, addMember);
channel.bind('pusher:member_removed, function() { /* TODO: implement */});
function addMember(member) {
var bounds = new google.maps.LatLngBounds();
var latlng = new google.maps.LatLng( member.info.lat, member.info.long);
var marker = new google.maps.Marker({
animation: google.maps.Animation.DROP,
icon: 'images/marker.png',
map: map,
position: latlng,
title: member.info.id // each user must have a unique ID and you could use it here
});
bounds.extend(latlng);
map.fitBounds(bounds);
};

Related

Managing 2 conferences with Voximplant scenario

I am trying to make conference with Voximplant, and when user makes a call to another user, while the call is still going on, it makes another call to another user making two calls and the callees is added to a video conferencing.
But it seems the caller is billed twice and the scenerio doesnt look optimised. What should i do to bill once and optimize it?
Scenario:
require(Modules.Conference);
var call, conf = null;
VoxEngine.addEventListener(AppEvents.Started, handleConferenceStarted);
function handleConferenceStarted(e) {
// Create 2 conferences right after session to manage audio in the right way
if( conf === null ){
conf = VoxEngine.createConference(); // create conference
}
conf.addEventListener(CallEvents.Connected,function(){
Logger.write('Conference started')
})
}
VoxEngine.addEventListener(AppEvents.CallAlerting, function(e) {
e.call.addEventListener(CallEvents.Connected, handleCallConnected);
let new_call = VoxEngine.callUser(e.destination,e.callerid,e.displayName,{},true)
new_call.addEventListener(CallEvents.Connected,handleCallConnected);
e.call.answer();
});
function handleCallConnected(e) {
Logger.write('caller connected');
conf.add({
call: e.call,
mode: "FORWARD",
direction: "BOTH", scheme: e.scheme
});
}
You need to end the conference when there are no participants. Refer to the following article in our documentation: https://voximplant.com/docs/guides/conferences/howto. You can find the full scenario code there.
Additionally, I recommend to add some handlers for the CallEvents.Disconnected and the CallEvent.Failed events right after
new_call.addEventListener(CallEvents.Connected,handleCallConnected);
because sometimes the callee may be offline or press a reject button. 🙂

How to limit search results to an extent with Esri Javascript map?

I believe by default the 4.13 Esri Javascript map will search addresses by the current map view extent. The issue is that if the user zooms in or out too far, the search results return addresses VERY far away. Here is my code:
function initESRIMap() {
require(["esri/Map", "esri/views/MapView", "esri/widgets/Search", "esri/layers/FeatureLayer", "esri/widgets/Popup", "esri/geometry/Extent", "esri/geometry/Geometry"], function (
Map,
MapView,
Search,
FeatureLayer,
Popup,
Extent,
Geometry
) {
var esriMap = new Map({
basemap: "streets",
});
var esriView = new MapView({
container: "map-div",
map: esriMap,
center: LatLong,
zoom: 11,
});
var search = new Search({
view: esriView,
});}
I want to be able to get the same search results REGARDLESS of my map view location. BUT limit results to a specific area. Therefore if I'm viewing another country I'll still see search results from my extent.
A search widget can be customized to use custom sources, and a custom source can be customized to use a filter, which can include a map extent.
For example, let's define a custom SearchSource - we'll just use the same search endpoint as the standard ArcGIS World Geocoder service, but we'll add a filter:
const source = {
locator: new Locator({
url: "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer"
}),
filter: {
geometry: new Extent({
xmax: -12921954.804910611,
xmin: -13126806.04071496,
ymax: 3909898.0736186495,
ymin: 3801204.619397087,
spatialReference: {
wkid: 102100
}
})
},
maxSuggestions: 10
};
Now when we're defining our search widget, we'll include this in the source property array, and we'll turn off includeDefaultSources so that the original, unbounded source, is not used:
const search = new Search({
view,
includeDefaultSources: false,
sources: [source]
});
Working Codesandbox
In this example, I set the extent in the filter to be around the area of san diego / tijuana. Wherever you pan or zoom on the map, you'll still only get results from that area. For example, move the map over to seattle, or china, or whatever, and search for something generic ("park", "road", whatever), and you'll see that no matter what the map's extent is, the search results will be limited to what you put in the search source's filter.
Note this answer was written with 4.18.

Using the same InfoWindow for two AttributeInspectors

I've got two editable feature layers in my application, and I'm trying to attach the appropriate attribute inspector depending on which feature layer my user is trying to edit.
I create the Attribute Inspectors for both feature layers when my application loads and then attach the appropriate attribute inspector to the map's InfoWindow when the user is trying to edit a feature layer.
All works well, until the user tries to edit another feature layer. When I try and attach a different attribute inspector to the infowindow, it just comes up blank.
Here's roughly what I'm doing:
// AttributeEditor1 for FeatureLayer1 in Class1
constructor: function(options) {
this.options = lang.mixin(this.options, options);
this.map = options.map;
this.configureAttributeEditor1();
},
configureAttributeEditor1: function() {
this.attributeEditor1 = new AttributeInspector({
layerInfos: layerInfos
}, domConstruct.create("div"));
// here I add a Save and Delete button and various event handlers
this.attributeEditor1.startup();
},
// I call this when I know that the user wants to edit FeatureLayer 1
attachEditor1: function() {
this.map.infoWindow.setContent(this.attributeEditor1.domNode);
this.map.infoWindow.resize(350, 240);
},
// AttributeEditor2 for FeatureLayer2 in Class2
constructor: function(options) {
this.options = lang.mixin(this.options, options);
this.map = options.map;
this.configureAttributeEditor2();
},
configureAttributeEditor2: function() {
this.attributeEditor2 = new AttributeInspector({
layerInfos: layerInfos
}, domConstruct.create("div"));
// here I add a Save and Delete button and various event handlers
this.attributeEditor2.startup();
},
// I call this when I know that the user wants to edit FeatureLayer 2
attachEditor2: function() {
this.map.infoWindow.setContent(this.attributeEditor2.domNode);
this.map.infoWindow.resize(350, 240);
},
Thanks in advance.
When you update the content of infoWindow using map.infoWindow.setContent, it destroys the previous content and then updated the new content. So basically your AttributeInspector1 will be destroyed when you updated the info window with AttributeInspector2.
You need not create multiple AttributeInspector, while working on multiple FeatureLayer. The layerInfos property is an array type, you could set upd multiple layers.
But, I guess you have different needs/actions to be taken when you switch between layers. The best thing you could do is to either create new AttributeInspector every time you switch, or just update the layerInfos and save and delete events. Make sure you remove the previous save and delete event handles.

How to create a functioning small thumbnail with small play button with Spotify Apps API?

somewhat of a javascript novice here.
I'm trying to create this: http://i.imgur.com/LXFzy.png from the Spotify UI Guidelines.
Basically a 64x64 album cover with an appropriate sized play button.
This is what I have so far:
function DataSource(playlist) {
this.count = function() {
return playlist.length;
}
// make node with cover, trackname, artistname
this.makeNode = function(track_num) {
var t = playlist.data.getTrack(track_num);
// console.log(t);
var li = new dom.Element('li');
//generate cover image with play/pause button
var track = m.Track.fromURI(t.uri, function(a) {
var trackPlayer = new v.Player();
trackPlayer.track;
trackPlayer.context = a;
dom.inject(trackPlayer.node, li, 'top')
});
//track name
var trackName = new dom.Element('p', {
className: 'track',
text: t.name
});
//artist name
var artistName = new dom.Element('p', {
className: 'artist',
text: t.artists[0].name
});
dom.adopt(li, trackName, artistName);
return li;
}
}
This datasource function feeds into a pager function later in the code. This code generates image, artist name and track name just fine except I can't seem to get the image to be 64x64 without overriding with my own css. I'm sure there is a way to set this in javascript since the core Spotify CSS files include a class for it however I'm at a loss at how to do it.
Also the play button renders but gives an error in the console that the track has no method 'get' when I click on it. How am I suppose to know it needs a get? Is there some way I can see this player function so I know what I'm doing wrong with it?
Any help would be greatly appreciated, I'm sure it'll help droves of people too as there is no documentation anywhere I can find on how to do this.
Check the code here: https://github.com/ptrwtts/kitchensink/blob/master/js/player.js
The kitchensink app displays a lot of the Spotify Apps API functionality
For the playback button, I know that it doesn't seem to actually work for single tracks used as the context. It really only works if you use either an Artist, Album, or Playlist context. Not sure why that is.

How do I get data from a background page to the content script in google chrome extensions

I've been trying to send data from my background page to a content script in my chrome extension. i can't seem to get it to work. I've read a few posts online but they're not really clear and seem quite high level. I've got managed to get the oauth working using the Oauth contacts example on the Chrome samples. The authentication works, i can get the data and display it in an html page by opening a new tab.
I want to send this data to a content script.
i'm having a lot of trouble with this and would really appreciate if someone could outline the explicit steps you need to follow to send data from a bg page to a content script or even better some code. Any takers?
the code for my background page is below (i've excluded the oauth paramaeters and other )
` function onContacts(text, xhr) {
contacts = [];
var data = JSON.parse(text);
var realdata = data.contacts;
for (var i = 0, person; person = realdata.person[i]; i++) {
var contact = {
'name' : person['name'],
'emails' : person['email']
};
contacts.push(contact); //this array "contacts" is read by the
contacts.html page when opened in a new tab
}
chrome.tabs.create({ 'url' : 'contacts.html'}); sending data to new tab
//chrome.tabs.executeScript(null,{file: "contentscript.js"});
may be this may work?
};
function getContacts() {
oauth.authorize(function() {
console.log("on authorize");
setIcon();
var url = "http://mydataurl/";
oauth.sendSignedRequest(url, onContacts);
});
};
chrome.browserAction.onClicked.addListener(getContacts);`
As i'm not quite sure how to get the data into the content script i wont bother posting the multiple versions of my failed content scripts. if I could just get a sample on how to request the "contacts" array from my content script, and how to send the data from the bg page, that would be great!
You have two options getting the data into the content script:
Using Tab API:
http://code.google.com/chrome/extensions/tabs.html#method-executeScript
Using Messaging:
http://code.google.com/chrome/extensions/messaging.html
Using Tab API
I usually use this approach when my extension will just be used once in a while, for example, setting the image as my desktop wallpaper. People don't set a wallpaper every second, or every minute. They usually do it once a week or even day. So I just inject a content script to that page. It is pretty easy to do so, you can either do it by file or code as explained in the documentation:
chrome.tabs.executeScript(tab.id, {file: 'inject_this.js'}, function() {
console.log('Successfully injected script into the page');
});
Using Messaging
If you are constantly need information from your websites, it would be better to use messaging. There are two types of messaging, Long-lived and Single-requests. Your content script (that you define in the manifest) can listen for extension requests:
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.method == 'ping')
sendResponse({ data: 'pong' });
else
sendResponse({});
});
And your background page could send a message to that content script through messaging. As shown below, it will get the currently selected tab and send a request to that page.
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {method: 'ping'}, function(response) {
console.log(response.data);
});
});
Depends on your extension which method to use. I have used both. For an extension that will be used like every second, every time, I use Messaging (Long-Lived). For an extension that will not be used every time, then you don't need the content script in every single page, you can just use the Tab API executeScript because it will just inject a content script whenever you need to.
Hope that helps! Do a search on Stackoverflow, there are many answers to content scripts and background pages.
To follow on Mohamed's point.
If you want to pass data from the background script to the content script at initialisation, you can generate another simple script that contains only JSON and execute it beforehand.
Is that what you are looking for?
Otherwise, you will need to use the message passing interface
In the background page:
// Subscribe to onVisited event, so that injectSite() is called once at every pageload.
chrome.history.onVisited.addListener(injectSite);
function injectSite(data) {
// get custom configuration for this URL in the background page.
var site_conf = getSiteConfiguration(data.url);
if (site_conf)
{
chrome.tabs.executeScript({ code: 'PARAMS = ' + JSON.stringify(site_conf) + ';' });
chrome.tabs.executeScript({ file: 'site_injection.js' });
}
}
In the content script page (site_injection.js)
// read config directly from background
console.log(PARAM.whatever);
I thought I'd update this answer for current and future readers.
According to the Chrome API, chrome.extension.onRequest is "[d]eprecated since Chrome 33. Please use runtime.onMessage."
See this tutorial from the Chrome API for code examples on the messaging API.
Also, there are similar (newer) SO posts, such as this one, which are more relevant for the time being.