<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  css: `
    ^ {
      position: relative;
    }

    ^scrollbar {
      box-sizing: border-box;
      width: 1px;
      background: rgba(0, 0, 0, 0);
    }

    ^scrollbarContainer {
      overflow: scroll;
      display: grid;
      grid-template-columns: 1px 1fr;
    }

    ^ th {
      position: -webkit-sticky;
      position: sticky;
      top: 0;
    }

    ^ table {
      table-layout: fixed;
      width: 1024px;
    }

    ^ td,
    ^ th {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
  `,

  constants: [
    {
      type: 'Integer',
      name: 'TABLE_HEAD_HEIGHT',
      value: 40
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'Int',
      name: 'limit',
      value: 18,
    },
    {
      class: 'Int',
      name: 'skip',
    },
    'columns',
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions'
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'selection',
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      documentation: `
        Set to true if users should be allowed to choose which columns to use.
      `,
      value: true
    },
    {
      class: 'Int',
      name: 'rowHeight',
      documentation: 'The height of one row of the table in px.',
      value: 48
    },
    {
      name: 'table_',
      documentation: `
        A reference to the table element we use in various calculations.
      `
    },
    {
      class: 'Int',
      name: 'accumulator',
      documentation: 'Used internally to mimic native scrolling speed.',
      adapt: function(_, v) {
        return v % this.rowHeight;
      }
    },
    {
      name: 'scrollbarContainer_',
      documentation: `
        A reference to the scrollbar's container element so we can update the
        height after the view has loaded.
      `
    },
    {
      type: 'String',
      name: 'scrollHeight',
      expression: function(daoCount, limit, rowHeight) {
        this.refresh();
        return rowHeight * Math.max(0, daoCount - limit) + this.TABLE_HEAD_HEIGHT + 'px';
      }
    },
    {
      type: 'Int',
      name: 'lastScrollTop_',
      value: 0
    },
    {
      type: 'Int',
      name: 'pageSize',
      value: 30,
      documentation: 'The number of items in each "page". There are three pages.'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage1DAO_',
      expression: function(data, pageSize) {
        return data && data.limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage2DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage3DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize * 2).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page1DAO_',
      factory: function() {
        return this.initialPage1DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page2DAO_',
      factory: function() {
        return this.initialPage2DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page3DAO_',
      factory: function() {
        return this.initialPage3DAO_;
      }
    },
    {
      type: 'Int',
      name: 'currentLowerBound',
      value: 0
    },
    {
      type: 'Int',
      name: 'currentUpperBound',
      expression: function(pageSize) {
        return pageSize * 3;
      }
    },
    {
      name: 'spacer_',
      documentation: `
        A named reference to an element so we can update its height
        periodically. Used to put empty space between the top of the container
        and the tables so we create the illusion of the table always being
        visible.
      `
    },
    'topBufferTable_',
    'visibleTable_',
    'bottomBufferTable_',
    {
      type: 'Int',
      name: 'tablesRemoved_',
      value: 0,
      documentation: `
        The number of tables that have been removed due to scrolling down.
      `
    },
    {
      type: 'String',
      name: 'spacerHeight_',
      expression: function(tablesRemoved_, pageSize, rowHeight) {
        return tablesRemoved_ * pageSize * rowHeight + 'px';
      }
    }
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({ fn: this.onDAOUpdate })));
      this.onDAOUpdate();
    },

    function initE() {
      this.
        addClass(this.myClass()).
        start('div', undefined, this.scrollbarContainer_$).
          addClass(this.myClass('scrollbarContainer')).
          on('scroll', this.onScroll).
          start().
            addClass(this.myClass('scrollbar')).
            style({ height: this.scrollHeight$ }).
          end().
          start().
            start('div', undefined, this.spacer_$).
              style({ height: this.spacerHeight_$ }).
            end().
            start(this.TableView, {
              data: this.page1DAO_$proxy,
              columns: this.columns,
              contextMenuActions: this.contextMenuActions,
              selection$: this.selection$,
              editColumnsEnabled: this.editColumnsEnabled
            }, this.table_$).
              addClass(this.myClass('table')).
            end().
          end().
        end();

      this.onDetach(this.onload.sub(this.addTbodies));

      this.onDetach(this.onload.sub(this.updateTableHeight));
      window.addEventListener('resize', this.updateTableHeight);
      this.onDetach(() => {
        window.removeEventListener('resize', this.updateTableHeight);
      });
    },
    {
      name: 'refresh',
      code: function() {
        this.lastScrollTop_ = 0;
        this.skip = 0;
        this.currentLowerBound = 0;
        this.currentUpperBound = this.pageSize * 3;
        this.tablesRemoved_ = 0;
        this.page1DAO_ = this.initialPage1DAO_;
        this.page2DAO_ = this.initialPage2DAO_;
        this.page3DAO_ = this.initialPage3DAO_;
      }
    },
    {
      name: 'scrollDownOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentUpperBound > this.daoCount ) return;

        this.currentUpperBound += this.pageSize;
        this.currentLowerBound += this.pageSize;

        // Remove the top buffer table.
        this.topBufferTable_.remove();
        this.tablesRemoved_ += 1;

        // Update the table references.
        this.topBufferTable_ = this.visibleTable_;
        this.visibleTable_   = this.bottomBufferTable_;

        // Add a new bottom buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentUpperBound - this.pageSize).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.add(rows);
        this.bottomBufferTable_ = this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .pop();
      }
    },
    {
      name: 'scrollUpOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentLowerBound <= 0 ) return;

        this.currentLowerBound -= this.pageSize;
        this.currentUpperBound -= this.pageSize;

        // Remove the bottom buffer table.
        this.bottomBufferTable_.remove();
        this.tablesRemoved_ -= 1;

        // Update the table references.
        this.bottomBufferTable_ = this.visibleTable_;
        this.visibleTable_      = this.topBufferTable_;

        // Add a new top buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentLowerBound).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.insertBefore(this.table_.slotE_(rows), this.visibleTable_);
        this.topBufferTable_ = this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .shift();
      }
    }
  ],

  listeners: [
    {
      name: 'onScroll',
      code: function(e) {
        var deltaY = e.target.scrollTop - this.lastScrollTop_;
        var negative = deltaY < 0;
        var rows = Math.floor(Math.abs(this.accumulator + deltaY) / this.rowHeight);
        this.accumulator += deltaY;
        var oldSkip = this.skip;
        this.skip = Math.min(this.daoCount - this.limit, Math.max(0, this.skip + (negative ? -rows : rows)));
        this.lastScrollTop_ = e.target.scrollTop;

        var uBound = () => this.currentUpperBound - this.pageSize;
        var lBound = () => this.currentLowerBound + this.pageSize;

        if ( oldSkip <= uBound() && this.skip > uBound() ) {
          // The user scrolled down past a page boundary.
          while (
            this.skip > uBound() &&
            this.currentUpperBound < this.daoCount
          ) {
            this.scrollDownOnePage();
          }
        } else if ( oldSkip > lBound() && this.skip <= lBound() ) {
          // The user scrolled up past a page boundary.
          while ( this.skip <= lBound() && this.currentLowerBound > 0 ) {
            this.scrollUpOnePage();
          }
        }
      }
    },
    {
      // TODO: Avoid onDAOUpdate approaches.
      name: 'onDAOUpdate',
      isFramed: true,
      code: function() {
        var self = this;
        this.data$proxy.select(this.Count.create()).then(function(s) {
          self.daoCount = s.value;
          self.refresh();
        });
      }
    },
    {
      name: 'updateTableHeight',
      code: function() {
        // Find the distance from the top of the table to the top of the screen.
        var distanceFromTop = this.scrollbarContainer_.el().getBoundingClientRect().y;

        // Calculate the remaining space we have to make use of.
        var remainingSpace = window.innerHeight - distanceFromTop;

        // TODO: Do we want to do this?
        // Leave space for the footer.
        remainingSpace -= 44;

        this.scrollbarContainer_.style({ height: `${remainingSpace}px` });
      }
    },
    {
      name: 'addTbodies',
      code: function() {
        this.table_.add(this.table_.rowsFrom(this.page2DAO_$proxy));
        this.table_.add(this.table_.rowsFrom(this.page3DAO_$proxy));
        var tbodies = this.table_.childNodes.filter((x) => x.nodeName === 'TBODY');
        this.topBufferTable_ = tbodies[0];
        this.visibleTable_ = tbodies[1];
        this.bottomBufferTable_ = tbodies[2];
      }
    }
  ]
});
<commit_msg>Fix bugs related to the ScrollTableView<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  css: `
    ^ {
      position: relative;
    }

    ^scrollbar {
      box-sizing: border-box;
      width: 1px;
      background: rgba(0, 0, 0, 0);
    }

    ^scrollbarContainer {
      overflow: scroll;
      display: grid;
      grid-template-columns: 1px 1fr;
    }

    ^ th {
      position: -webkit-sticky;
      position: sticky;
      top: 0;
    }

    ^ table {
      table-layout: fixed;
      width: 1024px;
    }

    ^ td,
    ^ th {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
  `,

  constants: [
    {
      type: 'Integer',
      name: 'TABLE_HEAD_HEIGHT',
      value: 40
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'Int',
      name: 'limit',
      value: 18,
    },
    {
      class: 'Int',
      name: 'skip',
    },
    'columns',
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions'
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'selection',
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      documentation: `
        Set to true if users should be allowed to choose which columns to use.
      `,
      value: true
    },
    {
      class: 'Int',
      name: 'rowHeight',
      documentation: 'The height of one row of the table in px.',
      value: 48
    },
    {
      name: 'table_',
      documentation: `
        A reference to the table element we use in various calculations.
      `
    },
    {
      class: 'Int',
      name: 'accumulator',
      documentation: 'Used internally to mimic native scrolling speed.',
      adapt: function(_, v) {
        return v % this.rowHeight;
      }
    },
    {
      name: 'scrollbarContainer_',
      documentation: `
        A reference to the scrollbar's container element so we can update the
        height after the view has loaded.
      `
    },
    {
      type: 'String',
      name: 'scrollHeight',
      expression: function(daoCount, rowHeight) {
        return rowHeight * daoCount + this.TABLE_HEAD_HEIGHT + 'px';
      }
    },
    {
      type: 'Int',
      name: 'lastScrollTop_',
      value: 0
    },
    {
      type: 'Int',
      name: 'pageSize',
      value: 30,
      documentation: 'The number of items in each "page". There are three pages.'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage1DAO_',
      expression: function(data, pageSize) {
        return data && data.limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage2DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage3DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize * 2).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page1DAO_',
      factory: function() {
        return this.initialPage1DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page2DAO_',
      factory: function() {
        return this.initialPage2DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page3DAO_',
      factory: function() {
        return this.initialPage3DAO_;
      }
    },
    {
      type: 'Int',
      name: 'currentLowerBound',
      value: 0
    },
    {
      type: 'Int',
      name: 'currentUpperBound',
      expression: function(pageSize) {
        return pageSize * 3;
      }
    },
    {
      name: 'spacer_',
      documentation: `
        A named reference to an element so we can update its height
        periodically. Used to put empty space between the top of the container
        and the tables so we create the illusion of the table always being
        visible.
      `
    },
    'topBufferTable_',
    'visibleTable_',
    'bottomBufferTable_',
    {
      type: 'Int',
      name: 'tablesRemoved_',
      value: 0,
      documentation: `
        The number of tables that have been removed due to scrolling down.
      `
    },
    {
      type: 'String',
      name: 'spacerHeight_',
      expression: function(tablesRemoved_, pageSize, rowHeight) {
        return tablesRemoved_ * pageSize * rowHeight + 'px';
      }
    }
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({ fn: this.onDAOUpdate })));
      this.onDAOUpdate();
    },

    function initE() {
      this.
        addClass(this.myClass()).
        start('div', undefined, this.scrollbarContainer_$).
          addClass(this.myClass('scrollbarContainer')).
          on('scroll', this.onScroll).
          start().
            addClass(this.myClass('scrollbar')).
            style({ height: this.scrollHeight$ }).
          end().
          start().
            start('div', undefined, this.spacer_$).
              style({ height: this.spacerHeight_$ }).
            end().
            start(this.TableView, {
              data: this.page1DAO_$proxy,
              columns: this.columns,
              contextMenuActions: this.contextMenuActions,
              selection$: this.selection$,
              editColumnsEnabled: this.editColumnsEnabled
            }, this.table_$).
              addClass(this.myClass('table')).
            end().
          end().
        end();

      this.onDetach(this.onload.sub(this.addTbodies));

      this.onDetach(this.onload.sub(this.updateTableHeight));
      window.addEventListener('resize', this.updateTableHeight);
      this.onDetach(() => {
        window.removeEventListener('resize', this.updateTableHeight);
      });
    },
    {
      name: 'refresh',
      code: function() {
        this.lastScrollTop_ = 0;
        this.skip = 0;
        this.currentLowerBound = 0;
        this.currentUpperBound = this.pageSize * 3;
        this.tablesRemoved_ = 0;
        this.page1DAO_ = this.initialPage1DAO_;
        this.page2DAO_ = this.initialPage2DAO_;
        this.page3DAO_ = this.initialPage3DAO_;
        this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .forEach((x) => x.remove());
        this.table_.add(this.table_.rowsFrom(this.page1DAO_$proxy));
        this.addTbodies();
        if ( this.scrollbarContainer_ && this.scrollbarContainer_.el() ) {
          this.scrollbarContainer_.el().scrollTop = 0;
        }
      }
    },
    {
      name: 'scrollDownOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentUpperBound > this.daoCount ) return;

        this.currentUpperBound += this.pageSize;
        this.currentLowerBound += this.pageSize;

        // Remove the top buffer table.
        this.topBufferTable_.remove();

        // Update the table references.
        this.topBufferTable_ = this.visibleTable_;
        this.visibleTable_   = this.bottomBufferTable_;

        // Add a new bottom buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this.tablesRemoved_ += 1;
        this[daoName] = this.data.skip(this.currentUpperBound - this.pageSize).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.add(rows);
        var x = this.table_.childNodes.filter((x) => x.nodeName === 'TBODY');
        this.bottomBufferTable_ = x[x.length - 1];
      }
    },
    {
      name: 'scrollUpOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentLowerBound <= 0 ) return;

        this.currentLowerBound -= this.pageSize;
        this.currentUpperBound -= this.pageSize;

        // Remove the bottom buffer table.
        this.bottomBufferTable_.remove();
        this.tablesRemoved_ -= 1;

        // Update the table references.
        this.bottomBufferTable_ = this.visibleTable_;
        this.visibleTable_      = this.topBufferTable_;

        // Add a new top buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentLowerBound).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.insertBefore(this.table_.slotE_(rows), this.visibleTable_);
        var x = this.table_.childNodes.filter((x) => x.nodeName === 'TBODY');
        this.topBufferTable_ = x[0];
      }
    }
  ],

  listeners: [
    {
      name: 'onScroll',
      code: function(e) {
        var deltaY = e.target.scrollTop - this.lastScrollTop_;
        var negative = deltaY < 0;
        var rows = Math.floor(Math.abs(this.accumulator + deltaY) / this.rowHeight);
        this.accumulator += deltaY;
        var oldSkip = this.skip;
        this.skip = Math.min(this.daoCount - this.limit, Math.max(0, this.skip + (negative ? -rows : rows)));
        this.lastScrollTop_ = e.target.scrollTop;

        var uBound = () => this.currentUpperBound - this.pageSize;
        var lBound = () => this.currentLowerBound + this.pageSize;

        if ( oldSkip <= uBound() && this.skip > uBound() ) {
          // The user scrolled down past a page boundary.
          while (
            this.skip > uBound() &&
            this.currentUpperBound < this.daoCount
          ) {
            this.scrollDownOnePage();
          }
        } else if ( oldSkip > lBound() && this.skip <= lBound() ) {
          // The user scrolled up past a page boundary.
          while ( this.skip <= lBound() && this.currentLowerBound > 0 ) {
            this.scrollUpOnePage();
          }
        }
      }
    },
    {
      // TODO: Avoid onDAOUpdate approaches.
      name: 'onDAOUpdate',
      isFramed: true,
      code: function() {
        var self = this;
        this.data$proxy.select(this.Count.create()).then(function(s) {
          self.daoCount = s.value;
          self.refresh();
        });
      }
    },
    {
      name: 'updateTableHeight',
      code: function() {
        // Find the distance from the top of the table to the top of the screen.
        var distanceFromTop = this.scrollbarContainer_.el().getBoundingClientRect().y;

        // Calculate the remaining space we have to make use of.
        var remainingSpace = window.innerHeight - distanceFromTop;

        // TODO: Do we want to do this?
        // Leave space for the footer.
        remainingSpace -= 44;

        this.scrollbarContainer_.style({ height: `${remainingSpace}px` });
      }
    },
    {
      name: 'addTbodies',
      code: function() {
        this.table_.add(this.table_.rowsFrom(this.page2DAO_$proxy));
        this.table_.add(this.table_.rowsFrom(this.page3DAO_$proxy));
        var tbodies = this.table_.childNodes.filter((x) => x.nodeName === 'TBODY');
        this.topBufferTable_ = tbodies[0];
        this.visibleTable_ = tbodies[1];
        this.bottomBufferTable_ = tbodies[2];
      }
    }
  ]
});
<|endoftext|>


