Listbuilder

The listbuilder component contains functionality for displaying and executing different actions on large sets of data.

bb-listbuilder — Container for the entire listbuilder

bb-listbuilder-toolbar — Component for the listbuilder toolbar.

  • bb-listbuilder-on-search — Callback to be executed when the toolbar's search is invoked. It has the following arguments:
    • searchText — Text in the toolbar's search input.
  • bb-listbuilder-search-text(Optional.) Text that the user can provide to set the text in the toolbar's search input.
  • bb-listbuilder-search-placeholder(Optional.) Specifies the placeholder text for the search input.
  • bb-listbuilder-vertical-offset-id(Optional.) Id of the element that should float above the listbuilder toolbar when the window is scrolled.
  • bb-listbuilder-toolbar-fixed(Optional.) Set to true if the toolbar should not float when the window is scrolled. (Default: false)
  • bb-listbuilder-add-button(Optional.) Component for the add button in a listbuilder toolbar
    • bb-listbuilder-add-action(Optional.) Specifies a function to be called when the add button is clicked.
    • bb-listbuilder-add-label(Optional.) Specifies text for the add button title.
  • bb-listbuilder-filter(Optional.) Container for the filter button in the listbuilder toolbar. See the filter module for content that can be placed here.
  • bb-listbuilder-sort(Optional.) Container for the sort button in the listbuilder toolbar. See the sort module for content that can be placed here.
  • bb-listbuilder-filter-summary(Optional.) Container for the filter summary in the listbuilder toolbar. See the filter module for content that can be placed here.
  • bb-listbuilder-toolbar-secondary-actions(Optional.) Container for the secondary actions dropdown in the listbuilder toolbar. Also contains the column picker component when using a grid in listbuilder.
    • bb-listbuilder-secondary-actions — Component for the secondary actions dropdown.
      • bb-listbuilder-secondary-actions-append-to-body(Optional.) Specifies whether the dropdown should be appended to the document body. (Default: false)
      • bb-listbuilder-secondary-action — Component for an individual action in the secondary actions dropdown.
        • bb-listbuilder-secondary-action-click — Specifies a function to be called when users click the action.
        • bb-listbuilder-secondary-action-disabled(Optional.) Specifies whether the action is disabled. (Default: false)
      • bb-listbuilder-column-picker — Component for choosing columns when using a grid within listbuilder.
        • bb-listbuilder-column-picker-columns — An array of columns that should be displayed in the column picker. See grid columns for column data options.
        • bb-listbuilder-column-picker-selected-column-ids — An array of unique identifiers that indicated the current visible columns.
        • bb-listbuilder-column-picker-selected-column-ids-changed — A function to be called when new columns are selected. It has the following arguments:
          • selectedColumnIds — The new array of unique identifiers that indicate the current visible columns.
        • bb-listbuilder-column-picker-help-key(Optional.) Sets the help key for the column picker modal.
        • bb-listbuilder-column-picker-subset-label(Optional.) Specifies a label for a checkbox to include or exclude a subset of the columns.
        • bb-listbuilder-column-picker-subset-property(Optional.) Specifies a property name of the column to be used to filter by subset. The property will be set to true if it is a member of the subset.
        • bb-listbuilder-column-picker-subset-exclude(Optional.) When set to true, instructs the column picker to exclude columns where the bbListbuilderColumnPickerSubsetProperty is set to true when the subset checkbox is selected. When set to false, the column picker includes the columns where the bbListbuilderColumnPickerSubsetProperty is set to true when the subset checkbox is selected.
        • bb-listbuilder-column-picker-only-selected(Optional.) When set to true, instructs the column picker to include a checkbox that hides unselected items.
  • bb-listbuilder-toolbar-multiselect(Optional.) Container for the multiselect area in the listbuilder toolbar.
    • bb-listbuilder-multiselect — Component for handling multiselect functionality in the listbuilder. When using multiselect with cards, bb-listbuilder-card-id, bb-card-selectable, and bb-card-selected should all be set. When using multiselect with repeater items, bb-listbuilder-repeater-item-id, bb-repeater-item-selectable, and bb-repeater-item-selected should all be set. When using multiselect with grids, set the bb-grid-options object's multiselect property to true and set the bb-grid-multiselect-selected-ids property to the same object being updated by the bb-listbuilder-multiselect-items-changed callback. Display actions for selected items using the summary action bar module.
      • bb-listbuilder-multiselect-items-changed — Callback to be executed when users select or deselect items in the listbuilder. It has the following arguments:
        • selectedIds — The array of unique identifiers that have been selected.
        • allSelected — Set totrue if the multiselect items were changed by selecting all items. Set to false otherwise.
      • bb-listbuilder-multiselect-selected-ids(Optional.) Specifies the array of selected unique identifiers.
      • bb-listbuilder-multiselect-available-items(Optional.) Specifies the array of items in the listbuilder. When specified, the selected property of the item will be updated when bb-listbuilder-multiselect-selected-ids are changed, when the select all button is clicked, and when the clear all button is clicked.
      • bb-listbuilder-multiselect-item-selected-property(Optional.) Specifies the name for the selected property of items in the bb-listbuilder-multiselect-available-items array. (Default: selected)
      • bb-listbuilder-multiselect-item-selected-id-property(Optional.) Specifies the name for the id property of items in the bb-listbuilder-multiselect-available-items array. (Default: id)
      • bb-listbuilder-on-show-only-selected — Callback to be executed when users select or deselect the 'Show only selected' checkbox in the multiselect area. It has the following arguments:
        • showOnlySelected — Set to true if the list should contain only selected items. Set to false otherwise.
      • bb-listbuilder-show-only-selected(Optional.) Specifies the value of the 'Show only selected' checkbox. (Default: false)
      • bb-listbuilder-multiselect-select-all — Component for a 'Select all' button.
        • bb-listbuilder-multiselect-on-select-all(Optional.) Callback to be executed when the 'Select all' button is clicked. The function should return an array of unique identifiers to be selected. When not specified, and when the bb-listbuilder-multiselect-available-items array is specified, all items in the array will be selected.
      • bb-listbuilder-multiselect-clear-all — Component for a 'Clear all' button.
        • bb-listbuilder-multiselect-on-clear-all(Optional.) Callback to be executed when the 'Clear all' button is clicked. The function should return an array of unique identifiers to be deselected. When not specified, and when the bb-listbuilder-multiselect-available-items array is specified, all items in the array will be deselected.

