How to make multiple image source assignments not make the page stuck? - javascript

I need to dynamically create a list of a couple thousand elements. Each list item contains an image of about half a Kb and some text.
The problem is that the assignment of all image sources to my list items, using the .src of my <img> elements, is taking a long time and making the page completely stuck for some seconds.
Is there a way that I could do this maybe asynchronously or more efficiently so that the page doesn't get stuck during the list generation?
Here's a simplified version of my code. If I remove the line with .src assignment, the code runs instantaneously.
// About a couple thousand elements
const item_data = [
{text: "text1", image: "img/image1.png"},
{text: "text2", image: "img/image2.png"},
... ];
const my_list = document.createElement("ul");
for (const item of item_data) {
const list_item = document.createElement("li");
const list_item_text = document.createElement("p");
list_item_text.innerText = item.text;
const list_item_image = document.createElement("img");
list_item_image.src = item.image;
list_item.appendChild(list_item_text);
list_item.appendChild(list_item_image);
my_list.appendChild(list_item);
}
document.body.appendChild(my_list);

Related

How to fix Javascript associative array error

I am trying to display a list of images with their titles stored in an associative array. But the code only displays the last image and title in the array, in the browser not all of them. However when I console log the values: value.title and value .image the full list is displayed in the console. I must be missing something simple any pointers would be welcome.
<center id="pics"></center>
<script>
const photos = [
{
title: "Landscape River",
image: "landscape1.png",
},
{
title: "Landscape Mountains",
image: "landscape2.png",
},
{
title: "Landscape Mountain Road",
image: "landscape3.png",
},
{
title: "Landscape Hills and Lake",
image: "landscape4.png",
},
];
photos.forEach((value) => {
console.log(value.title, value.image);
document.getElementById("pics").innerHTML =
`<img src="images/${value.image}" >` + "<br>" + value.title;
});
</script>
</body>
While the accepted answer kind of works, it is a not a good solution, because it leads to totally unnecessary re-rendering of the DOM with every loop iteration. And that is just the performance side of the story; using innerHTML also is bad security-wise.
Instead, create those elements dynamically using document.createElement, append it to a documentFragment, and when the loop is done, append that fragment to the actual DOM.
That being said, this is what a proper solution could look like:
const picsContainer = document.getElementById("pics");
const fragment = new DocumentFragment();
photos.forEach(({image, title}) => {
const img = document.createElement('img');
img.src = `images/${image}`;
const br = document.createElement('br');
const text = document.createTextNode(title);
fragment.append(img, br, text);
});
picsContainer.append(fragment);
You will want to do document.getElementById("pics").innerHTML += instead of just =. You want to append each title and image, and not reset the innerHTML every time with a new title/image if that makes sense. Happy coding!

Google Script: How to display an image instead of hyperlink using a Document template

