TodoListDemo.vala 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using Spry;
  2. using Astralis;
  3. using Inversion;
  4. using Invercargill.DataStructures;
  5. /**
  6. * TodoListDemo - A todo list demo for the Outlets documentation
  7. *
  8. * Demonstrates:
  9. * - Using spry-outlet for dynamic child components
  10. * - set_outlet_children() to populate outlets
  11. * - Parent/child component composition
  12. */
  13. public class TodoListDemo : Component {
  14. private TodoDemoStore store = inject<TodoDemoStore>();
  15. private ComponentFactory factory = inject<ComponentFactory>();
  16. private HttpContext http_context = inject<HttpContext>();
  17. public override string markup { get {
  18. return """
  19. <div sid="todo-list" class="demo-todo-list">
  20. <div class="todo-header">
  21. <h3>Todo List</h3>
  22. <span class="todo-count" sid="count"></span>
  23. </div>
  24. <form class="todo-form" spry-action=":Add" spry-target="todo-list">
  25. <input type="text" name="title" placeholder="Add a task..." sid="input"/>
  26. <button type="submit" class="todo-add-btn">Add</button>
  27. </form>
  28. <ul class="todo-items" sid="items">
  29. <spry-outlet sid="items-outlet"/>
  30. </ul>
  31. </div>
  32. """;
  33. }}
  34. public override async void prepare() throws Error {
  35. // Update count
  36. this["count"].text_content = @"$(store.items.length) items";
  37. // Build list items
  38. var children = new Series<Renderable>();
  39. foreach (var item in store.items) {
  40. var component = factory.create<TodoItemDemo>();
  41. component.item_id = item.id;
  42. children.add(component);
  43. }
  44. set_outlet_children("items-outlet", children);
  45. }
  46. public async override void handle_action(string action) throws Error {
  47. if (action == "Add") {
  48. var title = http_context.request.query_params.get_any_or_default("title");
  49. if (title != null && title.strip().length > 0) {
  50. store.add(title.strip());
  51. }
  52. }
  53. }
  54. }
  55. /**
  56. * TodoItemDemo - Individual todo item component
  57. */
  58. public class TodoItemDemo : Component {
  59. private TodoDemoStore store = inject<TodoDemoStore>();
  60. private HttpContext http_context = inject<HttpContext>();
  61. public int item_id { set; get; }
  62. public override string markup { get {
  63. return """
  64. <li sid="item" class="todo-item">
  65. <span sid="title" class="todo-title"></span>
  66. <button sid="toggle" spry-action=":Toggle" spry-target="item"
  67. class="todo-toggle"></button>
  68. <button sid="delete" spry-action=":Delete" spry-target="item"
  69. hx-swap="delete" class="todo-delete">×</button>
  70. </li>
  71. """;
  72. }}
  73. public override async void prepare() throws Error {
  74. var item = store.get_by_id(item_id);
  75. if (item == null) return;
  76. this["title"].text_content = item.title;
  77. this["item"].set_attribute("hx-vals", @"{\"id\":$item_id}");
  78. if (item.completed) {
  79. this["item"].add_class("completed");
  80. this["toggle"].text_content = "↩";
  81. } else {
  82. this["toggle"].text_content = "✓";
  83. }
  84. }
  85. public async override void handle_action(string action) throws Error {
  86. var id_str = http_context.request.query_params.get_any_or_default("id");
  87. var id = int.parse(id_str);
  88. switch (action) {
  89. case "Toggle":
  90. store.toggle(id);
  91. break;
  92. case "Delete":
  93. store.remove(id);
  94. break;
  95. }
  96. }
  97. }
  98. /**
  99. * TodoDemoStore - Singleton store for todo items
  100. */
  101. public class TodoDemoStore : Object {
  102. public Series<TodoDemoItem> items { get; set; default = new Series<TodoDemoItem>(); }
  103. private int _next_id = 1;
  104. public TodoDemoStore() {
  105. // Add some initial items
  106. add("Learn Spry components");
  107. add("Build a web app");
  108. add("Deploy to production");
  109. }
  110. public void add(string title) {
  111. items.add(new TodoDemoItem(_next_id++, title));
  112. }
  113. public TodoDemoItem? get_by_id(int id) {
  114. foreach (var item in items) {
  115. if (item.id == id) return item;
  116. }
  117. return null;
  118. }
  119. public void toggle(int id) {
  120. var item = get_by_id(id);
  121. if (item != null) {
  122. item.completed = !item.completed;
  123. }
  124. }
  125. public void remove(int id) {
  126. // Build a new list without the item
  127. var new_items = new Series<TodoDemoItem>();
  128. foreach (var item in items) {
  129. if (item.id != id) {
  130. new_items.add(item);
  131. }
  132. }
  133. items = new_items;
  134. }
  135. }
  136. /**
  137. * TodoDemoItem - Single todo item data
  138. */
  139. public class TodoDemoItem : Object {
  140. public int id { get; construct; }
  141. public string title { get; construct set; }
  142. public bool completed { get; set; default = false; }
  143. public TodoDemoItem(int id, string title) {
  144. Object(id: id, title: title);
  145. }
  146. }