Too early to be reading column cell data in OO5 omniJS?


#1

Is the omniJS interface to non-topic cells complete at this stage ?

I am finding it a little hard to read the Object data returned, and I wonder whether it would be helpful to add a cell.valueString method, returning a default stringification of the value, for the non topic cells ?

It looks as if we need to branch on conditionals like if (col.type === Column.Type.Text) before knowing how to read the contents of a value returned by item.valueForColumn(col), but even then I find that I am failing so far, to read the data corrrectly.

For example, I had assumed that values returned by item.valueForColumn(col) in columns which return ‘true’ for (col.type === Column.Type.Text) would be instances of the Text class, but I find that they are returning undefined for the Text.string property which I see in the documentation.

Perhaps a snippet showing best practice in reading data out of non-topic cells would be helpful ?

Or perhaps that interface is not quite ready yet ?

So far I am getting only as far as reading values which have a default string version:

    {
      "text": "Candide, ou l’Optimisme",
      "nest": [],
      "Title": {},
      "Author": {},
      "Year": 1759,
      "Origin": {},
      "Owned": {},
      "Read": {},
      "Started": "2013-01-14T08:00:00.000Z",
      "Finished": "2013-01-21T08:00:00.000Z",
      "My Rating": {}
    }

by using code like:

// jsoOutline :: OO.item -> Node {text:String, nest:[Node]}
var jsoOutline = item =>
    Object.assign({
        text: item.topic,
        nest: item.children.map(jsoOutline)
    }, (() => {
        let dct = {};
        cols.forEach(col => {
            dct[col.title] = item.valueForColumn(col);
        });
        return dct;
    })());

but I am not managing to read even Text values with code like:

var cellString = (col, item) => {
    const type = col.type;

    return (type === Column.Type.Text) ? item.valueForColumn(col).string : 'not Text';
}

( .string is reported to be undefined, though it appears to be listed in the documentation of Text instances)


#2

For reference, a fuller example of how I have got with trying to read cells in columns of types like (List/enumeration), and (Checkbox/state).

(() => {
    'use strict';

    // show :: a -> String
    const show = x => JSON.stringify(x, null, 2);

    // log :: a -> IO ()
    const log = x => console.log(show(x));

    const
        outline = document.outline,
        cols = outline.columns.filter(col => col.title.length > 0);

    // cellString :: OO.Column -> OO.Item -> String
    const cellString = (col, item) => {
        const
            v = item.valueForColumn(col),
            typeName = typeString(col.type),
            strGap = typeName + ' ???';

        return v !== null ? (
            typeName === 'rich-text' ? v.string :
            typeName === 'number' ? v :
            typeName === 'enumeration' ? strGap :
            typeName === 'state' ? strGap :
            typeName === 'date' ? v : strGap
        ) : '';
    };

    // typeString :: Column.Type -> String
    const typeString = type => type.identifier.split('.')
        .slice(-1)[0];

    // jsoOutline :: OO.item -> Node {text:String, nest:[Node]}
    const jsoOutline = item =>
        Object.assign({
            text: item.topic,
            nest: item.children.map(jsoOutline)
        }, cols.reduce(
            (a, col) => (a[col.title] = cellString(col, item), a), {}
        ));

    log(outline.rootItem.children.map(jsoOutline));
})();

UPDATE

Leading, with the Book List template, to:

// // TypeError: null is not an object (evaluating 'v.string') undefined:20

In the case of Column.Type.Text this arose because the rootItem, though modelled as an outline item, does, of course need distinct handling, which I had failed to give it. The code above now works (for Text columns, other column types still need special handling, in the absence of a general .value() method like the one which eases reading of cells in the JXA interface.

Obtaining strings for other types of cell data, including enumerations and checkboxes, still eludes me in omniJS, though it is already quite painless in JXA :-)

  {
    "text": "Candide, ou l’Optimisme",
    "nest": [],
    "Title": "Candide, ou l’Optimisme",
    "Author": "Voltaire",
    "Year": 1759,
    "Origin": "enumeration ???",
    "Owned": "state ???",
    "Read": "state ???",
    "Started": "2013-01-14T08:00:00.000Z",
    "Finished": "2013-01-21T08:00:00.000Z",
    "My Rating": "enumeration ???"
  }

#3

And for comparison with JXA (code below), in which the the Cell.value() method already allows us to get straight to a useful string value without special handling of cell types:

for example:

 {
    "text": "Candide, ou l’Optimisme",
    "nest": [],
    "Title": "Candide, ou l’Optimisme",
    "Author": "Voltaire",
    "Year": 1759,
    "Origin": "France",
    "Owned": "checked",
    "Read": "checked",
    "Started": "2013-01-14T08:00:00.000Z",
    "Finished": "2013-01-21T08:00:00.000Z",
    "My Rating": "★★★★★"
  }

which is an example of the same dictionary as the earlier example, returned from the ‘Book List’ sample OO5 outline, this time by JXA, with its Cell.value() method:

(() => {
    'use strict';

    // GENERIC FUNCTIONS ------------------------------------------------------

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) => [].concat.apply([], xs.map(f));

    // curry :: ((a, b) -> c) -> a -> b -> c
    const curry = f => a => b => f(a, b);

    // show :: a -> String
    const show = x => JSON.stringify(x, null, 2);


    // OMNIOUTLINER 5 JXA FUNCTION --------------------------------------------

    // jsoOutline :: [String] -> OO.Row -> Node {text:String, nest:[Node]}
    const jsoOutline = curry((colNames, row) =>
        Object.assign({
            text: row.topic(),
            nest: row.hasSubTopics ? row.children()
                .map(jsoOutline(colNames)) : []
        }, colNames.reduce((a, k) => Object.assign(a, {
            [k]: row.cells.byName(k)
                .value() // Cell.value() returns simple value for any cell type
        }), {})));


    // JXA: READING A MULTI-COLUMN OUTLINE LIKE THE 'BOOK LIST' SAMPLE --------
    const
        ds = Application('OmniOutliner')
        .documents,
        d = ds.length > 0 ? ds.at(0) : undefined;

    if (d) {
        // colTitles :: [String]
        const colTitles = concatMap(
            x => x.title() !== '' ? [x.title()] : [],
            d.columns()
        );

        // -> JSON
        return show(
            d.children()
            .map(jsoOutline(colTitles))
        );
    }
})();

#4

The OmniJS interface for interacting with cell values is minimally usable at this point, with support for Text being the most complete (and the simple values like numbers and dates, of course). Enumeration and Checkbox values need a bit more work currently.

It’s possibly worth nothing that the topic column isn’t guaranteed to be Text, so checking its column type is a good idea.


#5

Thanks Tim.

The parts that are already in place are generally much easier and more approachable than their JXA counterparts.

A very good and interesting interface, on both Omnigraffle and OmniOutliner.