• Publicity: Public Only All

yui-procs.tcl

yui procs: provide some support for yui library

Location:
packages/xowiki/tcl/yui-procs.tcl
Created:
2014-04-14
Authors:
Michael Aram
Gustaf Neumann
CVS Identification:
$Id: yui-procs.tcl,v 1.19 2024/09/11 06:15:56 gustafn Exp $

Procedures in this file

Detailed information

[ hide source ] | [ make this the default ]

Content File Source

::xo::library doc {
  yui procs: provide some support for yui library

  @creation-date 2014-04-14
  @author Michael Aram
  @author Gustaf Neumann
  @cvs-id $Id: yui-procs.tcl,v 1.19 2024/09/11 06:15:56 gustafn Exp $
}

::xo::library require -package xotcl-core 30-widget-procs
::xo::library require menu-procs

namespace eval ::xowiki {

  ::xo::tdom::Class create YUIMenuItemList \
      -superclass Menu \
      -parameter {
        header
      }

  YUIMenuItemList instproc render {} {
    if {[info exists :header]} {
      html::h6 {
        html::t [:header]
      }
    }
    next
  }

  ###################################################
  #
  # YUIMenu
  #
  ::xo::tdom::Class create YUIMenu \
      -superclass Menu \
      -parameter {
        header
        footer
        shadow
        {autorender false}
        {configuration {{}}}
      }

  YUIMenu instproc init {} {
    ::xowiki::Includelet require_YUI_CSS -ajaxhelper 1 "menu/assets/skins/sam/menu.css"
    ::xowiki::Includelet require_YUI_JS  -ajaxhelper 1 "yahoo-dom-event/yahoo-dom-event.js"
    ::xowiki::Includelet require_YUI_JS  -ajaxhelper 1 "container/container_core-min.js"
    ::xowiki::Includelet require_YUI_JS  -ajaxhelper 1 "menu/menu-min.js"
    next
  }

  YUIMenu instproc split_menu_groups {list} {
    #
    # split the list of entries into groups, which will be separated
    # with lines in the rendering
    #
    set result [list]
    if {[llength $list] < 1} {return $result}
    set group_name [[lindex $list 0] group]
    set group_list [list]
    foreach e $list {
      set gn [$e group]
      if {$gn ne $group_name} {
        lappend result $group_list
        set group_name $gn
        set group_list [list]
      }
      lappend group_list $e
    }
    lappend result $group_list
    return $result
  }

  YUIMenu ad_instproc render {} {
    http://developer.yahoo.com/yui/menu/
  } {
    append :CSSclass " yuimenu"
    set :extrajs ""

    # I want the menu to show up when JS is disabled
    # This gets overridden by JS, so its only relevant for the non-JS version
    #set :style "visibility: visible; position: relative;"

    html::div [:get_attributes {CSSclass class} id style] {
      # Header
      html::t \n
      if {[info exists :header]} {
        html::div -class "hd" {
          html::t [:header]
        }
      }
      # Body
      html::t \n
      html::div -class "bd" {
        foreach group [:split_menu_groups [:children]] {
          html::ul -class yuiml {
            foreach menuitemlist $group {$menuitemlist render}
          }
        }
      }
      # Footer
      if {[info exists :footer]} {
        html::div -class "ft" {
          html::t [:footer]
        }
      }
      # Shadow
      if {[info exists :shadow]} {
        html::div -class "yui-menu-shadow" {}
      }
      # JavaScript
      # only "root-level" menus need JS
      # TODO: is this parent-check sufficient / future-safe?
      if {[info exists :__parent]} {
        #
        # propagate extrajs from rendering
        #
        #ns_log notice "### propagate extrajs <${:extrajs}> from [:info class] to [${:__parent} info class]"
        ${:__parent} append extrajs ${:extrajs}
      } else {
        html::script -nonce [security::csp::nonce] -type "text/javascript" {
          html::t "var [:js_name] = new YAHOO.widget.Menu(\"[:id]\", ${:configuration});"
          html::t "
                        [:js_name].render();
                        [:js_name].show();
                        ${:extrajs}
                    "
        }
      }
    }
  }

