coffeescript-ui
Version:
Coffeescript User Interface System
982 lines (789 loc) • 21.8 kB
text/coffeescript
###
* coffeescript-ui - Coffeescript User Interface System (CUI)
* Copyright (c) 2013 - 2016 Programmfabrik GmbH
* MIT Licence
* https://github.com/programmfabrik/coffeescript-ui, http://www.coffeescript-ui.org
###
# Base class for all Buttons. Yeahhhh
#
#TODO document this in class...
# role: ui-role
# disabled: true, false
#
#
#Button.DOM: the actual button object
#Button.disable: disable button
#Button.enable: enable button
CUI.Template.loadTemplateText(require('./Button.html'));
CUI.Template.loadTemplateText(require('./Button_ng.html'));
class CUI.Button extends CUI.DOMElement
:
confirm_ok: "Ok"
confirm_icon: "question"
confirm_cancel: "Cancel"
confirm_title: "Confirmation"
disabled_css_class: "cui-disabled"
loading_css_class: "cui-loading"
active_css_class: "cui-active"
#Construct a new CUI.Button.
#
# [Object] options for button creation
# options [String] size controls the size of the button.
# "mini", small button.
# "normal", medium size button.
# "big", big sized button.
# "bigger", bigger sized button.
# options [String] appearance controls the style or appearance of the button.
# "flat", button has no border and inherits its background color from its parent div.
# "normal", standard button with border and its own background color.
# "link", standard button without border and a underlined text.
# "important", emphasized button , to show the user that the button is important.
constructor: (opts) ->
super(opts)
if
if .text or .content
=
= true
= true
= true
tname = ()
# getTemplateName, also sets has_left / has_right
= new CUI.Template
name: tname
map:
left: if then ".cui-button-left" else undefined
center: if then ".cui-button-center" else undefined
visual: if CUI.__ng__ then ".cui-button-visual" else undefined
right: if then ".cui-button-right" else undefined
()
= null
= false
= false
= false
= null
("cui-button-button")
if CUI.util.isString(?.text)
("label", ?.text)
= true
else
= false
CUI.dom.setAttribute(, "tabindex", )
if not ?.role
CUI.dom.setAttribute(, "role", )
if not or == true
if
CUI.util.assert(CUI.util.isUndef(), "new #{@__cls}", "opts.icon conflicts with opts.icon_left", opts: )
icon_left =
else
icon_left =
if icon_left
CUI.util.assert(not and not , "new CUI.Button", "opts.icon_active or opts.icon_inactive cannot be set together with opts.icon or opts.icon_left", opts: )
(icon_left)
else
(, "left")
if not
if
()
else if and != false
("cui-button--has-caret")
if
("fa-angle-right")
else
("fa-angle-down")
else if != true
(, "right")
()
if
("cui-button-appearance-"+)
if
("cui-button--primary")
if
(, "center")
else if
()
if and ( == true or .call(@, @))
()
if and ( == true or .call(@, @))
(true)
if and ( == true or .call(@, @))
()
if == true
(initial_activate: true)
else if == false or #switch default is active=false TODO initial_activate: true == bug!?
(initial_activate: true)
else
()
=
if
CUI.util.assert(CUI.util.isUndef(), "new CUI.Button", "opts.switch conflicts with opts.radio.", opts: )
if == true
= "radio--"+()
else
=
else if not CUI.util.isNull()
= "switch--"+()
= true
if
CUI.util.assert(not ?.radio, "new CUI.Button", "opts.radio conflicts with opts.attr.radio", opts: )
CUI.dom.setAttribute(, "radio", )
()
if
= {}
itemList_opts = {}
# rescue options for menu and separate them
# from itemlist
for k, v of
switch k
when "onShow", "onHide"
continue
when "class", "backdrop", "onPosition", "placement", "placements", "pointer"
[k] = v
else
itemList_opts[k] = v
if not CUI.util.isEmpty()
if .class
.class += " "+
else
.class =
if .itemList
.itemList = .itemList
else
.itemList = itemList_opts
.element = @
if not .hasOwnProperty("use_element_width_as_min_width")
if not
.use_element_width_as_min_width = true
.onHide = =>
.onHide?()
.onShow = =>
.onShow?()
if not .hasOwnProperty("backdrop")
.backdrop = policy: "click-thru"
if not .backdrop.hasOwnProperty("blur") and
?.getOpt("backdrop")?.blur
if
.backdrop =
policy: "click-thru"
blur: true
else
.backdrop.blur = true
if
.parent_menu =
CUI.Events.listen
type: "keydown"
node:
capture: true
call: (ev) =>
if ev.hasModifierKey()
return
if ev.keyCode() in [13, 32]
#space / return
(ev)
ev.stop()
return
if ev.keyCode() == 27
# blur button
.blur()
ev.stop()
return
el = null
right = =>
el = CUI.dom.findNextVisibleElement(, "[tabindex]")
left = =>
el = CUI.dom.findPreviousVisibleElement(, "[tabindex]")
switch ev.keyCode()
when 39 # right cursor
right()
when 40 # down cursor
right()
when 37 # left cursor
left()
when 38 # up cursor
left()
if el
el.focus()
ev.stop()
return
CUI.Events.listen
type: CUI.Button.clickTypesPrevent[]
node:
call: (ev) =>
ev.preventDefault()
# ev.stopPropagation()
return
CUI.Events.listen
type: CUI.Button.clickTypes[]
node:
call: (ev) =>
if CUI.globalDrag
# ev.stop()
return
if ev.getButton() != 0 and not ev.getType().startsWith("touch")
# ev.stop()
return
ev.stopPropagation()
(ev)
return
if
CUI.Button.menu_timeout = null
menu_stop_hide = =>
if not CUI.Button.menu_timeout
return
CUI.clearTimeout(CUI.Button.menu_timeout)
CUI.Button.menu_timeout = null
menu_start_hide = (ev, ms=700) =>
menu_stop_hide()
# we set a timeout, if during the time
# the focus enters the menu, we cancel the timeout
CUI.Button.menu_timeout = CUI.setTimeout
ms: ms
call: =>
().hide(ev)
if or or
CUI.Events.listen
type: "mouseenter"
node:
call: (ev) =>
if CUI.globalDrag
return
?(ev)
if ev.isImmediatePropagationStopped()
return
if
()
().showTimeout().start()
if
menu = ()
menu_stop_hide()
if not and menu.hasItems(ev)
menu_shown = CUI.dom.data(CUI.dom.find(".cui-button--hover-menu")[0], "element")
if menu_shown and menu_shown != menu
menu_shown.hide(ev)
CUI.dom.addClass(menu.DOM, "cui-button--hover-menu")
CUI.Events.ignore
instance: @
node: menu
CUI.Events.listen
type: "mouseenter"
node: menu
instance: @
only_once: true
call: =>
menu_stop_hide()
CUI.Events.listen
type: "mouseleave"
node: menu
instance: @
only_once: true
call: =>
menu_start_hide(ev)
menu.show(ev)
return
CUI.Events.listen
type: "mouseleave"
node:
call: (ev) =>
# = false
if CUI.globalDrag
return
?(ev)
if
menu_start_hide(ev, 100)
return
setSize: (size) ->
remove = []
for cls in .classList
if cls.startsWith("cui-button-size")
remove.push(cls)
for cls in remove
.classList.remove(cls)
if size
.classList.add("cui-button-size-"+size)
@
onClickAction: (ev) ->
if # or ev.button != 0
ev.preventDefault()
return
()?.hide(ev)
if
if
({}, ev)
else
({}, ev)
if () and
# not (ev.ctrlKey or ev.shiftKey or ev.altKey or ev.metaKey) and
not and
().hasItems(ev)
().show(ev)
# in some contexts (like FileUploadButton), this
# is necessary, so we stop the file upload
# to open
#
ev.preventDefault()
return
if ev.isImmediatePropagationStopped()
return
CUI.Events.trigger
type: "cui-button-click"
node: @
info:
event: ev
if ev.isImmediatePropagationStopped() or not
return
.call(@, ev, @)
initOpts: ->
super()
tabindex:
default: 0
check: (v) ->
CUI.util.isInteger(v) or v == false
size:
check: ["mini","normal","big","bigger"]
appearance:
check: ["link","flat","normal","important","transparent-border"]
primary:
mandatory: true
default: false
check: Boolean
onClick:
check: Function
click_type:
default: "click" # "touchend"
mandatory: true
check: (v) ->
!!CUI.Button.clickTypes[v]
text:
check: String
tooltip:
check: "PlainObject"
disabled:
default: false
check: (v) ->
CUI.util.isBoolean(v) or CUI.util.isFunction(v)
loading:
default: false
check: (v) ->
CUI.util.isBoolean(v) or CUI.util.isFunction(v)
active_css_class:
default: CUI.defaults.class.Button.defaults.active_css_class
check: String
left:
check: (v) ->
if v == true
return true
(CUI.util.isElement(v) or
v instanceof CUI.Element or
CUI.util.isString(v)) and
not @_icon and
not @_icon_left and
not @_icon_active and
not @_icon_inactive
right:
check: (v) ->
(v == true or CUI.util.isContent(v)) and not @_icon_right
center:
check: (v) ->
CUI.util.isContent(v) or CUI.util.isString(v)
icon:
check: (v) ->
v instanceof CUI.Icon or CUI.util.isString(v)
icon_left:
check: (v) ->
v instanceof CUI.Icon or CUI.util.isString(v)
icon_right:
check: (v) ->
v instanceof CUI.Icon or CUI.util.isString(v) or v == false
icon_active:
check: (v) ->
v instanceof CUI.Icon or CUI.util.isString(v)
icon_inactive:
check: (v) ->
v instanceof CUI.Icon or CUI.util.isString(v)
text_active:
check: String
text_inactive:
check: String
value: {}
name:
check: String
hidden:
check: (v) ->
CUI.util.isBoolean(v) or CUI.util.isFunction(v)
menu:
check: "PlainObject"
menu_on_hover:
check: Boolean
menu_parent:
check: CUI.Menu
onActivate:
check: Function
onDeactivate:
check: Function
onMouseenter:
check: Function
onMouseleave:
check: Function
# if set, this button belongs
# to a group of buttons
# on click, the active state of
# this button will be set and unset
# on the others
radio:
check: (v) ->
CUI.util.isString(v) or v == true
# whether to allow de-select
# on radio buttons
radio_allow_null:
check: Boolean
switch:
check: Boolean
active:
check: Boolean
# set to false to skip running onActivate and onDeactivate
# callbacks on initial activate/deactivate when the button is
# created
activate_initial:
default: true
check: Boolean
#group can be used for buttonbars to specify a group css style
group:
check: String
role:
default: "button"
mandatory: true
check: String
# return icon for string
__getIcon: (icon) ->
if not icon
null
else if icon instanceof CUI.Icon
icon
else
new CUI.Icon(icon: icon)
readOpts: ->
if .switch
CUI.util.assert(CUI.util.isUndef(.radio_allow_null), "new CUI.Button", "opts.switch cannot be used together with opts.radio_allow_null", opts: )
super()
if
CUI.util.assert( == true or not ( or or ), "new CUI.Button", "opts.left != true cannot be used togeter with opts.icon*", opts: )
getCenter: ->
return .map.center;
__getTemplateName: ->
if or or or or
= true
else
= false
if or ( and != false) or
= true
else
= false
if and
CUI.util.isUndef() and
CUI.util.isUndef() and
CUI.util.isUndef() and
CUI.util.isUndef() and
CUI.__ng__
= false
if and
return "button"
else if
if
return "button-left-center"
else
return "button-left"
else if
return "button-center-right"
else
return "button-center"
getTemplateName: ->
if CUI.__ng__
() + "-ng"
else
()
getValue: ->
getElementForLayer: ->
return .map.visual
getRadioButtons: ->
if not
return []
("radio", )
getGroupButtons: ->
if not ()
return []
("button-group", ())
# returns other buttons
__getButtons: (key, value) ->
parents = CUI.dom.parents(, ".cui-buttonbar,.cui-form-table,.cui-item-list-body,.cui-layer")
if parents.length == 0
# buttons are not grouped by anything, so we
# have no other buttons, so we use the top level element
parents = CUI.dom.parents()
if parents.length > 0
docElem = parents[parents.length-1]
else
return []
(CUI.dom.data(c, "element") for c in CUI.dom.matchSelector(docElem, ".cui-button[#{key}=\"#{value}\"]"))
hasMenu: ->
!!
hasLeft: ->
getMenu: ->
if not ()
return
if
else
= new CUI.Menu()
menuSetActiveIdx: (idx) ->
if
.setActiveIdx(idx)
else
.itemList.active_item_idx = idx
@
getMenuRootButton: ->
if
return .getButton()?.getMenuRootButton()
else if ()
return @
else
null
#TODO rename to toggleActiveState
toggle: (flags={}, event) ->
(not , flags, event)
setActive: (active, flags={}, event) ->
if active
(flags, event)
else
(flags, event)
__callOnGroup: (call, flags, event) ->
group = ()
if not group or not event?.hasModifierKey() or flags.ignore_ctrl
return
flags.ignore_ctrl = true
if not event.altKey()
started = true
else
started = false
for btn in ()
if btn == @
started = true
continue
if not started
continue
btn[call](flags, event)
return
activate: (flags={}, event) ->
# console.error "activate", flags, (), ,
activate = =>
()
("pressed", true)
()
("activate", flags, event)
return
if == false and flags.initial_activate
= true
activate()
return @
if == true and CUI.util.isEmptyObject(flags)
return @
if
_flags =
prior_activate: @
initial_activate: flags.initial_activate
for btn, idx in ()
if btn == @ or not btn.isActive()
continue
# don't send the event here, since this happens
# code driven
btn.deactivate(_flags)
= true
ret = ?(@, flags, event)
if CUI.util.isPromise(ret)
ret.done(activate).fail =>
= false
return ret
activate()
@
deactivate: (flags={}, event) ->
# console.error "deactivate", flags, (), , ,
deactivate = =>
()
("pressed", false)
()
("deactivate", flags, event)
return
if == false and flags.initial_activate
= false
deactivate()
return @
if == false and CUI.util.isEmptyObject(flags)
return @
= false
ret = ?(@, flags, event)
if CUI.util.isPromise(ret)
ret.done(deactivate).fail =>
= true
return ret
deactivate()
@
setIconRight: (icon=null) ->
(icon, "right")
setIcon: (icon=null, _key="left") ->
key = "__icon_"+_key
if icon == ""
@[key] = ""
else
@[key] = (icon)
CUI.util.assert(@[key] == null or @[key] == "" or @[key] instanceof CUI.Icon, "CUI.Button.setIcon", "icon needs to be instance of Icon", icon: icon)
if @[key] == null
(_key)
else if @[key] == ""
(CUI.dom.element("SPAN"), _key)
else
(@[key], _key)
@
startSpinner: ->
CUI.util.assert(, "CUI.Button.startSpinner", "No space for Icon found, make sure the Button was created with opts.left set.", opts: )
if
return
= ()
= true
("spinner")
@
stopSpinner: ->
()
= false
= null
@
getIcon: ->
getIconRight: ->
__setState: ->
()
()
__setIconState: ->
if not ( or )
return @
if ()
if not
("")
else
()
else
if not
("")
else
()
@
__setTextState: ->
if not ( or )
return @
if ()
if not CUI.util.isNull()
()
else
if not CUI.util.isNull()
()
@
isActive: ->
!!
isDisabled: ->
isEnabled: ->
not
setEnabled: (enabled) ->
if enabled
()
else
()
setLoading: (on_off) ->
if on_off
CUI.dom.addClass(, CUI.defaults.class.Button.defaults.loading_css_class)
= true
else
CUI.dom.removeClass(, CUI.defaults.class.Button.defaults.loading_css_class)
= false
isLoading: ->
disable: ->
CUI.dom.addClass(, CUI.defaults.class.Button.defaults.disabled_css_class)
CUI.dom.removeAttribute(, "tabindex")
= true
@
enable: ->
CUI.dom.removeClass(, CUI.defaults.class.Button.defaults.disabled_css_class)
CUI.dom.setAttribute(, "tabindex", )
= false
@
setText: () ->
if CUI.util.isEmpty()
= ''
span = CUI.dom.text()
if not
span.id = "button-text-"+()
("labelledby", span.id)
(span, "center")
setTextMaxChars: (max_chars) ->
CUI.dom.setAttribute(().firstChild, "data-max-chars", max_chars)
getText: ->
getGroup: ->
setGroup: () ->
if
CUI.dom.setAttribute(, "button-group", )
else
CUI.dom.removeAttribute(, "button-group")
__initTooltip: ->
if and not .isDestroyed()
return @
tt_opts = CUI.util.copyObject()
tt_opts.element ?=
# make sure the tooltip does not register any listeners
for k in ["on_hover", "on_click"]
CUI.util.assert(not tt_opts.hasOwnProperty(k), "CUI.Button.__initTooltip", "opts.tooltip cannot contain #{k}.", opts: )
tt_opts[k] = false
= new CUI.Tooltip(tt_opts)
@
getTooltip: ->
isShown: ->
not ()
isHidden: ->
destroy: ->
# console.debug "destroying button", , ()
?.destroy()
= null
?.destroy()
= null
super()
show: ->
= false
CUI.dom.removeClass(, "cui-button-hidden")
CUI.dom.showElement()
CUI.Events.trigger
type: "show"
node:
hide: ->
= true
CUI.dom.addClass(, "cui-button-hidden")
CUI.dom.hideElement()
CUI.Events.trigger
type: "hide"
node:
:
click: ['click']
mouseup: ['mouseup']
dblclick: ['dblclick']
:
click: ['dblclick', 'mousedown']
mouseup: ['mouseup', 'mousedown']
dblclick: ['click', 'mousedown']
CUI.defaults.class.Button = CUI.Button
CUI.Events.registerEvent
type: ["show", "hide", "cui-button-click"]
bubble: true