javascript how to insert new elements at different locations in page - javascript

I'm writing a chrome extension that will add helper text instructions/reminders to specific location in the "new order" form we use at work. I'm really new to coding (basically using this project as a way to learn). I've created something that works - but I'm convinced there's a more elegant solution that I just haven't been able to figure out.
var helpText = "this is the message"
var customAlert = makeAlert(helpText) //create html container for msg
function makerAlert(helpText){...} //createElem, add class/style, append children
I'm okay with that bit (above). But should i be storing information on each message in objects instead? why would/wouldn't i? what information would go in it?
function alertPlacer(customAlert){
var par = document.getElementsByClassName("class-name")[i];
var sib = par.childNodes[j];
par.insertBefore(customAlert, sib);
};
really struggling with this bit (above). I have actually made alertPlacer() functions for each message because i can't figure out how to create a function that will take different class name & index parameters. should i be breaking this up more? if i stored these bits of info in an object, would that be useful?
relevant info:
because the target locations are within a form, almost nothing has an "id" attribute. so i have to use getElementsByClassName & an index.
for each message, I know the target parent className & index and the child node index to "insert before".
i would like to stick with javascript-only solution.

functions can take multiple arguments:
function alertPlacer(customAlert,className,parIndex,childIndex){
var par = document.getElementsByClassName(className)[parIndex]; var sib = par.childNodes[childIndex];
par.insertBefore(customAlert, sib);
};
And you call your function like
alertPlacer(yourAlert,"class-name",6,9);

Related

Form's App Script does not replace fields in template accurately

I have a simple script to generate a doc and PDF upon form submission. It worked well on simple template (e.g. Only 1 sentence, First name, Last name and Company name).
However, when I use a template that's longer, having many fields, and formatting, the code runs but replace the text randomly.
I have tried to hardcode the fields of forms in ascending order as the doc template. However it still replace the text randomly
Can anybody points out what have I done wrong?
My code:
function myFunction(e) {
var response = e.response;
var timestamp = response.getTimestamp();
var [companyName, country, totalEmployees,totalPctWomenEmployees,numberNationality,name1,position1,emailAdd1,linkedin1,funFact1,name2,position2,emailAdd2,linkedin2,gameStage,gameStory] = response.getItemResponses().map(function(f) {return f.getResponse()});
var file = DriveApp.getFileById('XXXXX');
var folder = DriveApp.getFolderById('XXXXX')
var copy = file.makeCopy(companyName + '_one pager', folder);
var doc = DocumentApp.openById(copy.getId());
var body = doc.getBody();
body.replaceText('{{Company Name}}', companyName);
body.replaceText('{{Name}}', name1);
body.replaceText('{{Position}}', position1);
body.replaceText('{{Email}}', emailAdd1);
body.replaceText('{{Linkedin}}', linkedin1);
body.replaceText('{{Fun Fact}}', funFact1);
body.replaceText('{{Game Stage}}', gameStage);
body.replaceText('{{Game Story}}', gameStory);
doc.saveAndClose();
folder.createFile(doc.getAs("application/pdf"));}
My template -
Result -
Question - Does that mean the array declaration in line 3 was supposed to match the order of my form responses columns?
You can use Regular Expresion:
body.replace(/{{Company Name}}/g, companyName); // /g replace globaly all value like {{Company Name}}
Finally I found what have went wrong after so many trials and errors!
The reason is because I declared the array variables randomly without following the order of the form responses columns.
The issue is with the part -
var [companyName, country, totalEmployees,totalPctWomenEmployees,numberNationality,name1,position1,emailAdd1,linkedin1,funFact1,name2,position2,emailAdd2,linkedin2,gameStage,gameStory] = response.getItemResponses().map(function(f) {return f.getResponse()});
It's actually pulling responses from the spreadsheet, and should be corrected in order. The wrongly mapped values was what causing the replacement of text went haywire. I corrected the order as per form responses and it is all good now.
Learning points:
If you swapped around the variables, what response.getItemResponses().map(function(f) {return f.getResponse()} does is that it will go through the form responses column by column in order, and it will map the content to the wrong variable. As a result, when you replace your text later using body.replaceText('{{Game Stage}}', gameStage), there might be possibility that whatever stored in gameStage might be name1. Hence the replaced text will be wrong. And you will scratch your head until it bleeds without knowing why.
I saw #Tanaike's comment after I found the answer, but totally spot on!

Can I set a <div> with an ID that has been dynamically created?

In my program I am dynamically creating feedback boxes to respond to user input. My problem is, I don't know how many of these pieces of feedback will be outputted. I have created a function in my javascript that assigns an id to each new element I create at runtime by concatenating a string with a variable number - which is incremented after every time the constructor is called - and appending it to my CSS class for styling, however I'm not sure how to reference a varying id in my html so that they actually appear on the screen.
var counter = 0;
function constructFeedbackBox() {
counter++;
var newElement = 'toast' + counter;
var i = null;
i = document.createElement('div');
i.id = newElement;
i.className = ".toastStyle";
}
In addition, if there are any problems with the way I've done my javascript to create my Id and/or append it to my class, the info would be much appreciated (I'm still pretty new to this)
First of all, document.createElement() takes a tagName as an input. If you are making divs, your line should read document.createElement('div');.
You can then do i.setAttribute(id, 'toast' + counter);.
What are you trying to accomplish overall with this? Maybe there is a better approach. Also worth noting that IDs must be unique, so be careful about that.

