summaryrefslogtreecommitdiffstats
path: root/debian/missing-sources/epoch/src/core/css.coffee
blob: 03a73105e74ed51c65e9d01dd22eeefb0c9404e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Singelton class used to query CSS styles by way of reference elements.
# This allows canvas based visualizations to use the same styles as their
# SVG counterparts.
class QueryCSS
  # Reference container id
  REFERENCE_CONTAINER_ID = '_canvas_css_reference'

  # Container Hash Attribute
  CONTAINER_HASH_ATTR = 'data-epoch-container-id'

  # Handles automatic container id generation
  containerCount = 0
  nextContainerId = -> "epoch-container-#{containerCount++}"

  # Expression used to derive tag name, id, and class names from
  # selectors given the the put method.
  PUT_EXPR = /^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/

  # Whether or not to log full selector lists
  logging = false

  # Converts selectors into actual dom elements (replaces put.js)
  # Limited the functionality to what Epoch actually needs to
  # operate correctly. We detect class names, ids, and element
  # tag names.
  put = (selector) ->
    match = selector.match(PUT_EXPR)
    return Epoch.error('Query CSS cannot match given selector: ' + selector) unless match?
    [whole, tag, id, classNames] = match
    tag = (tag ? 'div').toUpperCase()

    element = document.createElement(tag)
    element.id = id.substr(1) if id?
    if classNames?
      element.className = classNames.substr(1).replace(/\./g, ' ')

    return element

  # Lets the user set whether or not to log selector lists and resulting DOM trees. 
  # Useful for debugging QueryCSS itself.
  @log: (b) ->
    logging = b

  # Key-Value cache for computed styles that we found using this class.
  @cache = {}

  # List of styles to pull from the full list of computed styles
  @styleList = ['fill', 'stroke', 'stroke-width']

  # The svg reference container
  @container = null

  # Purges the selector to style cache
  @purge: ->
    QueryCSS.cache = {}

  # Gets the reference element container.
  @getContainer: ->
    return QueryCSS.container if QueryCSS.container?
    container = document.createElement('DIV')
    container.id = REFERENCE_CONTAINER_ID
    document.body.appendChild(container)
    QueryCSS.container = d3.select(container)

  # @return [String] A unique identifier for the given container and selector.
  # @param [String] selector Selector from which to derive the styles
  # @param container The containing element for a chart.
  @hash: (selector, container) ->
    containerId = container.attr(CONTAINER_HASH_ATTR)
    unless containerId?
      containerId = nextContainerId()
      container.attr(CONTAINER_HASH_ATTR, containerId)
    return "#{containerId}__#{selector}"

  # @return The computed styles for the given selector in the given container element.
  # @param [String] selector Selector from which to derive the styles.
  # @param container HTML containing element in which to place the reference SVG.
  @getStyles: (selector, container) ->
    # 0) Check for cached styles
    cacheKey = QueryCSS.hash(selector, container)
    cache = QueryCSS.cache[cacheKey]
    return cache if cache?

    # 1) Build a full reference tree (parents, container, and selector elements)
    parents = []
    parentNode = container.node().parentNode

    while parentNode? and parentNode.nodeName.toLowerCase() != 'body'
      parents.unshift parentNode
      parentNode = parentNode.parentNode
    parents.push container.node()

    selectorList = []
    for element in parents
      sel = element.nodeName.toLowerCase()
      if element.id? and element.id.length > 0
        sel += '#' + element.id
      if element.className? and element.className.length > 0
        sel += '.' + Epoch.Util.trim(element.className).replace(/\s+/g, '.')
      selectorList.push sel

    selectorList.push('svg')

    for subSelector in Epoch.Util.trim(selector).split(/\s+/)
      selectorList.push(subSelector)

    console.log(selectorList) if logging

    parent = root = put(selectorList.shift())
    while selectorList.length
      el = put(selectorList.shift())
      parent.appendChild el
      parent = el

    console.log(root) if logging

    # 2) Place the reference tree and fetch styles given the selector
    QueryCSS.getContainer().node().appendChild(root)

    ref = d3.select('#' + REFERENCE_CONTAINER_ID + ' ' + selector)
    styles = {}
    for name in QueryCSS.styleList
      styles[name] = ref.style(name)
    QueryCSS.cache[cacheKey] = styles

    # 3) Cleanup and return the styles
    QueryCSS.getContainer().html('')
    return styles


Epoch.QueryCSS = QueryCSS