<commit_before>    //
    // Grid module for lux
    //
    //  Dependencies:
    //
    //      - use $modal service from angular-strap library
    //
    //
    function dateSorting(column) {

        column.sortingAlgorithm = function(a, b) {
            var dt1 = new Date(a).getTime(),
                dt2 = new Date(b).getTime();
            return dt1 === dt2 ? 0 : (dt1 < dt2 ? -1 : 1);
        };
    }

    angular.module('lux.grid', ['lux.services', 'templates-grid', 'ngTouch', 'ui.grid',
                                'ui.grid.pagination', 'ui.grid.selection', 'ui.grid.autoResize'])
        //
        .constant('gridDefaults', {
            //
            enableFiltering: true,
            enableRowHeaderSelection: false,
            useExternalPagination: true,
            useExternalSorting: true,
            useExternalFiltering: true,
            // Scrollbar display: 0 - never, 1 - always, 2 - when needed
            enableHorizontalScrollbar: 0,
            enableVerticalScrollbar: 0,
            //
            rowHeight: 30,
            minGridHeight: 250,
            offsetGridHeight: 102,
            //
            // request delay in ms
            requestDelay: 100,
            //
            paginationOptions: {
                sizes: [25, 50, 100]
            },
            //
            gridState: {
                page: 1,
                limit: 25,
                offset: 0
            },
            gridFilters: {},
            //
            showMenu: true,
            gridMenu: {
                'create': {
                    title: 'Add',
                    icon: 'fa fa-plus'
                },
                'delete': {
                    title: 'Delete',
                    icon: 'fa fa-trash'
                },
                'columnsVisibility': {
                    title: 'Columns visibility',
                    icon: 'fa fa-eye'
                }
            },
            modal: {
                delete: {
                    templates: {
                        'empty': 'grid/templates/modal.empty.tpl.html',
                        'delete': 'grid/templates/modal.delete.tpl.html',
                    },
                    messages: {
                        'info': 'Are you sure you want to delete',
                        'danger': 'DANGER - THIS CANNOT BE UNDONE',
                        'success': 'Successfully deleted',
                        'error': 'Error while deleting ',
                        'empty': 'Please, select some',
                    }
                },
                columnsVisibility: {
                    templates: {
                        'default': 'grid/templates/modal.columns.tpl.html',
                    },
                    messages: {
                        'info': 'Click button with column name to toggle visibility'
                    }
                }
            },
            // dictionary of call-backs for columns types
            // The function is called with four parameters
            //	* `column` ui-grid object
            //	* `col` object from metadata
            //	* `uiGridConstants` object
            //	* `gridDefaults` object
            columns: {
                date: dateSorting,

                datetime: dateSorting,

                // Font-awesome icon by default
                boolean: function (column, col, uiGridConstants, gridDefaults) {
                    column.cellTemplate = gridDefaults.wrapCell('');

                    if (col.hasOwnProperty('filter')) {
                        column.filter = {
                            type: uiGridConstants.filter.SELECT,
                            selectOptions: [{ value: 'true', label: 'True' }, { value: 'false', label: 'False'}]
                        };
                    }
                }
            },
            //
            // default wrapper for grid cells
            wrapCell: function (template) {
                return '
' + template + '
';
            }
        })
        //
        .service('GridService', ['$lux', '$q', '$location', '$compile', '$modal', 'uiGridConstants', 'gridDefaults',
            function($lux, $q, $location, $compile, $modal, uiGridConstants, gridDefaults) {

            function parseColumns(columns, metaFields) {
                var columnDefs = [],
                    column;

                angular.forEach(columns, function(col) {
                    column = {
                        field: col.name,
                        displayName: col.displayName,
                        type: getColumnType(col.type),
                        name: col.name
                    };

                    if (col.hasOwnProperty('hidden') && col.hidden)
                        column.visible = false;

                    if (!col.hasOwnProperty('sortable'))
                        column.enableSorting = false;

                    if (!col.hasOwnProperty('filter'))
                        column.enableFiltering = false;

                    var callback = gridDefaults.columns[col.type];
                    if (callback) callback(column, col, uiGridConstants, gridDefaults);

                    if (column.field === metaFields.repr) {
                        column.cellTemplate = gridDefaults.wrapCell('{{COL_FIELD}}');
                        // Set repr column as the first column
                        columnDefs.splice(0, 0, column);
                    }
                    else
                        columnDefs.push(column);
                });

                return columnDefs;
            }

            // Get specified page using params
            function getPage(scope, api) {
                var query = angular.extend({}, scope.gridState);

                // Add filter if available
                if (scope.gridFilters)
                    query = angular.extend(query, scope.gridFilters);

                api.get({}, query).success(function(resp) {
                    scope.gridOptions.totalItems = resp.total;
                    scope.gridOptions.data = resp.result;

                    // Update grid height depending on number of the rows
                    scope.updateGridHeight();
                });
            }

            // Return column type accirding to type
            function getColumnType(type) {
                switch (type) {
                    case 'integer':     return 'number';
                    case 'datetime':    return 'date';
                    default:            return type;
                }
            }

            // Add menu actions to grid
            function addGridMenu(scope, api, gridOptions) {
                var menu = [],
                    stateName = window.location.href.split('/').pop(-1),
                    model = stateName.slice(0, -1),
                    modalScope = scope.$new(true),
                    modal, title, template;

                scope.create = function($event) {
                    // if location path is available then we use ui-router
                    if (lux.context.uiRouterEnabled)
                        $location.path($location.path() + '/add');
                    else
                        $lux.window.location.href += '/add';
                };

                scope.delete = function($event) {
                    modalScope.selected = scope.gridApi.selection.getSelectedRows();

                    var firstField = gridOptions.columnDefs[0].field,
                        subPath = scope.options.target.path || '';

                    // Modal settings
                    angular.extend(modalScope, {
                        'stateName': stateName,
                        'repr_field': scope.gridOptions.metaFields.repr || firstField,
                        'infoMessage': gridDefaults.modal.delete.messages.info + ' ' + stateName + ':',
                        'dangerMessage': gridDefaults.modal.delete.messages.danger,
                        'emptyMessage': gridDefaults.modal.delete.messages.empty + ' ' + stateName + '.',
                    });

                    if (modalScope.selected.length > 0)
                        template = gridDefaults.modal.delete.templates.delete;
                    else
                        template = gridDefaults.modal.delete.templates.empty;

                    modal = $modal({scope: modalScope, template: template, show: true});

                    modalScope.ok = function() {

                        function deleteItem(item) {
                            var defer = $lux.q.defer(),
                                pk = item[scope.gridOptions.metaFields.id];

                            api.delete({path: subPath + '/' + pk})
                                .success(function(resp) {
                                    defer.resolve(gridDefaults.modal.delete.messages.success);
                                })
                                .error(function(error) {
                                    defer.reject(gridDefaults.modal.delete.messages.error);
                                });

                            return defer.promise;
                        }

                        var promises = [];

                        forEach(modalScope.selected, function(item, _) {
                            promises.push(deleteItem(item));
                        });

                        $q.all(promises).then(function(results) {
                            getPage(scope, api);
                            modal.hide();
                            $lux.messages.success(results[0] + ' ' + results.length + ' ' + stateName);
                        }, function(results) {
                            modal.hide();
                            $lux.messages.error(results + ' ' + stateName);
                        });
                    };
                };

                scope.columnsVisibility = function() {
                    modalScope.columns = scope.gridOptions.columnDefs;
                    modalScope.infoMessage = gridDefaults.modal.columnsVisibility.messages.info;

                    modalScope.toggleVisible = function(column) {
                        if (column.hasOwnProperty('visible'))
                            column.visible = !column.visible;
                        else
                            column.visible = false;

                        scope.gridApi.core.refresh();
                    };

                    modalScope.activeClass = function(column) {
                        if (column.hasOwnProperty('visible')) {
                            if (column.visible) return 'btn-success';
                                return 'btn-danger';
                        } else
                            return 'btn-success';
                    };
                    //
                    template = gridDefaults.modal.columnsVisibility.templates.default;
                    modal = $modal({scope: modalScope, template: template, show: true});
                };

                forEach(gridDefaults.gridMenu, function(item, key) {
                    title = item.title;

                    if (key === 'create')
                        title += ' ' + model;

                    menu.push({
                        title: title,
                        icon: item.icon,
                        action: scope[key]
                    });
                });

                extend(gridOptions, {
                    enableGridMenu: true,
                    gridMenuShowHideColumns: false,
                    gridMenuCustomItems: menu
                });
            }

            // Get initial data
            this.getInitialData = function(scope) {
                var api = $lux.api(scope.options.target),
                    sub_path = scope.options.target.path || '';

                api.get({path: sub_path + '/metadata'}).success(function(resp) {
                    scope.gridState.limit = resp['default-limit'];
                    scope.gridOptions.metaFields = {
                        id: resp.id,
                        repr: resp.repr
                    };
                    scope.gridOptions.columnDefs = parseColumns(resp.columns, scope.gridOptions.metaFields);

                    api.get({path: sub_path}, {limit: scope.gridState.limit}).success(function(resp) {
                        scope.gridOptions.totalItems = resp.total;
                        scope.gridOptions.data = resp.result;

                        // Update grid height
                        scope.updateGridHeight();
                    });
                });
            };

            // Builds grid options
            this.buildOptions = function(scope, options) {
                scope.options = options;
                scope.paginationOptions = gridDefaults.paginationOptions;
                scope.gridState = gridDefaults.gridState;
                scope.gridFilters = gridDefaults.gridFilters;

                scope.objectUrl = function(entity) {
                    return $lux.window.location + '/' + entity[scope.gridOptions.metaFields.id];
                };

                scope.getBooleanFieldIcon = function(COL_FIELD) {
                    return ((COL_FIELD) ? 'fa fa-check-circle text-success' : 'fa fa-check-circle text-danger');
                };

                scope.clearData = function() {
                    scope.gridOptions.data = [];
                };

                scope.updateGridHeight = function () {
                    var length = scope.gridOptions.totalItems,
                        element = angular.element(document.getElementsByClassName('grid')[0]),
                        totalPages = scope.gridApi.pagination.getTotalPages(),
                        currentPage = scope.gridState.page,
                        lastPage = scope.gridOptions.totalItems % scope.gridState.limit,
                        gridHeight = 0;

                    // Calculate grid height
                    if (length > 0) {
                        if (currentPage < totalPages || lastPage === 0)
                            gridHeight = scope.gridState.limit * gridDefaults.rowHeight + gridDefaults.offsetGridHeight;
                        else
                            gridHeight = lastPage * gridDefaults.rowHeight + gridDefaults.offsetGridHeight;
                    }

                    if (gridHeight < gridDefaults.minGridHeight)
                        gridHeight = gridDefaults.minGridHeight;

                    element.css('height', gridHeight + 'px');
                };

                var api = scope.options.target ? $lux.api(scope.options.target) : null,
                    gridOptions = {
                        paginationPageSizes: scope.paginationOptions.sizes,
                        paginationPageSize: scope.gridState.limit,
                        enableFiltering: gridDefaults.enableFiltering,
                        enableRowHeaderSelection: gridDefaults.enableRowHeaderSelection,
                        useExternalPagination: gridDefaults.useExternalPagination,
                        useExternalSorting: gridDefaults.useExternalSorting,
                        enableHorizontalScrollbar: gridDefaults.enableHorizontalScrollbar,
                        enableVerticalScrollbar: gridDefaults.enableVerticalScrollbar,
                        rowHeight: gridDefaults.rowHeight,
                        onRegisterApi: function(gridApi) {
                            scope.gridApi = gridApi;

                            require(['lodash'], function(_) {
                                //
                                // Pagination
                                scope.gridApi.pagination.on.paginationChanged(scope, _.debounce(function(pageNumber, pageSize) {
                                    scope.gridState.page = pageNumber;
                                    scope.gridState.limit = pageSize;
                                    scope.gridState.offset = pageSize*(pageNumber - 1);

                                    getPage(scope, api);
                                }, gridDefaults.requestDelay));
                                //
                                // Sorting
                                scope.gridApi.core.on.sortChanged(scope, _.debounce(function(grid, sortColumns) {
                                    if( sortColumns.length === 0) {
                                        delete scope.gridState.sortby;
                                        getPage(scope, api);
                                    } else {
                                        // Build query string for sorting
                                        angular.forEach(sortColumns, function(column) {
                                            scope.gridState.sortby = column.name + ':' + column.sort.direction;
                                        });

                                        switch( sortColumns[0].sort.direction ) {
                                            case uiGridConstants.ASC:
                                                getPage(scope, api);
                                                break;
                                            case uiGridConstants.DESC:
                                                getPage(scope, api);
                                                break;
                                            case undefined:
                                                getPage(scope, api);
                                                break;
                                        }
                                    }
                                }, gridDefaults.requestDelay));
                                //
                                // Filtering
                                scope.gridApi.core.on.filterChanged(scope, _.debounce(function() {
                                    var grid = this.grid;
                                    scope.gridFilters = {};

                                    // Add filters
                                    angular.forEach(grid.columns, function(value, _) {
                                        // Clear data in order to refresh icons
                                        if (value.filter.type === 'select')
                                            scope.clearData();

                                        if (value.filters[0].term)
                                            scope.gridFilters[value.colDef.name] = value.filters[0].term;
                                    });

                                    // Get results
                                    getPage(scope, api);

                                }, gridDefaults.requestDelay));
                            });
                        }
                    };

                if (gridDefaults.showMenu)
                    addGridMenu(scope, api, gridOptions);

                return gridOptions;
            };
        }])
        //
        // Directive to build Angular-UI grid options using Lux REST API
        .directive('restGrid', ['$compile', 'GridService', function ($compile, GridService) {

            return {
                restrict: 'A',
                link: {
                    pre: function (scope, element, attrs) {
                        var scripts= element[0].getElementsByTagName('script');

                        forEach(scripts, function (js) {
                            globalEval(js.innerHTML);
                        });

                        var opts = attrs;
                        if (attrs.restGrid) opts = {options: attrs.restGrid};

                        opts = getOptions(opts);

                        if (opts) {
                            scope.gridOptions = GridService.buildOptions(scope, opts);
                            GridService.getInitialData(scope);
                        }

                        var grid = '
0" class="grid" ui-grid="gridOptions" ui-grid-pagination ui-grid-selection ui-grid-auto-resize>
';
                        element.append($compile(grid)(scope));
                    },
                },
            };

        }]);
