TodoListDemo.vala 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. // If item_id is not set (fresh instance from action), try to get it from query params
  75. if (item_id == 0) {
  76. var id_str = http_context.request.query_params.get_any_or_default("id");
  77. if (id_str != null) {
  78. item_id = int.parse(id_str);
  79. }
  80. }
  81. var item = store.get_by_id(item_id);
  82. if (item == null) return;
  83. this["title"].text_content = item.title;
  84. this["item"].set_attribute("hx-vals", @"{\"id\":$item_id}");
  85. if (item.completed) {
  86. this["item"].add_class("completed");
  87. this["toggle"].text_content = "↩";
  88. } else {
  89. this["toggle"].text_content = "✓";
  90. }
  91. }
  92. public async override void handle_action(string action) throws Error {
  93. var id_str = http_context.request.query_params.get_any_or_default("id");
  94. var id = int.parse(id_str);
  95. switch (action) {
  96. case "Toggle":
  97. store.toggle(id);
  98. break;
  99. case "Delete":
  100. store.remove(id);
  101. break;
  102. }
  103. }
  104. }
  105. /**
  106. * TodoDemoStore - Singleton store for todo items
  107. */
  108. public class TodoDemoStore : Object {
  109. public Series<TodoDemoItem> items { get; set; default = new Series<TodoDemoItem>(); }
  110. private int _next_id = 1;
  111. public TodoDemoStore() {
  112. // Add some initial items
  113. add("Learn Spry components");
  114. add("Build a web app");
  115. add("Deploy to production");
  116. }
  117. public void add(string title) {
  118. items.add(new TodoDemoItem(_next_id++, title));
  119. }
  120. public TodoDemoItem? get_by_id(int id) {
  121. foreach (var item in items) {
  122. if (item.id == id) return item;
  123. }
  124. return null;
  125. }
  126. public void toggle(int id) {
  127. var item = get_by_id(id);
  128. if (item != null) {
  129. item.completed = !item.completed;
  130. }
  131. }
  132. public void remove(int id) {
  133. // Build a new list without the item
  134. var new_items = new Series<TodoDemoItem>();
  135. foreach (var item in items) {
  136. if (item.id != id) {
  137. new_items.add(item);
  138. }
  139. }
  140. items = new_items;
  141. }
  142. }
  143. /**
  144. * TodoDemoItem - Single todo item data
  145. */
  146. public class TodoDemoItem : Object {
  147. public int id { get; construct; }
  148. public string title { get; construct set; }
  149. public bool completed { get; set; default = false; }
  150. public TodoDemoItem(int id, string title) {
  151. Object(id: id, title: title);
  152. }
  153. }