The following script example incorporates the properties and methods of the Outline, Item, and Editor classes to automate the manipulation of an OmniOutliner document based upon JSON (JavaScript Object Notation) data imported from a server.
Import, Parse, and Build
The provided script example is functionally comprised of two sections:
Parse the imported JSON data to determine the number and type of columns to be added to the existing OmniOutliner document, and then add the columns required to display the imported data.
Populate the document with the imported data by adding rows for each imported data object.
As shown in the data snapshot above, each example JSON object contains five data pairs. These pairs are represented in the OmniOutliner document as columns, and each data object in the imported data object becomes a row in the outline.
NOTE: If an object pair has a value that is a string, the value is checked to see if it is a date string and if true, a date column is added instead of text column.
Import JSON from URL Action
The following Omni Automation action presents an action form displaying a text field in which you may enter the http or https URL to the source JSON server or file. Optionally, select the checkbox if you want to use the example JSON content from a file hosted on this website.
urlStr = 'https://omni-automation.com/omnioutliner/people.json'
var url = URL.fromString(urlStr)
url.fetch(function(data){
objArray = JSON.parse(data.toString())
// add columns based upon contents of 1st object
var obj1 = objArray[0]
var objectKeys = Object.keys(obj1)
var tree = document.outline
var editor = document.editors[0]
// add columns as necessary
var addedColumns = new Array()
objectKeys.forEach(key => {
var dataType = typeof obj1[key]
switch(dataType){
case "string":
// check for date string
var strToCheck = obj1[key]
var str = strToCheck.replace(/\s/g, '')
var result = Date.parse(str)
if(result > 0){
console.log("Add date column")
var newColumn = tree.addColumn(
Column.Type.Date,
editor.afterColumn(),
function(column){
column.title = key
column.formatter = Formatter.Date.withStyle(Formatter.Date.Style.Short)
}
)
} else {
console.log("Add text column")
var newColumn = tree.addColumn(
Column.Type.Text,
editor.afterColumn(),
function(column){
column.title = key
}
)
}
addedColumns.push(newColumn)
break
case "number":
console.log("Add number column")
var newColumn = tree.addColumn(
Column.Type.Number,
editor.afterColumn(),
function(column){
column.title = key
column.formatter = Formatter.Decimal.plain
}
)
addedColumns.push(newColumn)
break
case "boolean":
console.log("Add checkbox column")
var newColumn = tree.addColumn(
Column.Type.Checkbox,
editor.afterColumn(),
function(column){
column.title = key
column.style.set(Style.Attribute.ParagraphAlignment, TextAlignment.Center)
}
)
addedColumns.push(newColumn)
}
})
// Populate the data
objArray.forEach(obj =>{
var newRow = rootItem.addChild()
objectKeys.forEach(function(key,index,array){
var cellValue = obj[key]
if (typeof cellValue === "boolean"){
cellValue = (cellValue === true)? State.Checked:State.Unchecked
} else if (addedColumns[index].type === Column.Type.Date){
cellValue = cellValue.replace(/\s/g, '')
cellValue = new Date(cellValue)
}
newRow.setValueForColumn(cellValue,addedColumns[index])
})
})
})
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.json-import-from-url",
"version": "1.0",
"description": "This action will import the JSON data from the provided http or https URL, parse the data, and add the columns and rows necessary to express the data.",
"label": "Import JSON from URL",
"shortLabel": "JSON Import"
}*/
(() => {
var action = new PlugIn.Action(function(selection, sender){
// action code
// selection options: columns, document, editor, items, nodes, outline, styles
// CREATE FORM FOR GATHERING USER INPUT
var inputForm = new Form()
// CREATE TEXT FIELD
var textField = new Form.Field.String(
"textInput",
null,
null
)
// CREATE CHECKBOX
var checkSwitchField = new Form.Field.Checkbox(
"checkboxSwitch",
"Import JSON from demo file",
false
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(textField)
inputForm.addField(checkSwitchField)
// PRESENT THE FORM TO THE USER
formPrompt = "Enter the URL to the JSON data server or file:"
formPromise = inputForm.show(formPrompt,"Continue")
// VALIDATE THE USER INPUT
inputForm.validate = function(formObject){
var checkboxValue = formObject.values["checkboxSwitch"]
if (checkboxValue){return true}
var textValue = formObject.values["textInput"]
var textStatus = (textValue && textValue.length > 0) ? true:false
if (textStatus){
return (textValue.startsWith("http://", 0) || textValue.startsWith("https://", 0) && textValue.length > 10) ? true : false
}
return false
}
// PROCESSING USING THE DATA EXTRACTED FROM THE FORM
formPromise.then(function(formObject){
var checkboxValue = formObject.values["checkboxSwitch"]
if(checkboxValue){
var urlStr = "https://omni-automation.com/omnioutliner/people.json"
} else {
var urlStr = formObject.values["textInput"]
}
var url = URL.fromString(urlStr)
url.fetch(function(data){
objArray = JSON.parse(data.toString())
// add columns based upon contents of 1st object
var obj1 = objArray[0]
var objectKeys = Object.keys(obj1)
var tree = document.outline
var editor = document.editors[0]
// add columns as necessary
var addedColumns = new Array()
objectKeys.forEach(key => {
var dataType = typeof obj1[key]
switch(dataType){
case "string":
// check for date string
var strToCheck = obj1[key]
var str = strToCheck.replace(/\s/g, '')
var result = Date.parse(str)
if(result > 0){
console.log("Add date column")
var newColumn = tree.addColumn(
Column.Type.Date,
editor.afterColumn(),
function(column){
column.title = key
column.formatter = Formatter.Date.withStyle(Formatter.Date.Style.Short)
}
)
} else {
console.log("Add text column")
var newColumn = tree.addColumn(
Column.Type.Text,
editor.afterColumn(),
function(column){
column.title = key
}
)
}
addedColumns.push(newColumn)
break
case "number":
console.log("Add number column")
var newColumn = tree.addColumn(
Column.Type.Number,
editor.afterColumn(),
function(column){
column.title = key
column.formatter = Formatter.Decimal.plain
}
)
addedColumns.push(newColumn)
break
case "boolean":
console.log("Add checkbox column")
var newColumn = tree.addColumn(
Column.Type.Checkbox,
editor.afterColumn(),
function(column){
column.title = key
column.style.set(Style.Attribute.ParagraphAlignment, TextAlignment.Center)
}
)
addedColumns.push(newColumn)
}
})
// Populate the data
objArray.forEach(obj =>{
var newRow = rootItem.addChild()
objectKeys.forEach(function(key,index,array){
var cellValue = obj[key]
if (typeof cellValue === "boolean"){
cellValue = (cellValue === true)? State.Checked:State.Unchecked
} else if (addedColumns[index].type === Column.Type.Date){
cellValue = cellValue.replace(/\s/g, '')
cellValue = new Date(cellValue)
}
newRow.setValueForColumn(cellValue,addedColumns[index])
})
})
})
})
// PROMISE FUNCTION CALLED UPON FORM CANCELLATION
formPromise.catch(function(err){
console.log("form cancelled", err.message)
})
});
action.validate = function(selection, sender){
// validation code
// selection options: columns, document, editor, items, nodes, outline, styles
return true
};
return action;
})();
"description": "This action will import the JSON data from the provided http or https URL, parse the data, and add the columns and rows necessary to express the data.",
08
"label": "Import JSON from URL",
09
"shortLabel": "JSON Import"
10
}*/
11
(() => {
12
var action = newPlugIn.Action(function(selection, sender){
In the previous script, new columns are created for each data pair of the imported JSON, leaving the document’s default Topic column untouched. The following script enables you to select one of the created text columns and merge its content into the default Topic column, either replacing existing content or appending to it. After merging, the selected column will be removed from the outline.
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.merge-column-to-topic",
"version": "1.3",
"description": "The action will merge the contents of the selected column to the Topic column. If no column is selected, a list of column titles is presented.",
"label": "Merge into Topic Column",
"shortLabel": "Merge to Topic"
}*/
(() => {
var action = new PlugIn.Action(function(selection, sender){
var topicColumn = document.outline.outlineColumn
var noteColumn = document.outline.noteColumn
var textColumns = new Array()
columns.forEach(column => {
if (
column.type === Column.Type.Text &&
column != topicColumn &&
column != noteColumn
){textColumns.push(column)}
})
if(textColumns.length === 0){
new Alert("MISSING RESOURCE","No text columns to merge.").show()
console.error("MISSING RESOURCE: ","No text columns to merge.")
return "No text columns to merge."
}
var chooseColumnFlag = false
if (
selection.columns === undefined ||
selection.columns.length === 0 ||
selection.columns.length > 1 ||
selection.columns[0] === topicColumn
){
chooseColumnFlag = true
var textColumnsTitles = textColumns.map(column => {return column.title})
var textColumnsTitlesIndexes = new Array()
textColumnsTitles.forEach((item,index) => {
textColumnsTitlesIndexes.push(index)
})
var columnTitlesMenu = new Form.Field.Option(
"columnTitles",
"Column",
textColumnsTitlesIndexes,
textColumnsTitles,
0
)
}
// CONSTRUCT THE FORM
var form = new Form()
// CREATE FORM ELEMENTS
var columnTitleCheckbox = new Form.Field.Checkbox(
"shouldUseColumnTitle",
"Use column title for Topic column title",
false
)
var replaceContentCheckbox = new Form.Field.Checkbox(
"shouldReplaceContent",
"Replace existing Topic content (To append content, leave unchecked)",
false
)
var seperatorStringMenu = new Form.Field.Option(
"appendingString",
null,
[0,1],
["Append with Space","Append with New Line"],
0
)
// ADD ELEMENTS TO FORM
if(chooseColumnFlag){form.addField(columnTitlesMenu)}
form.addField(columnTitleCheckbox)
form.addField(replaceContentCheckbox)
form.addField(seperatorStringMenu)
// SHOW THE FORM AND RETURN JAVASCRIPT PROMISE
formPrompt = "Merge Options:"
buttonTitle = "Continue"
formPromise = form.show(formPrompt, buttonTitle)
// VALIDATE FORM CONTENT
form.validate = function(form){
// RETRIEVE CHOSEN VAUES
var shouldReplaceContent = form.values['shouldReplaceContent']
if(chooseColumnFlag === false){
var fieldsLength = 3
var targetFieldIndex = 2
} else {
var fieldsLength = 4
var targetFieldIndex = 3
}
if(shouldReplaceContent && form.fields.length === fieldsLength){
form.removeField(form.fields[targetFieldIndex])
} else if (!shouldReplaceContent && form.fields.length === fieldsLength - 1) {
var seperatorStringMenu = new Form.Field.Option(
"appendingString",
null,
[0,1],
["Append with Space","Append with New Line"],
0
)
form.addField(seperatorStringMenu)
}
return true
}
// PROCESS THE FORM RESULTS
formPromise.then(function(form){
// RETRIEVE CHOSEN VAUES
var selectedColumn
if(chooseColumnFlag === false){
var selectedColumn = selection.columns[0]
} else {
var chosenColumnTitleIndex = form.values['columnTitles']
var chosenColumnTitle = textColumnsTitles[chosenColumnTitleIndex]
var selectedColumn = columns.byTitle(chosenColumnTitle)
}
var selectedColumnTitle = selectedColumn.title
var shouldUseColumnTitle = form.values['shouldUseColumnTitle']
var shouldReplaceContent = form.values['shouldReplaceContent']
if(!shouldReplaceContent){
var appendingString = form.values['appendingString']
var seperator = (appendingString === 0) ? ' ' : "\n"
}
var topicValue
rootItem.descendents.forEach(item => {
var cellValue = item.valueForColumn(selectedColumn)
if (cellValue === null){cellValue = ''}
if (shouldReplaceContent){
item.topic = cellValue.string
} else {
topicValue = (item.topic === null) ? '': item.topic
if(topicValue === ''){
item.topic = cellValue.string
} else {
item.topic = topicValue + seperator + cellValue.string
}
}
})
if(shouldUseColumnTitle){topicColumn.title = selectedColumnTitle}
selectedColumn.remove()
})
// PROCESS FORM CANCELLATION
formPromise.catch(function(error){
console.log("form cancelled", error)
})
});
action.validate = function(selection, sender){
// validation code
return true
};
return action;
})();
"description": "The action will merge the contents of the selected column to the Topic column. If no column is selected, a list of column titles is presented.",
08
"label": "Merge into Topic Column",
09
"shortLabel": "Merge to Topic"
10
}*/
11
(() => {
12
var action = newPlugIn.Action(function(selection, sender){
13
var topicColumn = document.outline.outlineColumn
14
var noteColumn = document.outline.noteColumn
15
16
var textColumns = new Array()
17
columns.forEach(column => {
18
if (
19
column.type === Column.Type.Text &&
20
column != topicColumn &&
21
column != noteColumn
22
){textColumns.push(column)}
23
})
24
if(textColumns.length === 0){
25
newAlert("MISSING RESOURCE","No text columns to merge.").show()
26
console.error("MISSING RESOURCE: ","No text columns to merge.")
27
return"No text columns to merge."
28
}
29
30
var chooseColumnFlag = false
31
if (
32
selection.columns === undefined ||
33
selection.columns.length === 0 ||
34
selection.columns.length > 1 ||
35
selection.columns[0] === topicColumn
36
){
37
chooseColumnFlag = true
38
var textColumnsTitles = textColumns.map(column => {return column.title})
39
var textColumnsTitlesIndexes = new Array()
40
textColumnsTitles.forEach((item,index) => {
41
textColumnsTitlesIndexes.push(index)
42
})
43
var columnTitlesMenu = new Form.Field.Option(
44
"columnTitles",
45
"Column",
46
textColumnsTitlesIndexes,
47
textColumnsTitles,
48
0
49
)
50
}
51
52
// CONSTRUCT THE FORM
53
var form = new Form()
54
55
// CREATE FORM ELEMENTS
56
var columnTitleCheckbox = new Form.Field.Checkbox(
57
"shouldUseColumnTitle",
58
"Use column title for Topic column title",
59
false
60
)
61
62
var replaceContentCheckbox = new Form.Field.Checkbox(