<commit_msg>Issue #135 Added capacity for custom path for REPR field<commit_after>    //
    // Grid module for lux
    //
    //  Dependencies:
    //
    //      - use $modal service from angular-strap library
    //
    //
    function dateSorting(column) {

        column.sortingAlgorithm = function(a, b) {
            var dt1 = new Date(a).getTime(),
                dt2 = new Date(b).getTime();
            return dt1 === dt2 ? 0 : (dt1 < dt2 ? -1 : 1);
        };
    }

    angular.module('lux.grid', ['lux.services', 'templates-grid', 'ngTouch', 'ui.grid',
                                'ui.grid.pagination', 'ui.grid.selection', 'ui.grid.autoResize'])
        //
        .constant('gridDefaults', {
            //
            enableFiltering: true,
            enableRowHeaderSelection: false,
            useExternalPagination: true,
            useExternalSorting: true,
            useExternalFiltering: true,
            // Scrollbar display: 0 - never, 1 - always, 2 - when needed
            enableHorizontalScrollbar: 0,
            enableVerticalScrollbar: 0,
            //
            rowHeight: 30,
            minGridHeight: 250,
            offsetGridHeight: 102,
            //
            // request delay in ms
            requestDelay: 100,
            //
            paginationOptions: {
                sizes: [25, 50, 100]
            },
            //
            gridState: {
                page: 1,
                limit: 25,
                offset: 0
            },
            gridFilters: {},
            //
            showMenu: true,
            gridMenu: {
                'create': {
                    title: 'Add',
                    icon: 'fa fa-plus'
                },
                'delete': {
                    title: 'Delete',
                    icon: 'fa fa-trash'
                },
                'columnsVisibility': {
                    title: 'Columns visibility',
                    icon: 'fa fa-eye'
                }
            },
            modal: {
                delete: {
                    templates: {
                        'empty': 'grid/templates/modal.empty.tpl.html',
                        'delete': 'grid/templates/modal.delete.tpl.html',
                    },
                    messages: {
                        'info': 'Are you sure you want to delete',
                        'danger': 'DANGER - THIS CANNOT BE UNDONE',
                        'success': 'Successfully deleted',
                        'error': 'Error while deleting ',
                        'empty': 'Please, select some',
                    }
                },
                columnsVisibility: {
                    templates: {
                        'default': 'grid/templates/modal.columns.tpl.html',
                    },
                    messages: {
                        'info': 'Click button with column name to toggle visibility'
                    }
                }
            },
            // dictionary of call-backs for columns types
            // The function is called with four parameters
            //	* `column` ui-grid object
            //	* `col` object from metadata
            //	* `uiGridConstants` object
            //	* `gridDefaults` object
            columns: {
                date: dateSorting,

                datetime: dateSorting,

                // Font-awesome icon by default
                boolean: function (column, col, uiGridConstants, gridDefaults) {
                    column.cellTemplate = gridDefaults.wrapCell('');

                    if (col.hasOwnProperty('filter')) {
                        column.filter = {
                            type: uiGridConstants.filter.SELECT,
                            selectOptions: [{ value: 'true', label: 'True' }, { value: 'false', label: 'False'}]
                        };
                    }
                }
            },
            //
            // default wrapper for grid cells
            wrapCell: function (template) {
                return '
' + template + '
';
            }
        })
        //
        .service('GridService', ['$lux', '$q', '$location', '$compile', '$modal', 'uiGridConstants', 'gridDefaults',
            function($lux, $q, $location, $compile, $modal, uiGridConstants, gridDefaults) {

            function parseColumns(columns, metaFields) {
                var columnDefs = [],
                    column;

                angular.forEach(columns, function(col) {
                    column = {
                        field: col.name,
                        displayName: col.displayName,
                        type: getColumnType(col.type),
                        name: col.name
                    };

                    if (col.hasOwnProperty('hidden') && col.hidden)
                        column.visible = false;

                    if (!col.hasOwnProperty('sortable'))
                        column.enableSorting = false;

                    if (!col.hasOwnProperty('filter'))
                        column.enableFiltering = false;

                    var callback = gridDefaults.columns[col.type];
                    if (callback) callback(column, col, uiGridConstants, gridDefaults);

                    if (column.field === metaFields.repr) {
                        column.cellTemplate = gridDefaults.wrapCell('{{COL_FIELD}}');
                        // Set repr column as the first column
                        columnDefs.splice(0, 0, column);
                    }
                    else
                        columnDefs.push(column);
                });

                return columnDefs;
            }

            // Get specified page using params
            function getPage(scope, api) {
                var query = angular.extend({}, scope.gridState);

                // Add filter if available
                if (scope.gridFilters)
                    query = angular.extend(query, scope.gridFilters);

                api.get({}, query).success(function(resp) {
                    scope.gridOptions.totalItems = resp.total;
                    scope.gridOptions.data = resp.result;

                    // Update grid height depending on number of the rows
                    scope.updateGridHeight();
                });
            }

            // Return column type accirding to type
            function getColumnType(type) {
                switch (type) {
                    case 'integer':     return 'number';
                    case 'datetime':    return 'date';
                    default:            return type;
                }
            }

            // Add menu actions to grid
            function addGridMenu(scope, api, gridOptions) {
                var menu = [],
                    stateName = window.location.href.split('/').pop(-1),
                    model = stateName.slice(0, -1),
                    modalScope = scope.$new(true),
                    modal, title, template;

                scope.create = function($event) {
                    // if location path is available then we use ui-router
                    if (lux.context.uiRouterEnabled)
                        $location.path($location.path() + '/add');
                    else
                        $lux.window.location.href += '/add';
                };

                scope.delete = function($event) {
                    modalScope.selected = scope.gridApi.selection.getSelectedRows();

                    var firstField = gridOptions.columnDefs[0].field,
                        subPath = scope.options.target.path || '';

                    // Modal settings
                    angular.extend(modalScope, {
                        'stateName': stateName,
                        'repr_field': scope.gridOptions.metaFields.repr || firstField,
                        'infoMessage': gridDefaults.modal.delete.messages.info + ' ' + stateName + ':',
                        'dangerMessage': gridDefaults.modal.delete.messages.danger,
                        'emptyMessage': gridDefaults.modal.delete.messages.empty + ' ' + stateName + '.',
                    });

                    if (modalScope.selected.length > 0)
                        template = gridDefaults.modal.delete.templates.delete;
                    else
                        template = gridDefaults.modal.delete.templates.empty;

                    modal = $modal({scope: modalScope, template: template, show: true});

                    modalScope.ok = function() {

                        function deleteItem(item) {
                            var defer = $lux.q.defer(),
                                pk = item[scope.gridOptions.metaFields.id];

                            api.delete({path: subPath + '/' + pk})
                                .success(function(resp) {
                                    defer.resolve(gridDefaults.modal.delete.messages.success);
                                })
                                .error(function(error) {
                                    defer.reject(gridDefaults.modal.delete.messages.error);
                                });

                            return defer.promise;
                        }

                        var promises = [];

                        forEach(modalScope.selected, function(item, _) {
                            promises.push(deleteItem(item));
                        });

                        $q.all(promises).then(function(results) {
                            getPage(scope, api);
                            modal.hide();
                            $lux.messages.success(results[0] + ' ' + results.length + ' ' + stateName);
                        }, function(results) {
                            modal.hide();
                            $lux.messages.error(results + ' ' + stateName);
                        });
                    };
                };

                scope.columnsVisibility = function() {
                    modalScope.columns = scope.gridOptions.columnDefs;
                    modalScope.infoMessage = gridDefaults.modal.columnsVisibility.messages.info;

                    modalScope.toggleVisible = function(column) {
                        if (column.hasOwnProperty('visible'))
                            column.visible = !column.visible;
                        else
                            column.visible = false;

                        scope.gridApi.core.refresh();
                    };

                    modalScope.activeClass = function(column) {
                        if (column.hasOwnProperty('visible')) {
                            if (column.visible) return 'btn-success';
                                return 'btn-danger';
                        } else
                            return 'btn-success';
                    };
                    //
                    template = gridDefaults.modal.columnsVisibility.templates.default;
                    modal = $modal({scope: modalScope, template: template, show: true});
                };

                forEach(gridDefaults.gridMenu, function(item, key) {
                    title = item.title;

                    if (key === 'create')
                        title += ' ' + model;

                    menu.push({
                        title: title,
                        icon: item.icon,
                        action: scope[key]
                    });
                });

                extend(gridOptions, {
                    enableGridMenu: true,
                    gridMenuShowHideColumns: false,
                    gridMenuCustomItems: menu
                });
            }

            // Get initial data
            this.getInitialData = function(scope) {
                var api = $lux.api(scope.options.target),
                    sub_path = scope.options.target.path || '';

                api.get({path: sub_path + '/metadata'}).success(function(resp) {
                    scope.gridState.limit = resp['default-limit'];
                    scope.gridOptions.metaFields = {
                        id: resp.id,
                        repr: resp.repr
                    };
                    scope.gridOptions.columnDefs = parseColumns(resp.columns, scope.gridOptions.metaFields);

                    api.get({path: sub_path}, {limit: scope.gridState.limit}).success(function(resp) {
                        scope.gridOptions.totalItems = resp.total;
                        scope.gridOptions.data = resp.result;

                        // Update grid height
                        scope.updateGridHeight();
                    });
                });
            };

            // Builds grid options
            this.buildOptions = function(scope, options) {
                scope.options = options;
                scope.paginationOptions = gridDefaults.paginationOptions;
                scope.gridState = gridDefaults.gridState;
                scope.gridFilters = gridDefaults.gridFilters;

                var reprPath = options.reprPath || $lux.window.location;

                scope.objectUrl = function(entity) {
                    return reprPath + '/' + entity[scope.gridOptions.metaFields.id];
                };

                scope.getBooleanFieldIcon = function(COL_FIELD) {
                    return ((COL_FIELD) ? 'fa fa-check-circle text-success' : 'fa fa-check-circle text-danger');
                };

                scope.clearData = function() {
                    scope.gridOptions.data = [];
                };

                scope.updateGridHeight = function () {
                    var length = scope.gridOptions.totalItems,
                        element = angular.element(document.getElementsByClassName('grid')[0]),
                        totalPages = scope.gridApi.pagination.getTotalPages(),
                        currentPage = scope.gridState.page,
                        lastPage = scope.gridOptions.totalItems % scope.gridState.limit,
                        gridHeight = 0;

                    // Calculate grid height
                    if (length > 0) {
                        if (currentPage < totalPages || lastPage === 0)
                            gridHeight = scope.gridState.limit * gridDefaults.rowHeight + gridDefaults.offsetGridHeight;
                        else
                            gridHeight = lastPage * gridDefaults.rowHeight + gridDefaults.offsetGridHeight;
                    }

                    if (gridHeight < gridDefaults.minGridHeight)
                        gridHeight = gridDefaults.minGridHeight;

                    element.css('height', gridHeight + 'px');
                };

                var api = scope.options.target ? $lux.api(scope.options.target) : null,
                    gridOptions = {
                        paginationPageSizes: scope.paginationOptions.sizes,
                        paginationPageSize: scope.gridState.limit,
                        enableFiltering: gridDefaults.enableFiltering,
                        enableRowHeaderSelection: gridDefaults.enableRowHeaderSelection,
                        useExternalPagination: gridDefaults.useExternalPagination,
                        useExternalSorting: gridDefaults.useExternalSorting,
                        enableHorizontalScrollbar: gridDefaults.enableHorizontalScrollbar,
                        enableVerticalScrollbar: gridDefaults.enableVerticalScrollbar,
                        rowHeight: gridDefaults.rowHeight,
                        onRegisterApi: function(gridApi) {
                            scope.gridApi = gridApi;

                            require(['lodash'], function(_) {
                                //
                                // Pagination
                                scope.gridApi.pagination.on.paginationChanged(scope, _.debounce(function(pageNumber, pageSize) {
                                    scope.gridState.page = pageNumber;
                                    scope.gridState.limit = pageSize;
                                    scope.gridState.offset = pageSize*(pageNumber - 1);

                                    getPage(scope, api);
                                }, gridDefaults.requestDelay));
                                //
                                // Sorting
                                scope.gridApi.core.on.sortChanged(scope, _.debounce(function(grid, sortColumns) {
                                    if( sortColumns.length === 0) {
                                        delete scope.gridState.sortby;
                                        getPage(scope, api);
                                    } else {
                                        // Build query string for sorting
                                        angular.forEach(sortColumns, function(column) {
                                            scope.gridState.sortby = column.name + ':' + column.sort.direction;
                                        });

                                        switch( sortColumns[0].sort.direction ) {
                                            case uiGridConstants.ASC:
                                                getPage(scope, api);
                                                break;
                                            case uiGridConstants.DESC:
                                                getPage(scope, api);
                                                break;
                                            case undefined:
                                                getPage(scope, api);
                                                break;
                                        }
                                    }
                                }, gridDefaults.requestDelay));
                                //
                                // Filtering
                                scope.gridApi.core.on.filterChanged(scope, _.debounce(function() {
                                    var grid = this.grid;
                                    scope.gridFilters = {};

                                    // Add filters
                                    angular.forEach(grid.columns, function(value, _) {
                                        // Clear data in order to refresh icons
                                        if (value.filter.type === 'select')
                                            scope.clearData();

                                        if (value.filters[0].term)
                                            scope.gridFilters[value.colDef.name] = value.filters[0].term;
                                    });

                                    // Get results
                                    getPage(scope, api);

                                }, gridDefaults.requestDelay));
                            });
                        }
                    };

                if (gridDefaults.showMenu)
                    addGridMenu(scope, api, gridOptions);

                return gridOptions;
            };
        }])
        //
        // Directive to build Angular-UI grid options using Lux REST API
        .directive('restGrid', ['$compile', 'GridService', function ($compile, GridService) {

            return {
                restrict: 'A',
                link: {
                    pre: function (scope, element, attrs) {
                        var scripts= element[0].getElementsByTagName('script');

                        forEach(scripts, function (js) {
                            globalEval(js.innerHTML);
                        });

                        var opts = attrs;
                        if (attrs.restGrid) opts = {options: attrs.restGrid};
                        if (attrs.reprPath) opts.reprPath = attrs.reprPath;

                        opts = getOptions(opts);

                        if (opts) {
                            scope.gridOptions = GridService.buildOptions(scope, opts);
                            GridService.getInitialData(scope);
                        }

                        var grid = '
0" class="grid" ui-grid="gridOptions" ui-grid-pagination ui-grid-selection ui-grid-auto-resize>
';
                        element.append($compile(grid)(scope));
                    },
                },
            };

        }]);
<|endoftext|>


<commit_before>import models, { sequelize } from '../models';
import { getFxRate } from '../lib/currency';
import Promise from 'bluebird';

export const getFxRateController = (req, res, next) => {
  const {
    fromCurrency,
    toCurrency,
    date
  } = req.params;

  return getFxRate(fromCurrency, toCurrency, date).then(fxrate => {
    return res.send({fxrate});
  }).catch(next);
}

/**
 * Get a transaction
 */
export const getOne = (req, res) => {
  const { transaction } = req;

  return Promise.props({
    collective: transaction.getCollective(),
    fromCollective: transaction.getFromCollective(),
    createdByUser: transaction.getCreatedByUser(),
  })
  .then(async (results) => {
    const { collective, fromCollective, createdByUser } = results;
    let host;
    if (transaction.HostCollectiveId) {
      host = await collective.getHostCollective();
    } else {
      // If there is no HostCollectiveId, then the transaction is related to a user or organization
      // - could be a DEBIT (donation made by a user to `fromCollective`); or
      // - could be a CREDIT (received money from `fromCollective` to reimburse an ExpenseId)
      // in both cases, the host of the transaction is the host of the `fromCollective`
      host = await fromCollective.getHostCollective();
    }
    return {
      ...transaction.info,
      host: host.invoice,
      fromCollective: fromCollective.invoice,
      collective: collective.invoice,
      createdByUser: createdByUser && createdByUser.public
    };
  })
  .then(transaction => res.send(transaction))
};
<commit_msg>eslint<commit_after>import { getFxRate } from '../lib/currency';
import Promise from 'bluebird';

export const getFxRateController = (req, res, next) => {
  const {
    fromCurrency,
    toCurrency,
    date
  } = req.params;

  return getFxRate(fromCurrency, toCurrency, date).then(fxrate => {
    return res.send({fxrate});
  }).catch(next);
}

/**
 * Get a transaction
 */
export const getOne = (req, res) => {
  const { transaction } = req;

  return Promise.props({
    collective: transaction.getCollective(),
    fromCollective: transaction.getFromCollective(),
    createdByUser: transaction.getCreatedByUser(),
  })
  .then(async (results) => {
    const { collective, fromCollective, createdByUser } = results;
    let host;
    if (transaction.HostCollectiveId) {
      host = await collective.getHostCollective();
    } else {
      // If there is no HostCollectiveId, then the transaction is related to a user or organization
      // - could be a DEBIT (donation made by a user to `fromCollective`); or
      // - could be a CREDIT (received money from `fromCollective` to reimburse an ExpenseId)
      // in both cases, the host of the transaction is the host of the `fromCollective`
      host = await fromCollective.getHostCollective();
    }
    return {
      ...transaction.info,
      host: host.invoice,
      fromCollective: fromCollective.invoice,
      collective: collective.invoice,
      createdByUser: createdByUser && createdByUser.public
    };
  })
  .then(transaction => res.send(transaction))
};
<|endoftext|>


<commit_before>/*
 * Copyright 2006 Stephen Duncan Jr
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function Deck() {
	this.cards = [];
	this.drawnCards = [];

	for(var i = 0; i < Card.ranks.length; i++)
		for(var j = 0; j < Card.suits.length; j++)
			this.cards.push(new Card(Card.ranks[i], Card.suits[j]));
}

Deck.prototype.draw = function() {
	var result;

	var index = Math.floor(Math.random() * this.cards.length);
	card = this.cards[index];

	if(this.drawnCards.indexOf(card) != -1)
		result = this.draw();
	else {
		this.drawnCards.push(card);
		result = card;
	}

	return result;
}

Deck.prototype.reset = function() {
	this.drawnCards = [];
}<commit_msg>Added implementation of indexOf for browsers that don't natively support it<commit_after>/*
 * Copyright 2006 Stephen Duncan Jr
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

if(typeof Array.prototype.indexOf === 'undefined') {
	Array.prototype.indexOf = function(n) {
		for(var i=0;i<this.length;i++)
			if(this[i]===n)
				return i;

		return -1;
	}
}

function Deck() {
	this.cards = [];
	this.drawnCards = [];

	for(var i = 0; i < Card.ranks.length; i++)
		for(var j = 0; j < Card.suits.length; j++)
			this.cards.push(new Card(Card.ranks[i], Card.suits[j]));
}

Deck.prototype.draw = function() {
	var result;

	var index = Math.floor(Math.random() * this.cards.length);
	card = this.cards[index];

	if(this.drawnCards.indexOf(card) != -1)
		result = this.draw();
	else {
		this.drawnCards.push(card);
		result = card;
	}

	return result;
}

Deck.prototype.reset = function() {
	this.drawnCards = [];
}<|endoftext|>


<commit_before>var assert = require( 'assert' )
var File = require( '../' )

const TEMP = __dirname + '/file.tmp'
const RENM = __dirname + '/renamed.tmp'

describe( 'File', function() {

  var tmp = null

  it( 'should be able to create a new file', function( done ) {
    File.create( TEMP, done )
  })

  it( 'should be able to open a file', function( done ) {
    tmp = new File( TEMP )
      .open( done )
  })

  it( 'should be able to write to an opened file', function( done ) {
    tmp.write( 'HELLO WORLD\0', done )
    tmp.write( 'HELLO WORLD\0' )
  })

  it( 'should be able to write to an OOB offset', function( done ) {
    tmp.write( '\0', 127, done )
    tmp.write( '\0', 127 )
  })

  it( 'should NOT be able to seek to OOB offset', function() {
    assert.throws( function() {
      tmp.seek( 256 * 1024 )
    })
  })

  it( 'should be able to seek to with negative offset', function() {
    assert.doesNotThrow( function() {
      tmp.seek( -1 )
    })
  })

  it( 'should be able to read from an opened file', function( done ) {
    var data = 'HELLO WORLD\0'
    tmp.read( 0, data.length, function( error, bytesRead, buffer ) {
      assert.equal( bytesRead, data.length )
      assert.equal( buffer.toString(), data )
      done( error )
    })
  })

  it( 'should NOT be able to read from an OOB offset', function( done ) {
    tmp.read( 256, 10, function( error, read, buffer ) {
      assert.ok( error )
      done()
    })
    assert.throws( function() {
      tmp.read( 256, 10 )
    })
  })

  it( 'should be able to create a writeStream of file', function( done ) {
    var data = '11111 11111\0'
    var ws = tmp.writeStream()
    tmp.offset = 0
    ws.write( data, function() {
      done()
    })
  })

  it( 'should be able to create a readStream of file', function( done ) {
    var data = '11111 11111\0'
    var rs = tmp.readStream()
    rs.on( 'readable', function() {
      var bytes = rs.read()
      assert.ok( bytes.toString().indexOf( data ) === 0 )
      done()
    })
  })

  it( 'should be able to allocate to a file', function() {
    var size = 256 * 1024
    tmp.truncate( size )
    var stat = tmp.stat()
    assert.equal( stat.size, size )
  })

  it( 'should be able to truncate a file', function() {
    var size = 256
    tmp.truncate( size )
    var stat = tmp.stat()
    assert.equal( stat.size, size )
  })

  it( 'should be able to rename a file', function( done ) {
    tmp.rename( RENM, done )
  })

  it( 'should be able to close an opened file', function( done ) {
    tmp.close( function( error ) {
      tmp = null
      done( error )
    })
  })

  it( 'should be able to delete a file', function( done ) {
    File.delete( RENM, done )
  })

})
<commit_msg>Updated tests<commit_after>var assert = require( 'assert' )
var File = require( '../' )

const TEMP = __dirname + '/file.tmp'
const RENM = __dirname + '/renamed.tmp'

describe( 'File', function() {

  var tmp = null

  it( 'should be able to create a new file', function( done ) {
    File.create( TEMP, done )
  })

  it( 'should be able to open a file', function( done ) {
    tmp = new File( TEMP )
      .open( done )
  })

  it( 'should be able to write to an opened file', function( done ) {
    tmp.write( 'HELLO WORLD\0', done )
    tmp.write( 'HELLO WORLD\0' )
  })

  it( 'should be able to write to an OOB offset', function( done ) {
    tmp.write( '\0', 127, done )
    tmp.write( '\0', 396 )
  })

  it( 'should NOT be able to seek to OOB offset', function() {
    assert.throws( function() {
      tmp.seek( 256 * 1024 )
    })
  })

  it( 'should be able to read from an opened file', function( done ) {
    var data = 'HELLO WORLD\0'
    tmp.read( 0, data.length, function( error, bytesRead, buffer ) {
      assert.equal( bytesRead, data.length )
      assert.equal( buffer.toString(), data )
      done( error )
    })
  })

  it( 'should NOT be able to read from an OOB offset', function( done ) {
    tmp.read( 256 * 1024, 10, function( error, read, buffer ) {
      assert.ok( error )
      done()
    })
    assert.throws( function() {
      tmp.read( 256 * 1024, 10 )
    })
  })

  it( 'should be able to create a writeStream of file', function( done ) {
    var data = '11111 11111\0'
    var ws = tmp.writeStream()
    tmp.offset = 0
    ws.write( data, function() {
      done()
    })
  })

  it( 'should be able to create a readStream of file', function( done ) {
    var data = '11111 11111\0'
    var rs = tmp.readStream()
    rs.on( 'readable', function() {
      var bytes = rs.read()
      assert.ok( bytes.toString().indexOf( data ) === 0 )
      done()
    })
  })

  it( 'should be able to allocate to a file', function() {
    var size = 256 * 1024
    tmp.truncate( size )
    var stat = tmp.stat()
    assert.equal( stat.size, size )
  })

  it( 'should be able to truncate a file', function() {
    var size = 256
    tmp.truncate( size )
    var stat = tmp.stat()
    assert.equal( stat.size, size )
  })

  it( 'should be able to rename a file', function( done ) {
    tmp.rename( RENM, done )
  })

  it( 'should be able to close an opened file', function( done ) {
    tmp.close( function( error ) {
      tmp = null
      done( error )
    })
  })

  it( 'should be able to delete a file', function( done ) {
    File.delete( RENM, done )
  })

})
<|endoftext|>


<commit_before>$(function() {

(function($) {
	// todo move this stuff into the proper namespaces
	window.LibraryItem = Backbone.Model.extend({

		idAttribute: "key",

		getViewBookUrl: function(book) {
			return "/views/viewer.html?book=" + this.get('key');
		},

		openInReader: function() {
			window.location = this.getViewBookUrl();
		},

		delete: function() {
			var key = this.get('key');
			Lawnchair(function() {
				var that = this; // <=== capture Lawnchair scope
				this.get(key, function(book) {
					if(book) {
						Readium.FileSystemApi(function(fs) {
							fs.rmdir(book.key);
							that.remove(key);
						});
					}
				});
			});
		}
	});

	window.LibraryItems = Backbone.Collection.extend({

		model: LibraryItem,

	});

	window.LibraryItemView = Backbone.View.extend({

		tagName: 'div',

		className: "book-item clearfix",

		template: _.template( $('#library-item-template').html() ),

		initialize: function() {
			_.bindAll(this, "render");
		},

		render: function() {
			var renderedContent = this.template({data: this.model.toJSON()});
			$(this.el).html(renderedContent);
			return this;
		},

		events: {
			"click .delete": function(e) {
				e.preventDefault();
				var confMessage;
				var selector = "#details-modal-" + this.model.get('key');
				confMessage  = "Are you sure you want to perminantly delete "
				confMessage += this.model.get('title');
				confMessage += "?";

				if(confirm(confMessage)) {
					$(selector).modal('hide');
					this.model.delete();
					this.remove();
				}
			},

			"click .read": function(e) {
				this.model.openInReader();
			}

		}
	});

	window.LibraryItemsView = Backbone.View.extend({
		tagName: 'div',

		id: "library-items-container",

		className: 'row-view clearfix',

		template: _.template( $('#library-items-template').html() ),

		initialize: function() {
			_.bindAll(this, "render");
			this.collection.bind('reset', this.render);
			this.collection.bind('add',   this.addOne, this);
		},

		render: function() {
			var collection = this.collection;
			var $container = $(this.el);
			$container.html(this.template({}));
			this.$('#empty-message').toggle(this.collection.isEmpty());

			collection.each(function(item) {
				var view = new LibraryItemView({
					model: item,
					collection: collection,
					id: item.get('id')
				});
				$container.append( view.render().el );

			});

			// i dunno if this should go here
			$('#library-books-list').html(this.el)
			return this;
		},

		addOne: function(book) {
			var view = new LibraryItemView({
				model: book,
				collection: this.collection,
				id: book.get('id')
			});
			// we are adding so this should always be hidden!
			this.$('#empty-message').toggle(false);
			$(this.el).append( view.render().el );
		},

		events: {

		}
	});

	window.ExtractItem = Backbone.Model.extend({

		updateProgress: function(index, total) {
			var prog = index * 100 / total;
			this.set({
				progress: prog.toFixed().toString() + "%",
			});
		},

		start: function() {
			this.set({
				message: "Fetching ePUB",
				progress: 0,
				extracting: true
			});
		},

		end: function() {
			this.set({
				message: "Fetching ePUB",
				progress: 0,
				extracting: false
			});
		}

	});

	window.ExtractItemView = Backbone.View.extend({

		el: $('#progress-container')[0],

		template: _.template( $('#extracting-item-template').html() ),

		initialize: function() {
			_.bindAll(this, "render");
			this.model.bind('change', this.render, this);
		},

		render: function() {
			var $el = $(this.el);
			if( this.model.get('extracting') ) {

				$el.html(this.template(this.model.toJSON()));
				$el.show("slow");
			}
			else {
				$el.hide("slow");
			}
			return this;
		}
	});

	// the options
	window.ReadiumOptions = Backbone.Model.extend({

		defaults: {
			hijack_epub_urls: true,
			verbose_unpacking: true
		},

		sync: function(method, model, options) {
			// "create" create the model on the server
			// "read" : read this model from the server and return it
			// "update" : update the model on the server with the argument
			// "delete" : delete the model from the server.
			var json;
			var key = "READIUM_OPTIONS"

			if(method === "read") {
				json = window.localStorage.getItem(key);
				if(json) {
					// DON'T CHANGE IF NOTHING IN STORAGE
					model.attributes = (json && JSON.parse(json)) || {};
				}
				else {
					options.error('Failed to load settings from local storage')
					return;
				}
			}

			if(method === "create" || method === "update") {
				window.localStorage.setItem(key, JSON.stringify(model.attributes) );

			}

			if(method === "delete") {
				// TODO: this is not deleting stuff...
				window.localStorage.removeItem(key);
			}
			options.success(model);

		}
	});

	window.ReadiumOptionsView = Backbone.View.extend({
		el: "#readium-options-modal",

		initialize: function() {
			this.model.on("change", this.render, this);
		},

		render: function() {
			var m = this.model;
			this.$('#verbose_unpacking').prop('checked', m.get("verbose_unpacking"));
			this.$('#hijack_epub_urls').prop('checked', m.get("hijack_epub_urls"));
		},

		events: {
    		"change #verbose_unpacking": "updateSettings",
    		"change #hijack_epub_urls": "updateSettings",
    		"click #save-settings-btn": "save"
  		},

  		updateSettings: function() {
  			var hijack = this.$('#hijack_epub_urls').prop('checked')
  			var unpack = this.$('#verbose_unpacking').prop('checked')

			this.model.set({"verbose_unpacking": unpack});
			this.model.set({"hijack_epub_urls": hijack});
  		},

  		save: function() {
  			this.model.save();
  		}

	});

	window.options = new ReadiumOptions;
	window.optionsView = new ReadiumOptionsView({model: window.options});
	window.options.fetch({
		success: function() {
			//optionsView.render();
		}
	});

	window.extraction = new ExtractItem({extracting: false});
	window.extract_view = new ExtractItemView({model: extraction});
	extract_view.render();

	window.Library = new LibraryItems();
	window.lib_view = new LibraryItemsView({collection: window.Library});

})(jQuery);

var beginExtraction = function(url, filename) {
	 // Create a new window to the info page.
	 window.extraction.start();

	var extractOptions = {
		display_message: function(message) {
			window.extraction.set({
				message: message
			});
		},

var resetAndHideForm = function() {
	$('#add-book-modal').modal('hide');
};

var handleFileSelect = function(evt) {
	var files = evt.target.files; // FileList object
	var url = window.webkitURL.createObjectURL(files[0]);
	beginExtraction(url, files[0].name);
	resetAndHideForm();
};

/*
var flash = function(text, type) {
	var className = "alert";
	if(type) {
		className += " alert-" + type;
	}
	$('#flash-container').
		html('
'+text+'
').
		removeClass().
		addClass(className);

}

	document.getElementById('files').addEventListener('change', handleFileSelect, false);
	document.getElementById('url-button').addEventListener('click', clickHandler, false);
	_lawnchair = new Lawnchair(function() {
		this.all(function(all) {
			window.Library.reset(all);
		});
	});
	$("#block-view-btn").click(function(e) {
		$('#library-items-container').addClass("block-view").removeClass("row-view")
	});
	$("#row-view-btn").click(function(e) {
		$('#library-items-container').addClass("row-view").removeClass("block-view")
	});

	// TODO: use a Backbone router for this
	if(window.location.hash === "#options") {
		$('#readium-options-modal').modal('show')
	}
	/*
	$("#options-btn").click(function(e) {
		window.location = "/views/options.html";
	});
*/

});

<commit_msg>fixed messed up merge state in library.js<commit_after>$(function() {

(function($) {
	// todo move this stuff into the proper namespaces
	window.LibraryItem = Backbone.Model.extend({

		idAttribute: "key",

		getViewBookUrl: function(book) {
			return "/views/viewer.html?book=" + this.get('key');
		},

		openInReader: function() {
			window.location = this.getViewBookUrl();
		},

		delete: function() {
			var key = this.get('key');
			Lawnchair(function() {
				var that = this; // <=== capture Lawnchair scope
				this.get(key, function(book) {
					if(book) {
						Readium.FileSystemApi(function(fs) {
							fs.rmdir(book.key);
							that.remove(key);
						});
					}
				});
			});
		}
	});

	window.LibraryItems = Backbone.Collection.extend({

		model: LibraryItem,

	});

	window.LibraryItemView = Backbone.View.extend({

		tagName: 'div',

		className: "book-item clearfix",

		template: _.template( $('#library-item-template').html() ),

		initialize: function() {
			_.bindAll(this, "render");
		},

		render: function() {
			var renderedContent = this.template({data: this.model.toJSON()});
			$(this.el).html(renderedContent);
			return this;
		},

		events: {
			"click .delete": function(e) {
				e.preventDefault();
				var confMessage;
				var selector = "#details-modal-" + this.model.get('key');
				confMessage  = "Are you sure you want to perminantly delete "
				confMessage += this.model.get('title');
				confMessage += "?";

				if(confirm(confMessage)) {
					$(selector).modal('hide');
					this.model.delete();
					this.remove();
				}
			},

			"click .read": function(e) {
				this.model.openInReader();
			}

		}
	});

	window.LibraryItemsView = Backbone.View.extend({
		tagName: 'div',

		id: "library-items-container",

		className: 'row-view clearfix',

		template: _.template( $('#library-items-template').html() ),

		initialize: function() {
			_.bindAll(this, "render");
			this.collection.bind('reset', this.render);
			this.collection.bind('add',   this.addOne, this);
		},

		render: function() {
			var collection = this.collection;
			var $container = $(this.el);
			$container.html(this.template({}));
			this.$('#empty-message').toggle(this.collection.isEmpty());

			collection.each(function(item) {
				var view = new LibraryItemView({
					model: item,
					collection: collection,
					id: item.get('id')
				});
				$container.append( view.render().el );

			});

			// i dunno if this should go here
			$('#library-books-list').html(this.el)
			return this;
		},

		addOne: function(book) {
			var view = new LibraryItemView({
				model: book,
				collection: this.collection,
				id: book.get('id')
			});
			// we are adding so this should always be hidden!
			this.$('#empty-message').toggle(false);
			$(this.el).append( view.render().el );
		},

		events: {

		}
	});

	window.ExtractItemView = Backbone.View.extend({

		el: $('#progress-container')[0],

		template: _.template( $('#extracting-item-template').html() ),

		initialize: function() {
			this.model.bind('change', this.render, this);
			this.model.bind("change:error", this.extractionFailed, this);
		},

		render: function() {
			var $el = $(this.el);
			if( this.model.get('extracting') ) {
				$el.html(this.template(this.model.toJSON()));
				$el.show("slow");
			}
			else {
				$el.hide("slow");
			}
			return this;
		},

		extractionFailed: function(msg) {
			alert(this.model.get("error"));
			this.model.set("extracting", false);
		},

	});

	// the options
	window.ReadiumOptions = Backbone.Model.extend({

		defaults: {
			hijack_epub_urls: true,
			verbose_unpacking: true
		},

		sync: function(method, model, options) {
			// "create" create the model on the server
			// "read" : read this model from the server and return it
			// "update" : update the model on the server with the argument
			// "delete" : delete the model from the server.
			var json;
			var key = "READIUM_OPTIONS"

			if(method === "read") {
				json = window.localStorage.getItem(key);
				if(json) {
					// DON'T CHANGE IF NOTHING IN STORAGE
					model.attributes = (json && JSON.parse(json)) || {};
				}
				else {
					options.error('Failed to load settings from local storage')
					return;
				}
			}

			if(method === "create" || method === "update") {
				window.localStorage.setItem(key, JSON.stringify(model.attributes) );

			}

			if(method === "delete") {
				// TODO: this is not deleting stuff...
				window.localStorage.removeItem(key);
			}
			options.success(model);

		}
	});

	window.ReadiumOptionsView = Backbone.View.extend({
		el: "#readium-options-modal",

		initialize: function() {
			this.model.on("change", this.render, this);
		},

		render: function() {
			var m = this.model;
			this.$('#verbose_unpacking').prop('checked', m.get("verbose_unpacking"));
			this.$('#hijack_epub_urls').prop('checked', m.get("hijack_epub_urls"));
		},

		events: {
    		"change #verbose_unpacking": "updateSettings",
    		"change #hijack_epub_urls": "updateSettings",
    		"click #save-settings-btn": "save"
  		},

  		updateSettings: function() {
  			var hijack = this.$('#hijack_epub_urls').prop('checked')
  			var unpack = this.$('#verbose_unpacking').prop('checked')

			this.model.set({"verbose_unpacking": unpack});
			this.model.set({"hijack_epub_urls": hijack});
  		},

  		save: function() {
  			this.model.save();
  		}

	});

	window.FilePickerView = Backbone.View.extend({
		el:"#add-book-modal",

		events: {
			"change #files": "handleFileSelect",
			"change #dir_input": "handleDirSelect",
			"click #url-button": "handleUrl",
		},

		show: function() {
			this.$el.modal('show');
		},

		hide: function() {
			this.$el.modal('hide');
		},

		resetForm: function() {

		},

		handleUrl: function(evt) {
			var input = document.getElementById('book-url');
			if(input.value === null || input.value.length < 1) {
				alert("invalid url, cannot process");
			}
			else {
				var url = input.value;
				// TODO check src filename
				var extractor = new Readium.Models.ZipBookExtractor({url: url, src_filename: url});
				this.beginExtraction(extractor);
			}
		},

		handleFileSelect: function(evt) {
			var files = evt.target.files; // FileList object
			var url = window.webkitURL.createObjectURL(files[0]);
			// TODO check src filename
			var extractor = new Readium.Models.ZipBookExtractor({url: url, src_filename: files[0].name});
			this.beginExtraction(extractor);
		},

		handleDirSelect: function(evt) {
			var dirpicker = evt.target; // FileList object
			var extractor = new Readium.Models.UnpackedBookExtractor({dir_picker: dirpicker});
			this.beginExtraction(extractor);

		},

		beginExtraction: function(extractor) {
			window.extract_view = new ExtractItemView({model: extractor});
			extractor.on("extraction_success", function() {
				var book = extractor.packageDoc.toJSON();
				window.Library.add(new window.LibraryItem(book));
				setTimeout(function() {
					chrome.tabs.create({url: "/views/viewer.html?book=" + book.key });
				}, 800);
			});
			extractor.extract();
			this.resetForm();
			this.hide();
		},

	});

	window.options = new ReadiumOptions;
	window.optionsView = new ReadiumOptionsView({model: window.options});
	window.options.fetch({
		success: function() {
			//optionsView.render();
		}
	});

	window.Library = new LibraryItems();
	window.lib_view = new LibraryItemsView({collection: window.Library});
	window.fp_view = new FilePickerView();

})(jQuery);

/*
var flash = function(text, type) {
	var className = "alert";
	if(type) {
		className += " alert-" + type;
	}
	$('#flash-container').
		html('
'+text+'
').
		removeClass().
		addClass(className);

}
*/

	_lawnchair = new Lawnchair(function() {
		this.all(function(all) {
			window.Library.reset(all);
		});
	});
	$("#block-view-btn").click(function(e) {
		$('#library-items-container').addClass("block-view").removeClass("row-view")
	});
	$("#row-view-btn").click(function(e) {
		$('#library-items-container').addClass("row-view").removeClass("block-view")
	});

	// TODO: use a Backbone router for this
	if(window.location.hash === "#options") {
		$('#readium-options-modal').modal('show')
	}
	/*
	$("#options-btn").click(function(e) {
		window.location = "/views/options.html";
	});
*/

});
<|endoftext|>


<commit_before>var expect = require("chai").expect;
var Util = require("../js/helpers/Util");

describe("Util", function () {

  describe("param", function () {

    it("returns a valid urlencoded query string", function () {
      var obj = {
        hello: "world",
        foo: "bar"
      };
      expect(Util.param(obj)).to.equal("hello=world&foo=bar");
    });

    it("handles spaces and other entities correctly", function () {
      var obj = {
        q: "is:open is:issue label:gui"
      };
      expect(Util.param(obj))
        .to.equal("q=is%3Aopen%20is%3Aissue%20label%3Agui");
    });

    it("handles bad input gracefully", function () {
      var obj = {};
      expect(Util.param(obj)).to.equal("");

      obj = [1, 2, 3];
      expect(Util.param(obj)).to.equal("0=1&1=2&2=3");

      obj = null;
      expect(Util.param(obj)).to.equal(null);

      obj = "already=foo&also=bar";
      expect(Util.param(obj)).to.equal("already=foo&also=bar");

    });

  });

  describe("extendObject", function () {

    it("returns a new object with merged properties", function () {
      var objA = {hello: "world", foo: "bar"};
      var objB = {hello: "there", baz: "foo"};
      var expectedResult = {hello: "there", foo: "bar", baz: "foo"};
      var result = Util.extendObject(objA, objB);
      expect(result).to.deep.equal(expectedResult);
    });

    it("returns a new object without modifying the source", function () {
      var objA = {hello: "world", foo: "bar"};
      var objB = {hello: "there", baz: "foo"};
      var result = Util.extendObject(objA, objB);
      expect(objA).to.deep.equal({hello: "world", foo: "bar"});
    });

    it("accepts several sources", function () {
      var objA = {hello: "world", foo: "bar"};
      var objB = {hello: "there", baz: "foo"};
      var objC = {id: null, flag: true};
      var expectedResult = {
        hello: "there",
        foo: "bar",
        baz: "foo",
        id: null,
        flag: true
      };

      var result = Util.extendObject(objA, objB, objC);
      expect(result).to.deep.equal(expectedResult);
    });

    it("always returns an object", function () {
      var expectedResult = {"0": "faz", "1": "bar"};
      var result = Util.extendObject(["foo", "bar"], ["faz"]);
      expect(result).to.deep.equal(expectedResult);
    });
  });

  describe("serializedArrayToDictionary", function () {

    it("converts a flat array to a dictionary", function () {
      var input = [
        {name: "A", value: 1},
        {name: "B", value: 2},
        {name: "C", value: 3}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": 1, "B": 2, "C": 3});
    });

    it("nests property names with a dot separator", function () {
      var input = [
        {name: "A", value: 1},
        {name: "B.A", value: 2},
        {name: "B.B", value: 3}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": 1, "B": {"A": 2, "B": 3}});
    });

    it("does not error on duplicate keys", function () {
      var input = [
        {name: "A", value: 1},
        {name: "A", value: 2}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": 2});
    });

    it("handles several levels of nesting", function () {
      var input = [
        {name: "A.B.C.D.E", value: 1}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": {"B": {"C": {"D": {"E": 1}}}}});
    });

    it("handles empty arrays correctly", function () {
      var input = [];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({});
    });

    it("handles the array notation", function () {
      var input = [
        {name: "A[0]", value: 1},
        {name: "A[1]", value: 2}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [1, 2]});
    });

    it("handles multiple objects inside arrays", function () {
      var input = [
        {name: "A[0].A", value: 1},
        {name: "A[1].B", value: 2}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [{"A": 1}, {"B": 2}]});
    });

    it("handles objects with multiple properties inside arrays", function () {
      var input = [
        {name: "A[0].A", value: 1},
        {name: "A[0].B", value: 2},
        {name: "A[1].A", value: 3},
        {name: "A[1].B", value: 4}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [{"A": 1, "B": 2}, {"A": 3, "B": 4}]});
    });

    it("handles nested arrays", function () {
      var input = [
        {name: "A[0].B[0].C", value: 1}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [{"B": [{"C": 1}]}]});
    });

    describe("malformed keys", function () {

      it("handles leading dots", function () {
        var input = [
          {name: ".A.B", value: 1}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": {"B": 1}});
      });

      it("handles trailing dots", function () {
        var input = [
          {name: "A.B.", value: 1}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": {"B": 1}});
      });

      it("handles duplicate dots", function () {
        var input = [
          {name: "A..B", value: 1}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": {"B": 1}});
      });

      it("handles missing array indices", function () {
        var input = [
          {name: "A[].B", value: 1},
          {name: "A[].B", value: 2}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": [{"B": 1}, {"B": 2}]});
      });

    });

  });

  describe("isArray", function () {

    it("array is an array", function () {
      expect(Util.isArray([])).to.be.true;
    });

    it("object is not an array", function () {
      expect(Util.isArray({})).to.be.false;
    });

  });

  describe("isNumber", function () {

    it("number is a number", function () {
      expect(Util.isNumber(1)).to.be.true;
      expect(Util.isNumber(2.3)).to.be.true;
      expect(Util.isNumber(-5)).to.be.true;
    });

    it("string is not a number", function () {
      expect(Util.isNumber("666")).to.be.false;
    });

  });

  describe("isString", function () {

    it("is a string", function () {
      expect(Util.isString("1")).to.be.true;
      expect(Util.isString("abc")).to.be.true;
    });

    it("is not a string", function () {
      expect(Util.isString(123)).to.be.false;
      expect(Util.isString(function () {})).to.be.false;
    });
  });

});
<commit_msg>Making tests more expressive<commit_after>var expect = require("chai").expect;
var Util = require("../js/helpers/Util");

describe("Util", function () {

  describe("param", function () {

    it("returns a valid urlencoded query string", function () {
      var obj = {
        hello: "world",
        foo: "bar"
      };
      expect(Util.param(obj)).to.equal("hello=world&foo=bar");
    });

    it("handles spaces and other entities correctly", function () {
      var obj = {
        q: "is:open is:issue label:gui"
      };
      expect(Util.param(obj))
        .to.equal("q=is%3Aopen%20is%3Aissue%20label%3Agui");
    });

    it("handles bad input gracefully", function () {
      var obj = {};
      expect(Util.param(obj)).to.equal("");

      obj = [1, 2, 3];
      expect(Util.param(obj)).to.equal("0=1&1=2&2=3");

      obj = null;
      expect(Util.param(obj)).to.equal(null);

      obj = "already=foo&also=bar";
      expect(Util.param(obj)).to.equal("already=foo&also=bar");

    });

  });

  describe("extendObject", function () {

    it("returns a new object with merged properties", function () {
      var objA = {hello: "world", foo: "bar"};
      var objB = {hello: "there", baz: "foo"};
      var expectedResult = {hello: "there", foo: "bar", baz: "foo"};
      var result = Util.extendObject(objA, objB);
      expect(result).to.deep.equal(expectedResult);
    });

    it("returns a new object without modifying the source", function () {
      var objA = {hello: "world", foo: "bar"};
      var objB = {hello: "there", baz: "foo"};
      var result = Util.extendObject(objA, objB);
      expect(objA).to.deep.equal({hello: "world", foo: "bar"});
    });

    it("accepts several sources", function () {
      var objA = {hello: "world", foo: "bar"};
      var objB = {hello: "there", baz: "foo"};
      var objC = {id: null, flag: true};
      var expectedResult = {
        hello: "there",
        foo: "bar",
        baz: "foo",
        id: null,
        flag: true
      };

      var result = Util.extendObject(objA, objB, objC);
      expect(result).to.deep.equal(expectedResult);
    });

    it("always returns an object", function () {
      var expectedResult = {"0": "faz", "1": "bar"};
      var result = Util.extendObject(["foo", "bar"], ["faz"]);
      expect(result).to.deep.equal(expectedResult);
    });
  });

  describe("serializedArrayToDictionary", function () {

    it("converts a flat array to a dictionary", function () {
      var input = [
        {name: "A", value: 1},
        {name: "B", value: 2},
        {name: "C", value: 3}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": 1, "B": 2, "C": 3});
    });

    it("nests property names with a dot separator", function () {
      var input = [
        {name: "A", value: 1},
        {name: "B.A", value: 2},
        {name: "B.B", value: 3}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": 1, "B": {"A": 2, "B": 3}});
    });

    it("does not error on duplicate keys", function () {
      var input = [
        {name: "A", value: 1},
        {name: "A", value: 2}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": 2});
    });

    it("handles several levels of nesting", function () {
      var input = [
        {name: "A.B.C.D.E", value: 1}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": {"B": {"C": {"D": {"E": 1}}}}});
    });

    it("handles empty arrays correctly", function () {
      var input = [];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({});
    });

    it("handles the array notation", function () {
      var input = [
        {name: "A[0]", value: 1},
        {name: "A[1]", value: 2}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [1, 2]});
    });

    it("handles multiple objects inside arrays", function () {
      var input = [
        {name: "A[0].A", value: 1},
        {name: "A[1].B", value: 2}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [{"A": 1}, {"B": 2}]});
    });

    it("handles objects with multiple properties inside arrays", function () {
      var input = [
        {name: "A[0].A", value: 1},
        {name: "A[0].B", value: 2},
        {name: "A[1].A", value: 3},
        {name: "A[1].B", value: 4}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [{"A": 1, "B": 2}, {"A": 3, "B": 4}]});
    });

    it("handles nested arrays", function () {
      var input = [
        {name: "A[0].B[0].C", value: 1}
      ];
      var output = Util.serializedArrayToDictionary(input);
      expect(output).to.deep.equal({"A": [{"B": [{"C": 1}]}]});
    });

    describe("malformed keys", function () {

      it("handles leading dots", function () {
        var input = [
          {name: ".A.B", value: 1}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": {"B": 1}});
      });

      it("handles trailing dots", function () {
        var input = [
          {name: "A.B.", value: 1}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": {"B": 1}});
      });

      it("handles duplicate dots", function () {
        var input = [
          {name: "A..B", value: 1}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": {"B": 1}});
      });

      it("handles missing array indices", function () {
        var input = [
          {name: "A[].B", value: 1},
          {name: "A[].B", value: 2}
        ];
        var output = Util.serializedArrayToDictionary(input);
        expect(output).to.deep.equal({"A": [{"B": 1}, {"B": 2}]});
      });

    });

  });

  describe("isArray", function () {

    it("array is an array", function () {
      expect(Util.isArray([])).to.be.true;
    });

    it("object is not an array", function () {
      expect(Util.isArray({})).to.be.false;
    });

  });

  describe("isNumber", function () {

    it("detects numbers", function () {
      expect(Util.isNumber(1)).to.be.true;
      expect(Util.isNumber(2.3)).to.be.true;
      expect(Util.isNumber(-5)).to.be.true;
    });

    it("string is not a number", function () {
      expect(Util.isNumber("666")).to.be.false;
    });

  });

  describe("isString", function () {

    it("detects strings", function () {
      expect(Util.isString("1")).to.be.true;
      expect(Util.isString("abc")).to.be.true;
    });

    it("number is not a string", function () {
      expect(Util.isString(123)).to.be.false;
    });

    it("function is not a string", function () {
      expect(Util.isString(function () {})).to.be.false;
    });

  });

});
<|endoftext|>