In a mailMerge script I'm working on I use .replaceText()
to replace fields with their corresponding values in a database.
The interface allows to test in the document to see if the result is looking as expected and I need to have a 'UNDO' function to get my fields in their original position so that I can use it with other values.(this script is bounded to a document in a side bar, see this post for illustration)
The script below does that pretty well by keeping in memory the field names an their replacement values. The only detail that bothers me is that I had to define a special "empty" label for fields that have no values in the current test data to prevent losing their track in the document. (I used a numbered identifier like °vide12°).
This is working perfectly but it's not ideal since the document in test mode is not exactly a representation of the final document because of these °videXX° that I use...
The question is : does anyone have a better idea or another approach to "localize" the replacement data when there is no data in a less visible way ? (I know this sound weird... that's why I explain the whole situation :-)
Considering the way Google Docs are build I thought that I could get the complete element structure and rebuild the doc from that info but I'm afraid it won't be possible since the smallest element is a paragraph and fields are mainly just single words...
Here is the relevant part of the code I use, I added a few comments to make it (hopefully) clear.
function valuesInDoc(e){ // this function replaces the fields with database values
var app = UiApp.getActiveApplication();
var listVal = UserProperties.getProperty('listSel').split(',');
var replacements = [];
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var find = body.findText('#ch');
if(find == null){var ui = DocumentApp.getUi() ; ui.alert("Aucun champ (#chX#) trouvé dans le document... Veuillez insérer des identifiants aux endroits souhaités");return app};
var curData = UserProperties.getProperty('selItem').split('|');
var Headers = [];
var OriHeaders = UserProperties.getProperty('Headers').split('|');
for(n=0;n<OriHeaders.length;++n){
Headers.push('#'+OriHeaders[n]+'#');
}
var fctSpe = 0 ;
for(var i in Headers){if(Headers[i].indexOf('SS')>-1){fctSpe = i}}
for(var n=0;n<listVal.length;++n){
var realIdx = Number(listVal[n]);
var newField = ChampSpecial(curData,realIdx,fctSpe);
if(newField!=''){replacements.push(newField+'∏'+'#ch'+(n+1)+'#')};
//Logger.log('value in '+n+'='+realIdx+' >> '+Headers[realIdx]+' = '+ChampSpecial(curData,realIdx,fctSpe))
app.getElementById('textField'+(n+1)).setHTML(ChampSpecial(curData,realIdx,fctSpe));
if(e.parameter.source!='dataSelection'){
body.replaceText('#ch'+(n+1)+'#',newField);
}
}
UserProperties.setProperty('replacements',replacements.join('|'));// memorize the replacement pattern
cloakOn();// hide hidden fields
return app;
}
function fieldsInDoc(e){ // this function does the reverse process and restores the field identifiers
cloakOff();// show hidden fields
var replacements = UserProperties.getProperty('replacements').split('|');
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
for(var n=0;n<replacements.length;++n){
var field = replacements[n].split('∏')[1];
var testVal = replacements[n].split('∏')[0];
body.replaceText(testVal,field);
}
}
function ChampSpecial(curData,idx,ref){ // this function handles a special case for a specific field, the relevant part is right below, see comment
if(idx==-1){return''};
if(curData[idx-1]==''){return'°vide'+idx+'°'};// this is the "empty" identifier
if(idx<ref){return curData[idx]};
if(idx>ref){return curData[idx-1]}
var firstSpace = curData[idx-1].indexOf(' ');
var apos = curData[idx-1].indexOf("'");
//Logger.log('firstSpace='+firstSpace+' apos='+apos)
if(firstSpace<4&&firstSpace>-1){return curData[idx-1].substring(firstSpace+1)};
if(apos<3&&apos>-1){return curData[idx-1].substring(apos+1)};
return curData[idx-1];
}
EDIT : thanks to Mogsdad's brilliant answer I wrote these 2 functions to hide/show the unused fields. Sinc in my case I use °XX° (XX=2 digit number) to keep track of the unused fields I had to modify his code to look for this particular string and used 2 loops to get all the fields.
I call these function from the menu AND from the two other functions that handle the replacement (I updated the code above as well)
It might appear a waste of time since I iterate more that 100 times but the result is instantaneous... so why bother ? here is the code in case it gives someone an idea.
function cloakOn() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var found = [];
for(var n=1;n<23;++n){
for(var f=0;f<5;++f){
if(f==0){found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°')}else{found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°',found[f-1])}
if(found[f]!=null){
var elemTxt = found[f].getElement().asText();
elemTxt.setFontSize(found[f].getStartOffset(), found[f].getEndOffsetInclusive(),0)
var background = elemTxt.getBackgroundColor(found[f].getStartOffset()) || "#ffffff";
elemTxt.setForegroundColor(found[f].getStartOffset(), found[f].getEndOffsetInclusive(), background);
}
}
}
}
function cloakOff() {
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var found = [];
for(var n=1;n<23;++n){
for(var f=0;f<5;++f){
if(f==0){found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°')}else{found[f] = body.findText('°'+Utilities.formatString("%02d",n)+'°',found[f-1])}
if(found[f]!=null){
var elemTxt = found[f].getElement().asText();
var size = elemTxt.getParent().getFontSize();
elemTxt.setFontSize(found[f].getStartOffset(), found[f].getEndOffsetInclusive(),size)
var background = elemTxt.getBackgroundColor(found[f].getStartOffset()) || "#000000";
elemTxt.setForegroundColor(found[f].getStartOffset(), found[f].getEndOffsetInclusive(), background);
}
}
}
}