Module: Glimmer::Web::Component

Includes:
DataBinding::ObservableModel
Included in:
AddressForm, AddressPage, ButtonCounter, ContactForm, ContactTable, HelloFormMvp, HelloObserver, HelloParagraph, NewTodoForm, TodoFilters, TodoInput, TodoList, TodoListItem, TodoMvc, TodoMvcFooter
Defined in:
lib/glimmer/web/component.rb

Defined Under Namespace

Modules: ClassMethods, GlimmerSupersedable

Constant Summary collapse

ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE =
proc do
  class << self
    def inherited(subclass)
      Glimmer::Web::Component.add_component_keyword_to_classes_map_for(subclass)
      subclass.class_eval(&Glimmer::Web::Component::ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE)
    end
  end
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object



329
330
331
332
333
334
335
336
337
# File 'lib/glimmer/web/component.rb', line 329

def method_missing(method_name, *args, &block)
  if can_handle_observation_request?(method_name)
    handle_observation_request(method_name, block)
  elsif markup_root.respond_to?(method_name, true)
    markup_root.send(method_name, *args, &block)
  else
    super(method_name, *args, &block)
  end
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



179
180
181
# File 'lib/glimmer/web/component.rb', line 179

def args
  @args
end

#markup_rootObject (readonly)

Returns the value of attribute markup_root.



179
180
181
# File 'lib/glimmer/web/component.rb', line 179

def markup_root
  @markup_root
end

#optionsObject (readonly)

Returns the value of attribute options.



179
180
181
# File 'lib/glimmer/web/component.rb', line 179

def options
  @options
end

#parentObject (readonly) Also known as: parent_proxy

Returns the value of attribute parent.



179
180
181
# File 'lib/glimmer/web/component.rb', line 179

def parent
  @parent
end

Class Method Details

.add_component_keyword_to_classes_map_for(component_class) ⇒ Object



152
153
154
155
156
157
# File 'lib/glimmer/web/component.rb', line 152

def add_component_keyword_to_classes_map_for(component_class)
  keywords_for_class(component_class).each do |keyword|
    Glimmer::Web::Component.component_keyword_to_classes_map[keyword] ||= []
    Glimmer::Web::Component.component_keyword_to_classes_map[keyword] << component_class
  end
end

.component_keyword_to_classes_mapObject



164
165
166
# File 'lib/glimmer/web/component.rb', line 164

def component_keyword_to_classes_map
  @component_keyword_to_classes_map ||= reset_component_keyword_to_classes_map
end

.for(underscored_component_name) ⇒ Object



142
143
144
145
146
147
148
149
150
# File 'lib/glimmer/web/component.rb', line 142

def for(underscored_component_name)
  component_classes = Glimmer::Web::Component.component_keyword_to_classes_map[underscored_component_name]
  if component_classes.nil? || component_classes.empty?
    Glimmer::Config.logger.debug {"#{underscored_component_name} has no Glimmer web component class!" }
    nil
  else
    component_class = component_classes.first
  end
end

.included(klass) ⇒ Object



132
133
134
135
136
137
138
139
140
# File 'lib/glimmer/web/component.rb', line 132

def included(klass)
  if !klass.ancestors.include?(GlimmerSupersedable)
    klass.extend(ClassMethods)
    klass.include(Glimmer)
    klass.prepend(GlimmerSupersedable)
    Glimmer::Web::Component.add_component_keyword_to_classes_map_for(klass)
    klass.class_eval(&Glimmer::Web::Component::ADD_COMPONENT_KEYWORDS_UPON_INHERITANCE)
  end
end

.interpretation_stackObject



172
173
174
# File 'lib/glimmer/web/component.rb', line 172

def interpretation_stack
  @interpretation_stack ||= []
end

.keywords_for_class(component_class) ⇒ Object



159
160
161
162
# File 'lib/glimmer/web/component.rb', line 159

def keywords_for_class(component_class)
  namespaces = component_class.to_s.split(/::/).map(&:underscore).reverse
  namespaces.size.times.map { |n| namespaces[0..n].reverse.join('__') }
