Read a GeometryCollection from a string - openlayers-3

in OpenLayers 3 I can read Features or a single Geometry from a GeoJSON string through an instance of ol.format.GeoJSON().
Example:
var parser = new ol.format.GeoJSON();
var arrOfFeatures = parser.readFeatures(geoJsonString);
//or
var usrGeom = parser.readGeometry(geoJsonString);
Now I have a string representing a GeometryCollection (not a MultiPoint, MultiLine or MultyPolygon, nor a FeatureCollection).
I cannot find a way to readGeometryCollection(GeoJSONstring) or something, having an ol.geom.GeometryCollection object as the result.
Can anyone help with this?
Thanks in advance.

Ok, the solution was to read a GeometryCollection just like any other Openlayers ol.geom.Geometry and then loop to read any single geometry, so: 1)read the string into a Geometry object, 2) if .getType() is "GeometryCollection" then loop the collection, and forEach(item) in the collection read the Geometry.
parseSingleGeometry : function(geometry, callback) {
switch (geometry.getType()) {
case 'MultiLineString':
geometry.getLineStrings().forEach(function (p) {
var newFeat = new ol.Feature({ geometry: p });
_features.push(newFeat);
});
break;
case 'MultiPolygon':
geometry.getPolygons().forEach(function (p) {
var newFeat = new ol.Feature({ geometry: p });
_features.push(newFeat);
});
break;
case 'Polygon':
case 'LineString':
case 'Point':
var newFeat = new ol.Feature({ geometry: geometry });
_features.push(newFeat);
delete newFeat;
break;
default:
break;
}
if (callback) { callback(); }
},
parseGeometryCollection: function(collection, callback) {
var ciccio = new ol.geom.GeometryCollection();
//ciccio.getType
var itemCount = ciccio.getGeometries().length;
var itemProg = 0;
ciccio.getGeometries().forEach(function (item) {
parseSingleGeometry(item, function () {
itemProg++;
if (itemProg == itemCount) {
if (callback) {
callback();
}
}
})
});
}
[...]
importGeoJson: function (geoJsonString) {
var parser = new ol.format.GeoJSON();
var projection = parser.readProjection(geoJsonString);
var usrProjCode = projection.getCode();
var convert = false;
if (usrProjCode != 'EPSG:3857') {
var msg = "The file provided is in the " + usrProjCode + " projection. The system supports the EPSG:3857 projection only.\n\rWe are converting your geometries in EPSG:3857.\n\rPlease visually check that the added shapes are what your file defines.";
alert(msg);
convert = true;
}
//if it's a feature or feature collection than I can readFeatures()
if (geoJsonString.indexOf("Feature") !== -1) {
console.log("reading feature(s)");
var arrOfFeatures = parser.readFeatures(geoJsonString);
arrOfFeatures.forEach(function (item) {
var usrGeom = item.getGeometry();
var newGeom = (convert) ? usrGeom.clone().transform(usrProjCode, 'EPSG:3857') : usrGeom;
if (newGeom.getType() == "GeometryCollection") {
_private.parseGeometryCollection(newGeom, function () { /*do something*/ })
}
else {
_private.parseSingleGeometry(newGeom, function () { /*do something*/ });
}
});
}
else { //otherwise work with geometries
var usrGeom = parser.readGeometry(geoJsonString);
var newGeom = (convert) ? usrGeom.clone().transform(usrProjCode, 'EPSG:3857') : newGeom;
if (newGeom.getType() == "GeometryCollection") {
_private.parseGeometryCollection(newGeom, function () { /*do something*/ })
}
else {
_private.parseSingleGeometry(newGeom, function () { /*do something*/ });
}
}
},
_features is the ol.Collection containing the features showed on the map.

Related

Need help in simplifying the code to synchronise tooltips and crosshairs for Highcharts,