<commit_before>var app = app || {};

(function() {
	app.androidLayout = app.androidLayout || {};

	// hash of error names and their full text
	app.androidLayout.errorList = {
		'doubleOpenBracket': 'Did you forget to close the tag that you opened on line $lineNumInitialOpening?',
		'doubleCloseBracket': 'There are two >\'s in a row, on lines $lineNumInitialClosing and $lineNumSecondClosing. Double-check your tags to make sure you\'ve formed them properly. Remember, every tag or closing tag should be formed with < at the beginning, and a > at the end.',
		'doubleCloseBracketSameLine': 'There are two >\'s in a row on line $lineNum. Did you accidentally add two >\'s?',
		'unevenQuotesPerLine': 'Line $lineNum has an uneven number of quotes. Did you forget to open or close a quote?',
		'invalidOpeningTag': 'Line $lineNum: Tag `$tag` is not a supported opening tag.',
		'invalidClosingTag': 'Line $lineNum: Tag `$tag` is not a supported closing tag.',
		'unclosedSelfClosingTag': 'The tag $tag should be self-closing. This means it should end with /> .',
		'invalidAttribute': 'The attribute $attribute (Line $lineNum) is not supported here.',
		'invalidAttributeValue': 'The attribute $attribute does not support the value $attributeValue (Line $lineNum)'
	};

	app.androidLayout.validTags = ['FrameLayout','LinearLayout','RelativeLayout','TextView','ImageView','Button', 'View'];
	app.androidLayout.selfClosingTags = ['TextView','ImageView','Button'];

	app.androidLayout.validAttributes = [
		{
			name: 'android:id',
			pattern: /^\@\+id\/[a-z_]+$/  // is the + necessary?
		},
		{
			name: 'android:text'
		},

		{
			name: 'android:background',
			pattern: /^#(?:[0-9a-fA-F]{3}){1,2}$|\@android:color\/[a-z_]+$/
		},
		{
			name: 'android:textColor',
			pattern: /^#(?:[0-9a-fA-F]{3}){1,2}$|\@android:color\/[a-z_]+$/
		},

		{
			name: 'android:layout_width',
			pattern: /(^match_parent$|^wrap_content$|^\d*dp$)/
		},
		{
			name: 'android:layout_height',
			pattern: /(^match_parent$|^wrap_content$|^\d*dp$)/
		},

		{
			name: 'android:padding',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingTop',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingRight',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingBottom',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingLeft',
			pattern: /(^\d*dp$)/
		},

		{
			name: 'android:layout_alignParentTop',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_alignParentBottom',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_alignParentRight',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_alignParentLeft',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_above',
			pattern: /^\@id\/[a-z_]+$/
		},
		{
			name: 'android:layout_below',
			pattern: /^\@id\/[a-z_]+$/
		},
		{
			name: 'android:layout_toRightOf',
			pattern: /^\@id\/[a-z_]+$/
		},
		{
			name: 'android:layout_toLeftOf',
			pattern: /^\@id\/[a-z_]+$/
		},

		{
			name: 'android:gravity',
			pattern: /^(?:top|bottom|left|right|center_vertical|center_horizontal|center)(?:\|(top|bottom|left|right|center_vertical|center_horizontal|center))?$/
		},

		{
			name: 'android:textSize',
			pattern: /(^\d*sp$)/
		},
		{
			name: 'android:fontFamily',
			pattern: /^sans-serif(-light|-condensed|-thin|-medium|-black)?$/
		},

		{
			name: 'android:src',
			pattern: /^\@drawable\/[a-zA-Z_]$/
		},
	];

	// hash of valid fonts, and their web equivalents
	app.androidLayout.fontFamilyList = {
		'sans-serif': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '400'
		},
		'sans-serif-light': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '300'
		},
		'sans-serif-condensed': {
			'fontFamily': '"Roboto Condensed", sans-serif',
			'fontWeight': '100'
		},
		'sans-serif-thin': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '100'
		},
		'sans-serif-medium': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '500'
		},
		'sans-serif-black': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '900'
		}
	};

	// hash of color names and their rgba values
	app.androidLayout.COLOR = {
		"screen_background_light": "rgba(255,255,255,1)",
		"screen_background_dark": "rgba(0,0,0,1)",
		"status_bar_closed_default_background": "rgba(0,0,0,1)",
		"status_bar_opened_default_background": "rgba(0,0,0,1)",
		"search_bar_default_color": "rgba(0,0,0,1)",
		"safe_mode_background": "rgba(0,0,0,0.38)",
		"screen_background_dark_transparent": "rgba(0,0,0,0.5)",
		"screen_background_light_transparent": "rgba(255,255,255,0.5)",
		"safe_mode_text": "rgba(255,255,255,0.5)",
		"white": "rgba(255,255,255,1)",
		"black": "rgba(0,0,0,1)",
		"transparent": "rgba(0,0,0,0)",
		"background_dark": "rgba(0,0,0,1)",
		"background_light": "rgba(255,255,255,1)",
		"bright_foreground_dark": "rgba(0,0,0,1)",
		"bright_foreground_light": "rgba(255,255,255,1)",
		"bright_foreground_dark_disabled": "rgba(255,255,255,0.5)",
		"bright_foreground_light_disabled": "rgba(0,0,0,0.5)",
		"bright_foreground_dark_inverse": "rgba(255,255,255,1)",
		"bright_foreground_light_inverse": "rgba(0,0,0,1)",
		"dim_foreground_dark": "rgba(190,190,190,1)",
		"dim_foreground_dark_disabled": "rgba(190,190,190,0.5)",
		"dim_foreground_dark_inverse": "rgba(50,50,50,1)",
		"dim_foreground_dark_inverse_disabled": "rgba(50,50,50,0.5)",
		"hint_foreground_dark": "rgba(128,128,128,1)",
		"dim_foreground_light": "rgba(50,50,50,1)",
		"dim_foreground_light_disabled": "rgba(50,50,50,0.5)",
		"dim_foreground_light_inverse": "rgba(190,190,190,1)",
		"dim_foreground_light_inverse_disabled": "rgba(190,190,190,0.5)",
		"hint_foreground_light": "rgba(128,128,128,1)",
		"highlighted_text_dark": "rgba(131,204,57,0.6)",
		"highlighted_text_light": "rgba(131,204,57,0.6)",
		"link_text_dark": "rgba(92,92,255,1)",
		"link_text_light": "rgba(0,0,238,1)",
		"suggestion_highlight_text": "rgba(23,123,189,1)",
		"input_method_fullscreen_background": "rgba(249,249,249,1)",
		"input_method_fullscreen_background_holo": "rgba(0,0,0,0.5)",
		"selected_day_background": "rgba(0,146,244,1)",
		"lighter_gray": "rgba(221,221,221,1)",
		"darker_gray": "rgba(170,170,170,1)",
		"perms_dangerous_grp_color": "rgba(51,181,229,1)",
		"perms_dangerous_perm_color": "rgba(51,181,229,1)",
		"shadow": "rgba(34,34,34,0.8)",
		"search_url_text_normal": "rgba(127,168,127,1)",
		"search_url_text_selected": "rgba(0,0,0,1)",
		"search_url_text_pressed": "rgba(0,0,0,1)",
		"search_widget_corpus_item_background": "rgba(221,221,221,1)",
		"sliding_tab_text_color_active": "rgba(0,0,0,1)",
		"sliding_tab_text_color_shadow": "rgba(0,0,0,1)",
		"keyguard_text_color_normal": "rgba(255,255,255,1)",
		"keyguard_text_color_unlock": "rgba(167,216,76,1)",
		"keyguard_text_color_soundoff": "rgba(255,255,255,1)",
		"keyguard_text_color_soundon": "rgba(230,147,16,1)",
		"keyguard_text_color_decline": "rgba(254,10,90,1)",
		"lockscreen_clock_background": "rgba(255,255,255,1)",
		"lockscreen_clock_foreground": "rgba(255,255,255,1)",
		"lockscreen_clock_am_pm": "rgba(255,255,255,1)",
		"lockscreen_owner_info": "rgba(154,154,154,1)",
		"facelock_color_background": "rgba(0,0,0,1)",
		"screen_background_holo_light": "rgba(243,243,243,1)",
		"screen_background_holo_dark": "rgba(0,0,0,1)",
		"background_holo_dark": "rgba(0,0,0,1)",
		"background_holo_light": "rgba(243,243,243,1)",
		"bright_foreground_holo_dark": "rgba(243,243,243,1)",
		"bright_foreground_holo_light": "rgba(0,0,0,1)",
		"bright_foreground_disabled_holo_dark": "rgba(76,76,76,1)",
		"bright_foreground_disabled_holo_light": "rgba(178,178,178,1)",
		"bright_foreground_inverse_holo_dark": "rgba(0,0,0,1)",
		"bright_foreground_inverse_holo_light": "rgba(0,0,0,1)",
		"dim_foreground_holo_dark": "rgba(190,190,190,1)",
		"dim_foreground_disabled_holo_dark": "rgba(190,190,190,0.5)",
		"dim_foreground_inverse_holo_dark": "rgba(50,50,50,1)",
		"dim_foreground_inverse_disabled_holo_dark": "rgba(50,50,50,0.5)",
		"hint_foreground_holo_dark": "rgba(128,128,128,1)",
		"dim_foreground_holo_light": "rgba(50,50,50,1)",
		"dim_foreground_disabled_holo_light": "rgba(50,50,50,0.5)",
		"dim_foreground_inverse_holo_light": "rgba(190,190,190,1)",
		"dim_foreground_inverse_disabled_holo_light": "rgba(190,190,190,0.5)",
		"hint_foreground_holo_light": "rgba(128,128,128,1)",
		"highlighted_text_holo_dark": "rgba(51,181,229,0.4)",
		"highlighted_text_holo_light": "rgba(51,181,229,0.4)",
		"link_text_holo_dark": "rgba(92,92,255,1)",
		"link_text_holo_light": "rgba(0,0,238,1)",
		"group_button_dialog_pressed_holo_dark": "rgba(197,193,255,0.27)",
		"group_button_dialog_focused_holo_dark": "rgba(153,204,0,0.15)",
		"group_button_dialog_pressed_holo_light": "rgba(255,255,255,1)",
		"group_button_dialog_focused_holo_light": "rgba(153,204,0,0.27)",
		"legacy_pressed_highlight": "rgba(254,170,12,1)",
		"legacy_selected_highlight": "rgba(241,122,10,1)",
		"legacy_long_pressed_highlight": "rgba(255,255,255,1)",
		"holo_blue_light": "rgba(51,181,229,1)",
		"holo_green_light": "rgba(153,204,0,1)",
		"holo_red_light": "rgba(255,68,68,1)",
		"holo_blue_dark": "rgba(0,153,204,1)",
		"holo_green_dark": "rgba(102,153,0,1)",
		"holo_red_dark": "rgba(204,0,0,1)",
		"holo_purple": "rgba(170,102,204,1)",
		"holo_orange_light": "rgba(255,187,51,1)",
		"holo_orange_dark": "rgba(255,136,0,1)",
		"holo_blue_bright": "rgba(0,221,255,1)"
	};
})();<commit_msg>added textStyle attribute to supported list<commit_after>var app = app || {};