  #
  # YUIMenuItem
  #
  ::xo::tdom::Class create YUIMenuItem \
      -superclass MenuItem \
      -parameter {
        {href "#"}
        helptext
      }

  YUIMenuItem ad_instproc render {} {doku} {
    html::li [:get_attributes id {CSSclass class} style] {
      # if we have no href, mark entry as disabled
      if {![info exists :href] || [:href] eq ""} {append :linkclass " disabled"}
      if {[info exists :listener] && ${:listener} ne ""} {
        #ns_log notice "menuitem has id [:id] listener [:listener] parent ${:__parent} [${:__parent} info class]"
        lassign [:listener] type body
        ${:__parent} append extrajs [subst {
          document.getElementById('[:id]').addEventListener('$type', function (event) {
            $body;
          }, false);
        }]
      }
      html::a [:get_attributes target href {linkclass class} title] {
        html::t [:text]
        if {[info exists :helptext]} {
          html::em {
            html::t [:helptext]
          }
        }
      }
      foreach menu [:children] {$menu render}
    }
    html::t \n
  }


  #
  # YUIMenuBar
  #
  ::xo::tdom::Class create YUIMenuBar \
      -superclass YUIMenu \
      -parameter {
        {navbar true}
      }

  YUIMenuBar ad_instproc render {} {
    http://developer.yahoo.com/yui/menu/#menubar
    MenuBar looks best without a header and with one MenuItemList only
  } {
    append :CSSclass " yuimenubar"
    set :extrajs ""
    if {[:navbar]} {append :CSSclass " yuimenubarnav"}
    html::div [:get_attributes id {CSSclass class}] {
      html::div -class "bd" {
        html::t \n
        html::ul -class "first-of-type" {
          foreach li [:children] {$li render}
        }
        html::t \n
      }
      html::t \n
      ::xo::Page set_property body class "yui-skin-sam"
      ::xo::Page requireJS "YAHOO.util.Event.onDOMReady(function () {
            var [:js_name] = new YAHOO.widget.MenuBar('[:id]', ${:configuration});
            [:js_name].render();
            ${:extrajs}
      });"
    }
  }

  #
  # YUIMenuBarItem
  #
  ::xo::tdom::Class create YUIMenuBarItem \
      -superclass YUIMenuItem

  YUIMenuBarItem instproc init {} {
    #goto YUIMenuItem and set all those nice defaults
    next
    append :CSSclass " first-of-type"
    if {![info exists :href]} {
      # If not set to #, the title of the menu bar items won't expand the submenu (only the arrow)
      set :href "#"
    }
  }

  YUIMenuBarItem instproc render {} {
    set :extrajs ""
    set result [next]
    if {[info exists :__parent]} {
      #
      # propagate extrajs from rendering
      #
      #ns_log notice "### propagate extrajs <${:extrajs}> from [:info class] to [${:__parent} info class]"
      ${:__parent} append extrajs ${:extrajs}
    }
  }

  #
  # YUIContextMenu
  #

  # TODO: Support for Multiple Element IDs/Refs as Trigger

  ::xo::tdom::Class create YUIContextMenu \
      -superclass YUIMenu \
      -parameter {
        {trigger "document"}
        {triggertype "reference"}
      }

  YUIContextMenu ad_instproc render {} {
    http://developer.yahoo.com/yui/menu/#contextmenu
  } {
    append :CSSclass " yuimenu"
    html::div [:get_attributes id {CSSclass class}] {
      html::div -class "bd" {
        html::ul -class yuicm {
          foreach li [:children] {$li render}
        }
      }
      html::script -nonce [security::csp::nonce] -type "text/javascript" {
        html::t "var [:js_name] = new YAHOO.widget.ContextMenu('[:id]', { trigger: '${:trigger}' } );"
        html::t "[:js_name].render(document.body);"
      }
    }
  }

  #
  # YUIContextMenuItem
  #
  ::xo::tdom::Class create YUIContextMenuItem \
      -superclass YUIMenuItem

  ::xowiki::MenuBar instproc render-yui {} {
    set dict [:content]
    set mb [::xowiki::YUIMenuBar -id [:get_prop $dict id] -configuration {
      {autosubmenudisplay: false, keepopen: true, lazyload: false}
    } {
      foreach {menu_att menu} $dict {
        if {$menu_att eq "id"} continue
        set kind [:get_prop $menu kind]
        #ns_log notice "entry: kind $kind <$menu_att> <$menu>"

        if {$kind ne "MenuButton"} continue
        ::xowiki::YUIMenuBarItem -text [:get_prop $menu label] {
          ::xowiki::YUIMenu {
            foreach {item_att item} $menu {
              if {[string match {[a-z]*} $item_att]} continue
              ::xowiki::YUIMenuItem \
                  -text [:get_prop $item label] \
                  -href [:get_prop $item url] \
                  -group [:get_prop $item group] \
                  -listener [:get_prop $item listener] {}
            }
          }
        }
      }
    }]
    return [$mb asHTML]
  }

  namespace export YUIMenuBar YUIMenuBarItem
  namespace export YUIMenu YUIMenuItem YUIMenuItemList
  namespace export YUIContextMenu YUIContextMenuItem
}


