## A checkbox list in org-mode with one value

| categories: | tags: | View Comments

A while ago I had a need for a checklist in org-mode where only one value would be checked at a time. Like a radio button in a browser form. That isn't as far as I know a feature yet, but it was not hard to achieve thanks to the org-element api. My simple idea is to make a function that will be added to the org-checkbox-statistics-hook. The function will uncheck all the boxes, and recheck the one you just clicked with a hybrid of manipulating the cursor and inserting characters with org-element code. We will use an attribute on the checklist to indicate it is a "radio" list. This seems like a feature that might already exist, but I couldn't find it.

Here is the code we run. First, we make sure we are on a plain list that has an attr_org property of ":radio", that way this won't apply to all lists, just the radio lists. Then, we loop through each element in the structure, and if it is checked, we replace [X] with [ ]. Then, we reinsert the X and delete a space, which puts [X] where we originally clicked, or used C-c C-c. Finally, we add it to the hook, so it only gets run when a checkbox is changed via clicking with org-mouse, or C-c C-c. Of course, this doesn't work if you type X in the box.

(require 'dash)
(defun check-hook-fn ()
(when (-contains? (org-element-property
:attr_org
(org-element-property :parent (org-element-context)))
(save-excursion
(loop for el in (org-element-property :structure (org-element-context))
do
(goto-char (car el))
(when (re-search-forward "\$X\$" (line-end-position) t)
(replace-match "[ ]"))))
(forward-char)
(insert "X")
(delete-char 1)))


 check-hook-fn

Here is a regular checklist. You can check as many as you want.

• [X] one
• [X] two
• [ ] three

Now, here is a radio checklist. Only one item at a time can be checked. Nice!

• [ ] a
• [ ] b
• [X] c

It is worth noting here that if we put a name on the list, it becomes an addressable data source. First we need this convenient function to get the data associated with a named list.

(defun org-get-plain-list (name)
"Get the org-element representation of a plain-list with NAME."
(catch 'found
(org-element-map
(org-element-parse-buffer)
'plain-list
(lambda (plain-list)
(when
(string= name (org-element-property :name plain-list))
(throw 'found plain-list))))))

org-get-plain-list


Now, let's use that to get the value of the checked item in the "test" list. We define the item as everything after the [X] and get it from a regular expression match.

(defun get-radio-list-value (list-name)
"Return the value of the checked item in a radio list."
(save-excursion
(loop for el in (org-element-property
:structure
(org-get-plain-list list-name))
if (string= (nth 4 el) "[X]")
return (progn
(let ((item (buffer-substring (car el) (car (last el)))))
(string-match "\$X\$\$$.*\$$\$" item)
(match-string 1 item))))))


c