I've inherited a project and the original author split their code into separate JS files. One of them contains all of the GET requests, such as Departments, Categories, Titles, etc.
I have some code for an autocomplete dropdown in a separate file, index.js, and I want to populate the dropdown with the Department results. I'm not that well-versed in JS and I'm not sure what I have to do to "connect" the files and reference the Departments GET request.
index.js:
import "jquery";
import "jquery-autocomplete/jquery.autocomplete.css";
import "jquery-autocomplete/jquery.autocomplete.js";
function loadCombobox() {
let depts = {
"t1": "1",
"t2": "2"
} // added this as a test
let deptsArray = $.map(depts, function(value, key) {
return {
value: value,
data: key
};
})
console.log(deptsArray);
$('#combobox').autocomplete({
source: deptsArray
}).on("click", function() {
console.log("combobox clicked") // works
});
}
loadCombobox();
data-request.js
import axios from "axios";
const baseUrl = siteAbsoluteUrl;
const deptList = "Department List";
async function makeGetRequest(url) {
const response = await axios.get(url, getRequestConfig);
const { results, __next } = response.data.d;
return { results, __next };
}
// gives back an array containing all departments, without duplicates
export async function makeDepartmentsRequest() {
const { results } = await makeGetRequest(
`${baseUrl}/getbytitle('${deptBPList}')/fields?$filter=EntityPropertyName eq 'Department'`
);
return results[0].Choices.results;
}
html:
<input type="text" id="combobox" placeholder="Browse departments">
console:
Something like this would be the end result (note that I don't know what makeDepartmentsRequest returns or what #combobox is so I can't test this code)
import { makeDepartmentsRequest } from "path-to/data-request";
function loadCombobox() {
makeDepartmentsRequest.then(choices => {
const source = $.map(depts, function(value, key) {
return {
value: value,
data: key
};
});
$("#combobox")
.autocomplete({
source
})
.on("click", function() {
console.log("combobox clicked"); // works
});
});
}
Related
I have a chrome extension with the following webpack.config.js:
module.exports = {
mode,
entry: {
"content/content": [
"./src/js/content/content.js",
"./src/js/store.js",
"./src/js/content/overlay/style.scss",
],
"background/background": [
"./src/js/background/utils.js",
"./src/js/background/background.js",
],
"overlay/overlay": "./src/js/content/overlay/index.js",
"popup/popup": "./src/js/content/popup/index.js",
},
looking at
Shared vuex state in a web-extension (dead object issues)
https://github.com/xanf/vuex-shared-mutations
Adding a wrapper around browser local storage:
browserStore.js
import browser from "#/js/browser";
export function getStorageValue(payload) {
return new Promise((resolve) => {
browser.storage.local.get(payload, (items) => {
if (items) {
resolve(items);
}
});
});
}
export function setStorageValue(payload) {
return new Promise((resolve) => {
browser.storage.local.set(payload, (value) => {
resolve(value);
});
});
}
In "./src/js/content/popup/firstpage/store/index.js" vuex store is defined as:
import Vue from "vue";
import Vuex from "vuex";
import "es6-promise/auto";
import createMutationsSharer from "vuex-shared-mutations";
import dummyData from "./dummyData";
import { getStorageValue, setStorageValue } from "#/js/store";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
chromePagesState: {
allSections: [],
},
},
getters: {
...
},
mutations: {
setChromePagesState(state, value) {
...
},
// this function is to be called from a content script
addWhiteListedItem(state, item) {
// state not initialized here
state.chromePagesState.allSections[0].itemSectionCategory[0].tasks.splice(
0,
0,
item
);
},
...
}
actions: {
async saveChromePagesState({ state }) {
// Save only needed fields
let data = {
...
};
await setStorageValue({ inventoryData: JSON.stringify(data) });
},
async loadChromePagesState({ commit }) {
const json = await getStorageValue("inventoryData");
// json always an empty object
commit(
"setChromePagesState",
Object.keys(json).length === 0 && json.constructor === Object
? json
: dummyData
);
},
async loadChromePagesStateBrowser({ commit }) {
browser.runtime
.sendMessage({ type: "storeinit", key: "chromePagesState" })
.then(async (chromePagesState) => {
const json = await getStorageValue("inventoryData");
commit(
"setChromePagesState",
Object.keys(json).length === 0 && json.constructor === Object
? json
: dummyData
);
});
},
plugins: [
createMutationsSharer({
predicate: [
"addWhiteListedItem",
"loadChromePagesState",
"loadChromePagesStateBrowser",
],
}),
],
},
the background script has a listener; src/background/background.js:
browser.runtime.onMessage.addListener((message, sender) => {
if (message.type === "storeinit") {
return Promise.resolve(store.state[message.key]);
}
});
The content script that needs to make use of the shared store has an entry point in content.js:
import { initOverlay } from '#/js/content/overlay';
import browser from '#/js/browser';
browser.runtime.onMessage.addListener(function (request, _sender, _callback) {
// vue component gets created here:
if (request && request.action === 'show_overlay') {
initOverlay();
}
return true; // async response
});
initOverlay() creates a vue component in ./src/js/content/overlay/index.js:
import Vue from "vue";
import Overlay from "#/js/content/overlay/Overlay.vue";
import browser from "#/js/browser";
import { getStorageValue } from "#/js/store";
import store from "../popup/firstpage/store";
Vue.prototype.$browser = browser;
export async function initOverlay(lockScreen = defaultScreen, isPopUp = false) {
...
setVueOverlay(overlayContainer, cover);
...
}
function setVueOverlay(overlayContainer, elem) {
if (!elem.querySelector("button")) {
elem.appendChild(overlayContainer);
elem.classList.add("locked");
new Vue({
el: overlayContainer,
store,
render: (h) => h(Overlay, { props: { isPopUp: isPopUp } }),
});
}
}
Overlay.vue only needs to call a mutation (addWhiteListedItem) from store:
<template>
<button
#click="addToWhiteList()"
>White list!</button
>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
data() {
return {
};
},
computed: mapState(["chromePagesState"]),
methods: {
...mapMutations(["addWhiteListedItem"]),
addToWhiteList() {
console.log("addToWhiteList()");
let newItem = {
...
};
// store not defined fails with:
Uncaught TypeError: Cannot read property 'itemSectionCategory' of undefined
at Store.addWhiteListedItem (index.js:79)
at wrappedMutationHandler (vuex.esm.js:853)
at commitIterator (vuex.esm.js:475)
at Array.forEach (<anonymous>)
at eval (vuex.esm.js:474)
at Store._withCommit (vuex.esm.js:633)
at Store.commit (vuex.esm.js:473)
at Store.boundCommit [as commit] (vuex.esm.js:418)
at VueComponent.mappedMutation (vuex.esm.js:1004)
at eval (Overlay.vue?./node_modules/vue-loader/lib??vue-loader-options:95)
this.addWhiteListedItem(newItem);
}, 1500);
},
},
};
</script>
Why doesn't Overlay.vue "see" the state of store?
Flow:
enabling the extension injects a content script into a page
content script imports store object (that is not yet initialized)
upon clicking popup (/new tab) popup.js sends a message to the background script that also imports store and calls a mutation (that initializes state):
background.js
import store from "../content/popup/firstpage/store";
browser.runtime.onMessage.addListener((message, sender) => {
console.log("in background");
if (message.type === "storeinit") {
console.log("got storeinit message. Message key: ", message.key);
store.dispatch("loadChromePagesState");
console.log("current state in store:", JSON.stringify(store.state));
console.log(
"store.state[message.key]:",
JSON.stringify(store.state[message.key])
);
return Promise.resolve(store.state[message.key]);
}
});
now the store's state should be initialized and the mutation callable from the content script (vue-shared-mutations guarantees it)
Does export default new Vuex.Store mean that every script that imports the store gets a new instance with a default state that is not in sync with other imports?
As the error message suggests itemSectionCategory can not be found as it is expected to be an element of allSections[0]. However you never define index 0 of allSections before calling it.
So in short you need to either define allSections index 0 before using it, or make the index part optional and create it if it's not found.
Otherwise you could try one of the following solutions:
if you need to rely on index 0 being available, check if it is set before calling your function
!state.chromePagesState.allSections[0] ? [... insert initialize function call ...]
Maybe optional chaining could be another solution depending on what you use it for afterwards, for an example How to use optional chaining with array or functions?
I would like to use this text-highlighting library in my Vue project. Here's an example from their website of how it can be used:
import TextHighlighter from '#perlego/text-highlighter';
import { isDuplicate } from './utils';
import highlightsApi from './services/highlights-api';
class ArticleView {
constructor(data) {
this.data = data;
const pageElement = document.getElementById("article");
this.highlighter = new TextHighlighter(
pageElement,
{
version: "independencia",
onBeforeHighlight: this.onBeforeHighlight,
onAfterHighlight: this.onAfterHighlight,
preprocessDescriptors: this.preprocessDescriptors,
onRemoveHighlight: this.onRemoveHighlight
});
}
onBeforeHighlight = (range) => {
return !isDuplicate(range)
}
onRemoveHighlight = (highlightElement) => {
const proceed = window.confirm("Are you sure you want to remove this highlight?");
return proceed;
}
preprocessDescriptors = (range, descriptors, timestamp) => {
// Add an ID to the class list to identify each highlight
// (A highlight can be represented by a group of elements in the DOM).
const uniqueId = `hlt-${Math.random()
.toString(36)
.substring(2, 15) +
Math.random()
.toString(36)
.substring(2, 15)}`;
const descriptorsWithIds = descriptors.map(descriptor => {
const [wrapper, ...rest] = descriptor;
return [
wrapper.replace(
'class="highlighted"',
`class="highlighted ${uniqueId}"`
),
...rest
];
});
return { descriptors: descriptorsWithIds, meta: { id: uniqueId } };
}
onAfterHighlight = (range, descriptors, timestamp, meta) => {
highlightsApi.saveBatch(meta.id, descriptorsWithIds)
.then((result) => {
// Do something with the highlights that have been saved.
})
.catch((err) => console.error(err));
}
render = () => {
// Code that takes the data for the article and adds it to the DOM
// based on a html template here.
}
}
Using the above example, I would like to setup the highlighter (similar to the above code, but in a different file, for example ./utils/highlighter.js) with all the default options I want (onBeforeHighlight, onRemoveHighlight, etc.), and then be able to import it from there and override the options for which I don't want to use the defaults, so it looks something like this in the importing file:
import highlighter from "../utils/highlighter.js";
const overridingOptions = {
onAfterHighlight: (range, descriptors, timestamp, meta) => {
console.log(range, descriptors, timestamp, meta);
}
};
const target = document.getElementsByClassName("testme")[0];
highlighter(target, overridingOptions);
For some reason, I am not able to understand how to modify the ArticleView example to fit my needs, so I think I need to see this done once. How should the code in ./utils/highlighter.js look to make this possible?
I'm using Bootstrap vue table with contentful's API and could use some help with my code. I'm attempting to use a for loop to iterate over an array and get the property values. The console.info(episodes); call prints out each iteration for the var episodes, but now how do I bind this to my variable episodes. Using return only returns one result even outside of the for each loop. Any help or suggestions on another implementation is greatly appreciated. Full Template below.
<template>
<div>
<h1>Bootstrap Table</h1>
<b-table striped responsive hover :items="episodes" :fields="fields"></b-table>
</div>
</template>
<style>
</style>
<script>
import axios from "axios";
// Config
import config from "config";
// Vuex
import store from "store";
import { mapGetters, mapActions } from "vuex";
// Services
import { formatEntry } from "services/contentful";
// Models
import { entryTypes } from "models/contentful";
// UI
import UiEntry from "ui/Entry";
import UiLatestEntries from "ui/LatestEntries";
const contentful = require("contentful");
const client = contentful.createClient({
space: "xxxx",
environment: "staging", // defaults to 'master' if not set
accessToken: "xxxx"
});
export default {
name: "contentful-table",
data() {
return {
fields: [
{
key: "category",
sortable: true
},
{
key: "episode_name",
sortable: true
},
{
key: "episode_acronym",
sortable: true
},
{
key: "version",
sortable: true
}
],
episodes: []
};
},
mounted() {
return Promise.all([
// fetch the owner of the blog
client.getEntries({
content_type: "entryWebinar",
select: "fields.title,fields.description,fields.body,fields.splash"
})
])
.then(response => {
// console.info(response[0].items);
return response[0].items;
})
.then(response => {
this.episodes = function() {
var arrayLength = response.length;
var episodes = [];
for (let i = 0; i < arrayLength; i++) {
// console.info(response[i].fields.title + response[i].fields.splash + response[i].fields.description + response[i].fields.body );
var episodes = [
{
category: response[i].fields.title,
episode_name: response[i].fields.splash,
episode_acronym: response[i].fields.description,
version: response[i].fields.body
}
];
// episodes.forEach(category => episodes.push(category));
console.info(episodes);
}
return episodes;
};
})
.catch(console.error);
}
};
</script>
You can use the map method on the response array to return all the elements.
In your current example you keep re-setting the episodes variable, instead of the push() you actually want to do. The map method is still a more elegant way to solve your problem.
this.episodes = response.map((item) => {
return {
category: item.fields.title,
episode_name: items.fields.splash,
episode_acronym: item.fields.description,
version: item.fields.body
}
})
You can update the last then to match the last then below
]).then(response => {
return response[0].items;
})
.then((response) => {
this.episodes = response.map((item) => {
return {
category: item.fields.title,
episode_name: items.fields.splash,
episode_acronym: item.fields.description,
version: item.fields.body
};
});
})
.catch(console.error)
You do have an unnecessary second then, but I left it there so that you could see what I am replacing.
I am trying to get select the list of files(path) from directory and display it on the DOM,but in order to display data i have to first assign in vuejs data object.
mainRenderer
ipcMain.on("channel1", (e, args) => {
const files = getFileFromUserSelection();
e.reply("channel1", files); // sending to channel1 in app.vue
});
const getFileFromUserSelection = () => {
const files = dialog.showOpenDialog({
properties: ["multiSelections"]
});
if (!files) return;
return files;
};
App.Vue
<template>
<div>
<p v-for="file in files">{{file}}</p>
</div>
</template>
<script>
import { ipcRenderer } from "electron";
//this gets files from the main process
ipcRenderer.on("channel1", (e, files) => {
console.log(files); // i want this file into data:function()
});
export default {
data: function() {
return {
files: []
};
},
methods: {
clicked: function() {
ipcRenderer.send("channel1", "open dialog to getFiles from user");
}
}
};
</script>
You can use the beforeCreate hook on the Vuecomponent in order to hydrate the data property:
export default {
data: function() {
return {
files: []
};
},
beforeCreate() {
ipcRenderer.on("channel1", (e, files) => {
this.files = files
});
},
methods: {
clicked: function() {
ipcRenderer.send("channel1", "open dialog to getFiles from user");
}
}
};
Note that, of course, you cannot interact directly with the files array until you know it's been hydrated, so a computed getter here may be of some use to you, or just use files.length.
I have 2 components present in one ts file.
TS:
This is present in one component
addSubCategory(boat:Boat):void {
const reff = this.dialog.open(AddSubCategoryDialog, {
data: { }
});
const sub = reff.componentInstance.onBoatSubAdd.subscribe((data) => {
this.boatSubTypes.boat_type_id = boat.id;
this.service
.addSubBoat(data)
.subscribe(
response => {
console.log(response);
this.dialog.closeAll();
this.dialog.open(AddSubCategoryDialog);
this.loadAllBoats();
}, error => {
this.snackBar.open('Failed to add company', 'X' , {
duration: 2000,
});
});
});
}
}
I need to fetch the boat.id present in this file to the below given function
This is present in one more component but within same TS file:
add(boatSubTypes:BoatSubTypes): void {
this.addClicked = true;
this.boatSubTypes.boat_type_id = this.boatSubTypes.boat_type_id;
this.onBoatSubAdd.emit(boatSubTypes);
}
Can anyone help me to solve this.