(function() {
	app.androidLayout = app.androidLayout || {};

	// hash of error names and their full text
	app.androidLayout.errorList = {
		'doubleOpenBracket': 'Did you forget to close the tag that you opened on line $lineNumInitialOpening?',
		'doubleCloseBracket': 'There are two >\'s in a row, on lines $lineNumInitialClosing and $lineNumSecondClosing. Double-check your tags to make sure you\'ve formed them properly. Remember, every tag or closing tag should be formed with < at the beginning, and a > at the end.',
		'doubleCloseBracketSameLine': 'There are two >\'s in a row on line $lineNum. Did you accidentally add two >\'s?',
		'unevenQuotesPerLine': 'Line $lineNum has an uneven number of quotes. Did you forget to open or close a quote?',
		'invalidOpeningTag': 'Line $lineNum: Tag `$tag` is not a supported opening tag.',
		'invalidClosingTag': 'Line $lineNum: Tag `$tag` is not a supported closing tag.',
		'unclosedSelfClosingTag': 'The tag $tag should be self-closing. This means it should end with /> .',
		'invalidAttribute': 'The attribute $attribute (Line $lineNum) is not supported here.',
		'invalidAttributeValue': 'The attribute $attribute does not support the value $attributeValue (Line $lineNum)'
	};

	app.androidLayout.validTags = ['FrameLayout','LinearLayout','RelativeLayout','TextView','ImageView','Button', 'View'];
	app.androidLayout.selfClosingTags = ['TextView','ImageView','Button'];

	app.androidLayout.validAttributes = [
		{
			name: 'android:id',
			pattern: /^\@\+id\/[a-z_]+$/  // is the + necessary?
		},
		{
			name: 'android:text'
		},

		{
			name: 'android:background',
			pattern: /^#(?:[0-9a-fA-F]{3}){1,2}$|\@android:color\/[a-z_]+$/
		},
		{
			name: 'android:textColor',
			pattern: /^#(?:[0-9a-fA-F]{3}){1,2}$|\@android:color\/[a-z_]+$/
		},

		{
			name: 'android:layout_width',
			pattern: /(^match_parent$|^wrap_content$|^\d*dp$)/
		},
		{
			name: 'android:layout_height',
			pattern: /(^match_parent$|^wrap_content$|^\d*dp$)/
		},

		{
			name: 'android:padding',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingTop',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingRight',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingBottom',
			pattern: /(^\d*dp$)/
		},
		{
			name: 'android:paddingLeft',
			pattern: /(^\d*dp$)/
		},

		{
			name: 'android:layout_alignParentTop',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_alignParentBottom',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_alignParentRight',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_alignParentLeft',
			pattern: /^(true|false)$/
		},
		{
			name: 'android:layout_above',
			pattern: /^\@id\/[a-z_]+$/
		},
		{
			name: 'android:layout_below',
			pattern: /^\@id\/[a-z_]+$/
		},
		{
			name: 'android:layout_toRightOf',
			pattern: /^\@id\/[a-z_]+$/
		},
		{
			name: 'android:layout_toLeftOf',
			pattern: /^\@id\/[a-z_]+$/
		},

		{
			name: 'android:gravity',
			pattern: /^(?:top|bottom|left|right|center_vertical|center_horizontal|center)(?:\|(top|bottom|left|right|center_vertical|center_horizontal|center))?$/
		},

		{
			name: 'android:textSize',
			pattern: /(^\d*sp$)/
		},
		{
			name: 'android:textStyle',
			pattern: /^(?:bold\|italic|italic\|bold|italic|bold|normal)$/
		},
		{
			name: 'android:fontFamily',
			pattern: /^sans-serif(-light|-condensed|-thin|-medium|-black)?$/
		},

		{
			name: 'android:src',
			pattern: /^\@drawable\/[a-zA-Z_]$/
		},
	];

	// hash of valid fonts, and their web equivalents
	app.androidLayout.fontFamilyList = {
		'sans-serif': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '400'
		},
		'sans-serif-light': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '300'
		},
		'sans-serif-condensed': {
			'fontFamily': '"Roboto Condensed", sans-serif',
			'fontWeight': '100'
		},
		'sans-serif-thin': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '100'
		},
		'sans-serif-medium': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '500'
		},
		'sans-serif-black': {
			'fontFamily': 'Roboto, sans-serif',
			'fontWeight': '900'
		}
	};

	// hash of color names and their rgba values
	app.androidLayout.COLOR = {
		"screen_background_light": "rgba(255,255,255,1)",
		"screen_background_dark": "rgba(0,0,0,1)",
		"status_bar_closed_default_background": "rgba(0,0,0,1)",
		"status_bar_opened_default_background": "rgba(0,0,0,1)",
		"search_bar_default_color": "rgba(0,0,0,1)",
		"safe_mode_background": "rgba(0,0,0,0.38)",
		"screen_background_dark_transparent": "rgba(0,0,0,0.5)",
		"screen_background_light_transparent": "rgba(255,255,255,0.5)",
		"safe_mode_text": "rgba(255,255,255,0.5)",
		"white": "rgba(255,255,255,1)",
		"black": "rgba(0,0,0,1)",
		"transparent": "rgba(0,0,0,0)",
		"background_dark": "rgba(0,0,0,1)",
		"background_light": "rgba(255,255,255,1)",
		"bright_foreground_dark": "rgba(0,0,0,1)",
		"bright_foreground_light": "rgba(255,255,255,1)",
		"bright_foreground_dark_disabled": "rgba(255,255,255,0.5)",
		"bright_foreground_light_disabled": "rgba(0,0,0,0.5)",
		"bright_foreground_dark_inverse": "rgba(255,255,255,1)",
		"bright_foreground_light_inverse": "rgba(0,0,0,1)",
		"dim_foreground_dark": "rgba(190,190,190,1)",
		"dim_foreground_dark_disabled": "rgba(190,190,190,0.5)",
		"dim_foreground_dark_inverse": "rgba(50,50,50,1)",
		"dim_foreground_dark_inverse_disabled": "rgba(50,50,50,0.5)",
		"hint_foreground_dark": "rgba(128,128,128,1)",
		"dim_foreground_light": "rgba(50,50,50,1)",
		"dim_foreground_light_disabled": "rgba(50,50,50,0.5)",
		"dim_foreground_light_inverse": "rgba(190,190,190,1)",
		"dim_foreground_light_inverse_disabled": "rgba(190,190,190,0.5)",
		"hint_foreground_light": "rgba(128,128,128,1)",
		"highlighted_text_dark": "rgba(131,204,57,0.6)",
		"highlighted_text_light": "rgba(131,204,57,0.6)",
		"link_text_dark": "rgba(92,92,255,1)",
		"link_text_light": "rgba(0,0,238,1)",
		"suggestion_highlight_text": "rgba(23,123,189,1)",
		"input_method_fullscreen_background": "rgba(249,249,249,1)",
		"input_method_fullscreen_background_holo": "rgba(0,0,0,0.5)",
		"selected_day_background": "rgba(0,146,244,1)",
		"lighter_gray": "rgba(221,221,221,1)",
		"darker_gray": "rgba(170,170,170,1)",
		"perms_dangerous_grp_color": "rgba(51,181,229,1)",
		"perms_dangerous_perm_color": "rgba(51,181,229,1)",
		"shadow": "rgba(34,34,34,0.8)",
		"search_url_text_normal": "rgba(127,168,127,1)",
		"search_url_text_selected": "rgba(0,0,0,1)",
		"search_url_text_pressed": "rgba(0,0,0,1)",
		"search_widget_corpus_item_background": "rgba(221,221,221,1)",
		"sliding_tab_text_color_active": "rgba(0,0,0,1)",
		"sliding_tab_text_color_shadow": "rgba(0,0,0,1)",
		"keyguard_text_color_normal": "rgba(255,255,255,1)",
		"keyguard_text_color_unlock": "rgba(167,216,76,1)",
		"keyguard_text_color_soundoff": "rgba(255,255,255,1)",
		"keyguard_text_color_soundon": "rgba(230,147,16,1)",
		"keyguard_text_color_decline": "rgba(254,10,90,1)",
		"lockscreen_clock_background": "rgba(255,255,255,1)",
		"lockscreen_clock_foreground": "rgba(255,255,255,1)",
		"lockscreen_clock_am_pm": "rgba(255,255,255,1)",
		"lockscreen_owner_info": "rgba(154,154,154,1)",
		"facelock_color_background": "rgba(0,0,0,1)",
		"screen_background_holo_light": "rgba(243,243,243,1)",
		"screen_background_holo_dark": "rgba(0,0,0,1)",
		"background_holo_dark": "rgba(0,0,0,1)",
		"background_holo_light": "rgba(243,243,243,1)",
		"bright_foreground_holo_dark": "rgba(243,243,243,1)",
		"bright_foreground_holo_light": "rgba(0,0,0,1)",
		"bright_foreground_disabled_holo_dark": "rgba(76,76,76,1)",
		"bright_foreground_disabled_holo_light": "rgba(178,178,178,1)",
		"bright_foreground_inverse_holo_dark": "rgba(0,0,0,1)",
		"bright_foreground_inverse_holo_light": "rgba(0,0,0,1)",
		"dim_foreground_holo_dark": "rgba(190,190,190,1)",
		"dim_foreground_disabled_holo_dark": "rgba(190,190,190,0.5)",
		"dim_foreground_inverse_holo_dark": "rgba(50,50,50,1)",
		"dim_foreground_inverse_disabled_holo_dark": "rgba(50,50,50,0.5)",
		"hint_foreground_holo_dark": "rgba(128,128,128,1)",
		"dim_foreground_holo_light": "rgba(50,50,50,1)",
		"dim_foreground_disabled_holo_light": "rgba(50,50,50,0.5)",
		"dim_foreground_inverse_holo_light": "rgba(190,190,190,1)",
		"dim_foreground_inverse_disabled_holo_light": "rgba(190,190,190,0.5)",
		"hint_foreground_holo_light": "rgba(128,128,128,1)",
		"highlighted_text_holo_dark": "rgba(51,181,229,0.4)",
		"highlighted_text_holo_light": "rgba(51,181,229,0.4)",
		"link_text_holo_dark": "rgba(92,92,255,1)",
		"link_text_holo_light": "rgba(0,0,238,1)",
		"group_button_dialog_pressed_holo_dark": "rgba(197,193,255,0.27)",
		"group_button_dialog_focused_holo_dark": "rgba(153,204,0,0.15)",
		"group_button_dialog_pressed_holo_light": "rgba(255,255,255,1)",
		"group_button_dialog_focused_holo_light": "rgba(153,204,0,0.27)",
		"legacy_pressed_highlight": "rgba(254,170,12,1)",
		"legacy_selected_highlight": "rgba(241,122,10,1)",
		"legacy_long_pressed_highlight": "rgba(255,255,255,1)",
		"holo_blue_light": "rgba(51,181,229,1)",
		"holo_green_light": "rgba(153,204,0,1)",
		"holo_red_light": "rgba(255,68,68,1)",
		"holo_blue_dark": "rgba(0,153,204,1)",
		"holo_green_dark": "rgba(102,153,0,1)",
		"holo_red_dark": "rgba(204,0,0,1)",
		"holo_purple": "rgba(170,102,204,1)",
		"holo_orange_light": "rgba(255,187,51,1)",
		"holo_orange_dark": "rgba(255,136,0,1)",
		"holo_blue_bright": "rgba(0,221,255,1)"
	};
})();<|endoftext|>


