Sort Todo Lists

I am using a todo list as an actual list of things to do! Some of these things have due dates/times (I automatically add weekly tasks for example), and some of them do not (tasks that I want to keep track of, but may not get to for weeks or months).

Currently, adding a new item to the list will just add it to the bottom. This can obscure time-sensitive tasks with older but less important tasks, requiring me to scroll to review the whole list.

I would really like to be able to show the listed items in order from oldest to newest due date, with any items without a due date at the end (not sure order matters for those, maybe default sort by oldest creation date?). Other sorting methods would be nice to have too (like the creation date, alphabetical, and reverse order for all of them).

As best I can understand it, there are two ways to achieve this (which are not mutually exclusive).

  1. Augment the Todo card to have a sort order button in the UI, plus a configurable default option. This requires more logic in the card, but could be applied to any Todo entity and would allow for different cards to display any lists (or even the same list several times) in different orders very easily. This would not be reflected in tools outside of HA, however.
  2. Add a sort service call to the Todo entity type, and modify the actual list based on the desired order. This could be nice because it would persist and be reflected outside of HA, but I suspect this would be more challenging to implement consistently across the various integrations that provide Todo lists.

Thanks for your consideration.

Edit: as an imperfect workaround, I have developed a script blueprint to approximate the sorting behavior on the list itself). GitHub - mathmaniac43/Home-Assistant-Resources

While we are sorting, an option to sort alphabetically, for both checked and uncheck items would be appreciated too. My shopping list is now so long that it is hard to find items, and double and triple entries are getting in the list. If the list is sorted, it would be easier to see.

4 Likes

I’ve built a script that sorts a todo list each time a new item is added.
It keeps name, description and due date (with both formats).
It sorts items with due date first, and then items without due date alphabetically.

This is basic and additional improvements can be made but hopefully it will be useful for some people.

Known bug: if an item is added with the same name that an item that is also in the completed list, the completed item will be removed and duplicate items may be added in the todo list.

It could be simplified by using only one loop, but I kept both for readability.

alias: Sort todo list
mode: single
icon: mdi:sort-alphabetical-ascending
fields:
  todolist:
    name: Todo list
    required: true
    selector:
      entity: {}
    default: todo.shopping_list
sequence:
  - variables:
      todolist_entity: "{{ todolist }}"
  - service: todo.get_items
    target:
      entity_id: "{{ todolist_entity }}"
    response_variable: items
    data:
      status: needs_action
  - variables:
      items_with_due: >
        {{ items[todolist_entity]['items'] | selectattr('due', 'defined') |
        sort(attribute='due') | list }}
      items_without_due: >
        {{ items[todolist_entity]['items'] | rejectattr('due', 'defined') |
        sort(attribute='summary') | list }}
      sorted_items: |
        {{ items_with_due + items_without_due }}
  - repeat:
      count: "{{ items[todolist_entity]['items'] | length }}"
      sequence:
        - variables:
            item: "{{ sorted_items[repeat.index - 1] }}"
        - service: todo.remove_item
          continue_on_error: true
          target:
            entity_id: "{{ todolist_entity }}"
          data:
            item: "{{ items[todolist_entity]['items'][repeat.index - 1].summary }}"
  - repeat:
      count: "{{ sorted_items | length }}"
      sequence:
        - variables:
            item: "{{ sorted_items[repeat.index - 1] }}"
        - service: todo.add_item
          target:
            entity_id: "{{ todolist_entity }}"
          data: |
            {
              "item": "{{ item.summary }}",
              {% if item.description is defined and item.description != '' %}
              "description": "{{ item.description }}",
              {% endif %}
              {% if item.due is defined and item.due != '' %}
                {% if item.due | length == 10 %}
                  "due_date": "{{ item.due }}",
                {% else %}
                  "due_datetime": "{{ item.due }}",
                {% endif %}
              {% endif %}    
            }
  - delay:
      hours: 0
      minutes: 0
      seconds: 2
      milliseconds: 0
    enabled: false
trace:
  stored_traces: 20
2 Likes

Thanks, that is great! And very funny timing; I have been building and testing a blueprint this week that is very similar, but is for a script that can manually sort the list, vs. doing it when a change occurs. My intent was to post them on GitHub in a few days when I think it is fully working, perhaps I can include your automation there too?

