As said in the subject, I need to fill a web form using data locally available as excel tables. I am already making that with a combination of python and autohotkey, but I want to have some level of JavaScript control in order to correctly handle loading times and conditionals. As a web development newbie, I first thought I could just have a local iframe controlling the website where the form is, but I discovered soon enough that XSS thing that does not allow such a hack. I do not have access to the server.
The last iteration of my experiences is with Firefox webextensions, with which I hoped to open a local file (through a html5 file input widget), where I would previously have written my js code to fill the form. But apparently there are also limitations here, and I cannot make any sense out the docs I am looking at. My code is currently like that:
popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<input type="file" id="liquida-file" name="liquida">
<br>
<script src="background-script.js"></script>
</body>
</html>
background-script.js
function handleFiles() {
var fileList = this.files; /* now you can work with the file list */
var myFile = fileList[0]
var reader = new FileReader();
reader.onloadend = function(evt){
if (evt.target.readyState == FileReader.DONE) { // DONE == 2
var filedata = evt.target.result;
console.error("Analyzing file data")
console.error(filedata)
var data = JSON.parse(filedata)
console.error(data)
}
};
reader.readAsText(myFile)
}
var inputElement = document.getElementById("liquida-file");
inputElement.addEventListener("change", handleFiles, false);
This works as a standalone file, but not as the popup.html file of my webextension. In this case, none of the console.error lines are ever reached. BTW, here is my manifest.json:
manifest.json
{
"manifest_version": 2,
"name": "My extension",
"version": "1.0",
"description": "Retrieve local data.",
"homepage_url": "http://Nonefornow",
"icons": {
"48": "icons/beautiful-icon.png"
},
"permissions": [
"activeTab"
],
"browser_action": {
"browser_style": true,
"default_icon": "icons/icon.png",
"default_title": "My Ext",
"default_popup": "popup.html"
}
}
Is there any easier way to do what I am doing? I was expecting for this sort of thing to be a common need, am I wrong? And why doesn't my code work?
This problem has been pointed out in this question:
Firefox WebExtensions, get local files content by path.
The solution given there is the following:
function readFile(_path, _cb){
fetch(_path, {mode:'same-origin'}) // <-- important
.then(function(_res) {
return _res.blob();
})
.then(function(_blob) {
var reader = new FileReader();
reader.addEventListener("loadend", function() {
_cb(this.result);
});
reader.readAsText(_blob);
});
};
but in this solution the absolute path has to be passed to the function, like here:
readFile('file:///home/saba/desktop/test.txt', function(_res){
console.log(_res); // <-- result (file content)
});
If you want to load a file from an <input> field you have to pass the path of the file too, because for security reasons you can't retrieve that from the <input> field. My solution was to read the path from an input text field, reducing significantly the usability
html
path: <input type="input" id="importFilePathInput" value="file://" />
<br />
file: <input type="file" id="importFileInput" />
javascript
function importFromfile(){
let filename = jQuery('#importFileInput').val().replace('C:\\fakepath\\', '');
if (!filename) {
console.log('Select a filename');
} else {
let path = jQuery('#importFilePathInput').val() + '/' + filename;
if (!path.startsWith('file://')) {
path = 'file://' + path;
}
fetch(path, {mode:'same-origin'})
.then(function(result){
return result.blob();
})
.then(function(blob){
let reader = new FileReader();
reader.addEventListener('loadend', function(){
Model.save(JSON.parse(this.result)); // your function here
});
reader.readAsText(blob);
});
}
}
Note that unfortunately this solution doesn't work anymore on Firefox 57, giving the error:
TypeError: NetworkError when attempting to fetch resource.
This works as a standalone file, but not as the popup.html file of my webextension.
Aha. I would check the permissions ...
Related
I am creating a Google Chrome Extension that uses a script that works perfectly well in Google Chrome's console.
However, I am trying to use this same script beyond just printing the information in console.
How would I be able to somehow create HTML elements that contain this information within the popup.html page? I know that this idea might have to use the callback function within
Here is my code:
manifest.json
{
"manifest_version": 2,
"name": "MR QC Auditor View",
"version": "1.0",
"description": "This Google Chrome extension shows Ad ID's for print ads, and then links to Ad Tagger for tagging corrections",
"icons": {
"128": "MRLogo128.png",
"48": "MRLogo48.png",
"16": "MRLogo16.png"
},
"browser_action":{
"default_icon": "MRLogo16.png",
"default_popup": "popup.html"
},
"permissions": [
"storage",
"tabs",
"activeTab"
]}
popup.html:
<!DOCTYPE html>
<html>
<head>
<title>MR QC Auditor View</title>
<script src="jquery-3.3.1.min.js">
</script>
<script src="popup.js">
</script>
</head>
<body>
<img src="MRLogo128.png"/>
<h1>Current Ad's Brand: <h1><span id="brandNameText"></span>
<h2>Link To Ad Tagger</h2><span>Link</span>
</body>
</html>
popup.js:
// Current Post To Look At:
// https://stackoverflow.com/questions/4532236/how-to-access-the-webpage-dom-rather-than-the-extension-page-dom
// Related Google + Group Post:
// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/9Sue8JRwF_k
// Use chrome.runtime.onMessage()
// Documentation:
// https://stackoverflow.com/questions/3010840/loop-through-an-array-in-javascript
// chrome.tabs.executeScript():
// https://developer.chrome.com/extensions/tabs#method-executeScript
/*
Old Code Block;
chrome.tabs.executeScript({file: 'jquery-3.3.1.min.js'}, function () { chrome.tabs.executeScript({code: 'var printAds = document.getElementsByClassName("ad-image printadviewable pointer"); ', allFrames: true}, function(stuff){
chrome.runtime.sendMessage({greeting: "hello"}, function(){
// Call chrome.runtime.sendResponse()
// console.log("DOM Content Sent To Chrome Extension Webpage");
})
}}); });
*/
// Added an array called "adArray" that utilizes the .push() JavaScript array function
// I need to somehow add this to the popup.html page itself, look for the StackOverflow related pages.
chrome.tabs.executeScript({file: 'jquery-3.3.1.min.js'}, function (adArray) { chrome.tabs.executeScript({code: 'var printAds = document.getElementsByClassName("ad-image printadviewable pointer"); var adArray = []; for (var i = 0; i<printAds.length; i++){console.log("Current Ad Id = " + $(printAds[i]).attr("data-adid")); adArray.push($(printAds[i]).attr("data-adid"))}', allFrames: true}); });
/*
Console Based Code That Works:
$("#ad-image printadviewable pointer").find("img").attr("data-adid");
var printAds = document.getElementsByClassName("ad-image printadviewable pointer");
for (var i = 0; i<printAds.length; i++){console.log("Current Ad Id = " + $(printAds[i]).attr("data-adid"));}
*/
// One Line Version For Code Dictionary Key
// 'var printAds = document.getElementsByClassName("ad-image printadviewable pointer"); for (var i = 0; i<printAds.length; i++){console.log("Current Ad Id = " + $(printAds[i]).attr("data-adid"));}'
I am a quite coding beginner and I am struggling a lot to get data from a .txt with Javascript. I have a really simple txt file.
I would like to read the file with Javascript and store the info ideally into an array.
Here is my txt content:
"Italy": 30,
"France": 28,
"Netherlands": 1,
"Germany": 14,
"Greece": 4,
"Spain": 3,
"others": 12
What I manage to do after checking all the posible forums is the following. I can select the txt file and display the content with this following code:
HTML:
<!DOCTYPE html>
<html>
<style type="text/css">
#filecontents {
border:double;
overflow-y:scroll;
height:400px;
}
</style>
<head>
<meta charset="UTF-8">
<script src="FileReaderLogic.js"></script>
</head>
<body>
Please Select text file of which contents are to be read:
<input type="file" id="txtfiletoread" />
<div>The File Contents are as below:</div>
<div id="filecontents">
<script>ReadText()</script>
</div>
</body>
</html>
FileReaderLogic.js:
window.onload = function ReadText() {
//Check the support for the File API support
if (window.File && window.FileReader && window.FileList && window.Blob) {
var fileSelected = document.getElementById('txtfiletoread');
fileSelected.addEventListener('change', function (e) {
//Set the extension for the file
var fileExtension = /text.*/;
//Get the file object
var fileTobeRead = fileSelected.files[0];
//Check of the extension match
if (fileTobeRead.type.match(fileExtension)) {
//Initialize the FileReader object to read the 2file
var fileReader = new FileReader();
fileReader.onload = function (e) {
var fileContents = document.getElementById('filecontents');
fileContents.innerText = fileReader.result;
}
fileReader.readAsText(fileTobeRead);
}
else {
alert("Please select text file");
}
}, false);}
else {
alert("Files are not supported");
}
}
This works fine but I do not manage to change this code in oder to enter directly inthe code the file name/path (no button and no selection from the user needed) and store the file data into a variable or an array.
Could you please help me?
thanks a lot
This can be accomplished much simpler with an AJAX (Asynchronous JavaScript and XML) call for the text file's contents and then modify the text file just a little bit so that the data is stored in JSON (JavaScript Object Notation) format.
You can hard-code the path to the text file into the configuration of the AJAX component.
Also, your data in the text file is more suited to a JavaScript Object, rather than an Array because arrays don't allow for setting up key/value pairs without getting into nested arrays. Objects are data structures that store key/value pairs and, as a matter of fact, if you simple wrap your text file's contents with { and }, you will have the syntax for an object converted to JSON format.
data.txt (This is a string that conforms to the JSON data format)
{
"Italy": 30,
"France": 28,
"Netherlands": 1,
"Germany": 14,
"Greece": 4,
"Spain": 3,
"others": 12
}
HTML (Note: your tag was not located properly)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="FileReaderLogic.js"></script>
<style>
#filecontents {
border:double;
overflow-y:scroll;
height:400px;
}
</style>
</head>
<body>
Please Select text file of which contents are to be read:
<div>The File Contents are as below:</div>
<div id="filecontents"></div>
<script>ReadText()</script>
</body>
</html>
FileReaderLogic.js
var dataObj = null; // Will hold data after AJAX call completes
function ReadText() {
// Create instance of XHR
var xhr = new XMLHttpRequest();
// Set up event callback functions
xhr.addEventListener("load", transferComplete);
xhr.addEventListener("error", transferFailed);
// Configure the request. Replace pathToTextFile with a relative
// path to a file (in the same domain as this file).
xhr.open("GET", pathToTextFile, true);
// Request body will be plain text
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
// Make the request:
xhr.send(null);
}
// Success Callback:
function transferComplete(evt) {
// Place the response in the HTML placeholder:
document.getElementById("filecontents").textContent = xhr.responseText;
// Turn text file content into object
dataObj = JSON.parse(xhr.responseText);
// Now, use the object as you like:
console.log(dataObj.Italy); // 30
// This will print all the key/value pairs
for(var key in dataObj){
console.log(key + " = " + dataObj[key]);
}
}
// Error callback:
function transferFailed(evt) {
console.log("An error occurred while processing the request.");
}
As said in the comments, you can't for obvious security reasons.
Now, this kind of question often come from a wrong point of view on what problem you have and how to solve it.
Quickest way from where you are to make it work, if I understand your needs (it's a big if ^^) :
1-rename your .txt file as .js
2-make its content a js Object like so :
var country_code={
"Italy": 30,
"France": 28,
"Netherlands": 1,
"Germany": 14,
"Greece": 4,
"Spain": 3,
"others": 12
};
3-include your js file in your html
<script src="./script/country.js" type="text/javascript"></script>
And there you have your array variable with all your values from your file, usable in your main page.
For example :
<script type="text/javascript">
var test='';
for (var country in country_code) {
test += country+':'+country_code[country]+'<br />';
}
document.body.innerHTML = test;
</script>
Does it help ?
im making a chrome extension and i have a problem loading a file...
I want to load a file when i select an option in the popup.html file
for example, if i select option1 i want eventPage1.js, to be loaded, and if i select option2 i want eventPage2.js to be loaded, but not the two at the same time, just one.
This its my popup.html file
<!DOCTYPE html>
<html>
<form>
Paises en el menu contextual:
<br>
<div>
<input type="radio" id="opcion1" name="opcion" value="opcion1" onclick= <script src="eventPage1.js"></script>
<label for="opcion1">Todos los paises</label>
<input type="radio" id="opcion2" name="opcion" value="opcion2" onclick= <script src="eventPage2.js"></script>
<label for="opcion2">Solo Mexico</label>
</div>
</form>
</body>
</html>
each eventpage file, its a context menu...
this its how my manifest file looks like
{
"manifest_version": 2,
"name": "help me",
"author": "me man",
"version": "1.1.4",
"description": "test test",
"browser_action":
{
"default_icon": "icon-large.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["menuSelection.js"]
},
"permissions": [
"storage",
"contextMenus"
],
"icons": {
"16": "icon-bitty.png",
"48": "icon-small.png",
"128": "icon-large.png"
}
}
another problem that i have, its that when i select an option... save that option in storage so everytime i use the extension its automatically loaded that option
Event page is a technical term for the page declared in "background" section of manifest.json with "persistent": false. It's not just a name. It's a special context which exists only for that single hidden page. Make sure to read the extension architecture overview.
Inline code in html file doesn't work in extension pages by default for security reasons (the onclick attribute in your html). Handle all events in a separate js file
The input tags in your html aren't closed, and actually malformed. There's no opening <body>.
To save the options use chrome.storage API or the old primitive string-based localStorage
To dynamically load js files, add a script element dynamically into head with its src property set to the name of the file to load. Or use the modern require()-based approach. However, you might want to start learning using a single script file.
Don't use form: the browser is not a web server so the page cannot be submitted. It will simply reload and you'll lose all data. Of course you can prevent the submit event in a listener but it defeats the purpose of using a form. Instead process a change immediately.
popup.html:
<!DOCTYPE html>
<html>
<body>
Paises en el menu contextual:
<div>
<label><input type="radio" name="opcion" value="opcion1">Todos los paises</label>
<label><input type="radio" name="opcion" value="opcion2">Solo Mexico</label>
</div>
<script src="popup.js"></script>
</body>
</html>
popup.js:
function setRadio(name, value) {
const el = document.querySelector(`input[name="${name}"][value="${value}"]`);
if (el && !el.checked) {
el.checked = true;
}
}
chrome.storage.sync.get('opcion', data => {
setRadio('opcion', data.opcion);
});
document.onchange = event => {
if (event.target.name == 'opcion') {
const value = event.target.value;
chrome.storage.sync.set({opcion: value});
switch (value) {
case 'opcion1':
doSomething1();
break;
case 'opcion2':
doSomething2();
break;
}
}
};
chrome.storage.onChanged.addListener((changes, area) => {
if (area == 'sync' && 'opcion' in changes) {
setRadio('opcion', changes.opcion.newValue);
}
});
I think you would probably want to do something like this when you click on the selected option:
<script type="text/javascript">
function LoadJavascriptFile(fileToLoad){
var script = document.createElement("script");
script.id = "customScript";
script.type = "text/javascript";
script.src = "fileToLoad";
document.getElementsByTagName("head")[0].appendChild(script);
}
</script>
On you option select you'd call
LoadJavascriptFile("PathToMyFile.js")
You'd then check if the Script ID exists in the header. If it does, update the SRC otherwise create the SRC
I found a very easy way to implement translation (or localization) of my Google Chrome Extension, but that seems to apply only to .json, css and js files.
But how to localize my html content, say in the popup or an options window?
What you would do is this.
First, in your HTML use the same syntax as Chrome requires anywhere else. So your basic popup.html will be:
<!DOCTYPE html>
<html>
<head>
<title>__MSG_app_title__</title>
</head>
<body>
__MSG_link001__
<!-- Need to call our JS to do the localization -->
<script src="popup.js"></script>
</body>
</html>
Then provide the usual translation in _locales\en\messages.json:
{
"app_title": {
"message": "MyApp",
"description": "Name of the extension"
},
"link001": {
"message": "My link",
"description": "Link name for the page"
},
"prompt001": {
"message": "Click this link",
"description": "User prompt for the link"
}
}
And finally your popup.js will perform the actual localization:
function localizeHtmlPage()
{
//Localize by replacing __MSG_***__ meta tags
var objects = document.getElementsByTagName('html');
for (var j = 0; j < objects.length; j++)
{
var obj = objects[j];
var valStrH = obj.innerHTML.toString();
var valNewH = valStrH.replace(/__MSG_(\w+)__/g, function(match, v1)
{
return v1 ? chrome.i18n.getMessage(v1) : "";
});
if(valNewH != valStrH)
{
obj.innerHTML = valNewH;
}
}
}
localizeHtmlPage();
Plain an simple:
{
"exmaple_key": {
"message": "example_translation"
}
}
<sometag data-locale="example_key">fallback text</sometag>
document.querySelectorAll('[data-locale]').forEach(elem => {
elem.innerText = chrome.i18n.getMessage(elem.dataset.locale)
})
Building from ahmd0's answer. Use a data attribute to allow a hard-coded fallback.
<!DOCTYPE html>
<html>
<head>
<title data-localize="__MSG_app_title__">My Default Title</title>
</head>
<body>
Default link text
<script src="localize.js"></script>
</body>
</html>
Then provide the usual translation in _locales\en\messages.json:
{
"app_title": {
"message": "MyApp",
"description": "Name of the extension"
},
"link001": {
"message": "My link",
"description": "Link name for the page"
},
"prompt001": {
"message": "Click this link",
"description": "User prompt for the link"
}
}
And finally your localize.js will perform the actual localization:
function replace_i18n(obj, tag) {
var msg = tag.replace(/__MSG_(\w+)__/g, function(match, v1) {
return v1 ? chrome.i18n.getMessage(v1) : '';
});
if(msg != tag) obj.innerHTML = msg;
}
function localizeHtmlPage() {
// Localize using __MSG_***__ data tags
var data = document.querySelectorAll('[data-localize]');
for (var i in data) if (data.hasOwnProperty(i)) {
var obj = data[i];
var tag = obj.getAttribute('data-localize').toString();
replace_i18n(obj, tag);
}
// Localize everything else by replacing all __MSG_***__ tags
var page = document.getElementsByTagName('html');
for (var j = 0; j < page.length; j++) {
var obj = page[j];
var tag = obj.innerHTML.toString();
replace_i18n(obj, tag);
}
}
localizeHtmlPage();
The hard-coded fallback avoids the i18n tags being visible while the JavaScript does the replacements. Hard-coding seems to negate the idea of internationalisation, but until Chrome supports i18n use directly in HTML we need to use JavaScript.
As RobW noted in a comment, a feature request for adding i18n support in HTML using the same mechanism was created, but it has since then been rejected due to performance and security concerns. Therefore you can't use the same approach.
The issue mentions one possible workaround: to have separate HTML pages per language and switch between them in the manifest:
"browser_action": {
"default_popup": "__MSG_browser_action_page__"
}
But if that's not a suitable approach, the only way is to translate the page dynamically via JavaScript. You mention a solution the simplest approach, by just tagging elements to translate with ids and replacing them on page load.
You can also employ more sophisticated tools like webL10n in parallel with Chrome's approach. Note that you should probably still minimally implement Chrome's approach, so that Web Store knows that the item is supporting several languages.
Rather than parsing the full DOM, just add a class "localize" to the elements that have to be translated and add a data attribute data-localize="open_dashboard"
<div class="localize" data-localize="open_dashboard" >
Open Dashboard
</div>
JavaScript :
$('.localize').each(function(index,item){
var localizeKey = $(item).data( 'localize' );
$(item).html(chrome.i18n.getMessage(localizeKey));
});
'_locales/en/messages.json' file
{
"open_dashboard": {
"message": "Open Dashboard",
"description": "Opens the app dashboard"
}
}
A workaround to avoid replacements:
Use a simple "redirect"
It works for popups and options
In your manifest, declare the default popup
"default_popup": "popup/redirect.html"
The popup/redirect.html is almost empty. It just includes the script link to the redirect script
<!DOCTYPE html>
<html>
<head></head>
<body>
<script src="redirect.js"></script>
</body>
The popup/redirect.js file is very simple too:
var currentlang = chrome.i18n.getMessage("lang");
var popupUrl = chrome.runtime.getURL("popup/popup-"+currentlang+".html");
window.location.href = popupUrl;
Create multiple popups, already localized:
popup-fr.html
popup-en.html
Go into each of your messages.json files (in _locales) and add a "lang" message with the current language abbreviation as value: en for the english json, fr in the french json...
example for _locales/en/message.json:
"lang": {
"message": "en",
"description": "Locale language of the extension."
},
A simple workaround for very small project... definitely not a good choice for large ones. And it also works for Option pages.
One of the ways to localize your content in popup html is to fetch it from javascript onLoad. Store the strings in the _locales folder under various languages supported by you as mentioned here and do chrome.i18n.getMessage("messagename") to fetch and load the variable strings and set them using javascript/jquery onLoad function for each html element from your background.js or whatever js you load before your html pages loads.
I faced the same problem, but I solved it with a simple approach using custom data attributes.
Implement a localizing class that uses chrome.i18n and call it in the DOMContentLoaded event. In HTML, mark up the element you want to localize with the data-chrome-i18n attribute. (This attribute name is tentatively named.) Specifying the message name as the value of this attribute localizes the text content of the element. If you want to localize an attribute, specify it in the format attribute_name=message_name. Multiple specifications can be specified by separating them with ;.
const i18n = (window.browser || window.chrome || {}).i18n || { getMessage: () => undefined };
class Localizer {
constructor(options = {}) {
const { translate = Localizer.defaultTranslate, attributeName = Localizer.defaultAttributeName, parse = Localizer.defaultParse } = options;
this.translate = translate;
this.attributeName = attributeName;
this.parse = parse;
}
localizeElement(element) {
for (const [destination, name] of this.parse(element.getAttribute(this.attributeName))) {
if (!name)
continue;
const message = this.translate(name) || '';
if (!destination) {
element.textContent = message;
}
else {
element.setAttribute(destination, message);
}
}
}
localize(target = window.document) {
const nodes = target instanceof NodeList ? target : target.querySelectorAll(`[${CSS.escape(this.attributeName)}]`);
for (const node of nodes)
this.localizeElement(node);
}
}
Localizer.defaultTranslate = i18n.getMessage;
Localizer.defaultAttributeName = 'data-chrome-i18n';
Localizer.defaultParse = (value) => {
return (value || '').split(';').map(text => (text.includes('=') ? text.split('=') : ['', text]));
};
const localizer = new Localizer();
window.addEventListener('DOMContentLoaded', () => {
localizer.localize();
});
<!DOCTYPE html>
<html data-chrome-i18n="lang=##ui_locale">
<head>
<meta charset="UTF-8" />
<title data-chrome-i18n="extensionName"></title>
</head>
<body>
<p data-chrome-i18n="foo;title=bar;lang=##ui_locale"></p>
</body>
</html>
There are several things to consider to solve this problem.
Use chrome.i18n (Many people will want to aggregate in messages.json.)
Supports attributes as well as element content
Supports not only popup but also options page
Rendering performance
Security
First, the approach of switching HTML for each language in manifest.json does not work. Even if you give __MSG_*__ to the default_popup field, popup will still show the error "ERR_FILE_NOT_FOUND". I don't know why. There is no detailed reference to default_popup in the Chrome extensions Developer Guide, but MDN mentions that it is a localizable property. Similarly, if you give __MSG _*__ to the page field in options_ui, the extension itself will fail to load.
I intuitively felt that the approach of replacing __MSG_*__ in HTML and rewriting the result usinginnerHTML had performance and security problems.
This answer is cool!
And I want to make some modifications.
For chrome 93.0.4577.63 chrome.i18n.getMessage permalink, link-by-version
chrome.i18n.getMessage(messageName, substitutions, {escapeLt})
So I want to make it support
substitutions
escapeLt
Test Data
// _locales/en/messages.json
{
"hello": {
"message": "<b>Hello</b> $USER$ Welcoming $OUR_SITE$. $EMOJI$",
"description": "Greet the user",
"placeholders": {
"user": {
"content": "$1", // chrome.i18n.getMessage("hello", "variable 1")
"example": "Carson"
},
"our_site": {
"content": "Example.com"
},
"emoji": {
"content": "$2",
"example": "\uD83D\uDE42" // 🙂, 😎
}
}
},
"app": {
"message": "My cool APP.",
"description": "description"
}
}
<!-- test.html-->
<script src="my-i18n.js"></script>
<p data-i18n="__MSG_hello__"></p>
<p data-i18n="__MSG_hello__<b>Carson</b>"></p>
<p data-i18n="__MSG_hello__<b>Carson</b>|0"></p>
<p data-i18n="__MSG_hello__<i>Carson</i>|1"></p>
<button title="__MSG_hello__<b>Carson</b>" data-i18n></button>
<button title="__MSG_hello__<b>Carson</b>|0" data-i18n></button>
<button title="__MSG_hello__<b>Carson</b>|1" data-i18n></button>
<p title="__MSG_app__" data-i18n="__MSG_hello__Carson,🙂"></p>
output
Script
// my-i18n.js
/**
* #param {string} msg "__MSG_Hello__para1,para2|1" or "__MSG_Hello__para1,para2|0"
* */
function convertMsgAsFuncPara(msg) {
const match = /__MSG_(?<id>\w+)__(?<para>[^|]*)?(\|(?<escapeLt>[01]{1}))?/g.exec(msg) // https://regex101.com/r/OeXezc/1/
if (match) {
let {groups: {id, para, escapeLt}} = match
para = para ?? ""
escapeLt = escapeLt ?? false
return [id, para.split(","), Boolean(Number(escapeLt))]
}
return [undefined]
}
function InitI18nNode() {
const msgNodeArray = document.querySelectorAll(`[data-i18n]`)
msgNodeArray.forEach(msgNode => {
const [id, paraArray, escapeLt] = convertMsgAsFuncPara(msgNode.getAttribute("data-i18n"))
if (id) {
msgNode.innerHTML = chrome.i18n.getMessage(id, paraArray, {escapeLt})
}
// ↓ handle attr
for (const attr of msgNode.attributes) {
const [attrName, attrValue] = [attr.nodeName, attr.nodeValue]
const [id, paraArray, escapeLt] = convertMsgAsFuncPara(attrValue)
if (!id) {
continue
}
msgNode.setAttribute(attrName, chrome.i18n.getMessage(id, paraArray, {escapeLt}))
}
})
}
(() => {
window.addEventListener("load", InitI18nNode, {once: true})
})()
Modify pseudo-category content in batches.
<div data-content="font"></div>
div::before {
content: attr(data-content);
}
document.querySelectorAll('[data-content]').forEach(el => {
el.dataset.content = chrome.i18n.getMessage(el.dataset.content);
});
Use CSS Internationalization.
<p></p>
p::before {
content: "__MSG_font__";
}
Another workaround - you can use content property in css with __MSG_myText inside.
Use Vue.js:
<html>
<head>
</head>
<body>
<div id="app">{{msgTranslated}}</div>
</body>
</html>
javascript file injected:
new Vue({
el: '#app',
data: {
msgTranslated: chrome.i18n.getMessage("message")
}
})
i have two files
one called stats.js
one called storage.html
in stats.js in contains
var stats = {
myFunc : function() {
//do something
}
}
in storage.html I have
<html>
<head>
<script src="stats.js"></script>
<script>
$(document).ready(function() {
stats.myFunc();
});
</script>
</head>
</html>
But I get
Uncaught TypeError: Cannot call method 'myFunc' of undefined
Update
Ok so that was a really simplified example.
The basics of it are,
This is a google chrome extension, So you will see some code specific to that.
Here is the literal pages concerned:
Popup.html
<html>
<head>
<title>Extension</title>
<script src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script src="js/popup.js"></script>
<script src="js/statsapi.js"></script>
<link type="text/css" rel="stylesheet" href="css/popup.css" />
</head>
<body>
<div id="content">
</div>
</body>
</html>
popup.js
$(document).ready(function() {
if(background.storage.get('firstRun') == null)
background.initialize();
if(background.storage.get('metaExpire') >= Date.parse(Date()))
background.updateMeta();
$('#content').append(read_object(stats.getMetaData()));
});
function read_object(object){
var $obj = $('<div />');
for(var o in object) {
$obj.append(o+' : ');
if(typeof(object[o]) == 'object' && object[o] != null)
$obj.append(read_object(object[o]));
else
$obj.append(object[o]+'<br />');
}
return $obj;
}
Manifest.json
{
"name": "Halo Reach: Stats",
"description": "This extension allows you to keep track of your own, and your friends Halo Reach Stats.",
"version": "1.0.0.1",
"permissions": [
"http://www.bungie.net/"
],
"icons": {
"128": "images/logo/logo128.jpg",
"64": "images/logo/logo64.jpg",
"32": "images/logo/logo32.jpg",
"16": "images/logo/logo16.jpg"
},
"browser_action": {
"default_title": "Open Stats",
"default_icon": "images/logo/logo32.jpg",
"popup": "popup.html"
},
"background_page": "background.html"
}
statsapi.js
var background = chrome.extension.getBackgroundPage();
var apikey = background.storage.get('apikey');
var gamertage = background.storage.get('gamertag');
var page = '0';
var stats = {
getMetaData : function() {
var url = 'http://www.bungie.net/api/reach/reachapijson.svc/game/metadata/'+apikey;
console.log(url);
$.ajax({
url: url,
success: function(data) {
return data;
}
});
},
meta : {
read : function(param) {
var meta = background.storage.get('metaData');
}
}
};
Background.html
<html>
<head>
<script src="js/statsapi.js"></script>
<script>
var storage = {
set : function (key, value) {
window.localStorage.removeItem(key);
window.localStorage.setItem(key, value);
},
get : function (key) {
return window.localStorage.getItem(key);
},
clear : function () {
window.localStorage.clear();
}
};
function updateMeta() {
var meta = stats.getMetaData();
if(meta['status'] == 0){
storage.set('metaData', JSON.stringify(meta));
storage.set('metaExpire', Date.parse(Date())+900000);
}
}
function initialize() {
storage.set('apikey', '***');
storage.set('gamertag', 'The Hailwood');
updateMeta();
}
</script>
</head>
</html>
When the extension is invoked it calls popup.html
and the document ready javascript is invoked.
The check for first run fails,
so it calls initialize() in background.html
But this is where the error occurs.
the actual error is
Uncaught TypeError: Cannot call method 'getMetaData' of undefined.
So why can it not see the stats class?
its not a script include problem as if the path is wrong for the statsapi.js I get
Uncaught ReferenceError: stats is not defined.
The issue seems to be with the var stats {} as if under that I have a function called test() I can call that fine :/
Hmm,
is there an issue because it is an external stylesheet?
I suspect the error lies somewhere else - these are my examples:
mark#localhost:~/ccsite$ cat cat.js
var stats = {
myFunc : function() {
alert('wtf');
}
}
mark#localhost:~/ccsite$ cat hat.htm
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="cat.js"></script>
<script>
$(document).ready(function() {
stats.myFunc();
});
</script>
</head>
</html>
Viewing hat.htm in either FF, IE6 or Chrome produces the alert, 'wtf'. As written you'd get $ is undefined, since it's not including jQuery of course, so I added that.
So, your problems likely are elsewhere. I assume this is a simplified example - what else is going on in your page?
This is because there is some syntax error in your code. I had same problem. I opened my background.html page in fire fox with fire-bug plug-in enabled. Fire-bug console should me the error, I fixed and it is working now.
I have suspicions that it's because you include js/statsapi.js script into both your popup and background page, so it gets confused which stats you are referring to as you have 2 of them in the popup - one included through script tag and another one loaded from background page after you call chrome.extension.getBackgroundPage()