*I have a Google Document with a string like "text {logo} text"
How do place an image where {logo} is?
So far I tried:
var logoElement = s.findText("{logo}").getElement();
logoElement.getParent().insertInlineImage(0,logoBlob);
s.replaceText("{logo}", "");
But this inserts the image before the found paragraph (or with 1: after). How do I place it inside the paragraph at the exact location?
I hope will be helpful, The following code is fine to me.
function insertImage(doc) {
// Retrieve an image from the web.
var resp = UrlFetchApp.fetch("http://www.google.com/intl/en_com/images/srpr/logo2w.png");
var image = resp.getBlob();
var body = doc.getBody();
var oImg = body.findText("<<Logo1>>").getElement().getParent().asParagraph();
oImg.clear();
oImg = oImg.appendInlineImage(image);
oImg.setWidth(100);
oImg.setHeight(100);
}
Thanks to the comment from Serge and the original post of the link for pointing me in the right direction.
Here's what I have now:
var s = d.getHeader();
var logoResult = s.findText("{logo}"); // search result
var logoElement = logoResult.getElement(); // the paragraph that contains the placeholder
var text = logoElement.getText();
var placeholderStart = logoResult.getStartOffset(); // character position start placeholder
var placeholderEnd = logoResult.getEndOffsetInclusive(); // char. position end placeholder
var parent = logoElement.getParent();
var parPosition = parent.getChildIndex(logoElement);
// add new paragraph after the found paragraph, with text preceding the placeholder
var beforeAndLogo = s.insertParagraph(parPosition+2, text.substring(0, placeholderStart));
var logo = beforeAndLogo.appendInlineImage(logoBlob); // append the logo to that new paragraph
// add new paragraph after the new logo paragraph, containing the text after the placeholder
var afterLogo = s.insertParagraph(parPosition+3, text.substring(placeholderEnd+1));
afterLogo.merge(); // merge these two paragraphs
// finally remove the original paragraph
parent.removeFromParent(); // remove the original paragraph
It is not complete, I should also copy all the attributes.
More importantly, it does not copy the tab settings (e.g. center tab). Have not found a way to set tab positions.
I tried a simpler version based on your answer that keeps the format of the original paragraph...
Give it a try, it's not "foolproof" but it works in my test and is (I think) an interresting trial ;-)
code here :
function test(){
placeImage('{logo}','0B3qSFd3iikE3SkFXc3BYQmlZY1U');
//This is my page and I’d like to have a {logo} on it right here
}
function placeImage(placeHolder,imageId) {
var logoBlob = DocsList.getFileById(imageId).getBlob();
var d = DocumentApp.getActiveDocument()
var s = d.getHeader();
var logoResult = s.findText(placeHolder);
var placeholderStart = logoResult.getStartOffset();
var par = s.getChild(0).asParagraph();
var parcopy = par.copy();
var parLen = par.editAsText().getText().length-1;
Logger.log('placeholderStart = '+placeholderStart+' parLen = '+parLen)
par.editAsText().deleteText(placeholderStart, parLen);
parcopy.editAsText().deleteText(0, placeholderStart+placeHolder.length);
var img = s.getChild(0).appendInlineImage(logoBlob);
s.appendParagraph(parcopy);
parcopy.merge();
}
Related
I am trying to populate a page with firebase data.
This is my firebase data structure...
What I want is to create number of divs according to the number of posts in firebase. And in the divs with title and subtitle in h2 tag and p tag.
I am new to firebase soo any help would be appreciated...
and also i want to limit the number of divs to 4 starting from the latest post.
this is my java script
firebase.initializeApp(firebaseConfig);
var postsRef = firebase.database().ref("posts").orderByKey();
postsRef.once("value").then(function (snapshot) {
snapshot.forEach(function (childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var name_val = childSnapshot.val().title;
var id_val = childSnapshot.val().subtitle;
console.log(name_val);
var post = document.getElementById('#tst-post');
var divh2 = document.createElement('h2');
divh2.innerText - childData.val().title + "---" + JSON.stringify(childData.val());
$(post).append(divh2);
});
});
i dont know what i am doing in this code, I just watched some tutorials. Please help me.
You are not very far from a result.
By searching on the internet (https://www.google.com/search?client=firefox-b-d&q=how+to+dynamically+create+div+in+javascript) you can easily find a lot of examples on how to create DIVs dynamically. For example: https://stackoverflow.com/a/50950179/3371862
Then, in the Firebase Realtime Database documentation you find how to filter data and in particular how to "Sets the maximum number of items to return from the end of the ordered list of results" with limitToLast().
So if you put all of that together as follows, it should do the trick:
<script>
var postsRef = firebase
.database()
.ref('posts')
.orderByKey()
.limitToLast(4);
postsRef.once('value').then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key;
var childData = childSnapshot.val();
var name_val = childSnapshot.val().title;
var id_val = childSnapshot.val().subtitle;
createDiv(name_val, id_val);
});
});
function createDiv(title, subtitle) {
var myDiv = document.createElement('DIV'); // Create a <div> node
var myTitle = document.createTextNode(title); // Create a text node
myDiv.appendChild(myTitle); // Append the text
var mySubtitle = document.createTextNode(subtitle); // Create a text node
myDiv.appendChild(mySubtitle); // Append the text
myDiv.style.backgroundColor = 'grey';
myDiv.style.border = 'solid';
myDiv.style.margin = '10px';
document.body.appendChild(myDiv);
}
</script>
Currently quill editor will save the image in base64 image format. That is fine for now.
But how do I make sure quill save the base64 content only and without those formatting?
This is how it would look like after the image has been saved in the db.
<p><img src="data:image/png;base64,iVBXXXXXXXXXXXXXXXXXXXXXXXXXXX"></p>
Basically I want to strip off "< p >" and < img src= tags.
I would like to retain data:image/png;base64,iVBXXXXXXXXXX only. Is there any setting that I can do on quill modules?
If slicing the string is a doable approach, something like this should work for your current format:
var str = '<p><img src="data:image/png;base64,iVBXXXXXXXXXXXXXXXXXXXXXXXXXXX"></p>'
var srcPosition = str.indexOf('src');
var pEndPosition = str.indexOf('</p>');
var strippedString = str.slice(srcPosition + 5, pEndPosition - 2);
Good luck!
Not sure about modules but you could use something something like the below to get all the images into an array:
function GetImageData(str) {
var retArray = [];
var regexPattern = /src="([^"]+)/gi;
var matches = str.match(regex);
for (var i = 0;i < matches.length;i++) {
retArray.push(matches[i].replace('src="',''));
}
return retArray;
}
var test = '<p><img src="data:image/png;base64,iVBXXXXXXXXXXXXXXXXXXXXXXXXXXX"></p><p><img src="data:image/png;base64,iVBYYYYYYYYYYYYYYYYYYYYYYYYYYY"></p><p><img src="data:image/png;base64,iVBZZZZZZZZZZZZZZZZZZZZZZZZZZZ"></p>';
var result = GetImageData(test);
This gives you an array with the following:
"data:image/png;base64,iVBXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"data:image/png;base64,iVBYYYYYYYYYYYYYYYYYYYYYYYYYYY"
"data:image/png;base64,iVBZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
I'm trying to write a function that would remove the heading, but retain the font and size, of the current paragraph.
However, it seems that values for font family/size cannot be retrieved from paragraphs that have their default font, as set by the heading applied.
var cursor = DocumentApp.getActiveDocument().getCursor(); if (!cursor) return;
var ctext = cursor.getSurroundingText();
var para = ctext.asParagraph();
para.setHeading(DocumentApp.ParagraphHeading.HEADING1); // sets Arial 20
var text = ctext.asText();
var ff = text.getFontFamily();
var fs = text.getFontSize();
DocumentApp.getUi().alert(ff+" "+fs); // NULL NULL
I tried accessing the headers' fonts and sizes, to get at them this way, but Google App Script doesn't seem to expose those anywhere.
A header's style attributes can be retrieved with Body.getHeadingAttributes(paragraphHeading).
A paragraph's style attributes can be overridden at the character level. That's what text.getFontFamily() and text.getFontSize() are retrieving. But, since these attributes weren't overridden at the character level in your example, they came back as null, in which case it is necessary to fall back to the paragraph's heading's style definition.
I added the fall-back logic to your example:
var cursor = DocumentApp.getActiveDocument().getCursor(); if (!cursor) return;
var ctext = cursor.getSurroundingText();
var para = ctext.asParagraph();
para.setHeading(DocumentApp.ParagraphHeading.HEADING1); // sets Arial 20
var text = ctext.asText();
// Get heading attributes
var body = DocumentApp.getActiveDocument().getBody();
var headingAtts = body.getHeadingAttributes(para.getHeading());
var ff = text.getFontFamily();
// If not set, fall back to heading attributes
if (ff == null) ff = headingAtts[DocumentApp.Attribute.FONT_FAMILY];
var fs = text.getFontSize();
// If not set, fall back to heading attributes
if (fs == null) fs = headingAtts[DocumentApp.Attribute.FONT_SIZE];
DocumentApp.getUi().alert(ff+" "+fs); // Arial 20
More of a confirmation that this cannot be done right now, I took a look at the Attributes object and even accessed them directly with the same result. As a workaround, since you know the defaults, you can store those as Objects and set them when conditions are met in the script:
function resetHeadings() {
var body = DocumentApp.getActiveDocument().getBody();
// store an object with default object attributes you can apply later
var defaults = {
"Heading 1": {
FONT_SIZE: 20,
FONT_FAMILY: 'Arial',
},
"Heading 2": {
FONT_SIZE: 16,
FONT_FAMILY: 'Arial'
}
};
var pars = body.getParagraphs();
for(var i in pars) {
var props = pars[i].getAttributes();
if(props["HEADING"] == "Heading 1") {
// reset the Heading to normal
props[i].setHeading(DocumentApp.ParagraphHeading.NORMAL);
// Then, spoof with your stored defaults
// You can chain .setAttributes() with the line above. Shown separate for clarity.
props[i].setAttributes(defaults["Heading 1"]);
}
}
}
It's not perfect, but it'll get you the outcome you're describing. You could use a case/switch test for each heading rather than multiple if statements.
My goal is to parse a TableOfContents element in a Google Document and write it to another one. I want to do this for every document in a folder.
Having gone to the bother of converting each document to the type generated by DocsList just so I can use this method [ which a document generated by DocumentApp does not have. Why, I don't understand, because otherwise the two 'documents' are similar when it comes to finding parts. ], I find that what I get back is a SearchResult. How is this elusive construction used? I've tried converting it into a TableOfContents element [ ele = searchResult.asTableOfContents() ], which does not error out, but nothing I do allows me parse through its child elements to recover their text works. Interestingly enough, if you get a TableOfContents element by parsing through the document's paragraphs to get it, THAT let's you parse the TOC.
Would someone speak to this question. I sure would appreciate a code snippet because I'm getting nowhere, and I have put some hours into this.
The asTableOfContents() method is only there to help the editor's autocomplete function. It has no run-time impact, and cannot be used to cast to a different type. (See ContainerElement documentation.)
To parse the table of contents, start by retrieving the element from the SearchResult. Below is an example that goes through the items in a document's table of contents to produce an array of item information.
Example Document
Parsing results
On a simple document with a few headings and a table of contents, here's what it produced:
[13-08-20 16:31:56:415 EDT]
[
{text=Heading 1.0, linkUrl=#heading=h.50tkhklducwk, indentFirstLine=18.0, indentStart=18.0},
{text=Heading 1.1, linkUrl=#heading=h.ugj69zpoikat, indentFirstLine=36.0, indentStart=36.0},
{text=Heading 1.2, linkUrl=#heading=h.xb0y0mu59rag, indentFirstLine=36.0, indentStart=36.0},
{text=Heading 2.0, linkUrl=#heading=h.gebx44eft4kq, indentFirstLine=18.0, indentStart=18.0}
]
Code
function test_parseTOC() {
var fileId = '--Doc-ID--';
Logger.log( parseTOC( fileId ) );
}
function parseTOC( docId ) {
var contents = [];
var doc = DocumentApp.openById(docId);
// Define the search parameters.
var searchElement = doc.getBody();
var searchType = DocumentApp.ElementType.TABLE_OF_CONTENTS;
// Search for TOC. Assume there's only one.
var searchResult = searchElement.findElement(searchType);
if (searchResult) {
// TOC was found
var toc = searchResult.getElement().asTableOfContents();
// Parse all entries in TOC. The TOC contains child Paragraph elements,
// and each of those has a child Text element. The attributes of both
// the Paragraph and Text combine to make the TOC item functional.
var numChildren = toc.getNumChildren();
for (var i=0; i < numChildren; i++) {
var itemInfo = {}
var tocItem = toc.getChild(i).asParagraph();
var tocItemAttrs = tocItem.getAttributes();
var tocItemText = tocItem.getChild(0).asText();
// Set itemInfo attributes for this TOC item, first from Paragraph
itemInfo.text = tocItem.getText(); // Displayed text
itemInfo.indentStart = tocItem.getIndentStart(); // TOC Indentation
itemInfo.indentFirstLine = tocItem.getIndentFirstLine();
// ... then from child Text
itemInfo.linkUrl = tocItemText.getLinkUrl(); // URL Link in document
contents.push(itemInfo);
}
}
// Return array of objects containing TOC info
return contents;
}
Bad news
The bad news is that you are limited in what you can do to a table of contents from a script. You cannot insert a TOC or add new items to an existing one.
See Issue 2502 in the issue tracker, and star it for updates.
If you can post code or explain your issue with DocsList vs DocumentApp, it could be looked at. The elements of a Google Document can only be manipulated via DocumentApp.
I modified the above code to re-create the TOC in a table only with the desired levels(i.e. h1, h2). The only caveat is that TOC must be present & updated before running this.
function findToc(body, level = 2) {
const indent = 18;
let contents = [];
const tocType = TABLE_OF_CONTENTS;
const tocContainer = body.findElement(tocType);
if (tocContainer) {
// TOC was found
const toc = tocContainer.getElement().asTableOfContents();
const totalLines = toc.getNumChildren();
for (let lineIndex = 0; lineIndex < totalLines; lineIndex++) {
const tocItem = toc.getChild(lineIndex).asParagraph();
const { INDENT_START } = tocItem.getAttributes();
const isDesiredLevel = Number(INDENT_START) <= indent * (level - 1);
if (isDesiredLevel) {
contents.push(tocItem.copy());
}
}
}
return contents;
}
function addToTable(cellText) {
body = DocumentApp.openById(docId).getBody();
const table = body.appendTable();
const tr = table.insertTableRow(0);
const td = tr.insertTableCell(0);
cellText.forEach(text => {
td.appendParagraph(text);
})
}
function parseTOC(docId) {
body = DocumentApp.openById(docId).getBody();
const contents = findToc(body);
addToTable(contents);
}
EDIT (last ! :-) : A workaround to this is putting the whole UI in an absolutePanel (thx megabyte1024).
It's not perfect since I cannot have a background color in the whole display area but it's at least much more confortable. (link to online test app is updated with this new version) and screen capture of the final version... much better :-)
I have a standalone webapp written in GAS that has a scrollPanel, the whole UI is rather small and occupies only a part of a (even small) display area in the browser window.
What bothers me is that I always have both an horizontal and a vertical scrollbar in the browser window and it interferes with the UI content when I use a mouse or a trackpad to scroll my scrollpanel in the UI window...
So, my question is : is there a way to avoid this, to tell the browser that there is no need to add scrollbars or to define a smaller "webapp area" ?
note that the size of these scroll bars are fully independent from the UI panel size as long as this last one is smaller than the browser window.
Here is a screen capture to illustrate what I'm saying (I do understand that it's a detail but it makes the use of this app sometimes just uncomfortable ;-) Here is also a link to a public version of the app.
Another detail I'd like to solve is the font color in the upper part of this UI : these are textBoxes that I set to 'read only' because I don't want them to be editable (it would give the illusion that the user could modify data which is not the case) and a side effect of this read only status is that fonts are "greyed" ... is there some way to avoid that while keeping the same aspect (except color) on this 'false table' ?
EDIT : found the second question about text color : .setStyleAttribute('color','#000000') as simple as that... too stupid from me not to have found it earlier ;-)
NOTE 2 : interestingly, UI designed with the GUI builder do not suffer the same problem ...
EDIT2 :here is the code of the doGet part (modified to run without functionality but to showup):
var key='0AnqSFd3iikE3dFV3ZlF5enZIV0JQQ0c1a3dWX1dQbGc'
var ss = SpreadsheetApp.openById(key)
var sh = ss.getSheetByName('Sheet1')
var idx = 0
var data = ss.getDataRange().getValues();
var len = data.length;
var scrit = ['All fields','Name','Lastname','Postal Adress','ZIP code','City','Country','email','phone#']
//public version
function doGet() {
var app = UiApp.createApplication().setTitle("BrowseList Test")
.setHeight(420).setWidth(800).setStyleAttribute("background-color","beige").setStyleAttribute('padding','20');
var title = app.createHTML('<B>DATABASE User Interface</B>').setStyleAttribute('color','#888888');
app.add(title);
var scroll = app.createScrollPanel().setPixelSize(750,150)
var vpanel = app.createVerticalPanel();
var cell = new Array();
var cellWidth = [45,135,150,250,50,100]
var row = new Array();
var cHandler = app.createServerHandler('showpicked').addCallbackElement(scroll);
for(vv=0;vv<15;++vv){
row[vv]=app.createHorizontalPanel();
vpanel.add(row[vv]);
for(hh=0;hh<cellWidth.length;++hh){
cell[hh+(vv)*cellWidth.length]=app.createTextBox().setWidth(cellWidth[hh]+"").setTitle('Click to show below')
.setReadOnly(true).setId((hh+vv*cellWidth.length)+'').addClickHandler(cHandler).setStyleAttribute('background','#eeeeff').setStyleAttribute('color','#000000');
row[vv].add(cell[hh+(vv)*cellWidth.length])
}
}
app.add(scroll.add(vpanel))
// Initial populate
var resindex = new Array()
for(vv=0;vv<15;++vv){
resindex.push(vv+1)
for(hh=0;hh<cellWidth.length;++hh){
var rowpos=vv+1+idx
var cellpos = hh+vv*cellWidth.length
cell[cellpos].setValue(data[rowpos][hh]);
}
}
var rHandler = app.createServerHandler('refresh');
//
var slist = app.createListBox().setName('critere').setId('slist').addClickHandler(rHandler);
for(nn=0;nn<scrit.length;++nn){
slist.addItem(scrit[nn]);
}
var search = app.createTextBox().setName('search').setId('search').setTitle('becomes yellow if no match is found');
var modeS = app.createRadioButton('chkmode','strict').setId('chkmodes').addClickHandler(rHandler);
var modeG = app.createRadioButton('chkmode','global').setValue(true).setId('chkmodeg').addClickHandler(rHandler);
var letter = app.createRadioButton('show','letter').setValue(true).setId('letter').addClickHandler(rHandler);
var raw = app.createRadioButton('show','raw data').setId('raw').addClickHandler(rHandler);
var index = app.createHTML('found/'+len).setId('index').setStyleAttribute('color','#aaaaaa');
var grid = app.createGrid(2,10).setWidth('750');
grid.setWidget(1, 0, app.createLabel('search'));
grid.setWidget(1, 1, search);
grid.setWidget(1, 2, modeS);
grid.setWidget(1, 3, modeG);
grid.setWidget(1, 5, slist);
grid.setWidget(1, 6, app.createLabel('show mode'));
grid.setWidget(1, 7, letter);
grid.setWidget(1, 8, raw);
grid.setWidget(1, 9, index);
app.add(grid);
var hidden = app.createHidden('hidden').setId('hidden').setValue(resindex.toString());
cHandler.addCallbackElement(grid).addCallbackElement(scroll).addCallbackElement(hidden);
var result = app.createRichTextArea().setPixelSize(745,160).setId('result')
.setStyleAttribute('background','white').setStyleAttribute('font-family',"Arial, sans-serif")
.setStyleAttribute('font-size','small');
result.setHTML('test ui');
app.add(result).add(hidden);
var sHandler = app.createServerHandler('searchH').addCallbackElement(grid).addCallbackElement(scroll);
search.addKeyUpHandler(sHandler);
rHandler.addCallbackElement(grid).addCallbackElement(scroll);
slist.addChangeHandler(rHandler);
return app
}
A possible solution to get rid of the scroll bar is to use an intermediate Absolute panel. The following code has the scroll bars.
function doGet() {
var app = UiApp.createApplication();
var panel = app.createScrollPanel().setSize('100%', '100%');
var content = app.createButton('Scroll Bars').setSize('100%', '100%');
panel.setWidget(content);
app.add(panel);
return app;
}
And the code bellow has no the scroll bars
function doGet() {
var app = UiApp.createApplication();
var panel = app.createScrollPanel().setSize('100%', '100%');
var subPanel = app.createAbsolutePanel().setSize('100%', '100%');
var content = app.createButton('No Scroll Bars').setSize('100%', '100%');
subPanel.add(content);
panel.setWidget(subPanel);
app.add(panel);
return app;
}
when embedding a HTMLService web app inside a Google Site, it looks like the iFrame gadget has to be about 50 pixels larger than the app height for the vertical scroll bar to disappear. Assuming same thing applies to UiApp.