Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Reminders: List Index Out of range
-
I get a list index bug at line 10, not quite sure why.
# coding: utf-8 import dialogs import reminders import ui v = ui.load_view('reminders') reminders_table = v['reminders'] def picked(sender): item = sender.items[sender.selected_row] r = item['reminder'] r.completed = True r.save() del sender.items[sender.selected_row] def grabbed(): global todo_items, completed_items todo = reminders.get_reminders(completed=False) todo_items = [{'title': r.title, 'reminder': r} for r in todo] done = reminders.get_reminders(completed=True) completed_items = [{'title': r.title, 'reminder': r} for r in done] reminders_table.data_source = ui.ListDataSource(items=todo_items) reminders_table.data_source.action = picked reminders_table.reload() def button_action(sender): if segment.selected_index == 0: reminders_table.data_source = ui.ListDataSource(items=todo_items) reminders_table.data_source.action = picked reminders_table.reload() elif segment.selected_index == 1: reminders_table.data_source = ui.ListDataSource(items=completed_items) reminders_table.data_source.action = picked reminders_table.reload() @ui.in_background def but_action(sender): fields = [{'key' : 'name', 'type' : 'text', 'value' : 'Name your reminder'},] result=dialogs.form_dialog(title='Create a Reminder', fields=fields) r = reminders.Reminder() r.title = result['name'] r.save() segment.selected_index = 0 grabbed() segment = v['segmentedcontrol1'] segment.action = button_action reminders_table.data_source.action = picked create_button = ui.ButtonItem() create_button.image = ui.Image.named('ionicons-ios7-plus-empty-32') create_button.action = but_action grabbed() v.right_button_items = [create_button] v.present('sheet')
-
Where can we find
reminders.pyui
? -
[{"class":"View","attributes":{"name":"Reminders","background_color":"RGBA(1.000000,1.000000,1.000000,1.000000)","tint_color":"RGBA(0.000000,0.478000,1.000000,1.000000)","enabled":true,"border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","flex":""},"frame":"{{0, 0}, {540, 575}}","nodes":[{"class":"SegmentedControl","attributes":{"name":"segmentedcontrol1","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","uuid":"74975CE5-5D48-4308-871C-A0C96E4656F0","enabled":true,"segments":"To-Do|Done","flex":"LR"},"frame":"{{137, 6}, {265, 29}}","nodes":[]},{"class":"TableView","attributes":{"background_color":"RGBA(1.000000,1.000000,1.000000,1.000000)","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","data_source_number_of_lines":1,"enabled":true,"flex":"WH","row_height":44,"data_source_action":"picked","data_source_items":"","data_source_delete_enabled":false,"editing":false,"name":"reminders","uuid":"CEF3207F-E38F-409D-AB5D-695A3C52D0D8","data_source_font_size":18},"frame":"{{6, 43}, {528, 526}}","nodes":[]}]}]"
-
Looks smelly to me. The sender.items list doesn't look right. It only has a single item so any selection except the top (row == 0) will fail with index out of range. Selecting the top item gets past that error but fails because the entry it doesn't have a "reminder" attribute.
The sender.items list has a single entry, a dictionary with the standard attributes but with empty values. I would have expected the items list to contain all table row values but it doesn't.
note: changing the assignment to:
item = reminders_table.data_source.items[sender.selected_row]
seems to work. At least it stops generating error messages.
Summary: I'm guessing the sender variable is being trashed before the call. Maybe a ui bug. The code does reassign data_source many times. Maybe a missing update.
-
Forgot to mention: I would have expected sender to equal
reminders_table.data_source
in picked but it doesn't. That probably says something. I'm not sure what.
-
Another note: I've never used the built-in list_data_source so my knowledge is shaky. I generate TableViewCells directly in my own data_source. However if I did I would try to create a single ui.ListDataSource object at startup and update its contents when needed instead of creating a new one every time the contents change.
-
Each time you have the line:
reminders_table.data_source = ui.ListDataSource(items=SomethingOrOther)
Immediately follow that with the line:
reminders_table.delegate = reminders_table.data_source
That would alone would solve the problem that you mentioned but I would also recommend simplifying
button_action()
to be just:def button_action(sender): if segment.selected_index: reminders_table.data_source.items = completed_items else: reminders_table.data_source.items = todo_items
So that you reuse the same ListDatasource instance over and over again instead of allocating a new one each time the data changes.
-
@ccc Thanks for this, only problem is that when a reminder is marked as completed, you need to re run the script for it to be in the completed items.
-
You could create a function like:
def get_reminder_items(completed=False): return [{'title': r.title, 'reminder': r} for r in reminders.get_reminders(completed=completed)]
which you could call (
reminders_table.data_source.items = get_reminder_items(completed=TrueOrFalse
) frompicked()
,grabbed()
, andbutton_action()
to put fresh data into the ListDataSource. -
@ccc Using your help, I was able to delete
grabbed()
all together# coding: utf-8 import dialogs import reminders import ui v = ui.load_view('reminders') reminders_table = v['reminders'] def get_reminder_items(completed=False): return [{'title': r.title, 'reminder': r} for r in reminders.get_reminders(completed=completed)] def picked(sender): item = sender.items[sender.selected_row] r = item['reminder'] r.completed = True r.save() del sender.items[sender.selected_row] def button_action(sender): if segment.selected_index: reminders_table.data_source.items = get_reminder_items(completed=True) else: reminders_table.data_source.items = get_reminder_items(completed=False) @ui.in_background def but_action(sender): fields = [{'key' : 'name', 'type' : 'text', 'value' : 'Name your reminder'},] result=dialogs.form_dialog(title='Create a Reminder', fields=fields) r = reminders.Reminder() r.title = result['name'] r.save() segment.selected_index = 0 reminders_table.data_source.items = get_reminder_items(completed=False) segment = v['segmentedcontrol1'] segment.action = button_action reminders_table.data_source.action = picked create_button = ui.ButtonItem() create_button.image = ui.Image.named('ionicons-ios7-plus-empty-32') create_button.action = but_action reminders_table.data_source.items = get_reminder_items(completed=False) v.right_button_items = [create_button] v.present('sheet')
-
Cool... As I user, I would find it a bit of a shock that just tapping an incomplete task moves it to a completed state with no chance to undo that action but you might want your app to work that way.
One super minor suggestion:
def button_action(sender): completed = segment.selected_index == 1 reminders_table.data_source.items = get_reminder_items(completed=completed)
-
That's a nice script. Thanks. I was just about to add reminders to my app and this will be a great demo. It is also interesting that you don't need to create a ListDataSource at all.
-
When you create a TableView from the ui builder, it creates a ListDataSource for you. If you were creating this from code, I believe you do need to specify a delegate and data source.
-
@ccc and @zencuke It's actually part of a something bigger I've been working on and thanks to ccc I've been able to move on to adding more features. Just added the calendar ability.
# coding: utf-8 import console import dialogs import reminders import ui v = ui.load_view('reminders') reminders_table = v['reminders'] def get_reminder_items(completed=False): return [{'title': r.title, 'reminder': r} for r in reminders.get_reminders(completed=completed)] def picked(sender): item = sender.items[sender.selected_row] r = item['reminder'] if r.completed == True: r.completed = False else: r.completed = True r.save() del sender.items[sender.selected_row] def button_action(sender): completed = segment.selected_index == 1 reminders_table.data_source.items = get_reminder_items(completed=completed) @ui.in_background def but_action(sender): fields = [{'key' : 'name', 'type' : 'text', 'value' : 'Name your reminder'}, {'key' : 'calendar', 'type' : 'text', 'value' : 'Name a calendar for this reminder'}] result=dialogs.form_dialog(title='Create a Reminder', fields=fields) all_calendars = reminders.get_all_calendars() for calendar in all_calendars: if calendar.title == result['calendar']: r = reminders.Reminder(calendar) r.title = result['name'] r.save() break else: q = console.alert('Could not find calendar', 'Could not find calendar named ' + result['calendar'] + ' Would you like to create one?', 'Yes', hide_cancel_button=False) if q == 1: new_calendar = reminders.Calendar() new_calendar.title = result['calendar'] new_calendar.save() calendar.title == result['calendar'] r = reminders.Reminder(calendar) r.title = result['name'] r.save() segment.selected_index = 0 reminders_table.data_source.items = get_reminder_items(completed=False) #console.hud_alert('Reminder Created', 'success', 1) segment = v['segmentedcontrol1'] segment.action = button_action reminders_table.data_source.action = picked create_button = ui.ButtonItem() create_button.image = ui.Image.named('ionicons-ios7-plus-empty-32') create_button.action = but_action reminders_table.data_source.items = get_reminder_items(completed=False) v.right_button_items = [create_button] v.present('sheet')
-
if r.completed == True: r.completed = False else: r.completed = True # can be replaced by r.completed = not r.completed