<commit_before>angular.module('siyfion.sfTypeahead', [])
  .directive('sfTypeahead', function () {
    return {
      restrict: 'ACE',
      scope: {
        datasets: '=',
        ngModel: '='
      },
      link: function (scope, element) {
        var localChange = false;
        element.typeahead(scope.datasets);

        // Updates the ngModel binding when a value is manually selected from the dropdown.
        element.bind('typeahead:selected', function (object, datum, dataset) {
          scope.$apply(function() {
            localChange = true;
            scope.ngModel = datum;
            scope.selectedDataset = dataset;
          });
        });

        // Updates the ngModel binding when a query is autocompleted.
        element.bind('typeahead:autocompleted', function (object, datum, dataset) {
          scope.$apply(function() {
            localChange = true;
            scope.ngModel = datum;
            scope.selectedDataset = dataset;
          });
        });

        // Updates the ngModel binding when the user manually enters some text
        element.bind('input', function () {
          scope.$apply(function () {
            localChange = true;
            var value = element.val();
            scope.ngModel = value;
          });
        });

        // Updates typeahead when ngModel changed.
        scope.$watch('ngModel', function (newVal) {
          if (localChange) {
            localChange = false;
            return;
          }

          if ($.isArray(scope.datasets)) {
            for (var i = 0; i < scope.datasets.length; i++) {
              if (scope.datasets[i].name ==  scope.selectedDataset) {
                var valueKey = scope.datasets[i].valueKey;
                break;
              }
            }
          } else {
            var valueKey = scope.datasets.valueKey;
          }

          if (newVal && valueKey && newVal.hasOwnProperty(valueKey)) {
            newVal = newVal[valueKey];
          }
          element.typeahead('setQuery', newVal || '');
        });
      }
    };
  });
