Component.vala 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Inversion;
  4. using Astralis;
  5. namespace Spry {
  6. public abstract class Component : Object, Renderable {
  7. private static Dictionary<Type, ComponentTemplate> templates;
  8. private static Mutex templates_lock = Mutex();
  9. public abstract string markup { get; }
  10. public virtual StatusCode get_status() {
  11. return StatusCode.OK;
  12. }
  13. public virtual async void prepare() throws Error {
  14. // No-op default
  15. }
  16. public virtual async void handle_action(string action) throws Error {
  17. // No-op default
  18. }
  19. public virtual async void continuation(SseStream stream) throws Error {
  20. // No-op default
  21. }
  22. private PathProvider _path_provider = inject<PathProvider>();
  23. private ContinuationProvider _continuation_provider = inject<ContinuationProvider>();
  24. private Catalogue<string, Renderable> _children = new Catalogue<string, Renderable>();
  25. private HashSet<Component> _global_sources = new HashSet<Component>();
  26. private MarkupDocument _instance;
  27. private MarkupDocument instance { get {
  28. if(_instance == null) {
  29. try {
  30. lock(_instance) {
  31. if(_instance == null) {
  32. templates_lock.lock ();
  33. if(templates == null) {
  34. templates = new Dictionary<Type, ComponentTemplate>();
  35. }
  36. var type = this.get_type();
  37. ComponentTemplate template;
  38. if(!templates.try_get(type, out template)) {
  39. template = new ComponentTemplate(markup);
  40. templates[type] = template;
  41. }
  42. templates_lock.unlock();
  43. _instance = template.new_instance();
  44. }
  45. }
  46. }
  47. catch (Error e) {
  48. error(e.message);
  49. }
  50. }
  51. return _instance;
  52. }}
  53. protected MarkupNodeList get_elements_by_class_name(string class_name) {
  54. return instance.get_elements_by_class_name(class_name);
  55. }
  56. protected MarkupNodeList get_elements_by_tag_name(string tag_name) {
  57. return instance.get_elements_by_tag_name(tag_name);
  58. }
  59. protected new MarkupNodeList query(string xpath) {
  60. return instance.select(xpath);
  61. }
  62. protected MarkupNode? query_one(string xpath) {
  63. return instance.select_one(xpath);
  64. }
  65. protected MarkupNode get_element_by_global_id(string global_id) {
  66. return instance.get_element_by_id(global_id);
  67. }
  68. protected new MarkupNode? @get(string spry_id) {
  69. return instance.select_one(@"//*[@sid='$(spry_id)']");
  70. }
  71. protected void add_outlet_child(string outlet_id, Renderable renderable) {
  72. _children.add(outlet_id, renderable);
  73. }
  74. protected void add_outlet_children(string outlet_id, Enumerable<Renderable> renderables) {
  75. _children.add_all(outlet_id, renderables);
  76. }
  77. protected void set_outlet_children(string outlet_id, Enumerable<Renderable> renderables) {
  78. _children[outlet_id] = renderables;
  79. }
  80. protected void set_outlet_child(string outlet_id, Renderable renderable) {
  81. _children[outlet_id] = Iterate.single(renderable);
  82. }
  83. protected void clear_outlet_children(string outlet_id) {
  84. _children.clear_key(outlet_id);
  85. }
  86. protected void add_globals_from(Component component) {
  87. _global_sources.add(component);
  88. }
  89. public async MarkupDocument to_document() throws Error {
  90. yield prepare();
  91. var final_instance = instance.copy();
  92. // Replace outlets
  93. var outlets = final_instance.select("//spry-outlet");
  94. foreach (var outlet in outlets) {
  95. var nodes = new Series<MarkupNode>();
  96. foreach(var renderable in _children.get_or_empty(outlet.get_attribute("sid"))) {
  97. var document = yield renderable.to_document();
  98. nodes.add_all(document.body.children);
  99. }
  100. outlet.replace_with_nodes(nodes);
  101. }
  102. // Remove hidden blocks
  103. final_instance.select("//*[@spry-hidden]")
  104. .iterate(n => n.remove());
  105. // Replace control blocks with their children
  106. final_instance.select("//spry-control")
  107. .iterate(n => n.replace_with_nodes(n.children));
  108. var action_nodes = final_instance.select("//*[@spry-action]");
  109. foreach(var node in action_nodes) {
  110. var action = node.get_attribute("spry-action").split(":", 2);
  111. var component_name = action[0].replace(".", "");
  112. if(component_name == "") {
  113. component_name = this.get_type().name();
  114. }
  115. var component_action = action[1];
  116. node.remove_attribute("spry-action");
  117. node.set_attribute("hx-get", _path_provider.get_action_path(component_name, component_action));
  118. }
  119. var target_nodes = final_instance.select("//*[@spry-target]");
  120. foreach(var node in target_nodes) {
  121. var target_node = final_instance.select_one(@"//*[@sid='$(node.get_attribute("spry-target"))']");
  122. if(target_node.id == null) {
  123. target_node.id = "_spry-" + Uuid.string_random();
  124. }
  125. node.set_attribute("hx-target", @"#$(target_node.id)");
  126. node.remove_attribute("spry-target");
  127. }
  128. var global_nodes = final_instance.select("//*[@spry-global]");
  129. foreach(var node in global_nodes) {
  130. var key = node.get_attribute("spry-global");
  131. node.set_attribute("hx-swap-oob", @"[spry-global=\"$key\"]");
  132. }
  133. var script_nodes = final_instance.select("//script[@spry-res]");
  134. foreach(var node in script_nodes) {
  135. var res = node.get_attribute("spry-res");
  136. if(res != null) {
  137. node.set_attribute("src", "/_spry/res/" + res);
  138. }
  139. node.remove_attribute("spry-res");
  140. }
  141. var continuation_nodes = final_instance.select("//*[@spry-continuation]");
  142. foreach(var node in continuation_nodes) {
  143. var path = _continuation_provider.get_continuation_path(this);
  144. node.set_attribute("hx-ext", "sse");
  145. node.set_attribute("sse-connect", path);
  146. node.remove_attribute("spry-continuation");
  147. }
  148. // Remove all internal SIDs
  149. final_instance.select("//*[@sid]")
  150. .iterate(n => n.remove_attribute("sid"));
  151. // Add globals
  152. foreach(var source in _global_sources) {
  153. var document = yield source.to_document();
  154. var globals = document.select("//*[@spry-global]");
  155. final_instance.body.append_nodes(globals);
  156. }
  157. return final_instance;
  158. }
  159. public async HttpResult to_result() throws Error {
  160. var document = yield to_document();
  161. return document.to_result(get_status());
  162. }
  163. private class ComponentTemplate : MarkupTemplate {
  164. private string _markup;
  165. protected override string markup { get { return _markup; } }
  166. public ComponentTemplate(string markup) {
  167. this._markup = markup;
  168. }
  169. }
  170. }
  171. }