I have this problem is that when the second suggestion is selected the dropdown does not disappear, so i want it to disappear as soon as it clicked
I have a script for uk address which is working fine, so here is how it works so if someone enter a postal code after that the first suggestion shows up and then when selected the second suggestion shows up to complete the address. Am using woosmap for the api.
Please note for this particular project i cant use google api, its specifically for the uk addresses
here is the code.
<div id="app">
<div hidden id="map"></div>
<div class="autocomplete-input-container">
<div class="autocomplete-input">
<input id="input-value" type="text" autocomplete="off" placeholder="Type in a UK postal code..">
</div>
<div class="autocomplete-results-container">
<div class="autocomplete-results">
</div>
</div>
</div>
</div>
<script
src="https://sdk.woosmap.com/map/map.js?key=API-KEY&language=en"></script>
<script>
javascript am using
let myMap;
let markerAddress;
const woosmap_key = "API-KEY";
function buildQueryString(params) {
const queryStringParts = [];
for (let key in params) {
if (params.hasOwnProperty(key)) {
let value = params[key];
queryStringParts.push(
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
);
}
}
return queryStringParts.join("&");
}
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = arguments;
const later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
function autocompleteLocalities(input) {
const args = {
key: woosmap_key,
input,
types: "postal_code",
components: "country:gb|country:je|country:im|country:gg"
};
return fetch(
`https://api.woosmap.com/localities/autocomplete/?${buildQueryString(args)}`
).then((response) => response.json());
}
function getDetails(public_id) {
let args = {
key: woosmap_key,
public_id,
addresses_per_page: 1000,
language: "en"
};
return fetch(
`https://api.woosmap.com/localities/details/?${buildQueryString(args)}`
).then((response) => response.json());
}
function displayDetails(res) {
markerAddress.setMap(null);
const result = res.result;
document.getElementById("input-value").value = result.formatted_address;
const results = document.querySelector(".autocomplete-results");
const position = {
lat: result.geometry.location.lat,
lng: result.geometry.location.lng
};
if (myMap.getZoom() < 16) {
myMap.setZoom(19);
myMap.panTo(position);
}
myMap.panTo(position);
markerAddress.setMap(myMap);
if (result.addresses) {
results.innerHTML = "";
let html = "";
for (let addressIndex in result.addresses.list) {
const address = result.addresses.list[addressIndex];
let predictionClass = "no-viewpoint";
let formatted_name = address["description"];
const publidId = address["public_id"];
html += `<div class="prediction ${predictionClass}" detail="true" public-id="${publidId}">
${formatted_name}
</div>`;
}
results.innerHTML = html;
results.style.display = "block";
const data = results.querySelectorAll(".prediction");
for (let result of data) {
result.addEventListener("click", () => {
const detail = result.getAttribute("detail");
const publicId = result.getAttribute("public-id");
if (detail === "true") {
getDetails(publicId).then((res) => displayDetails(res));
}
});
}
}
}
function displayResults(
value,
environment = "develop-api.woosmap.com",
postal_code
) {
let results = document.querySelector(".autocomplete-results");
// Get Woosmap results
autocompleteLocalities(value, myMap.getCenter()).then(
({ localities, addresses }) => {
results.innerHTML = "";
results.parentElement.style.display = "none";
markerAddress.setMap(null);
let html = "";
const responses = localities || addresses || [];
for (let prediction_id in responses || []) {
const prediction = responses[prediction_id];
const predictionClass = prediction["has_addresses"]
? `prediction-expandable`
: "";
const formatted_name = prediction["description"];
const addresses = prediction["has_addresses"]
? `<span class='light'>- see addresses</span>`
: "";
const publidId = prediction["public_id"];
html += `<div class="prediction ${predictionClass}" prediction-id=${prediction_id} detail="${prediction["has_addresses"]}" public-id="${publidId}">
${formatted_name} ${addresses}
</div>`;
}
results.innerHTML = html;
results.style.display = "block";
results.parentElement.style.display = "flex";
const data = results.querySelectorAll(".prediction");
for (let result of data) {
result.addEventListener("click", () => {
results.style.display = "none";
const detail = result.getAttribute("detail");
const predictionId = parseInt(
result.getAttribute("prediction-id"),
10
);
const publicId = result.getAttribute("public-id");
const prediction = localities[predictionId];
const position = {
lat: prediction.location.lat,
lng: prediction.location.lng
};
if (prediction.viewpoint) {
myMap.fitBounds(prediction.viewpoint.bounds);
myMap.panTo(position);
} else {
myMap.panTo(position);
myMap.setZoom(4);
}
if (detail === "true") {
getDetails(publicId).then((res) => displayDetails(res));
result.setAttribute("detail", "false");
}
markerAddress.setPosition(position);
markerAddress.setMap(myMap);
if (detail === "true") {
getDetails(publicId).then((res) => displayDetails(res));
}
});
}
}
);
}
function init() {
const mapElement = document.getElementById("map");
myMap = new woosmap.map.Map(mapElement, {
center: {
lat: 52.2928,
lng: -1.717
},
disableDefaultUI: true,
gestureHandling: "greedy",
zoom: 6,
styles: {
featureType: "poi",
stylers: [{ visibility: "off" }]
}
});
markerAddress = new woosmap.map.Marker({
icon: {
url: "https://images.woosmap.com/icons/pin-red.png",
scaledSize: {
height: 64,
width: 46
}
}
});
registerEvents();
}
function registerEvents() {
const results = document.querySelector(".autocomplete-results");
const input = document.querySelector(".autocomplete-input > input");
input.addEventListener(
"input",
debounce(function () {
let value = this.value;
value.replace('"', '\\"').replace(/^\s+|\s+$/g, "");
if (value !== "") {
displayResults(value);
} else {
results.innerHTML = "";
}
}, 150)
);
}
init();
here the example for the above and also with the problem https://codesandbox.io/s/localities-autocomplete-uk-postalcode-woosmap-map-v0y6so?from-embed
thank u
Related
Good evening! I have a problem.
I am creating a collection of NFTs using this js file.
What I am trying to modify is that once it reads the images in the layers folder, creates the final image and writes the metadata, these read and used images are deleted in the source folder (${basePath}/layers).
const basePath = process.cwd();
const { NETWORK } = require(`${basePath}/constants/network.js`);
const fs = require("fs");
const sha1 = require(`${basePath}/node_modules/sha1`);
const { createCanvas, loadImage } = require(`${basePath}/node_modules/canvas`);
const buildDir = `${basePath}/build`;
const layersDir = `${basePath}/layers`;
const {
format,
baseUri,
description,
background,
uniqueDnaTorrance,
layerConfigurations,
rarityDelimiter,
shuffleLayerConfigurations,
debugLogs,
extraMetadata,
text,
namePrefix,
network,
solanaMetadata,
gif,
} = require(`${basePath}/src/config.js`);
const canvas = createCanvas(format.width, format.height);
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = format.smoothing;
var metadataList = [];
var attributesList = [];
var dnaList = new Set();
const DNA_DELIMITER = "-";
const HashlipsGiffer = require(`${basePath}/modules/HashlipsGiffer.js`);
let hashlipsGiffer = null;
const buildSetup = () => {
if (fs.existsSync(buildDir)) {
fs.rmdirSync(buildDir, { recursive: true });
}
fs.mkdirSync(buildDir);
fs.mkdirSync(`${buildDir}/json`);
fs.mkdirSync(`${buildDir}/images`);
if (gif.export) {
fs.mkdirSync(`${buildDir}/gifs`);
}
};
const getRarityWeight = (_str) => {
let nameWithoutExtension = _str.slice(0, -4);
var nameWithoutWeight = Number(
nameWithoutExtension.split(rarityDelimiter).pop()
);
if (isNaN(nameWithoutWeight)) {
nameWithoutWeight = 1;
}
return nameWithoutWeight;
};
const cleanDna = (_str) => {
const withoutOptions = removeQueryStrings(_str);
var dna = Number(withoutOptions.split(":").shift());
return dna;
};
const cleanName = (_str) => {
let nameWithoutExtension = _str.slice(0, -4);
var nameWithoutWeight = nameWithoutExtension.split(rarityDelimiter).shift();
return nameWithoutWeight;
};
const getElements = (path) => {
return fs
.readdirSync(path)
.filter((item) => !/(^|\/)\.[^\/\.]/g.test(item))
.map((i, index) => {
return {
id: index,
name: cleanName(i),
filename: i,
path: `${path}${i}`,
weight: getRarityWeight(i),
};
});
};
const layersSetup = (layersOrder) => {
const layers = layersOrder.map((layerObj, index) => ({
id: index,
elements: getElements(`${layersDir}/${layerObj.name}/`),
name:
layerObj.options?.["displayName"] != undefined
? layerObj.options?.["displayName"]
: layerObj.name,
blend:
layerObj.options?.["blend"] != undefined
? layerObj.options?.["blend"]
: "source-over",
opacity:
layerObj.options?.["opacity"] != undefined
? layerObj.options?.["opacity"]
: 1,
bypassDNA:
layerObj.options?.["bypassDNA"] !== undefined
? layerObj.options?.["bypassDNA"]
: false,
}));
return layers;
};
const saveImage = (_editionCount) => {
fs.writeFileSync(
`${buildDir}/images/${_editionCount}.png`,
canvas.toBuffer("image/png")
);
};
const genColor = () => {
let hue = Math.floor(Math.random() * 360);
let pastel = `hsl(${hue}, 100%, ${background.brightness})`;
return pastel;
};
const drawBackground = () => {
ctx.fillStyle = background.static ? background.default : genColor();
ctx.fillRect(0, 0, format.width, format.height);
};
const addMetadata = (_dna, _edition) => {
let dateTime = Date.now();
let tempMetadata = {
name: `${namePrefix} #${_edition}`,
description: description,
image: `${baseUri}/${_edition}.png`,
dna: sha1(_dna),
edition: _edition,
date: dateTime,
...extraMetadata,
attributes: attributesList,
compiler: "CryptoGioconda Engine",
};
if (network == NETWORK.sol) {
tempMetadata = {
//Added metadata for solana
name: tempMetadata.name,
symbol: solanaMetadata.symbol,
description: tempMetadata.description,
//Added metadata for solana
seller_fee_basis_points: solanaMetadata.seller_fee_basis_points,
image: `image.png`,
//Added metadata for solana
external_url: solanaMetadata.external_url,
edition: _edition,
...extraMetadata,
attributes: tempMetadata.attributes,
properties: {
files: [
{
uri: "image.png",
type: "image/png",
},
],
category: "image",
creators: solanaMetadata.creators,
},
};
}
metadataList.push(tempMetadata);
attributesList = [];
};
const addAttributes = (_element) => {
let selectedElement = _element.layer.selectedElement;
attributesList.push({
trait_type: _element.layer.name,
value: selectedElement.name,
});
};
const loadLayerImg = async (_layer) => {
return new Promise(async (resolve) => {
const image = await loadImage(`${_layer.selectedElement.path}`);
resolve({ layer: _layer, loadedImage: image });
});
};
const addText = (_sig, x, y, size) => {
ctx.fillStyle = text.color;
ctx.font = `${text.weight} ${size}pt ${text.family}`;
ctx.textBaseline = text.baseline;
ctx.textAlign = text.align;
ctx.fillText(_sig, x, y);
};
const drawElement = (_renderObject, _index, _layersLen) => {
ctx.globalAlpha = _renderObject.layer.opacity;
ctx.globalCompositeOperation = _renderObject.layer.blend;
text.only
? addText(
`${_renderObject.layer.name}${text.spacer}${_renderObject.layer.selectedElement.name}`,
text.xGap,
text.yGap * (_index + 1),
text.size
)
: ctx.drawImage(
_renderObject.loadedImage,
0,
0,
format.width,
format.height
);
addAttributes(_renderObject);
};
const constructLayerToDna = (_dna = "", _layers = []) => {
let mappedDnaToLayers = _layers.map((layer, index) => {
let selectedElement = layer.elements.find(
(e) => e.id == cleanDna(_dna.split(DNA_DELIMITER)[index])
);
return {
name: layer.name,
blend: layer.blend,
opacity: layer.opacity,
selectedElement: selectedElement,
};
});
return mappedDnaToLayers;
};
/**
* In some cases a DNA string may contain optional query parameters for options
* such as bypassing the DNA isUnique check, this function filters out those
* items without modifying the stored DNA.
*
* #param {String} _dna New DNA string
* #returns new DNA string with any items that should be filtered, removed.
*/
const filterDNAOptions = (_dna) => {
const dnaItems = _dna.split(DNA_DELIMITER);
const filteredDNA = dnaItems.filter((element) => {
const query = /(\?.*$)/;
const querystring = query.exec(element);
if (!querystring) {
return true;
}
const options = querystring[1].split("&").reduce((r, setting) => {
const keyPairs = setting.split("=");
return { ...r, [keyPairs[0]]: keyPairs[1] };
}, []);
return options.bypassDNA;
});
return filteredDNA.join(DNA_DELIMITER);
};
/**
* Cleaning function for DNA strings. When DNA strings include an option, it
* is added to the filename with a ?setting=value query string. It needs to be
* removed to properly access the file name before Drawing.
*
* #param {String} _dna The entire newDNA string
* #returns Cleaned DNA string without querystring parameters.
*/
const removeQueryStrings = (_dna) => {
const query = /(\?.*$)/;
return _dna.replace(query, "");
};
const isDnaUnique = (_DnaList = new Set(), _dna = "") => {
const _filteredDNA = filterDNAOptions(_dna);
return !_DnaList.has(_filteredDNA);
};
const createDna = (_layers) => {
let randNum = [];
_layers.forEach((layer) => {
var totalWeight = 0;
layer.elements.forEach((element) => {
totalWeight += element.weight;
});
// number between 0 - totalWeight
let random = Math.floor(Math.random() * totalWeight);
for (var i = 0; i < layer.elements.length; i++) {
// subtract the current weight from the random weight until we reach a sub zero value.
random -= layer.elements[i].weight;
if (random < 0) {
return randNum.push(
`${layer.elements[i].id}:${layer.elements[i].filename}${
layer.bypassDNA ? "?bypassDNA=true" : ""
}`
);
}
}
});
return randNum.join(DNA_DELIMITER);
};
const writeMetaData = (_data) => {
fs.writeFileSync(`${buildDir}/json/_metadata.json`, _data);
};
const saveMetaDataSingleFile = (_editionCount) => {
let metadata = metadataList.find((meta) => meta.edition == _editionCount);
debugLogs
? console.log(
`Writing metadata for ${_editionCount}: ${JSON.stringify(metadata)}`
)
: null;
fs.writeFileSync(
`${buildDir}/json/${_editionCount}.json`,
JSON.stringify(metadata, null, 2)
);
};
function shuffle(array) {
let currentIndex = array.length,
randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
const startCreating = async () => {
let layerConfigIndex = 0;
let editionCount = 1;
let failedCount = 0;
let abstractedIndexes = [];
for (
let i = network == NETWORK.sol ? 0 : 1;
i <= layerConfigurations[layerConfigurations.length - 1].growEditionSizeTo;
i++
) {
abstractedIndexes.push(i);
}
if (shuffleLayerConfigurations) {
abstractedIndexes = shuffle(abstractedIndexes);
}
debugLogs
? console.log("Editions left to create: ", abstractedIndexes)
: null;
while (layerConfigIndex < layerConfigurations.length) {
const layers = layersSetup(
layerConfigurations[layerConfigIndex].layersOrder
);
while (
editionCount <= layerConfigurations[layerConfigIndex].growEditionSizeTo
) {
let newDna = createDna(layers);
if (isDnaUnique(dnaList, newDna)) {
let results = constructLayerToDna(newDna, layers);
let loadedElements = [];
results.forEach((layer) => {
loadedElements.push(loadLayerImg(layer));
});
await Promise.all(loadedElements).then((renderObjectArray) => {
debugLogs ? console.log("Clearing canvas") : null;
ctx.clearRect(0, 0, format.width, format.height);
if (gif.export) {
hashlipsGiffer = new HashlipsGiffer(
canvas,
ctx,
`${buildDir}/gifs/${abstractedIndexes[0]}.gif`,
gif.repeat,
gif.quality,
gif.delay
);
hashlipsGiffer.start();
}
if (background.generate) {
drawBackground();
}
renderObjectArray.forEach((renderObject, index) => {
drawElement(
renderObject,
index,
layerConfigurations[layerConfigIndex].layersOrder.length
);
if (gif.export) {
hashlipsGiffer.add();
}
});
if (gif.export) {
hashlipsGiffer.stop();
}
debugLogs
? console.log("Editions left to create: ", abstractedIndexes)
: null;
saveImage(abstractedIndexes[0]);
addMetadata(newDna, abstractedIndexes[0]);
saveMetaDataSingleFile(abstractedIndexes[0]);
console.log(
`Created edition: ${abstractedIndexes[0]}, with DNA: ${sha1(
newDna
)}`
);
});
dnaList.add(filterDNAOptions(newDna));
editionCount++;
abstractedIndexes.shift();
} else {
console.log("DNA exists!");
failedCount++;
if (failedCount >= uniqueDnaTorrance) {
console.log(
`You need more layers or elements to grow your edition to ${layerConfigurations[layerConfigIndex].growEditionSizeTo} artworks!`
);
process.exit();
}
}
}
layerConfigIndex++;
}
writeMetaData(JSON.stringify(metadataList, null, 2));
};
module.exports = { startCreating, buildSetup, getElements };
I tried to use this code by inserting it inside the main document, but it doesn't work.
try {
fs.unlinkSync(path)
//file removed
} catch(err) {
console.error(err)
}
Can anyone tell me how can I solve?
I use i18next to localize HTML and I am trying to cache the selected language so it will not fallback on page refresh but can't get it to work.
here is my code.
<script src="https://unpkg.com/i18next/dist/umd/i18next.min.js"></script>
<script>
function updateContent() {
const elements = document.getElementsByClassName("i18nelement");
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const k = element.getAttribute("data-i18n");
element.innerHTML = i18next.t(k);
}
}
async function i18Loader() {
const langs = ["en", "ru"];
const jsons = await Promise.all(
langs.map((l) => fetch("src/i18/" + l + ".json").then((r) => r.json()))
);
const res = langs.reduce((acc, l, idx) => {
acc[l] = { translation: jsons[idx] };
return acc;
}, {});
await i18next.init({
lng: 'en',
debug: true,
resources: res,
fallbackLng: "en-US"
});
updateContent();
i18next.on("languageChanged", () => {
updateContent();
});
const langSelector = document.getElementById("langSelector");
langSelector.removeAttribute("disabled");
langSelector.addEventListener("change", (e) => {
i18next.changeLanguage(e.target.value);
});
}
i18Loader();
</script>
How can I store selected language in localstorage?
I found the solution. the correct code here
async function i18Loader() {
const langs = ["en", "ru"];
const jsons = await Promise.all(
langs.map((l) => fetch("src/i18/" + l + ".json").then((r) => r.json()))
);
const res = langs.reduce((acc, l, idx) => {
acc[l] = { translation: jsons[idx] };
return acc;
}, {});
await i18next
.init({
lng: localStorage.getItem("lan") || 'en',
debug: true,
resources: res,
fallbackLng: "en-US",
backend: {
backendOptions: [{
// can be either window.localStorage or window.sessionStorage. Default: window.localStorage
store: typeof window !== 'undefined' ? window.localStorage : null
}, {
loadPath: '/scr/i18/{{lng}}.json' // xhr load path for my own fallback
}]
}
});
function updateContent() {
const elements = document.getElementsByClassName("i18nelement");
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const k = element.getAttribute("data-i18n");
element.innerHTML = i18next.t(k);
}
}
updateContent();
i18next.on("languageChanged", () => {
updateContent();
});
const langSelector = document.getElementById("langSelector");
langSelector.removeAttribute("disabled");
langSelector.addEventListener("change", (e) => {
i18next.changeLanguage(e.target.value);
localStorage.setItem("lan", e.target.value);
});
}
i18Loader();
I have an array that has 3 contacts. I want the same person's name to be deleted when I click on the delete button, but unfortunately I do not know where the problem is that it does not work.
I have two functions in this program, one removeContact to perform the delete operation
And I have a function called showrecords to get the content of the array and display the name and number of contacts with a dedicated delete button for each contact
In this program, I used the pattern builder pattern
Please guide me to the conclusion to solve the problem of not being deleted
Please click on the show Person button to test the program. Contacts will be displayed and click on the delete button. You will see that the delete operation is not performed.
function ElementBuilder(name) {
this.element = document.createElement(name);
this.appendSelector = function(selector) {
this.appendElement = document.querySelector(selector).appendChild(this.element);
return this
};
this.setAttribute = function(attribute, valueAttribute) {
this.element.setAttribute(attribute, valueAttribute)
return this;
};
this.addEventListener = function(event, fun) {
this.element.addEventListener(event, fun);
return this;
};
this.text = function(text) {
this.element.textContent = text;
return this;
};
this.build = function() {
return this.element;
};
}
const builder = {
create: function(name) {
return new ElementBuilder(name);
}
};
function PhoneBook() {
this.records = [{ name: "niloufar", phone: 1111 }, { name: "sara", phone: 2222 }, { name: "sara", phone: 3333 }];
// function remove
this.removeContact = function() {
const self = this
function removePerson(item) {
if (item.target.classList.contains('delbutton')) {
const remID = item.target.getAttribute('data-id');
self.records.splice(remID, 1);
}
}
return removePerson;
}
}
function Render(container) {
this.container = container;
const phoneBook = new PhoneBook();
const remove = phoneBook
.removeContact();
this.removeEntry = (item) => {
remove(item); //
this.showrecords();
}
this.init = function() {
const btn = builder
.create("button")
.text("show person")
.addEventListener("click", this.showrecords)
.appendSelector("div")
.build();
};
// Function: Read contacts from the array and display them
this.showrecords = () => {
const addBookId = document.getElementById('phone-book-container');
let index = 0;
addBookId.innerHTML = '';
const arry = phoneBook.records;
arry.forEach(elm => {
const nameContent = builder
.create('p')
.text(`${elm.name}`)
.appendSelector("div")
.build();
const phoneContent = builder
.create('p')
.text(`${elm.phone}`)
.appendSelector("div")
.build();
const anchor = builder
.create('a')
.addEventListener('click', this.removeEntry)
.setAttribute('href', '#')
.setAttribute('class', 'delbutton')
.setAttribute("id", "deleteButton")
.text("delete")
.setAttribute('date-id', `${index}`)
.appendSelector("div")
.build();
});
}
}
const phoneBookContainer = document.getElementById("phone-book-container");
const app = new Render(phoneBookContainer);
app.init();
<div id="phone-book-container"></div>
You have to pass the item (which is actually the event object) to your function:
function ElementBuilder(name) {
this.element = document.createElement(name);
this.appendSelector = function(selector) {
this.appendElement = document.querySelector(selector).appendChild(this.element);
return this
};
this.setAttribute = function(attribute, valueAttribute) {
this.element.setAttribute(attribute, valueAttribute)
return this;
};
this.addEventListener = function(event, fun) {
this.element.addEventListener(event, fun);
return this;
};
this.text = function(text) {
this.element.textContent = text;
return this;
};
this.build = function() {
return this.element;
};
}
const builder = {
create: function(name) {
return new ElementBuilder(name);
}
};
function PhoneBook() {
this.records = [{ name: "niloufar", phone: 1111 }, { name: "sara", phone: 2222 }, { name: "sara", phone: 3333 }];
// function remove
this.removeContact = function() {
const self = this
function removePerson(item) {
if (item.target.classList.contains('delbutton')) {
const remID = item.target.getAttribute('date-id');
self.records.splice(remID, 1);
}
}
return removePerson;
}
}
function Render(container) {
this.container = container;
const phoneBook = new PhoneBook();
const remove = phoneBook
.removeContact();
this.removeEntry = (item) => {
remove(item);
this.showrecords();
}
this.init = function() {
const btn = builder
.create("button")
.text("show person")
.addEventListener("click", this.showrecords)
.appendSelector("div")
.build();
};
// Function: Read contacts from the array and display them
this.showrecords = () => {
const addBookId = document.getElementById('phone-book-container');
addBookId.innerHTML = '';
const arry = phoneBook.records;
arry.forEach((elm, index) => {
const nameContent = builder
.create('p')
.text(`${elm.name}`)
.appendSelector("div")
.build();
const phoneContent = builder
.create('p')
.text(`${elm.phone}`)
.appendSelector("div")
.build();
const anchor = builder
.create('a')
.addEventListener('click', this.removeEntry)
.setAttribute('href', '#')
.setAttribute('class', 'delbutton')
.setAttribute("id", "deleteButton")
.text("delete")
.setAttribute('date-id', `${index}`)
.appendSelector("div")
.build();
});
}
}
const phoneBookContainer = document.getElementById("phone-book-container");
const app = new Render(phoneBookContainer);
app.init();
<div id="phone-book-container"></div>
I am creating a copyCode plugin for QuillJs. Everything seems to be working for the plugin, however, when a space is created between the text and the code-block, you get this error:
Failed to execute 'insertBefore' on 'Node'
Here is the code:
const copyContentIntoClipboard = (rawData: string) => {
const encodedContent = encodeURIComponent(rawData);
const filteredEncodedContent = encodedContent.replace(/%EF%BB%BF/g, "");
const targetContent = decodeURIComponent(filteredEncodedContent);
const tmpHolder = document.createElement("textarea");
tmpHolder.value = targetContent;
document.body.appendChild(tmpHolder);
tmpHolder.select();
document.execCommand("copy");
document.body.removeChild(tmpHolder);
};
const CodeBlock = Quill.import("formats/code-block");
class CopyCode extends CodeBlock {
copyBadge: HTMLDivElement | null;
domNode: HTMLElement;
container: HTMLElement | null;
parent: HTMLElement;
copyHandler: EventListener;
_mountContainer() {
const container = document.createElement("div");
container.classList.add("ql-container");
if (this.domNode.nextSibling) {
this.domNode.parentElement?.insertBefore(
container,
this.domNode
);
container.appendChild(this.domNode); // <-- error starts here
this.container = container;
}
}
_dismountContainer() {
if (this.container) {
this.container.parentElement?.insertBefore(
this.domNode,
this.container.nextSibling
);
this.domNode.parentElement?.removeChild(this.container);
}
this.container = null;
}
_mountBadge() {
const copyBadge = document.createElement("div");
copyBadge.contentEditable = "false";
copyBadge.classList.add("ql-badge", "ql-badge-copy");
copyBadge.textContent = "copy";
this.domNode.parentElement?.insertBefore(
copyBadge,
this.domNode.nextSibling
);
const copyHandler = (e: MouseEvent) => {
e.stopPropagation();
e.preventDefault();
const target = e.target as HTMLElement;
const codeArea = target.previousSibling;
const copyCode = codeArea?.textContent?.trim() || '';
if (!codeArea) {
return;
}
copyBadge.textContent = "copied!";
setTimeout(function() {
copyBadge.textContent = "copy";
}, 2000);
copyContentIntoClipboard(copyCode);
};
copyBadge.addEventListener("click", copyHandler, true);
this.copyHandler = copyHandler;
this.copyBadge = copyBadge;
}
_dismountBadge() {
const badgeIsInDom = this.domNode.parentElement?.contains(this.copyBadge);
if (this.copyBadge && badgeIsInDom) {
this.copyBadge.removeEventListener("click", this.copyHandler, true);
this.copyBadge.parentElement?.removeChild(this.copyBadge);
}
this.copyBadge = null;
this.copyHandler = () => {};
}
_mount() {
this._mountContainer();
this._mountBadge();
}
insertInto(...args: any) {
super.insertInto(...args);
const allowCustomMount = !this.copyBadge && !this.container && this.parent;
if (allowCustomMount) {
this._mount();
}
}
remove() {
this._dismountBadge();
this._dismountContainer();
super.remove();
}
}
Here is the StackBlitz: https://stackblitz.com/edit/typescript-ggvuuy?file=index.html
I believe the error is caused by QuillJS believing the code-block should be a pre block instead of a div block containing a pre block. However, I don't know how to fix it...
Any ideas?
Instead of extending formats/code-block you can use Modules to extend Quill
import hljs from "highlight.js";
import "highlight.js/styles/monokai-sublime.css";
import "./style.css";
import Quill from "quill";
hljs.configure({
languages: ["javascript", "python"]
});
const copyContentIntoClipboard = (rawData: string) => {
const encodedContent = encodeURIComponent(rawData);
const filteredEncodedContent = encodedContent.replace(/%EF%BB%BF/g, "");
const targetContent = decodeURIComponent(filteredEncodedContent);
const tmpHolder = document.createElement("textarea");
tmpHolder.value = targetContent;
document.body.appendChild(tmpHolder);
tmpHolder.select();
document.execCommand("copy");
document.body.removeChild(tmpHolder);
};
class CopyCode {
quill: any;
options: any;
container: HTMLElement;
unusedBadges: HTMLElement[] = [];
reference: { [index: string]: {
parent : HTMLElement | null,
copyBadge : HTMLElement | null
} } = {};
constructor(quill: any, options: any) {
this.quill = quill;
this.options = options;
this.container = this.quill.addContainer('ql-badge-container');
this.quill.root.parentNode.style.position = this.quill.root.parentNode.style.position || 'relative';
this.registerCodeBlock();
this.quill.on('editor-change', () => {
Object.values(this.reference).forEach((item) => {
this.addCopyBadge(item);
this.repositionCopyBadge(item);
})
});
}
registerCodeBlock = () => {
const self = this;
const CodeBlock = Quill.import("formats/code-block");
let counter = 0;
class CopyMode extends CodeBlock {
domNode: HTMLElement;
insertInto(...args: any) {
super.insertInto(...args);
const index = String(counter);
const _node = this.domNode;
_node.setAttribute('data-index', index);
counter++;
self.reference[index] = { parent : _node, copyBadge: null };
}
remove() {
const index = this.domNode.getAttribute("data-index");
if (self.reference[index] && self.reference[index]['copyBadge']) {
const copyBadge = self.reference[index]['copyBadge'];
copyBadge.style.display = 'none';
self.unusedBadges.push(copyBadge);
}
delete self.reference[index];
super.remove();
}
}
Quill.register(CopyMode, true);
}
addCopyBadge = (obj: any) => {
if (obj.copyBadge != null || obj.parent == null) {
return;
}
const index = obj.parent.getAttribute('data-index');
const copyBadge = this.unusedBadges.length ? this.unusedBadges.shift() : document.createElement("span");
copyBadge.style.display = 'block';
copyBadge.contentEditable = "false";
copyBadge.classList.add("ql-badge", "ql-badge-copy");
copyBadge.textContent = "copy";
const copyHandler = (evt: MouseEvent) => {
evt.stopPropagation();
evt.preventDefault();
const codeArea = obj.parent;
const copyText = codeArea?.textContent?.trim() || '';
if (!codeArea) {
return;
}
copyBadge.textContent = "copied!";
setTimeout(function() {
copyBadge.textContent = "copy";
}, 2000);
copyContentIntoClipboard(copyText);
};
copyBadge.addEventListener("click", copyHandler, true);
this.container.appendChild(copyBadge);
this.reference[index]['copyBadge'] = copyBadge;
}
repositionCopyBadge(obj: any) {
const parent: HTMLElement = this.quill.root.parentNode;
const specRect = obj.parent.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
Object.assign(obj.copyBadge.style, {
right: `${specRect.left - parentRect.left - 1 + parent.scrollLeft + 4}px`,
top: `${(specRect.top - parentRect.top + parent.scrollTop) + 3}px`,
});
}
}
Quill.register("modules/copy-code", CopyCode);
const quill = new Quill("#editor", {
modules: {
syntax: true,
'copy-code': true,
toolbar: {
container: ["code-block"]
}
},
theme: "snow"
});
Here working example
I am retrieving firestore data and populate to the interface with Vuejs. Unfortunately, the innerHTML is not updated and I am stuck for 3 hours but still do not know where have I done wrong.
HTML
<section>
<div v-html="boardDetails"></div>
</section>
Vuejs
Vue.component('dashboard', {
template: '#dashboard',
data() {
return {
boardDetails: ''
}
},
mounted(){
var studIdentity, studName, studEmail, photoUrl, uid, emailVerified;
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
studIdentity = user;
if (studIdentity != null) {
studName = studIdentity.displayName;
studEmail = studIdentity.email;
photoUrl = studIdentity.photoURL;
emailVerified = studIdentity.emailVerified;
uid = studIdentity.uid;
}
var boardId = [];
ref_du.get().then(function (snapshot) {
snapshot.forEach(childSnapshot => {
let data = childSnapshot.data();
let st_id = childSnapshot.id;
let userId = data.studId;
if (userId == uid) {
boardId.push({ st_id: st_id, t_id: data.taskboardId });
}
});
});
var boardInfo = []
du.get().then(function (snapshot) {
snapshot.forEach(childSnapshot => {
let data = childSnapshot.data();
let t_id = childSnapshot.id;
for (let i = 0; i < boardId.length; i++) {
if (boardId[i].t_id == t_id) {
boardInfo.push({ t_id: t_id, name: data.taskboardName, date: data.createdDate });
}
}
});
let html = '';
for (let i = 0; i < boardInfo.length; i++) {
html += `
<div style="color:red;">${boardInfo[i].name}</div>
`;
}
this.boardDetails = html;
console.log(this.boardDetails);
});
} else {
window.location.href = 'index.html';
}
});
}
});
The console log shows that the boardDetails is updated but the interface does not show anything.
When you use the function keyword, it changes the this context.
To fix your problem, add var self = this; at the top of mounted and then replace anywhere you have this.boardDetails with self.boardDetails