<commit_msg>hack around [Object] in the input value<commit_after>angular.module('siyfion.sfTypeahead', [])
  .directive('sfTypeahead', function () {
    return {
      restrict: 'ACE',
      scope: {
        datasets: '=',
        ngModel: '='
      },
      link: function (scope, element) {
        var localChange = false;
        element.typeahead(scope.datasets);

        function updateScope(object, datum, dataset) {
          // for some reason $apply will place [Object] into element, this hacks around it
          var preserveVal = element.val();
          scope.$apply(function(){
            localChange = true;
            scope.ngModel = datum;
            scope.selectedDataset = dataset;
          });
          element.val(preserveVal);
        }

        // Updates the ngModel binding when a value is manually selected from the dropdown.
        element.bind('typeahead:selected', updateScope);

        // Updates the ngModel binding when a query is autocompleted.
        element.bind('typeahead:autocompleted', updateScope);

        // Updates the ngModel binding when the user manually enters some text
        element.bind('input', function () {
          scope.$apply(function () {
            localChange = true;
            var value = element.val();
            scope.ngModel = value;
          });
        });

        // Updates typeahead when ngModel changed.
        scope.$watch('ngModel', function (newVal) {
          var valueKey;
          if (localChange) {
            localChange = false;
            return;
          }

          if ($.isArray(scope.datasets)) {
            for (var i = 0; i < scope.datasets.length; i++) {
              if (scope.datasets[i].name ==  scope.selectedDataset) {
                valueKey = scope.datasets[i].valueKey;
                break;
              }
            }
          } else {
            valueKey = scope.datasets.valueKey;
          }

          if (newVal && valueKey && newVal.hasOwnProperty(valueKey)) {
            newVal = newVal[valueKey];
          }
          element.typeahead('setQuery', newVal || '');
        });
      }
    };
  });

