coffeescript-ui
Version:
Coffeescript User Interface System
1,290 lines (996 loc) • 35.3 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
###
# a layer is a top level positioned div
# which can be used to display Menu
# Popver, Modal, Autocompletion and
# other dialogs
CUI.Template.loadTemplateText(require('./Layer.html'));
class CUI.Layer extends CUI.DOMElement
= ["auto", "both", "horizontal", "vertical"]
constructor: (opts) ->
super(opts)
= ()
# layer is registered as our main layer TODO register layer_root as our main layer!
()
# .DOM.attr("role", )
# layer root is a container for the backdrop, pointer and layer
#
# this is only to hold all nodes in one node, the backdrop is a sibling
# of the layer, that way we can give the backdrop an opacity which does
# not affect the layer's opacity as it would to if the layer would be
# a child of backdrop
= new CUI.Template
class: "cui-layer-root-"+(CUI.util.toDash()+" "+( or "")).trim().split(/\s+/).join(" cui-layer-root-")
name: "layer-root"
# = false
if
= .policy or "click-thru"
= new CUI.Template
class: "cui-layer-backdrop"
name: "layer-backdrop"
.addClass("cui-layer-backdrop-policy-"+)
if .content
(.content)
else if .blur
# clone body
body_clone = document.body.firstChild.cloneNode(true)
if == "click-thru"
# backdrop needs to be cropped
= .DOM
(body_clone)
else
= CUI.dom.div("cui-layer-backdrop-body-clone")
.appendChild(body_clone)
()
# console.error "crop this:",
.addClass("cui-layer-root-backdrop-blur")
if == "click-thru" and not .blur and not .content
; # = true
else
.DOM.appendChild(.DOM)
# .DOM.attr("role", )
switch
when "click-thru"
; # ()
when "click"
CUI.Events.listen
type: ["click", "contextmenu"]
node:
call: (ev) =>
if ev.ctrlKey() and ev.getButton() == 2
return
# if
# return
(ev)
ev.stopPropagation()
when "modal"
.addClass("cui-layer-backdrop--visible")
if .add_bounce_class != false
if CUI.util.isString(.add_bounce_class)
bc = .add_bounce_class
else
bc = "cui-layer-bounce"
CUI.Events.listen
type: "click"
node:
call: (ev) =>
# console.debug "clicked on modal backdrop", bc,
if not
return
CUI.Events.wait
type: "transitionend"
node:
.always =>
if ()
return
.removeClass(bc)
.addClass(bc)
return
else
CUI.util.assert("new #{@__cls}", "Unknown backdrop policy: \"#{@__bd_policy}\".")
if == false
()
.DOM.appendChild(.DOM)
if
CUI.dom.setAttribute(.DOM, "tabindex", "0")
if
()
if
("cui-layer--size-"+)
if
CUI.util.assert(, "new CUI.Layer", "opts.use_element_width_as_min_width requires opts.element to be set.", opts: )
if
if
cls = "cui-layer-pointer-"+.split(/\s+/).join(" cui-layer-pointer-")
= new CUI.Template
class: cls
name: "layer-pointer"
.DOM
#append pointer and make sure pointer is rendered beneath layer.
.DOM.appendChild()
= false
# __addClickThruListener: ->
# CUI.Events.listen
# type: "mousedown"
# capture: true
# node: window
# call: (ev) =>
# console.debug "ev", ev.getTarget(), CUI.dom.parents(ev.getTarget())
# if ev.ctrlKey() and ev.getButton() == 2
# return
# if CUI.dom.closest(ev.getTarget(), ".cui-tmpl-layer-root")
# console.debug "ignoring click on layer 'backdrop'"
# return
# # if
# # # this is used in Popover when all buttons are disabled
# # # we need to eat this
# # return
# (ev)
# return
# disableBackdropClick: ->
# = true
# @
# enableBackdropClick: ->
# = false
# @
setBackdropContent: (content) ->
CUI.util.assert(, "CUI.Layer.setBackdropContent", "No backdrop found in layer", layer: @)
CUI.dom.append(.DOM, content)
getBackdrop: ->
getTemplate: ->
new CUI.Template(name: "layer")
getLayerRoot: ->
getLayer: ->
initOpts: ->
super()
# role:
# mandatory: true
# check: String
# set to true if a backdrop should be
# added to the DOM tree
backdrop:
default:
policy: "click-thru"
add_bounce_class: true
content: null
check: (v) ->
if CUI.util.isPlainObject(v) or v == false
return true
# if added, a bounce class will be added and after a css transition
# removed to the layer, if the user clicks on a modal backdrop
# the bounce class defaults to "cui-layer-bounce"
add_bounce_class:
deprecated: "use backdrop.add_bounce_class instead"
onBeforeShow:
check: Function
onShow:
check: Function
onPosition:
check: Function
onHide:
check: Function
# add a size class
size:
check: ["xs", "s", "m", "l", "xl"]
# handle focus on tab index
handle_focus:
default: true
check: Boolean
# a rectangle box to position the layer to
# a pointer is a helper to show the position of the layer
# allowed values are "arrow"
pointer:
check: ["arrow"]
# the preferred placement
placement:
check: String
placements:
check: (v) ->
if not CUI.util.isArray(v)
return false
for a in v
if @knownPlacements.indexOf(a) == -1
return false
return true
# element to position the layer to
element:
check: (v) ->
CUI.util.isElement(v) or CUI.util.isElement(v?.DOM)
use_element_width_as_min_width:
default: false
check: Boolean
show_at_position:
check: (v) ->
CUI.util.isPlainObject(v) and v.top >= 0 and v.left >= 0
# fills the available space to the maximum
# if used with "placement", the placement is not
# chosen
#
# auto: check if width or height is already set, use the other
#
#
# both: overwrite width and height, no matter what
# horizontal: stretch width only
# vertical: stretch height only
fill_space:
default: "auto"
mandatory: true
check: CUI.Layer.FILL_SPACE_CHECK_ARRAY
# if set, the Layer, when shown checks if the "element"
# is still in the DOM tree
check_for_element:
default: false
check: Boolean
show_ms:
default: 700
check: (v) =>
v > 0
visible:
check: Boolean
hide_ms:
default: 100
check: (v) =>
v > 0
#
@
readOpts: ->
super()
@__fill_space = @_fill_space
return @
setVisible: (on_off=true) ->
if on_off
CUI.dom.setStyleOne(@__layer_root.DOM, "visibility", "")
else
CUI.dom.setStyleOne(@__layer_root.DOM, "visibility", "hidden")
__allPlacements: ["s", "e", "w", "ws", "wn", "n", "se", "ne", "es", "en", "nw", "sw", "c"]
knownPlacements: ["s", "e", "w", "ws", "wn", "n", "se", "ne", "es", "en", "nw", "sw", "c"]
__setElement: (element) ->
if element instanceof CUI.DOMElement
@__element = element.getElementForLayer()
else if element.DOM
@__element = element.DOM
else
@__element = element
CUI.util.assert(not CUI.dom.closest(@__element, ".cui-tmpl"), "Layer.__setElement", "element cannot be inside a Template.", element: element)
CUI.util.assert(@__element instanceof HTMLElement, "Layer.__setElement", "element needs to be HTMLElement.", element: element)
@__element
__getOriginalElement: ->
@_element?.DOM or @_element or null
autoSize: ->
@position()
position: (ev) ->
#
if not @isShown()
return @
dim_body = CUI.dom.getDimensions(document.body)
dim_body.isPositioned = dim_body.computedStyle.position in ["relative", "fixed", "absolute"]
dim_window =
width: window.innerWidth
height: window.innerHeight
get_pointer_direction = (placement) ->
# return direction
{
n: "s"
s: "n"
e: "w"
w: "e"
}[placement]
get_pointer_class = (direction) =>
"cui-layer-pointer--"+direction
if
# reset pointer
CUI.dom.setStyle(,
top: 0
left: 0
margin: ""
)
for direction in ["w", "s", "e", "n"]
CUI.dom.removeClass(, get_pointer_class(direction))
# measure all 4 directions for all pointers
dim_pointer = {}
for placement in ["n", "s", "e", "w"]
pointer_direction = get_pointer_direction(placement)
if
CUI.dom.addClass(, get_pointer_class(pointer_direction))
dim_pointer[placement] = CUI.dom.getDimensions()
CUI.dom.removeClass(, get_pointer_class(pointer_direction))
else
dim_pointer[placement] =
borderBoxWidth: 0
borderBoxHeight: 0
marginLeft: 0
marginRight: 0
marginTop: 0
marginBottom: 0
dim_pointer[placement].direction = pointer_direction
# store scroll positions
for el in CUI.dom.matchSelector(.DOM, "*")
if el.scrollTop
el._storedScrollTop = el.scrollTop
if el.scrollLeft
el._storedScrollLeft = el.scrollLeft
# reset previously set layer dimensions
CUI.dom.setStyle .DOM,
position: ""
top: ""
left: ""
width: ""
height: ""
margin: ""
minWidth: ""
maxWidth: ""
maxHeight: ""
dim_layer = CUI.dom.getDimensions(.DOM)
# console.debug "layer dimensions:", dim_layer.borderBoxWidth, dim_layer.borderBoxHeight, .DOM
allowed_placements = ( or ).slice(0)
wanted_placement = or allowed_placements[0]
body_scroll_top = dim_body.scrollTop + document.documentElement.scrollTop
body_scroll_left = dim_body.scrollLeft + document.documentElement.scrollLeft
if
dim_element = CUI.dom.getDimensions()
else if
dim_element =
viewportTop: .top
viewportLeft: .left
dim_element.viewportBottom = dim_element.viewportTop
dim_element.viewportRight = dim_element.viewportLeft
else
dim_element =
viewportTop: 0
viewportLeft: 0
viewportBottom: dim_window.height
viewportRight: dim_window.width
viewportCenterLeft: dim_window.width / 2
viewportCenterTop: dim_window.height / 2
borderBoxWidth: dim_window.width
borderBoxHeight: dim_window.height
vp_pl = {}
# calc all possible layer viewports
for placement in
if placement not in ["n", "s", "e", "w", "c"]
continue
vp_pl[placement] = vp = {}
vp.window_top = dim_layer.marginTop
vp.window_left = dim_layer.marginLeft
vp.window_right = dim_window.width - dim_layer.marginRight
vp.window_bottom = dim_window.height - dim_layer.marginBottom
vp.dim_window = dim_window
vp.dim_layer = dim_layer
vp.dim_element = dim_element
vp.dim_pointer = dim_pointer[placement]
switch placement
when "c"
vp.top = vp.window_top
vp.left = vp.window_left
vp.right = vp.window_right
vp.bottom = vp.window_bottom
vp.align_vertical = "center"
vp.align_horizontal = "center"
when "n"
vp.top = dim_layer.marginTop
vp.left = dim_layer.marginLeft
vp.right = dim_window.width - dim_layer.marginRight
vp.bottom = dim_element.viewportTop - vp.dim_pointer.borderBoxHeight - vp.dim_pointer.marginBottom
vp.align_vertical = "bottom"
vp.align_horizontal = "center"
when "s"
vp.top = dim_element.viewportBottom + vp.dim_pointer.borderBoxHeight + vp.dim_pointer.marginTop
vp.left = dim_layer.marginLeft
vp.right = dim_window.width - dim_layer.marginRight
vp.bottom = dim_window.height - dim_layer.marginBottom
vp.align_vertical = "top"
vp.align_horizontal = "center"
when "e"
vp.top = dim_layer.marginTop
vp.right = dim_window.width - dim_layer.marginRight
vp.bottom = dim_window.height - dim_layer.marginBottom
vp.left = dim_element.viewportRight + vp.dim_pointer.borderBoxWidth + vp.dim_pointer.marginLeft
vp.align_vertical = "center"
vp.align_horizontal = "left"
when "w"
vp.top = dim_layer.marginTop
vp.bottom = dim_window.height - dim_layer.marginBottom
vp.right = dim_element.viewportLeft - vp.dim_pointer.borderBoxWidth - vp.dim_pointer.marginRight
vp.left = dim_layer.marginLeft
vp.align_vertical = "center"
vp.align_horizontal = "right"
vp.pointer_align_vertical = vp.align_vertical
vp.pointer_align_horizontal = vp.align_horizontal
vp.overlap_align = null
# add two-direction placements
for placement in
if placement in ["n", "s", "e", "w", "c"]
continue
placement_parts = placement.split("")
vp_pl[placement] = vp = CUI.util.copyObject(vp_pl[placement_parts[0]])
vp.dim_pointer = dim_pointer[placement_parts[0]]
if not vp
continue
switch placement_parts[1]
when "s"
vp.top = dim_element.viewportTop
vp.align_vertical = "top"
vp.pointer_align_vertical = "center"
vp.overlap_align = "bottom"
when "n"
vp.bottom = dim_element.viewportBottom
vp.align_vertical = "bottom"
vp.pointer_align_vertical = "center"
vp.overlap_align = "top"
when "e"
vp.left = dim_element.viewportLeft
vp.align_horizontal = "left"
vp.pointer_align_horizontal = "center"
vp.overlap_align = "right"
when "w"
vp.right = dim_element.viewportRight
vp.align_horizontal = "right"
vp.pointer_align_horizontal = "center"
vp.overlap_align = "left"
# throw out placements which are too small
for placement in
if placement not in allowed_placements
delete(vp_pl[placement])
continue
vp = vp_pl[placement]
if vp.window_top > vp.top
vp.top = vp.window_top
if vp.window_left > vp.left
vp.left = vp.window_left
if vp.window_bottom < vp.bottom
vp.bottom = vp.window_bottom
if vp.window_right < vp.right
vp.right = vp.window_right
vp.width = vp.right - vp.left
vp.height = vp.bottom - vp.top
# placement might not be available, no space
# FIXME: we can read minWidth & minHeight from layer
# here, so the design can decide on a minimum
if vp.width < 10 or vp.height < 10
delete(vp_pl[placement])
# now we need to position the layer within the available viewport
for placement, vp of vp_pl
layer_pos = vp.layer_pos = {}
pointer_pos = vp.pointer_pos = {}
# number of times we need to cut the layer
# to make it fit into the viewport
vp.cuts = 0
# set width on height on the layer
# depending on the available viewport and the
# fill space policy
switch
when "both"
layer_pos.width = vp.width
layer_pos.height = vp.height
when "vertical"
layer_pos.height = vp.height
layer_pos.width = dim_layer.borderBoxWidth
when "horizontal"
layer_pos.width = vp.width
layer_pos.height = dim_layer.borderBoxHeight
else
layer_pos.width = dim_layer.borderBoxWidth
layer_pos.height = dim_layer.borderBoxHeight
if layer_pos.width > vp.width
layer_pos.width = vp.width
vp.cuts++
if layer_pos.height > vp.height
layer_pos.height = vp.height
vp.cuts++
# console.debug , placement, vp, layer_pos.width, layer_pos.height, vp.width, vp.height
# now align the layer within the available viewport
switch vp.align_horizontal
when "left"
layer_pos.left = vp.left
when "right"
layer_pos.left = vp.right - layer_pos.width
when "center"
layer_pos.left = dim_element.viewportCenterLeft - layer_pos.width / 2
switch vp.align_vertical
when "top"
layer_pos.top = vp.top
when "bottom"
layer_pos.top = vp.bottom - layer_pos.height
when "center"
layer_pos.top = dim_element.viewportCenterTop - layer_pos.height / 2
if vp.dim_pointer
# now align the pointer within the available viewport
switch vp.pointer_align_horizontal
when "left"
pointer_pos.left = dim_element.viewportRight + vp.dim_pointer.marginLeft
when "right"
pointer_pos.left = dim_element.viewportLeft - vp.dim_pointer.borderBoxWidth - vp.dim_pointer.marginLeft
when "center"
pointer_pos.left = dim_element.viewportCenterLeft - vp.dim_pointer.borderBoxWidth / 2
switch vp.pointer_align_vertical
when "top"
pointer_pos.top = dim_element.viewportBottom + vp.dim_pointer.marginTop
when "bottom"
pointer_pos.top = dim_element.viewportTop - vp.dim_pointer.marginBoxHeight + vp.dim_pointer.marginTop
when "center"
pointer_pos.top = dim_element.viewportCenterTop - vp.dim_pointer.borderBoxHeight / 2
pointer_pos.width = vp.dim_pointer.borderBoxWidth
pointer_pos.height = vp.dim_pointer.borderBoxHeight
pointer_pos.direction = vp.dim_pointer.direction
# move layer into viewport in case we overlap
if layer_pos.top < vp.top
layer_pos.top = vp.top
vp.cuts++
if layer_pos.left < vp.left
layer_pos.left = vp.left
vp.cuts++
overlap_bottom = layer_pos.top + layer_pos.height - vp.bottom
if overlap_bottom > 0
layer_pos.top = layer_pos.top - overlap_bottom
overlap_right = layer_pos.left + layer_pos.width - vp.right
if overlap_right > 0
layer_pos.left = layer_pos.left - overlap_right
vp.overlap_height = 0
vp.overlap_width = 0
if or
# we allow an overlap push for certain placements,
# that means, if we cut the layer we allow it to
# be positioned outside the viewport (but inside the window)
overlap_height = dim_layer.borderBoxHeight - layer_pos.height
if overlap_height > 0
switch vp.overlap_align
when "bottom"
vp.overlap_height = Math.min(layer_pos.top - vp.window_top, overlap_height)
layer_pos.top = layer_pos.top - vp.overlap_height
layer_pos.height = layer_pos.height + vp.overlap_height
when "top"
vp.overlap_height = Math.min(vp.window_bottom - layer_pos.top - layer_pos.height, overlap_height)
layer_pos.height = layer_pos.height + vp.overlap_height
overlap_width = dim_layer.borderBoxWidth - layer_pos.width
# console.debug "overlap:", dim_layer.borderBoxWidth, layer_pos.width, overlap_width
if overlap_width > 0
switch vp.overlap_align
when "right"
vp.overlap_width = Math.min(layer_pos.left - vp.window_left, overlap_width)
layer_pos.left = layer_pos.left - vp.overlap_width
layer_pos.width = layer_pos.width + vp.overlap_width
when "left"
vp.overlap_height = Math.min(vp.window_right - layer_pos.right, overlap_width)
layer_pos.width = layer_pos.width + vp.overlap_width
if
layer_pos_right = vp.layer_pos.left + vp.layer_pos.width
layer_pos_bottom = vp.layer_pos.top + vp.layer_pos.height
pointer_pos_right = vp.pointer_pos.left + vp.pointer_pos.width
pointer_pos_bottom = vp.pointer_pos.top + vp.pointer_pos.height
# push layer further, so the pointer has enough margin to the
# edge of the layer
switch vp.pointer_pos.direction
when "n", "s"
marginLeft = vp.pointer_pos.left - vp.layer_pos.left
pushNeeded = marginLeft - vp.dim_pointer.marginLeft
if pushNeeded < 0
spaceAvailable = vp.layer_pos.left - vp.window_left
vp.push_left = Math.min(spaceAvailable, -pushNeeded)
vp.layer_pos.left = vp.layer_pos.left - vp.push_left
marginRight = layer_pos_right - pointer_pos_right
pushNeeded = marginRight - vp.dim_pointer.marginRight
if pushNeeded < 0
spaceAvailable = vp.window_right - layer_pos_right
vp.push_right = Math.min(spaceAvailable, -pushNeeded)
vp.layer_pos.left = vp.layer_pos.left + vp.push_right
when "e", "w"
marginTop = vp.pointer_pos.top - vp.layer_pos.top
pushNeeded = marginTop - vp.dim_pointer.marginTop
if pushNeeded < 0
spaceAvailable = vp.layer_pos.top - vp.window_top
vp.push_top = Math.min(spaceAvailable, -pushNeeded)
vp.layer_pos.top = vp.layer_pos.top - vp.push_top
marginBottom = layer_pos_bottom - pointer_pos_bottom
pushNeeded = marginBottom - vp.dim_pointer.marginBottom
if pushNeeded < 0
spaceAvailable = vp.window_bottom - layer_pos_bottom
vp.push_bottom = Math.min(spaceAvailable, -pushNeeded)
vp.layer_pos.top = vp.layer_pos.top + vp.push_bottom
if
# link dimensions, so the callback can
# use it
?(@, vp)
# overflow cutting
# the higher this number, the better
vp.layer_pos.estate = vp.layer_pos.width * vp.layer_pos.height
vp.layer_pos.aspect_ratio = (vp.layer_pos.width or 1) / (vp.layer_pos.height or 1)
vp.dim_layer.aspect_ratio = (vp.dim_layer.borderBoxWidth or 1) / (vp.dim_layer.borderBoxHeight or 1)
wanted_rank = (allowed_placements.length - CUI.util.idxInArray(placement, allowed_placements))
if wanted_placement == placement
wanted_rank = allowed_placements.length + 1
vp.ranking =
wanted_rank*10 +
1 - Math.abs(vp.layer_pos.aspect_ratio - vp.dim_layer.aspect_ratio) +
vp.layer_pos.estate
# pick best placement
available_placements = []
for placement, vp of vp_pl
available_placements.push(placement)
if available_placements.length == 0
()
console.warn("Layer.position", "No available placements found.")
return
# console.debug "sorting placements BEFORE", ((pl+"["+vp_pl[pl].ranking+"]") for pl in available_placements).join(", ")
# sort available placements
available_placements.sort (pl1, pl2) ->
value = (pl) ->
vp_pl[pl].ranking
CUI.util.compareIndex(value(pl1), value(pl2))
available_placements.reverse()
# console.debug "sorting placements AFTER", available_placements.join(", ")
placement = available_placements[0]
if ev?.hasModifierKey()
console.debug "Layer.position", @,
console.debug "layer", dim_layer
console.debug "element", dim_element
console.debug "pointer", dim_pointer
console.debug "window", dim_window
console.debug "placements", placement, vp_pl
show_dbg_div = (placement) =>
()
_vp = vp_pl[placement]
console.info("Layer: Placement", placement, _vp)
= CUI.dom.element("DIV")
= CUI.dom.element("DIV")
= CUI.dom.element("DIV")
style1 =
position: "absolute"
zIndex: 2
border: "2px solid #ff0032"
backgroundColor: "rgba(255, 0, 0, 0.4)"
top: _vp.top
left: _vp.left
width: _vp.width
height: _vp.height
CUI.dom.setStyle(, style1)
style2 =
position: "absolute"
zIndex: 2
border: "2px solid #00ff32"
backgroundColor: "rgba(0, 255, 0, 0.4)"
top: _vp.layer_pos.top
left: _vp.layer_pos.left
width: _vp.layer_pos.width
height: _vp.layer_pos.height
alignItems: "center"
justifyContent: "center"
fontSize: 40
color: "rgb(0,255,50)"
span = CUI.dom.element("SPAN")
span.textContent = placement
.appendChild(span)
CUI.dom.setStyle(, style2)
style3 =
position: "absolute"
zIndex: 2
border: "2px solid #0032ff"
backgroundColor: "rgba(0, 0, 255, 0.4)"
top: _vp.pointer_pos.top
left: _vp.pointer_pos.left
width: _vp.pointer_pos.width
height: _vp.pointer_pos.height
CUI.dom.setStyle(, style3)
.DOM.appendChild()
.DOM.appendChild()
if
.DOM.appendChild()
dbg_pl = 0
listener = CUI.Events.listen
node: document
type: "keyup"
call: (ev, info) =>
if ev.keyCode() != 32
return
while (true)
dbg_pl = dbg_pl + 1
if dbg_pl == .length
()
listener.destroy()
return
_placement = [dbg_pl]
if vp_pl[_placement]
show_dbg_div(_placement)
return
console.warn("Placement", _placement, "is unavailable.")
return
vp = vp_pl[placement]
# console.debug "Layer.position: Placement:", placement, "Wanted:", wanted_placement, "Allowed:", allowed_placements, "Viewports:", vp_pl, @
# console.info "PLACEMENT --- ", placement, "---"
# console.debug "Layer POS", vp.layer_pos, "align:", vp.align_horizontal, "/", vp.align_vertical, "overlap:", vp.overlap_align, vp.push_left, vp.push_right
# console.debug "Pointer POS", vp.pointer_pos, "align:", vp.pointer_align_horizontal, "/", vp.pointer_align_vertical
if vp.layer_pos.width < 10 or vp.layer_pos.height < 10
; # console.warn("Layer: DIM is very small, layer might not be visible. Placement:", placement, "Dimensions:", vp, "Layer:", .DOM)
if and
minWidth = dim_element.borderBoxWidth
else
minWidth = undefined
CUI.dom.setAttribute(.DOM, "cui-placement", placement)
CUI.dom.setAttribute(.DOM, "cui-fill-space", )
set_css =
top: vp.layer_pos.top
left: vp.layer_pos.left
minWidth: minWidth
margin: 0
if not dim_layer.computedStyle.maxWidth or dim_layer.computedStyle.maxWidth == 'none'
set_css.maxWidth = vp.width + vp.overlap_width
if not dim_layer.computedStyle.maxHeight or dim_layer.computedStyle.maxHeight == 'none'
set_css.maxHeight = vp.height + vp.overlap_height
if placement == "c"
is_fixed = true
else
is_fixed = .DOM.previousElementSibling?.hasAttribute("cui-layer-fixed") or false
if is_fixed
.DOM.setAttribute("cui-layer-fixed", "")
set_css.top = vp.layer_pos.top
set_css.left = vp.layer_pos.left
else
.DOM.removeAttribute("cui-layer-fixed")
set_root_css =
top: body_scroll_top
left: body_scroll_left
bottom: 0
right: 0
# if the body is positioned, we need to adjust the root layer's position
if dim_body.isPositioned
set_root_css.top -= (dim_body.marginTop + dim_body.borderTopWidth)
set_root_css.left -= (dim_body.marginLeft + dim_body.borderLeftWidth)
set_root_css.bottom -= (dim_body.marginBottom + dim_body.borderBottomWidth)
set_root_css.right -= (dim_body.marginRight + dim_body.borderRightWidth)
CUI.dom.setStyle(.DOM, set_root_css)
if placement == "c" and not CUI.browser.ie
# placement can be done by pure CSS
; # return @
set_css.width = Math.ceil(vp.layer_pos.width)
set_css.height = Math.ceil(vp.layer_pos.height)
if CUI.browser.ie
# IE 11 (Edge renderer) cannot perfectly update a DIVs CSS
# without leaving scrollbars in some cases, we need to fully
# DOM detach the DIV and put it back in.
sibl = .DOM.previousElementSibling
CUI.dom.remove()
CUI.dom.setStyle(.DOM, set_css)
# console.debug "pos:", dim_element, vp.layer_pos.top, "body scroll:", body_scroll_top
if
# set pointer
if is_fixed
CUI.dom.setStyle ,
top: vp.pointer_pos.top
left: vp.pointer_pos.left
margin: 0
else
CUI.dom.setStyle ,
top: vp.pointer_pos.top
left: vp.pointer_pos.left
margin: 0
CUI.dom.addClass(, get_pointer_class(vp.pointer_pos.direction))
if
CUI.dom.setStyle ,
top: vp.layer_pos.top
left: vp.layer_pos.left
width: vp.layer_pos.width
height: vp.layer_pos.height
CUI.dom.setStyle .firstChild,
width: dim_window.width
height: dim_window.height
top: -vp.layer_pos.top
left: -vp.layer_pos.left
if CUI.browser.ie
CUI.dom.insertAfter(sibl, .DOM)
# restore scroll positions
for el in CUI.dom.matchSelector(.DOM, "*")
if el._storedScrollTop
el.scrollTop = el._storedScrollTop
delete(el._storedScrollTop)
if el._storedScrollLeft
el.scrollLeft = el._storedScrollLeft
delete(el._storedScrollLeft)
# We could re-read the layer width & height here to actually
# set it in Style. By doing that we could have support for transitions
# when Layer content size changes.
return @
__removeDebugDivs: ->
CUI.dom.remove()
CUI.dom.remove()
CUI.dom.remove()
= null
= null
= null
hide: (ev) ->
#remove our fixed size
if ()
return
if not ()
return @
()
if
if
CUI.clearInterval()
CUI.dom.removeClass((), ())
CUI.dom.remove(.DOM)
CUI.dom.setAttributeMap .DOM,
"cui-layer-stack-number": null
"cui-layer-stack-count": null
()
= false
if
(ev)
# if
# =
# delete()
CUI.Events.ignore(instance: @)
?(@, ev)
@
__updateLayerStackCounter: ->
# count all active layers and set a counter to the body
# and to each, so a layer knows its stack number
layer_elements = CUI.dom.matchSelector(document.documentElement, "body > .cui-layer-root")
total = layer_elements.length
for el, idx in layer_elements
CUI.dom.setAttributeMap el,
"cui-layer-stack-number": idx
"cui-layer-stack-count": total
@
getElementOpenClass: ->
"cui-layer-open"
# use element to temporarily overwrite element used
# for positioning
show: (ev) ->
# console.error "show ", ()
# "element" as first parameter is gone, i don't think we need this
CUI.util.assert(not (), "#{@__cls}.show", "Unable to show, Layer ##{@getUniqueId()} is already destroyed", layer: @)
if ()
()
return @
# if element
# =
# (element)
# .DOM.css
# top: 0
# left: 0
document.body.appendChild(.DOM)
if
CUI.dom.addClass((), ())
for scroll_parent in CUI.dom.parentsScrollable()
CUI.Events.listen
type: "scroll"
instance: @
node: scroll_parent
call: =>
()
if
= CUI.setInterval =>
if not CUI.dom.isInDOM()
()
,
200
()
CUI.Events.listen
type: "content-resize"
instance: @
node:
call: (ev) =>
# console.error "Layer caught event:", ev.getType()
()
CUI.Events.listen
type: "viewport-resize"
instance: @
node:
call: (ev) =>
if and not CUI.dom.isInDOM()
# ignore the event
return
# console.info("Layer caught event:", ev.getType)
()
return
?(@, ev)
= true
(ev)
if
(ev)
?(@, ev)
@
isKeyboardCancellable: (ev) ->
if in ["click", "click-thru"] # and not
true
else
false
doCancel: (ev) ->
(ev)
focusOnShow: (ev) ->
if ev == CUI.KeyboardEvent
= true
else if and CUI.dom.matchSelector(document.documentElement, ":focus").length > 0
; # = true
else
= false
if or ()
.focus()
@
forceFocusOnShow: ->
false
focusOnHide: (ev) ->
if not
return @
if ev == CUI.KeyboardEvent or
CUI.dom.findElement(, "[tabindex]")?.focus()
@
getElement: ->
isShown: ->
destroy: ->
# console.error "Layer.destroy",@, ()
if
()
super()
?.destroy()
?.destroy()
= null
= null
CUI.dom.remove()
= null
?.destroy()
= null
setFillSpace: (fillSpace) ->
CUI.util.assert(fillSpace in Layer.FILL_SPACE_CHECK_ARRAY, "Layer.setFillSpace", "Parameter fillSpace should be: #{Layer.FILL_SPACE_CHECK_ARRAY.join('')}", fillSpace: fillSpace)
= fillSpace
()
return @
CUI.ready ->
CUI.Events.listen
type: ["mousedown", "touchend"]
capture: true
# install not do high, do Drag & Drop can be on top
node: document.body
call: (ev, info) ->
layer_elements = CUI.dom.find("body > .cui-layer-root, body > .cui-pane-fill-screen-is-on, body > .cui-layer-prevent-click-thru")
target = ev.getTarget()
for layer_element in layer_elements by -1
if not CUI.dom.hasClass(layer_element, "cui-layer-backdrop-policy-click-thru")
return
if CUI.dom.closest(target, layer_element)
return
layer = CUI.dom.data(CUI.dom.children(layer_element, ".cui-layer")[0], "element")
# prevent following click, if the target of the mousedown
# is the element which positioned the layer.
element = layer.getElement()
if element and CUI.dom.closest(ev.getTarget(), element)
CUI.Events.listen
node: document.documentElement
type: ["dblclick", "click"]
capture: true
only_once: true
call: (ev) =>
# console.error "eating click on layer opening element"
return ev.stop()
ev.cui_layer_closed = true
layer.hide(ev)
return
CUI.Events.listen
type: ["keyup"]
node: document.body
call: (ev) ->
if ev.keyCode() != 27 or CUI.globalDrag
return
layer_elements = CUI.dom.find("body > .cui-layer-root > .cui-layer:not(.cui-tooltip)")
layer_element = layer_elements[layer_elements.length-1]
# console.error ev.getType(), ev, layer_elements
if not layer_element
return
if ev.getTarget() not in [layer_element, document.body]
return
layer = CUI.dom.data(layer_element, "element")
ev.stopImmediatePropagation()
ev.preventDefault()
if layer.isKeyboardCancellable(ev)
layer.doCancel(ev)
return false
return