Still most potential for a GUI to Haskell for me, but missing some essential info in the examples, being a noob Haskeller. Assuming one of the examples:
{-
webviewhs
(C) 2018 David Lettier
lettier.com
-}
{-# LANGUAGE
OverloadedStrings
#-}
import qualified Graphics.UI.Webviewhs as WHS
main :: IO ()
main =
WHS.createWindowAndBlock
WHS.WindowParams
{ WHS.windowParamsTitle = "webviewhs - How do I create a window and have it run itself?"
-- This could be a localhost URL to your single-page application (SPA).
, WHS.windowParamsUri = "https://lettier.github.com"
, WHS.windowParamsWidth = 800
, WHS.windowParamsHeight = 600
, WHS.windowParamsResizable = True
, WHS.windowParamsDebuggable = True
}
This creates a window in which I can load a custom webpage. Assuming this webpage has a <input type="text" id="mytext"> and there's a button next to it. Not that it matters but <button type="submit" id="sendtohaskell">. How would I go about getting the info in the textfield to Haskell by pressing the button? There's no example like that in the tutorial. For me it is the missing link to get info from a webapp, processing it in Haskell and returning it to eg. SQLite.
As the github page shows, you can receive data from JS with a callback, and execute arbitrary JS in the window from Haskell. This is enough to do any sort of communication you might want, here's an example that that executes some Haskell on a button press and then shows the result in the webpage:
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module Main where
import System.Directory(getCurrentDirectory)
import Text.Printf
import Control.Monad(void)
import Language.Javascript.JMacro
import qualified Graphics.UI.Webviewhs as WHS
import qualified Data.Text as T
windowCallback window = do
return True
handleJSRequest window request = void . WHS.runJavaScript window $ [jmacro|
show_response `(printf "'Got response: %s'" request :: String)`
|]
main :: IO ()
main = void $ do
dir <- getCurrentDirectory
WHS.withWindowLoop
WHS.WindowParams
{ WHS.windowParamsTitle = "Test"
, WHS.windowParamsUri = T.pack $ printf "file://%s/example.html" dir
, WHS.windowParamsWidth = 800
, WHS.windowParamsHeight = 600
, WHS.windowParamsResizable = True
, WHS.windowParamsDebuggable = True
}
handleJSRequest
windowCallback
<html>
<head>
<title>Example</title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
function show_response(response) {
document.getElementById('response').innerHTML = response;
}
function submit() {
var value = document.getElementById('textbox').value;
window.external.invoke(value)
}
</script>
<input type="text" id="textbox"/>
<input value="say hello" type="button" onclick="submit()"/>
<p id="response"></p>
</body>
</html>
You should note though that the haskell webview library has only 2 commits, the last of which was more than 7 months ago, so it's not exactly being actively developed at the moment.
Related
It has an embedded system, which operates with an IP (example: 192.168.0.237) different from each device, where the Import/Load file functionality depends on JQuery. The goal is to get this feature to work without needing an internet connection, it is currently using pointing to https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js and it works without problems.
Changing the code, to access the "jquery.js" file locally.
The page that loads the UPLOAD functionality is operating on port 8989. This, the path can be placed in the HEAD of the files, like this:
<script type='text/javascript' src='jquery.js'></script>
When running, inspecting the NETWORK tab, we have the result path:
"192.168.0.237:8989\jquery.js"
that is, it maps through port 8989, it fails to find the file, as it is stored in the root with port 80. Therefore, to validate the solution FORCE doing:
<script src='http://192.168.0.237/jquery.js'></script>
I added this instruction at the top of the file inside HEAD, AND the file UPLOAD feature WORKED correctly. Validating, which solution is suitable.
But, in order to work correctly, it must obtain the IP of the device, so a SCRIPT was made using "window.location.hostname" to obtain the IP of the device and assemble the URL.
<script>
let k2 = '#http://';
let k3 = window.location.hostname;
let k4 = '#jquery.js';
let fim = `${k2} ${k3} ${k4}`;
fim = fim.replace(' #', '/');
fim = fim.replace('#', ' ');
fim = fim.replace('// ', '//');
fim = fim.trim();
document.getElementById('demo').innerHTML = window.jQuery;
document.getElementById('kkk').innerHTML = fim;
document.head.appendChild(document.createElement('script')).src = fim;
</script>
In this SCRIPT we have the last line "document.head.appendChild(document.createElement('script')).src = fim"
which inserts the statement to load JQuery into the HEAD. Clicking on the "Check JQuery Status" button, the message that JQuery is loaded and available appears.
But now comes the problem, when pressing the submit button, update, the shown below occurs:
Imagem upload1
Imagem upload2
Imagem upload3
In short, an error occurs because it points to "serverIndex:1" and the correct would be only "serverIndex".
In other words, we have the INITIATOR pointing to "serverIndex:1" which doesn't exist (it doesn't even have to adjust the instruction, a syntax error will happen).
So, in short, with statement adding "jquery.js" STATIC = WORKS, but with statement ADDING "jquery.js" DYNAMICLY DOES NOT WORK.
With the STATIC instruction inserted in the HEAD, we have the expected operation as indicated below: (Note the INITIATOR = serverIndex)
WORKS OK RESULT
I need guidance as I don't know how to solve and also if this solution is suitable.
Below is the rendered code:
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<script>
function checa() {
if(jQuery) {
alert('Jquery ONLOAD!');
} else {
alert('Jquery NOT HAVE - NUNCA EXECUTA');
}
}
</script>
<script src='http://192.168.0.237/jquery.js'></script>
</head>
<button onclick='checa()'>Check Status JQuery</button>
<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>
<h4>valor:</h4>
<p id='demo'> </p>
<p id='kkk'></p>
<h3>Verificador de preço</h3>
<h3>Update Firmware</h3>
<input type='file' name='update' id='file' onchange='sub(this)' style=display:none accept='.bin' required>
<label id='file-input' for='file'> Selecione o arquivo ...</label>
<input type='submit' class=btn value='Atualizar'>
<br>
<br>
<div id='prg'></div>
<br>
<div id='prgbar'>
<div id='bar'></div>
</div>
<br>
</form>
<script>
let k2 = '#http://';
let k3 = window.location.hostname;
let k4 = '#jquery.js';
let fim = `${k2} ${k3} ${k4}`;
fim = fim.replace(' #', '/');
fim = fim.replace('#', ' ');
fim = fim.replace('// ', '//');
fim = fim.trim();
document.getElementById('demo').innerHTML = window.jQuery;
document.getElementById('kkk').innerHTML = fim;
document.head.appendChild(document.createElement('script')).src = fim;
</script>
<script> <!-- DOWN IS SCRIPT UPLOAD FILE !-->
function sub(obj) {
var fileName = obj.value.split('\\');
document.getElementById('file-input').innerHTML = ' ' + fileName[fileName.length - 1];
How the code is structured: (it is enclosed in QUOTES because as soon as it is stored in the String)
Code up
Code bottom
Code call String serverIndex
Finally, I hope to be guided on how to resolve the situation and whether this solution is adequate and correct.
The form html and submit event is part of the "renderer".
The submitted data should be available in the main process.
What's the proper way to submit the form and make that data accessible in main.js ?
Should I simply use the "remote" module to pass the data to a function from main.js or is there a better approach?
We use a service (Angular) to process form data in a window. Then notify the remote, if needed.
From your renderer you can send data to the ipc, then in your main.js you catch this event and the passed form data:
// renderer.js
let ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.send('submitForm', formData);
// main.js
ipcMain.on('submitForm', function(event, data) {
// Access form data here
});
You can also send messages back to the renderer from the main.js.
Either sync:
// main.js
ipcMain.on('submitForm', function(event, data) {
// Access form data here
event.returnValue = {"any": "value"};
});
Or async:
// main.js
ipcMain.on('submitForm', function(event, data) {
// Access form data here
event.sender.send('formSubmissionResults', results);
});
// renderer.js
ipcRenderer.on('formSubmissionResults', function(event, args) {
let results = args.body;
});
There are several variations on how to do this, but all are via IPC.
IPC (inter process communication) is the only way to get data from the render process to the main process, and is event driven. The way this works is that you can use custom defined events which the process listens for and returns something when that event happens.
The example stated by #Adam Eri is a variation on the ipcMain example found in the documentation, but this method is not one size fits all.
The reason for saying that is the matter can quickly become complicated if you are trying to send events via the menu (which typically runs on the main process), or via components through a front end framework like Vue or Angular.
I will give a few examples:
Using Remote with WebContents
To your point, yes you can use electron remote, but for the purposes of forms it is not the recommended approach. Based on the documentation, the point of remote is to
Use main process modules from the renderer process
tl:dr -This process can cause deadlocks due to its synchronous nature, can cause event object leaks (due to garbage collection), and leads to unexpected results with callbacks.
Further explanation can be had from the documentation but ultimately this is set for using items like dialog and menu in the render process.
index.js (main process)
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require ('path');
const fs = require('fs');
const os = require('os');
let window;
function createWindow(){
window = new BrowserWindow({
show: false
});
window.loadURL(`file://${__dirname}/index.html`);
window.once('ready-to-show', function (){
window.show();
});
window.webContents.openDevTools();
let contents = window.webContents;
window.on('closed', function() {
window = null;
});
}
exports.handleForm = function handleForm(targetWindow, firstname) {
console.log("this is the firstname from the form ->", firstname)
targetWindow.webContents.send('form-received', "we got it");
};
app.on('ready', function(){
createWindow();
});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron App</title>
</head>
<body>
<form action="#" id="ipcForm2">
First name:<br>
<input type="text" name="firstname" id="firstname" value="John">
<br>
Last name:<br>
<input type="text" name="lastname" id="lastname" value="Smith">
<br><br>
<input id="submit" type="submit" value="submit">
</form>
<p id="response"></p>
<script src='renderFile.js'></script>
</body>
</html>
renderFile.js (Render Process)
const { remote, ipcRenderer } = require('electron');
const { handleForm} = remote.require('./index');
const currentWindow = remote.getCurrentWindow();
const submitFormButton = document.querySelector("#ipcForm2");
const responseParagraph = document.getElementById('response')
submitFormButton.addEventListener("submit", function(event){
event.preventDefault(); // stop the form from submitting
let firstname = document.getElementById("firstname").value;
handleForm(currentWindow, firstname)
});
ipcRenderer.on('form-received', function(event, args){
responseParagraph.innerHTML = args
/*
you could choose to submit the form here after the main process completes
and use this as a processing step
*/
});
Traditional IPC
index.js (Main Process)
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require ('path');
const fs = require('fs');
const os = require('os');
let window;
function createWindow(){
window = new BrowserWindow({
show: false
});
window.loadURL(`file://${__dirname}/index.html`);
window.once('ready-to-show', function (){
window.show();
});
window.webContents.openDevTools();
let contents = window.webContents;
window.on('closed', function() {
window = null;
});
}
ipcMain.on('form-submission', function (event, firstname) {
console.log("this is the firstname from the form ->", firstname)
});
app.on('ready', function(){
createWindow();
});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron App</title>
</head>
<body>
<form name="ipcForm" onSubmit="JavaScript:sendForm(event)">
First name:<br>
<input type="text" name="firstname" id="firstname" value="John">
<br>
Last name:<br>
<input type="text" name="lastname" id="lastname" value="Smith">
<br><br>
<input type="submit" value="Submit">
</form>
<script src='renderFile.js'></script>
</body>
</html>
renderFile.js (Render Process)
const ipcRenderer = require('electron').ipcRenderer;
function sendForm(event) {
event.preventDefault() // stop the form from submitting
let firstname = document.getElementById("firstname").value;
ipcRenderer.send('form-submission', firstname)
}
Using WebContents
A possible third option is webContents.executeJavascript to access the renderer process from the main process. This explanation from the remote documentation section.
Summary
As you can see, there are a few options on how to handle forms with Electron. So long as you use IPC, you should be fine; its just how you use it that can get you into trouble. I have shown plain javascript options for handling forms, but there are countless ways to do so. When you bring a front end framework into the mix, it gets even more interesting.
I personally use the traditional IPC approach when I can.
Hope that clears things up for you!
i wouldnt necessarily recommend this way since it may interfere with other functioninality but its a way more concise approach
const str = `
<form action="form://submit" >
<input name="test" >
<button type="submit"> OK </button>
</form>
`
promptWindow.loadURL(`data:text/html;base64,${Buffer.from(str).toString("base64")}`)
promptWindow.webContents.session.protocol.registerStringProtocol("form", e => {
const request = new URL(e.url)
const data = request.searchParams // {test:"inputvalue"}
debugger;
})
Remote is great way to share data. Using global variables and share them with other pages of our electron application. So, based on the following IPC approach, I was able to manage it this way :
1) Add this code in the main.js file :
global.MyGlobalObject = {
variable_1: '12345'
}
2) Use this on your 1st page to update global variable value :
require('electron').remote.getGlobal('MyGlobalObject').variable_1= '4567'
3) Lastly, use something like this on your 2nd page where you'll access the modified global variable and print it :
console.log(require('electron').remote.getGlobal('MyGlobalObject').variable_1)
You can find the same thing in electron's documentation.
I am running into an issue where all google app endpoints are running smoothly. No issue at getting, updating or deleting entries while using the API explorer.
I am running into issue when trying to mesh all this together in an html file.
Updates and deletes work fine... Only get/list methods are returning no record.
Any idea what the issue is? What is best to troubleshoot this kind of issue?
Thanks.
test.java
import com.googlecode.objectify.annotation.*;
#Entity
#Index
public class Test {
#Id Long number;
Long number2;
public Test(){
}
public Test(Long number, Long number2){
this.number = number;
this.number2 = number2;
}
public Test(Long number2){
this.number2 = number2;
}
public Long getNumber2() {
return number2;
}
public void setNumber2(Long number2) {
this.number2 = number2;
}
public Long getNumber() {
return number;
}
public void setNumber(Long number) {
this.number = number;
}
}
test.html
<html>
<head>
<title></title>
</head>
<body>
<div>
<form>
<div>
<input type="text" id="insert" value="2"><br><br>
<button type="submit" id="getTestButton" onclick="getTest()" disabled>Get</button>
</div>
</form>
<p id="result"></p>
<hr>
</div>
<!--
Load the Google APIs Client Library for JavaScript
More info here : https://developers.google.com/api-client- library/javascript/reference/referencedocs
-->
<script type="text/javascript">
function init() {
gapi.client.load('testEndpoint', 'v1', enableButton, 'http://localhost:8080/_ah/api');
document.getElementById('getTestButton').onclick = function() {
getTest();
}
}
function enableButton() {
console.log("enabling button");
getTestButton.disabled = false;
}
function getTest() {
console.log("entering getTest function");
var features = {};
features.number2 = document.getElementById("insert").value;
console.log(features);
var req = gapi.client.testEndpoint.getTest(features);
req.execute(function(data) {
console.log(data);
document.getElementById("result").innerHTML = data.number + " " + data.number2;
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
</body>
</html>
TestEndpoint.java
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import java.util.logging.Logger;
import javax.inject.Named;
import com.googlecode.objectify.cmd.Query;
import static com.Backend.OfyService.ofy;
/** An endpoint class we are exposing */
#Api(name = "testEndpoint", version = "v1", namespace = #ApiNamespace(ownerDomain = "Backend.com", ownerName = "Backend.com", packagePath=""))
public class TestEndpoint {
// Make sure to add this endpoint to your web.xml file if this is a web application.
private static final Logger LOG = Logger.getLogger(TestEndpoint.class.getName());
/**
* This method gets the <code>Test</code> object associated with the specified <code>id</code>.
* #param id The id of the object to be returned.
* #return The <code>Test</code> associated with <code>id</code>.
*/
#ApiMethod(name = "getTest")
public Test getTest(#Named("number2") Long number2) {
// Implement this function
LOG.info("***GetTest***NUMBER=" + number2);
Test t = new Test(1L, 2L);
//t = ofy().load().type(Test.class).filter("number2", number2).first().now();
LOG.info("t.getNumber()==> " + t.getNumber().toString());
LOG.info("Calling getTest method");
return t;
}
}
Seems legit to me. To isolate the cause of the error, I would suggest to do the following :
Test your getTest endpoint method through the API explorer, located at http://yourAppAddress/_ah/api/explorer . If it works you know the issue is in your client code.
If it did not work, then the issue is either with the code inside getTest or with the way you use endpoints (or with endpoints itself). To check this, create a pure Java servlet that will call the getTest() method directly, and log the result. If it works you know the issue is either on the way you use endpoints or on endpoints itself.
Sorry I don't have an immediate answer to your question.
EDIT : Since you tell me that the API Explorer test works, I checked your javascript code and there's something weird : you try to get resp.items.length in the callback. But your endpoint method only returns a Test entity, so there are no items attribute.
Can you try this code ?
gapi.client.testEndpoint.getTest(requestData).execute(function(resp) {
console.log(resp);
});
EDIT 2 : So you've found the cause of the issue by yourself : you cannot call the endpoints method before the API client is loaded. Since your method is called by an onClick event, probably on a button, you must do the following :
By default, disable the button
In the gapi.client.load 's callback, enable the button
That way you will be sure to only click on the button when the Google service is initialized.
EDIT 3 : It is actually a problem in your html. You have a submit button, which makes Chrome reload the page. Every time you click the button the page reloads and you never see the result of your endpoint query.
This HTML file works for me on the dev server :
<html>
<head>
<title></title>
</head>
<body>
<div>
<div>
<input type="text" id="insert" value="2"><br><br>
<button id="getTestButton" onclick="getTest()" disabled>Get</button>
</div>
<p id="result"></p>
<hr>
</div>
<!--
Load the Google APIs Client Library for JavaScript
More info here : https://developers.google.com/api-client- library/javascript/reference/referencedocs
-->
<script type="text/javascript">
function init() {
gapi.client.load('testEndpoint', 'v1', enableButton, '/_ah/api');
document.getElementById('getTestButton').onclick = function () {
getTest();
}
}
function enableButton() {
console.log("enabling button");
getTestButton.disabled = false;
}
function getTest() {
console.log("entering getTest function");
var features = {};
features.number2 = document.getElementById("insert").value;
console.log(features);
var req = gapi.client.testEndpoint.getTest(features);
req.execute(function (data) {
console.log(data);
document.getElementById("result").innerHTML = data.number + " " + data.number2;
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
</body>
</html>
I am currently using this code:
var wordRandomizer = {
run: function (targetElem) {
var markup = this.createMarkup();
targetElem.appendChild(markup);
},
createMarkup: function () {
var that = this;
var frag = document.createDocumentFragment();
this.elem = document.createElement('span');
var button = document.createElement('button');
button.innerText = 'Change Item';
button.addEventListener('click', function () {
that.changeItem();
});
frag.appendChild(this.elem);
frag.appendChild(button);
return frag;
},
changeItem: function () {
var rand = this.getRandInt(1, this.items.length) - 1;
console.log(rand);
this.elem.innerText = this.items[rand];
},
getRandInt: function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
},
items: ['itemA', 'itemB', 'itemC', 'itemD']
};
wordRandomizer.run(document.body);
I code is a button which when pressed grabs one of the items in the list. However, I don't want the items to show on the same page as the generator as people simply look at the source code. How can I make it so once the button is pressed it grabs the random item from another location where people cannot view them all using the source code.
If it helps, you can see the code in action here - http://jsbin.com/ESOdELU/1/edit
I will give you a solution using PHP since it is a free scripting language and is the most likely to be supported by a host or default web server...
For starters, here is the code to include jquery and the basic AJAX script
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/JavaScript">
$(document).ready(function(){
$("#generate").click(function(){
$("#madlibs p").load("script.php");
});
});
</script>
Here is the code for script.php
<?php
header("Cache-Control: no-cache");
// For testing you can use an inline array like the lines below
// Just remove the comment slashes "//" from the beginning of the line
// and comment out the external declarations
//$actors = array('Denzel Washington','Michael J. Fox','Jim Carey','Boris Kodjoe');
//$roles = array('Mental Patient','Homeless Musician','Drag Queen Detective','Tormented Mathematician');
// In production, you would put these in a text file or a database.
// For $actors, put an entry on each line of a text file and save it as 'leads.txt'
// Do the same with a separate file for $roles (roles.txt).
$actors = file("leads.txt");
$roles = file("roles.txt");
// This selects a random element of each array on the fly
echo $prefixes[rand(0,count($actors)-1)] . " stars as a "
. $suffixes[rand(0,count($roles)-1)] . " in the next blockbuster film.";
// Example output:
// Michael J. Fox stars as a Tormented Mathematician in the next blockbuster film.
?>
Put this in the body of your page and be sure to style everything up for display.
<body>
<div id="madlibs"><p> </p></div>
<button id="generate">Surprise Me!</button>
</body>
A couple of notes:
- You can include your basic layout HTML in the script.php file and then would only need the ID of the DIV in which you will be displaying the result $("#madlibs")
You can use any server side language to achieve the same result, just swap out the external file call to the appropriate name and extension (.asp, .cfm, etc.)
Here is a link to the original tutorial that helped me with a similar project:
http://www.sitepoint.com/ajax-jquery/
I hope this helps. Sorry, but I couldn't come up with a purely Java of JavaScript solution on lunch.
I'm trying to get the most basic XPCOM javascript object to be accessible to the javascript I load into my webpage. I'm using the example code from this tutorial:
https://developer.mozilla.org/en-US/docs/How_to_Build_an_XPCOM_Component_in_Javascript
Here is my set up:
install.rdf:
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>helloworld#thellamatesting.com</em:id>
<em:name>Hello World</em:name>
<em:version>1.0</em:version>
<em:type>2</em:type>
<em:creator>The Llama</em:creator>
<em:description>Testing</em:description>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>2.0</em:minVersion>
<em:maxVersion>20.0</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
chrome.manifest
content helloworld chrome/content/
content helloworld chrome/content/ contentaccessible=yes
overlay chrome://browser/content/browser.xul chrome://helloworld/content/browser.xul
component {4762b5c0-5b32-11e2-bcfd-0800200c9a66} components/HelloWorld.js
contract #thellamatesting.com/helloworld;1 {4762b5c0-5b32-11e2-bcfd-0800200c9a66}
locale helloworld en-US locale/en-US/
components/HelloWorld.js
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
function HelloWorld() {
// If you only need to access your component from Javascript, uncomment the following line:
this.wrappedJSObject = this;
}
HelloWorld.prototype = {
classDescription: "My Hello World Javascript XPCOM Component",
classID: Components.ID("{4762b5c0-5b32-11e2-bcfd-0800200c9a66}"),
//Also tried
//classID: Components.ID("4762b5c0-5b32-11e2-bcfd-0800200c9a66"),
contractID: "#thellamatesting.com/helloworld;1",
QueryInterface: XPCOMUtils.generateQI(),
// Also tried
//QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIHelloWorld]),
hello: function() {
return "Hello World!";
}
};
var components = [HelloWorld];
if ("generateNSGetFactory" in XPCOMUtils)
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); // Firefox 4.0 and higher
else
var NSGetModule = XPCOMUtils.generateNSGetModule(components); // Firefox 3.x
Testing HTML:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="application/javascript">
function go() {
try {
var coms = Components;
alert(Components.classes);
var myComponent = Components.classes['#thellamatesting.com/helloworld;1'].getService().wrappedJSObject;
alert(myComponent.hello());
} catch (anError) {
dump("ERROR: " + anError);
}
};
</script>
</head>
<body>
<button onclick="javascript:go()">Click to go</button>
</body>
</html>
After all this, I end up with "Components.classes is undefined". Does anyone know what I'm doing wrong here?
Thanks so much!
In order to gain access to the Components object from a javascript context, you need to have extended capabilities, that is, run from a chrome:// URL. There used to be a way for a regular web page (served from http://) to request extended capabilities (called UniversalXPConnect) but it has been removed out of security concerns.
I think you should tell us a little more about what it is you're trying to achieve. If you're trying to export data from your addon into a webpage, the AddonSDK (see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/) has a very good protocol for doing that called page-mod; it allows you to inject data into web pages.
Thanks to Jonathan's advice I was able to come up with a great solution to this problem. Here is the code I'm using:
main.js:
var data = require("self").data;
var pageMod = require("page-mod");
const {Cc,Ci} = require("chrome");
pageMod.PageMod({
include: "*",
contentScriptFile: data.url("copy-helper.js"),
onAttach: function(worker) {
worker.port.on("handleCopy", function(copyInfo) {
var gClipboardHelper = Cc["#mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
gClipboardHelper.copyString(copyInfo.dataToCopy);
});
}
});
copy-helper.js:
window.addEventListener("copyEvent", function (event) {
self.port.emit('handleCopy', event.detail.copyInfo);
}, false);
in my apps javascript
var event = new CustomEvent("copyEvent", {
detail:{
copyInfo: {dataToCopy:"my string"}
}
});
window.dispatchEvent(event);
Hope this helps anyone else who's ran into this issue!