InDesign Scripting: Deleting elements from the structure panel

I've imported some XML files inside InDesign (you can see the structure in the picture below) and I've also created a script to get some statistics concerning this hierarchy.
For example, to count the "free" elements:
var items = app.activeDocument.xmlElements.everyItem();
var items1 = items.xmlElements.itemByName("cars");
var cars = items1.xmlElements.everyItem();
var c_free = cars.xmlElements.itemByName("free");
var cars_free = c_free.xmlElements.count().length;
I also have apartments in my structure that's why I'm using itemByName.
The code above returns the correct number of free cars in my structure.
What I'm trying to do - without any luck so far - is to target those free items (inside cars) and either delete all of them or a specific number.
My last attempt was using:
var del1 = myInputGroup2.add ("button", undefined, "Delete All");
del1.onClick = function () {
cars.xmlElements.everyItem().remove();
}
inside a dialog I've created.
Any suggestions will be appreciated cause I'm really stuck here.
I would probably use XPath for this. You can use evaluateXPathExpression to create an array of the elements you want to target. Assuming your root element is cars and it contains elements called cars1, and you want to delete all free elements within a cars1 element, you could do something like:
var myDoc = app.activeDocument;
//xmlElements[0] is your root element, in this case "cars". The xPath expression is evaluated from cars.
//evaluateXPathExpression returns an array of all of the free elements that are children of cars.
var myFrees = myDoc.xmlElements[0].evaluateXPathExpression("cars1/free");
for (var i = myFrees.length - 1; i>=0; i--){
myFrees[i].remove();
}
Tweaking this would require some knowledge of xPath, but it's not terribly hard to learn the basics and it does seem like the simplest approach.
I think your main problem was that XMLElements hasn't a itemByName method. You can only reference XMLElements through their indeces or ids.
Secondly you assume that you got xmlElements from XPath expression but it's likely that you got nothing as your xpath seems uncorrect.
var myFrees = myDoc.xmlElements[0].evaluateXPathExpression("./cars1/free");
var n = myFrees.length;
if ( !n ) {
alert("Aucun élément trouvé");
}
else {
while (n--) myFrees[n].remove();
}
You need to start your expression by setting the origin of your xpath. Here a dot "./" is used to tell you want to look for cars1/free xml elements at the "root" of the xmlelement. Using "//" on the contrary would have returned any cars/free items unregardingly of their locations.

Action Script 3. How to access button from Movie Clip in Flash?

