How can I add hyperlinks to d3-generated table rows and/or cells?
If I were following the same convention as adding hyperlinks to rects, lines, and other objects, after importing the xlink namespace, you append an a tag and set the hyperlink attribute, and then append your desired object, like so:
chartBody.selectAll("node")
.data(dataset)
.enter()
.append("a")
.attr("xlink:href", "http://stackoverflow.com")
.append("line")
...
But when you change the character in the story from a rect or line to a table row or cell, it doesn't work...
tablebody.selectAll("row")
.data(parsedtext)
.enter().append("a")
.attr("xlink:href", "http://stackoverflow.com")
.append("tr")
...
Ah, ok then I think what you'll need to use is selection.html, as in:
var data = ['http://stackoverflow.com', 'http://d3js.org/']
var para = d3.select("body").append("p");
para.selectAll("p")
.data(data)
.enter()
.append("p")
.html(function(d,i){ return "" + "site_" + i + ""; });
I think you've probably got it covered from here but here's the link to the selection.html api doc. Basically this allows you to append (in this case) some innerHTML property. Given what I think you're doing it might be worth reading the Subselections section on that page.
That's because rects and lines are SVG elements, while tables are HTML elements. (At least I can't find any reference to table elements within SVG.)
And as for HTML, there is no row element, nor is it valid HTML to enclose a table row within an anchor tag.
Related
I have a list of cells that contain strings that were converted to links using CTRL+K
https://docs.google.com/spreadsheets/d/1Afshcs8f0A3CTeTPaudAhxZVYjMP22KY2gtLeu5qmAs/edit#gid=0
How can I retrieve the URLs in each of these cells?
If it was a formula (i.e. Hyperlink) I could use FormulaText to do so, but it doesn't work in this case.
You would need Apps Script for this
Specifically the getRichTextValue function:
let richText = cellRange.getRichTextValue();
let link = richText.getLinkUrl();
I did some experiments to see if it could be written as a custom function so that you could use it like any old formula, i.e. =getHyperlink(A1) however, when you pass in a range to a custom function, the value of the cell, the contents, gets passed to the function, not the address. There may be a funky workaround in this WebApps Exchange thread but depending on what you need it for, it may not be needed.
You could write a function like this:
function getHyperlink(cellReference) {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
let range = sheet.getRange(cellReference);
let richText = range.getRichTextValue();
let link = richText.getLinkUrl();
return link;
}
Which you could call from another function like this:
let aLink = getHyperlink("A1");
NOTE - the speech brackets around the cell reference.
If you use it as-is as a custom function, you would need to pass in the cell reference as TEXT (you would need to make sure you run the script once from the editor first, to ensure that it has been authorized).
EDIT:
Not sure why I didn't think of this earlier, but you could use this custom formula as-is in combination with =CELL, to make it work like a formula should.
=getHyperlink(CELL("address", A2))
Google Sheets allows to specify (hyper)links in two ways:
By using HYPERLINK formula/function, e.g. =HYPERLINK("http://example.com/", "Example.com")
By using "linking" feature – Insert » Insert Link
There are a lot of solutions around the web, and StackOverflow, for extracting URL from the first option - the HYPERLINK formula, but I haven't found any way how to extract it from the second option.
Example Sheet
How to extract with Apps Script a link URL inserted with Insert » Insert Link
Apps Script has a class https://developers.google.com/apps-script/reference/spreadsheet/rich-text-value that allows you to retrieve not only the plain text contained in a cell, but also its properties, such as the link URL.
To access this property use the method getRichTextValue() combined with getLinkUrl()
Sample to retrieve the link URL in a custom function:
function getLink(range){
var link = SpreadsheetApp.getActive().getActiveSheet().getRange(range).getRichTextValue().getLinkUrl();
return link;
}
After writing and saving this code, you simply need to call it from any cell, giving it the reference of the cell with the link URL as parameter.
Important:
Since it is the reference and not the value of the cell that should be passed to the function, you need to put the A1 notation in quotes.
Sample:
=getLink("A1")
We can improve ziganotschka's solution to avoid the need for quotation marks.
Enter this in B1 (assuming text with the embedded link is in A1):
=getLink(ADDRESS(row(),column(A1)))
This way it can be dragged down to quickly get data for an entire column.
If want to use like normal formula without quotation marks,
function GetURL(input) {
var formula = SpreadsheetApp.getActiveRange().getFormula();
var rx = /=geturl\((.*)\)/gi;
var rng_txt = rx.exec(formula)[1]
var rng = SpreadsheetApp.getActiveSheet().getRange(rng_txt);
return rng.getRichTextValue().getLinkUrl()
}
I have proven to myself that I can insert text into a Google Docs document using this code:
function appendToDocument() {
let offset = 12;
let updateObject = {
documentId: 'xxxxxxx',
resource: {
requests: [{
"insertText": {
"text": "John Doe",
"location": {
"index": offset,
},
},
}],
},
};
gapi.client.docs.documents.batchUpdate(updateObject).then(function(response) {
appendPre('response = ' + JSON.stringify(response));
}, function(response) {
appendPre('Error: ' + response.result.error.message);
});
}
My next step is to create an entire, complex document using the api. I am stunned by what appears to be the fact that I need to maintain locations into the documents, like this
new Location().setIndex(25)
I am informing myself of that opinion by reading this https://developers.google.com/docs/api/how-tos/move-text
The document I am trying to create is very dynamic and very complex, and handing the coding challenge to keeping track of index values to the api user, rather than the api designer, seems odd.
Is there an approach, or a higher level api, that allows me construct a document without this kind of house keeping?
Unfortunately, the short answer is no, there's no API that lets you bypass the index-tracking required of the base Google Docs API - at least when it comes to building tables.
I recently had to tackle this issue myself - a combination of template updating and document construction - and I basically ended up writing an intermediate API with helper functions to search for and insert by character indices.
For example, one trick I've been using for table creation is to first create a table of a specified size at a given index, and put some text in the first cell. Then I can search the document object for the tableCells element that contains that text, and work back from there to get the table start index.
Another trick is that if you know how many specific kinds of objects (like tables) you have in your document, you can parse through the full document object and keep track of table counts, and stop when you get to the one you want to update/delete (you can use this approach for creating too but the target text approach is easier, I find).
From there with some JSON parsing and trial-and-error, you can figure out the start index of each cell in a table, and write functions to programmatically find and create/replace/delete. If there's an easier way to do all this, I haven't found it. There is one Github repo with a Google Docs API wrapper specifically for tables, and it does appear to be active, although I found it after I wrote everything on my own and I haven't used it.)
Here's a bit of code to get you started:
def get_target_table(doc, target_txt):
""" Given a target string to be matched in the upper left column of a table
of a Google Docs JSON object, return JSON representing that table. """
body = doc["body"]["content"]
for element in body:
el_type = list(element.keys())[-1]
if el_type == "table":
header_txt = get_header_cell_text(element['table']).lower().strip()
if target_txt.lower() in header_txt:
return element
return None
def get_header_cell_text(table):
""" Given a table element in Google Docs API JSON, find the text of
the first cell in the first row, which should be a column header. """
return table['tableRows'][0]\
['tableCells'][0]\
['content'][0]\
['paragraph']['elements'][0]\
['textRun']['content']
Assuming you've already created a table with the target text in it: now, start by pulling the document JSON object from the API, and then use get_target_table() to find the chunk of JSON related to the table.
doc = build("docs", "v1", credentials=creds).documents().get(documentId=doc_id).execute()
table = get_target_table(doc, "my target")
From there you'll see the nested tableRows and tableCells objects, and the content inside each cell has a startIndex. Construct a matrix of table cell start indices, and then, for populating them, work backwards from the bottom right cell to the upper left, to avoid displacing your stored indices (as suggested in the docs and in one of the comments).
It's definitely a bit of a slog. And styling table cells is a whole 'nother beast, which is a dizzying maze of JSON options. The interactive JSON constructor tool on the Docs API site is useful to get the syntax write.
Hope this helps, good luck!
The answer I arrived at: You can create Docs without using their JSON schema.
https://developers.google.com/drive/api/v3/manage-uploads#node.js_1
So, create the document in your format of choice (HTML, DocX, MD (you'd use pandoc to convert MD to another format)), and then upload that.
I want to load data from many files. Each file is named with a date and I need to inject this date to each of the fetched Entries of my file.
I know I could do this with an foreach - loop before inserting the data into the collection, but I think there should be a better solution.
Content of one file
[{"price":"95,34","isin":"FR0000120073"},{"price":"113,475","isin":"CA13645T1003"}]
The Code I use to move the data into a collection.
$collection= collect(json_decode(File::get($file)));
I tried for example the "map" method, however I don't know how to pass an additional variable to the anonymous function.
The content of my collection should look like this:
[{"price":"95,34","isin":"FR0000120073","date":"2016-06-23"},{"price":"113,475","isin":"CA13645T1003","date":"2016-06-23"}]
Is there any simple solution using the collections or do I have to use a foreach-loop?
May be this will help
$collection = collect(json_decode(File::get($file)));
$collection = $collection->each(function ($item, $key) {
//First iteration of $item will be {"price":"95,34","isin":"FR0000120073"}
$item->date = "2016-06-23"; //Insert key, value pair to the collection
});
I want to define a new element <my-table> with can contain a number of
columns <mytable-col>
The usage should look like this:
<my-table id="mt1">
<mytable-col id="c1" title ="name" type="string"width="150"></mytable-col>
<mytable-col id="c2" title ="age" type="number" width="60"></mytable-col>
</my-table>
Is it possible to define an element with another (required) new "inner"
element?
How is it possible to access from the dart code of the outer the markup of
the inner elements instances.
If both the template of <my-table> and <mytable-col> contain markup, where
is the markup of the inner <mytable-col> inserted?
The way you wrote your markup the <mytable-col> elements are children of <my-table> which are added to the <content> node inside your <my-table> element.
You can access those child elements from the code of your <mytable-col> like
var listContent = ($['content'] as ContentElement).getDistributedNodes();
I'm not sure what you mean by
Is it possible to define an element with another (required) new "inner" element?
You can add code in your enteredView() method (after the super.enteredView(); call that verifies that the right child nodes are available in the <content> node and throws an exception if not.
where is the markup of the inner inserted?
The markup is inserted into it's elements shadowDOM.
The markup changes the appearance of your element but is normally not visible,
for example when your open view source from your browser (except when you enable the option to show shadowDOM in Chromium/Dartium or your browser doesn't support shadowDOM then you see how polyfills try to emulate shadowDOM).
You can compare it with tags like <video> where just adding this tag creates many elements (like play-, stop-, continue-, fast-forward- buttons and <span>s and <div>s for the layout that are added behind the scenes which are responsible for the layout and behavior of the <video> tag but are not visible in the markup.
with #Günter Zöchbauer ´s help I found the solution:
As he wrote you can get the inner HTML with:
var listContent = ($['content'] as ContentElement).getDistributedNodes();
Because this only finds Nodes with an id, you have do define the Element in the Component´s HTML with an id too.
<content id = content><span>inner HTML</span></content>