reports.coffee 13.2 KB
Newer Older
1
# Need to use Coffeescript 1.x to maintain IE compatibility.
2 3 4 5
# controls whether results are updated automatically or manually by
# a direct call to 'update_results'
_auto_recalc = true

Ian Moore's avatar
Ian Moore committed
6 7 8
# flags whether inputs are in the process of being loaded
_loading_inputs = false

9 10
# Called when inputs that affect the results have changed
inputs_changed = ->
11 12 13
  # having trouble with multi-select refreshing
  if $('#select_sensor_multi').prop('disabled') == false
    $('#select_sensor_multi').selectpicker('refresh')
Ian Moore's avatar
Ian Moore committed
14 15 16 17 18 19 20 21
  if _auto_recalc and not _loading_inputs
    # update the window location url if needed
    if urlQueryString() == ''
      history.replaceState(null,null,"?".concat(serializedInputs()))
    else if serializedInputs() != urlQueryString()
      history.pushState(null,null,"?".concat(serializedInputs()))
    # update the results display
    update_results() 
22

Ian Moore's avatar
Ian Moore committed
23
serializedInputs = ->
24
  $("select, input").serialize()
Ian Moore's avatar
Ian Moore committed
25
  
26 27 28
# Updates the results portion of the page
update_results = ->
  $("body").css "cursor", "wait"    # show hourglass
29 30 31
  
  # get lingering tooltips from prior result.  Hide them.
  $('#results [data-toggle="tooltip"]').tooltip('hide')   
Ian Moore's avatar
Ian Moore committed
32

Alan Mitchell's avatar
Alan Mitchell committed
33
  $.getJSON($("#BaseURL").text() + "reports/results/", serializedInputs()).done((results) -> 
34 35 36 37 38
    # load the returned HTML into the results div, but empty first to ensure
    # event handlers, etc. are removed
    $("body").css "cursor", "default"   # remove hourglass cursor
    $("#results").empty()
    $("#results").html results.html
39 40 41

    $('#results [data-toggle="tooltip"]').tooltip()

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    # apply custom settings to bmon-sensor-id elements
    $("#results .bmon-sensor-id")
        .attr("data-toggle","tooltip")
        .attr("data-original-title", "Click to copy Sensor ID to Clipboard")
        .css("cursor","pointer")
        .tooltip()
        .click ->
            target = this;
            navigator.clipboard.writeText(target.innerText).then ->
                $(target)
                    .attr("data-original-title", "Copied Sensor ID to Clipboard!")
                    .tooltip('show')
                return
            return
        .on('hidden.bs.tooltip', ->
            $(this).attr("data-original-title", "Click to copy Sensor ID to Clipboard")
            return
        )
        

62 63 64 65
    # Loop through the returned JavaScript objects to create and make them
    $.each results.objects, (ix, obj) ->
      [obj_type, obj_config] = obj
      switch obj_type
66 67 68 69
        when 'plotly'
          Plotly.plot(obj_config.renderTo, obj_config.data, obj_config.layout, obj_config.config)
        when 'dashboard'
          ANdash.createDashboard(obj_config)
70 71
  ).fail (jqxhr, textStatus, error) ->
    $("body").css "cursor", "default"   # remove hourglass cursor
72
    err = textStatus + ", " + error
73 74
    alert "Error Occurred: " + err

75 76
# copies a link to embed the current report into another page
get_embed_link = ->
77 78 79 80 81
  title = document.getElementById("report_title")
  if title != null
    link_comment = "<!--- Embedded BMON Chart: #{title.innerText} --->"
  else
    link_comment = "<!--- Embedded BMON Chart --->"
82
  link_text = '<script src="' + $("#BaseURL").text() + 'reports/embed/' + '?' + serializedInputs() + '" async></script>'
83
  link_dialog = $("<div class='popup' title='Copy and paste this text to embed this view in a Custom Report:'><textarea id='embed_link' rows=5 style='width: 99%;font-size: 85%;resize: vertical'>#{link_comment}&#010;#{link_text}&#010;</textarea></div>")