I have a G-Sheet with 3000 rows of data with links to images in one column. I am able to use body.replaceText to fill out a Google Doc template I have created. The issue is that it simply pulls the hyperlink.
Instead of displaying hyperlinks, how would you show the actual image?
Thank you for your time!
function newGoogleDoc() {
const googleDocTemplate = DriveApp.getFileById('XXXXX');
const destinationFolder = DriveApp.getFolderById('XXXXX')
const sheet = SpreadsheetApp
.getActiveSpreadsheet()
const rows = sheet.getDataRange().getValues();
rows.forEach(function(row, index){
if (index === 0) return;
if (row[15]) return;
const copy = googleDocTemplate.makeCopy(`${row[1]}, ${row[0]} Employee Details` , destinationFolder)
const doc = DocumentApp.openById(copy.getId())
const body = doc.getBody();
const friendlyDate = new Date(row[12]).toLocaleDateString();
body.replaceText('{{Scope}}', row[0]);
body.replaceText('{{Block}}', row[1]);
body.replaceText('{{Line Item}}', row[2]);
body.replaceText('{{Date}}', friendlyDate);
body.replaceText('{{User}}', row[13]);
doc.saveAndClose();
const url = doc.getUrl();
sheet.getRange(index + 1, 15).setValue(url)
})
}
If the placeholder for the image is a paragraph of its own, and not part of a container element that includes other content, you could to the following:
Find the placeholder using findText(searchPattern).
Get the Blob from the image URL via UrlFetchApp.fetch(url) and getBlob().
Get the container element that included the placeholder, and use insertInlineImage to insert the retrieved image to the beginning of this element.
For example, if the URL to the images were in column A (so the images should replace {{Scope}}), you could replace this line:
body.replaceText('{{Scope}}', row[0]);
With this:
const rangeElement = body.findText('{{Scope}}');
const imageBlob = UrlFetchApp.fetch(row[0]).getBlob();
if (rangeElement) {
const element = rangeElement.getElement();
element.asText().setText("");
const image = element.getParent().asParagraph().insertInlineImage(0, imageBlob);
}
Important note:
This will remove all the other content in the paragraph the placeholder was on. You could also append the image just before or after the paragraph, but not at the exact same location the placeholder was on. See this related question.
Other issues:
Instead of checking for each iteration whether index === 0, I'd suggest either getting the range without headers (.getRange(2,1, sheet.getLastRow() - 1, sheet.getLastColumn())), or removing the header array from the outer array (rows.shift()). In this case, though, you should be careful to modify all the other mentions of index inside the loop, since the row this will reference will be a different one.
It's not a best practice to call setValue inside a loop. I'd suggest to push the data into an array instead (e.g. urls.push([url])) and write those values at once (using setValues) after the loop is finished (e.g. sheet.getRange(1,15,urls.length).setValues(urls)).
You will need to convert the image url into a blob and then define the position where to enter it in the doc. Replace text only replaces text with text.
Adapted solution from https://www.labnol.org/code/20078-insert-image-in-google-document
function webImage() {
//continuing from your code
const doc = DocumentApp.openById(copy.getId())
const body = doc.getBody();
// using example image url, not sure which row has your urls
var image = 'www.imageurl.com/image.jpg';
var blob = UrlFetchApp.fetch(image).getBlob();
// find position, not sure what you are using in the doc, used text PLACEHOLDER as an example
var position = body.findText('PLACEHOLDER').getElement()
var offset = body.getChildIndex(position.getParent())
// insert image
body.insertImage(offset +1,blob)
// remove placeholder text
position.removeFromParent()
}
Another solution: Add images to Google Document via Google Apps Script

Using for loop to write a paragraph inside .docx file using js with npm docx

the main issue: i want to insert more than 40 text to the Microsoft word document.
through java script code :
// requiring modules
const fs = require('fs'),
docx = require('docx');
// create a new doc
const doc = new docx.Document();
// get paragraph text
const getData = new docx.Paragraph({
children:[
new docx.TextRun({
text: 'Welcome from getData',
size: 72
}),
],
})
//add a section to document to render the paragraph
doc.addSection({
children:[
getData
]
});
//write the buffer to the file.docx
docx.Packer.toBuffer(doc).then((Buffer)=>{
fs.writeFileSync("text.docx" , Buffer);
})
i tried every thing but nothing work with me.
I'd the same issue... But here is a work around. The Docx.J's library doesn't allow loop in either TableCell or TableRow, so it's best you loop outside Table(). Assuming you have a list of string...
let myStrings = ['firstString', 'secondString', 'thirdString'];
const buildParagraph = async(arr)=>{
let paragraphArray = []
arr.map((cur , index)=> {
paragraphArray.push(new Paragraph(cur))
});
return paragraphArray;
};
So buildParagrahp() will give you an array of paragraphs like you intended and you can just place it as the value of the children in your Table(), this should also apply to other things you may try looping...

Create an array of specific elements from another array?