<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  css: `
    ^ {
      position: relative;
    }

    ^scrollbar {
      box-sizing: border-box;
      width: 1px;
      background: rgba(0, 0, 0, 0);
    }

    ^scrollbarContainer {
      overflow: scroll;
      display: grid;
      grid-template-columns: auto auto;
    }

    ^ th {
      position: -webkit-sticky;
      position: sticky;
      top: 0;
    }
  `,

  constants: [
    {
      type: 'Integer',
      name: 'TABLE_HEAD_HEIGHT',
      value: 40
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'Int',
      name: 'limit',
      value: 18,
    },
    {
      class: 'Int',
      name: 'skip',
    },
    'columns',
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions'
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'selection',
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      documentation: `
        Set to true if users should be allowed to choose which columns to use.
      `,
      value: true
    },
    {
      class: 'Int',
      name: 'rowHeight',
      documentation: 'The height of one row of the table in px.',
      value: 48
    },
    {
      name: 'table_',
      documentation: `
        A reference to the table element we use in various calculations.
      `
    },
    {
      class: 'Int',
      name: 'accumulator',
      documentation: 'Used internally to mimic native scrolling speed.',
      adapt: function(_, v) {
        return v % this.rowHeight;
      }
    },
    {
      name: 'scrollbarContainer_',
      documentation: `
        A reference to the scrollbar's container element so we can update the
        height after the view has loaded.
      `
    },
    {
      type: 'String',
      name: 'scrollHeight',
      expression: function(daoCount, limit, rowHeight) {
        this.refresh();
        return rowHeight * (daoCount - limit) + this.TABLE_HEAD_HEIGHT + 'px';
      }
    },
    {
      type: 'Int',
      name: 'lastScrollTop_',
      value: 0
    },
    {
      type: 'Int',
      name: 'pageSize',
      value: 30,
      documentation: 'The number of items in each "page". There are three pages.'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage1DAO_',
      expression: function(data, pageSize) {
        return data && data.limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage2DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage3DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize * 2).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page1DAO_',
      factory: function() {
        return this.initialPage1DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page2DAO_',
      factory: function() {
        return this.initialPage2DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page3DAO_',
      factory: function() {
        return this.initialPage3DAO_;
      }
    },
    {
      type: 'Int',
      name: 'currentLowerBound',
      value: 0
    },
    {
      type: 'Int',
      name: 'currentUpperBound',
      expression: function(pageSize) {
        return pageSize * 3;
      }
    },
    {
      name: 'spacer_',
      documentation: `
        A named reference to an element so we can update its height
        periodically. Used to put empty space between the top of the container
        and the tables so we create the illusion of the table always being
        visible.
      `
    },
    'topBufferTable_',
    'visibleTable_',
    'bottomBufferTable_',
    {
      type: 'Int',
      name: 'tablesRemoved_',
      value: 0,
      documentation: `
        The number of tables that have been removed due to scrolling down.
      `
    },
    {
      type: 'String',
      name: 'spacerHeight_',
      expression: function(tablesRemoved_, pageSize, rowHeight) {
        return tablesRemoved_ * pageSize * rowHeight + 'px';
      }
    }
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({ fn: this.onDAOUpdate })));
      this.onDAOUpdate();
    },

    function initE() {
      this.
        addClass(this.myClass()).
        start('div', undefined, this.scrollbarContainer_$).
          addClass(this.myClass('scrollbarContainer')).
          on('scroll', this.onScroll).
          start().
            show(this.daoCount$.map((count) => count >= this.limit)).
            addClass(this.myClass('scrollbar')).
            style({ height: this.scrollHeight$ }).
          end().
          start().
            start('div', undefined, this.spacer_$).
              style({ height: this.spacerHeight_$ }).
            end().
            start(this.TableView, {
              data: this.page1DAO_$proxy,
              columns: this.columns,
              contextMenuActions: this.contextMenuActions,
              selection$: this.selection$,
              editColumnsEnabled: this.editColumnsEnabled
            }, this.table_$).
              addClass(this.myClass('table')).
            end().
          end().
        end();

      this.onDetach(this.onload.sub(this.addTbodies));

      this.onDetach(this.onload.sub(this.updateTableHeight));
      window.addEventListener('resize', this.updateTableHeight);
      this.onDetach(() => {
        window.removeEventListener('resize', this.updateTableHeight);
      });
    },
    {
      name: 'refresh',
      code: function() {
        this.lastScrollTop_ = 0;
        this.skip = 0;
        this.currentLowerBound = 0;
        this.currentUpperBound = this.pageSize * 3;
        this.tablesRemoved_ = 0;
        this.page1DAO_ = this.initialPage1DAO_;
        this.page2DAO_ = this.initialPage2DAO_;
        this.page3DAO_ = this.initialPage3DAO_;
      }
    },
    {
      name: 'scrollDownOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentUpperBound > this.daoCount ) return;

        this.currentUpperBound += this.pageSize;
        this.currentLowerBound += this.pageSize;

        // Remove the top buffer table.
        this.topBufferTable_.remove();
        this.tablesRemoved_ += 1;

        // Update the table references.
        this.topBufferTable_ = this.visibleTable_;
        this.visibleTable_   = this.bottomBufferTable_;

        // Add a new bottom buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentUpperBound - this.pageSize).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.add(rows);
        this.bottomBufferTable_ = this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .pop();
      }
    },
    {
      name: 'scrollUpOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentLowerBound <= 0 ) return;

        this.currentLowerBound -= this.pageSize;
        this.currentUpperBound -= this.pageSize;

        // Remove the bottom buffer table.
        this.bottomBufferTable_.remove();
        this.tablesRemoved_ -= 1;

        // Update the table references.
        this.bottomBufferTable_ = this.visibleTable_;
        this.visibleTable_      = this.topBufferTable_;

        // Add a new top buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentLowerBound).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.insertBefore(this.table_.slotE_(rows), this.visibleTable_);
        this.topBufferTable_ = this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .shift();
      }
    }
  ],

  listeners: [
    {
      name: 'onScroll',
      code: function(e) {
        var deltaY = e.target.scrollTop - this.lastScrollTop_;
        var negative = deltaY < 0;
        var rows = Math.floor(Math.abs(this.accumulator + deltaY) / this.rowHeight);
        this.accumulator += deltaY;
        var oldSkip = this.skip;
        this.skip = Math.min(this.daoCount - this.limit, Math.max(0, this.skip + (negative ? -rows : rows)));
        this.lastScrollTop_ = e.target.scrollTop;

        var uBound = () => this.currentUpperBound - this.pageSize;
        var lBound = () => this.currentLowerBound + this.pageSize;

        if ( oldSkip <= uBound() && this.skip > uBound() ) {
          // The user scrolled down past a page boundary.
          while (
            this.skip > uBound() &&
            this.currentUpperBound < this.daoCount
          ) {
            this.scrollDownOnePage();
          }
        } else if ( oldSkip > lBound() && this.skip <= lBound() ) {
          // The user scrolled up past a page boundary.
          while ( this.skip <= lBound() && this.currentLowerBound > 0 ) {
            this.scrollUpOnePage();
          }
        }
      }
    },
    {
      // TODO: Avoid onDAOUpdate approaches.
      name: 'onDAOUpdate',
      isFramed: true,
      code: function() {
        var self = this;
        this.data$proxy.select(this.Count.create()).then(function(s) {
          self.daoCount = s.value;
          self.refresh();
        });
      }
    },
    {
      name: 'updateTableHeight',
      code: function() {
        // Find the distance from the top of the table to the top of the screen.
        var distanceFromTop = this.scrollbarContainer_.el().getBoundingClientRect().y;

        // Calculate the remaining space we have to make use of.
        var remainingSpace = window.innerHeight - distanceFromTop;

        // TODO: Do we want to do this?
        // Leave space for the footer.
        remainingSpace -= 44;

        this.scrollbarContainer_.style({ height: `${remainingSpace}px` });
      }
    },
    {
      name: 'addTbodies',
      code: function() {
        this.table_.add(this.table_.rowsFrom(this.page2DAO_$proxy));
        this.table_.add(this.table_.rowsFrom(this.page3DAO_$proxy));
        var tbodies = this.table_.childNodes.filter((x) => x.nodeName === 'TBODY');
        this.topBufferTable_ = tbodies[0];
        this.visibleTable_ = tbodies[1];
        this.bottomBufferTable_ = tbodies[2];
      }
    }
  ]
});
<commit_msg>Set ScrollTableView table min-width to 968px<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  css: `
    ^ {
      position: relative;
    }

    ^scrollbar {
      box-sizing: border-box;
      width: 1px;
      background: rgba(0, 0, 0, 0);
    }

    ^scrollbarContainer {
      overflow: scroll;
      display: grid;
      grid-template-columns: 1px minmax(968px, 1fr);
    }

    ^ th {
      position: -webkit-sticky;
      position: sticky;
      top: 0;
    }

    ^ table {
      min-width: 100%;
    }
  `,

  constants: [
    {
      type: 'Integer',
      name: 'TABLE_HEAD_HEIGHT',
      value: 40
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'Int',
      name: 'limit',
      value: 18,
    },
    {
      class: 'Int',
      name: 'skip',
    },
    'columns',
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions'
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'selection',
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      documentation: `
        Set to true if users should be allowed to choose which columns to use.
      `,
      value: true
    },
    {
      class: 'Int',
      name: 'rowHeight',
      documentation: 'The height of one row of the table in px.',
      value: 48
    },
    {
      name: 'table_',
      documentation: `
        A reference to the table element we use in various calculations.
      `
    },
    {
      class: 'Int',
      name: 'accumulator',
      documentation: 'Used internally to mimic native scrolling speed.',
      adapt: function(_, v) {
        return v % this.rowHeight;
      }
    },
    {
      name: 'scrollbarContainer_',
      documentation: `
        A reference to the scrollbar's container element so we can update the
        height after the view has loaded.
      `
    },
    {
      type: 'String',
      name: 'scrollHeight',
      expression: function(daoCount, limit, rowHeight) {
        this.refresh();
        return rowHeight * (daoCount - limit) + this.TABLE_HEAD_HEIGHT + 'px';
      }
    },
    {
      type: 'Int',
      name: 'lastScrollTop_',
      value: 0
    },
    {
      type: 'Int',
      name: 'pageSize',
      value: 30,
      documentation: 'The number of items in each "page". There are three pages.'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage1DAO_',
      expression: function(data, pageSize) {
        return data && data.limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage2DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'initialPage3DAO_',
      expression: function(data, pageSize) {
        return data && data.skip(pageSize * 2).limit(pageSize);
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page1DAO_',
      factory: function() {
        return this.initialPage1DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page2DAO_',
      factory: function() {
        return this.initialPage2DAO_;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'page3DAO_',
      factory: function() {
        return this.initialPage3DAO_;
      }
    },
    {
      type: 'Int',
      name: 'currentLowerBound',
      value: 0
    },
    {
      type: 'Int',
      name: 'currentUpperBound',
      expression: function(pageSize) {
        return pageSize * 3;
      }
    },
    {
      name: 'spacer_',
      documentation: `
        A named reference to an element so we can update its height
        periodically. Used to put empty space between the top of the container
        and the tables so we create the illusion of the table always being
        visible.
      `
    },
    'topBufferTable_',
    'visibleTable_',
    'bottomBufferTable_',
    {
      type: 'Int',
      name: 'tablesRemoved_',
      value: 0,
      documentation: `
        The number of tables that have been removed due to scrolling down.
      `
    },
    {
      type: 'String',
      name: 'spacerHeight_',
      expression: function(tablesRemoved_, pageSize, rowHeight) {
        return tablesRemoved_ * pageSize * rowHeight + 'px';
      }
    }
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({ fn: this.onDAOUpdate })));
      this.onDAOUpdate();
    },

    function initE() {
      this.
        addClass(this.myClass()).
        start('div', undefined, this.scrollbarContainer_$).
          addClass(this.myClass('scrollbarContainer')).
          on('scroll', this.onScroll).
          start().
            addClass(this.myClass('scrollbar')).
            style({ height: this.scrollHeight$ }).
          end().
          start().
            start('div', undefined, this.spacer_$).
              style({ height: this.spacerHeight_$ }).
            end().
            start(this.TableView, {
              data: this.page1DAO_$proxy,
              columns: this.columns,
              contextMenuActions: this.contextMenuActions,
              selection$: this.selection$,
              editColumnsEnabled: this.editColumnsEnabled
            }, this.table_$).
              addClass(this.myClass('table')).
            end().
          end().
        end();

      this.onDetach(this.onload.sub(this.addTbodies));

      this.onDetach(this.onload.sub(this.updateTableHeight));
      window.addEventListener('resize', this.updateTableHeight);
      this.onDetach(() => {
        window.removeEventListener('resize', this.updateTableHeight);
      });
    },
    {
      name: 'refresh',
      code: function() {
        this.lastScrollTop_ = 0;
        this.skip = 0;
        this.currentLowerBound = 0;
        this.currentUpperBound = this.pageSize * 3;
        this.tablesRemoved_ = 0;
        this.page1DAO_ = this.initialPage1DAO_;
        this.page2DAO_ = this.initialPage2DAO_;
        this.page3DAO_ = this.initialPage3DAO_;
      }
    },
    {
      name: 'scrollDownOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentUpperBound > this.daoCount ) return;

        this.currentUpperBound += this.pageSize;
        this.currentLowerBound += this.pageSize;

        // Remove the top buffer table.
        this.topBufferTable_.remove();
        this.tablesRemoved_ += 1;

        // Update the table references.
        this.topBufferTable_ = this.visibleTable_;
        this.visibleTable_   = this.bottomBufferTable_;

        // Add a new bottom buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentUpperBound - this.pageSize).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.add(rows);
        this.bottomBufferTable_ = this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .pop();
      }
    },
    {
      name: 'scrollUpOnePage',
      code: function() {
        // Update the bounds.
        if ( this.currentLowerBound <= 0 ) return;

        this.currentLowerBound -= this.pageSize;
        this.currentUpperBound -= this.pageSize;

        // Remove the bottom buffer table.
        this.bottomBufferTable_.remove();
        this.tablesRemoved_ -= 1;

        // Update the table references.
        this.bottomBufferTable_ = this.visibleTable_;
        this.visibleTable_      = this.topBufferTable_;

        // Add a new top buffer table.
        var daoName = ['page1DAO_', 'page2DAO_', 'page3DAO_'][this.tablesRemoved_ % 3];
        this[daoName] = this.data.skip(this.currentLowerBound).limit(this.pageSize);
        var rows = this.table_.rowsFrom(this[daoName + '$proxy']);
        this.table_.insertBefore(this.table_.slotE_(rows), this.visibleTable_);
        this.topBufferTable_ = this.table_.childNodes
          .filter((x) => x.nodeName === 'TBODY')
          .shift();
      }
    }
  ],

  listeners: [
    {
      name: 'onScroll',
      code: function(e) {
        var deltaY = e.target.scrollTop - this.lastScrollTop_;
        var negative = deltaY < 0;
        var rows = Math.floor(Math.abs(this.accumulator + deltaY) / this.rowHeight);
        this.accumulator += deltaY;
        var oldSkip = this.skip;
        this.skip = Math.min(this.daoCount - this.limit, Math.max(0, this.skip + (negative ? -rows : rows)));
        this.lastScrollTop_ = e.target.scrollTop;

        var uBound = () => this.currentUpperBound - this.pageSize;
        var lBound = () => this.currentLowerBound + this.pageSize;

        if ( oldSkip <= uBound() && this.skip > uBound() ) {
          // The user scrolled down past a page boundary.
          while (
            this.skip > uBound() &&
            this.currentUpperBound < this.daoCount
          ) {
            this.scrollDownOnePage();
          }
        } else if ( oldSkip > lBound() && this.skip <= lBound() ) {
          // The user scrolled up past a page boundary.
          while ( this.skip <= lBound() && this.currentLowerBound > 0 ) {
            this.scrollUpOnePage();
          }
        }
      }
    },
    {
      // TODO: Avoid onDAOUpdate approaches.
      name: 'onDAOUpdate',
      isFramed: true,
      code: function() {
        var self = this;
        this.data$proxy.select(this.Count.create()).then(function(s) {
          self.daoCount = s.value;
          self.refresh();
        });
      }
    },
    {
      name: 'updateTableHeight',
      code: function() {
        // Find the distance from the top of the table to the top of the screen.
        var distanceFromTop = this.scrollbarContainer_.el().getBoundingClientRect().y;

        // Calculate the remaining space we have to make use of.
        var remainingSpace = window.innerHeight - distanceFromTop;

        // TODO: Do we want to do this?
        // Leave space for the footer.
        remainingSpace -= 44;

        this.scrollbarContainer_.style({ height: `${remainingSpace}px` });
      }
    },
    {
      name: 'addTbodies',
      code: function() {
        this.table_.add(this.table_.rowsFrom(this.page2DAO_$proxy));
        this.table_.add(this.table_.rowsFrom(this.page3DAO_$proxy));
        var tbodies = this.table_.childNodes.filter((x) => x.nodeName === 'TBODY');
        this.topBufferTable_ = tbodies[0];
        this.visibleTable_ = tbodies[1];
        this.bottomBufferTable_ = tbodies[2];
      }
    }
  ]
});
<|endoftext|>