84
  $('#embed_link').text(link_comment + '\n' + link_text + '\n')
85
  
86 87 88
# Sets the visibility of elements in the list of ids 'ctrl_list'.
# If 'show' is true then the element is shown, hidden otherwise.
set_visibility = (ctrl_list, show) ->
89 90 91
  for ctrl in ctrl_list
    element = document.getElementById($.trim(ctrl))
    if show
92
      $(element).show().find("select, input:visible").prop( "disabled", false )
93 94
    else
      $(element).hide().find("select, input").prop( "disabled", true )
95 96 97
    $("#report-container").removeClass("d-none")
    $("#report-container").removeClass("d-block")
    $("#report-container").addClass("d-block")
98
  show
99 100 101 102 103 104 105 106 107

# A timer used by some charts to do a timed refresh of the results.
REFRESH_MS = 600000  # milliseconds between timed refreshes
_refresh_timer = setInterval update_results, REFRESH_MS

# Handles actions required when the chart type changes.  Mostly sets
# the visibility of controls.
process_chart_change = ->

108
    # start by hiding all input controls
109
  set_visibility(['refresh', 'ctrl_sensor', 'ctrl_avg', 'ctrl_avg_export',
110
    'ctrl_normalize', 'ctrl_occupied', 'xy_controls', 'time_period_group', 
111
    'download_many', 'get_embed_link','ctrl_use_rolling_averaging'], false)
112

113 114 115 116 117 118 119 120 121
  # get the chart option control that is selected.  Then use the data
  # attributes of that option element to configure the user interface.
  selected_chart_option = $("#select_chart").find("option:selected")

  # list of controls that should be visible
  vis_ctrls = selected_chart_option.data("ctrls").split(",")
  set_visibility(vis_ctrls, true)

  # Should timed refresh be set?
Alan Mitchell's avatar
Alan Mitchell committed
122
  clearInterval _refresh_timer   # clear any old timer
123 124
  if selected_chart_option.data("timed_refresh") == 1
    _refresh_timer = setInterval update_results, REFRESH_MS
Alan Mitchell's avatar
Alan Mitchell committed
125
 
126 127 128
  # set auto recalculation
  _auto_recalc = (selected_chart_option.data("auto_recalc") == 1)

129 130 131
  # Show the proper sensor selector
  single = $('#select_sensor')
  multi = $('#select_sensor_multi')
132
  if selected_chart_option.data("multi_sensor") == 1
133 134 135 136 137 138 139 140 141
    sensor_val = single.val()
    single.prop('disabled', true)
    single.hide()
    multi.selectpicker('show')
    multi.prop('disabled', false)
    # use last value from single select as the starting value for the multi-select
    multi.selectpicker('val', [sensor_val]) 
    multi.selectpicker('refresh')
    multi.off().change inputs_changed
142
    $('#label_sensor').html('Select Sensors to Plot:')
143
  else
144 145 146 147 148 149 150 151
    sensor_val = multi.selectpicker('val')[0]
    multi.prop('disabled', true)
    multi.selectpicker('hide')
    single.show()
    single.prop('disabled', false)
    # transfer over the first selected value from the multi-select
    single.val(sensor_val)
    single.off().change inputs_changed
152
    $('#label_sensor').html('Select Sensor to Plot:')
153 154 155 156 157

  # if this is an XY plot, transfer the sensor value over to
  # the Y sensor selector.
  if $('#select_sensor_y').prop('disabled')  == false
    $('#select_sensor_y').val(sensor_val)
158 159 160 161 162 163 164 165 166

  # if manual recalc, then blank out the results area to clear our remnants
  # from last chart
  $("#results").empty() if _auto_recalc == false

  # the chart type changed so indicated that inputs have changed
  inputs_changed()