end

.reset_component_keyword_to_classes_mapObject



168
169
170
# File 'lib/glimmer/web/component.rb', line 168

def reset_component_keyword_to_classes_map
  @component_keyword_to_classes_map = {}
end

Instance Method Details

#add_observer(observer, attribute_name) ⇒ Object



256
257
258
259
260
261
262
# File 'lib/glimmer/web/component.rb', line 256

def add_observer(observer, attribute_name)
  if has_instance_method?(attribute_name)
    super(observer, attribute_name)
  else
    @markup_root.add_observer(observer, attribute_name)
  end
end

#attribute_setter(attribute_name) ⇒ Object



293
294
295
# File 'lib/glimmer/web/component.rb', line 293

def attribute_setter(attribute_name)
  "#{attribute_name}="
end

#bind_content(*binding_args, &content_block) ⇒ Object



310
311
312
# File 'lib/glimmer/web/component.rb', line 310

def bind_content(*binding_args, &content_block)
  @markup_root&.bind_content(*binding_args, &content_block)
end

#can_add_observer?(attribute_name) ⇒ Boolean

Returns:

  • (Boolean)


252
253
254
# File 'lib/glimmer/web/component.rb', line 252

def can_add_observer?(attribute_name)
  has_instance_method?(attribute_name) || has_instance_method?("#{attribute_name}?") || @markup_root.can_add_observer?(attribute_name)
end

#can_handle_observation_request?(observation_request) ⇒ Boolean

Returns:

  • (Boolean)


232
233
234
235
236
237
238
239
240
# File 'lib/glimmer/web/component.rb', line 232

def can_handle_observation_request?(observation_request)
  observation_request = observation_request.to_s
  result = false
  if observation_request.start_with?('on_updated_')
    property = observation_request.sub(/^on_updated_/, '')
    result = can_add_observer?(property)
  end
  result || markup_root&.can_handle_observation_request?(observation_request)
end

#content(*args, &block) ⇒ Object

Returns content block if used as an attribute reader (no args) Otherwise, if a block is passed, it adds it as content to this Glimmer web component



316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/glimmer/web/component.rb', line 316

def content(*args, &block)
  if args.empty?
    if block_given?
      Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Web::ComponentExpression.new, self.class.keyword, &block)
    else
      @content
    end
  else
    # delegate to GUI DSL ContentExpression
    super
  end
end

#data_bind(property, model_binding) ⇒ Object



306
307
308
# File 'lib/glimmer/web/component.rb', line 306

def data_bind(property, model_binding)
  @markup_root&.data_bind(property, model_binding)
end

#get_attribute(attribute_name) ⇒ Object



285
286
287
288
289
290
291
# File 'lib/glimmer/web/component.rb', line 285

def get_attribute(attribute_name)
  if has_instance_method?(attribute_name)
    send(attribute_name)
  else
    @markup_root.get_attribute(attribute_name)
  end
end

#handle_observation_request(observation_request, block) ⇒ Object



242
243
244
245
246
247
248
249
250
# File 'lib/glimmer/web/component.rb', line 242

def handle_observation_request(observation_request, block)
  observation_request = observation_request.to_s
  if observation_request.start_with?('on_updated_')
    property = observation_request.sub(/^on_updated_/, '') # TODO look into eliminating duplication from above
    add_observer(DataBinding::Observer.proc(&block), property) if can_add_observer?(property)
  else
    markup_root.handle_observation_request(observation_request, block)
  end
end

#has_attribute?(attribute_name, *args) ⇒ Boolean

Returns:

  • (Boolean)


264
265
266
267
# File 'lib/glimmer/web/component.rb', line 264

def has_attribute?(attribute_name, *args)
  has_instance_method?(attribute_setter(attribute_name)) ||
    @markup_root.has_attribute?(attribute_name, *args)
end

#has_instance_method?(method_name) ⇒ Boolean

This method ensures it has an instance method not coming from Glimmer DSL