<commit_before>define([
	'knockout',
	'atlas-state',
	'text!./data-sources.html',
	'appConfig',
	'services/AuthAPI',
	'pages/Page',
	'utils/CommonUtils',
	'databindings',
	'./components/reports/person',
	'./components/reports/dashboard',
	'./components/reports/datadensity',
	'./components/reports/person',
	'./components/reports/visit',
	'./components/reports/condition',
	'./components/reports/conditionEra',
	'./components/reports/procedure',
	'./components/reports/drug',
	'./components/reports/drugEra',
	'./components/reports/measurement',
	'./components/reports/observation',
	'./components/reports/death',
	'./components/reports/achillesHeel',
	'./components/reports/observation-period',
	'less!./data-sources.less'
], function (
	ko,
	sharedState,
	view,
	config,
	authApi,
	Page,
	commonUtils
) {
	class DataSources extends Page {
		constructor(params) {
			super(params);

			this.reports = [{
					name: ko.i18n('dataSources.reports.dashboard', 'Dashboard'),
					path: "dashboard",
					component: "report-dashboard",
					summary: ko.observable()
				},
				{
					name: ko.i18n('dataSources.reports.dataDensity', 'Data Density'),
					path: "datadensity",
					component: "report-datadensity",
				},
				{
					name: ko.i18n('dataSources.reports.person', 'Person'),
					path: "person",
					component: "report-person",
				},
				{
					name: ko.i18n('dataSources.reports.visit', 'Visit'),
					path: "visit",
					component: "report-visit",
				},
				{
					name: ko.i18n('dataSources.reports.conditionOccurrence', 'Condition Occurrence'),
					path: "condition",
					component: "report-condition",
				},
				{
					name: ko.i18n('dataSources.reports.conditionEra', 'Condition Era'),
					path: "conditionera",
					component: "report-condition-era",
				},
				{
					name: ko.i18n('dataSources.reports.procedure', 'Procedure'),
					path: "procedure",
					component: "report-procedure",
				},
				{
					name: ko.i18n('dataSources.reports.drugExposure', 'Drug Exposure'),
					path: "drug",
					component: "report-drug",
				},
				{
					name: ko.i18n('dataSources.reports.drugEra', 'Drug Era'),
					path: "drugera",
					component: "report-drug-era",
				},
				{
					name: ko.i18n('dataSources.reports.measurement', 'Measurement'),
					path: "measurement",
					component: "report-measurement",
				},
				{
					name: ko.i18n('dataSources.reports.observation', 'Observation'),
					path: "observation",
					component: "report-observation",
				},
				{
					name: ko.i18n('dataSources.reports.death', 'Death'),
					path: "death",
					component: "report-death",
				},
				{
					name: ko.i18n('dataSources.reports.achillesHeel', 'Achilles Heel'),
					path: "achillesheel",
					component: "report-achilles-heel",
				},
				{
					name: ko.i18n('dataSources.reports.observationPeriod', 'Observation Period'),
					path: "observationPeriod",
					component: "report-observation-period"
				}
			];

			this.sources = ko.computed(() => sharedState.sources().filter(function (s) {
				return s.hasResults && s.hasVocabulary && authApi.isPermittedViewDataSourceReport(s.sourceKey);
			}));

			this.loadingReport = ko.observable(false);
			this.hasError = ko.observable(false);
			this.errorMessage = ko.observable();
			this.loadingReportDrilldown = ko.observable(false);
			this.isReportLoading = ko.pureComputed(function () {
				return this.loadingReport() && !this.hasError() && !this.loadingReportDrilldown();
			}, this);

			this.isAuthenticated = authApi.isAuthenticated;
			this.canViewCdmResults = ko.pureComputed(() => {
				return (config.userAuthenticationEnabled && this.isAuthenticated() && authApi.isPermittedViewCdmResults()) || !config.userAuthenticationEnabled;
			});

			this.showSelectionArea = params.showSelectionArea == undefined ? true : params.showSelectionArea;
			this.currentSource = ko.observable(this.sources()[0]);
			this.currentReport = ko.observable();
			this.selectedReport = ko.observable();

			this.currentSource.subscribe((source) => source && this.hasError(false));
			this.currentReport.subscribe((report) => report && this.hasError(false));

			this.subscriptions.push(this.selectedReport.subscribe(r => this.updateLocation()));
			this.subscriptions.push(this.currentSource.subscribe(r => this.updateLocation()));

			this.currentConcept = ko.observable();
		}

		updateLocation() {
			if (this.currentSource() && this.selectedReport()) {
				document.location = "#/datasources/" + this.currentSource().sourceKey + "/" + this.selectedReport().path;
			}
		}

		onRouterParamsChanged(changedParams, newParams) {
			if (newParams == null && changedParams == null)
				return;

			if (newParams == null) {
				// initial page load direct from URL
				this.currentSource(this.sources().find(s => s.sourceKey == changedParams.sourceKey));
				this.currentReport(this.reports.find(r => r.path == changedParams.reportName));
				this.selectedReport(this.reports.find(r => r.path == changedParams.reportName));
			} else {
				if (changedParams.sourceKey && this.currentSource() && changedParams.sourceKey !== this.currentSource().sourceKey) {
					this.currentSource(this.sources().find(s => s.sourceKey == newParams.sourceKey));
				}
				if (changedParams.reportName) {
					this.currentReport(this.reports.find(r => r.path == newParams.reportName));
					this.selectedReport(this.reports.find(r => r.path == newParams.reportName));
				}
			}
		}
	}

	return commonUtils.build('data-sources', DataSources, view);
});<commit_msg>Set the Observation Period report in a logical order(after the Observation report) #2506 (#2507)<commit_after>define([
	'knockout',
	'atlas-state',
	'text!./data-sources.html',
	'appConfig',
	'services/AuthAPI',
	'pages/Page',
	'utils/CommonUtils',
	'databindings',
	'./components/reports/person',
	'./components/reports/dashboard',
	'./components/reports/datadensity',
	'./components/reports/person',
	'./components/reports/visit',
	'./components/reports/condition',
	'./components/reports/conditionEra',
	'./components/reports/procedure',
	'./components/reports/drug',
	'./components/reports/drugEra',
	'./components/reports/measurement',
	'./components/reports/observation',
	'./components/reports/death',
	'./components/reports/achillesHeel',
	'./components/reports/observation-period',
	'less!./data-sources.less'
], function (
	ko,
	sharedState,
	view,
	config,
	authApi,
	Page,
	commonUtils
) {
	class DataSources extends Page {
		constructor(params) {
			super(params);

			this.reports = [{
					name: ko.i18n('dataSources.reports.dashboard', 'Dashboard'),
					path: "dashboard",
					component: "report-dashboard",
					summary: ko.observable()
				},
				{
					name: ko.i18n('dataSources.reports.dataDensity', 'Data Density'),
					path: "datadensity",
					component: "report-datadensity",
				},
				{
					name: ko.i18n('dataSources.reports.person', 'Person'),
					path: "person",
					component: "report-person",
				},
				{
					name: ko.i18n('dataSources.reports.visit', 'Visit'),
					path: "visit",
					component: "report-visit",
				},
				{
					name: ko.i18n('dataSources.reports.conditionOccurrence', 'Condition Occurrence'),
					path: "condition",
					component: "report-condition",
				},
				{
					name: ko.i18n('dataSources.reports.conditionEra', 'Condition Era'),
					path: "conditionera",
					component: "report-condition-era",
				},
				{
					name: ko.i18n('dataSources.reports.procedure', 'Procedure'),
					path: "procedure",
					component: "report-procedure",
				},
				{
					name: ko.i18n('dataSources.reports.drugExposure', 'Drug Exposure'),
					path: "drug",
					component: "report-drug",
				},
				{
					name: ko.i18n('dataSources.reports.drugEra', 'Drug Era'),
					path: "drugera",
					component: "report-drug-era",
				},
				{
					name: ko.i18n('dataSources.reports.measurement', 'Measurement'),
					path: "measurement",
					component: "report-measurement",
				},
				{
					name: ko.i18n('dataSources.reports.observation', 'Observation'),
					path: "observation",
					component: "report-observation",
				},
				{
					name: ko.i18n('dataSources.reports.observationPeriod', 'Observation Period'),
					path: "observationPeriod",
					component: "report-observation-period"
				},
				{
					name: ko.i18n('dataSources.reports.death', 'Death'),
					path: "death",
					component: "report-death",
				},
				{
					name: ko.i18n('dataSources.reports.achillesHeel', 'Achilles Heel'),
					path: "achillesheel",
					component: "report-achilles-heel",
				}
			];

			this.sources = ko.computed(() => sharedState.sources().filter(function (s) {
				return s.hasResults && s.hasVocabulary && authApi.isPermittedViewDataSourceReport(s.sourceKey);
			}));

			this.loadingReport = ko.observable(false);
			this.hasError = ko.observable(false);
			this.errorMessage = ko.observable();
			this.loadingReportDrilldown = ko.observable(false);
			this.isReportLoading = ko.pureComputed(function () {
				return this.loadingReport() && !this.hasError() && !this.loadingReportDrilldown();
			}, this);

			this.isAuthenticated = authApi.isAuthenticated;
			this.canViewCdmResults = ko.pureComputed(() => {
				return (config.userAuthenticationEnabled && this.isAuthenticated() && authApi.isPermittedViewCdmResults()) || !config.userAuthenticationEnabled;
			});

			this.showSelectionArea = params.showSelectionArea == undefined ? true : params.showSelectionArea;
			this.currentSource = ko.observable(this.sources()[0]);
			this.currentReport = ko.observable();
			this.selectedReport = ko.observable();

			this.currentSource.subscribe((source) => source && this.hasError(false));
			this.currentReport.subscribe((report) => report && this.hasError(false));

			this.subscriptions.push(this.selectedReport.subscribe(r => this.updateLocation()));
			this.subscriptions.push(this.currentSource.subscribe(r => this.updateLocation()));

			this.currentConcept = ko.observable();
		}

		updateLocation() {
			if (this.currentSource() && this.selectedReport()) {
				document.location = "#/datasources/" + this.currentSource().sourceKey + "/" + this.selectedReport().path;
			}
		}

		onRouterParamsChanged(changedParams, newParams) {
			if (newParams == null && changedParams == null)
				return;

			if (newParams == null) {
				// initial page load direct from URL
				this.currentSource(this.sources().find(s => s.sourceKey == changedParams.sourceKey));
				this.currentReport(this.reports.find(r => r.path == changedParams.reportName));
				this.selectedReport(this.reports.find(r => r.path == changedParams.reportName));
			} else {
				if (changedParams.sourceKey && this.currentSource() && changedParams.sourceKey !== this.currentSource().sourceKey) {
					this.currentSource(this.sources().find(s => s.sourceKey == newParams.sourceKey));
				}
				if (changedParams.reportName) {
					this.currentReport(this.reports.find(r => r.path == newParams.reportName));
					this.selectedReport(this.reports.find(r => r.path == newParams.reportName));
				}
			}
		}
	}

	return commonUtils.build('data-sources', DataSources, view);
});<|endoftext|>