# Updates the list of charts and sensors appropriate for the building selected.
167 168
# If chart_id and sensor_id are passed, selects that chart and sensor after
# updating the list of apprpriate charts and sensors.
169
update_chart_sensor_lists = (event) ->
170
  # load the options from a AJAX query for the selected building
171
  url = "#{$("#BaseURL").text()}chart-sensor-list/#{$("#select_org").val()}/#{$("#select_bldg").val()}/"
Ian Moore's avatar
Ian Moore committed
172 173 174
  $.ajax
    url: url
    dataType: "json"
Ian Moore's avatar
Ian Moore committed
175
    async: not _loading_inputs
Ian Moore's avatar
Ian Moore committed
176 177 178
    success: (data) ->
      $("#select_chart").html(data.charts)
      $("#select_sensor").html(data.sensors)
179
      $("#select_sensor_multi").html(data.sensors)
Ian Moore's avatar
Ian Moore committed
180 181
      $("#select_sensor_x").html(data.sensors)
      $("#select_sensor_y").html(data.sensors)
182
      process_chart_change()
183

184 185 186 187 188 189 190 191 192 193 194 195 196 197
# Updates the list of building groups associated with the Organization selected.
update_group_list = ->
  # load the group choices from a AJAX query for the selected organization
  url = "#{$("#BaseURL").text()}group-list/#{$("#select_org").val()}/"
  $.ajax
    url: url
    dataType: "html"
    async: not _loading_inputs
    success: (data) ->
      $("#select_group").html(data)
      if not _loading_inputs
        # trigger the change event of the building selector to get the 
        # selected option to process.
        $("#select_group").trigger "change"
198 199 200 201 202
      # if there is only one item in the facility groups selector, hide it.
      if document.getElementById("select_group").length > 1
        $('#group_controls').show()
      else
        $('#group_controls').hide()
203

204 205 206
# Updates the list of buildings associated with the Building Group selected.
update_bldg_list = ->
  # load the building choices from a AJAX query for the selected building group
207
  url = "#{$("#BaseURL").text()}bldg-list/#{$("#select_org").val()}/#{$("#select_group").val()}/"
Ian Moore's avatar
Ian Moore committed
208 209 210
  $.ajax
    url: url
    dataType: "html"
Ian Moore's avatar
Ian Moore committed
211
    async: not _loading_inputs
Ian Moore's avatar
Ian Moore committed
212 213
    success: (data) ->
      $("#select_bldg").html(data)
Ian Moore's avatar
Ian Moore committed
214
      if not _loading_inputs
Ian Moore's avatar
Ian Moore committed
215 216 217
        # trigger the change event of the building selector to get the 
        # selected option to process.
        $("#select_bldg").trigger "change"
Ian Moore's avatar
Ian Moore committed
218 219 220 221 222

# handle the history.popstate event
$(window).on "popstate", (event) ->
  handleUrlQuery()
  update_results()
223

Ian Moore's avatar
Ian Moore committed
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
# extract the query string portion of the current window's url
urlQueryString = () ->
  url = window.location.href
  queryStart = url.indexOf('?') + 1
  if queryStart > 0
    url.substr(queryStart)
  else
    ''

# parse and handle the url query string
handleUrlQuery = () ->
    params = {}
    $.each urlQueryString().replace(/\+/g, '%20').split('&'), ->
      name_value = @split('=')
      name = decodeURIComponent(name_value[0])
      value = if name_value.length > 1 then decodeURIComponent(name_value[1]) else null
      if !(name of params)
        params[name] = []
      params[name].push value
      return
      
    # sort the params so their events fire properly
    sortedNames = do ->
247
      names = ['select_org', 'select_group','select_bldg','select_chart']
Ian Moore's avatar
Ian Moore committed
248 249 250 251 252 253 254 255
      for name of params
        if name not in names
          names.push name
      names
      
    # update control values
    _loading_inputs = true
    for name in sortedNames
Ian Moore's avatar
Ian Moore committed
256
      element = $('[name=\'' + name + '\']')
257
      if params.hasOwnProperty(name) and element.length > 0
