Is there a way to use the index number of preview element within the template code so I can add sequential id's(id="item-[1,2,3]") to each time the template is been used? Similar to the use of placeholders such data-dz-thumbnail, data-dz-name, data-dz-size, etc. or a way to acheive that dynamicly as elements(files) are been added?
Thanks
I did something similar to what you are asking but I used the UUID value that is assigned to each file object by Dropzone rather than a sequential number. This accomplishes the goal of ensuring each entry has a unique identifier while tying it back to the file object and not being dependent on the order of the files array.
myDropzone = new Dropzone('form#myuploadform')
myDropzone.on(
'addedFile',
function (file) {
// get unique UUID value assigned when `file` was created
let uuid = file.upload.uuid
// add the UUID value as an attribute to the HTML preview element
file.previewElement.setAttribute('data-uuid', uuid)
/* additional uses of the UUID not part of the original question */
// append the UUID value to the `for` attribute of an LABEL elements
let labels = file.previewElement.querySelectorAll('label')
for (let label of labels) {
let value = label.getAttribute('for')
label.setAttribute('for', value + '-' + uuid)
}
// append the UUID value to the `id` attribute of any form elements
let inputs = file.previewElement.querySelectorAll('input, select, textarea')
for (let input of inputs) {
let value = input.getAttribute('id')
input.setAttribute('id', value + '-' + uuid)
}
}
)
This also means you can write a function to get the file object based on the UUID.
myDropzone = new Dropzone('form#myuploadform')
function get_file(uuid) {
for (let file of myDropzone.files) {
if (file.upload.uuid == uuid) return file;
}
return undefined
}
Related
i want to pull data from a .csv file and pass this data to a function to format every row of the .csv file to be a single object. These objects are then stored in an array called "list". So far so good. This is all working.
Next i want a button that calls a function onclick called "roll" that takes a random index from the "list" array and saves an instance of the random oject in a temporarily variable called randomItem.
Then i want to check certain properties of randomItem for specific values and if a certain condition is met it should change a specific property called "randomItem.Name". Finally i want the altered "randomItem" to be pushed into a new array called "results". These results are then being displayed on the website.
If I change the propertys value with "randomItem.Name = randomItem.Name + 'someString'" it also overwrites the original object in the "list" array. I dont want it to do this as i want to repeat the process of rolling random objects from this list several times. Therefor i need the "list" array to keep its original state. I cant get my head around why it overwrites the any list.
html
<button id="btnItem1">Roll</button>
js
$(document).ready(function() {
let list = [];
let results = [];
$('#btnItem1').click({slotName:'item1', listName:list, index:0}, roll);
// i need to pass slotName, listName and index because i have several buttons and list in the real project
getData('data.csv').then(data => formatData(data)); // get data from csv files
async function getData(url) {
const response = await fetch(url);
const data = await response.text();
return data;
};
function formatData(data) { // real formatting function taken out for stackflow
let formatted = // some formatting stuff;
list.push(formatted);
};
function roll(options) {
const slot = options.data.slotName;
const listTemp = options.data.listName;
const index = options.data.index;
if (slot.includes('item')) { // real criteria taken out for stackflow
do {
const randomNumber = Math.floor(Math.random() * (listTemp.length - 1) + 1);
let randomItem = listTemp[randomNumber];
if (1 == 1) { // real criteria taken out for stackflow
let chance = Math.round(Math.random());
if (chance) {
randomItem.Name += '(altered)';
}
}
results.splice(index, 1, randomItem);
} while (1 != 1); // real criteria taken out for stackflow
};
};
});
I expect it to write the altered "randomItem.Name" only to randomItem itself. Not even to listTemp but definitly not to the global "list". Do you have any idea why it is doing this and how i can prevent this? How can i get the object into randomItem without randomItem keeping its reference to any list. Thank you guys in advance!
I have a text phrase on an HTML page that is built, in part, from dropdown lists of words (built from arrays).
I want to be able to push a button and transfer the text of the phrase to another div.
The problem is - it transfers ALL of the options in the dropdown list and not just the one I select.
Here is a working example: https://nessify.co.uk/sandbox/textbuilder/textbuilder.html
function transfer() {
total = document.getElementById('original').innerText;
document.getElementById('result').innerHTML = total;
}
This is the function I'm using to 'transfer'. I've also tried...
function transfer() {
total = document.getElementById('original').innerHTML;
document.getElementById('result').innerHTML = total;
}
I'm guessing I have to convert the HTML in 'original' to plain text somehow... but I don't know how.
You can read the selected value using the value property of the select tag. Try this:
function transfer() {
const originalNode = document.getElementById('original')
const resultNode = document.getElementById('result')
// Iterate over all the nodes within the original node
const resultFragment = document.createDocumentFragment()
originalNode.childNodes.forEach(cn => {
if (cn.nodeName === 'SELECT') {
// If the node is a 'select' tag, read its value as text
resultFragment.appendChild(document.createTextNode(cn.value))
} else {
// If the node is a text node (or anything else), copy it as-is
resultFragment.appendChild(cn.cloneNode(true))
}
})
// Reset the result node
resultNode.innerHTML = ""
// Set the result to the fragment created above
resultNode.appendChild(resultFragment)
}
I'm creating a document merge (mail merge) from Google App Maker to a Google Document template. I can do so successfully when merging one single record, but how do you merge several records into the one document?
I have an purchase_orders parent record which has several purchase_order_line_items child records but I can't seem to get all of these records into a single document merge.
A similar question (Document Merge with Google App Maker) was asked by by Johan W with a comprehensive answer by Markus Malessa and Pavel Shkleinik (thank you!). However, it only caters for cases when you are merging one single record.
I have tried to build on their answer by using a second for loop to get the data of all associated child records. The script runs but only seems to merge the first child record; not all of them.
Here is an example of the server-side code I've tried to use:
function Export(key, key2) {
// Get the parent record by its key, which was passed by the first parameter above
var record = app.models.Purchase_Orders.getRecord(key);
// Get the first child record by its key, which was passed by the second parameter above
var childRecord = app.models.Purchase_Order_Line_Items.getRecord(key2);
// Get the Google Document which will be used as a template for this merge
var templateId = '1Xbt8camqHJYrhBnx0a6G2-RvTvybqU0PclHifcdiLLA';
//Set the filename of the new merge document to be created
var filename = 'Document for Customer ' + new Date();
//Make a copy of the template to use as the merge document
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
//Get the Google Docs ID of the newly created merge document
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
// Replace the field names in the template with the field data from the parent record
var fields = app.metadata.models.Purchase_Orders.fields;
for (var i in fields) {
console.log(i);
var text = '<<' + fields[i].name + '>>';
var data = record[fields[i].name];
if (data !== null) {
copyBody.replaceText(text, data);
} else {
// do nothing
}
}
// Replace the field names in the template with the field data from the child records
childFields = app.metadata.models.Purchase_Order_Line_Items.fields;
for (i in childFields) {
console.log(i);
var childtext = '<<' + childFields[i].name + '>>';
var childdata = childRecord[childFields[i].name];
if (childdata !== null) {
copyBody.replaceText(childtext, childdata);
} else {
// do nothing
}
}
}
How can I improve my code so that all associated child records are merged into a single document?
How can I set up my Google Document template to cater for any number of child records?
Rather than passing in the child record key via a second parameter, I would suggest just passing in the parent key and then changing your function as follows:
function Export(key) {
// Get the parent record by its key, which was passed by the first parameter above
var record = app.models.Purchase_Orders.getRecord(key);
// Get the first child record by its key, which was passed by the second parameter above
var childRecords = record.Purchase_Order_Line_Items;
// Get the Google Document which will be used as a template for this merge
var templateId = '1Xbt8camqHJYrhBnx0a6G2-RvTvybqU0PclHifcdiLLA';
//Set the filename of the new merge document to be created
var filename = 'Document for Customer ' + new Date();
//Make a copy of the template to use as the merge document
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
//Get the Google Docs ID of the newly created merge document
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
// Replace the field names in the template with the field data from the parent record
var fields = app.metadata.models.Purchase_Orders.fields;
for (var i in fields) {
console.log(i);
var text = '<<' + fields[i].name + '>>';
var data = record[fields[i].name];
if (data !== null) {
copyBody.replaceText(text, data);
} else {
// do nothing
}
}
// Replace the field names in the template with the field data from the child records
var childFields = app.metadata.models.Purchase_Order_Line_Items.fields;
var table = [];
var tableheader = [];
for (i in childFields) {
console.log(i);
tableheader.push(childFields[i].displayName);
}
table.push(tableheader);
for (i in childRecords) {
var data = [];
for (var j in childFields) {
data.push(childRecords[i][childFields[j].name]);
}
table.push(data);
}
copyBody.appendTable(table);
The table building is based on a 2D array and the documentation is here https://developers.google.com/apps-script/reference/document/table. But you will also need to remove your prebuilt table in favor of just appending a table instead. This way you are not dependent on the quantity of child records being fixed like they currently are in your document template. Also, the variable for childRecords may or may not work, I have not tested this since I am unsure if prefetch works in conjunction with .getRecord(key). This may require some additional testing but hopefully this will provide enough guidance.
Thought I would add this as an alternative. Lets say you keep your table but remove all the rows with exception for the header row then you could still use DocumentApp service to add your rows to the table like so:
var tableheaderfieldnames = ['Quantity_for_PO', 'Inventory_Item.id', 'Unit_Price']; //set a fixed table header with the field names, uncertain if the table header for the related inventory item will work or not
var table = copyBody.getTables()[0];
for (i in childRecords) {
var row = table.appendRow();
for (var j in tableheaderfieldnames) {
row.appendTableCell(childRecords[i][tableheaderfieldnames[j]]);
}
}
Keep in mind that AM does not allow you to use FK references, so for your inventory item that appears to use a fk field you may need to tinker around with setting the proper name reference for when you are trying to fill in the item in your table.
I set up a search function for a content system.
The user is able to publish and unpublish his elements.
Every element has two keywords + the condition state = 3 keywords for the search in sum.
Every element has an attribute namend "data-meta" in which the three keywords are stored.
eg.
data-meta="banana blue public"
How can i edit the last value "public", if the user wants to unpublish his element and set it to private?
Without actually altering how you store these values, such as creating custom attributes like
data-keyone="banana" data-keytwo="blue" data-state="public"
you can pull the value of the attribute, split it, modify the third element, join it, then set the attribute value to the new string.
Starting with this:
<myelement id="example" data-meta="banana blue public">
Pull the value:
var oElem = document.getElementById("example");
var strTemp = oElem.getAttribute("data-meta"); //javascript
var strTemp = $('myelement#example').attr('data-meta'); // jquery
Split it:
var astrVals = strTemp.split(" ");
Modify the third value:
astrVals[2] = "private";
Rejoin it:
strTemp = astrVals.join(" ");
Then set the value again:
$('myelement#example').attr('data-meta', strTemp); //jquery
oElem.setAttribute("data-meta", strTemp); // javascript
Edit with jQuery not with javascript:
First:
$('#anyID').removeAttr("meta-data")
After:
$('#anyID').attr("data-meta", "banana blue private")
I've made a few small functions that can make toggle like functionality.
The first one takes the string from attribute and splits it by spaces to make an array, the next one looks at the array to see if it contains what you want to toggle. Third and forth add and remove from the array and then join the array back together as a string with spaces.
I haven't added any error handling so if you try and remove an attribute that isn't there it will have issues but other than that is should get you started.
// take contents of attribute and return an array
function arrFromAttr(selector, attribute) {
return selector.getAttribute(attribute).split(" ");
}
// check the array and toggle the value
function toggleAttr(selector, attribute, value) {
let attrs = arrFromAttr(selector, attribute);
if (attrs.includes(value)) {
removeAttr(selector, attribute, value)
} else {
addAttr(selector, attribute, value)
}
}
// add to the array and set the attribute
function addAttr(selector, attribute, value) {
let attrs = arrFromAttr(selector, attribute);
attrs.push(value);
selector.setAttribute(attribute, attrs.join(' '));
}
// remove from the array and set the attribute
function removeAttr(selector, attribute, value) {
let attrs = arrFromAttr(selector, attribute);
attrs.splice(attrs.indexOf(value), 1);
selector.setAttribute(attribute, attrs.join(' '));
}
// toggle the attribute on button click
document.querySelector('button').addEventListener('click', () => {
toggleAttr(document.querySelector('[data-meta]'), "data-meta", "public");
})
div[data-meta]:after {
content: attr(data-meta)
}
<div data-meta="banana blue public"></div>
<button>Toggle public</button>
I hope you find this helpful 🙂
If you're feeling fancy you can have your own polyfill you make a toggle function, though I wouldn't recommend doing something like this until you're confident in JS
if (!(Element.prototype.toggleAttribute || Element.prototype.deleteAttribute || Element.prototype.addAttribute)) {
Element.prototype.toggleAttribute = function(name, value) {
let attrs = this.getAttribute(name).split(" ");
if (attrs.includes(value)) {
this.deleteAttribute(name, value)
} else {
this.addAttribute(name, value);
}
}
Element.prototype.addAttribute = function(name, value) {
let attrs = this.getAttribute(name).split(" ");
attrs.push(value);
this.setAttribute(name, attrs.join(' '));
}
Element.prototype.deleteAttribute = function(name, value) {
let attrs = this.getAttribute(name).split(" ");
attrs.splice(attrs.indexOf(value), 1);
this.setAttribute(name, attrs.join(' '));
}
}
document.querySelector('button').addEventListener('click', () => {
document.querySelector('div').toggleAttribute('data-meta', 'public');
})
div[data-meta]:after {
content: attr(data-meta)
}
<div data-meta="banana blue public"></div>
<button>Toggle public</button>
This code adds the functions to the native code so you can call the functions on a element rather than having to pass the element as an arguement.
I'm trying to reset data attributes after an animation and am running through some trouble applying the technique from answer 2 of this post.
Not sure what I'm missing here. Seems theoretically feasible to say for each data attribute, etc.
UPDATE:
Worth mentioning that the data keys are all different. E.g. data-1="abc", data-2="abc", etc, hence the need for a for loop that simply looks for data attributes.
HTML
var total = 0;
$.each($('*').data(), function(key, value) {
if (key){
var thiis = $(this);
total += key;
thiis.removeData();
thiis.data(total, value);
}
});
Boom, got it. The script has a lot of overhead, so running it in an instance that a user will wait through isn't an option, IMO. You could improve it with specificity instead of the * selector.
JavaScript (jQuery):
var counter = 1; // not necessary for your implementation, using it to adjust numeric data keys
$('*').each(function(){ // query all selectors and run through a loop
var thiis = $(this),
dataAttr = thiis.data(),
i;
if (dataAttr) { // if the element has data (regardless of attribute)
var newAttrs = []; // for the element's new data values
$.each(dataAttr, function(key, value) { // loop through each data object
var newKey = key + counter, // calculate new data key
newAttr = [newKey, value]; // push the new data set
newAttrs.push(newAttr); // push to elements new attributes array
thiis
.removeData(key) // remove the data
.removeAttr('data-' + key); // remvoe the attribute (unnecessary)
});
for (i = 0; i < newAttrs.length; i++) { // for each new attribute
thiis.data(newAttrs[i][0], newAttrs[i][1]); // add the data
thiis.attr('data-' + newAttrs[i][0], newAttrs[i][1]); // add the attribute (unnecessary)
}
}
});