bb-listbuilder-content — Component for the listbuilder content.

  • bb-listbuilder-content-active-view(Optional.) Specifies the name of the active view to display as the listbuilder content. Values can be card, repeater, or the name of a custom view.
  • bb-listbuilder-content-view-changed(Optional.) Specifies a function to be called when the listbuilder view is changed. It has the following arguments:
    • newView — The name of the new active view.
  • bb-listbuilder-cards — Component that contains a card view for the listbuilder.
    • bb-listbuilder-card — Component that contains an individual card in the listbuilder.
      • bb-listbuilder-card-id(Optional.) Specifies a unique identifier for the card. Required when using multiselect with cards.
  • bb-listbuilder-repeater — Component that contains a repeater view for the listbuilder.
    • bb-listbuilder-repeater-item — Attribute that can be placed on an individual repeater item in the listbuilder to highlight last search text on load.
    • bb-listbuilder-repeater-item-id(Optional.) Specifies a unique identifier for the repeater item. Required when using multiselect with repeater items.
  • bb-listbuilder-grid — Component that contains a grid view for the listbuilder. You can add a column picker in the listbuilder secondary actions by using the bb-listbuilder-column-picker component. When placing a bb-grid within this component, the following attributes can be used (see grid documentation for information about grid options).
    • bb-grid-options
      • columns
      • data
      • getContextMenuItems
      • multiselect
      • resources
      • selectedColumnIds
      • sortOptions
    • bb-grid-multiselect-selected-ids
  • bb-listbuilder-content-custom — Component that contains a custom view in the listbuilder.
    • bb-listbuilder-content-custom-view-name — Specifies a unique name for the custom view.
    • bb-listbuilder-content-custom-view-switcher-class — Specifies a css class for the icon for this custom view to be used in the view switcher.
    • bb-listbuilder-content-custom-view-switcher-label — Specifies text for the title attribute for this custom view to be used in the view switcher.
    • bb-listbuilder-content-custom-highlight-class — Specifies a css class where search text will be highlighted in the custom view.
    • bb-listbuilder-content-custom-item — Attribute that can be placed on an individual custom item in the listbuilder to highlight last search text on load.
    • bb-listbuilder-content-custom-item-id(Optional.) Specifies a unique identifier for the custom item. Required when using multiselect with custom items.

bb-listbuilder-footer — Component for the listbuilder footer, which contains the ability to load data using infinite scroll.

  • bb-listbuilder-show-load-more — When true, indicates that more data is ready to be loaded by the listbuilder.
  • bb-listbuilder-on-load-more — Callback to be executed when the listbuilder's load more functionality is invoked.

Demo

Add Stuff {{item.label}} Save list {{item.label}} {{item.name}}
Membership dues paid
Membership dues not paid
Occupation: {{item.occupation}}
Joined on: {{item.joinDate | date}}
Action 1 Action 2
{{item.name}}
Membership dues paid
Membership dues not paid
Occupation: {{item.occupation}}
Joined on: {{item.joinDate | date}}
Action 1 Action 2
{{item.name}}
Membership dues paid
Membership dues not paid
Occupation: {{item.occupation}}
Joined on: {{item.joinDate | date}}
Action 1 Action 2
Pay membership dues ({{listCtrl.payMembershipSelections.length}}) Secondary action ({{listCtrl.secondarySelections.length}})

Markup


<style>
    .bb-custom-content-item {
        border-bottom: 1px solid green;
        padding: 20px;
    }

    
</style>
<div ng-controller="ListbuilderTestController as listCtrl">
    <button type="button" class="btn btn-primary" style="margin-bottom: 10px"
        ng-click="listCtrl.activeView = 'repeater'">
        Change to repeater view
    </button>
  <bb-listbuilder>
    <bb-listbuilder-toolbar 
        bb-listbuilder-on-search="listCtrl.onSearch(searchText)"
        bb-listbuilder-search-text="listCtrl.searchText">
      <bb-listbuilder-add bb-listbuilder-add-action="listCtrl.onAddClick()">
        Add Stuff
      </bb-listbuilder-add>
      <bb-listbuilder-filter> 
        <bb-filter-button bb-filter-button-on-click="listCtrl.onFilterClick()">  
        </bb-filter-button>
      </bb-listbuilder-filter>
      <bb-listbuilder-sort>
        <bb-sort bb-sort-append-to-body>
            <bb-sort-item 
                ng-repeat="item in listCtrl.sortOptions" 
                bb-sort-item-active="listCtrl.initialState === item.id"
                bb-sort-item-select="listCtrl.sortItems(item)">
            {{item.label}}
            </bb-sort-item>
        </bb-sort>
      </bb-listbuilder-sort>
      <bb-listbuilder-toolbar-secondary-actions>
          <bb-listbuilder-secondary-actions bb-listbuilder-secondary-actions-append-to-body="true">
              <bb-listbuilder-column-picker
                bb-listbuilder-column-picker-help-key="listCtrl.gridOptions.columnPickerHelpKey"
                bb-listbuilder-column-picker-selected-column-ids="listCtrl.gridOptions.selectedColumnIds"
                bb-listbuilder-column-picker-columns="listCtrl.gridOptions.columns"
                bb-listbuilder-column-picker-selected-column-ids-changed="listCtrl.selectedColumnIdsChanged(selectedColumnIds)">
              </bb-listbuilder-column-picker>
              <bb-listbuilder-secondary-action 
                bb-listbuilder-secondary-action-click="listCtrl.saveAction()">
                  Save list
              </bb-listbuilder-secondary-action>
          </bb-listbuilder-secondary-actions>
      </bb-listbuilder-toolbar-secondary-actions>
      <bb-listbuilder-filter-summary>
        <bb-filter-summary ng-show="listCtrl.appliedFilters.length > 0">
            <bb-filter-summary-item
                ng-repeat="item in listCtrl.appliedFilters"
                bb-filter-summary-item-on-click="listCtrl.openFilters()"
                bb-filter-summary-item-on-dismiss="listCtrl.onDismissFilter($index)"
                >
                {{item.label}}
            </bb-filter-summary-item>
        </bb-filter-summary>
      </bb-listbuilder-filter-summary>
      <bb-listbuilder-toolbar-multiselect>
          <bb-listbuilder-multiselect
            ng-show="listCtrl.multiselectAvailable()"
            bb-listbuilder-on-show-only-selected="listCtrl.toggleOnlySelected(showOnlySelected)"
            bb-listbuilder-multiselect-items-changed="listCtrl.itemsChanged(selectedIds, allSelected)"
            bb-listbuilder-multiselect-selected-ids="listCtrl.selectedIds"
            bb-listbuilder-multiselect-available-items="listCtrl.gridOptions.data">
            <bb-listbuilder-multiselect-select-all>
            </bb-listbuilder-multiselect-select-all>
            <bb-listbuilder-multiselect-clear-all>
            </bb-listbuilder-multiselect-clear-all>
          </bb-listbuilder-multiselect>

      </bb-listbuilder-toolbar-multiselect>
    </bb-listbuilder-toolbar>
    <bb-listbuilder-content 
        bb-listbuilder-content-active-view="{{listCtrl.activeView}}"
        bb-listbuilder-content-view-changed="listCtrl.viewChanged(newView)"
        >
      <bb-listbuilder-cards>
        <bb-listbuilder-card bb-listbuilder-card-id="item.id" ng-repeat="item in listCtrl.gridOptions.data">
            <bb-card bb-card-selectable="true" bb-card-selected="item.selected">
                <bb-card-title>
                {{item.name}}
                </bb-card-title>
                <bb-card-content>
                    <div ng-if="item.duesPaid" style="margin-bottom: 10px; margin-top: 10px;">
                        <span class="label label-success">Membership dues paid</span>
                    </div>
                    <div ng-if="!item.duesPaid" style="margin-bottom: 10px; margin-top: 10px;">
                        <span class="label label-danger">Membership dues not paid</span>
                    </div>
                    <div style="margin-bottom: 10px">
                        Occupation: {{item.occupation}}
                    </div>
                    <div>
                        Joined on: {{item.joinDate | date}}
                    </div>
                    
                </bb-card-content>
                <bb-card-actions>
                    <bb-context-menu>
                        <bb-context-menu-item>Action 1</bb-context-menu-item>
                        <bb-context-menu-item>Action 2</bb-context-menu-item>
                    </bb-context-menu>
                </bb-card-actions>
            </bb-card>
        </bb-listbuilder-card>
      </bb-listbuilder-cards>
      <bb-listbuilder-repeater>
        <bb-repeater>
            <bb-repeater-item 
                bb-listbuilder-repeater-item 
                bb-listbuilder-repeater-item-id="item.id" 
                ng-repeat="item in listCtrl.gridOptions.data"
                bb-repeater-item-selectable="true"
                bb-repeater-item-selected="item.selected"
                >
                <bb-repeater-item-title>
                {{item.name}}
                </bb-repeater-item-title>
                <bb-repeater-item-content>
                <div ng-if="item.duesPaid" style="margin-bottom: 10px; margin-top: 10px;">
                    <span class="label label-success">Membership dues paid</span>
                </div>
                <div ng-if="!item.duesPaid" style="margin-bottom: 10px; margin-top: 10px;">
                    <span class="label label-danger">Membership dues not paid</span>
                </div>
                <div style="margin-bottom: 10px">
                    Occupation: {{item.occupation}}
                </div>
                <div>
                    Joined on: {{item.joinDate | date}}
                </div>
                </bb-repeater-item-content>
                <bb-repeater-item-context-menu>
                    <bb-context-menu>
                        <bb-context-menu-item>Action 1</bb-context-menu-item>
                        <bb-context-menu-item>Action 2</bb-context-menu-item>
                    </bb-context-menu>
                </bb-repeater-item-context-menu>
            </bb-repeater-item>
        </bb-repeater>
      </bb-listbuilder-repeater>
      <bb-listbuilder-grid>
        <bb-grid
            bb-grid-options="listCtrl.gridOptions"
            bb-grid-multiselect-selected-ids="listCtrl.selectedIds">
        </bb-grid>
      </bb-listbuilder-grid>
      <bb-listbuilder-content-custom 
        bb-listbuilder-content-custom-view-name="custom-1" 
        bb-listbuilder-content-custom-view-switcher-class="fa-pied-piper"
        bb-listbuilder-content-custom-highlight-class="bb-custom-content-item"
        bb-listbuilder-content-custom-view-switcher-label="Switch to custom">

        <div class="bb-custom-content">
            <div class="bb-custom-content-item" bb-listbuilder-content-custom-item ng-repeat="item in listCtrl.gridOptions.data">
                <div>
                {{item.name}}
                </div>
                <div ng-if="item.duesPaid" style="margin-bottom: 10px; margin-top: 10px;">
                    <span class="label label-success">Membership dues paid</span>
                </div>
                <div ng-if="!item.duesPaid" style="margin-bottom: 10px; margin-top: 10px;">
                    <span class="label label-danger">Membership dues not paid</span>
                </div>
                <div style="margin-bottom: 10px">
                    Occupation: {{item.occupation}}
                </div>
                <div>
                    Joined on: {{item.joinDate | date}}
                </div>
                
                <div>
                    <bb-context-menu>
                        <bb-context-menu-item>Action 1</bb-context-menu-item>
                        <bb-context-menu-item>Action 2</bb-context-menu-item>
                    </bb-context-menu>
                </div>
            </div>
        </div> 
      </bb-listbuilder-content-custom>
      
    </bb-listbuilder-content>
    <bb-listbuilder-footer
        bb-listbuilder-on-load-more="listCtrl.onLoadMore()"
        bb-listbuilder-show-load-more="listCtrl.hasMoreData">
    </bb-listbuilder-footer>
  </bb-listbuilder>
  <bb-summary-actionbar 
    ng-show="listCtrl.actionsShown()">
      <bb-summary-actionbar-actions>
          <bb-summary-actionbar-primary
            bb-summary-action-disabled="listCtrl.payMembershipSelections.length < 1" 
            bb-summary-action-click="listCtrl.payMembership(listCtrl.payMembershipSelections)">
              Pay membership dues ({{listCtrl.payMembershipSelections.length}})
          </bb-summary-actionbar-primary>
          <bb-summary-actionbar-secondary-actions>
              <bb-summary-actionbar-secondary
                bb-summary-action-disabled="listCtrl.secondarySelections.length < 1" 
                bb-summary-action-click="listCtrl.secondaryAction(listCtrl.secondarySelections)">
                  Secondary action ({{listCtrl.secondarySelections.length}})
              </bb-summary-actionbar-secondary>
          </bb-summary-actionbar-secondary-actions>
      </bb-summary-actionbar-actions>
  </bb-summary-actionbar>
</div>

</script>
<script type="text/ng-template" id="demo/listbuilder/filters.html">
    <bb-modal>
        <div class="modal-form">
            <bb-modal-header>Food preferences</bb-modal-header>
            <div bb-modal-body>
                <form>
                    <label style="margin-top: 15px;">
                        <input type="checkbox" bb-check ng-model="filterCtrl.filters.tenYears" />
                        Show only members with 10 years experience
                    </label>
                    <label style="margin-top: 15px;">
                        <input type="checkbox" bb-check ng-model="filterCtrl.filters.onlyUnpaid" />
                        Show only members with unpaid dues
                    </label>
                </form>
            </div>
            <bb-filter-modal-footer
                bb-filter-modal-apply="filterCtrl.applyFilters()"
                bb-filter-modal-clear="filterCtrl.clearAllFilters()">
            </bb-filter-modal-footer>
        </div>
    </bb-modal>
</script>

JavaScript

/*global angular */
(function () {
    'use strict';

    function ListbuilderFilterController($uibModalInstance, existingFilters) {
        var self = this;

        function clearAllFilters() {
            self.filters = {
                tenYears: false,
                onlyUnpaid: false
            };
        }
        
        function transformFiltersToArray(filters) {
            var result = [];

            if (filters.tenYears) {
                result.push({name: 'tenYears', value: true, label: 'members with ten years'});
            }

            if (filters.onlyUnpaid) {
                result.push({name: 'onlyUnpaid', value: true, label: 'unpaid members'});
            }

            return result;
        }

        function transformArrayToFilters(array) {
            var i,
                filters = {};

            for (i = 0; i < array.length; i++) {
                filters[array[i].name] = array[i].value;
            }

            return filters;
        }

        function applyFilters() {
            var result = transformFiltersToArray(self.filters);
            $uibModalInstance.close(result);
        }


        if (!existingFilters) {
            clearAllFilters();
        } else {
            self.filters = transformArrayToFilters(existingFilters);
        }

        self.clearAllFilters = clearAllFilters;
        self.applyFilters = applyFilters;

    }

    ListbuilderFilterController.$inject = ['$uibModalInstance', 'existingFilters'];
    
    function ListbuilderTestController($timeout, bbModal) {
        var self = this,
            sortProperty,
            sortDescending,
            maxRecordsShown = 0,
            nextSkip = 0,
            nextTop = 6,
            dataSet = [
                {
                    id: 0,
                    name: 'Tim Duggy',
                    occupation: 'Software Engineer',
                    joinDate: new Date('1/1/2016'),
                    duesPaid: false
                },
                {
                    id: 1,
                    name: 'Janet Smith',
                    occupation: 'Accountant',
                    joinDate: new Date('2/23/2006'),
                    duesPaid: true
                },
                {
                    id: 2,
                    name: 'James Wheeler',
                    occupation: 'Marketing Director',
                    joinDate: new Date('4/12/2013'),
                    duesPaid: true
                },
                {
                    id: 3,
                    name: 'Jarod Douglas',
                    occupation: 'Real Estate Agent',
                    joinDate: new Date('12/12/2012'),
                    duesPaid: false
                },
                {
                    id: 4,
                    name: 'Sarah Silver',
                    occupation: 'Software Engineer',
                    joinDate: new Date('11/7/1999'),
                    duesPaid: true
                },
                {
                    id: 5,
                    name: 'Tina Maller',
                    occupation: 'Sales',
                    joinDate: new Date('3/5/2016'),
                    duesPaid: true
                },
                {
                    id: 6,
                    name: 'Megan Johnson',
                    occupation: 'Manager',
                    joinDate: new Date('1/1/2013'),
                    duesPaid: true
                },
                {
                    id: 7,
                    name: 'Reed Sawyer',
                    occupation: 'Accountant',
                    joinDate: new Date('3/9/2006'),
                    duesPaid: true
                },
                {
                    id: 8, 
                    name: 'James Dunn',
                    occupation: 'Auto Technician',
                    joinDate: new Date('6/12/2015'),
                    duesPaid: false
                },
                {
                    id: 9,
                    name: 'Douglas Herman',
                    occupation: 'Lawyer',
                    joinDate: new Date('3/1/2012'),
                    duesPaid: true
                },
                {
                    id: 10,
                    name: 'Helen Walker',
                    occupation: 'Software Consultant',
                    joinDate: new Date('4/7/2007'),
                    duesPaid: true
                },
                {
                    id: 11,
                    name: 'Christopher Lewen',
                    occupation: 'Sales',
                    joinDate: new Date('5/26/2016'),
                    duesPaid: true
                }
            ],
        sortOptions = [
            {
                id: 1,
                label: 'Name (A - Z)',
                name: 'name',
                descending: false
            },
            {
                id: 2,
                label: 'Name (Z - A)',
                name: 'name',
                descending: true
            },
            {
                id: 3,
                label: 'Date joined (newest first)',
                name: 'joinDate',
                descending: true
            },
            {
                id: 4,
                label: 'Date joined (oldest first)',
                name: 'joinDate',
                descending: false
            },
            {
                id: 5,
                label: 'Occupation (A - Z)',
                name: 'occupation',
                descending: false
            },
            {
                id: 6,
                label: 'Occupation (Z - A)',
                name: 'occupation',
                descending: true
            }
        ],
        gridOptions = {
                columns: [
                    {
                        caption: 'Name',
                        jsonmap: 'name',
                        id: 1,
                        name: 'name',
                        category: 'My category',
                        description: 'Column description',
                        width_all: 300,
                        width_xs: 100
                    },
                    {
                        caption: 'Occupation',
                        jsonmap: 'occupation',
                        id: 2,
                        name: 'occupation',
                        width_all: 300,
                        width_xs: 100
                    },
                    {
                        caption: 'Dues paid',
                        jsonmap: 'duesPaid',
                        id: 3,
                        name: 'duesPaid',
                        width_all: 300,
                        template_url: 'bbGrid/samples/mycolumn.html'
                    },
                    {
                        caption: 'Date',
                        jsonmap: 'joinDate',
                        id: 4,
                        name: 'joinDate',
                        width_all: 200,
                        template_url: 'bbGrid/samples/date.html'
                    }
                ],
                getContextMenuItems: function () {
                    return [
                        {
                            id: 'menu',
                            title: 'Option1',
                            cmd: function () {
                                alert('Context menu option chosen!');
                                return false;
                            }
                        }
                    ];
                },
                multiselect: true,
                selectedColumnIds: [1, 2, 3, 4],
                columnPickerHelpKey: 'bb-security-users.html'
            };

        function getData(top, skip) {
            var i,
                newData = [];
            
            for (i = 0; i < top && dataSet.length > skip + i; i++) {
                newData.push(dataSet[skip + i]);
            }

            return newData;
        }

        function searchArray(searchText, array) {
            var filteredData;
            if (searchText) {
                filteredData = array.filter(function (item) {
                    var property;
                    for (property in item) {
                        if (item.hasOwnProperty(property) && (property === 'name' || property === 'occupation')) {
                            if (item[property].indexOf(searchText) > -1) {
                                return true;
                            }
                        }
                    }
                    return false;
                });
                return filteredData;
            }
            return array;
            
        }

        function filter(array, filters) {
            var i,
                item,
                newData = angular.copy(array);
            if (angular.isDefined(filters) &&  filters.length > 0) {
                for (i = 0; i < filters.length; i++) {
                    item = filters[i];
                    if (item.name === 'tenYears') {
                        newData = newData.filter(function (filterObj) {
                            return new Date().getFullYear() - filterObj.joinDate.getFullYear() >= 10;
                        });
                    }
                    if (item.name === 'onlyUnpaid') {
                        newData = newData.filter(function (filterObj) {
                            return filterObj.duesPaid === false;
                        });
                    }
                }
                return newData;
            } else {
                return array;
            }
        }

        function sortArray(sortProperty, sortDescending, array) {    

            if (sortProperty) {

                self.gridOptions.sortOptions = {
                    column: sortProperty,
                    descending: sortDescending
                };

                return array.sort(function (a, b) {
                    var descending = sortDescending ? -1 : 1;

                    if (a[sortProperty] > b[sortProperty]) {
                        return (descending);
                    } else if (a[sortProperty] < b[sortProperty]) {
                        return (-1 * descending);
                    } else {
                        return 0;
                    }
                });
            } else {
                return array;
            }
            
        }

        function getItemById(id, array) {
            var i,
                length = array.length;

            for (i = 0; i < length; i++) {
                if (array[i].id === id) {
                    return array[i];
                }
            }
        }

        function applyMultiselectOptions(array, onlyShowSelected, selectedIds) {
            var selectedLength = selectedIds.length,
                arrayLength = array.length,
                item,
                newData = [],
                i;

            /* 
                if selectAll is active, set each data item as selected and add new items to selected items
            */
            if (self.selectAllActive) {
                for (i = 0; i < arrayLength; i++) {
                    if (selectedIds.indexOf(array[i].id) === -1) {
                        selectedIds.push(array[i].id);
                    }
                }
            } else if (onlyShowSelected) { /* Filter if showOnly selected is set */
                for (i = 0; i < selectedLength; i++) {
                    item = getItemById(selectedIds[i], array);
                    if (item) {
                        newData.push(item);
                    } 
                }
                return newData;
            }

            return array;
        }

        function getAllFilteredData(searchText, filters, sortProperty, sortDescending, showOnlySelected, selectedIds) {
            var filteredData,
                baseData = angular.copy(dataSet);
            filteredData = searchArray(searchText, baseData);
            filteredData = filter(filteredData, filters);
            filteredData = applyMultiselectOptions(filteredData, showOnlySelected, selectedIds);
            filteredData = sortArray(sortProperty, sortDescending, filteredData);

            return filteredData;
        }

        function applySearchFilterSort(searchText, filters, sortProperty, sortDescending, maxData, showOnlySelected, selectedIds) {
            var filteredData = getAllFilteredData(searchText, filters, sortProperty, sortDescending, showOnlySelected, selectedIds);

            self.gridOptions.data = filteredData.slice(0, maxData);
            self.hasMoreData = filteredData.length > self.gridOptions.data.length;
            nextSkip = self.gridOptions.data.length;

        }

        function applyAllAndUpdateSelectOptions(searchText, filters, sortProperty, sortDescending, maxData, showOnlySelected, selectedIds) {
            applySearchFilterSort(searchText, filters, sortProperty, sortDescending, maxData, showOnlySelected, selectedIds);
            itemsChanged(selectedIds, self.selectAllActive, true);
        }

        function onSearch(searchText) {
            return $timeout(function () {
                var searchedData
                self.searchText = searchText;
                applyAllAndUpdateSelectOptions(self.searchText, self.appliedFilters, sortProperty, sortDescending, maxRecordsShown, self.showOnlySelected, self.selectedIds);
                
            });
        }

        function onDismissFilter(index) {
            self.appliedFilters.splice(index, 1);
            applyAllAndUpdateSelectOptions(self.searchText, self.appliedFilters, sortProperty, sortDescending, maxRecordsShown, self.showOnlySelected, self.selectedIds);
        }

        function sortItems(item) {
            sortProperty = item.name;
            sortDescending = item.descending;
            applyAllAndUpdateSelectOptions(self.searchText, self.appliedFilters, sortProperty, sortDescending, maxRecordsShown, self.showOnlySelected, self.selectedIds);
        }

        function onFilterClick() {
            bbModal.open({
                controller: 'ListbuilderFilterController as filterCtrl',
                templateUrl: 'demo/listbuilder/filters.html',
                resolve: {
                    existingFilters: function () {
                        return angular.copy(self.appliedFilters);
                    }
                }
            }).result
                .then(function (result) {
                    self.appliedFilters = angular.copy(result);
                    applyAllAndUpdateSelectOptions(self.searchText, self.appliedFilters, sortProperty, sortDescending, maxRecordsShown, self.showOnlySelected, self.selectedIds);
                });
        }

        function getNextData(top, skip, searchText, appliedFilters, sortProperty, sortDescending, maxRecordsShown, showOnlySelected, selectedIds) {
            var allFilteredData = getAllFilteredData(searchText, appliedFilters, sortProperty, sortDescending, showOnlySelected, selectedIds),
                newDataLength,
                nextFilteredData;

            nextFilteredData = allFilteredData.slice(skip, top + skip);
            newDataLength = self.gridOptions.data.length + nextFilteredData.length
            self.hasMoreData = allFilteredData.length > newDataLength;
            nextSkip = newDataLength;
            return nextFilteredData;
        }

        function loadData() {
            var newData = getNextData(
                                nextTop, 
                                nextSkip, 
                                self.searchText, 
                                self.appliedFilters, 
                                sortProperty, 
                                sortDescending, 
                                maxRecordsShown, 
                                self.showOnlySelected, 
                                self.selectedIds);

            self.gridOptions.data = self.gridOptions.data.concat(newData);
            if (maxRecordsShown < self.gridOptions.data.length) {
                maxRecordsShown = self.gridOptions.data.length;
            }

        }

        function onLoadMore() {
            return $timeout(function () {
                loadData();
            }, 4000);
        }

        function onAddClick() {
            alert('Add button clicked');
        }

        function viewChanged(newView) {
            self.activeView = newView;
        }

        function itemsChanged(selectedIds, allSelected, shouldNotApplyFilters) {
            var i,
                item; 

            self.selectedIds = selectedIds;
            self.selectAllActive = allSelected;

            if (self.showOnlySelected && !shouldNotApplyFilters) {
                applySearchFilterSort(self.searchText, self.appliedFilters, sortProperty, sortDescending, maxRecordsShown, self.showOnlySelected, selectedIds);
            }

            self.payMembershipSelections = [];
            self.secondarySelections = [];
            for (i = 0; i < selectedIds.length; i++) {
                item = getItemById(selectedIds[i], self.gridOptions.data);
                if (item) {
                    if (!item.duesPaid) {
                        self.payMembershipSelections.push(selectedIds[i]);
                    }
                    self.secondarySelections.push(selectedIds[i]);
                }
                
            }
        }

        function payMembership(selections) {
            var i,
                item;
            for (i = 0; i < selections.length; i++) {
                //simulate client side duesPaid changing
                item = getItemById(selections[i], self.gridOptions.data); 
                item.duesPaid = true;

                //simulate server side duesPaid changing
                item = getItemById(selections[i], dataSet);
                item.duesPaid = true;
            }
            self.payMembershipSelections = [];

            //Refresh data for bbGrid
            self.gridOptions.data = angular.copy(self.gridOptions.data);
        }

        function secondaryAction(selections) {
            console.log('secondary action taken with ', selections);
        }

        function toggleOnlySelected(showOnlySelected) {
            self.showOnlySelected = showOnlySelected;
            // reload data with filter options (including only show selected)

            applyAllAndUpdateSelectOptions(self.searchText, self.appliedFilters, sortProperty, sortDescending, maxRecordsShown, showOnlySelected, self.selectedIds);
            
        }

        function multiselectAvailable() {
            return self.activeView === 'repeater' || self.activeView === 'card' || self.activeView === 'grid';
        }

        function actionsShown() {
            return (self.payMembershipSelections.length > 0 || self.secondarySelections.length > 0) && multiselectAvailable();
        }

        function saveAction() {
            alert('List has been saved!');
        }

        function selectedColumnIdsChanged(selectedColumnIds) {
            self.gridOptions.selectedColumnIds = selectedColumnIds;
        }

        self.saveAction = saveAction;

        self.multiselectAvailable = multiselectAvailable;

        self.actionsShown = actionsShown;

        self.toggleOnlySelected = toggleOnlySelected;

        self.secondarySelections = [];
        self.payMembershipSelections = [];

        self.selectedIds = [];

        self.payMembership = payMembership;
        self.secondaryAction = secondaryAction;
        self.itemsChanged = itemsChanged;
        self.onFilterClick = onFilterClick;

        self.onSearch = onSearch;
        self.onLoadMore = onLoadMore;
        self.onAddClick = onAddClick;
        self.sortItems = sortItems;
        self.viewChanged = viewChanged;
        self.hasMoreData = true;
        self.onDismissFilter = onDismissFilter;
        self.selectedColumnIdsChanged = selectedColumnIdsChanged;
        self.gridOptions = gridOptions;
        self.gridOptions.data = [];
        self.activeView = 'card';
        loadData();

        self.sortOptions = sortOptions;

        self.initialState = self.sortOptions[4].id;

    }

    ListbuilderTestController.$inject = ['$timeout', 'bbModal'];

    function RunTemplateCache($templateCache) {
        $templateCache.put('bbGrid/samples/date.html', '<div>{{data | date: \'medium\'}}</div>');

        $templateCache.put('bbGrid/samples/mycolumn.html',
            '<div>' +
            '<div ng-if="data" style="margin-bottom: 10px; margin-top: 10px;">' +
                ' <span class="label label-success">Membership dues paid</span>' +
            '</div>' +
            '<div ng-if="!data" style="margin-bottom: 10px; margin-top: 10px;">' +
                '<span class="label label-danger">Membership dues not paid</span>' +
            '</div>' +
            '</div>');
    }
    RunTemplateCache.$inject = ['$templateCache'];

    angular
        .module('stache')
        .controller('ListbuilderTestController', ListbuilderTestController)
        .controller('ListbuilderFilterController', ListbuilderFilterController)
        .run(RunTemplateCache);
})();