###############################################################################
#   YUI loader
###############################################################################

namespace eval ::YUI {

  Object loader -ad_doc {
    The YUI Library comes with a "Loader" module, that resolves YUI-module
    dependencies. Also, it combines numerous files into one single file to
    increase page loading performance.
    This works only for the "hosted" YUI library. This Loader module should
    basically do the same (in future). For two simple calls like e.g.
    "::YUI::loader require menu" and "::YUI::loader require datatable"
    it should take care of selecting all the files needed and assemble them
    into one single resource, that may be delivered.
    Note that this is not implemented yet.
  }

  loader set ajaxhelper 1

  # TODO: Make "::YUI::loader require -module XYZ" work everywhere "out-of-the-box"
  #       Now, as we use "::xo:Page require_JS" we have to include the generated
  #       header_stuff "manually" (e.g. in tcl-adp pairs),  whereas ::template::head...
  #       includes it directly, which is nice.

  loader ad_proc require {
    -module
    {-version "2.7.0b"}
  } {
    This is the key function of the loader, that will be used by other packages.
    @param module
    The YUI Module to be loaded
  } {
    switch -- [string tolower $module] {

      utilities {
        # utilities.js: The utilities.js aggregate combines the Yahoo Global Object,
        # Dom Collection, Event Utility, Element Utility, Connection Manager,
        # Drag & Drop Utility, Animation Utility, YUI Loader and the Get Utility.
        # Use this file to reduce HTTP requests whenever you are including more
        # than three of its constituent components.
        ::xo::Page requireJS urn:ad:js:yui2:yahoo-dom-event/yahoo-dom-event
        ::xo::Page requireJS urn:ad:js:yui2:utilities/utilities
      }
      menubar {
        #
        # We should not have two different versions of the YUI
        # library on one page, because YUI2 (afaik) doesn't support
        # "sandboxing". If we use e.g. the yui-hosted utilities.js file here
        # we may end up with two YAHOO object definitions, because e.g.
        # the tree-procs uses the local yahoo-dom-event.

        # In future, the YUI loader object should be capable of
        # resolving such conflicts. For now, the simple fix is to stick to
        # the local versions, because then the requireJS function takes care
        # of duplicates.
        #
        :require -module "utilities"
        # todo : this is more than necessary
        foreach jsFile {
          container/container-min
          treeview/treeview-min
          button/button-min
          menu/menu-min
          datasource/datasource-min
          autocomplete/autocomplete-min
          datatable/datatable-min
          selector/selector-min
        } {
          ::xo::Page requireJS urn:ad:js:yui2:$jsFile
        }

        :require -module "reset-fonts-grids"
        :require -module "base"

        foreach cssFile {
          container/assets/container
          datatable/assets/skins/sam/datatable
          button/assets/skins/sam/button
          assets/skins/sam/skin
          menu/assets/skins/sam/menu
          treeview/assets/folders/tree
        } {
          ::xo::Page requireCSS urn:ad:css:yui2:$cssFile
        }
      }
      datatable {
        # see comment above
        :require -module "utilities"
        # todo : this is more than necessary
        foreach jsFile {
          container/container-min
          treeview/treeview-min
          button/button-min
          menu/menu-min
          datasource/datasource-min
          autocomplete/autocomplete-min
          datatable/datatable-min
          selector/selector-min
        } {
          ::xo::Page requireJS urn:ad:js:yui2:$jsFile
        }

        :require -module "reset-fonts-grids"
        :require -module "base"

        foreach cssFile {
          container/assets/container
          datatable/assets/skins/sam/datatable
          button/assets/skins/sam/button
          assets/skins/sam/skin
          menu/assets/skins/sam/menu
        } {
          ::xo::Page requireCSS urn:ad:css:yui2:$cssFile
        }
      }
      reset {
        ::xo::Page requireCSS urn:ad:css:yui2:reset/reset
      }
      fonts {
        ::xo::Page requireCSS urn:ad:css:yui2:fonts/fonts-min
      }
      grids {
        ::xo::Page requireCSS urn:ad:css:yui2:grids/grids
      }
      base {
        ::xo::Page requireCSS urn:ad:css:yui2:base/base
      }
      "reset-fonts-grids" {
        ::xo::Page requireCSS urn:ad:css:yui2:reset-fonts-grids/reset-fonts-grids
      }
    }
  }