I have created a series of synchronised charts, the code to link the crosshair and tooltip is rather complicated:
function syncronizeCrossHairs(chart) {
['mousemove', 'touchmove', 'touchstart'].forEach(function(eventType) {
var container = $(chart.container),
offset = container.offset(),
x;
container[0].addEventListener(eventType,
(function(evt) {
x = evt.clientX - chart.plotLeft - offset.left;
//remove old plot line and draw new plot line (crosshair) for this chart
var xAxis1 = chart1.xAxis[0],
points = [],
points1 = [],
points2 = [],
points3 = [],
e = chart1.pointer.normalize(evt); // Find coordinates within the chart
chart1.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points.push(point)
}
})
if (points) {
var number = 0;
Highcharts.each(points, function(p, i) {
if (!p.series.visible) {
points.splice(i - number, 1);
number++;
}
})
if (points.length) {
chart1.tooltip.refresh(points); // Show the tooltip
}
}
xAxis1.drawCrosshair(x, points[0])
/*----- second chart ------*/
var xAxis2 = chart2.xAxis[0];
chart2.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points1.push(point)
}
})
if (points1[0]) {
var number = 0;
Highcharts.each(points1, function(p, i) {
if (!p.series.visible) {
points1.splice(i - number, 1);
number++;
}
})
if (points1.length) {
chart2.tooltip.refresh(points1); // Show the tooltip
}
}
xAxis2.drawCrosshair(x, points1[0])
/*----- third chart ------*/
var xAxis3 = chart3.xAxis[0];
chart3.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points2.push(point)
}
console.log(points2)
})
if (points2[0]) {
var number = 0;
Highcharts.each(points1, function(p, i) {
if (!p.series.visible) {
points2.splice(i - number, 1);
number++;
}
})
if (points2.length) {
chart3.tooltip.refresh(points2); // Show the tooltip
}
}
xAxis3.drawCrosshair(x, points2[0])
/* ----- fourth chart ------ */
var xAxis4 = chart4.xAxis[0];
chart4.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points3.push(point)
}
})
if (points3[0]) {
var number = 0;
Highcharts.each(points3, function(p, i) {
if (!p.series.visible) {
points3.splice(i - number, 1);
number++;
}
})
if (points3.length) {
chart4.tooltip.refresh(points3); // Show the tooltip
}
}
xAxis4.drawCrosshair(x, points3[0])
}))
})
}
How ever I have found a better example here: http://jsfiddle.net/mushigh/a3kjrz6u/
$('#container').bind('mousemove touchmove touchstart', function(e) {
var chart,
point,
i,
event;
for (i = 0; i < Highcharts.charts.length; i = i + 1) {
chart = Highcharts.charts[i];
event = chart.pointer.normalize(e.originalEvent); // Find coordinates within the chart
point = chart.series[0].searchPoint(event, true); // Get the hovered point
if (point) {
point.onMouseOver(); // Show the hover marker
chart.tooltip.refresh(point); // Show the tooltip
chart.xAxis[0].drawCrosshair(event, point); // Show the crosshair
}
}
});
/**
* Override the reset function, we don't need to hide the tooltips and crosshairs.
*/
Highcharts.Pointer.prototype.reset = function() {
return undefined;
};
How do I adapt my code here: https://jsfiddle.net/ashenshugar/716jx4n9/
To use the simplified code in my example
I don't think that the demo which you have found is a good approach to your requirements.
The demo needs to the specific data structure, like: https://github.com/highcharts/highcharts/blob/master/samples/data/activity.json
The demo is an example to show tooltip for only one series per chart, meanwhile, you have got a few and a shared tooltip, so finally, you will need to do the same calculations to get the array of points for shared tooltip.
Except that, I think that a better approach is to clean up your code.
Notice that the functionalities to calculate points for each chart are similar and can be paste into the loop:
function syncronizeCrossHairs(chart) {
['mousemove', 'touchmove', 'touchstart'].forEach(function(eventType) {
var container = $(chart.container),
offset = container.offset(),
x;
container[0].addEventListener(eventType,
(function(evt) {
x = evt.clientX - chart.plotLeft - offset.left;
Highcharts.charts.forEach(ch => {
var e = ch.pointer.normalize(evt), // Find coordinates within the chart
points = [];
ch.series.forEach(s => {
var point = s.searchPoint(e, true);
if (point) {
point.setState();
points.push(point)
}
})
if (points) {
var number = 0;
Highcharts.each(points, function(p, i) {
if (!p.series.visible) {
points.splice(i - number, 1);
number++;
}
})
if (points.length) {
ch.tooltip.refresh(points); // Show the tooltip
}
}
ch.xAxis[0].drawCrosshair(x, points[0])
})
}))
})
}
And notice that also your afterSetextreme callback can be change to trigger this function:
function setExtremes(chart, min, max) {
Highcharts.charts.forEach(ch => {
if (ch !== chart) {
ch.xAxis[0].setExtremes(min, max)
}
})
}
Also, you can define options which are common for each chart, like it is done here: https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/gauge-solid/
Finally demo: https://jsfiddle.net/BlackLabel/hv7azdgm/

vectorSource.addFeatures with the parameter Array containing more than 10 features using openlayers 4.3.3

I have a map and I'd like to add features. The features are added correctly always at the end of the array. If there are more than 10 features added, the eleven one - is inserted anywhere.
draw.on('drawend', function(evt) {
var feature = evt.feature;
var anfang = ol.proj.transform(feature.getGeometry().getFirstCoordinate(), 'EPSG:3857', 'EPSG:4326');
var ende = ol.proj.transform(feature.getGeometry().getLastCoordinate(), 'EPSG:3857', 'EPSG:4326');
var url = '?lonlats='+anfang+'|'+ende+'&nogos=&profile=shortest&alternativeidx=0&format=geojson';
url = encodeURIComponent(url);
url = broutes+url;
var response = new XMLHttpRequest();
response.open('GET', url);
var onError = function() {
console.log('error');
}
response.onerror = onError;
response.onload = function() {
if (response.status == 200) {
var data = Ext.decode(response.responseText);
var arr = data.features[0].geometry.coordinates;
var arrayNew = [];
for (i=0; i<arr.length; i++){
var n = ol.proj.transform(arr[i], 'EPSG:4326', 'EPSG:3857');
arrayNew.push(n);
}
var data = new ol.geom.LineString( arrayNew );
//console.log(data);
var featureSnake = new ol.Feature({
geometry: data
});
snakeSource.addFeature( featureSnake );
var win = Ext.create('MyProject.view.window.Edit', {
record : featureSnake,
toDo: 'insert',
});
win.show();
} else {
onError();
}
}
I chanced my code like you told me (using version 4.3.3)
vectorSource = new ol.source.Vector({
useSpatialIndex: false
});
Now I have the following result vectorSource.getFeatures()
polygon1
polygon2
...
polygon15
polygon2
polygon3
...
polygon15
polygon1
I have all features twice.

Detecting TabMove in firefox add-on

Tried out the impl. given in : https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser#Notification when a tab is added or removed
for tracking 'tabmove'. Didn't work.
Would appreciate any help in this regard.
BTW, already tried below code. Only 'TabOpen' event is received. 'TabClose' and 'TabMove' does not work:
var browserWindows = require("sdk/windows").browserWindows;
var activeWindow = browserWindows ? browserWindows.activeWindow : {};
var browserWindow = activeWindow ? require("sdk/view/core").viewFor(activeWindow) : {};
var DOMWindow = browserWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
var exampleTabAdded = function(event) {
var browser = DOMWindow.gBrowser.getBrowserForTab(event.target);
console.log('tab added: '+ event.target);
};
var exampleTabMoved = function(event) {
var browser = DOMWindow.gBrowser.getBrowserForTab(event.target);
console.log('tab moved: '+ event.target);
};
function exampleTabRemoved(event) {
var browser = gBrowser.getBrowserForTab(event.target);
console.log('tab removed: '+ event.target);
}
function exampleTabSelected(event) {
var browser = gBrowser.getBrowserForTab(event.target);
console.log('tab selected: '+ event.target);
}
var container = DOMWindow.gBrowser.tabContainer;
container.addEventListener("TabMove", exampleTabMoved, false);
container.addEventListener("TabOpen", exampleTabAdded, false);
container.addEventListener("TabClose", exampleTabRemoved, false);
container.addEventListener("TabSelect", exampleTabSelected, false);
Thanks
This seems to work. Uses both sdk api's as well as XUL content document.
If there is a better way to handle 'tabmove', please do post the answer.
var tabs = require("sdk/tabs");
var { modelFor } = require("sdk/model/core");
var { viewFor } = require("sdk/view/core");
var tab_utils = require("sdk/tabs/utils");
var contentDocumentMap = new Map();
function mapHighLevelTabToLowLevelContentDocument(tab) {
var lowLevelTab = viewFor(tab);
var browser = tab_utils.getBrowserForTab(lowLevelTab);
return browser.contentDocument;
}
function onOpen(tab) {
tab.on("pageshow", logShow);
tab.on("activate", logActivate);
tab.on("deactivate", logDeactivate);
tab.on("close", logClose);
}
function logShow(tab) {
var contentWindow = mapHighLevelTabToLowLevelContentDocument(tab);
if ((contentWindow.URL === 'about:newtab') || (contentWindow.URL === 'about:blank')) {
return;
}
if (contentDocumentMap.has(contentWindow)) {
return;
}
contentDocumentMap.set(contentWindow, tab.id.toString());
}
function logActivate(tab) {
var contentWindow = mapHighLevelTabToLowLevelContentDocument(tab);
if ((contentWindow.URL === 'about:newtab') || (contentWindow.URL === 'about:blank')) {
return;
}
if (contentDocumentMap.has(contentWindow)) {
return;
}
contentDocumentMap.set(contentWindow, tab.id.toString());
}
function logDeactivate(tab) {
setTimeout(function() {
var windows = require("sdk/windows").browserWindows;
for (let window of windows) {
var activeTabContentWindow = mapHighLevelTabToLowLevelContentDocument(window.tabs.activeTab);
var activeTabId = window.tabs.activeTab.id.toString();
if ((contentDocumentMap.has(activeTabContentWindow)) && (contentDocumentMap.get(activeTabContentWindow) !== activeTabId)) {
console.log('M O V E D. url: '+ window.tabs.activeTab.url);
console.log('from tabid: '+ contentDocumentMap.get(activeTabContentWindow) + ' to tabid: ' + activeTabId);
contentDocumentMap.delete(activeTabContentWindow);
contentDocumentMap.set(activeTabContentWindow, activeTabId);
}
}
}, 150);
}
function logClose(tab) {
var targetTabId = tab.id.toString();
setTimeout(function() {
var windows = require("sdk/windows").browserWindows;
for (let window of windows) {
var activeTabContentWindow = mapHighLevelTabToLowLevelContentDocument(window.tabs.activeTab);
var activeTabId = window.tabs.activeTab.id.toString();
if (contentDocumentMap.has(activeTabContentWindow)) {
if (contentDocumentMap.get(activeTabContentWindow) !== activeTabId) {
console.log('M O V E D. url: '+ window.tabs.activeTab.url);
console.log('from tabid: '+ contentDocumentMap.get(activeTabContentWindow) + ' to tabid: ' + activeTabId);
contentDocumentMap.delete(activeTabContentWindow);
contentDocumentMap.set(activeTabContentWindow, activeTabId);
}
else if (targetTabId === activeTabId){
contentDocumentMap.delete(activeTabContentWindow);
}
}
}
}, 150);
}
tabs.on('open', onOpen);

breeze observableArray binding - are properties observable?

I have a viewmodel which consists of a list(foreach loop) of DoctorPrices and when clicking on an item in the list it open up a CRUD form on the side. However when i update the values on the CRUD the observableArray that is bound to the foreach is not refreshing? (although the values are updates in the DB correctly)
From my data access module i call the following query.
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
return manager.executeQueryLocally(query);
}
In my viewmodel i have the following code:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
services is bound using a foreach loop (not posting here as the code is simple and works)
When i click on a one of the DoctorPrices it gets the data as follows and places it in an observable:
this.selectedPrice = function (data, event) {
self.currentService(data);
self.showEdit(true);
};
I then bind selectPrice to a simple form that has the properties on it to be modified by the user. I then call manager.SaveChanges().
This results in the following problem: the value is being updated correctly but the GUI / Original List that is bound in the foreach is not being updated? Are the properties in breeze not observables? What is the best way to work with something like this.
I thought of a workaround and changing the code with something like this:
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
});
But i feel that clearing the array in that way is sloppy and not the right way of doing things specially with long lists.
Can someone please point me in the right direction on how to bind observableArray properties properly so they are updated?
Additional code my VM Component:
function services() {
var self = this;
this.showForm = ko.observable(false);
this.currentService = ko.observable();
this.services = ko.observableArray(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
this.title = ko.observable();
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
self.showDetails(false);
});
this.show = function (value) {
self.showForm(value);
};
this.showDetails = ko.observable(false);
this.addNewService = function() {
self.currentService(doctorServices.createService(doctorList.viewModel.instance.currentDoctorID()));
console.log(self.currentService().entityAspect.entityState);
self.showDetails(true);
};
this.showDelete = ko.computed(function() {
if (self.currentService() == null)
return false;
else if (self.currentService().entityAspect.entityState.isDetached()) {
self.title('Add new service');
return false;
} else {
self.title('Edit service');
return true;
}
});
this.deleteService = function() {
self.currentService().entityAspect.setDeleted();
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
};
this.closeDetails = function () {
doctorServices.manager.rejectChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.showDetails(false);
};
this.selectService = function (data, event) {
self.currentService(data);
self.showDetails(true);
};
this.saveChanges = function () {
console.log(self.currentService().entityAspect.entityState);
if (self.currentService().entityAspect.entityState.isDetached()) {
doctorServices.attachEntity(self.currentService());
}
console.log(self.currentService().entityAspect.entityState);
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.currentService.notifySubscribers();
self.showDetails(true);
};
}
return {
viewModel: {
instance: new services()
},
template: servicesTemplate,
};
Below is my Breeze Data Class:
define('data/doctorServices', ['jquery', 'data/dataManager', 'knockout','mod/medappBase', 'breeze', 'breeze.savequeuing'], function ($, manager, ko,base, breeze, savequeuing) {
var services = ko.observableArray([]);
return {
attachEntity:attachEntity,
getServices: getServices,
services: services,
manager:manager,
getDoctorServices: getDoctorServices,
getServiceById: getServiceById,
createService:createService,
hasChanges: hasChanges,
saveChanges: saveChanges
};
function getServices() {
var query = breeze.EntityQuery.from("DoctorPrices");
return manager.executeQuery(query).then(function (data) {
services(data.results);
}).fail(function (data) {
console.log('fetch failed...');
console.log(data);
});;
}
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
var set = manager.executeQueryLocally(query);
return set;
}
function getServiceById(serviceId) {
return manager.createEntity('DoctorPrice', serviceId);
//return manager.getEntityByKey('DoctorPrice', serviceId);
}
function handleSaveValidationError(error) {
var message = "Not saved due to validation error";
try { // fish out the first error
var firstErr = error.innerError.entityErrors[0];
message += ": " + firstErr.errorMessage;
base.addNotify('error', 'Could not save.', message);
} catch (e) { /* eat it for now */ }
return message;
}
function hasChanges() {
return manager.hasChanges();
}
function attachEntity(entity) {
manager.addEntity(entity);
}
function createService(doctorId) {
return manager.createEntity('DoctorPrice', { DoctorPricingID: breeze.core.getUuid(), DoctorID:doctorId }, breeze.EntityState.Detached);
};
function saveChanges() {
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
function saveSucceeded(saveResult) {
base.addNotify('success', 'Saved.', 'Your updates have been saved.');
}
function saveFailed(error) {
var reason = error.message;
var detail = error.detail;
if (error.innerError.entityErrors) {
reason = handleSaveValidationError(error);
} else if (detail && detail.ExceptionType &&
detail.ExceptionType.indexOf('OptimisticConcurrencyException') !== -1) {
// Concurrency error
reason =
"Another user, perhaps the server, " +
"may have deleted one or all of the settings." +
" You may have to restart the app.";
} else {
reason = "Failed to save changes: " + reason +
" You may have to restart the app.";
}
console.log(error);
console.log(reason);
}
}
});
Please note this is my frist attempt at both a data class and VM. At the moment i am relying heavily on clearing the array ([]) and using notifySubscribers to make the array refresh :(
I bet you're missing an observable somewhere. I can't tell because you keep hopping from property to property whose definition is not shown.
For example, I don't know how you defined this.currentService.
I'm confused by this:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
Why is it a ko.computed? Why not just make it an observable array.
self.service = ko.observableArray();
// ... later replace the inner array in one step ...
self.service(doctorServices.getDoctorServices(
doctorList.viewModel.instance.currentDoctorID()));
I urge you to follow the observability trail, confident that your Breeze entity properties are indeed observable.
vm.selectedPrice = ko.dependentObservable(function () {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
}, vm);
vm is ur model on which u applied bindings , try this it will work.

Setting Context Item position in Firefox addons SDK

I'm writing an extension that involving adding an item to Firefox's context menu, but it appends to the end of the menu and I couldn't find any pointers customizing item's position using Addon SDK (insertBefore/insertAfter), I know how this can be done using XUL, but I'm trying to do it using Addon SDK or some sort of Addon SDK/XUL combination
This is the code snippet related to context menu
main.js
var pageMod = require("sdk/page-mod");
var data = require("sdk/self").data;
var tabs = require("sdk/tabs");
var cm = require("sdk/context-menu");
pageMod.PageMod({
include: "*.youtube.com",
contentScriptFile: data.url("page.js"),
onAttach: function (worker) {
worker.port.emit('link', data.url('convertbutton.png'));
}});
cm.Item({
label: "Convert File",
image: data.url("bighdconverterlogo128png.png"),
context: [
cm.URLContext(["*.youtube.com"]),
cm.PageContext()
],
contentScriptFile: data.url("menu.js"),
onMessage: function(vUrl){
tabs.open(vUrl);
}
});
data/menu.js
self.on("click", function(){
self.postMessage('http://hdconverter.co/' + 'c.php?url=' + window.location.href);
});
Thanks
i dont know about sdk but for non-sdk addons its easy. but because you dont have the boiler plate setup its going to look long. add this code to your addon at the bottom:
var positionToInsertMenu = 0; //set the position you want it at here
var myLabelText = 'Convert File';
const {interfaces: Ci,utils: Cu} = Components;
Cu.import('resource://gre/modules/Services.jsm');
/*start - windowlistener*/
var windowListener = {
//DO NOT EDIT HERE
onOpenWindow: function (aXULWindow) {
// Wait for the window to finish loading
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
aDOMWindow.addEventListener("load", function () {
aDOMWindow.removeEventListener("load", arguments.callee, false);
windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
}, false);
},
onCloseWindow: function (aXULWindow) {},
onWindowTitleChange: function (aXULWindow, aNewTitle) {},
register: function () {
// Load into any existing windows
let XULWindows = Services.wm.getXULWindowEnumerator(null);
while (XULWindows.hasMoreElements()) {
let aXULWindow = XULWindows.getNext();
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
windowListener.loadIntoWindow(aDOMWindow, aXULWindow);
}
// Listen to new windows
Services.wm.addListener(windowListener);
},
unregister: function () {
// Unload from any existing windows
let XULWindows = Services.wm.getXULWindowEnumerator(null);
while (XULWindows.hasMoreElements()) {
let aXULWindow = XULWindows.getNext();
let aDOMWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
windowListener.unloadFromWindow(aDOMWindow, aXULWindow);
}
//Stop listening so future added windows dont get this attached
Services.wm.removeListener(windowListener);
},
//END - DO NOT EDIT HERE
loadIntoWindow: function (aDOMWindow, aXULWindow) {
if (!aDOMWindow) {
return;
}
var contentAreaContextMenu = aDOMWindow.document.getElementById('contentAreaContextMenu');
var myMenuItem;
if (contentAreaContextMenu) {
var menuItems = contentAreaContextMenu.querySelector('menuitem');
[].forEach.call(menuItems, function(item) {
if (item.getAttribute('label') == myLabelText) {
myMenuItem = item;
}
});
contentAreaContextMenu.removeChild(myMenuItem);
if (contentAreaContextMenu.childNodes.length >= positionToInsertMenu) { //position is greater then number of childNodes so append to end
contentAreaContextMenu.appendChild(myMenuItem);
} else {
contentAreaContextMenu.insertBefore(myMenuItem, contentAreaContextMenu.childNodes[thePosition]);
}
}
},
unloadFromWindow: function (aDOMWindow, aXULWindow) {
if (!aDOMWindow) {
return;
}
var myMenuItem = aDOMWindow.document.getElementById('myMenuItem');
if (myMenuItem) {
myMenuItem.parentNode.removeChild(myMenuItem);
}
}
};
windowListener.register();
on unload of your addon add this:
windowListener.unregister();
i copied pasted from a template and modded it real fast. for position to be accurate you probably have to consider which menuitems are hidden and which are not

Resources