text: (function() {
      'use strict';
  
      angular.module('chaise.record')
  
      .controller('RecordController', ['AlertsService', 'ConfigUtils', 'DataUtils', 'ERMrest', 'ErrorService', 'logService', 'MathUtils', 'messageMap', 'modalBox', 'modalUtils', 'recordAppUtils', 'recordCreate', 'recordsetDisplayModes', 'UiUtils', 'UriUtils', '$cookies', '$document', '$log', '$rootScope', '$scope', '$timeout', '$window',
          function RecordController(AlertsService, ConfigUtils, DataUtils, ERMrest, ErrorService, logService, MathUtils, messageMap, modalBox, modalUtils, recordAppUtils, recordCreate, recordsetDisplayModes, UiUtils, UriUtils, $cookies, $document, $log, $rootScope, $scope, $timeout, $window) {
          var vm = this;
  
          var initialHref = $window.location.href;
          var mainContainerEl = angular.element(document.getElementsByClassName('main-container')[0]);
          vm.addRecordRequests = {}; /// generated id: {displayMode: "", containerIndex: integer}
          vm.editRecordRequests = {}; // generated id: {displayMode: "", containerIndex: integer, completed: boolean}
          var modalUpdate = false;
          vm.alerts = AlertsService.alerts;
          vm.makeSafeIdAttr = DataUtils.makeSafeIdAttr;
  
          vm.sidePanToggleBtnIndicator = "Show";
          $rootScope.recordSidePanOpen = true;
  
          // the top-left-panel that needs to be resizable with toc
          vm.resizePartners = document.querySelector(".top-left-panel");
  
          var chaiseConfig = ConfigUtils.getConfigJSON();
  
          vm.tooltip = messageMap.tooltip;
          vm.queryTimeoutTooltip = messageMap.queryTimeoutTooltip;
  
          vm.versionDisplay = function () {
              return UiUtils.humanizeTimestamp($rootScope.reference.location.versionAsMillis);
          }
  
          vm.versionDate = function () {
              return UiUtils.versionDate($rootScope.reference.location.versionAsMillis);
          }
  
          vm.toggleSidebar = function() {
              var action = ($rootScope.recordSidePanOpen ? logService.logActions.TOC_HIDE : logService.logActions.TOC_SHOW );
              logService.logClientAction({
                  action: logService.getActionString(action),
                  stack: logService.getStackObject()
              }, $rootScope.reference.defaultLogInfo);
  
              $rootScope.recordSidePanOpen = !$rootScope.recordSidePanOpen;
          };
  
          vm.canCreate = function() {
              return ($rootScope.reference && $rootScope.reference.canCreate && $rootScope.modifyRecord);
          };
  
          // TODO change this to reference.unfilteredReference.contextualize...
          vm.createRecord = function() {
              $window.location.href = $rootScope.reference.table.reference.contextualize.entryCreate.appLink;
          };
  
          vm.canEdit = function() {
              var canEdit = ($rootScope.reference && $rootScope.reference.canUpdate && $rootScope.modifyRecord);
              // If user can edit this record (canEdit === true), then change showEmptyRelatedTables.
              // Otherwise, canEdit will be undefined, so no need to change anything b/c showEmptyRelatedTables is already false.
  
              return canEdit;
          };
  
          vm.editRecord = function() {
              $window.location.href = $rootScope.reference.contextualize.entryEdit.appLink;
          };
  
          vm.copyRecord = function() {
              var appLink = $rootScope.reference.contextualize.entryCreate.appLink;
              var separator = "?";
              // if appLink already has query params, add &
              // NOTE: With the ppid and pcid implementation appLink will always have
              // that, this is just to avoid further changes if we reverted that change.
              if (appLink.indexOf("?") !== -1) {
                  separator = "&";
              }
  
              $window.location.href = appLink + separator + "copy=true&limit=1";
          };
  
          vm.canDelete = function() {
              return ($rootScope.reference && $rootScope.reference.canDelete && $rootScope.modifyRecord && $rootScope.showDeleteButton);
          };
  
          vm.deleteRecord = function() {
              var errorData = {};
              var logObj = {
                  action: logService.getActionString(logService.logActions.DELETE),
                  stack: logService.getStackObject()
              };
              $rootScope.reference.delete(logObj).then(function deleteSuccess() {
                  // Get an appLink from a reference to the table that the existing reference came from
                  var unfilteredRefAppLink = $rootScope.reference.table.reference.contextualize.compact.appLink;
                  $rootScope.showSpinner = false;
                  $window.location.href = unfilteredRefAppLink;
              }, function deleteFail(error) {
                  $rootScope.showSpinner = false;
                  errorData.redirectUrl = $rootScope.reference.unfilteredReference.contextualize.compact.appLink;
                  errorData.gotoTableDisplayname = $rootScope.reference.displayname.value;
                  error.errorData = errorData;
                  ErrorService.handleException(error, true); // call error module with isDismissible = True
              });
          };
  
          // this function assumes tuple and reference are attached to the $rootScope
          vm.sharePopup = function() {
              var tuple = $rootScope.tuple;
              var ref = $rootScope.reference;
              var refTable = ref.table;
  
              var params = {
                  displayname: refTable.name+'_'+tuple.uniqueId,
                  reference: ref
              }
  
              var versionString = "@" + (ref.location.version || refTable.schema.catalog.snaptime);
              params.permalink = UriUtils.resolvePermalink(tuple, ref);
              params.versionLink = UriUtils.resolvePermalink(tuple, ref, versionString);
              params.versionDateRelative = UiUtils.humanizeTimestamp(ERMrest.versionDecodeBase32(refTable.schema.catalog.snaptime));
              params.versionDate = UiUtils.versionDate(ERMrest.versionDecodeBase32(refTable.schema.catalog.snaptime));
  
              var snaptimeHeader = {
                  action: logService.getActionString(logService.logActions.SHARE_OPEN),
                  stack: logService.getStackObject(),
                  catalog: ref.defaultLogInfo.catalog,
                  schema_table: ref.defaultLogInfo.schema_table
              }
              refTable.schema.catalog.currentSnaptime(snaptimeHeader).then(function (snaptime) {
                  // if current fetched snpatime doesn't match old snaptime, show a warning
                  params.showVersionWarning = (snaptime !== refTable.schema.catalog.snaptime);
              }).finally(function() {
                  modalUtils.showModal({
                      templateUrl: UriUtils.chaiseDeploymentPath() + "common/templates/shareCitation.modal.html",
                      controller: "ShareCitationController",
                      windowClass: "chaise-share-citation",
                      controllerAs: "ctrl",
                      resolve: {
                          params: params
                      }
                  }, false, false, false); // not defining any extra callbacks
              });
          };
  
          vm.toRecordSet = function(ref) {
              event.preventDefault();
              event.stopPropagation();
              // Search app might be broken and should not be linked from here.
              var appUrl = ref.appLink,
                  recordSetUrl;
  
              if (appUrl.search("/search/") > 0) {
                  recordSetUrl = appUrl.replace("/search/", "/recordset/");
              } else {
                  recordSetUrl = appUrl;
              }
              return $window.location.href = recordSetUrl;
          };
  
          /**
           * Make sure we're showing related tables from top to bottom.
           * If the previous one has not been loaded yet, it should return false.
           * @param  {integer} i related table index
           * @return {boolean}   whether to show related table or not
           */
          vm.showRelatedTable = function(i) {
              if (!$rootScope.relatedTableModels) return false;
  
              var rtm = $rootScope.relatedTableModels[i];
              var canShow = function () {
                  if (!rtm.tableModel.initialized || !rtm.tableMarkdownContentInitialized) {
                    return false;
                  }
  
                  if (i === 0 || $rootScope.lastRendered === i-1)  {
                      $rootScope.lastRendered = i;
  
                      // don't show the loading if it's done
                      if ($rootScope.loading && $rootScope.lastRendered === $rootScope.relatedTableModels.length-1) {
                          // defer autoscroll to next digest cycle to ensure aggregates and images were fetched and loaded for last RT
                          $timeout(autoScroll, 0);
                          $rootScope.loading = false;
                      }
  
                      return true;
                  }
  
                  return false;
              };
  
              if (canShow()) {
                  return ($rootScope.showEmptyRelatedTables || (rtm.tableModel.page && rtm.tableModel.page.length > 0));
              }
              return false;
          };
  
          vm.showInlineTable = function (i) {
              var cm = $rootScope.columnModels[i];
              return cm.isInline && ($rootScope.showEmptyRelatedTables || (cm.tableModel.page && cm.tableModel.page.length > 0));
          };
  
          /**
           * allow related table markdown display if all the following are true:
           *  - reference.display.type is `markdown`
           *  - related table has data.
           *  - related table's tableMarkdownContent is not empty string
           *
           * we are going to show the markdown display if the result if this function
           * is true and the related table model is in markdown mode.
           * @param  {integer} i related table index
           * @return {boolean}   whether to show related table markdown or not.
           */
          vm.allowRelatedTableMarkdown = function (i) {
              if (!$rootScope.relatedTableModels) return false;
              var rtm = $rootScope.relatedTableModels[i];
              var tm = rtm.tableModel;
              return tm.reference.display.type == 'markdown' && tm.page && tm.page.tuples.length > 0 && rtm.tableMarkdownContentInitialized && rtm.tableMarkdownContent != '';
          };
  
          vm.noVisibleRelatedTables = function () {
              if ($rootScope.relatedTableModels) {
                  return !$rootScope.relatedTableModels.some(function (tm, index) {
                      return vm.showRelatedTable(index);
                  });
              }
              return true;
          };
  
          vm.toggleDisplayMode = function(dataModel) {
              event.preventDefault();
              event.stopPropagation();
  
              // if tableModel, table context is nested inside model
              var tableModel = dataModel;
              if (dataModel.tableModel) tableModel = dataModel.tableModel;
  
              var action = dataModel.isTableDisplay ? logService.logActions.RELATED_DISPLAY_MARKDOWN : logService.logActions.RELATED_DISPLAY_TABLE;
              logService.logClientAction({
                  action: logService.getActionString(action, tableModel.logStackPath),
                  stack: tableModel.logStack
              }, tableModel.reference.defaultLogInfo);
  
              dataModel.isTableDisplay = !dataModel.isTableDisplay;
          };
  
          vm.toggleRelatedTables = function() {
              var action = ($rootScope.showEmptyRelatedTables ? logService.logActions.EMPTY_RELATED_HIDE : logService.logActions.EMPTY_RELATED_SHOW);
              logService.logClientAction({
                  action: logService.getActionString(action),
                  stack: logService.getStackObject()
              }, $rootScope.reference.defaultLogInfo);
  
              $rootScope.showEmptyRelatedTables = !$rootScope.showEmptyRelatedTables;
              // NOTE: there's a case where clicking the button to toggle this doesn't re-paint the footer until the mouse "moves"
              // having this $timeout triggers the function after the digest cycle which is after the elements have finished showing/hiding based on the above flag
              $timeout(function () {
                  UiUtils.attachFooterResizeSensor(0);
              }, 0);
          };
  
          vm.logAccordionClick = function (rtm) {
              var action = (rtm.open ? logService.logActions.CLOSE : logService.logActions.OPEN);
  
              logService.logClientAction({
                  action: logService.getActionString(action, rtm.tableModel.logStackPath),
                  stack: rtm.tableModel.logStack
              }, rtm.tableModel.reference.defaultLogInfo);
          }
  
          vm.canEditRelated = function(ref) {
             if(angular.isUndefined(ref)) return false;
             return (ref.canUpdate && $rootScope.modifyRecord);
          };
  
          vm.canCreateRelated = function(relatedRef) {
              if(angular.isUndefined(relatedRef) || !$rootScope.modifyRecord) {
                  return false;
              }
  
              // we are not supporting add in this case
              if (relatedRef.pseudoColumn && !relatedRef.pseudoColumn.isInboundForeignKey) {
                  return false;
              }
  
              var ref = (relatedRef.derivedAssociationReference ? relatedRef.derivedAssociationReference : relatedRef);
              return ref.canCreate;
          };
  
          // Send user to RecordEdit to create a new row in this related table
          function onSuccess (tableModel){
              return function () {
                  AlertsService.addAlert("Your data has been submitted. Showing you the result set...","success");
                  vm.resultset = true;
                  onfocusEventCall({
                      displayMode: tableModel.config.displayMode,
                      containerIndex: tableModel.config.containerIndex
                  });
              }
          }
  
          function onModalClose () {
              recordAppUtils.resumeUpdateRecordPage();
          }
  
          /**
           * Whether we can pre-fill the foreignkey value given origFKR as the foreignkey
           * relationship between parent and related table.
           *
           * A foreignkey can only prefilled if it's a superset of the origFKR,
           * and extra columns are not-null.
           *
           * By superset we mean that it must include all the columns of origFKR and
           * the mapping must be exactly the same as origFKR. So for example if origFKR
           * is T1(c1,c2) -> T2(v1,v2), the candidate foreignkey must have at least
           * c1 and c2, and in its definition c1 must map to v1 and c2 must map to v2.
           * It could also have any extra not-null columns.
           *
           * NOTE: Technically we can prefill all the foreignkeys that are supserset
           * of "key" definitions that are in origFKR (assuming extra columns are not-null).
           * For example assuming origFKR is T1(RID, c1) -> T2(RID, v1). A foreignkey definied as
           * T1(RID, c2) -> T2(RID, v2) could be prefilled (assuming c2 is not-null).
           * Since the RID alone is defining the one-to-one relation between the current
           * row and the rows that we want to add for the related table.
           * For now, we decided to not do this complete check and just stick with
           * foreignkeys that are supserset of the origFKR.
           *
           *
           * NOTE: recordedit will prefill all the foreignkeys that their constituent
           * columns are prefilled. Therefore we don't need to send the foreignkey
           * constraint names that must be prefilled and we can only send the "keys" attribute.
           * Recordedit page can easily deduce the foreignkey values and rowname itself.
           * Although in that case recordedit is going to create different references for
           * each foreignkeys eventhough they are referring to the same row of data.
           * So instead of multiple reads, we just have to read the parent record once
           * and use that data for all the foreignkeys that can be prefilled.
           * For this reason, I didn't remove passing of fkColumnNames for now.
           *
           * @param  {Object} fk foreignkey object that we want to test
           * @return {boolean} whether it can be prefilled
           */
          function foreignKeyCanBePrefilled(fk, origFKR) {
              // origFKR will be added by default
             if (fk === origFKR) return true;
  
             // if fk is not from the same table, or is shorter
             if (fk.colset.length < origFKR.length) return false;
             if (fk.colset.columns[0].table.name !== origFKR.colset.columns[0].table.name) return false;
  
             var len = 0;
             for (var i = 0; i < fk.colset.length(); i++) {
                 var fkCol = fk.colset.columns[i];
                 var origCol = origFKR.colset.columns.find(function (col) {
                     return col.name === fkCol.name;
                 });
  
                 // same column
                 if (origCol) {
                     // it must map to the same column
                     if (fk.mapping.get(fkCol).name !== origFKR.mapping.get(origCol).name) {
                         return false;
                     }
  
                     len++; // count number of columns that overlap
                 } else if (fkCol.nullok) {
                     return false;
                 }
             }
  
             // the foriegnkey must be superset of the origFKR
             return len == origFKR.key.colset.length();
          }
  
          function getPrefillCookieObject(ref) {
              var origTable;
              if (ref.derivedAssociationReference) {
                  // add association relies on the object that this returns for
                  // prefilling the data.
                  origTable = ref.derivedAssociationReference.table;
              } else {
                  // we should contextualize to make sure the same table is shown in create mode
                  origTable = ref.contextualize.entryCreate.table;
              }
  
              var prefilledFks = [], keys = {};
              origTable.foreignKeys.all().forEach(function (fk) {
                  if (!foreignKeyCanBePrefilled(fk, ref.origFKR)) return;
                  prefilledFks.push(fk.name);
  
                  // add foreign key column data
                  fk.mapping._from.forEach(function (fromColumn, i) {
                      keys[fromColumn.name] = $rootScope.tuple.data[fk.mapping._to[i].name];
                  })
              });
  
              return {
                  rowname: $rootScope.recordDisplayname, // the displayed value in the form
                  origUrl: $rootScope.reference.uri, // used for reading the actual foreign key data
                  fkColumnNames: prefilledFks, // the foreignkey columns that should be prefileld
                  keys: keys // raw values of the foreign key columns
              };
          }
  
          vm.addRelatedRecord = function(tableModel) {
              var ref = tableModel.reference;
  
              event.preventDefault();
              event.stopPropagation();
              var cookie = getPrefillCookieObject(ref);
  
              if(ref.derivedAssociationReference){
                  recordAppUtils.pauseUpdateRecordPage();
                  recordCreate.addRelatedRecordFact(true, ref, 0, cookie, vm.editMode, vm.formContainer, vm.readyToSubmit, vm.recordsetLink, vm.submissionButtonDisabled, $rootScope.reference, [$rootScope.tuple], $rootScope.session, ConfigUtils.getContextJSON().queryParams, onSuccess(tableModel), onModalClose);
                  return;
              }
  
              // log the client action
              logService.logClientAction({
                  action: logService.getActionString(logService.logActions.ADD_INTEND, tableModel.logStackPath),
                  stack: tableModel.logStack
              }, tableModel.reference.defaultLogInfo);
  
              // 2. Generate a unique cookie name and set it to expire after 24hrs.
              var COOKIE_NAME = 'recordedit-' + MathUtils.getRandomInt(0, Number.MAX_SAFE_INTEGER);
              $cookies.putObject(COOKIE_NAME, cookie, {
                  expires: new Date(Date.now() + (60 * 60 * 24 * 1000))
              });
  
              // Generate a unique id for this request
              // append it to the URL
              var referrer_id = 'recordedit-' + MathUtils.getRandomInt(0, Number.MAX_SAFE_INTEGER);
              vm.addRecordRequests[referrer_id] = {
                  displayMode: tableModel.config.displayMode,
                  containerIndex: tableModel.config.containerIndex
              };
  
              // 3. Get appLink, append ?prefill=[COOKIE_NAME]&referrer=[referrer_id]
              var appLink = ref.unfilteredReference.contextualize.entryCreate.appLink;
              appLink = appLink + (appLink.indexOf("?") === -1? "?" : "&") +
                  'prefill=' + UriUtils.fixedEncodeURIComponent(COOKIE_NAME) +
                  '&invalidate=' + UriUtils.fixedEncodeURIComponent(referrer_id);
  
              // 4. Redirect to the url in a new tab
              $window.open(appLink, '_blank');
          };
  
          $scope.$on("edit-request", function(event, args) {
              vm.editRecordRequests[args.id] = {"displayMode": args.displayMode, "containerIndex": args.containerIndex, "finished": false};
          });
  
          $scope.$on('record-deleted', function (event, args) {
              var isInline = args.displayMode === recordsetDisplayModes.inline;
              recordAppUtils.updateRecordPage(true, "", [{
                  cause: isInline ? logService.reloadCauses.RELATED_INLINE_DELETE : logService.reloadCauses.RELATED_DELETE,
                  isInline: isInline,
                  index: args.containerIndex
              }]);
          });
  
          // When page gets focus, check cookie for completed requests
          // re-read the records for that table
          $window.onfocus = function() {
              onfocusEventCall(false);
          }
  
          var onfocusEventCall = function(changedContainerDetails) {
              if ($rootScope.loading !== false) return;
  
              var uc = logService.reloadCauses, id, cookie;
  
              // where in the page has been changed
              var changedContainers = [];
  
              var addToChangedContainers = function (details, causeDefs) {
                  var isInline = details.displayMode === recordsetDisplayModes.inline;
                  changedContainers.push({
                      cause: causeDefs[isInline ? 1 : 0],
                      isInline: isInline,
                      index: details.containerIndex
                  });
              };
  
              // modal create
              if (changedContainerDetails) {
                  addToChangedContainers(changedContainerDetails, [uc.RELATED_CREATE, uc.RELATED_INLINE_CREATE]);
              }
  
              //find the completed edit requests
              for (id in vm.editRecordRequests) {
                  if (vm.editRecordRequests[id].completed) {
                      addToChangedContainers(vm.editRecordRequests[id], [uc.RELATED_UPDATE, uc.RELATED_INLINE_UPDATE]);
                      delete vm.editRecordRequests[id];
                  }
              }
  
              // find the completed create requests
              for (id in vm.addRecordRequests) {
                  cookie = $cookies.getObject(id);
                  if (cookie) { // add request has been completed
                      console.log('Cookie found for the id=' + id);
                      addToChangedContainers(vm.addRecordRequests[id], [uc.RELATED_CREATE, uc.RELATED_INLINE_CREATE]);
  
                      // remove cookie and request
                      $cookies.remove(id);
                      delete vm.addRecordRequests[id];
                  } else {
                      console.log('Could not find cookie', cookie);
                  }
              }
  
              // if something has changed
              if (changedContainers.length > 0) {
                  recordAppUtils.updateRecordPage(true, "", changedContainers);
              }
          };
  
          // function called from form.controller.js to notify record that an entity was just updated
          window.updated = function(id) {
              vm.editRecordRequests[id].completed = true;
          }
  
          // to make sure we're adding the watcher just once
          var hasTheMainContainerPaddingWatcher = false;
  
          // watch for the display to be ready before setting the main container height
          var unbindDisplayReady = $scope.$watch(function() {
              return $rootScope.displayReady;
          }, function (newValue, oldValue) {
              if (newValue) {
                  $rootScope.recordSidePanOpen = (chaiseConfig.hideTableOfContents === true || $rootScope.reference.display.collapseToc === true) ? false : true;
                  $rootScope.hideColumnHeaders = $rootScope.reference.display.hideColumnHeaders;
  
                  // fix the size of main-container and sticky areas
                  UiUtils.attachContainerHeightSensors();
  
                  // NOTE this function is being called here because this is the
                  // first place that we can be sure that the record-container elements
                  // are available and visible. This is because of the ng-if that we have
                  // on the top-panel container. If we call this function in the watch below,
                  // it will throw an error.
  
                  // make sure the padding of main-container is correctly set
                  UiUtils.attachMainContainerPaddingSensor(document.querySelector(".record-container"));
  
                  // make sure footer is always at the bottom of the page
                  UiUtils.attachFooterResizeSensor(0);
  
                  unbindDisplayReady();
              }
          });
  
          /*** scroll to events ***/
          // scroll to top button
          $scope.scrollToTop = function (fromToc) {
              var action = (fromToc ? logService.logActions.TOC_SCROLL_TOP : logService.logActions.SCROLL_TOP);
              logService.logClientAction({
                  action: logService.getActionString(action),
                  stack: logService.getStackObject()
              }, $rootScope.reference.defaultLogInfo);
  
              mainContainerEl.scrollTo(0, 0, 500);
          };
  
          /**
           * Function for headings in table of contents to scroll to a section of record app
           * {String} sectionId - the displayname.value for table/column
           */
          vm.scrollToSection = function (sectionId) {
              var relatedObj = determineScrollElement(sectionId);
  
              logService.logClientAction({
                  action: logService.getActionString(logService.logActions.TOC_SCROLL_RELATED, relatedObj.rtm.tableModel.logStackPath),
                  stack: relatedObj.rtm.tableModel.logStack
              }, relatedObj.rtm.tableModel.reference.defaultLogInfo);
  
              scrollToElement(relatedObj.element);
          }
  
          /**
           * Function called when all related tables have been loaded
           * checks if a query parameter is present to scroll to a specific page section
           */
          function autoScroll () {
              // query param is url decoded by this function
              var queryParam = UriUtils.getQueryParam(initialHref, "scrollTo");
              // return if no query parameter, nothing to scroll to
              if (!queryParam) return;
  
              var elementObj = determineScrollElement(queryParam);
              // no element was returned, means there wasn't a matching displayname on the page
              if (!elementObj.element) return;
  
              scrollToElement(elementObj.element);
          }
  
          /**
           * function for finding the related table element to scroll to
           * @param {String} displayname -  should be the un-encoded displayname.value
           *      - this means it _could_ be a value generated from templating then run through the mkdn interpreter
           *
           * @returns {Object} that contains:
           *   `.element`     - element to scroll to
           *   `.rtm`         - related table model for RT to scroll to
           */
          function determineScrollElement (displayname) {
              var matchingRtm;
              // id enocde query param
              var htmlId = vm.makeSafeIdAttr(displayname);
              // "entity-" is used for record entity section
              var el = angular.element(document.getElementById("entity-" + htmlId));
  
              if (el[0]) {
                  // if in entity section, grab parent
                  el = el.parent();
  
                  matchingRtm = $rootScope.columnModels.filter(function (cm) {
                      return cm.column.displayname.value == displayname;
                  })[0];
              } else {
                  // "rt-heading-" is used for related table section
                  el = angular.element(document.getElementById("rt-heading-" + htmlId));
                  // return if no element after checking entity section and RT section
                  if (!el[0]) return;
  
                  matchingRtm = $rootScope.relatedTableModels.filter(function (rtm) {
                      return rtm.displayname.value == displayname;
                  })[0];
  
                  // matchingRtm should only ever be size 1, unless 2 different RTs have the same displayname
                  // make sure RT is open before scrolling
                  matchingRtm.open = true;
              }
  
              return {
                  element: el,
                  rtm: matchingRtm
              }
          }
  
          // given an element, scroll to the top of that element "slowly"
          function scrollToElement (element) {
              mainContainerEl.scrollToElementAnimated(element, 40).then(function () {
                  $timeout(function () {
                      element.addClass("row-focus");
                  }, 100);
                  $timeout(function () {
                      element.removeClass('row-focus');
                  }, 1600);
              }).catch(function (err) {
                  // the scroll promise might be rejected, but we should just fail silently
                  // we saw this happening when you double click on the element.
                  // in this case, the second promise will be rejected.
              });
          }
  
          mainContainerEl.on('scroll', $scope.$apply.bind($scope, function () {
              if (mainContainerEl.scrollTop() > 300) {
                $scope.showTopBtn = true;
              } else {
                $scope.showTopBtn = false;
              }
          }));
      }]);
  })();
  