<commit_before>/*!
 * angular-vertilize 1.0.0
 * Christopher Collins
 * https://github.com/Sixthdim/angular-vertilize.git
 * License: MIT
 */
(function () {
  "use strict";

  var module = angular.module('angular.vertilize', []);

  // Vertilize Container
  module.directive('vertilizeContainer', [
    function(){
      return {
        restrict: 'EA',
        controller: [
          '$scope', '$window',
          function($scope, $window){
            // Alias this
            var _this = this;

            // Array of children heights
            _this.childrenHeights = [];

            // API: Allocate child, return index for tracking.
            _this.allocateMe = function(){
              _this.childrenHeights.push(0);
              return (_this.childrenHeights.length - 1);
            };

            // API: Update a child's height
            _this.updateMyHeight = function(index, height){
              _this.childrenHeights[index] = height;
            };

            // API: Get tallest height
            _this.getTallestHeight = function(){
              var height = 0;
              for (var i=0; i < _this.childrenHeights.length; i=i+1){
                height = Math.max(height, _this.childrenHeights[i]);
              }
              return height;
            };

            // Add window resize to digest cycle
            angular.element($window).bind('resize', function(){
              return $scope.$apply();
            });
          }
        ]
      };
    }
  ]);

  // Vertilize Item
  module.directive('vertilize', [
    function(){
      return {
        restrict: 'EA',
        require: '^vertilizeContainer',
        link: function(scope, element, attrs, parent){
          // My index allocation
          var myIndex = parent.allocateMe();

          // Get my real height by cloning so my height is not affected.
          var getMyRealHeight = function(){
            var clone = element.clone()
              .removeAttr('vertilize')
              .css({
                height: '',
                width: element.width(),
                position: 'fixed',
                top: 0,
                left: 0,
                visibility: 'hidden'
              });
            element.after(clone);
            var realHeight = clone.height();
            clone['remove']();
            return realHeight;
          };

          // Watch my height
          scope.$watch(getMyRealHeight, function(myNewHeight){
            if (myNewHeight){
              parent.updateMyHeight(myIndex, myNewHeight);
            }
          });

          // Watch for tallest height change
          scope.$watch(parent.getTallestHeight, function(tallestHeight){
            if (tallestHeight){
              element.css('height', tallestHeight);
            }
          });
        }
      };
    }
  ]);

}());<commit_msg>Use outerWidth() for height calculation.<commit_after>/*!
 * angular-vertilize 1.0.0
 * Christopher Collins
 * https://github.com/Sixthdim/angular-vertilize.git
 * License: MIT
 */
(function () {
  "use strict";

  var module = angular.module('angular.vertilize', []);

  // Vertilize Container
  module.directive('vertilizeContainer', [
    function(){
      return {
        restrict: 'EA',
        controller: [
          '$scope', '$window',
          function($scope, $window){
            // Alias this
            var _this = this;

            // Array of children heights
            _this.childrenHeights = [];

            // API: Allocate child, return index for tracking.
            _this.allocateMe = function(){
              _this.childrenHeights.push(0);
              return (_this.childrenHeights.length - 1);
            };

            // API: Update a child's height
            _this.updateMyHeight = function(index, height){
              _this.childrenHeights[index] = height;
            };

            // API: Get tallest height
            _this.getTallestHeight = function(){
              var height = 0;
              for (var i=0; i < _this.childrenHeights.length; i=i+1){
                height = Math.max(height, _this.childrenHeights[i]);
              }
              return height;
            };

            // Add window resize to digest cycle
            angular.element($window).bind('resize', function(){
              return $scope.$apply();
            });
          }
        ]
      };
    }
  ]);

  // Vertilize Item
  module.directive('vertilize', [
    function(){
      return {
        restrict: 'EA',
        require: '^vertilizeContainer',
        link: function(scope, element, attrs, parent){
          // My index allocation
          var myIndex = parent.allocateMe();

          // Get my real height by cloning so my height is not affected.
          var getMyRealHeight = function(){
            var clone = element.clone()
              .removeAttr('vertilize')
              .css({
                height: '',
                width: element.outerWidth(),
                position: 'fixed',
                top: 0,
                left: 0,
                visibility: 'hidden'
              });
            element.after(clone);
            var realHeight = clone.height();
            clone['remove']();
            return realHeight;
          };

          // Watch my height
          scope.$watch(getMyRealHeight, function(myNewHeight){
            if (myNewHeight){
              parent.updateMyHeight(myIndex, myNewHeight);
            }
          });

          // Watch for tallest height change
          scope.$watch(parent.getTallestHeight, function(tallestHeight){
            if (tallestHeight){
              element.css('height', tallestHeight);
            }
          });
        }
      };
    }
  ]);

}());
<|endoftext|>