Returns:

  • (Boolean)


278
279
280
281
282
283
# File 'lib/glimmer/web/component.rb', line 278

def has_instance_method?(method_name)
  respond_to?(method_name) and
    !markup_root&.respond_to?(method_name) and
    !method(method_name)&.source_location&.first&.include?('glimmer/dsl/engine.rb') and
    !method(method_name)&.source_location&.first&.include?('glimmer/web/element_proxy.rb')
end

#initialize(parent, args, options, &content) ⇒ Object

Raises:

  • (Glimmer::Error)


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/glimmer/web/component.rb', line 182

def initialize(parent, args, options, &content)
  Component.interpretation_stack.push(self)
  @parent = parent
  options = args.delete_at(-1) if args.is_a?(Array) && args.last.is_a?(Hash)
  if args.is_a?(Hash)
    options = args
    args = []
  end
  options ||= {}
  @args = args
  options ||= {}
  @options = self.class.options.merge(options)
  @content = Util::ProcTracker.new(content) if content
  execute_hooks('before_render')
  markup_block = self.class.instance_variable_get("@markup_block")
  raise Glimmer::Error, 'Invalid Glimmer web component for having no markup! Please define markup block!' if markup_block.nil?
  @markup_root = instance_exec(&markup_block)
  @markup_root.options[:parent] = options[:parent] if !options[:parent].nil?
  @parent ||= @markup_root.parent
  raise Glimmer::Error, 'Invalid Glimmer web component for having an empty markup! Please fill markup block!' if @markup_root.nil?
  if options[:render] != false
    execute_hooks('after_render')
  else
    on_render_listener = proc { execute_hooks('after_render') }
    @markup_root.handle_observation_request('on_render', on_render_listener)
  end
  
  # TODO adapt for web
  observer_registration_cleanup_listener = proc do
    observer_registrations.compact.each(&:deregister)
    observer_registrations.clear
  end
  @markup_root.handle_observation_request('on_remove', observer_registration_cleanup_listener)
  post_add_content if content.nil?
end

#local_respond_to?Object



339
# File 'lib/glimmer/web/component.rb', line 339

alias local_respond_to? respond_to_missing?

#observer_registrationsObject

This stores observe keyword registrations of model/attribute observers



228
229
230
# File 'lib/glimmer/web/component.rb', line 228

def observer_registrations
  @observer_registrations ||= []
end

#post_add_contentObject



223
224
225
# File 'lib/glimmer/web/component.rb', line 223

def post_add_content
  Component.interpretation_stack.pop
end

#post_initialize_child(child) ⇒ Object

Subclasses may override to perform post initialization work on an added child



219
220
221
# File 'lib/glimmer/web/component.rb', line 219

def post_initialize_child(child)
  # No Op by default
end

#removeObject



302
303
304
# File 'lib/glimmer/web/component.rb', line 302

def remove
  @markup_root&.remove
end

#render(parent: nil, custom_parent_dom_element: nil, brand_new: false) ⇒ Object



297
298
299
300
# File 'lib/glimmer/web/component.rb', line 297

def render(parent: nil, custom_parent_dom_element: nil, brand_new: false)
  # this method is defined to prevent displaying a harmless Glimmer no keyword error as an annoying useless warning
  @markup_root&.render(parent: parent, custom_parent_dom_element: custom_parent_dom_element, brand_new: brand_new)
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


340
341
342
343
344
# File 'lib/glimmer/web/component.rb', line 340

def respond_to_missing?(method_name, include_private = false)
  super(method_name, include_private) or
    can_handle_observation_request?(method_name) or
    markup_root.respond_to?(method_name, include_private)
end

#set_attribute(attribute_name, *args) ⇒ Object



269
270
271
272
273
274
275
# File 'lib/glimmer/web/component.rb', line 269

def set_attribute(attribute_name, *args)
  if has_instance_method?(attribute_setter(attribute_name))
    send(attribute_setter(attribute_name), *args)
  else
    @markup_root.set_attribute(attribute_name, *args)
  end
end