  Class create AnchorField \
      -superclass ::xo::Table::AnchorField \
      -ad_doc "
            In addition to the standard TableWidget's AnchorField, we also allow the attributes
            <ul>
                <li>onclick
                <li>target
            </ul>
        " \
      -instproc get-slots {} {
        set slots [list -${:name}]
        foreach subfield {href title CSSclass target onclick} {
          lappend slots [list -${:name}.$subfield ""]
        }
        return $slots
      }
}

###############################################################################
#   YUI table
###############################################################################

# TODO Allow renderers from other namespaces in 30-widget-procs

namespace eval ::xo::Table {

  Class create ::xowiki::YUIDataTable \
      -superclass ::xo::Table \
      -parameter {
        {skin "yui-skin-sam"}
      }

  ::xowiki::YUIDataTable instproc init {} {
    set trn_mixin [expr {[lang::util::translator_mode_p] ?"::xo::TRN-Mode" : ""}]
    :render_with YUIDataTableRenderer $trn_mixin
    next
  }

  Class create YUIDataTableRenderer \
      -superclass TABLE3 \
      -instproc init_renderer {} {
        next
        set :css.table-class list-table
        set :css.tr.even-class even
        set :css.tr.odd-class odd
        set :id [::xowiki::Includelet js_name [::xowiki::Includelet html_id [self]]]
      }

