veyron/examples/pipetobrowser: Supporting search (in message,
file path, threadid ), filtering (log levels, date),
sorting (all columns except message) for veyron log viewer.
Also adding ability to pause/resume the log viewer.

To support these features for veyron log viewer and other plugins,
a new reusable data grid component was created (libs/ui-components/data-grid)

data grid can host search, filters and supports sortable columns and custom
cell renderer

Please see documentation in (libs/ui-components/data-grid/grid/component.html)
for usage and other details.

still TODO: UI cleanup

Change-Id: I638e71dfac5246c4abb19da7c3b3a270eaf3fb87
diff --git a/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html b/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html
index 2b8a370..6ad8264 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/blackhole/component.html
@@ -3,7 +3,7 @@
 p2b-ui-components-blackhole is a simple image of a blackhole in the middle of a galaxy
 when blackhole is running, a spinner gif is displayed in the middle of the blackhole.
 -->
-<polymer-element name="p2b-ui-components-blackhole">
+<polymer-element name="p2b-blackhole">
   <template>
     <link rel="stylesheet" href="../common/common.css">
     <link rel="stylesheet" href="component.css">
@@ -11,7 +11,7 @@
     <cite class="attribution">Photo from Wikipedia <a target="_blank" href="http://en.wikipedia.org/wiki/Black_hole#mediaviewer/File:BH_LMC.png">by Alan R</a> (CC BY-SA 2.5)</cite>
   </template>
   <script>