I have an array that can't be changed in terms of the element positions:
var array = ['item1', 'section1', 'section2', 'section3', 'section4', 'section5', 'prod1', 'prod2']
I want to make a new array from 'array' that takes the elements from position 1 - 5 (so all the section elements). It needs to be by position as the section elements make change by name.
var array2=array.slice(1,6)
See here for more information.
let's say we have an array like this
const FILES_WITH_10_ASSETS = [
'../assets/uploads/Desktop-300x600.jpeg',
'../assets/uploads/Desktop-728x90.png',
'../assets/uploads/Mobile-160x600.jpeg',
'../assets/uploads/Mobile-300X50.jpeg',
'../assets/uploads/Mobile-320x50.jpeg',
'../assets/uploads/Mobile-320x480.jpeg',
'../assets/uploads/Mobile-1024x768.jpeg',
'../assets/uploads/Tablet-300x250.jpeg',
'../assets/uploads/Tablet-Interstitial-320x480.gif',
'../assets/uploads/Tablet-Interstitial-320x480.jpeg'
];
now we want some specific items from an array, we will create a function for this, here I have stored the function in an constant for further use
const SELECT_ASSETS = function(assetName: string) {
let filtered_assets: string[] = [];
for (let i in FILES_WITH_10_ASSETS) {
if (FILES_WITH_10_ASSETS[i].includes(assetName) === true) {
filtered_assets.push(FILES_WITH_10_ASSETS[i]);
}
}
return filtered_assets;
};
and we call the method like this in our file
const ASSETS_NAME = SELECT_ASSETS(ASSET_NAME);
now we can pass the assets name to any function or method, if we pass the ASSET_NAME as Tablet we will get all three tablet paths, or if we change it to Mobile, we will get all five Mobile paths

Randomize text, embed codes and URL's from list

I have a list with movie titles, youtube URL's (with which I can construct an embed code) and URL's to IMDB. On every page refresh, another movie title, Youtube video, and text with a URL to IMDB has to appear.
["Movie A", "URL_to_youtube_movie_A", "URL_to_IMDB_movie_A"],
["Movie B", "URL_to_youtube_movie_B", "URL_to_IMDB_movie_B"],
etc.
My website already has a refresh button: the page reloads with . How can I make the youtube video displayed randomized? I want the output to be HTML, so I can add elements afterwards, if necessary.
Shuffle the array, you can use underscore, or write custom function:
list = _.shuffle(list)
then output first n movies. This is the simplest method, that I think will be good enough for your case.
I would make an array of objects. Movie is an object, like this:
var list = [{ title: "Movie A", urlYouTube: "URL_to_youtube_movie", urlImdb: "URL_to_IMDB_movie_A"}, ...]
Also if you plan to do more operations than just show the random movies look at some javascript frameworks(backbone.js, angular, ...).
I would recommend you to use templates for the HTML output. Underscore also has simple template implementation. _.template(templateString, [data], [settings])
Something like this: DEMO && CODE
With simple javascript without any Library
You will need suffle function like below
DEMO
function arrayShuffle(oldArray) {
var newArray = oldArray.slice();
var len = newArray.length;
var i = len;
while (i--) {
var p = parseInt(Math.random()*len);
var t = newArray[i];
newArray[i] = newArray[p];
newArray[p] = t;
}
return newArray;
};
Call it like
var list = [{ title: "Movie A", urlYouTube: "URL_to_youtube_movie_A", urlImdb: "URL_to_IMDB_movie_A"},
{ title: "Movie B", urlYouTube: "URL_to_youtube_movie_B", urlImdb: "URL_to_IMDB_movie_B"},
{ title: "Movie C", urlYouTube: "URL_to_youtube_movie_C", urlImdb: "URL_to_IMDB_movie_C"}];
var Suffledlist = arrayShuffle(list);
And then show top 2 or 5 elements

Categories

Resources