258
        new_value = params[name]
259
        if element[0].getAttribute("type") == "radio"
260 261 262 263
          old_value = element.filter(":radio:checked").val()
        else
          old_value = element.val()
        if `old_value != new_value`
264 265 266 267 268 269 270 271 272
          if element[0].getAttribute("type") == "radio"
            # need to use an array to set value of radio buttons.
            element.val([new_value])
            # if this is the time_period radio group, reset the active class
            if element[0].getAttribute("name") == "time_period"
              # special case due to being a Bootstrap 4 button group.
              # remove active from labels, and add active to selected radio's label.
              element.parent().removeClass("active")
              $('input[name=time_period]:checked').parent().addClass("active")
273 274 275
          else if element.hasClass('selectpicker')
            # special case of bootstrap-multiselect
            element.selectpicker('val', new_value)
276 277
          else
            element.val(new_value)
Ian Moore's avatar
Ian Moore committed
278
      element.change() # trigger the change event
Ian Moore's avatar
Ian Moore committed
279 280 281
    _loading_inputs = false
    params
  
282 283 284 285 286 287 288
# Function to show or hide the Custom Date range inputs
setCustomDateVis = () ->
  unless $("input:radio[name=time_period]:checked").val() is "custom"
    $("#custom_dates").hide().find("select, input").prop( "disabled", true )
  else
    $("#custom_dates").show().find("select, input").prop( "disabled", false )

289 290 291 292 293
# ---------------------------------------------------------------
# function that runs when the document is ready.
$ ->

  # Related to selecting the range of dates to chart
294 295 296
  $('#start_date').datepicker uiLibrary: 'bootstrap4'
  $('#end_date').datepicker uiLibrary: 'bootstrap4'

297 298
  d = new Date() # current date to use for a default for Start Date
  $("#start_date").val (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear()
299
  
300
  # Show and Hide custom date range selector
301 302
  $(document).on 'change', 'input:radio[name="time_period"]', setCustomDateVis
  setCustomDateVis()
303
  
304
  # make refresh button a jQuery button & call update when clicked
305
  $("#refresh").click update_results
306
  $("#get_embed_link").click get_embed_link     
307
  $("#div_date").datepicker uiLibrary: 'bootstrap4'   # for xy plot
308 309 310 311 312

  # special handling of the Excel Export button because the content for this report
  # is not displayed in a normal results div.
  $("#download_many").button().click ->
    window.location.href = "#{$("#BaseURL").text()}reports/results/?" + 
Ian Moore's avatar
Ian Moore committed
313
      serializedInputs();
314 315

  # Set up controls and functions to respond to events
316
  $("#select_org").change update_bldg_list
317
  $("#select_org").change update_group_list
318 319 320 321
  $("#select_group").change update_bldg_list
  $("#select_bldg").change update_chart_sensor_lists
  $("#select_chart").change process_chart_change

322
  # Set up change handlers for inputs.
323
  ctrls = ['averaging_time', 'averaging_time_export', 'normalize', 'use_rolling_averaging', 'show_occupied', 
324 325
    'select_sensor', 'select_sensor_x', 'select_sensor_y', 'averaging_time_xy', 'div_date',
    'start_date', 'end_date']
326 327
  $("##{ctrl}").change inputs_changed for ctrl in ctrls

Alan Mitchell's avatar
Alan Mitchell committed
328 329 330
  # Special change handling of the time period radio inputs.
  $(document).on 'change', 'input:radio[name="time_period"]', inputs_changed

Ian Moore's avatar
Ian Moore committed
331
  # Handle any query parameters on the url
Ian Moore's avatar
Ian Moore committed
332
  handleUrlQuery()
Ian Moore's avatar
Ian Moore committed
333 334 335 336 337 338
  
  # Update the window's url to reflect the current inputs state
  history.replaceState(null,null,"?".concat(serializedInputs()))
  
  # Update the display based on the inputs
  inputs_changed()