I have MyMovieClip named mClip. Inside mClip are 2 buttons named: btn1 btn2
I need access these buttons in my Action Script code. I have declared It:
var mClip:MyMovieClip = new MyMovieClip();
var btn1:MyButton1 = new MyButton1();
var btn2:MyButton2 = new MyButton2();
I have tried in 2 ways, but both unsuccessfully:
1.
mClip.btn1.addEventListener(MouseEvent.CLICK, popUp1);
mClip.btn2.addEventListener(MouseEvent.CLICK, popUp2);
function popUp1(event:MouseEvent):void {
trace("test 1");
}
function popUp2(event:MouseEvent):void {
trace("test 2");
}
2.
btn1.addEventListener(MouseEvent.CLICK, popUp1); //removed mClip
btn2.addEventListener(MouseEvent.CLICK, popUp2); //removed mClip
function popUp1(event:MouseEvent):void {
trace("test 1");
}
function popUp2(event:MouseEvent):void {
trace("test 2");
}
I don't get any errors, just nothing happens after buttons is clicked. Could you help me, please? Thank you
Here is what is likely going on. Though I have to make the following assumptions:
You are using flash pro
In flash pro, you've created a movie clip, placed it on the main timeline, and given it the instance name of mClip
On the mClip timeline, you've created two buttons and given them the instance names of btn1 and btn2.
All your code is on the main timeline.
When you do the following:
var mClip:MyMovieClip = new MyMovieClip();
var btn1:MyButton1 = new MyButton1();
var btn2:MyButton2 = new MyButton2();
You are actually telling flash to take those names (mClip, btn1,btn2) and assign NEW objects to them (the old ones still exists, but the vars refer to the new objects you've just created - so your adding click events to objects that aren't on screen).
When you add a movie clip to the timeline and give it an instance name, behind the scenes it's effectively done the same as that code. Remove those three lines, and your code from your first try will work. (assuming all my assumptions above are correct, please advise if not)
The first try would be correct if the class is marked as dynamic. But as this is not your case, you should either use the variable directly:
btn1.addEventListener(..
IF this is used exactly in the same class that you define them (and add them in the mClip). What actually happens is that you work with the variable, rather than the instance name. You might have problem because you are not working where you define those buttons.
If that still doesn't work, you are probably clicking on something above that child.
It's not entirely clear from your diagram, but the buttons should have colored (not empty) fields (although the alpha can be anything, including 'zero') -- so that you're actually clicking on something. And, if the 'buttons' are just MovieClips coded as buttons, the labels should be static text and non-selectable.
We're assuming that your code is timeline code, NOT part of coded classes.

Trying to reduce repetition of javascript using a variable

I am trying to reduce the repetition in my code but not having any luck. I reduced the code down to its simplest functionality to try and get it to work.
The idea is to take the last two letters of an id name, as those letters are the same as a previously declared variable and use it to refer to the old variable.
I used the alert to test whether I was getting the right output and the alert window pops up saying "E1". So I am not really sure why it wont work when I try and use it.
E1 = new Audio('audio/E1.ogg');
$('#noteE1').click(function() {
var fileName = this.id.slice(4);
//alert(fileName); used to test output
fileName.play();
$('#note' + fileName).addClass('active');
});
The code block works when I use the original variable E1 instead of fileName. I want to use fileName because I am hoping to have this function work for multiple elements on click, instead of having it repeated for each element.
How can I make this work? What am I missing?
Thanks.
fileName is still a string. JavaScript does not know that you want to use the variable with the same name. You are calling the play() method on a string, which of course does not exist (hence you get an error).
Suggestion:
Store your objects in a table:
var files = {
E1: new Audio('audio/E1.ogg')
};
$('#noteE1').click(function() {
var fileName = this.id.slice(4);
//alert(fileName); used to test output
files[fileName].play();
$('#note' + fileName).addClass('active');
});
Another suggestion:
Instead of using the ID to hold information about the file, consider using HTML5 data attributes:
<div id="#note" data-filename="E1">Something</div>
Then you can get the name with:
var filename = $('#note').data('filename');
This makes your code more flexible. You are not dependent on giving the elements an ID in a specific format.

Categories