# backtick_javascript: true module GxG module Gui module Vdom @@CssCache = [] @@CssRuleProperties = {} # def self.load_font(font_profile=nil) if font_profile.is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) if font_profile.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) font_profile = font_profile.unpersist() end if font_profile[:resource_type] == "font-face" thefamily = (font_profile[:font_family] || "Unknown").to_s thestyle = (font_profile[:font_style] || "normal").to_s theweight = (font_profile[:font_weight] || 400).to_s if font_profile[:format] fontformat = font_profile[:format].to_s.downcase else case File.extname(font_profile[:source]).downcase when ".ttf", ".otf" fontformat = "truetype" when ".eot" fontformat = "embedded-opentype" when ".svg" fontformat = "svg" when ".woff" fontformat = "woff" when ".woff2" fontformat = "woff2" else fontformat = "unknown" end end if ["sftp:","ftp:/","http:", "https"].include?(font_profile[:source][0..4].downcase) # reach out over the internet for the resource source_url = font_profile[:source] else # search local site at resource path source_url = ::GxG::CONNECTION.relative_url().to_s + font_profile[:source] # source_url = GxG::DISPLAY_DETAILS[:object].site_asset(font_profile[:source]) end # puts "Source: #{source_url.inspect}" # %x{ var family = #{thefamily.to_s}; var sourceurl = "url('" + #{source_url} + "')"; var theformat = "format('" + #{fontformat} + "')"; if (theformat == "format('unknown')") { var sourcestring = sourceurl; } else { var sourcestring = (sourceurl + " " + theformat); }; var theweight = #{theweight}; var thestyle = #{thestyle}; var thefont = new FontFace(family, sourceurl, {weight: theweight, style: thestyle}); thefont.load().then(function(loaded_face) { document.fonts.add(loaded_face); }).catch(function(error) { alert(error); }); } true else log_warn("You attempted to load a non-font resource as a font: #{font_profile.inspect}") false end else log_warn("You attempted to load a font with the wrong value: #{font_profile.inspect}") false end end # class CSSRule # Provide EASY interface to native CSS Rule. def initialize(the_native_rule) @native_rule = the_native_rule @name = (`#{@native_rule}.selectorText` || "").to_s.to_sym end # def native_object() @native_rule end # def inspect() "" end # def name() @name end # def keys() GxG::Gui::Vdom::valid_properties() end # def [](the_key) `#{@native_rule}.style[#{the_key}]` end # def []=(the_key, the_value) # `#{@native_rule}.style[#{the_key}] = #{(the_value || "")}` the_value end # def merge(properties={}) properties.keys.each do |the_key| self[(the_key)] = properties[(the_key)] end self end # end # def self.style_sheet() result = nil sheets = `document.styleSheets.length`.to_i (0..(sheets - 1)).each do |sheet_index| the_sheet = `document.styleSheets[#{sheet_index}]` if the_sheet if `#{the_sheet}.href`.to_s.include?("/themes/page.css") result = the_sheet break end end end result end # def self.refresh_css_cache() the_style_sheet = GxG::Gui::Vdom::style_sheet() if the_style_sheet new_cache = [] begin ruleset = `#{the_style_sheet}.cssRules` rescue Exception => the_error ruleset = `#{the_style_sheet}.rules` end if ruleset rules_count = (`#{ruleset}.length`.to_i) if rules_count > 0 end_index = ((`#{ruleset}.length`.to_i) - 1) else end_index = 0 end (0..(end_index)).each do |indexer| begin native_rule = `#{the_style_sheet}.cssRules[#{indexer}]` rescue Exception => the_error native_rule = `#{the_style_sheet}.rules[#{indexer}]` end if native_rule new_cache << GxG::Gui::Vdom::CSSRule.new(native_rule) end end end @@CssCache = new_cache end true end # def self.valid_properties() @@CssRuleProperties.keys end # def self.get_property_name(the_key=nil) result = nil if the_key.is_a?(::Symbol) result = @@CssRuleProperties[(the_key)] end result end # def self.rules() result = [] @@CssCache.each do |entry| result << entry.name end result end # def self.rule_names() result = [] @@CssCache.each do |entry| result << entry.name end result end # def self.find_rule(the_rule_selector=nil) result = [] @@CssCache.each do |entry| if entry.name == (the_rule_selector.to_s.to_sym) result << entry end end result end # def self.clear_rules() list = ::GxG::Gui::Vdom::rule_names() list.each do |the_rule_name| unless the_rule_name == :".hidden" ::GxG::Gui::Vdom::remove_rule(the_rule_name) end end true end # def self.set_rule(the_rule_selector=nil, the_properties={}, options={:merge => true}) # find existing # remove existing # create or re-create the_style_sheet = GxG::Gui::Vdom::style_sheet() if the_style_sheet found = GxG::Gui::Vdom::find_rule(the_rule_selector) if found.size > 0 GxG::Gui::Vdom::remove_rule(the_rule_selector) end # create it the_site_prefix = ::GxG::CONNECTION.relative_url() rule_string = "{ " the_properties.keys.each do |property_key| the_value = the_properties[(property_key)].to_s if the_value.include?("url(") end_point = the_value.index(")") the_path = the_value[4..(end_point - 1)] if the_path[0] == "'" || the_path[0] == '"' the_path = the_path[1..-1] end if the_path[-1] == "'" || the_path[-1] == '"' the_path = the_path[0..-2] end unless the_path.include?("data:") || the_path.include?("http:") || the_path.include?("https:") unless (the_path[(0..(the_site_prefix.size - 1))] == the_site_prefix) the_value = ("url('" + File.expand_path(the_site_prefix + "/" + the_path) + "')").gsub("//","/") end end end rule_string = rule_string + ("#{property_key.to_s}: #{the_value.to_s}; ") end rule_string = rule_string + " }" if `#{the_style_sheet}.insertRule(#{the_rule_selector.to_s + rule_string.to_s},(#{the_style_sheet}.cssRules.length))`.to_i == `(#{the_style_sheet}.cssRules.length - 1)`.to_i begin native_rule = `#{the_style_sheet}.cssRules[(#{the_style_sheet}.cssRules.length - 1)]` rescue Exception => the_error native_rule = `#{the_style_sheet}.rules[(#{the_style_sheet}.cssRules.length - 1)]` end @@CssCache << GxG::Gui::Vdom::CSSRule.new(native_rule) true else log_warn("GxG Style Sheet Rule creation failed..") false end else log_warn("GxG Style Sheet not found.") false end # end # def self.remove_rule(the_rule_selector=nil) found_indexes = [] @@CssCache.each_with_index do |entry, index| if entry.name == the_rule_selector.to_s.to_sym || entry.name == the_rule_selector.to_s found_indexes << index end end found = [] found_indexes.reverse.each do |the_index| found << @@CssCache.delete_at(the_index) end if found.size > 0 found.each do |the_rule| native_rule = the_rule.native_object() the_sheet = GxG::Gui::Vdom::style_sheet() if the_sheet rule_count = `#{the_sheet}.cssRules.length`.to_i if rule_count > 0 (0..(rule_count - 1)).to_a.reverse.each do |rule_index| other_native_rule = `#{the_sheet}.cssRules[#{rule_index}]` if other_native_rule if `#{native_rule}.selectorText == #{other_native_rule}.selectorText` `#{the_sheet}.deleteRule(#{rule_index})` end end end end end end true else false end end # # Derivitive of Ferro Virtual Dom gem (greatly simplified) # -------------------------------------------------------------------------------------------------------------------------------------- module ElementMethods # Internal Methods: def _before_create;end def before_create;end # ## Calls the factory to create the DOM element. def create if @domtype @element = factory.create_element(self, @domtype, @parent, @options) end end # def _after_create self.update_classes _stylize end # def after_create;end # Override this method to return a Hash of styles. # Hash-key is the CSS style name, hash-value is the CSS style value. def style @style ||= {} end def _stylize styles = style if styles.class == Hash set_attribute( 'style', styles.map { |k, v| "#{k}:#{v};" }.join ) end end # Override this method to continue the MOM creation process. def cascade;end # ## Creation def creation _before_create before_create create _after_create after_create _stylize cascade end # Add a child element. # # @param [String] name A unique name for the element that is not # in RESERVED_NAMES # @param [String] element_class Ruby class name for the new element # @param [Hash] options Options to pass to the element. Any option key # that is not recognized is set as an attribute on the DOM element. # Recognized keys are: # prepend Prepend the new element before this DOM element # content Add the value of content as a textnode to the DOM element def add_child(options={},other_data={}) new_child = nil if options[:component].to_s.downcase.to_sym == :unknown raise "Unknown Component Type" else element_class = GxG::Gui::component_class(options[:component].to_s.downcase.to_sym) if element_class.is_a?(::Class) new_child = element_class.new(self,options,other_data) @children[(new_child.title.to_s.to_sym)] = new_child # Set :template_uuid if present if other_data[:template_uuid] new_child.settings()[:template_uuid] = other_data[:template_uuid] end # Register Object with Page ::GxG::DISPLAY_DETAILS[:object].register_object(new_child.title, new_child) else raise "Unknown Component Type" end end new_child end # def each_child(&block) @children.values.each do |the_child| block.call(the_child) end end # Recursively iterate all child elements def all_descendants(&block) @process_queue = @children.values.clone while @process_queue.size > 0 do entry = @process_queue.shift if entry block.call(entry) if entry.children.values.size > 0 entry.each_child do |the_child| @process_queue << the_child end end end end end # Remove all child entries. def forget_children clearing_queue = @children.values while clearning_queue.size > 0 entry = clearing_queue.shift if entry entry.children.values.each do |the_child| clearing_queue << the_child end page.unregister_object(entry.title.to_s) end end @children = {} end # # Remove a specific child entry. def forget_child(the_title) page.unregister_object(the_title) @children.delete(the_title.to_s.to_sym) end # # Content of the entity (similar to how DetachedHash works) def entity_content() @children.values end # Remove a DOM element. def destroy self.every_child do |the_child| the_child.remove_listeners page.unregister_object(the_child.title.to_s) end self.remove_listeners parent.forget_child(@title.to_s) `#{parent.element}.removeChild(#{element})` true end # ### Path supports: def vdom_path() result = "/" # if @parent path_array = [] queue = [(self)] while queue.size > 0 do entry = queue.shift() if entry unless page() == entry path_array << entry.title.to_s end if entry.parent() queue << entry.parent() end end end if path_array.size > 0 result = ("/" + path_array.reverse.join("/")) end end # result end # def vdom_object(the_path=nil) # access the object tree with a path result = nil # if the_path the_path = the_path.to_s # if the_path[(0..1)] == "./" || the_path[(0..1)] == ".." the_path = File.expand_path(the_path, self.vdom_path()) end # path_array = the_path.split("/") object_array = [] path_array.each_with_index do |entry, indexer| if entry == "" if indexer == 0 object_array[(indexer)] = page() else break end else if indexer > 0 if object_array[(indexer - 1)] object_array[(indexer - 1)].children.each_pair do |object_name, the_object| if object_name == path_array[(indexer)] object_array[(indexer)] = the_object break end end end end end end if object_array.last result = object_array.last end end # result end # ### # Getter for children. def method_missing(method_name, *args, &block) if @children.has_key?(method_name) @children[method_name] else super end end # ### Element identity # Get the id of the elements corresponding DOM element # # @return [String] The id def dom_id # "_#{self.object_id}" self.uuid.to_s end # def title() @title end # # def title=(the_title=nil) # if the_title.is_any?(::String, ::Symbol) # @title = the_title # end # end # # ### Universal Element Style, Class, and Attribute Methods: def update_classes() new_classes = "" class_list = `#{@element}.className`.split(" ") class_list.each_with_index do |the_classname, indexer| unless @states.keys.include?(the_classname.to_s.to_sym) new_classes = (new_classes + the_classname) unless the_classname == class_list.last new_classes = (new_classes + " ") end end end if @states.keys.size > 0 new_classes = (new_classes + " ") end @states.each_pair do |the_state, the_value| if the_value == true new_classes = (new_classes + the_state.to_s) unless @states.keys.last == the_state new_classes = (new_classes + " ") end else # `#{@element}.classList.remove(#{the_state.to_s})` end end `#{@element}.className = #{new_classes.to_s}` true end # Delete a key from the elements options hash. Will be renamed # to option_delete. # # @param [key] key Key of the option hash to be removed # @param [value] default Optional value to use if option value is nil # @return [value] Return the current option value or value of # default parameter def option_replace(key, default = nil) value = @options[key] || default @options.delete(key) if @options.has_key?(key) value end # def set_state(the_state, the_value=false) if the_value == true @states[(the_state.to_s.to_sym)] = true else @states[(the_state.to_s.to_sym)] = false end self.update_classes end # def set_states(the_state_list={}) the_state_list.each_pair do |the_state, the_value| if the_value == true @states[(the_state.to_s.to_sym)] = true else @states[(the_state.to_s.to_sym)] = false end end self.update_classes end # def get_state(the_state) (@states[(the_state.to_s.to_sym)] || false) end # def get_states() @states end # def toggle_state(the_state) self.set_state(the_state,(! self.get_state(the_state))) end # def set_application(the_application=nil) @application = the_application end # def application() @application end # Determine if the state is active # # @param [String] state The state name # @return [Boolean] The state value def state_active?(the_state) @states.keys.include?(the_state.to_s.to_sym) end # @param [Array] states An array of state names to add to the # element. All disabled (false) state initially. def add_states(states) states.each do |state| add_state(state) end end # Add a state to the element. A state toggles a CSS class with the # same (dasherized) name as the state. # If the state is thruthy (not nil or true) the CSS class is added # to the element. Otherwise the CSS class is removed. # # @param [String] state The state name to add to the element # @param [value] value The initial enabled/disabled state value def add_state(state, value = false) self.set_state(state,value) end # Get the current html value of the element # # @return [String] The html value def value `#{@element}.innerHTML` end # Get the current text content of the element # # @return [String] The text value def get_text `#{@element}.textContent` end # Set the current html value of the element # Useful for input elements # # @param [String] The new value def value=(value) `#{@element}.value = #{value}` end # Set the current text content of the element # # @param [String] value The new text value def set_text(value) # https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent `#{@element}.textContent = #{value}` end # Set the current html content of the element. Use with caution if # the html content is not trusted, it may be invalid or contain scripts. # # @param [String] raw_html The new html value def html(raw_html) `#{@element}.innerHTML = #{raw_html}` end # # def set_style(the_style={}) new_style = "" if the_style.is_a?(::Hash) the_style.each_pair do |property,value| new_style = (new_style + "#{property.to_s}: " + "#{value.to_s};") end `#{@element}.setAttribute("style", #{new_style}); ` true else false end end # def merge_style(the_style={}) if the_style.is_a?(::Hash) the_style.each_pair do |property,value| `#{element}.style[#{property}]=#{value.to_s}` end true else false end end # def set_bg_color(the_color=0) self.merge_style({:"background-color" => "##{the_color.to_s}"}) end # def get_attribute(the_attribute=nil) result = "" if the_attribute the_element = self.element the_value = nil # Boolean Attributes: if attribute exists, can be a boolean. # if present returns "true" if null (since it exists), else other value %x{ if (the_element.getAttributeNames().includes(the_attribute)) { if (the_element.getAttribute(the_attribute) != null) { the_value = the_element.getAttribute(the_attribute); } else { the_value = true; }; }; } result = the_value.to_s end result end # def set_attribute(the_attribute=nil, the_value=nil) result = false if the_attribute if the_value result = `#{self.element}.setAttribute(#{the_attribute},#{the_value})` else # Boolean attribute result = `#{self.element}.setAttribute(#{the_attribute},null)` end end result end # def clear_attribute(the_attribute=nil) result = false if the_attribute `#{self.element}.removeAttribute(#{the_attribute})` result = true end result end def each_child(&block) if block.respond_to?(:call) @children.values.each do |the_child| block.call(the_child) end else @children.values.each end end # def every_child(&block) child_manifest = [] build_queue = [(self)] while build_queue.size > 0 do entry = build_queue.shift if entry if entry.children.keys.size > 0 entry.children.values.each do |the_child| child_manifest << the_child if the_child.children.keys.size > 0 build_queue << the_child end end end end end if block.respond_to?(:call) child_manifest.each do |the_child| block.call(the_child) end else child_manifest.values.each end end # ### Finding Objects def find_child(the_reference=nil) result = nil search_queue = self.children.values while search_queue.size > 0 do entry = search_queue.shift if entry if entry.uuid == the_reference || entry.title == the_reference || entry.component == the_reference || entry.class == the_reference || entry.domtype == the_reference result = entry break end # end end result end # def find_descendant(the_reference=nil) result = nil search_queue = self.children.values while search_queue.size > 0 do entry = search_queue.shift if entry if entry.uuid == the_reference || entry.title == the_reference || entry.component == the_reference || entry.class == the_reference || entry.domtype == the_reference result = entry break end # unless result entry.children.values.each do |the_child| search_queue << the_child end end end end result end # def find_descendants(the_reference=nil) result = [] search_queue = self.children.values while search_queue.size > 0 do entry = search_queue.shift if entry if entry.uuid == the_reference || entry.title == the_reference || entry.component == the_reference || entry.class == the_reference || entry.domtype == the_reference result << entry end # entry.children.values.each do |the_child| search_queue << the_child end end end result end # def find_ancestor(the_reference=nil) result = nil search_queue = [(self.parent)] while search_queue.size > 0 do entry = search_queue.shift if entry if entry.uuid == the_reference || entry.title == the_reference || entry.component == the_reference || entry.class == the_reference || entry.domtype == the_reference result = entry break else unless entry.component == :page search_queue << entry.parent end end end end result end # def find_ancestors(the_reference=nil) result = [] search_queue = [(self.parent)] while search_queue.size > 0 do entry = search_queue.shift if entry if entry.uuid == the_reference || entry.title == the_reference || entry.component == the_reference || entry.class == the_reference || entry.domtype == the_reference result << entry end unless entry.component == :page search_queue << entry.parent end end end result end # end # -------------------------------------------------------------------------------------------------------------------------------------- module EventHandlers # Constants: EVENT_TYPES = [ "abort", "afterprint", "animatedend", "animationiteration", "animationstart", "beforeprint", "beforeunload", "blur", "canplay", "canplaythrough", "change", "click", "contextmenu", "copy", "cut", "dblclick", "drag", "dragend", "dragcenter", "dragleave", "dragover", "dragstart", "drop", "durationchange", "ended", "error", "focus", "focusin", "focusout", "fullscreenchange", "fullscreenerror", "hashchange", "input", "invalid", "keydown", "keypress", "keyup", "load", "loadeddata", "loadedmetadata", "loadstart", "message", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseover", "mouseup", "offline", "online", "open", "pagehide", "pageshow", "paste", "play", "playing", "popstate", "progress", "ratechange", "resize", "reset", "scroll", "search", "seeked", "select", "show", "stalled", "storage", "submit", "suspend", "timeupdate", "toggle", "touchcancel", "touchend", "touchmove", "touchstart", "transitionend", "unload", "volumechange", "waiting", "wheel" ] # EVENT_GENRES = { :click => :mouse, :contextmenu => :mouse, :dblclick => :mouse, :mouseenter => :mouse, :mouseleave => :mouse, :mouseover => :mouse, :mouseup => :mouse, :mousedown => :mouse, :mousemove => :mouse, :mouseout => :mouse, :keydown => :keyboard, :keypress => :keyboard, :keyup => :keyboard, :touchcancel => :touch, :touchmove => :touch, :touchstart => :touch, :touchend => :touch, :animationstart => :animation, :animationend => :animation, :animationiteration => :animation, :animationpause => :animation, :animationresume => :animation, :animationstop => :animation, :input => :input, :drag => :drag, :dragend => :drag, :dragenter => :drag, :dragleave => :drag, :dragover => :drag, :dragstart => :drag, :drop => :drag, :wheel => :wheel, :abort => :ui, :beforeunload => :ui, :error => :ui, :load => :ui, :resize => :ui, :scroll => :ui, :select => :ui, :unload => :ui, :blur => :focus, :focus => :focus, :focusin => :focus, :focusout => :focus, :afterprint => :event, :beforeprint => :event, :canplay => :event, :canplaythrough => :event, :change => :event, :fullscreenchange => :event, :fullscreenerror => :event, :invalid => :event, :loadeddata => :event, :loadedmetadata => :event, :message => :event, :offline => :event, :online => :event, :open => :event, :close => :event, :pause => :event, :play => :event, :playing => :event, :progress => :event, :changerate => :event, :reset => :event, :search => :event, :seek => :event, :seeking => :event, :show => :event, :hide => :event, :stalled => :event, :submit => :event, :suspend => :event, :timeupdate => :event, :toggle => :event, :waiting => :event, :copy => :clipboard, :cut => :clipboard, :paste => :clipboard, :pagehide => :transition, :pageshow => :transition, :popstate => :history, :loadstart => :progress, :storage => :storage, :credentialchanged => :ui } #TODO: look into fully supporting touch screen gleaning functions. EVENT_FIELDS = { :mouse => [:altKey, :button, :buttons, :clientX, :clientY, :ctrlKey, :metaKey, :movementX, :movementY, :offsetX, :offsetY, :pageX, :pageY, :screenX, :screenY, :shiftKey, :which, :detail, :view, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :keyboard => [:altKey, :charCode, :code, :ctrlKey, :metaKey, :isComposing, :key, :keyCode, :location, :shift, :repeat, :shiftKey, :which, :detail, :view, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :touch => [:altKey, :ctrlKey, :metaKey, :shiftKey, :detail, :view, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :animation => [:animationName, :elapsedTime, :pseudoElement, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :input => [:data, :inputType, :isComposing, :detail, :view, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :drag => [:dataTranser, :altKey, :button, :buttons, :clientX, :clientY, :ctrlKey, :metaKey, :movementX, :movementY, :offsetX, :offsetY, :pageX, :pageY, :screenX, :screenY, :shiftKey, :which, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :wheel => [:deltaX, :deltaY, :deltaZ, :deltaMode, :altKey, :button, :buttons, :clientX, :clientY, :ctrlKey, :metaKey, :movementX, :movementY, :offsetX, :offsetY, :pageX, :pageY, :screenX, :screenY, :shiftKey, :which, :detail, :view, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :ui => [:detail, :view,:bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :focus => [:relatedTarget, :detail, :view, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :event => [:bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :transition => [:persisted, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :history => [:state, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :progress => [:lengthComputable, :loaded, :total, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :storage => [:key, :newValue, :oldValue, :storageArea, :url, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp], :clipboard => [:clipboardData, :bubbles, :cancelable, :currentTarget, :defaultPrevented, :eventPhase, :isTrusted, :target, :timeStamp] } # Internal method on def on(the_event_type=nil, captured=false, &block) result = false unless @event_handlers.is_a?(::Hash) @event_handlers = {} @event_listeners = {} end if the_event_type.is_any?(::String, ::Symbol) if block.respond_to?(:call) @event_handlers[(the_event_type.to_s.downcase.to_sym)] = block @event_listeners[(the_event_type.to_s.downcase.to_sym)] = `#{self.method(:event_handler).to_proc}` `#{self.element}.addEventListener(#{the_event_type.to_s.downcase}, #{@event_listeners[(the_event_type.to_s.downcase.to_sym)]}, #{captured})` result = true end end result end # def remove_listener(the_event_type=nil) if @event_handlers[(the_event_type.to_s.downcase.to_sym)] && @event_listeners[(the_event_type.to_s.downcase.to_sym)] `#{self.element}.removeEventListener(#{the_event_type.to_s.downcase},#{@event_listeners[(the_event_type.to_s.downcase.to_sym)]});` @event_handlers.delete(the_event_type.to_s.downcase.to_sym) @event_listeners.delete(the_event_type.to_s.downcase.to_sym) end true end # def remove_listeners() @event_handlers.keys.clone.each do |the_event_type| self.remove_listener(the_event_type) end true end # def event_handler(the_event) # format the event record for travel in the system. # See: https://www.w3schools.com/jsref/dom_obj_event.asp # Setup event type groupings to glean appropriate details from the event. the_type = `#{the_event}.type`.to_s.downcase.to_sym event_record = {} if @event_handlers[(the_type)] genre = (GxG::Gui::Vdom::EventHandlers::EVENT_GENRES[(the_type)] || :event) if genre fields = GxG::Gui::Vdom::EventHandlers::EVENT_FIELDS[(genre)] if fields # %x{ serializer = function thisOne (e) { if (e) { var o = { eventName: e.toString(), altKey: e.altKey, bubbles: e.bubbles, button: e.button, buttons: e.buttons, cancelBubble: e.cancelBubble, cancelable: e.cancelable, clientX: e.clientX, clientY: e.clientY, composed: e.composed, ctrlKey: e.ctrlKey, currentTarget: e.currentTarget ? e.currentTarget.outerHTML : null, defaultPrevented: e.defaultPrevented, detail: e.detail, eventPhase: e.eventPhase, fromElement: e.fromElement ? e.fromElement.outerHTML : null, isTrusted: e.isTrusted, layerX: e.layerX, layerY: e.layerY, metaKey: e.metaKey, movementX: e.movementX, movementY: e.movementY, offsetX: e.offsetX, offsetY: e.offsetY, pageX: e.pageX, pageY: e.pageY, relatedTarget: e.relatedTarget ? e.relatedTarget.outerHTML : null, returnValue: e.returnValue, screenX: e.screenX, screenY: e.screenY, shiftKey: e.shiftKey, sourceCapabilities: e.sourceCapabilities ? e.sourceCapabilities.toString() : null, target: e.target ? e.target.outerHTML : null, timeStamp: e.timeStamp, toElement: e.toElement ? e.toElement.outerHTML : null, type: e.type, view: e.view ? e.view.toString() : null, which: e.which, x: e.x, y: e.y }; return o; }; }; event_record = JSON.stringify(serializer(#{the_event})); } event_record = ::JSON::parse(event_record.to_s, {:symbolize_names => true}) # puts "Got: #{event_record.inspect}" end end # TODO: switch in edit mode to object_selection_component_overly if GxG::DISPLAY_DETAILS[:mode] == :browsing GxG::DISPLAY_DETAILS[:target] = self @event_handlers[(the_type)].call(event_record) else if GxG::DISPLAY_DETAILS[:mode] == :editing if GxG::EDITMASK.keys.include?(@uuid) GxG::DISPLAY_DETAILS[:target] = self @event_handlers[(the_type)].call(event_record) else # ??? # edit object GxG::DISPLAY_DETAILS[:target] = nil page.editor_select_object(self,{}) end else # unsupported mode --> expansion end end # end end # def editmask(options={:original_content => true}) process_queue = [(self)] mask_queue = [(self)] while process_queue.size > 0 do entry = process_queue.shift if entry if options[:original_content] == true if entry.respond_to?(:original_content) entry.original_content.each_pair do |object_name, the_object| process_queue << the_object mask_queue << the_object end end end # entry.content.each_pair do |object_name, the_object| process_queue << the_object mask_queue << the_object end end end mask_queue.each do |the_object| unless GxG::EDITMASK[(the_object.uuid)] GxG::EDITMASK[(the_object.uuid)] = the_object end end true end # def editunmask(options={:original_content => true}) process_queue = [(self)] mask_queue = [(self)] while process_queue.size > 0 do entry = process_queue.shift if entry if options[:original_content] == true if entry.respond_to?(:original_content) entry.original_content.each_pair do |object_name, the_object| process_queue << the_object mask_queue << the_object end end end # entry.content.each_pair do |object_name, the_object| process_queue << the_object mask_queue << the_object end end end mask_queue.each do |the_object| tempobject = GxG::EDITMASK.delete(the_object.uuid) end true end # TODO: fix indent wierdness def set_script(the_script_body="", alternate_target=nil) result = false if the_script_body.size > 0 # begin # if the_script_body.base64? the_script_body = the_script_body.decode64 end # if alternate_target alternate_target.remove_listeners() alternate_target.instance_eval(the_script_body) else self.remove_listeners() instance_eval(the_script_body) end result = true rescue Exception => the_error log_error({:error => the_error, :parameters => {:script => the_script_body}}) end end result end # end # -------------------------------------------------------------------------------------------------------------------------------------- class Animation include GxG::Gui::Vdom::EventHandlers # def initialize(details={}, the_uuid=nil) @event_handlers = {} @event_listeners = {} if details.is_any?(::Hash, ::GxG::Database::DetachedHash) if details[:origin] if details[:origin].is_a?(::GxG::Database::DetachedHash) @origin = details[:origin].unpersist else @origin = details[:origin] end else @origin = nil end # unless details[:destination] raise ArgumentError, "You MUST provide a :destination state for the object." end # if details[:destination].is_a?(::GxG::Database::DetachedHash) @destination = details[:destination].unpersist else @destination = details[:destination] end # if details[:options] @options = {} if details[:options].is_a?(::GxG::Database::DetachedHash) the_options = details[:options].unpersist else the_options = details[:options] end callback_map = {:animationstart => :start, :animationend => :complete, :animationiteration => :update, :animationstop => :stop, :animationpause => :pause, :animationresume => :resume} the_options.each_pair do |the_key, the_value| if callback_map.keys.include?(the_key) if the_value.respond_to?(:call) # Expansion: compile formatted hashes of script records ?? @event_handlers[(the_key)] = the_value @options[(the_key)] = the_value else log_warn("Attempted to pass an invalid object as an event handler. #{the_key.inspect} --> #{the_value.inspect}") end else @options[(the_key)] = the_value end end else @options = nil end # unless details[:targets] raise ArgumentError, "You MUST provide a one or more :targets." end # if details[:targets].is_a?(::GxG::Database::DetachedHash) @target_list = details[:targets].unpersist else @target_list = details[:targets] end end @uuid = (the_uuid || ::GxG::uuid_generate).to_s.to_sym @targets = nil @tweens = [] # self end # def update_targets(data=nil) result = false if data.is_any?(::Hash, ::GxG::Database::DetachedHash) if data.is_a?(::GxG::Database::DetachedHash) @target_list = data.unpersist else @target_list = data end result = true end result end # def update_origin(data=nil) result = false if data.is_any?(::Hash, ::GxG::Database::DetachedHash) if data.is_a?(::GxG::Database::DetachedHash) @origin = data.unpersist else @origin = data end result = true end result end # def update_destination(data=nil) result = false if data.is_any?(::Hash, ::GxG::Database::DetachedHash) if data.is_a?(::GxG::Database::DetachedHash) @destination = data.unpersist else @destination = data end result = true end result end # def update_options(data=nil) result = false if data.is_any?(::Hash, ::GxG::Database::DetachedHash) if data.is_a?(::GxG::Database::DetachedHash) the_options = data.unpersist else the_options = data end @options = {} callback_map = {:animationstart => :start, :animationend => :complete, :animationiteration => :update, :animationstop => :stop, :animationpause => :pause, :animationresume => :resume} the_options.each_pair do |the_key, the_value| if callback_map.keys.include?(the_key) if the_value.respond_to?(:call) # Expansion: compile formatted hashes of script records ?? @event_handlers[(the_key)] = the_value @options[(the_key)] = the_value else log_warn("Attempted to pass an invalid object as an event handler. #{the_key.inspect} --> #{the_value.inspect}") end else @options[(the_key)] = the_value end end result = true end result end # def uuid() @uuid end # # def compile() result = false begin @targets = [] native_targets = `[]` @target_list.keys.each do |the_method| if the_method == :class unless @target_list[:class].is_a?(::String) raise Exception, "You MUST provide :class target as a String, not: #{@target_list[:class].class.inspect}" end @target_list[:class].split(" ").each do |the_classname| if the_classname.size > 0 found_list = `document.getElementsByClassName(#{the_classname})` found_length = `#{found_list}.length`.to_i if found_length > 0 (0..(found_length - 1)).each do |the_index| the_element = `#{found_list}.item(#{the_index})` unless @targets.include?(the_element) @targets << the_element end end end end end end # if the_method == :objects # provide a list of UUIDs of components to include as targets unless @target_list[:objects].is_a?(::Array) raise Exception, "You MUST provide :object target list as an Array, not: #{@target_list[:objects].class.inspect}" end @target_list[:objects].each do |the_reference| found_object = GxG::DISPLAY_DETAILS[:object].find_object(the_reference.to_s.to_sym) if found_object element_id = `#{found_object.element}.attributes.getNamedItem('id') || 0` unless `#{element_id} == 0` found_element = `document.getElementById(#{element_id})` if found_element unless @targets.include?(found_element) @targets << found_element end end end end end end if the_method == :elements # provide a list of native elements to include as targets unless @target_list[:elements].is_a?(::Array) raise Exception, "You MUST provide :elements target list as an Array, not: #{@target_list[:elements].class.inspect}" end @target_list[:elements].each do |the_element| element_id = `#{the_element}.attributes.getNamedItem('id') || 0` unless `#{element_id} == 0` element_id = `#{element_id}.value` found_element = `document.getElementById(#{element_id})` if found_element unless @targets.include?(found_element) @targets << found_element end end end end end if the_method == :assets # Future expansion end end # DEBUG: unless @targets.size > 0 puts "No valid targets resulted." end @targets.each do |the_element| `#{native_targets}.push(#{the_element})` end # if @origin.is_a?(::Hash) if @origin.size > 0 from_state = {} @origin.each_pair do |the_key, the_value| # key translations: if the_key == :attribue || the_key == "attribute" new_key = :attr else new_key = the_key end # from_state[(new_key)] = the_value end else from_state = nil end else from_state = nil end # if @destination.size > 0 to_state = {} @destination.each_pair do |the_key, the_value| # key translations: if the_key == :attribue || the_key == "attribute" new_key = :attr else new_key = the_key end # to_state[(new_key)] = the_value end else raise Exception, "You MUST define a valid :destination state. not: #{@destination.inspect}" end # if @options.is_a?(::Hash) if @options.size > 0 options = {} callback_map = {:animationstart => :start, :animationend => :complete, :animationiteration => :update, :animationstop => :stop, :animationpause => :pause, :animationresume => :resume} @options.each_pair do |the_key, the_value| # Key translations: if callback_map.keys.include?(the_key) # include handlers new_key = callback_map[(the_key)] # if @event_handlers[(the_key)].respond_to?(:call) # FIXME # puts "Setting callback for: #{new_key.inspect}" options[(new_key)] = @event_handlers[(the_key)] end else unless [:start_time].include?(the_key) # TODO: research ALL options and only allow valid options to be set. options[(the_key)] = the_value end end end else options = nil end else options = nil end # @tweens = [] # FIXME: # puts "Compiling Options: #{options.inspect}" @targets.each do |the_target| if from_state if options @tweens << `KUTE.fromTo(#{the_target}, #{from_state.to_n}, #{to_state.to_n}, #{options.to_n})` else @tweens << `KUTE.fromTo(#{the_target}, #{from_state.to_n}, #{to_state.to_n})` end else if options @tweens << `KUTE.to(#{the_target},#{to_state.to_n}, #{options.to_n})` else @tweens << `KUTE.to(#{the_target},#{to_state.to_n})` end end end # result = true rescue Exception => the_error log_error({:error => the_error}) @targets = nil @tweens = [] end result end # def start(the_time=nil) unless @tweens.size > 0 self.compile end unless the_time.is_a?(::Numeric) the_time = `window.performance.now()` end if @tweens.size > 0 @tweens.each do |the_tween| `#{the_tween}.start(#{the_time})` end end end # def stop() if @tweens.size > 0 @tweens.each do |the_tween| `#{the_tween}.stop()` end end end # def pause() if @tweens.size > 0 @tweens.each do |the_tween| `#{the_tween}.pause()` end end end # def resume() if @tweens.size > 0 @tweens.each do |the_tween| `#{the_tween}.resume()` end end end # def included() # return a list of all page components included in this animation result = [] if @targets @targets.each do |the_element| element_id = `#{the_element}.attributes.getNamedItem('id') || 0` unless `#{element_id} == 0` element_id = `#{element_id}.value` found_object = GxG::DISPLAY_DETAILS[:object].find_child(element_id.to_s) if found_object result << found_object end end end end result end # def each_included(&block) if block.respond_to?(:call) self.included.each do |the_object| block.call(the_object) end else self.included.each end end # send events to all target components (not their elements) def broadcast(the_method=nil, the_parameters=nil) self.each_included do |the_object| the_object.send(the_method, the_parameters) end end # end # -------------------------------------------------------------------------------------------------------------------------------------- module AnimationSupport # def animate(animation_parameters=nil) if animation_parameters[:destination].is_any?(::Hash, ::GxG::Database::DetachedHash) the_animation = ::GxG::Gui::Vdom::Animation.new({:targets => {:elements => [(self.element)]}, :origin => animation_parameters[:origin], :destination => animation_parameters[:destination], :options => animation_parameters[:options]}) start_time = `window.performance.now()` if animation_parameters[:options].is_any?(::Hash, ::GxG::Database::DetachedHash) start_time = animation_parameters[:options][:start_time] end # FIXME: # GxG::ANIMATION[:animations][(the_animation.uuid)] = the_animation # the_animation.compile the_animation.start(start_time) true else log_warn("Ignoring invalid animation: NO valid :destination state specified (required). not: #{animation_parameters[:destination].inspect}") false end end # ------------------------------------------------------------------------------ def show(transition_parameters=nil,&block) if transition_parameters.is_any?(::Hash, ::GxG::Database::DetachedHash) if transition_parameters.is_a?(::GxG::Database::DetachedHash) transition_parameters = transition_parameters.unpersist end # if transition_parameters[:destination].is_a?(::Hash) if transition_parameters[:destination][:opacity].is_a?(::Numeric) if transition_parameters[:origin].is_a?(::Hash) if transition_parameters[:origin][:opacity].is_a?(::Numeric) self.merge_style({:opacity => transition_parameters[:origin][:opacity]}) else self.merge_style({:opacity => 0}) end else self.merge_style({:opacity => 0}) end end end self.set_state(:hidden, false) if block.respond_to?(:call) unless transition_parameters[:options] transition_parameters[:options] = {} end transition_parameters[:options][:animationend] = Proc.new do block.call() end end # self.animate(transition_parameters) else self.set_state(:hidden, false) if block.respond_to?(:call) block.call() end end # GxG::DISPLAY_DETAILS[:object].layout_refresh ::GxG::Gui::layout_refresh true end # def hide(transition_parameters=nil,&block) if transition_parameters.is_any?(::Hash, ::GxG::Database::DetachedHash) if transition_parameters.is_a?(::GxG::Database::DetachedHash) transition_parameters = transition_parameters.unpersist end if block.respond_to?(:call) unless transition_parameters[:options] transition_parameters[:options] = {} end transition_parameters[:options][:animationend] = Proc.new do self.set_state(:hidden, true) block.call() end else set_state = nil if transition_parameters[:options] if transition_parameters[:options][:animationend].respond_to?(:call) old_complete = transition_parameters[:options][:animationend] set_state = Proc.new do old_complete.call() self.set_state(:hidden, true) end else set_state = Proc.new do self.set_state(:hidden, true) end end else transition_parameters[:options] = {} set_state = Proc.new do self.set_state(:hidden, true) end end if set_state unless transition_parameters[:options].is_a?(::Hash) transition_parameters[:options] = {} end transition_parameters[:options][:animationend] = set_state end end # self.animate(transition_parameters) else self.set_state(:hidden, true) if block.respond_to?(:call) block.call() end end # GxG::DISPLAY_DETAILS[:object].layout_refresh ::GxG::Gui::layout_refresh true end # end # -------------------------------------------------------------------------------------------------------------------------------------- module ApplicationSupport # def find_parent_type(the_type=nil) # return the first parent of type: the_type (Class, String, Symbol) result = nil if the_type.is_any?(::Class, ::String, ::Symbol) if the_type.is_any?(::String, ::Symbol) the_type = GxG::Gui::component_class(the_type.to_s.to_sym) end if the_type.is_a?(::Class) search_queue = [(self)] while search_queue.size > 0 do entry = search_queue.shift if entry if entry.is_a?(the_type) result = entry break end search_queue << entry.parent end end end end result end # def viewport() # find nearest viewport in object heirarchy. self.find_parent_type(::GxG::Gui::ApplicationViewport) end # def tree_node() # find nearest :tree_node parent in object heirarchy. self.find_parent_type(::GxG::Gui::TreeNode) end # def tree() # find nearest :tree parent in object heirarchy. self.find_parent_type(::GxG::Gui::Tree) end # def window() # find nearest window in object heirarchy. self.find_parent_type(::GxG::Gui::Window) end # def find_parent(the_reference=nil) result = nil if GxG::valid_uuid?(the_reference) uuid_check = true else uuid_check = false end search_queue = [(self.parent)] while search_queue.size > 0 do entry = search_queue.shift if entry if uuid_check if entry.uuid == the_reference result = entry break end else if entry.title == the_reference result = entry break end end unless result search_queue << entry.parent end end end result end # def find_child_type(the_type=nil) # return the first parent of type: the_type (Class, String, Symbol) result = nil if the_type.is_any?(::Class, ::String, ::Symbol) if the_type.is_any?(::String, ::Symbol) the_type = GxG::Gui::component_class(the_type.to_s.to_sym) end if the_type.is_a?(::Class) search_queue = self.children.values while search_queue.size > 0 do entry = search_queue.shift if entry if entry.is_a?(the_type) result = entry break end end end end end result end # def find_child(the_reference=nil) result = nil if GxG::valid_uuid?(the_reference) uuid_check = true else uuid_check = false end search_queue = self.children.values while search_queue.size > 0 do entry = search_queue.shift if entry if uuid_check if entry.uuid == the_reference result = entry break end else if entry.title == the_reference result = entry break end end unless result entry.children.values.each do |the_child| search_queue << the_child end end end end result end # def find_peer(the_reference=nil) self.parent.find_child(the_reference) end # end # -------------------------------------------------------------------------------------------------------------------------------------- module ThemeSupport def theme_icon(image_name=nil) GxG::DISPLAY_DETAILS[:object].theme_icon(image_name) end # def theme_background(image_name=nil) GxG::DISPLAY_DETAILS[:object].theme_background(image_name) end # def theme_widget(image_name=nil) GxG::DISPLAY_DETAILS[:object].theme_widget(image_name) end # def theme_font(font_name=nil) GxG::DISPLAY_DETAILS[:object].theme_font(font_name) end # Asset retrieval support: def site_asset(asset_path=nil) GxG::DISPLAY_DETAILS[:object].site_asset(asset_path) end end # -------------------------------------------------------------------------------------------------------------------------------------- # # Create DOM elements. class Factory attr_reader :body # Creates the factory. Do not create a factory directly, instead # call the 'factory' method that is available in all Ferro classes # # @param [Object] target The Ruby class instance # @param [Compositor] compositor A style-compositor object or nil def initialize(target) @body = `document.body` %x{ while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); }; } # composite_classes(target, @body, false) end # Create a DOM element. # # @param [Object] target The Ruby class instance # @param [String] type Type op DOM element to create # @param [String] parent The Ruby parent element # @param [Hash] options Options to pass to the element. # See FerroElementary::add_child # @return [String] the DOM element def create_element(target, type, parent, options = {}) # Create element if type == :body element = `document.body` else element = `document.createElement(#{type})` end # Add element to DOM unless type == :body if options[:prepend] == true `#{parent.element}.insertBefore(#{element}, #{options[:prepend].element})` else `#{parent.element}.appendChild(#{element})` end end # if !@compositor # # Add ruby class to the node `#{element}.classList.add(#{dasherize(target.class.name)})` # # Add ruby superclass to the node to allow for more generic styling # if target.class.superclass != BaseElement # `#{element}.classList.add(#{dasherize(target.class.superclass.name)})` # end # else # # Add classes defined by compositor # composite_classes(target, element, target.class.superclass != BaseElement) # end # Set ruby object_id as default element id if !options.has_key?(:id) `#{element}.id = #{target.uuid.to_s}` end # Set attributes # Review : alter to support set_attribute toolbox and Foundation options.each do |name, value| case name when :prepend nil when :content `#{element}.appendChild(document.createTextNode(#{value}))` else `#{element}.setAttribute(#{name}, #{value})` end end element end # Convert a Ruby classname to a dasherized name for use with CSS. # # @param [String] class_name The Ruby class name # @return [String] CSS class name def dasherize(class_name) the_name = class_name.gsub("::","-").downcase.gsub("_","-") if the_name[0] == "-" the_name = the_name[(1..-1)] end the_name end # Convert a CSS classname to a camelized Ruby class name. # # @param [String] class_name CSS class name # @return [String] A Ruby class name def camelize(class_name) return class_name if class_name !~ /-/ class_name.gsub(/(?:-|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.strip end # Convert a state-name to a list of CSS class names. # # @param [String] class_name Ruby class name # @param [String] state State name # @return [String] A list of CSS class names # def composite_state(class_name, state) # if @compositor # list = @compositor.css_classes_for("#{class_name}::#{state}") # return list if !list.empty? # end # [ dasherize(state) ] # end # Internal method # Composite CSS classes from Ruby class name # def composite_classes(target, element, add_superclass) # if @compositor # composite_for(target.class.name, element) # if add_superclass # composite_for(target.class.superclass.name, element) # end # end # end # Internal method # def composite_for(classname, element) # @compositor.css_classes_for(classname).each do |name| # `#{element}.classList.add(#{name})` # end # end end # End Factory class BaseElement include GxG::Gui::Vdom::ElementMethods include GxG::Gui::Vdom::EventHandlers include GxG::Gui::Vdom::AnimationSupport include GxG::Gui::Vdom::ApplicationSupport include GxG::Gui::Vdom::ThemeSupport attr_reader :uuid, :title, :component, :parent, :children, :element, :domtype, :settings def set_settings(the_settings=nil) @settings = the_settings end alias :settings= :set_settings # Create the element and continue the creation # process (casading). # Ferro elements should not be instanciated directly. Instead the # parent element should call the add_child method. # # @param [String] parent The parent Ruby element # @param [Hash] options Any options for the creation process def initialize(parent, options={}, other_data={}) if options.is_a?(GxG::Database::DetachedHash) # ??? @uuid = options.uuid.to_s.to_sym @uuid = ::GxG::uuid_generate().to_s.to_sym if options[:title].to_s.size > 0 if options[:title].to_s.include("Untitled ") @title = "Untitled Component #{@uuid.to_s}" else @title = options[:title].to_s end else @title = options.title.to_s end else # ??? @uuid = (options[:uuid] || ::GxG::uuid_generate()).to_s.to_sym @uuid = ::GxG::uuid_generate().to_s.to_sym @title = (options[:title] || "Untitled Component #{@uuid.to_s}").to_s end @component = (options[:component] || :unknown).to_s.downcase.to_sym @parent = parent # if options[:settings].is_a?(GxG::Database::DetachedHash, GxG::Database::PersistedHash) @settings = options[:settings].unpersist else if options[:settings].is_a?(::Hash) @settings = options[:settings] else @settings = {} end end # if options.respond_to?(:uuid) # @settings[:template] = options.uuid.to_s.to_sym # else # @settings[:template] = nil # end # if other_data[:application] @application = other_data[:application] else @application = nil end @children = {} @element = nil @domtype = :div # ### CSS states @states = {} @states[(@component.to_s.gsub(".","-").to_sym)] = true if options[:options].is_any?(::Hash, GxG::Database::DetachedHash) if options[:options][:states].is_any?(::Array, GxG::Database::DetachedArray) options[:options][:states].each do |the_state| @states[(the_state.to_s.to_sym)] = true end end end # ### Style @style = {} if options[:options].is_a?(::Hash, GxG::Database::DetachedHash) if options[:options][:style].is_a?(::Hash, GxG::Database::DetachedHash) @style = options[:options][:style] end end # ### Other Attributes @options = {} if options[:options].is_a?(::Hash, GxG::Database::DetachedHash) options[:options].each_pair do |the_option, the_value| unless [:"background-color", :icon, :label, :data, :content, :uuid, :title, :opacity, :window_title, :menu, :top, :right, :bottom, :left, :height, :width, :close, :minimize, :maximize, :resize, :scroll, :modal, :track, :states, :style].include?(the_option.to_s.to_sym) @options[(the_option.to_s.to_sym)] = the_value.to_s end end end # Event Stuff @event_handlers = {} @event_listeners = {} # creation # if options[:options].is_a?(::Hash, GxG::Database::DetachedHash) if options[:content].is_a?(::String) self.set_text(options[:content]) end end # Review : return self ?? self end # def parent() @parent end # Searches the element hierarchy upwards until the root element is found. def root result = nil search_queue = [(@parent)] while search_queue.size > 0 do entry = search_queue.shift if entry if entry.component == :page result = entry break else search_queue << entry.parent() end end end result end # Searches the element hierarchy upwards until the factory is found def factory self.root.factory end # def build(content_manifest=nil, options={}) page.build_components(self, content_manifest, options) end # end # End BaseElement class BaseInputElement < ::GxG::Gui::Vdom::BaseElement def _before_create super() @domtype = :input @options[:type] ||= :text if @options[:content] @options[:value] = @options.delete(:content) end @disabled = @options[:disabled] || false end # Getter method for input value. def value `#{@element}.value` end # Setter method for input value. def value=(value) `#{@element}.value = #{value}` end # def _after_create super() if @disabled disable end end # Disable this input. def disable set_attribute(:disabled, "disabled") end # Enable this input. def enable clear_attribute(:disabled) end end # End BaseInputElement class BaseClickableElement < ::GxG::Gui::Vdom::BaseInputElement def _before_create super() # on(:click) do |event| # `document.activeElement.blur()` # end end # Review : alter default click behavior ?? Requires setting on(:click);`document.activeElement.blur()`;end sequence. end # End BaseClickableElement # # This is the entry point for any Ferro application. # It represents the top level object of the # Master Object Model (MOM). # There should be only one class that inhertits # from FerroDocument in an application. # This class attaches itself to the DOM # `document.body` object. # Any existing child nodes of `document.body` are removed. class Page include GxG::Gui::Vdom::ElementMethods include GxG::Gui::Vdom::EventHandlers include GxG::Gui::Vdom::AnimationSupport include GxG::Gui::Vdom::ApplicationSupport include GxG::Gui::Vdom::ThemeSupport # Universal attribute readers: attr_reader :uuid, :title, :component, :parent, :children, :domtype, :settings def set_settings(the_settings=nil) @settings = the_settings end alias :settings= :set_settings # Page-specific attribute readers (if not covered by a method): # ### Loading Libraries def get_library(the_reference=nil) result = nil if the_reference.is_any?(::Hash, ::GxG::Database::DetachedHash) GxG::APPLICATIONS[:libraries].values.each do |the_library| if the_library.title == the_reference[:library].to_s && the_library.type == (the_reference[:type] || the_library.type) if the_reference[:maximum] if ((the_reference[:minimum].to_f)..(the_reference[:maximum].to_f)).include?(the_library.version) result = the_library break end else if the_library.version >= the_reference[:minimum].to_f result = the_library break end end end end else if GxG::APPLICATIONS[:libraries][(the_reference.to_s.to_sym)] result = GxG::APPLICATIONS[:libraries][(the_reference.to_s.to_sym)] else GxG::APPLICATIONS[:libraries].values.each do |the_library| if the_library.title == the_reference result = the_library break end end end end result end # def library_loaded?(the_reference=nil) if the_reference.is_any?(::Hash, ::GxG::Database::DetachedHash) found = false GxG::APPLICATIONS[:libraries].values.each do |the_library| if the_library.title == the_reference[:library].to_s && the_library.type == (the_reference[:type] || the_library.type).to_s if the_reference[:maximum] if ((the_reference[:minimum].to_f)..(the_reference[:maximum].to_f)).include?(the_library.version) found = the_library.loaded?() break end else if the_library.version >= the_reference[:minimum].to_f found = the_library.loaded?() break end end end end found else if GxG::APPLICATIONS[:libraries][(the_reference.to_s.to_sym)] true else found = false GxG::APPLICATIONS[:libraries].values.each do |the_library| if the_library.title == the_reference found = the_library.loaded?() break end end found end end end # def load_library(criteria=nil, &block) if criteria.is_any?(::Hash, ::GxG::Database::DetachedHash) # {:library => "lib-name", :manifest => true/false, :dependencies => true/false, :minimum => 0.0, :maximum => 0.0, :loaded => [...]} if criteria[:maximum] maximum = criteria[:maximum].to_f else maximum = nil end GxG::CONNECTION.library_pull({:library => criteria[:library].to_s, :type => (criteria[:type] || "text/ruby").to_s, :minimum => (criteria[:minimum] || 0.0).to_f, :maximum => maximum,:dependencies => true, :loaded => GxG::APPLICATIONS[:libraries].keys}) do |response| # process response[:result] if response.is_a?(::Hash) if response[:result].is_a?(::Array) response[:result].each do |the_record| unless GxG::APPLICATIONS[:libraries].keys.include?(the_record[:uuid].to_s.to_sym) definition = the_record[:data] if definition.is_a?(::String) if definition.base64? definition = definition.decode64 end if definition.json? definition = ::JSON::parse(definition,{:symbolize_names => true}) end if definition.is_a?(::Hash) definition = ::GxG::Database::process_import(definition) end if definition.is_a?(::GxG::Database::DetachedHash) GxG::APPLICATIONS[:libraries][(the_record[:uuid].to_s.to_sym)] = ::GxG::Library.new(definition) if block.respond_to?(:call) block.call(GxG::APPLICATIONS[:libraries][(the_record[:uuid].to_s.to_sym)]) end end end end end end end end # end end # def load_libraries(load_queue=[], &block) result = true if load_queue.is_any?(::Array, ::GxG::Database::DetachedArray) load_queue.each do |the_requirement| GxG::DISPLAY_DETAILS[:object].load_library(the_requirement) do |the_library| unless the_library.loaded? log_warn("Unable to load library: #{the_library.title}") result = false break end end end # end if block.respond_to?(:call) block.call(result) end result end # ### Application Supports: def application_open(specifier={}, parameters={}) result = false if specifier.is_a?(::Hash) error_hander = Proc.new { # self.set_busy(false) } self.set_busy(true) GxG::CONNECTION.application_open(specifier,error_hander) do |app_stub| if app_stub.is_a?(::Hash) if app_stub[:result].is_a?(::Hash) GxG::CONNECTION.pull_object(app_stub[:result][:location]) do |source_object| GxG::APPLICATIONS[:processes][(app_stub[:result][:application].to_s.to_sym)] = GxG::Application.new(app_stub[:result].merge({:source => source_object})) # result = GxG::APPLICATIONS[:processes][(app_stub[:result][:application].to_s.to_sym)].run(parameters) # self.set_busy(false) end else log_warn('Error opening Application: ' + app_stub.inspect) end else log_warn("Malformed response: #{app_stub.inspect}") end end else log_warn("Invalid Argument: #{specifier.inspect}") end result end # ### Theme Supports: def site_asset(asset_path=nil) if (`window.location['href']`).include?("https://") host_prefix = ("https://" + `window.location['host']`) else host_prefix = ("http://" + `window.location['host']`) end if ::GxG::CONNECTION.relative_url().to_s[0] != "/" host_prefix = (host_prefix + "/") end if asset_path (host_prefix + File.expand_path((::GxG::CONNECTION.relative_url().to_s + "/" + asset_path.to_s))) else "" end end # def theme_icon(image_name=nil) if image_name (@theme_prefix + "/icons/" + image_name.to_s) else "" end end # def theme_background(image_name=nil) if image_name (@theme_prefix + "/backgrounds/" + image_name.to_s) else "" end end # def theme_widget(image_name=nil) if image_name (@theme_prefix + "/widgets/" + image_name.to_s) else "" end end # def theme_font(font_name=nil) if font_name (@theme_prefix + "/fonts/" + font_name.to_s) else "" end end # def theme_window_title_layout() {:left => [:close, :minimize, :maximize], :center => [:title], :right => []} end # ### Component Building def build_components(root_object=nil, content_manifest=nil, options={}) result = false # unless root_object root_object = ::GxG::DISPLAY_DETAILS[:object] end # if root_object && content_manifest.is_any?(::Array, ::GxG::Database::DetachedArray) build_queue = [{:parent => root_object, :content => content_manifest}] while build_queue.size > 0 do record = build_queue.shift if record record[:content].each do |the_entry| if the_entry.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) new_object = record[:parent].add_child(the_entry, options.merge({:template_uuid => the_entry.uuid})) else new_object = record[:parent].add_child(the_entry, options) end if the_entry[:content].size > 0 build_queue << {:parent => new_object, :content => the_entry[:content]} end # Link viewport to provided application if the_entry[:component].to_s.downcase.to_sym == :"org.gxg.gui.application.viewport" && options[:application] new_object.set_application(options[:application]) unless options[:application].get_viewport(new_object.uuid) options[:application].link_viewport(new_object) end end # Link window to provided application if the_entry[:component].to_s.downcase.to_sym == :"org.gxg.gui.window" && options[:application] options[:application].link_window(new_object) new_object.set_application(options[:application]) end # Link dialog_box to provided application if the_entry[:component].to_s.downcase.to_sym == :"org.gxg.gui.window.dialog" && options[:application] new_object.set_application(options[:application]) end # Link org.gxg.gui.menu.bar to provided application if the_entry[:component].to_s.downcase.to_sym == :"org.gxg.gui.menu.bar" && options[:application] new_object.set_application(options[:application]) end # Link org.gxg.gui.menu to provided application if the_entry[:component].to_s.downcase.to_sym == :"org.gxg.gui.menu" && options[:application] new_object.set_application(options[:application]) end # Window Content Tracking if record[:parent].is_a?(::GxG::Gui::Window) if the_entry[:options][:track] record[:parent].track_deltas(new_object.uuid, new_object, the_entry[:options][:track]) end end end end end result = true end # result end # def build(content_manifest=nil, options={}) self.build_components(self, content_manifest, options) end # ### Page Building def build_page(page_definition=nil) # ### Example page_definition : # page_format = { # :component => "page", # :requirements => [], # :auto_start => [], # :settings => {:page_title => "Untitled Page", :theme => "default", :accesskey => "", :contenteditable => false, :dir => "ltr", :draggable => false, :dropzone => "", :lang => "en", :spellcheck => false, :translate => "no"}, # :options => {:style => {}, :states => ["page"], :tabindex => 0}, # :script => "", # :content => [] # } # if page_definition.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) # ### Universal component attributes: @element = `document.body` @settings = page_definition[:settings] @theme_prefix = (::GxG::CONNECTION.relative_url() + "/themes/#{(@settings[:theme] || 'default')}") @uuid = page_definition.uuid.to_s.to_sym `#{@element}.id = #{@uuid.to_s}` @title = @settings[:page_title].to_s `document.title = #{@title.to_s}` @component = :"org.gxg.gui.page" @parent = self @children = {} @domtype = :body # ### CSS states @states = {:"org-gxg-gui-page" => true} if page_definition[:options].is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) if page_definition[:options][:states].is_any?(::Array, ::GxG::Database::PersistedArray, ::GxG::Database::DetachedArray) page_definition[:options][:states].each do |the_state| @states[(the_state.to_s.to_sym)] = true end end end # ### Style @style = {} if page_definition[:options].is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) if page_definition[:options][:style].is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) @style = page_definition[:options][:style] end end # ### Other Attributes @options = {} if page_definition[:options].is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash) page_definition[:options].each_pair do |the_option, the_value| unless [:"background-color", :data, :icon, :label, :content, :uuid, :title, :opacity, :window_title, :menu, :top, :right, :bottom, :left, :height, :width, :close, :minimize, :maximize, :resize, :scroll, :modal, :track, :states, :style].include?(the_option.to_s.to_sym) @options[(the_option.to_s.to_sym)] = the_value.to_s end end end # ### Event Stuff @event_handlers = {} @event_listeners = {} # ### Commit Base Page Build creation # ### Set Script if any if page_definition[:script].is_a?(::String) if page_definition[:script].size > 0 self.set_script(page_definition[:script]) end end # ### Commit Component Builds self.build_components(self, page_definition[:content]) # end end # Create the document and start the creation process (casading). def initialize(page_definition=nil) ::GxG::DISPLAY_DETAILS[:object] = self ::GxG::DISPLAY_DETAILS[:object].on :onbeforeunload do |event| ::GxG::DISPLAY_DETAILS[:socket].close # ::GxG::CONNECTION.close end ::GxG::DISPLAY_DETAILS[:object].on :beforeunload do |event| ::GxG::DISPLAY_DETAILS[:socket].close # ::GxG::CONNECTION.close end ::GxG::DISPLAY_DETAILS[:object].on :onunload do |event| ::GxG::DISPLAY_DETAILS[:socket].close # ::GxG::CONNECTION.close end @theme_prefix = (::GxG::CONNECTION.relative_url() + "/themes/#{::GxG::DISPLAY_DETAILS[:theme]}") @settings = nil @busy = false @window_switcher = nil @window_registry = {} @dialog = nil @dialog_queue = [] @title_registry = {} self.build_page(page_definition) # Page-specific attributes: # Review : go over object formats again and then lay out additional instance vars. # self end # ### Object Registration and Search def find_object_by_dom_id(the_id=nil) result = nil if the_id.is_a?(::Numeric) the_id = ("_" + the_id.to_i.to_s()) else the_id = the_id.to_s end search_queue = [(self)] while search_queue.size > 0 do entry = search_queue.shift attrib = `#{entry.element()}.attributes.getNamedItem('id') || 0` unless `#{attrib} == 0` attrib = `#{attrib}.value` if `(#{attrib.to_s} == #{the_id.to_s})` result = entry break end end if entry.respond_to?(:children) entry.children.keys.each do |the_child_key| attrib = `#{entry.children[(the_child_key)].element()}.attributes.getNamedItem('id') || 0` unless `#{attrib} == 0` attrib = `#{attrib}.value` if `(#{attrib.to_s} == #{the_id.to_s})` result = entry.children[(the_child_key)] break else search_queue << entry.children[(the_child_key)] end end end end end result end # def find_object(object_name=nil) result = nil search_queue = [(self)] while search_queue.size > 0 do entry = search_queue.shift if entry.respond_to?(:children) if entry.children[(object_name)] result = entry.children[(object_name)] break else entry.children.keys.each do |the_child| search_queue << entry.children[(the_child)] end end end end result end # def find_object_by(the_title=nil) result = nil if GxG::valid_uuid?(the_title) uuid_check = true else uuid_check = false end @title_registry.keys.each do |the_uuid| if uuid_check == true if @title_registry[(the_uuid)].to_s.include?(the_title.to_s) result = self.find_object(the_uuid) break end else if the_title == @title_registry[(the_uuid)] result = self.find_object(the_uuid) break end end end result end # def register_object(the_title=nil, the_object=nil) if the_title && the_object @title_registry[(the_object.uuid)] = the_title.to_s true else false end end # def unregister_object(the_title=nil) if the_title the_object = self.find_object_by(the_title) if the_object if the_object.is_a?(::GxG::Gui::ApplicationViewport) the_object.detach() end @title_registry.delete(the_object.uuid) end true else false end end # Returns the one and only instance of the factory. def factory @factory ||= GxG::Gui::Vdom::Factory.new(self) end # Returns the DOM element. def element() factory.body end def parent() self end # The document class is the root element. def root() self end # # ### Display Helpers def domclear() %x{ while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); }; } true end # def clear() self.all_descendants do |the_child| the_child.remove_listeners() end self.remove_listeners() self.each_child do |the_child| the_child.destroy() end the_attributes = `document.body.getAttributeNames()` the_attributes.each do |the_attribute| self.clear_attribute(the_attribute) end true end # end # End Page def self.load_page(page_object=nil) if ::GxG::DISPLAY_DETAILS[:object].is_any?(::GxG::Gui::Page, ::GxG::Gui::Vdom::Page) ::GxG::DISPLAY_DETAILS[:object].clear() ::GxG::Gui::Vdom::clear_rules() ::GxG::Gui::Vdom::refresh_css_cache() # end # Load current theme CSS rules the_theme = (page_object[:settings][:theme] || "default") ::GxG::CONNECTION.pull_object("/Public/www/content/themes/#{the_theme}") do |response| if response.is_a?(::Array) # puts "Response (Theme Load): #{response.inspect}" # Resources response[0][:resources].each do |resource_profile| if resource_profile[:resource_type] == "font-face" ::GxG::Gui::Vdom::load_font(resource_profile) end end # Rules # puts "set css rules" response[0][:rules].each_pair do |rule_name, rule_properties| ::GxG::Gui::Vdom::set_rule(rule_name, rule_properties) end # Build Page # puts "build page" if ::GxG::DISPLAY_DETAILS[:object].is_any?(::GxG::Gui::Page, ::GxG::Gui::Vdom::Page) ::GxG::DISPLAY_DETAILS[:object].build_page(page_object) else ::GxG::DISPLAY_DETAILS[:object] = ::GxG::Gui::Page.new(page_object) end # self.layout_refresh ::GxG::Gui::layout_refresh else log_error({:error => Exception.new("Invalid theme object as response.")}) end end # Load Libraries # puts "load libraries" if page_object[:requirements].is_any?(::Array, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray) if page_object[:requirements].size > 0 self.load_libraries(page_object[:requirements]) end end # Load Applications # puts "launch applications" if page_object[:auto_start].is_any?(::Array, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray) if page_object[:auto_start].size > 0 page_object[:auto_start].each do |the_requirement| self.application_open({:name => the_requirement[:name], :restore => true}) end end end # true end # def self.navigate_to(the_location="") # ### Fetch page content and set build options # if ::GxG::CONNECTION.relative_url().to_s.size > 0 request_path = the_location[(::GxG::CONNECTION.relative_url().size)..-1] else request_path = the_location end if request_path == "/" || request_path == "" request_path = "/index" end unless request_path[0] == "/" request_path = ("/" + request_path) end ::GxG::DISPLAY_DETAILS[:host_path] = request_path if GxG::PAGES[(request_path)] GxG::Gui::Vdom::load_page(GxG::PAGES[(request_path)]) else error_handler = Proc.new { |error| log_error error.inspect } # Load Page Content ::GxG::CONNECTION.pull_object("/Public/www/content/pages#{request_path}",error_handler) do |response| puts "Response (Page Load): #{response.inspect}" if response.is_any?(::Array) GxG::PAGES[(request_path)] = response[0] GxG::Gui::Vdom::load_page(GxG::PAGES[(request_path)]) else log_error({:error => Exception.new("Invalid page object as response.")}) end # self.layout_refresh ::GxG::Gui::layout_refresh end end # end end end end