-    Polymer('p2b-ui-components-blackhole', {
+    Polymer('p2b-blackhole', {
       /*
        * Sets the blackhole in motion.
        */
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.html
new file mode 100644
index 0000000..f005eb3
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/component.html
@@ -0,0 +1,67 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-checkbox/paper-checkbox.html">
+<link rel="import" href="/libs/vendor/polymer/paper-radio-button/paper-radio-button.html">
+<link rel="import" href="/libs/vendor/polymer/paper-radio-group/paper-radio-group.html">
+<polymer-element name="p2b-grid-filter-select" attributes="multiple key" grid-filter expects-grid-state>
+  <template>
+    <link rel="stylesheet" href="component.css">
+    <content id="content" select="*"></content>
+    <div>
+      <template if="{{ multiple }}" bind>
+        <core-selector id="multiSelector" multi selectedAttribute="checked" valueattr="value" core-select="{{updateGridState}}" selected="{{ selected }}">
+          <template repeat="{{ item in items }}">
+            <paper-checkbox value="{{ item.value }}" on-change="{{ updateGridState }}" label="{{ item.label }}"></paper-checkbox>
+          </template>
+        </core-selector>
+      </template>
+
+      <template if="{{ !multiple }}" bind>
+        <paper-radio-group id="singleSelector" valueattr="value" selected="{{ selected }}">
+          <template repeat="{{ item in items }}">
+            <paper-radio-button value="{{ item.value }}" on-change="{{ updateGridState }}" label="{{ item.label }}"></paper-radio-button>
+          </template>
+        </paper-radio-group>
+      </template>
+    </div>
+  </template>
+  <script>
+    Polymer('p2b-grid-filter-select', {
+     /*
+      * Whether multiple items can be selected
+      * @type {boolean}
+      */
+      multiple: false,
+
+      /*
+       * Key that will be added to filters map passed to the fetch() function of your data source.
+       * @type {string}
+       */
+      key: '',
+
+      ready: function() {
+        // find the selected items from the child nodes
+        this.items = Array.prototype.slice.call(this.$.content.getDistributedNodes());
+        for(var i=0; i < this.items.length; i++){
+          if(this.items[i].checked) {
+            if(this.multiple) {
+              this.selected = this.selected || [];
+              this.selected.push(this.items[i].value);
+            } else {
+              this.selected = this.items[i].value;
+            }
+          }
+        }
+      },
+
+      updateGridState: function() {
+        if( this.multiple ) {
+          // quirk: we need to copy the array so change is observed. .slice() does that
+          this.gridState.filters[this.key] = this.$.multiSelector.selected.slice();
+        } else {
+          this.gridState.filters[this.key] = this.$.singleSelector.selected.slice();
+        }
+      }
+
+    });
+    </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.html
new file mode 100644
index 0000000..e338bd9
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/select/item/component.html
@@ -0,0 +1,25 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-filter-select-item" attributes="label checked value">
+  <script>
+    Polymer('p2b-grid-filter-select-item', {
+      /*
+       * Label text for the item
+       * @type {string}
+       */
+      label: '',
+
+      /*
+       * Whether toggle is checked or not
+       * @type {boolean}
+       */
+      checked: false,
+
+      /*
+       * Value that will available as filters[key] in the fetch() function of your data source.
+       * Where key is the key of the select filter that contains this item
+       * @type {string}
+       */
+      value: ''
+    });
+    </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.html
new file mode 100644
index 0000000..70b8bfc
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/filter/toggle/component.html
@@ -0,0 +1,32 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-toggle-button/paper-toggle-button.html">
+<polymer-element name="p2b-grid-filter-toggle" attributes="key checked label" grid-filter expects-grid-state>
+  <template>
+    <link rel="stylesheet" href="component.css">
+    <paper-toggle-button id="toggle" on-change="{{ updateGridState }}" checked?="{{ checked }}"></paper-toggle-button>
+  </template>
+  <script>
+    Polymer('p2b-grid-filter-toggle', {
+      /*
+       * Label text for the toggle filter
+       * @type {string}
+       */
+      label: '',
+
+      /*
+       * Whether toggle is checked or not
+       * @type {boolean}
+       */
+      checked: false,
+
+      /*
+       * Key that will be added to filters map passed to the fetch() function of your data source.
+       * @type {string}
+       */
+      key: '',
+      updateGridState: function() {
+        this.gridState.filters[this.key] = this.$.toggle.checked;
+      }
+    });
+    </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.html
new file mode 100644
index 0000000..fd2a2cb
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/cell/renderer.html
@@ -0,0 +1,14 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-cell-renderer" extends="td">
+  <template>
+    <link rel="stylesheet" href="renderer.css">
+    <content></content>
+  </template>
+  <script>
+   /*
+    * @private
+    */
+    Polymer('p2b-grid-cell-renderer', {
+    });
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/component.html
new file mode 100644
index 0000000..d2067a2
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/component.html
@@ -0,0 +1,24 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-column" grid-column attributes="label sortable key">
+  <script>
+    Polymer('p2b-grid-column', {
+      /*
+       * Label text for the column
+       * @type {string}
+       */
+      label: '',
+
+      /*
+       * whether this column is sortable
+       * @type {boolean}
+       */
+      sortable: false,
+
+      /*
+       * Key that will be pass as sort.key to fetch() function of your data source.
+       * @type {string}
+       */
+      key: ''
+    });
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
new file mode 100644
index 0000000..7b57b8a
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
@@ -0,0 +1,32 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-column-renderer" extends="th" attributes="data gridState" expects-grid-state on-tap="{{ updateGridState }}">
+  <template>
+    <link rel="stylesheet" href="renderer.css">
+    <span> {{ data.label }}</span>
+    <template if="{{ data.sortable && gridState.sort.key == data.key }}">
+
+      <template if="{{ gridState.sort.ascending }}">
+        <span>▲</span>
+      </template>
+
+      <template if="{{ !gridState.sort.ascending }}">
+        <span>▼</span>
+      </template>
+    </template>
+
+  </template>
+  <script>
+   /*
+    * @private
+    */
+    Polymer('p2b-grid-column-renderer', {
+      updateGridState:function() {
+        if( !this.data.sortable ) {
+          return;
+        }
+        this.gridState.sort.ascending = !this.gridState.sort.ascending;
+        this.gridState.sort.key = this.data.key;
+      }
+    });
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
new file mode 100644
index 0000000..178f529
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
@@ -0,0 +1,230 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/row/renderer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/cell/renderer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/column/renderer.html">
+
+<polymer-element name="p2b-grid" attributes="summary dataSource defaultSortKey defaultSortAscending">
+  <template>
+    <link rel="stylesheet" href="component.css">
+    <div id="templates"></div>
+    <div>
+      <content select="[grid-search]"></content>
+    </div>
+    <div>
+      <content select="[grid-filter]"></content>
+    </div>
+    <div>{{ dataSourceResult.length }} items</div>
+    <table summary="{{ summary }}">
+      <thead>
+        <tr>
+          <th is="p2b-grid-column-renderer" gridState="{{ gridState }}" data="{{ col.columnData }}" repeat="{{ col in columns }}" template></th>
+        </tr>
+      </thead>
+      <tbody>
+        <!-- quirk: Shadow Dom breaks parent-child relationships in HTML, this causes issues with
+         elements like table. Ideally we could have had <grid><grid-row><grid-cell> but we can't do
+         that yet since the tr and td rendered by <grid-row> <grid-cell> will be in shadow Dom and isolated.
+         Chromium bug: https://code.google.com/p/chromium/issues/detail?id=374315
+         W3C Spec bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=15616
+         -->
+        <tr is="p2b-grid-row-renderer" repeat="{{ item in dataSourceResult }}" template>
+          <td is="p2b-grid-cell-renderer" repeat="{{ col in columns }}" template>
+            <template ref="{{ col.cellTemplateId }}" bind></template>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </template>
+  <script>
+    /*
+     * Reusable grid that can host search, filters and supports sortable columns and custom cell renderer
+     * @example usage:
+
+           <p2b-grid defaultSortKey="firstName"
+            defaultSortAscending
+            dataSource="{{ myContactsDataSource }}"
+            summary="Displays your contacts in a tabular format">
+
+            <!-- Search contacts-->
+            <p2b-grid-search label="Search Contacts"></p2b-grid-search>
+
+            <!-- Filter for circles -->
+            <p2b-grid-filter-select multiple key="circle" label="Circles">
+              <p2b-grid-filter-select-item checked label="Close Friends" value="close"></p2b-grid-filter-select-item>
+              <p2b-grid-filter-select-item label="Colleagues" value="far"></p2b-grid-filter-select-item>
+            </p2b-grid-filter-select>
+
+            <!-- Toggle to allow filtering by online mode-->
+            <p2b-grid-filter-toggle key="online" label="Show online only" checked></p2b-grid-filter-toggle>
+
+            <!-- Columns, sorting and cell templates -->
+            <p2b-grid-column sortable label="First Name" key="firstName" />
+              <template>{{ item.firstName }}</template>
+            </p2b-grid-column>
+
+            <p2b-grid-column sortable label="Last Name" key="lastName" />
+              <template>
+                <span style="text-transform:uppercase;">
+                  {{ item.lastName }}
+                </span>
+              </template>
+            </p2b-grid-column>
+
+            <p2b-grid-column label="Circle" key="circle"/>
+              <template>
+                <img src="images\circls\{{ item.circle }}.jpg" alt="in {{ item.circle }} circle"><img>
+              </template>
+            </p2b-grid-column>
+
+          </p2b-grid>
+
+     * DataSource attribute expects an object that has a fetch(search, sort, filters) method. Please see
+     * documentation on DataSource property for details.
+     */
+    Polymer('p2b-grid', {
+      /*
+       * DataSource is an object that has a fetch(search, sort, filters) method where
+       * search{key<string>} is current search keyword
+       * sort{key<string>, ascending<bool>} current sort key and direction
+       * filter{map{key<string>, values<Array>}} Map of filter keys to currently selected filter values
+       * search, sort and filters are provided by the grid control whenever they are changed by the user.
+       * DataSource is called automatically by the grid when user interacts with the component
+       * Grid does some batching of user actions and only calls fetch when needed in a requestAnimationFrame
+       * Keys provided for sort and filters correspond to keys set in the markup when constructing the grid.
+       * DataSource.fetch() is expected to return an array of filtered sorted results of the items.
+       */
+      dataSource: null,
+
+      /*
+       * Summary for the grid.
+       * @type {string}
+       */
+      summary: '',
+
+      /*
+       * Initial sort key
+       * @type {string}
+       */
+      defaultSortKey: '',
+
+      /*
+       * Initial sort direction
+       * @type {string}
+       */
+      defaultSortAscending: false,
+
+      ready: function() {
+
+        // private property fields
+        this.columns = [];
+        this.shouldRefetchData = true;
+        this.dataSource = null;
+        this.cachedDataSourceResult = [];
+        this.gridState = {
+          sort: {
+            key: '',
+            ascending: false
+          },
+          search: {
+            keyword: ''
+          },
+          filters: {}
+        },
+
+        // set the default sort and direction on the state object
+        this.gridState.sort.key = this.defaultSortKey;
+        this.gridState.sort.ascending = this.defaultSortAscending;
+
+        this.initTemplates(); // loads cell templates
+        this.initGridStateDependents(); //  initialize filters and search
+        this.initGridStateObserver(); // observe changes to grid state by filters
+      },
+
+      dataSourceChanged: function() {
+        this.refresh();
+      },
+
+      /*
+       * Sets up an object observer to get any mutations on the grid state object.
+       * Filters or sortable columns can change the state and we like to refresh
+       * when changes happens.
+       * @private
+       */
+      initGridStateObserver: function() {
+        var self = this;
+        for (key in this.gridState) {
+          var observer = new ObjectObserver(this.gridState[key])
+          observer.open(function() {
+            // refresh the grid on any mutations
+            self.refresh();
+          });
+        }
+      },
+
+      /*
+       * Copies the cell templates as defined by the user for each column into
+       * the grid so that we can reference them in a loop.
+       * quirk: Need to reference them by Id so a new Id is generated for each one
+       * Ids are scoped in the shadow DOM so no collisions.
+       * @private
+       */
+      initTemplates: function() {
+        var columnNodes = this.querySelectorAll('[grid-column]');
+        for (var i = 0; i < columnNodes.length; i++) {
+          var col = columnNodes[i];
+          var cellTemplate = col.querySelector('template');
+          var cellTemplateId = "userProvidedCellTemplate" + i;
+          cellTemplate.id = cellTemplateId;
+          this.$.templates.appendChild(cellTemplate);
+          this.columns.push({
+            cellTemplateId: cellTemplateId,
+            columnData: col
+          });
+        }
+      },
+
+      /*
+       * Provide the grid state to any component that expects it so they can mutate
+       * without the grid needing to know about them at all.
+       * @private
+       */
+      initGridStateDependents: function() {
+        var gridStateDependents = this.querySelectorAll('[expects-grid-state]');
+        for (var i = 0; i < gridStateDependents.length; i++) {
+          gridStateDependents[i].gridState = this.gridState;
+        }
+      },
+
+      /*
+       * Refreshed the grid by fetching the data again and updating the UI in the next render tick
+       */
+      refresh: function() {
+        var self = this;
+        requestAnimationFrame(function() {
+          self.shouldRefetchData = true;
+        });
+      },
+
+      /*
+       * dataSourceResult is what the UI binds to and integrate over.
+       * Only fetches data if scheduled to do so
+       * @private
+       */
+      get dataSourceResult() {
+        if (!this.shouldRefetchData || !this.dataSource) {
+          return this.cachedDataSourceResult;
+        }
+
+        this.cachedDataSourceResult = this.dataSource.fetch(
+          this.gridState.search,
+          this.gridState.sort,
+          this.gridState.filters
+        );
+
+        this.shouldRefetchData = false;
+
+        return this.cachedDataSourceResult;
+      }
+    });
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.html
new file mode 100644
index 0000000..e6ad86f
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/row/renderer.html
@@ -0,0 +1,14 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<polymer-element name="p2b-grid-row-renderer" extends="tr">
+  <template>
+    <link rel="stylesheet" href="renderer.css">
+    <content></content>
+  </template>
+  <script>
+   /*
+    * @private
+    */
+    Polymer('p2b-grid-row-renderer', {
+    });
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.css
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.html
new file mode 100644
index 0000000..3ede3b1
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/search/component.html
@@ -0,0 +1,23 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/vendor/polymer/paper-input/paper-input.html">
+<polymer-element name="p2b-grid-search" grid-search expects-grid-state attributes="label">
+  <template>
+    <link rel="stylesheet" href="component.css">
+    <paper-input on-change="{{ updateGridState }}" id="search" label="{{ label }}"></paper-input>
+  </template>
+  <script>
+    /*
+     * Renders a search box inside the grid components
+     */
+    Polymer('p2b-grid-search', {
+      /*
+       * Label text for the search
+       * @type {string}
+       */
+      label: '',
+      updateGridState: function() {
+        this.gridState.search.keyword = this.$.search.value.trim();
+      }
+    });
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
index 176c0bd..1b3732c 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
@@ -17,4 +17,4 @@
       }
     });
     </script>
-</polymer-element>
\ No newline at end of file
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js
index baa2c8f..06546dc 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/dev/null/plugin.js
@@ -15,7 +15,7 @@
 
   play(stream) {
 
-    var blackhole = document.createElement('p2b-ui-components-blackhole');
+    var blackhole = document.createElement('p2b-blackhole');
     blackhole.start();
 
     stream.on('data', () => {
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
index e99e5f8..b0b01e3 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
@@ -1,24 +1,7 @@
-tr.error {
-  background-color: #FF9700;
-}
-
-tr.fatal {
-  background-color: #DA4336;
-}
-
-tr.warning {
-  background-color: #FFEA3A;
-}
-
-tr.info {
-  background-color: #FAFAFA;
-}
-
-.lineNumber::before {
+::shadow /deep/ .lineNumber::before {
   content: '#';
 }
-
-.lineNumber {
+::shadow /deep/ .lineNumber {
   color: rgb(51, 103, 214);
   margin-left: 0.1em;
-}
\ No newline at end of file
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
index d1a0fb7..7f31ef6 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -1,39 +1,63 @@
 <link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/grid/column/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/filter/select/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/filter/select/item/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/filter/toggle/component.html">
+<link rel="import" href="/libs/ui-components/data-grid/search/component.html">
 
 <polymer-element name="p2b-plugin-vlog">
   <template>
     <link rel="stylesheet" href="component.css">
-    <table summary="Data Grid displaying veyron log items in a tabular format.">
-      <thead>
-        <tr>
-          <th>Log Level</th>
-          <th>File</th>
-          <th>Message</th>
-          <th>Date</th>
-          <th>ThreadId</th>
-        </tr>
-      </thead>
-      <tbody>
-        <template repeat="{{ item in logItems }}">
-          <tr class="{{ item.level }}">
-            <td>{{ item.level }}</td>
-            <th scope="row">{{ item.file }}<span class="lineNumber">{{ item.fileLine }}<span/></th>
-            <td>{{ item.message }}</td>
-            <td>{{ item.date }}</td>
-            <td>{{ item.threadId }}</td>
-          </tr>
-        </template>
-      </tbody>
-    </table>
 
+    <p2b-grid id="grid" defaultSortKey="date" defaultSortAscending dataSource="{{ dataSource }}" summary="Data Grid displaying veyron log items in a tabular format with filters and search options.">
+
+      <!-- Search -->
+      <p2b-grid-search label="Search Logs"></p2b-grid-search>
+
+      <!-- Filter to select log level (multiple allowed) -->
+      <p2b-grid-filter-select multiple key="level" label="Levels">
+        <p2b-grid-filter-select-item checked label="Fatal" value="fatal"></p2b-grid-filter-select-item>
+        <p2b-grid-filter-select-item checked label="Error" value="error"></p2b-grid-filter-select-item>
+        <p2b-grid-filter-select-item checked label="Warning" value="warning"></p2b-grid-filter-select-item>
+        <p2b-grid-filter-select-item checked label="Info" value="info"></p2b-grid-filter-select-item>
+      </p2b-grid-filter-select>
+
+      <!-- Filter to select date range -->
+      <p2b-grid-filter-select key="date" label="Date">
+        <p2b-grid-filter-select-item checked label="Any time" value="all"></p2b-grid-filter-select-item>
+        <p2b-grid-filter-select-item label="Past 1 hour" value="1"></p2b-grid-filter-select-item>
+        <p2b-grid-filter-select-item label="Past 24 hours" value="24"></p2b-grid-filter-select-item>
+      </p2b-grid-filter-select>
+
+      <!-- Toggle to allow one to pause the display of incoming logs -->
+      <p2b-grid-filter-toggle key="autorefresh" label="Live Refresh" checked></p2b-grid-filter-toggle>
+
+      <!-- Columns, sorting and cell templates -->
+      <p2b-grid-column sortable label="Level" key="level">
+        <template>{{ item.level }}</template>
+      </p2b-grid-column>
+      <p2b-grid-column sortable label="File" key="file">
+        <template>{{ item.file }}<span class="lineNumber">{{ item.fileLine }}</template>
+      </p2b-grid-column>
+      <p2b-grid-column label="Message" key="message">
+        <template>{{ item.message }}</template>
+      </p2b-grid-column>
+      <p2b-grid-column sortable label="Date" key="date">
+        <template>{{ item.date }}</template>
+      </p2b-grid-column>
+      <p2b-grid-column sortable label="threadid" key="threadid">
+        <template>{{ item.threadid }}</template>
+      </p2b-grid-column>
+
+    </p2b-grid>
   </template>
+
   <script>
     Polymer('p2b-plugin-vlog', {
-      /*
-       * List of log items to display
-       * @type {object}
-       */
-      logItems: []
+      refreshGrid: function() {
+        this.$.grid.refresh();
+      }
     });
-    </script>
-</polymer-element>
\ No newline at end of file
+  </script>
+</polymer-element>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/data-source.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/data-source.js
new file mode 100644
index 0000000..be602c0
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/data-source.js
@@ -0,0 +1,87 @@
+/*
+ * Implement the data source which handles parsing the stream, searching, filtering
+ * and sorting of the vLog items.
+ * @fileoverview
+ */
+import { parse } from './parser';
+import { vLogSort } from './sorter';
+import { vLogSearch } from './searcher';
+import { vLogFilter } from './filterer';
+
+export class vLogDataSource {
+  constructor(stream, onNewItem, onError) {
+
+    /*
+     * all logs, unlimited buffer for now.
+     * @private
+     */
+    this.allLogItems = [];
+
+    /*
+     * Raw stream of log data
+     * @private
+     */
+    this.stream = stream;
+
+    stream.on('data', (line) => {
+      if (line.trim() === '') {
+        return;
+      }
+      var logItem = null;
+      // try to parse and display as much as we can.
+      try {
+        logItem = parse(line);
+      } catch (e) {
+        if (onError) {
+          onError(e);
+        }
+      }
+
+      if (logItem) {
+        this.allLogItems.push(logItem);
+        if (onNewItem) {
+          onNewItem(logItem);
+        }
+      }
+    });
+
+  }
+
+  /*
+   * Implements the fetch method expected by the grid components.
+   * handles parsing the stream, searching, filtering and sorting of the data.
+   * search, sort and filters are provided by the grid control whenever they are
+   * changed by the user.
+   * DataSource is called automatically by the grid when user interacts with the component
+   * Grid does some batching of user actions and only calls fetch when needed.
+   * keys provided for sort and filters correspond to keys set in the markup
+   * when constructing the grid.
+   * @param {object} search search{key<string>} current search keyword
+   * @param {object} sort sort{key<string>, ascending<bool>} current sort key and direction
+   * @param {map} filters map{key<string>, values<Array>} Map of filter keys to currently selected filter values
+   * @return {Array<object>} Returns an array of filtered sorted results of the items.
+   */
+  fetch(search, sort, filters) {
+
+    var filteredSortedItems = this.allLogItems.
+    filter((item) => {
+      return vLogFilter(item, filters);
+    }).
+    filter((item) => {
+      return vLogSearch(item, search.keyword);
+    }).
+    sort((item1, item2) => {
+      return vLogSort(item1, item2, sort.key, sort.ascending);
+    });
+
+    // pause or resume the stream when auto-refresh filter changes
+    if (filters['autorefresh'] && this.stream.paused) {
+      this.stream.resume();
+    }
+    if (!filters['autorefresh'] && !this.stream.paused) {
+      this.stream.pause();
+    }
+
+    return filteredSortedItems;
+  }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/filterer.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/filterer.js
new file mode 100644
index 0000000..e40ebb0
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/filterer.js
@@ -0,0 +1,73 @@
+/*
+ * Returns whether the given vLogItem matches the map of filters.
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {map} filters Map of keys to selected filter values as defined
+ * when constructing the filters in the grid components.
+ * e.g. filters:{'date':'all', 'levels':['info','warning']}
+ * @return {boolean} Whether the item satisfies ALL of the given filters.
+ */
+export function vLogFilter(item, filters) {
+  if (Object.keys(filters).length === 0) {
+    return true;
+  }
+
+  for (var key in filters) {
+    var isMatch = applyFilter(item, key, filters[key]);
+    // we AND all the filters, short-circuit for early termination
+    if (!isMatch) {
+      return false;
+    }
+  }
+
+  // matches all filters
+  return true;
+};
+
+/*
+ * Returns whether the given vLogItem matches a single filter
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {string} key filter key e.g. 'date'
+ * @param {string} value filter value e.g. 'all'
+ * @return {boolean} Whether the item satisfies then the given filter key value pair
+ * @private
+ */
+function applyFilter(item, key, value) {
+  switch (key) {
+    case 'date':
+      return filterDate(item, value);
+    case 'level':
+      return filterLevel(item, value);
+    default:
+      // ignore unknown filters
+      return true;
+  }
+}
+
+/*
+ * Returns whether item's date satisfies the date filter
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {string} since One of 'all', '1' or '24'. for Anytime, past one hour, past 24 hours.
+ * @return {boolean} whether item's date satisfies the date filter
+ * @private
+ */
+function filterDate(item, since) {
+  if (since === 'all') {
+    return true;
+  } else {
+    var hours = parseInt(since);
+    var targetDate = new Date();
+    targetDate.setHours(targetDate.getHours() - hours);
+    return item.date > targetDate;
+  }
+}
+
+/*
+ * Returns whether item's level is one of the given values
+ * @param {Object} item A single veyron log item as defined by parser.item
+ * @param {Array<string>} levels Array of level values e.g. ['info','warning']
+ * @return {boolean} whether item's level is one of the given values
+ * @private
+ */
+function filterLevel(item, levels) {
+  return levels.indexOf(item.level) >= 0;
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
index 340b8af..d28e108 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
@@ -17,16 +17,15 @@
  *  file             The file name
  *  line             The line number
  *  msg              The user-supplied message
- * @param {string} vlogLine A single line of
- * by git status --short command
+ * @param {string} vlogLine A single line of veyron log
  * @return {parser.item} A parsed object containing log level, date, file,
  * line number, thread id and message.
  */
 export function parse(vlogLine) {
 
-  var validLogLineRegEx =  /^([IWEF])(\d{2})(\d{2})\s(\d{2}:\d{2}:\d{2}\.\d+)\s(\d+)\s(.*):(\d+)]\s+(.*)$/
+  var validLogLineRegEx = /^([IWEF])(\d{2})(\d{2})\s(\d{2}:\d{2}:\d{2}\.\d+)\s(\d+)\s(.*):(\d+)]\s+(.*)$/
   var logParts = vlogLine.match(validLogLineRegEx);
-  if(!logParts || logParts.length != 8+1) { // 8 parts + 1 whole match
+  if (!logParts || logParts.length != 8 + 1) { // 8 parts + 1 whole match
     throw new Error('Invalid vlog line format. ' + vlogLine +
       ' Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg.. pattern');
   }
@@ -44,7 +43,7 @@
   var year = now.getFullYear();
   var thisMonth = now.getMonth() + 1; // JS months are 0-11
   // Year flip edge case, if log month > this month, we assume log line is from previous year
-  if(parseInt(month) > thisMonth) {
+  if (parseInt(month) > thisMonth) {
     year--;
   }
 
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
index e7dc5af..28ff69c 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -8,8 +8,10 @@
  */
 import { View } from 'view';
 import { PipeViewer } from 'pipe-viewer';
+import { vLogDataSource } from './data-source';
+import { Logger } from 'logger'
 
-import { parse } from './parser';
+var log = new Logger('pipe-viewers/builtin/vlog');
 
 var streamUtil = require('event-stream');
 
@@ -23,17 +25,24 @@
 
     // split by new line
     stream = stream.pipe(streamUtil.split(/\r?\n/));
-
-    var logItems = [];
     var logView = document.createElement('p2b-plugin-vlog');
-    logView.logItems = logItems;
 
-    stream.on('data', (line) => {
-      // try to parse and display as much as we can.
-      try {
-        logItems.push( parse(line) );
-      } catch(e) {}
+    // create a new data source from the stream and set it.
+    logView.dataSource = new vLogDataSource(
+      stream,
+      function onNewItem() {
+        // also refresh the grid when new data comes in.
+        // grid component batches requests and refreshes UI on the next animation frame.
+        logView.refreshGrid();
+      },
+      function onError(err) {
+        log.debug(err);
+      });
 
+    // also refresh the grid when new data comes in.
+    // grid component batches requests and refreshes UI on the next animation frame.
+    stream.on('data', () => {
+      logView.refreshGrid();
     });
 
     return new View(logView);
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/searcher.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/searcher.js
new file mode 100644
index 0000000..35e346c
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/searcher.js
@@ -0,0 +1,14 @@
+export function vLogSearch(logItem, keyword) {
+  if (!keyword) {
+    return true;
+  }
+
+  // we do a contains for message, file and threadId fields only
+  if (logItem.message.indexOf(keyword) >= 0 ||
+    logItem.file.indexOf(keyword) >= 0 ||
+    logItem.threadId.toString().indexOf(keyword) >= 0) {
+    return true
+  }
+
+  return false;
+};
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
new file mode 100644
index 0000000..f2663f4
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/sorter.js
@@ -0,0 +1,14 @@
+export function vLogSort(logItem1, logItem2, key, ascending) {
+  var first = logItem1;
+  var second = logItem2;
+  if (!ascending) {
+    first = logItem2;
+    second = logItem1;
+  }
+
+  if (typeof first[key] === 'string') {
+    return first[key].localeCompare(second[key]);
+  } else {
+    return first[key] - second[key];
+  }
+};
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/runtime/init.js b/examples/pipetobrowser/browser/runtime/init.js
index 281a0d2..b5b1cc3 100644
--- a/examples/pipetobrowser/browser/runtime/init.js
+++ b/examples/pipetobrowser/browser/runtime/init.js
@@ -9,7 +9,8 @@
     '*': '*.js',
     'pipe-viewer': 'pipe-viewers/pipe-viewer.js',
     'pipe-viewer-delegation': 'pipe-viewers/pipe-viewer-delegation.js',
-    'view': 'libs/mvc/view.js'
+    'view': 'libs/mvc/view.js',
+    'logger': 'libs/logs/logger.js'
   };
 
   System.import('runtime/app').then(function(app) {
diff --git a/examples/pipetobrowser/browser/views/publish/view.js b/examples/pipetobrowser/browser/views/publish/view.js
index 3318d51..bc3843f 100644
--- a/examples/pipetobrowser/browser/views/publish/view.js
+++ b/examples/pipetobrowser/browser/views/publish/view.js
@@ -23,3 +23,56 @@
     });
   }
 }
+
+
+
+/*veyron/examples/pipetobrowser: Supporting search (in message, file path, threadid),
+filtering (log levels, date), sorting (all columns except message) for
+veyron log viewer. Also adding ability to pause/resume the log viewer.
+
+To support these features for veyron log viewer and other plugins,
+a new reusable data grid component was created (libs/ui-components/data-grid)
+data grid can host search, filters and supports sortable columns and custom cell renderer
+example usage:
+
+ <p2b-grid defaultSortKey="firstName"
+  defaultSortAscending
+  dataSource="{{ myContactsDataSource }}"
+  summary="Displays your contacts in a tabular format">
+
+  <!-- Search contacts-->
+  <p2b-grid-search label="Search Contacts"></p2b-grid-search>
+
+  <!-- Filter for circles -->
+  <p2b-grid-filter-select multiple key="circle" label="Circles">
+    <p2b-grid-filter-select-item checked label="Close Friends" value="close"></p2b-grid-filter-select-item>
+    <p2b-grid-filter-select-item label="Colleagues" value="far"></p2b-grid-filter-select-item>
+  </p2b-grid-filter-select>
+
+  <!-- Toggle to allow filtering by online mode-->
+  <p2b-grid-filter-toggle key="online" label="Show online only" checked></p2b-grid-filter-toggle>
+
+  <!-- Columns, sorting and cell templates -->
+  <p2b-grid-column sortable label="First Name" key="firstName" />
+    <template>{{ item.firstName }}</template>
+  </p2b-grid-column>
+
+  <p2b-grid-column sortable label="Last Name" key="lastName" />
+    <template>
+      <span style="text-transform:uppercase;">
+        {{ item.lastName }}
+      </span>
+    </template>
+  </p2b-grid-column>
+
+  <p2b-grid-column label="Circle" key="circle"/>
+    <template>
+      <img src="images\circls\{{ item.circle }}.jpg" alt="in {{ item.circle }} circle"><img>
+    </template>
+  </p2b-grid-column>
+
+</p2b-grid>
+Please see documentation in (libs/ui-components/data-grid/grid/component.html) for more details.
+
+still TODO: UI cleanup
+*/