Tabular Array of Arguments: Nomenclature and Best Practices?


(Matthew G. Saroff) #1

I am trying to impliment an addon that works in the context menu.

An abbreviated set the menu would look like this:

BBCWX (top level menu of the addon)
    |
    _____bbCode (This sub menu covers bbCode tags)
           |   
           ____________Clipboard (Operations involving the contents of the clipboard)
                           |
                           __________Paste as Quote (Requests an author, but ignore that)
                           |
                           __________Paste as Code
                           |
                           __________Paste as unordered list

I can assign this to a table of values for the different menu options, with the a tabular object, where we could be calling out a unique MenuID, a Title, a Parent Menu, and code to be executed by some other function when the menu item is clicked.

Something like this:

var defaultMenu = [
{MenuID: "bbcwbx.bbcode", title: browser.i18n.getMessage(bbcwbx.bbcode), Parent: "", ParseCode: ""
     {MenuID: "bbcwbx.bbcode.clp", title: browser.i18n.getMessage(bbcwbx.bbcode.clp), Parent: "bbcwbx.bbcode", ParseCode: ""
     {MenuID: "bbcwbx.bbcode.clp.quote", title: browser.i18n.getMessage(bbcwbx.bbcode.clp.quote), Parent: "bbcwbx.bbcode.clp", ParseCode: "[quote{{popup}}"Please insert author name or leave empty:"}}]{{clipboard}}[/quote]"
     {MenuID: "bbcwbx.bbcode.clp.code", title: browser.i18n.getMessage(bbcwbx.bbcode.clp.code), Parent: "bbcwbx.bbcode.clp", ParseCode: "[code]{{clipboard}}[/code]"
     {MenuID: "bbcwbx.bbcode.clp.list", title: browser.i18n.getMessage(bbcwbx.bbcode.clp.list), Parent: "bbcwbx.bbcode.clp", ParseCode: "{{list{{clipboard}},"bbCode"}}"
]

This is a relatively clear notation, and it also provides a framework into which user defined tags can be appended to the end relatively easily.

I have seen an example of this out there, but I am having problems googling examples, because I am unclear as to what is is ordinarily called.

Is there anything fundamentally flawed with this approach?

The data table in question could end up with more than 200 rows depending on how many custom tags the user defines, so I am concerned about this potentially bogging down the browser.


(Niklas Gollenstede) #2

The size if just the instantiated object literal itself won’t be a problem. Pretty much if you can write it by hand, the browser can easily handle it.
What can be problematic are the operations you perform based on those objects. If you keep cloning the objects, adding/removing them as menus, … unnecessarily, that could be expensive.
But I’d suggest that you just do it first and optimize when you actually encounter performance issues. There is a learning curve to programming. I think you are quite at the beginning of it (at least with JavaScript). If you keep at it, you will probably want to rewrite everything you did so far in a year or two. but that is ok, you can’t get everything right immediately.

A few more notes:

  • your objects aren’t closed. I suspect you somehow forgot a }, at the end of each line.
  • always indent properly. It helps you and others to read your code. Find some indention rules that make sense for yourself, then execute them. With a decent text editor, it should happen almost automatically.
  • it is a general convention in JavaScript to only capitalize class/function constructors and some important “singleton” objects. Other properties /variable names start with a small letter.
  • I think it makes more sense here to explicitly state the children relation instead of parent. This also allows for proper indention, and if you need the parent relation, you can add it programmatically later.

So I would use this as my base object structure:

const defaultMenu = { id: 'bbcwbx.bbCode', children: {
    clp: { children: {
    	quote: { code: `[quote{{popup}}"Please insert author name or leave empty:"}}]{{clipboard}}[/quote]`, },
    	code: { code: `[code]{{clipboard}}[/code]`, },
    	list: { code: `{{list{{clipboard}},"bbCode"}}`, },
    }, },
}, };

const contextsts = [ /* ... */ ];

async function addMenu(parentId, { id, title, command, children, }) {
	id = parentId ? parentId +'.'+ id : id;
	return Promise.all([
		new Promise((yay, meh) => browser.menus.create({
			id, parentId, contexts,
			onclick: command ? () => {
				// do something with `command`
			} : undefined,
			title: title || browser.i18n.getMessage(id),
		}, () => browser.runtime.lastError ? meh(browser.runtime.lastError) : yay()),
		...Object.entries(children || { }).map(([ key, child, ]) => addMenu(id, { id: key, ...child, })),
	])
}

(await addMenu(undefined, defaultMenu));
// all menus added

As you see, the initial object structure is a lot smaller (and therefore easier to read and maintain), but just as “powerful”/informative!

Site notes: Don’t use variable names like yay or meh. Only childish people do that. But do use syntactic sugar, even if it is known to be slow. Encourage the browser engeneers to speed things up by using them! (At least where performance is not critical, as here.)


(Matthew G. Saroff) #3

Thank you.

This code is readable enough for me, and I’m pretty much hopeless at Javascript. (It’s why I comment the heck out of my code)

I am very impressed at the elegance.

How would you incorporated user-created tags with this format?

Also: What is syntactic sugar?


(Niklas Gollenstede) #4

Since the defaultMenu structure is (currently) JSON, you could do this:

let userMenus; try { userMenus = JSON.parse(userMenusJson); }
catch (error) { /* not valid JSON */ return; }
// TODO: should verify the structure of `userMenus`!
Object.assign(defaultMenu.children, userMenus);

before calling addMenu. The user could thus specify custom submenus as JSON. I modified the previous code a bit to allow the userJsonMenus to define titles.


Syntactic sugar is a name for programing language features that don’t really enable you to do things you couldn’t before, but just let you write the same stuff more compact/readable/beautiful. It adds visual “sugar” do your code (syntax). In statically compiled languages that usually comes at zero run time penalty (sometimes it’s even faster), but in dynamic languages some shorter syntax is considerably slower (because it is designed in to work in many different situations, or isn’t optimized yet). Examples:

  • id = 42; obj = { id, } instead of id = 42; obj = { id: id, }` is just as fast
  • arr = [ 42, 23, ]; [ foo, bar, ] = arr; is slower than the (roughly) equivalent arr = [ 42, 23, ]; foo = arr[0]; bar = arr[1];, because the former syntax needs to handle other iterates as well

But don’t worry about performance yet. use what comes natural and looks good!


(Mittineague) #5

I Agree :+1: :+1:
I always lean towards readability. For example:
let a = "foo";
might save a few bytes than
let test_string = "foo";
but to me those bytes aren’t worth the trouble I’ll have trying to figure out what “a” is later.

Although performance inefficiencies can accumulate, I have found most are insignificant. An exception being back before I knew enough to avoid globals whenever possible and had large arrays not being Garbage Collected causing memory problems.

In any case, I wouldn’t worry about optimizing code yet, there’s more than enough to learn and keep up with, and you’ll know when you have a problem with code performance.


(Matthew G. Saroff) #6

Agreed, 95% of code optimization (not counting things like compilers, operating systems, etc.) achieves minimal impact.

Then again, there is stuff that I am looking at WHILE I AM CODING IT that makes me impugn my own parentage.

My 3 minute old code may be uglier than my 35 year old hair. (Profile pic)

Also, is there a trick to not leaving dangling parenthesis, brackets, braces, etc?


(Mittineague) #7

I pay strict attention to indentation lining up the “starts” with the “ends”. But it is possible to use a text editor/ IDE set up to automatically add the “ends” for you every time you add a “start” if you find that easier for you.


(Matthew G. Saroff) #8

I just discovered Lint, particularly an online Linters.

It is an epiphany.

This Linter highlights bracket/brace/parenthesis pairs.


(Matthew G. Saroff) #9

I just wanted to note that I have most of the menu structure laid out in an excel spread sheet, including the language packs (which I am not posting here, too stuff), because this allows me to reuse (with permission) the language packs from bbCodeXtra.

If I export to a CSV, I get this:

Navigation Menu,Menu ID,Argument,Parent,
TRUE,bbcwbx.bbcode,[b]{{selection}}[/b],,
TRUE,bbcwbx.bbcode.clp,[b]{{selection}}[/b],bbcwbx.bbcode,
FALSE,bbcwbx.bbcode.clp.quote,"[quote{{popup,""Please insert author name or leave empty:""}}]{{clipboard}}[/quote]",bbcwbx.bbcode.clp,
FALSE,bbcwbx.bbcode.clp.code,[code]{{clipboard}}[/quote],bbcwbx.bbcode.clp,
FALSE,bbcwbx.bbcode.clp.list,"{{createList,{{clipboard}},""bbcode""}}",bbcwbx.bbcode.clp,

I can convert this to JSON online, and I get:

[
  {
    "Navigation Menu": "TRUE",
    "Menu ID": "bbcwbx.bbcode",
    "Argument": "[b]{{selection}}[/b]",
    "Parent": "",
    "": ""
  },
  {
    "Navigation Menu": "TRUE",
    "Menu ID": "bbcwbx.bbcode.clp",
    "Argument": "[b]{{selection}}[/b]",
    "Parent": "bbcwbx.bbcode",
    "": ""
  },
  {
    "Navigation Menu": "FALSE",
    "Menu ID": "bbcwbx.bbcode.clp.quote",
    "Argument": "[quote{{popup,\"Please insert author name or leave empty:\"}}]{{clipboard}}[/quote]",
    "Parent": "bbcwbx.bbcode.clp",
    "": ""
  },
  {
    "Navigation Menu": "FALSE",
    "Menu ID": "bbcwbx.bbcode.clp.code",
    "Argument": "[code]{{clipboard}}[/quote]",
    "Parent": "bbcwbx.bbcode.clp",
    "": ""
  },
  {
    "Navigation Menu": "FALSE",
    "Menu ID": "bbcwbx.bbcode.clp.list",
    "Argument": "{{createList,{{clipboard}},\"bbcode\"}}",
    "Parent": "bbcwbx.bbcode.clp",
    "": ""
  }
]

Converting it to XML looks something like this:

<document>
 <row>
  <Col0>Navigation Menu</Col0 >
  <Col1>Menu ID</Col1 >
  <Col2>Argument</Col2 >
  <Col3>Parent</Col3 >
  <Col4></Col4 >
  <Col5></Col5 >
 </row>
 <row>
  <Col0>TRUE</Col0 >
  <Col1>bbcwbx.bbcode</Col1 >
  <Col2>[b]&#123;&#123;selection&#125;&#125;[/b]</Col2 >
  <Col3></Col3 >
  <Col4></Col4 >
  <Col5></Col5 >
 </row>
 <row>
  <Col0>TRUE</Col0 >
  <Col1>bbcwbx.bbcode.clp</Col1 >
  <Col2>[b]&#123;&#123;selection&#125;&#125;[/b]</Col2 >
  <Col3>bbcwbx.bbcode</Col3 >
  <Col4></Col4 >
  <Col5></Col5 >
 </row>
 <row>
  <Col0>FALSE</Col0 >
  <Col1>bbcwbx.bbcode.clp.quote</Col1 >
  <Col2>[quote&#123;&#123;popup,&quot;Please insert author name or leave empty:&quot;&#125;&#125;]&#123;&#123;clipboard&#125;&#125;[/quote]</Col2 >
  <Col3>bbcwbx.bbcode.clp</Col3 >
  <Col4></Col4 >
  <Col5></Col5 >
 </row>
 <row>
  <Col0>FALSE</Col0 >
  <Col1>bbcwbx.bbcode.clp.code</Col1 >
  <Col2>[code]&#123;&#123;clipboard&#125;&#125;[/quote]</Col2 >
  <Col3>bbcwbx.bbcode.clp</Col3 >
  <Col4></Col4 >
  <Col5></Col5 >
 </row>
 <row>
  <Col0>FALSE</Col0 >
  <Col1>bbcwbx.bbcode.clp.list</Col1 >
  <Col2>&#123;&#123;createList,&#123;&#123;clipboard&#125;&#125;,&quot;bbcode&quot;&#125;&#125;</Col2 >
  <Col3>bbcwbx.bbcode.clp</Col3 >
  <Col4></Col4 >
  <Col5></Col5 >
 </row>
</document>

I was thinking that using parent might be simpler than using child because doing a many to one mapping (declaring parent) would be easier than a one to many declaration (declaring child).

If I do this, should I include in the main background script, or should it should be in an outside file (I favor the later)?


(Niklas Gollenstede) #10

I really don’t think an Excel sheet is a good format to manage this. CSV is just an incredibly stupid “format”, and I wouldn’t see the data structure you have here as a table. IMHO its a tree(isch) thing that should also be written down as such.

But of cause you can (and I think you should) keep it in a separate file. And you can also export from your existing tables (once) and work from there. Even though if your actual sheet file format isn’t CSV already, I’d very much try to avoid CSV as an intermediate format and convert directly to JSON if possible. When refactoring something like this, a text editor with regexp, find/replace and multiple cursors can really speed things up.


(Matthew G. Saroff) #11

The Excel spreadsheet is purely for development.

The idea is that I would read the default menus in JSON format.

Excel, with the text manipulation tools that I know (and hate) is purely a place to keep everything organized for development. (I am re-reusing the language customizations from bbCodeXtra, and so its tabular format makes it a lot easier to keep that straight, and then to generate the requisite files).

The spreadsheet/CSV is basically a reference document, and will not be a part of the extension, though I’ll probably include the CSV file, with the I18n data, in the docs.