I have a json file that stores data which is displayed on my page using javascript. This json file and its key val pairs are not visible or accessible in Chrome's Dev Tools. This component manages json files:
/**
* Takes a filename and a JS object and initiates a download through the browser
* #param {String} filename
* #param {any} object JSON serializable object
* #return {undefined}
*/
export const downloadJson = (filename, object) => {
const content = JSON.stringify(object, null, 2);
const el = document.createElement('a');
el.setAttribute('href', `data:application/json;charset=utf-8,${encodeURIComponent(content)}`);
el.setAttribute('download', filename);
el.hidden = true;
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
};
/**
* Gets the `target.result` property from an event, or returns null
* if it fails at any point
* #type {Function}
* #param {Event} event load Event
* #return {File}
*/
const getFileResult = propPathOr(null, ['target', 'result']);
/**
* Takes a file and reads it as JSON, resolving the JSON-parsed
* file contents
* #param {File} file
* #return {Promise<[Object]>} Returns Promise of Array of Archive Entries
*/
export const readFileAsJson = file => {
const reader = new FileReader();
const promise = new Promise((resolve, reject) => {
reader.onload = compose(resolve, JSON.parse, getFileResult);
reader.onerror = reject;
});
reader.readAsText(file);
return promise;
};
export const readFileListAsJson = files =>
Promise.all(
Array.from(files)
.map(readFileAsJson)
)
.catch(console.error);
This is the database component:
// DATABASE functions
import { get, set, keys } from 'idb-keyval';
import { sha1 } from './hash.js';
const getKey = key => get(key);
export const getAllEntries = async () =>
await Promise.all((await keys()).map(getKey));
export const writeMultipleEntries = entries =>
entries.forEach(writeSingleEntry);
/**
* #typedef {Object} ArchiveEntry
* #property {String} date
* #property {String} passage
* #property {String} question
* #property {String} answer
*/
/**
* Writes a single archive entry to idb
* #param {ArchiveEntry} entry
* #return {ArchiveEntry}
*/
export const writeSingleEntry = async ({ date, passage, question, answer }) => {
const hash = await hashEntry({ date, passage, question });
await set(hash, { date, passage, question, answer });
return { date, passage, question, answer };
};
/**
* Generates a hash of an entry to use as it's idb key
* #param {ArchiveEntry} entry
* #return {string}
*/
const hashEntry = ({ date, passage, question }) =>
sha1(`${date}-${passage}-${question}`);
Values are stored using this function:
const updateDb =
({ passage, question }) =>
(answer) =>
writeSingleEntry({ date: new Date(), answer, passage, question });
Storage is handled by its own script:
export const storeOnInput = key => ({ target: { value } }) => writeValue(key, value);
export const readValue = key => localStorage.getItem(key);
export const writeValue = (key, val) => localStorage.setItem(key, val);
It is called in several components. Here to write and read the value of a text passage:
onActiveChanged(active) {
this.passage = readValue('passage-input');
}
onKeyup(event) {
writeValue('passage-input', event.target.value);
}
Here to write and record a question:
onActiveChanged(active) {
this.question = readValue("question-input");
this.passage = readValue("passage-input");
}
onKeyup(event) {
writeValue("question-input", event.target.value);
}
Here to provide an answer and reset the form:
const answer = document.getElementById('answer');
const write = document.getElementById('write');
const question = document.getElementById('question');
const onAnswerSubmitted = ({ detail: answer }) => {
writeValue('answer', answer);
};
onActiveChanged(active) {
if (!active) return;
this.answer = readValue('answer');
}
resetQuestion() {
this.dispatchEvent(new CustomEvent('reset-question'));
writeValue('question-input', '');
writeValue('answer', '');
}
resetWrite() {
this.resetQuestion();
this.dispatchEvent(new CustomEvent('reset-passage'));
writeValue('passage-input', '');
}
Here to get entries:
onActiveChanged(active) {
if (active) this.getEntries();
}
async getEntries() {
this.entries = await getAllEntries();
this.entry = new URLSearchParams(location.search.substring(1)).get("date");
console.log("here are the dates: \n", prettyDate(this.entries[0].date));
console.log("here is an answer: \n", this.entries[0].answer);
}
Here to download and upload the JSON file:
async exportBackup() {
downloadJson(`backup ${new Date()}.json`, await getAllEntries());
}
async importBackup({ target: { files } }) {
return readFileListAsJson(files)
.then(map(writeMultipleEntries));
}
Unlike this question, nothing is showing in Storage > Local Storage, and it is not
a Chrome UI design flaw issue.
It is possible to confirm the values have been written and are are accessible from the json file using functions like:
console.log(this.entries[0].date)
console.log(this.entries[0].answer)
but I would like to be able to debug by viewing the entire json file.
I had the same problem today while working on my webapp :
I could access some data i registered on the localstorage via the console (JSON.parse(localStorage["my-storage-key"])
But in the Chrome dev tools, in the Application tab, the https://localhost:4200 entry was totaly empty, just like in the screen capture you provided.
What fixed the problem for me was to click "Restore defaults and reload" in the preferences of the chrome DevTools, and i could see the entries in the table again.
It doesn't appear as though you have loaded the JSON file into local storage at any point. Perhaps there is driver code which you can share so that your issue can be more easily debugged.
In the meantime, checkout the documentation for localstorage on mdn. I think you may find the answer by reading up on how to set local storage.
Related
So I have a response object with a data property. For this example, its data is of type text. I want the data property, and I want VSCode to recognize it types so that I can get some of that IntelliSense love.
const response = await someRequest();
const {data} = response;
First I try:
const response = await someRequest();
/** #type {string} */
const {data} = response;
Nope, so then I try:
const response = await someRequest();
const {
/** #type {string} */
data,
} = response;
that didn't work either.
Then I try:
/**
* #typedef {Object} Response
* #property {string} data
*/
/** #type {Response} */
const response = await someRequest();
const { data } = response;
That worked!
I'm not sure how much of a difference it makes, but I'm using VSCode September 2020 (version 1.50).
Edits: I changed how my question ended because it turned out that my final example actually works...
/**
* #typedef {Object} Response
* #property {string} data
*/
/** #type {Response} */
const response = await someRequest();
const { data } = response;
I didn't think this was working, but it is, so I'm marking it as the answer.
Thanks to Dolly for her comment with how to document a deconstructed variable with jsdoc. It wasn't an exact fit, but it does answer my question.
I have ran the chrome audit for my project, where I was suggested to reduce the impact of third-party code. In my case I am loading google maps api. I am doing that like this:
In my main.js I check for the element to attach the map:
if (document.querySelector('.js-map')) {
GMap.GMap('.js-map', process.env.GOOGLE_MAPS_API_KEY);
}
GMap looks like this:
export function GMap(el, apiKey) {
const gApiKey = apiKey
const gmapApi = new GoogleMapsApi(gApiKey)
const mapEl = document.querySelector(el)
const data = {
lat: parseFloat(mapEl.dataset.lat ? mapEl.dataset.lat : 0),
lng: parseFloat(mapEl.dataset.lng ? mapEl.dataset.lng : 0),
zoom: parseFloat(mapEl.dataset.zoom ? mapEl.dataset.zoom: 12),
}
// Call map renderer
gmapApi.load().then(() => {
renderMap(mapEl, data);
document.querySelector('.static-map-img').setAttribute("style", "display:none;");
document.querySelector('.map').removeAttribute('style');
})
}
And this is the GoogleMapsApi:
/**
* GoogleMapsApi
* Class to load google maps api with api key
* and global Callback to init map after resolution of promise.
*
* #exports {GoogleMapsApi}
* #example MapApi = new GoogleMapsApi();
* MapApi.load().then(() => {});
*/
class GoogleMapsApi {
/**
* Constructor
* #property {string} apiKey
* #property {string} callbackName
*/
constructor(gApiKey) {
// api key for google maps
this.apiKey = gApiKey;
// Set global callback
if (!window._GoogleMapsApi) {
this.callbackName = '_GoogleMapsApi.mapLoaded';
window._GoogleMapsApi = this;
window._GoogleMapsApi.mapLoaded = this.mapLoaded.bind(this);
}
}
/**
* Load
* Create script element with google maps
* api url, containing api key and callback for
* map init.
* #return {promise}
* #this {_GoogleMapsApi}
*/
load() {
if (!this.promise) {
this.promise = new Promise(resolve => {
this.resolve = resolve;
if (typeof window.google === 'undefined') {
const script = document.createElement('script');
script.src = `//maps.googleapis.com/maps/api/js?key=${this.apiKey}&callback=${this.callbackName}`;
script.async = 'true';
script.defer = 'true';
script.setAttribute('rel', 'preload');
document.body.append(script);
} else {
this.resolve();
}
});
}
return this.promise;
}
/**
* mapLoaded
* Global callback for loaded/resolved map instance.
* #this {_GoogleMapsApi}
*
*/
mapLoaded() {
if (this.resolve) {
this.resolve();
}
}
}
export default GoogleMapsApi;
After running the audit I have changed the loading of the map to happen after the page has loaded:
window.onload = function () {
if (document.querySelector('.js-map')) {
GMap.GMap('.js-map', process.env.GOOGLE_MAPS_API_KEY);
}
};
But, I still get the same audit message from chrome's lighthouse:
You can see a working example here.
Update
I have also tried to load the map with async await logic like this:
const loadMap = async () => {
if (document.querySelector('.js-map')) {
setTimeout(async () => {
const GMap = await import('./GMap/index');
GMap.default('.js-map', process.env.GOOGLE_MAPS_API_KEY)
}, 0);
}
}
async function concurrentStart() {
const map = loadMap();
InfoComponent.init();
SelectBuilder.init();
FilterBuilder.init();
window.onload = async function () {
const lightButton = document.querySelector('.light-icon');
const changeTheme = await import('./themeSelector');
lightButton.addEventListener('click', changeTheme.default, { capture: true, passive: true });
};
await map;
}
concurrentStart();
But, I kept getting the same result when running lighthouse audit. What is the right way of loading the map without blocking the rest of the app's code?
Lighthouse will wait until there is no a single task in the event loop, so the longer you set the setTimeout the lower rank you'll get. Check this out
I'd just let the browser do the dirty work.
Just add the script, and use it's async / defer attributes
Here you go with more information about async / defer and about the event loop
Consider using other performance tools as well, like https://developers.google.com/speed and https://www.webpagetest.org/
If it is critical, you could load the lib only when it is visible by using scroll events or intersection observer but it is likely that lighthouse won't feel any improvements, the online tools above will do.
I have a cloud function that triggers when new file is created in firebase storage. Inside this function logic I need to collect every other file located at the same path in array. But I don't know how.
exports.testCloudFunc = functions.storage.object().onFinalize(async object => {
const filePath = object.name;
const { Logging } = require('#google-cloud/logging');
console.log(`Logged: ${filePath}`);
let obj = JSON.stringify(object);
console.log(`Logged: ${obj}`);
});
After that I will try to merge all PDFs in one new file and save it back to firebase storage by the same path. Any help is highly appretiated! Thank you in advance for your wisdom )
As per the documentation Doug Stevenson linked (2nd code sample for Node.js), you can list objects from a specified folder within your bucket using prefixes and delimiters.
Sample from the mentioned documentation:
/**
* TODO(developer): Uncomment the following lines before running the sample.
*/
// const bucketName = 'Name of a bucket, e.g. my-bucket';
// const prefix = 'Prefix by which to filter, e.g. public/';
// const delimiter = 'Delimiter to use, e.g. /';
// Imports the Google Cloud client library
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
async function listFilesByPrefix() {
/**
* This can be used to list all blobs in a "folder", e.g. "public/".
*
* The delimiter argument can be used to restrict the results to only the
* "files" in the given "folder". Without the delimiter, the entire tree under
* the prefix is returned. For example, given these blobs:
*
* /a/1.txt
* /a/b/2.txt
*
* If you just specify prefix = '/a', you'll get back:
*
* /a/1.txt
* /a/b/2.txt
*
* However, if you specify prefix='/a' and delimiter='/', you'll get back:
*
* /a/1.txt
*/
const options = {
prefix: prefix,
};
if (delimiter) {
options.delimiter = delimiter;
}
// Lists files in the bucket, filtered by a prefix
const [files] = await storage.bucket(bucketName).getFiles(options);
console.log('Files:');
files.forEach(file => {
console.log(file.name);
});
}
listFilesByPrefix().catch(console.error);
Does that mean that all files will be returned at first and then will
be filtered by prefix?
As I see in the code sample above, the array [files] will store the objects that already pass the filter requirements:
const [files] = await storage.bucket(bucketName).getFiles(options);
I have a Vue component with one method...
methods: {
launchOpinionLab () {
initOpinionLab({
clientId: this.clientId,
flow: this.flow,
srcCorrelationId: this.srcCorrelationId
})
window.OOo.inlineFeedbackShow()
}
initOpinionLab() is a exported function as such
initOpinionLab.js
/**
* Initializes the OpinionLab library with custom data.
*
* #param {Object} data - any data we want to attach to user feedback submissions
*/
export default function initOpinionLab (data) {
const opinionLab = window.OOo
/**
* Callback to launch the feedback comment card.
* This assigns our custom data to the OpinionLab instance.
*
* #param {Event} event - window event
*/
opinionLab.inlineFeedbackShow = (event) => {
let replacePattern = '://' + window.location.host
if (window.location.host !== 'checkout.mastercard.com') {
replacePattern = '://test.checkout.mastercard.com'
}
opinionLab.oo_feedback = new opinionLab.Ocode({
referrerRewrite: {
searchPattern: /:\/\/[^/]*/,
replacePattern: replacePattern
},
customVariables: data
})
// Now that oo_feedback has been re-initialized with the custom
// var and context of the current page, launch the comment card
opinionLab.oo_launch(event, 'oo_feedback')
}
/**
* Launches opinionLab.
*
* #param {Event} event
* #param {Object} feedback - opinionLab-structured feedback object
*/
opinionLab.oo_launch = (event, feedback) => {
opinionLab[feedback].show(event || window.event)
}
}
At the top of my unit test is the mock of that module, jest.mock('./initOpinionLab', () => jest.fn()).
My goal is to assert that initOpinionLab({}) is called with the correct props. When I try to spy on it, I get "Cannot spy the initOpinionLab property because it is not a function; undefined given instead". How do I write a robust test for this?
Or, what if I moved initOpinionLab() to mounted? Can I better test it then?
component.spec.js
it('[positive] initOpinionLab should be initialized with props' , () => {
const initOpinionLab = jest.fn()
jest.spyOn(wrapper.vm, 'initOpinionLab')
wrapper.vm.launchOpinionLab()
expect(initOpinionLab).toHaveBeenCalled()
})
I am attempting to use wordpress to build a website that integrates google maps. I am doing some overlays with the maps and use the google developers API and Python to make the appropriate javascript. I have successfully written the js files and Python necessary to accomplish this.
My website is built in Worpress and I would like add a page (not the home page) that has n links and each one would populate a box with the corresponding map. I can take care of the layout and design issues but I am at a loss on how to:
a) Include the javascript as a file that
b) gets called upon clicking the link and thus populates that map without calling a new page
That is, the javascript is HUGE because it may include thousands of lat/lon points. Therefore including n of these written into the header is unreasonable. I want to simply call it from filename.js when the link is clicked.
There is a plugin that allows me to include whatever I want in the header. So, if I can find out where to put the *.js files (or txt file) in the directory tree and how to have the corresponding file activated upon click I should be good. Thanks!
This Display different maps with onClick event - Google Maps V3. kind of helps with doing an on-click display but everyone's solution was to make one map. I cannot do that. I am overlaying vast amounts of data.
Here is a way you can get that done. (Jump down to the get started part of the script.)
For brevity, I've included a bunch of scripts in one 'file', but you'll want to break them in to individual files.
You may also need to try the html and js in jsbin js bin example, b/c SO may or may not allow the dynamic loading of js.
(function(undefined) {
/**
* #author (#colecmc)
* #method turn collection into an array
* #param {object} collection - NodeList, HTMLCollection, etc. Should have an "item" method and/or a "length" property
*/
ToArray = collection => Array.prototype.slice.call(collection);
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
Observer = (function(undefined) {
/**
* pub sub
*/
'use strict';
var subUid = -1;
return {
topics: {},
subscribe: function(topic, func) {
/**
* #param {string} topic
* #param {function} func
* #returns {string} - a token such as '3'
* #example Observer.subscribe('any-valid-string',function(name,resp){
console.log(resp.prop);
});
*/
if (!Observer.topics[topic]) {
Observer.topics[topic] = [];
}
var token = (++subUid).toString();
Observer.topics[topic].push({
token: token,
func: func
});
return token;
},
publish: function publish(topic, args) {
/**
* #param {string} topic
* #param {object} args
* #returns {boolean} - true if topic is valid, false otherwise
* #example Observer.publish('any-valid-string',{
prop: 'this is a test'
});
*/
if (!Observer.topics[topic]) {
return false;
}
setTimeout(function() {
var subscribers = Observer.topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
}, 0);
return true;
},
unsubscribe: function unsubscribe(token) {
/**
* #param {string} token - value should be saved from the original subscription
* #example Observer.unsubscribe('2');
* #example Observer.unsubscribe(member); - where member is the value returned by Observer.subscribe();
*/
var m,
forEachTopic = function(i) {
if (Observer.topics[m][i].token === token) {
Observer.topics[m].splice(i, 1);
return token;
}
};
for (m in Observer.topics) {
if (Observer.topics.hasOwnProperty(m)) {
Observer.topics[m].forEach(forEachTopic);
}
}
return false;
}
};
}(undefined));
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
SetAttributes = function(el, attrs) {
/**
* #author (#colecmc)
* #method simple for in loop to help with creating elements programmatically
* #param {object} el - HTMLElement attributes are getting added to
* #param {object} attrs - object literal with key/values for desired attributes
* #example SetAttributes(info,{
* 'id' : 'utswFormInfo'
* 'class' : 'my-class-name'
* });
*/
'use strict';
var key;
for (key in attrs) {
if (attrs.hasOwnProperty(key)) {
el.setAttribute(key, attrs[key]);
}
}
return el;
};
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
GetScript = function(url, fullPath) {
/**
* #author (#colecmc)
* #version 1.0.4
* #requires Swlxws.SetAttributes, Swlxws.Observer
* #method dynamically add script tags to the page.
* #param {string} url - relative path and file name - do not include extension
* #param {string} fullPath - absolute path
* #example GetScript('myLocalScript');
* #example GetScript('','https://www.google-analytics.com/analytics.js');
*/
'use strict';
function onLoad(event) {
var result;
if (event.type === 'load') {
result = 1;
} else {
result = -1;
}
Observer.publish('get-script-onload-complete', {
successful: result,
eventData: event
});
}
var JSPATH = '/js/',
/* or where ever you keep js files */
el = document.createElement('script'),
attrs = {
defer: true,
src: null,
type: 'text/javascript'
};
/** look for a string based, protocol agnostic, js file url */
if (typeof fullPath === 'string' && fullPath.indexOf('http') === 0) {
attrs.src = fullPath;
}
/** look for any string with at least 1 character and prefix our root js dir, then append extension */
if (typeof url === 'string' && url.length >= 1) {
attrs.src = JSPATH + url + '.js';
}
SetAttributes(el, attrs);
el.addEventListener('load', onLoad);
el.addEventListener('error', onLoad);
document.body.appendChild(el);
return el;
};
/** \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ **/
/**
* Get Started
*/
function onClick(event) {
GetScript('', event.target.dataset.namespaceUrl);
}
Observer.subscribe('get-script-onload-complete', function(name, resp) {
/** check to make resp is what you expect, ie: the correct script loaded */
/** then it is safe to use */
});
ToArray(document.querySelectorAll('.load-scripts')).map(script => script.addEventListener('click', onClick, false));
}(undefined));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>How to include js files in header of wordpress pages that are activated on-click</title>
</head>
<body>
Load Google Analytics
</body>
</html>
You can use the function wp_enqueue_script() to load the necessary JS files on only the templates you want.
As for your large data set, I recommend that you cache it in an external .json file and use wp_enqueue_script() to load it only when necessary.
Well if the onclick event suggestion is pretty much what you want and you are just concerned about the large amount of data. Then there are a few ways to tackle it. I am not sure if the dataset is a js file or php/json files but i came across a similar issue on one of my projects, dont remember properly but i was doing something with maxmind's ip/location data set.
So i just splitted the large file into 3 smaller ones. Then i looped through each of the file and if the stuff that i was looking for was found in the file then i just breaked out. And definitely as Brian suggested caching and using a CDN would help a lot.