Sure, you can. Indeed, the code could also be a script called from an automation.
Good news, I managed to handle all the properties (name, description and due time).
I’ve updated the code in my post.

I just realized that the completed items are sorted alphabetically by design…
I would have think that the items would be added by their date of completion. I guess the reason for that is that this completion date just does not exist.

Edit: I’ve also added the sorting with items with due dates first in my automation.

Haha interested to see how similar these are, hoping to have time to get out what I have later today or tomorrow.

In my case, I saw I have a bug when I add an item with the same name than another one already completed, it will delete the completed item and create duplicates.

It’s not possible to specify a status in todo.remove_item and it seems it will try to remove items in the completed list first.

I guess I have a crazy workaround for this. I’d need to save the list of completed items, them remove them all with todo.remove_completed_items, then do the sorting of the main list, then add back the completed items…

Edit: Nope, won’t work since todo.add_item does not support status… So even crazier, after all items are removed, add the completed items first, use todo.update_item on them to update their status, then re-add the uncompleted ones…

Interesting post on sorting. The only thing keeping me from using Local Todo lists is that new additions to a list get put at the bottom. The way I uses lists is that I want to see new items on top. Those are usually more recent and important, if not, I will drag previous entries up to the top. The problem is some items that are not so important never get done so the ToDo list can get very long and I don’t want to have to scroll down to the bottom and find a new entry to move to the top of a huge list. Not fast or easy to move items on such a long list when often just having the item appear on the top is all I need. Can you suggest how I could sort the list by default to have new entries appear on the top? And then allow manually dragging to prioritized them ? Any feedback would be appreciated. Thanks

Found a serious bug. When a item is on the todo list that has a space at the end, this script fails and delete all unchecked items! For example when adding "buy fruit " (without the quotes) all unchecked items in the list will disappear. If such an item was already in the list, I could see there was an error in the traces but I don’t remember what exactly, something that it could not find that item.

You probably ask why add a space at the end? I don’t know, but I had several of them in my list. Maybe due to some copy paste actions, who knows? After correcting these, I added one on purpose with a space, and suddenly my unchecked list had no items anymore.

Hello,
I’m sorry. I knew about this bug, but did not update the code here because I don’t have the right fix. In my case, the LLM adds spaces at the end of items from time to time. Even though I told it in the prompt to trim before adding, it happens.

The updated script will not drop items, but items with spacing at the end will appear as duplicated. This is the best I managed to do for now. Hopefully, someone will find a better way to handle this.

The code above is now based on the latest version of my script.

I am not sure about that. It is not the item with the space behind it, it is all the other unchecked items that have disappeared in my list.

Yes, I get it. What’s happening is that the script saves all the items, and try to delete them one by one. When it reaches the item with the space, it cannot find it and the script crashes, so the elements after are not added back.

Sorry for the long radio silence. I have been adding options and testing my solution for a few weeks, and am finally comfortable sharing it publicly. I just got it up on GitHub, and you can import the blueprint from there. I wrote it by hand (I write code for my job and wanted to understand how all the pieces work together), so I am pretty sure there are no spurious errors at this point. I primarily tested against a CalDAV-synced todo list, but early on tested against a local todo entity as well.

Thank you Nick! That’s a very flexible solution with this blueprint.

Your implementation suffers from the same bug (or “limitation”) than mine, it will fail with items that are not trimmed.

If you add "A " (with the trailing space) programmatically or in the edit form or the item, you won’t be able to target it when you want to remove it. The extra space is trimmed if the item is added from the list directly.

I don’t know how to work around that. When we list the items, we are unable to capture the extra spaces, so it’s not possible to target them to edit or remove them.

To make these scripts even uglier, we could attempt to add spaces in a loop if we catch the error until the item is removed…

At that point, I guess a PR to implement a native sorting method will be way more easier.

Hm, I understand what you are saying. I think my blueprint either already works around that, or could be adjusted pretty easily to? Since it captures all the data into variables and deletes all of the original entries to then re-populate them in order, it should be using the version that gets trimmed when getting the list of the items from the service call?

Edit: Never mind, I see exactly what you are saying. I wish there was a “delete all” service call.

Exactly, a delete all non completed items would help in this regard.

Any ideas on how to solve this sorting issue?