  YUIDataTableRenderer ad_instproc -private render_yui_js {} {
    Generates the JavaScript fragment, that is put below and
    (progressively enhances) the HTML table.
  } {
    set container   ${:id}_container
    set datasource  ${:id}_datasource
    set datatable   ${:id}_datatable
    set coldef      ${:id}_coldef
    set finaljs     ""
    set js      "var $datasource = new YAHOO.util.DataSource(YAHOO.util.Dom.get('${:id}')); \n"
    append js   "$datasource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; \n"
    append js   "$datasource.responseSchema = \{ \n"
    append js   "   fields: \[ \n"
    set js_fields [list]
    foreach field [[self]::__columns children] {
      if {[$field hide]} continue
      lappend js_fields "       \{ key: \"[$field set name]\" \}"
    }
    append js [join $js_fields ", ""   \] \n\};\n"
    append js "var $coldef = \[\n"
    set js_fields [list]
    foreach field [[self]::__columns children] {
      if {[$field hide]} continue
      if {[$field istype HiddenField]} continue
      if {[$field istype BulkAction]} {
        set subid [::xowiki::Includelet html_id $field]
        set label "<input type='checkbox' id='$subid'></input>"
        if {[info exists ::__csrf_token]} {
          append label "<input type='hidden' name='__csrf_token' value='$::__csrf_token'>"
        }
        set sortable false
        append finaljs [subst {
          document.getElementById('$subid').addEventListener('click', function (event) {
            acs_ListCheckAll('objects', this.checked);
          }, false);
        }]
      } else {
        set label [lang::util::localize [$field label]]
        set sortable [expr {[$field exists sortable] ? [$field set sortable] : true}]
      }
      lappend js_fields "    \{ key: \"[$field set name]\" , sortable: $sortable, label: \"$label\" \}"
    }
    append js  [join $js_fields ", ""\];\n"
    append js  "var $datatable = new YAHOO.widget.DataTable('$container', $coldef$datasource);\n"
    append js $finaljs
    return $js
  }

  YUIDataTableRenderer instproc render-body {} {
    html::thead {
      html::tr -class list-header {
        foreach o [[self]::__columns children] {
          #ns_log notice "YUIDataTableRenderer $o [$o set name] HIDE [$o hide] RENDER [$o procsearch render]"
          if {[$o hide]} continue
          $o render
        }
      }
    }
    set children [:children]
    html::tbody {
      foreach line [:children] {
        html::tr -class [expr {[incr :__rowcount]%2 ? ${:css.tr.odd-class} : ${:css.tr.even-class} }] {
          foreach field [[self]::__columns children] {
            if {[$field hide]} continue
            html::td [concat [list class [concat list [$field CSSclass]]] [$field html]] {
              $field render-data $line
            }
          }
        }
      }
    }
  }

  YUIDataTableRenderer instproc render {} {
    ::YUI::loader require -module "datatable"
    if {![nsf::is object [self]::__actions]} {:actions {}}
    if {![nsf::is object [self]::__bulkactions]} {:__bulkactions {}}
    set bulkactions [[self]::__bulkactions children]
    if {[[self]::__bulkactions exists __identifier] || [llength $bulkactions]>0} {
      set name [[self]::__bulkactions set __identifier]
    } else {
      set name [::xowiki::Includelet js_name [self]]
    }
    # TODO: maybe use skin everywhere? When to use style/CSSclass or skin?
    set skin [expr {[info exists :skin] ? ${:skin} : ""}]
    html::div -id ${:id}_wrapper -class $skin {
      html::form -name $name -id $name -method POST {
        html::div -id ${:id}_container {
          html::table -id ${:id} -class ${:css.table-class} {
            # TODO do i need that?
            :render-actions
            :render-body
          }
          if {[llength $bulkactions]>0} { :render-bulkactions }
        }
      }
      ::xo::Page requireJS "YAHOO.util.Event.onDOMReady(function () {\n[:render_yui_js]});"
    }
  }


  #Class create YUIDataTableRenderer::AnchorField -superclass TABLE::AnchorField

  Class create YUIDataTableRenderer::AnchorField \
      -superclass TABLE::Field \
      -ad_doc "
            In addition to the standard TableWidget's AnchorField, we also allow the attributes
            <ul>
                <li>onclick
                <li>target
            </ul>
        " \
      -instproc render-data {line} {
        set __name ${:name}
        if {[$line exists $__name.href] &&
            [set href [$line set $__name.href]] ne ""} {
          # use the CSS class rather from the Field than not the line
          set CSSclass ${:CSSclass}
          if {[$line exists $__name.CSSclass]} {
            set lineCSSclass [$line set $__name.CSSclass]
            if {$lineCSSclass ne ""} {
              append CSSclass " " $lineCSSclass
            }
          }
          $line instvar   [list $__name.title title] \
              [list $__name.target target] \
              [list $__name.onclick onclick]
          html::a [:get_local_attributes href title {CSSclass class} target onclick] {
            return "[next]"
          }
        }
        next
      }

  Class create YUIDataTableRenderer::Action -superclass TABLE::Action
  Class create YUIDataTableRenderer::Field -superclass TABLE::Field
  Class create YUIDataTableRenderer::HiddenField -superclass TABLE::HiddenField
  Class create YUIDataTableRenderer::ImageField -superclass TABLE::ImageField
  Class create YUIDataTableRenderer::ImageAnchorField -superclass TABLE::ImageAnchorField
  Class create YUIDataTableRenderer::BulkAction -superclass TABLE::BulkAction
}

::xo::library source_dependent

#
# Local variables:
#    mode: tcl
#    tcl-indent-level: 2
#    indent-tabs-mode: nil
# End: