matestack-ui-core 0.7.6 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +8 -0
  3. data/README.md +27 -215
  4. data/app/concepts/matestack/ui/core/abbr/abbr.haml +2 -2
  5. data/app/concepts/matestack/ui/core/abbr/abbr.rb +3 -2
  6. data/app/concepts/matestack/ui/core/action/action.rb +23 -19
  7. data/app/concepts/matestack/ui/core/address/address.haml +2 -2
  8. data/app/concepts/matestack/ui/core/address/address.rb +1 -1
  9. data/app/concepts/matestack/ui/core/app/app.haml +2 -3
  10. data/app/concepts/matestack/ui/core/app/app.js +4 -1
  11. data/app/concepts/matestack/ui/core/app/app.rb +32 -56
  12. data/app/concepts/matestack/ui/core/app/store.js +23 -1
  13. data/app/concepts/matestack/ui/core/area/area.rb +12 -10
  14. data/app/concepts/matestack/ui/core/async/async.haml +6 -2
  15. data/app/concepts/matestack/ui/core/async/async.js +37 -3
  16. data/app/concepts/matestack/ui/core/async/async.rb +29 -4
  17. data/app/concepts/matestack/ui/core/async/children_wrapper.haml +2 -0
  18. data/app/concepts/matestack/ui/core/bdo/bdo.rb +1 -1
  19. data/app/concepts/matestack/ui/core/collection/content/content.rb +2 -2
  20. data/app/concepts/matestack/ui/core/collection/content/page/link/link.rb +4 -2
  21. data/app/concepts/matestack/ui/core/collection/filter/filter.rb +4 -5
  22. data/app/concepts/matestack/ui/core/collection/filter/select/select.haml +10 -0
  23. data/app/concepts/matestack/ui/core/collection/filter/select/select.rb +29 -0
  24. data/app/concepts/matestack/ui/core/collection/helper.rb +14 -6
  25. data/app/concepts/matestack/ui/core/collection/order/order.js +1 -1
  26. data/app/concepts/matestack/ui/core/collection/order/order.rb +4 -5
  27. data/app/concepts/matestack/ui/core/collection/order/toggle/indicator/indicator.rb +6 -5
  28. data/app/concepts/matestack/ui/core/component/base.rb +424 -0
  29. data/app/concepts/matestack/ui/core/component/children.haml +2 -2
  30. data/app/concepts/matestack/ui/core/component/component.js +1 -55
  31. data/app/concepts/matestack/ui/core/component/dynamic.haml +1 -1
  32. data/app/concepts/matestack/ui/core/component/dynamic.rb +24 -247
  33. data/app/concepts/matestack/ui/core/component/rerender.rb +8 -0
  34. data/app/concepts/matestack/ui/core/component/static.rb +3 -12
  35. data/app/concepts/matestack/ui/core/form/checkbox/checkbox.rb +79 -0
  36. data/app/concepts/matestack/ui/core/form/form.js +25 -20
  37. data/app/concepts/matestack/ui/core/form/form.rb +7 -2
  38. data/app/concepts/matestack/ui/core/form/has_errors.rb +54 -0
  39. data/app/concepts/matestack/ui/core/form/has_input_html_attributes.rb +13 -0
  40. data/app/concepts/matestack/ui/core/form/input/input.rb +32 -52
  41. data/app/concepts/matestack/ui/core/form/radio/radio.rb +71 -0
  42. data/app/concepts/matestack/ui/core/form/select/select.haml +9 -80
  43. data/app/concepts/matestack/ui/core/form/select/select.rb +61 -56
  44. data/app/concepts/matestack/ui/core/form/submit/submit.rb +4 -1
  45. data/app/concepts/matestack/ui/core/form/textarea/textarea.rb +28 -0
  46. data/app/concepts/matestack/ui/core/form/utils.rb +47 -0
  47. data/app/concepts/matestack/ui/core/heading/heading.rb +2 -0
  48. data/app/concepts/matestack/ui/core/input/input.haml +1 -2
  49. data/app/concepts/matestack/ui/core/input/input.rb +4 -0
  50. data/app/concepts/matestack/ui/core/isolated/children_wrapper.haml +2 -0
  51. data/app/concepts/matestack/ui/core/isolated/isolated.haml +10 -0
  52. data/app/concepts/matestack/ui/core/isolated/isolated.js +108 -0
  53. data/app/concepts/matestack/ui/core/isolated/isolated.rb +59 -0
  54. data/app/concepts/matestack/ui/core/js/core.js +5 -4
  55. data/app/concepts/matestack/ui/core/link/link.rb +7 -7
  56. data/app/concepts/matestack/ui/core/map/map.rb +1 -1
  57. data/app/concepts/matestack/ui/core/onclick/onclick.rb +1 -0
  58. data/app/concepts/matestack/ui/core/page/{content.js → content/content.js} +8 -2
  59. data/app/concepts/matestack/ui/core/page/content/content.rb +29 -0
  60. data/app/concepts/matestack/ui/core/page/page.haml +3 -9
  61. data/app/concepts/matestack/ui/core/page/page.rb +15 -179
  62. data/app/concepts/matestack/ui/core/progress/progress.rb +1 -1
  63. data/app/concepts/matestack/ui/core/slot/slot.rb +2 -2
  64. data/app/concepts/matestack/ui/core/textarea/textarea.haml +2 -0
  65. data/app/concepts/matestack/ui/core/textarea/textarea.rb +10 -0
  66. data/app/concepts/matestack/ui/core/toggle/toggle.haml +2 -0
  67. data/app/concepts/matestack/ui/core/toggle/toggle.js +71 -0
  68. data/app/concepts/matestack/ui/core/toggle/toggle.rb +14 -0
  69. data/app/concepts/matestack/ui/core/transition/transition.js +1 -0
  70. data/app/concepts/matestack/ui/core/transition/transition.rb +1 -0
  71. data/app/concepts/matestack/ui/core/unescaped/unescaped.rb +1 -1
  72. data/app/concepts/matestack/ui/core/video/video.rb +1 -1
  73. data/app/concepts/matestack/ui/core/view/view.haml +1 -0
  74. data/app/concepts/matestack/ui/core/view/view.rb +30 -0
  75. data/app/concepts/matestack/ui/core/youtube/youtube.rb +1 -1
  76. data/app/helpers/matestack/ui/core/application_helper.rb +89 -20
  77. data/app/lib/matestack/ui/component.rb +1 -0
  78. data/app/lib/matestack/ui/core/has_view_context.rb +4 -4
  79. data/app/lib/matestack/ui/core/html_attributes.rb +43 -0
  80. data/app/lib/matestack/ui/core/properties.rb +88 -0
  81. data/app/lib/matestack/ui/core/rendering/default_renderer_class_determiner.rb +33 -0
  82. data/app/lib/matestack/ui/core/rendering/main_renderer.rb +199 -0
  83. data/app/lib/matestack/ui/isolated_component.rb +1 -0
  84. data/app/lib/matestack/ui/vue_js_component.rb +1 -0
  85. data/lib/matestack/ui/core.rb +4 -2
  86. data/lib/matestack/ui/core/cell.rb +0 -2
  87. data/lib/matestack/ui/core/component/registry.rb +47 -0
  88. data/lib/matestack/ui/core/components.rb +267 -0
  89. data/lib/matestack/ui/core/dsl.rb +6 -0
  90. data/lib/matestack/ui/core/engine.rb +16 -0
  91. data/lib/matestack/ui/core/version.rb +1 -1
  92. data/vendor/assets/javascripts/dist/matestack-ui-core.js +329 -122
  93. data/vendor/assets/javascripts/dist/matestack-ui-core.js.map +1 -1
  94. data/vendor/assets/javascripts/dist/matestack-ui-core.min.js +1 -1
  95. data/vendor/assets/javascripts/dist/matestack-ui-core.min.js.gz +0 -0
  96. data/vendor/assets/javascripts/dist/matestack-ui-core.min.js.map +1 -1
  97. data/vendor/assets/javascripts/dist/matestack-ui-core.min.js.map.gz +0 -0
  98. metadata +47 -64
  99. data/MIT-LICENSE +0 -20
  100. data/app/concepts/matestack/ui/core/absolute/absolute.haml +0 -3
  101. data/app/concepts/matestack/ui/core/absolute/absolute.rb +0 -17
  102. data/app/concepts/matestack/ui/core/component/response.haml +0 -2
  103. data/app/concepts/matestack/ui/core/component/response_dynamic.haml +0 -7
  104. data/app/concepts/matestack/ui/core/component/response_dynamic_without_rerender.haml +0 -3
  105. data/app/concepts/matestack/ui/core/component/static.haml +0 -1
  106. data/app/concepts/matestack/ui/core/form/inline/inline.haml +0 -6
  107. data/app/concepts/matestack/ui/core/form/inline/inline.rb +0 -9
  108. data/app/concepts/matestack/ui/core/form/input/input.haml +0 -46
  109. data/app/concepts/matestack/ui/core/html/html.haml +0 -3
  110. data/app/concepts/matestack/ui/core/html/html.js +0 -10
  111. data/app/concepts/matestack/ui/core/html/html.rb +0 -17
  112. data/app/concepts/matestack/ui/core/isolate/isolate.haml +0 -2
  113. data/app/concepts/matestack/ui/core/isolate/isolate.rb +0 -11
  114. data/app/concepts/matestack/ui/core/page/content.haml +0 -7
  115. data/app/concepts/matestack/ui/core/page/content.rb +0 -5
  116. data/app/concepts/matestack/ui/core/pg/pg.haml +0 -5
  117. data/app/concepts/matestack/ui/core/pg/pg.rb +0 -5
  118. data/app/lib/matestack/ui/core/app_node.rb +0 -53
  119. data/app/lib/matestack/ui/core/component_node.rb +0 -87
  120. data/app/lib/matestack/ui/core/page_node.rb +0 -100
  121. data/app/lib/matestack/ui/core/render.rb +0 -89
  122. data/app/lib/matestack/ui/core/to_cell.rb +0 -129
@@ -8,6 +8,7 @@ Vue.use(Vuex)
8
8
  const store = new Vuex.Store({
9
9
  state: {
10
10
  pageTemplate: null,
11
+ pageLoading: false,
11
12
  currentPathName: document.location.pathname,
12
13
  currentSearch: document.location.search,
13
14
  currentOrigin: document.location.origin
@@ -16,6 +17,9 @@ const store = new Vuex.Store({
16
17
  setPageTemplate (state, serverResponse){
17
18
  state.pageTemplate = serverResponse
18
19
  },
20
+ setPageLoading (state, boolean){
21
+ state.pageLoading = boolean
22
+ },
19
23
  setCurrentLocation (state, current){
20
24
  state.currentPathName = current.path
21
25
  state.currentSearch = current.search
@@ -23,10 +27,26 @@ const store = new Vuex.Store({
23
27
  },
24
28
  resetPageTemplate (state) {
25
29
  state.pageTemplate = null;
30
+ },
31
+ pageScrollTop (state) {
32
+ //https://stackoverflow.com/a/35940276/13886137
33
+ const getScrollParent = function(node) {
34
+ if (node == null) {
35
+ return null
36
+ }
37
+ if (node.scrollHeight > node.clientHeight) {
38
+ return node
39
+ } else {
40
+ return getScrollParent(node.parentNode)
41
+ }
42
+ }
43
+ getScrollParent(document.getElementsByClassName("matestack-page-root")[0]).scrollTop = 0
26
44
  }
27
45
  },
28
46
  actions: {
29
47
  navigateTo ({ commit, state }, { url, backwards }) {
48
+ const self = this
49
+ commit('setPageLoading', true)
30
50
  matestackEventHub.$emit("page_loading", url);
31
51
  if (typeof matestackUiCoreTransitionStart !== 'undefined') {
32
52
  matestackUiCoreTransitionStart(url);
@@ -51,9 +71,11 @@ const store = new Vuex.Store({
51
71
  window.history.pushState({matestackApp: true, url: url}, null, url);
52
72
  }
53
73
  setTimeout(function () {
54
- resolve(response["data"])
74
+ resolve(response["data"]);
55
75
  commit('setPageTemplate', response["data"])
56
76
  commit('setCurrentLocation', { path: url.split("?")[0], search: document.location.search, origin: document.location.origin })
77
+ commit('setPageLoading', false)
78
+ commit('pageScrollTop')
57
79
  matestackEventHub.$emit("page_loaded", url);
58
80
  if (typeof matestackUiCoreTransitionSuccess !== 'undefined') {
59
81
  matestackUiCoreTransitionSuccess(url);
@@ -1,17 +1,19 @@
1
1
  module Matestack::Ui::Core::Area
2
2
  class Area < Matestack::Ui::Core::Component::Static
3
+ optional :alt, :coords, :download, :href, :hreflang, :media, :rel, :shape, :target, :type
4
+
3
5
  def setup
4
6
  @tag_attributes.merge!({
5
- alt: options[:alt],
6
- coords: options[:coords].join(','),
7
- download: options[:download],
8
- href: options[:href],
9
- hreflang: options[:hreflang],
10
- media: options[:media],
11
- rel: options[:rel],
12
- shape: options[:shape],
13
- target: options[:target],
14
- type: options[:type]
7
+ alt: alt,
8
+ coords: coords.join(','),
9
+ download: download,
10
+ href: href,
11
+ hreflang: hreflang,
12
+ media: media,
13
+ rel: rel,
14
+ shape: shape,
15
+ target: target,
16
+ type: type
15
17
  })
16
18
  end
17
19
  end
@@ -1,2 +1,6 @@
1
- %div{@tag_attributes}
2
- = yield
1
+ %component{dynamic_tag_attributes}
2
+ %div{class: "matestack-async-component-container", "v-bind:class": "{ 'loading': loading === true }"}
3
+ %div{class: "matestack-async-component-wrapper", "v-if": "asyncTemplate == null", "v-bind:class": "{ 'loading': loading === true }"}
4
+ = render_content
5
+ %div{class: "matestack-async-component-wrapper", "v-if": "asyncTemplate != null", "v-bind:class": "{ 'loading': loading === true }"}
6
+ %v-runtime-template{":template":"asyncTemplate"}
@@ -1,4 +1,6 @@
1
1
  import Vue from 'vue/dist/vue.esm'
2
+ import axios from 'axios'
3
+ import VRuntimeTemplate from "v-runtime-template"
2
4
  import matestackEventHub from '../js/event-hub'
3
5
  import componentMixin from '../component/component'
4
6
 
@@ -6,8 +8,10 @@ const componentDef = {
6
8
  mixins: [componentMixin],
7
9
  data: function(){
8
10
  return {
11
+ asyncTemplate: null,
9
12
  showing: true,
10
- hide_after_timeout: null,
13
+ loading: false,
14
+ hideAfterTimeout: null,
11
15
  event: {
12
16
  data: {}
13
17
  }
@@ -27,7 +31,7 @@ const componentDef = {
27
31
  }
28
32
  }
29
33
  if(this.componentConfig["hide_after"] != undefined){
30
- self.hide_after_timeout = setTimeout(function () {
34
+ self.hideAfterTimeout = setTimeout(function () {
31
35
  self.hide()
32
36
  }, parseInt(this.componentConfig["hide_after"]));
33
37
  }
@@ -38,9 +42,36 @@ const componentDef = {
38
42
  },
39
43
  startDefer: function(){
40
44
  const self = this
45
+ self.loading = true;
41
46
  setTimeout(function () {
42
47
  self.rerender()
43
48
  }, parseInt(this.componentConfig["defer"]));
49
+ },
50
+ rerender: function(){
51
+ var self = this;
52
+ self.loading = true;
53
+ axios({
54
+ method: "get",
55
+ url: location.pathname + location.search,
56
+ headers: {
57
+ 'X-CSRF-Token': document.getElementsByName("csrf-token")[0].getAttribute('content')
58
+ },
59
+ params: {
60
+ "component_key": self.componentConfig["component_key"],
61
+ "component_class": self.componentConfig["parent_class"]
62
+ }
63
+ })
64
+ .then(function(response){
65
+ var tmp_dom_element = document.createElement('div');
66
+ tmp_dom_element.innerHTML = response['data'];
67
+ var template = tmp_dom_element.querySelector('#' + self.componentConfig["component_key"]).outerHTML;
68
+ self.loading = false;
69
+ self.asyncTemplate = template;
70
+ })
71
+ .catch(function(error){
72
+ console.log(error)
73
+ matestackEventHub.$emit('async_rerender_error', { id: self.componentConfig["component_key"] })
74
+ })
44
75
  }
45
76
  },
46
77
  created: function () {
@@ -74,7 +105,7 @@ const componentDef = {
74
105
  },
75
106
  beforeDestroy: function() {
76
107
  const self = this
77
- clearTimeout(self.hide_after_timeout)
108
+ clearTimeout(self.hideAfterTimeout)
78
109
  matestackEventHub.$off(this.componentConfig["rerender_on"], self.rerender);
79
110
  matestackEventHub.$off(this.componentConfig["show_on"], self.show);
80
111
  matestackEventHub.$off(this.componentConfig["hide_on"], self.hide);
@@ -91,6 +122,9 @@ const componentDef = {
91
122
  rerender_events.forEach(rerender_event => matestackEventHub.$off(rerender_event.trim(), self.rerender));
92
123
  }
93
124
  },
125
+ components: {
126
+ VRuntimeTemplate: VRuntimeTemplate
127
+ }
94
128
  }
95
129
 
96
130
  let component = Vue.component('matestack-ui-core-async', componentDef)
@@ -1,12 +1,37 @@
1
1
  module Matestack::Ui::Core::Async
2
- class Async < Matestack::Ui::Core::Component::Dynamic
2
+ class Async < Matestack::Ui::Core::Component::Rerender
3
+ vue_js_component_name "matestack-ui-core-async"
3
4
 
4
- def setup
5
- @rerender = true
5
+ optional :id # will be required in 1.0.0
6
+
7
+ def initialize(*args)
8
+ super
9
+ ActiveSupport::Deprecation.warn(
10
+ 'Calling async components without id is deprecated. Instead provide a unique id for async components.'
11
+ ) if id.blank?
12
+ @component_config[:component_key] = id || "async_#{Digest::SHA256.hexdigest(caller[3])}"
13
+ if @included_config.present? && @included_config[:isolated_parent_class].present?
14
+ @component_config[:parent_class] = @included_config[:isolated_parent_class]
15
+ end
6
16
  @tag_attributes.merge!({
7
- "v-if": "showing"
17
+ "v-if": "showing",
18
+ id: @component_config[:component_key]
8
19
  })
9
20
  end
10
21
 
22
+ def show
23
+ render :async
24
+ end
25
+
26
+ def render_content
27
+ render :children_wrapper do
28
+ render :children
29
+ end
30
+ end
31
+
32
+ def get_component_key
33
+ @component_config[:component_key]
34
+ end
35
+
11
36
  end
12
37
  end
@@ -0,0 +1,2 @@
1
+ %div{@tag_attributes.merge(class: "matestack-async-component-root")}
2
+ =yield
@@ -1,6 +1,6 @@
1
1
  module Matestack::Ui::Core::Bdo
2
2
  class Bdo < Matestack::Ui::Core::Component::Static
3
- REQUIRED_KEYS = [:dir]
3
+ requires :dir
4
4
 
5
5
  def setup
6
6
  @tag_attributes.merge!({
@@ -1,8 +1,8 @@
1
1
  module Matestack::Ui::Core::Collection::Content
2
- class Content < Matestack::Ui::Core::Component::Dynamic
2
+ class Content < Matestack::Ui::Core::Component::Rerender
3
+ vue_js_component_name 'matestack-ui-core-collection-content'
3
4
 
4
5
  def setup
5
- @rerender = true
6
6
  @component_config = @component_config.except(:data, :paginated_data)
7
7
  end
8
8
 
@@ -1,5 +1,7 @@
1
- module Matestack::Ui::Core::Collection::Content::Page::Link
2
- class Link < Matestack::Ui::Core::Component::Static
1
+ module Matestack::Ui::Core::Collection::Content::Page
2
+ module Link
3
+ class Link::Link < Matestack::Ui::Core::Component::Static
3
4
 
5
+ end
4
6
  end
5
7
  end
@@ -1,16 +1,15 @@
1
1
  module Matestack::Ui::Core::Collection::Filter
2
2
  class Filter < Matestack::Ui::Core::Component::Dynamic
3
+ vue_js_component_name 'matestack-ui-core-collection-filter'
3
4
 
4
5
  def setup
5
6
  @component_config = @component_config.except(:data, :paginated_data)
6
7
  end
7
8
 
8
9
  def response
9
- components {
10
- div @tag_attributes do
11
- yield_components
12
- end
13
- }
10
+ div @tag_attributes do
11
+ yield_components
12
+ end
14
13
  end
15
14
 
16
15
  end
@@ -0,0 +1,10 @@
1
+ - if options[:type] == :dropdown
2
+
3
+ %select{@tag_attributes}
4
+ %option{disabled: true}= options[:placeholder]
5
+ - if @options[:options].is_a?(Hash)
6
+ - @options[:options].each do |key, value|
7
+ %option{value: key}=value
8
+ - if @options[:options].is_a?(Array)
9
+ - @options[:options].each do |option|
10
+ %option{value: option}=option
@@ -0,0 +1,29 @@
1
+ module Matestack::Ui::Core::Collection::Filter::Select
2
+ class Select < Matestack::Ui::Core::Component::Static
3
+
4
+ def setup
5
+ if options[:type].nil?
6
+ options[:type] = :dropdown #nothing else supported at the moment
7
+ end
8
+ @tag_attributes.merge!({
9
+ "v-model#{'.number' if options[:type] == :number}": input_key,
10
+ type: options[:type],
11
+ class: options[:class],
12
+ id: component_id,
13
+ "@keyup.enter": "submitFilter()",
14
+ "@change": "$forceUpdate()",
15
+ ref: "filter.#{attr_key}",
16
+ placeholder: options[:placeholder]
17
+ })
18
+ end
19
+
20
+ def input_key
21
+ 'filter["' + options[:key].to_s + '"]'
22
+ end
23
+
24
+ def attr_key
25
+ return options[:key].to_s
26
+ end
27
+
28
+ end
29
+ end
@@ -1,6 +1,6 @@
1
1
  module Matestack::Ui::Core::Collection
2
2
 
3
- CollectionConfig = Struct.new(:id, :init_offset, :init_limit, :filtered_count, :base_count, :data, :context) do
3
+ CollectionConfig = Struct.new(:id, :init_offset, :init_limit, :filtered_count, :base_count, :data, :params) do
4
4
 
5
5
  def paginated_data
6
6
  resulting_data = data
@@ -11,11 +11,11 @@ module Matestack::Ui::Core::Collection
11
11
  end
12
12
 
13
13
  def get_collection_offset
14
- (context[:params]["#{id}-offset".to_sym] ||= init_offset).to_i
14
+ (params["#{id}-offset".to_sym] ||= init_offset).to_i
15
15
  end
16
16
 
17
17
  def get_collection_limit
18
- (context[:params]["#{id}-limit".to_sym] ||= init_limit).to_i
18
+ (params["#{id}-limit".to_sym] ||= init_limit).to_i
19
19
  end
20
20
 
21
21
  def pages
@@ -63,7 +63,7 @@ module Matestack::Ui::Core::Collection
63
63
 
64
64
  def get_collection_filter collection_id, key=nil
65
65
  filter_hash = {}
66
- context[:params].each do |param_key, param_value|
66
+ controller_params.each do |param_key, param_value|
67
67
  if param_key.start_with?("#{collection_id}-filter-")
68
68
  param_key.gsub("#{collection_id}-filter-", "")
69
69
  filter_hash[param_key.gsub("#{collection_id}-filter-", "").to_sym] = param_value
@@ -78,7 +78,7 @@ module Matestack::Ui::Core::Collection
78
78
 
79
79
  def get_collection_order collection_id, key=nil
80
80
  order_hash = {}
81
- context[:params].each do |param_key, param_value|
81
+ controller_params.each do |param_key, param_value|
82
82
  if param_key.start_with?("#{collection_id}-order-")
83
83
  param_key.gsub("#{collection_id}-order-", "")
84
84
  order_hash[param_key.gsub("#{collection_id}-order-", "").to_sym] = param_value
@@ -101,7 +101,7 @@ module Matestack::Ui::Core::Collection
101
101
  filtered_count,
102
102
  base_count,
103
103
  data,
104
- context
104
+ controller_params,
105
105
  )
106
106
 
107
107
  @collections[id.to_sym] = collection_config
@@ -109,5 +109,13 @@ module Matestack::Ui::Core::Collection
109
109
  return collection_config
110
110
  end
111
111
 
112
+ # try to get params from either controller params, when called in a rails legacy view or
113
+ # from cells context when called in a matestack app/page/component
114
+ def controller_params
115
+ return context[:params] if defined? context
116
+ return params.to_unsafe_h if defined? params
117
+ raise 'collection component is missing access to params or context'
118
+ end
119
+
112
120
  end
113
121
  end
@@ -16,7 +16,7 @@ const componentDef = {
16
16
  this.ordering[key] = "asc"
17
17
  } else if (this.ordering[key] == "asc") {
18
18
  this.ordering[key] = "desc"
19
- } else if (this.ordering[key] = "desc") {
19
+ } else if (this.ordering[key] == "desc") {
20
20
  this.ordering[key] = undefined
21
21
  }
22
22
  var url;
@@ -1,16 +1,15 @@
1
1
  module Matestack::Ui::Core::Collection::Order
2
2
  class Order < Matestack::Ui::Core::Component::Dynamic
3
+ vue_js_component_name 'matestack-ui-core-collection-order'
3
4
 
4
5
  def setup
5
6
  @component_config = @component_config.except(:data, :paginated_data)
6
7
  end
7
8
 
8
9
  def response
9
- components {
10
- div @tag_attributes do
11
- yield_components
12
- end
13
- }
10
+ div @tag_attributes do
11
+ yield_components
12
+ end
14
13
  end
15
14
 
16
15
  end
@@ -2,11 +2,12 @@ module Matestack::Ui::Core::Collection::Order::Toggle::Indicator
2
2
  class Indicator < Matestack::Ui::Core::Component::Static
3
3
 
4
4
  def response
5
- components {
6
- span @tag_attributes do
7
- plain "{{orderIndicator( '#{@component_config[:key]}', {asc: '#{@component_config[:asc]}', desc: '#{@component_config[:desc]}'} ) }}"
8
- end
9
- }
5
+ span @tag_attributes do
6
+ span attributes: {"v-if": "ordering['#{@component_config[:key]}'] === undefined"}, text: @component_config[:default]
7
+ unescaped "{{
8
+ orderIndicator('#{@component_config[:key]}', { asc: '#{@component_config[:asc]}', desc: '#{@component_config[:desc]}'})
9
+ }}"
10
+ end
10
11
  end
11
12
 
12
13
  end
@@ -0,0 +1,424 @@
1
+ module Matestack::Ui::Core::Component
2
+ class Base < Trailblazer::Cell
3
+ include Matestack::Ui::Core::Cell
4
+ include Matestack::Ui::Core::HasViewContext
5
+ include Matestack::Ui::Core::HtmlAttributes
6
+ include Matestack::Ui::Core::Properties
7
+
8
+ # define html global attributes
9
+ html_attributes *HTML_GLOBAL_ATTRIBUTES, *HTML_EVENT_ATTRIBUTES
10
+
11
+ # probably need to remove for other tests to be green again
12
+ include Matestack::Ui::Core::DSL
13
+
14
+ view_paths << "#{Matestack::Ui::Core::Engine.root}/app/concepts"
15
+ view_paths << "#{::Rails.root}#{'/' unless ::Rails.root.nil?}app/matestack"
16
+
17
+ extend ViewName::Flat
18
+
19
+ attr_reader :children, :yield_components_to, :argument, :options
20
+
21
+ # TODO: Seems the `context` method is defined in Cells, would be
22
+ # easy to move up - question really is how much of cells we're still using?
23
+ def initialize(model = nil, options = {})
24
+ # @model also exists with the same content? Is there any reason we wouldn't
25
+ # wanna use it instead of @argument? There's even a `model` accessor for it
26
+ # TODO
27
+ @argument = model
28
+ @options = options
29
+
30
+ # TODO works around a semantic where if just a hash is passed apparently
31
+ # those are the options
32
+ @options = model.dup if @options.empty? && model.is_a?(Hash)
33
+
34
+ super(model, @options)
35
+ # DSL-relevant
36
+ @children = []
37
+ @current_parent_context = self
38
+ # remember where we need to insert components on yield_components_for usage
39
+ @yield_components_to = nil
40
+
41
+ # TODO: everything beyond this point is probably not needed for the
42
+ # Page subclass
43
+
44
+ # TODO: potentially only used in form like components
45
+ # Suggestion: Introduce a new super class to remove this complexity
46
+ # from the base class.
47
+ @included_config = @options[:included_config]
48
+ # p self.class.name
49
+ # p @included_config
50
+
51
+ # TODO seemingly never accessed? (at least by us)
52
+ # but probably good to expose to have access to current_user & friends
53
+ # #context is defined in `Cell::ViewModel`
54
+ # and it just grabs @options[:context]
55
+ @controller_context = context&.fetch(:controller_context, nil)
56
+
57
+ # Add matestack context, containing controller etc.
58
+ @matestack_context = options.dig(:matestack_context)
59
+
60
+ # TODO: technically only relevant for Dynamic, however it relies on
61
+ # @options being set but must be set before `setup` is called.
62
+ # As both happen in this controller it isn't possible to squeeze
63
+ # it inbetween the super calls in the Dynamic super class.
64
+ #
65
+ # This is the configuration for the VueJS component
66
+ @component_config = @options.except(:context, :children, :url_params, :included_config, :matestack_context, :slots)
67
+
68
+ # TODO: no idea why this is called `url_params` it contains
69
+ # much more than this e.g. almost all params so maybe rename it?
70
+ # will be deprecated in future releases. use the `params` method in order to access query params
71
+ @url_params = context&.[](:params)&.except(:action, :controller, :component_key, :matestack_context)
72
+
73
+ # used when creating the child component tree
74
+ # if true, the block of an async component with a defer value will not be processed
75
+ # this saves serverside computation time on initial page requests where some components
76
+ # should only be resolved in a subsequent component rendering call
77
+ # whithin this subsequent component rendering call, the value is set to false via
78
+ # matestack_set_skip_defer(false)
79
+ @matestack_skip_defer = true
80
+
81
+ # TODO: do we realy have to call this every time on initialize or should
82
+ # it maybe be called more dynamically like its dynamic_tag_attributes
83
+ # equivalent in Dynamic?
84
+ set_tag_attributes
85
+ setup
86
+ validate_options
87
+ end
88
+
89
+ # whithin subsequent component rendering calls, the value is set to false in order to render
90
+ # the content of deferred components
91
+ def matestack_set_skip_defer bool
92
+ @matestack_skip_defer = bool
93
+ end
94
+
95
+ def set_included_config config
96
+ @included_config = config
97
+ end
98
+
99
+ def get_included_config
100
+ @included_config
101
+ end
102
+
103
+ # TODO: modifies/recreates view lookup paths on every invocation?!
104
+ # At least memoize it I guess...
105
+ # better even maybe/probably give a component an (automatic) way to know
106
+ # exactly where its template is probably based on its own file location.
107
+ # Then no lookup/search has to happen.
108
+ def self.prefixes
109
+ _prefixes = super
110
+ modified_prefixes = _prefixes.map do |prefix|
111
+ prefix_parts = prefix.split("/")
112
+ if prefix_parts.last.include?(self.name.split("::")[-1].underscore)
113
+ prefix_parts[0..-2].join("/") + '/'
114
+ else
115
+ prefix
116
+ end
117
+ end
118
+ return modified_prefixes + _prefixes
119
+ end
120
+
121
+ def self.views_dir
122
+ return ""
123
+ end
124
+
125
+ # Special validation logic
126
+ def validate_options
127
+ if defined? self.class::REQUIRED_KEYS
128
+ ActiveSupport::Deprecation.warn("REQUIRED_KEYS is deprecated. Use `require :foo` instead.")
129
+ self.class::REQUIRED_KEYS.each do |key|
130
+ raise "#{self.class.name}: required key '#{key}' is missing" if options[key].nil?
131
+ end
132
+ end
133
+ custom_options_validation
134
+ end
135
+
136
+ def custom_options_validation
137
+ true
138
+ end
139
+
140
+ # custom component setup that doesn't seem to be documented
141
+ # but lots of components use it
142
+ def setup
143
+ true
144
+ end
145
+
146
+ # Setup meant to be overridden to setup data from DB or what not
147
+ # why not just call these functions at the beginning of whatever
148
+ # we'll call the method like:
149
+ #
150
+ # def respone
151
+ # result = i_call_stuff
152
+ # plain result
153
+ # end
154
+ #
155
+ # Seems like it might be more complicated? Not sure probably missing something.
156
+ def prepare
157
+ true
158
+ end
159
+
160
+ # access params like you would do on rails views and controllers
161
+ def params
162
+ if @matestack_context.present? && @matestack_context[:controller].present?
163
+ @matestack_context[:controller].params
164
+ else
165
+ context[:params]
166
+ end
167
+ end
168
+
169
+ ## ------------------ Rendering ----------------
170
+ # Invoked by Cell::ViewModel from Rendering#call
171
+ #
172
+ def show
173
+ raise "subclass responsibility"
174
+ end
175
+
176
+ def to_html
177
+ show
178
+ end
179
+
180
+ def render_content
181
+ # When/if we implement response then our display purely relies on that
182
+ # of our children
183
+ # TODO: this might be another sub class or module for the difference
184
+ # Like Native Component vs. composed component? Unsure. Might also not be worth it.
185
+ if respond_to? :response
186
+ render :children
187
+ else
188
+ # We got a template render it around our children
189
+ render do
190
+ render :children
191
+ end
192
+ end
193
+ end
194
+
195
+ def component_id
196
+ options[:id] ||= nil
197
+ end
198
+
199
+ def js_action name, arguments
200
+ argumentString = arguments.join('", "')
201
+ argumentString = '"' + argumentString + '"'
202
+ [name, '(', argumentString, ')'].join("")
203
+ end
204
+
205
+ def navigate_to path
206
+ js_action("navigateTo", [path])
207
+ end
208
+
209
+ def get_children
210
+ return options[:children]
211
+ end
212
+
213
+ ## ---------------------- DSL ------------------------------
214
+ # Add a new child when building the component tree.
215
+ #
216
+ # Invoked in 2 ways
217
+ # * directly ass add_child class, args, block
218
+ # * as defined by the DSL methods in `Matestack::Ui::Core::Component::Registry`
219
+ # which does the same but allows the nicer DSL methods on top of it
220
+ #
221
+ # add_child only builds up the whole ruby component structure. Rendering is done
222
+ # in a later step by calling `#show` on the component where you want to start
223
+ # rendering.
224
+ def add_child(child_class, *args, &block)
225
+
226
+ # when the child is an async or isolate component like shown below, only render its wrapper
227
+ # and skip its content on normal page rendering call indicated by @matestack_skip_defer == true
228
+ # Example: async defer: 1000 { plain "I should be deferred" }
229
+ # the component will triger a subsequent component rendering call after 1000ms
230
+ # the main renderer will then set @matestack_skip_defer to false which allows processing
231
+ # the childs content in order to respond to the subsequent component rendering call with
232
+ # the childs content. In this case: "I should be deferred"
233
+ skip_deferred_child_response = false
234
+ if child_class <= Matestack::Ui::Core::Async::Async || child_class < Matestack::Ui::Core::Isolated::Isolated
235
+ if args.any? { |arg| arg[:defer].present? } && @matestack_skip_defer == true
236
+ skip_deferred_child_response = true
237
+ end
238
+ end
239
+
240
+ # check only allowed keys are passed to isolated components
241
+ if child_class < Matestack::Ui::Core::Isolated::Isolated
242
+ unless args.empty? || args[0].keys.all? { |key| [:defer, :public_options, :rerender_on, :init_on, :rerender_delay, :matestack_context].include? key }
243
+ raise "isolated components can only take params in a public_options hash, which will be exposed to the client side in order to perform an async request with these params."
244
+ end
245
+ if args.any? { |arg| arg[:init_on].present? } && @matestack_skip_defer == true
246
+ skip_deferred_child_response = true
247
+ end
248
+ end
249
+
250
+ if self.class < Matestack::Ui::Core::Isolated::Isolated
251
+ parent_context_included_config = @current_parent_context.get_included_config || {}
252
+ parent_context_included_config.merge!({ isolated_parent_class: self.class.name })
253
+ args_with_context = add_context_to_options(args,parent_context_included_config)
254
+ else
255
+ args_with_context = add_context_to_options(args, @current_parent_context.get_included_config)
256
+ end
257
+
258
+ child = child_class.new(*args_with_context)
259
+
260
+ # set the current @matestack_skip_defer state on the child instance
261
+ # otherwise nested deferred components would never be rendered as
262
+ # @matestack_skip_defer is true by default
263
+ child.matestack_set_skip_defer(@matestack_skip_defer)
264
+
265
+ @current_parent_context.children << child
266
+
267
+ # skip childs body if it should be deferred
268
+ # only the wrapping structure is rendered in this case
269
+ unless skip_deferred_child_response
270
+ child.prepare
271
+ child.response if child.respond_to?(:response)
272
+
273
+ if child_class == Matestack::Ui::Core::Form::Form
274
+ included_config = args.select { |arg| arg.is_a?(Hash) ? arg[:for] : nil }[0]
275
+ end
276
+ execute_child_block(child, included_config, block) if block
277
+ end
278
+
279
+ child
280
+ end
281
+
282
+ # compatibility layer to old-school (not needed anymore)
283
+ def components(&block)
284
+ instance_eval &block
285
+ end
286
+
287
+ # TODO: partial is weird, I highly recommend removing it
288
+ # it exists in basically 2 forms, one that is basically `send`
289
+ # the other just executes the block it's given.
290
+ # Same thing can now be achieved through simple method calls
291
+ def partial(*args)
292
+ if block_given?
293
+ yield
294
+ else
295
+ send(*args)
296
+ end
297
+ end
298
+
299
+ # slot allows generating content in one component and passing it to another
300
+ #
301
+ # It's a 2 purpose method (might be redone):
302
+ # * with a block creates the children to be inserted
303
+ # * without a block it inserts the children at the current point
304
+ #
305
+ #
306
+ def slot(slot_content = [], &block)
307
+ if block_given?
308
+ create_slot_children_to_be_inserted(block)
309
+ else
310
+ # at this point the children are completely built, we just need
311
+ # to insert them into the tree at the right spot (which is marked
312
+ # by where we are currently called hence @current_parent_context)
313
+ @current_parent_context.children.concat(slot_content)
314
+ end
315
+ end
316
+
317
+ # TODO the implementation is simple, but reasoning about is quite
318
+ # complex imo. The main reason is that `yield_components` has no
319
+ # access to the block. Of course that could be solved by making
320
+ # it an instance variable. Might be nicer if we could do
321
+ # `def response(&block)`
322
+ # Also:
323
+ # * right now only works with one yield_components, would break with
324
+ # two that might be nice to raise/warn about
325
+ #
326
+ # The biggest trick this pulls is in execte_child_block where the
327
+ # parent context is shifted to whatever this points to, so that it's
328
+ # inserted at the right point.
329
+ def yield_components
330
+ @yield_components_to = @current_parent_context
331
+ end
332
+
333
+ private
334
+
335
+ # This should be simpler, all it does is try to figure out where the hash/option
336
+ # argument goes and put context in it
337
+ # Partially caused by the behavior that we have 2 initialize args and it's unclear
338
+ # which one should be an options hash as both `plain "hello"` and `div id: "lala"`
339
+ # should work currently
340
+ def add_context_to_options(args, included_config=nil)
341
+ case args.size
342
+ when 0 then [
343
+ {
344
+ context: context,
345
+ included_config: included_config,
346
+ }
347
+ ]
348
+ when 1 then
349
+ arg = args.first
350
+ if arg.is_a?(Hash)
351
+ arg[:context] = context
352
+ arg[:included_config] = included_config
353
+ arg[:matestack_context] = @matestack_context
354
+ [arg]
355
+ else
356
+ [arg, {context: context, included_config: included_config, matestack_context: @matestack_context}]
357
+ end
358
+ when 2 then
359
+ if args[1] == :include
360
+ if args.first.is_a?(Hash)
361
+ args.first[:context] = context
362
+ args.first[:included_config] = included_config
363
+ args.first[:matestack_context] = @matestack_context
364
+ [args.first]
365
+ end
366
+ else
367
+ args[1][:context] = context
368
+ args[1][:included_config] = included_config
369
+ args[1][:matestack_context] = @matestack_context
370
+ [args.first, args[1]]
371
+ end
372
+ else
373
+ raise "too many child arguments what are you doing?"
374
+ end
375
+ end
376
+
377
+ def execute_child_block(child, included_config=nil, block)
378
+ previous_parent_context = @current_parent_context
379
+ begin
380
+ @current_parent_context = child.yield_components_to || child
381
+ @current_parent_context.set_included_config(included_config) if included_config.present?
382
+ instance_eval(&block)
383
+ ensure
384
+ @current_parent_context = previous_parent_context
385
+ end
386
+ end
387
+
388
+ def create_slot_children_to_be_inserted(block)
389
+ # Basically works through:
390
+ # 1. create a fake parent (execution_parent_proxy)
391
+ # 2. set it as the current parrent
392
+ # 3. evaluate the block in the context in which it was defined
393
+ # to have access to methods/instance variables
394
+ # 4. make sure parent context is the previous one again
395
+ # 5. return the children we added to our "fake parent" so
396
+ # that they can be inserted wherever again
397
+ execution_parent_proxy = Base.new()
398
+ previous_parent_context = @current_parent_context
399
+ @current_parent_context = execution_parent_proxy
400
+
401
+ begin
402
+ instance_eval(&block)
403
+ ensure
404
+ @current_parent_context = previous_parent_context
405
+ end
406
+
407
+ execution_parent_proxy.children
408
+ end
409
+
410
+ ## ------------------------ Also Rendering ---------------------
411
+ # common attribute handling for tags/components
412
+ def set_tag_attributes
413
+ default_attributes = {
414
+ id: component_id,
415
+ class: options[:class]
416
+ }
417
+ unless options[:attributes].nil?
418
+ default_attributes.merge!(options[:attributes])
419
+ end
420
+
421
+ @tag_attributes = default_attributes
422
+ end
423
+ end
424
+ end