pix-angular-filebrowser
Version:
File browser for Web2
289 lines (243 loc) • 11 kB
JavaScript
var _ = require("lodash");
function FileBrowserService(
$http, $q, $translate, $log, APP_SETTINGS, LOADING_OBJECT,
PIXFolder, ItemsCollectionModel, NotificationsService, SessionService) {
// PRIVATE variables
var items = [];
var isInitialized = false;
var root;
/**
* Sets the items list in place so our scope reference stays intact
*
* @param val {array} Array of items to set the items array to
*/
function setItems(val) {
items.length = 0;
addItems(val);
}
function addItems(val, index) {
index = index || 0;
var args = [index, 0].concat(val);
Array.prototype.splice.apply(items, args);
}
function replaceItems(val, index) {
index = index || 0;
var args = [index, val.length].concat(val);
Array.prototype.splice.apply(items, args);
}
/**
* Expands a given object and places it in the proper place of the data array.
*
* @param item {PIXContainer} the item to expand
*/
function expand(item) {
// set the parent state to loading.
// call expand on the item to load the initial set of data
return item.expand()
.then(function() {
// set up the attributes that are needed for the children (tree depth and parent)
var childAttrs = {
'$$level': (item.$$level + 1) || 1,
'$$parentId': item.id
}
// grab the index of the parent
var parentIndex = items.indexOf(item);
// stores the current index our list is at
var index = 0;
// create a copy of the data that has come in so we can safely manipuate the array
var results = _.clone(item.contentsCollectionModel.collection);
// walk through the list of all children for this item (both currently loaded and that to be loaded)
var children = _.map(Array(item.contentsCollectionModel.info.total), function() {
// if we still have data from the initial load use it
if (results.length > 0) {
index++;
return _.defaults(results.shift(), childAttrs);
}
// otherwise add a loading object
else {
return _.defaults({
id: _.uniqueId('tmpFile')
}, childAttrs, LOADING_OBJECT)
}
});
// add the children to the grid data array
addItems(children, parentIndex + 1);
/**
* Checks if we have the entire set of children and if not kicks off another request
*
* @returns {boolean} whether or not more data is available
*/
function loadMoreIfNeeded() {
// if there are more items we need to request
if (item.contentsCollectionModel.cacheSettings.hasOutstandingRecords) {
// set our page size to the max
item.contentsCollectionModel.cacheSettings.pageSize = 500;
//requests the next page of data
item.contentsCollectionModel.cache.getNextPage().then(onNextPage);
return true;
}
return false;
}
/**
* The callback for a successful load of the next page of data
*/
function onNextPage() {
// grab the grid index of the last item we added from our children collection
var scopeIndex = items.indexOf(item.contentsCollectionModel.collection[index - 1]) + 1;
// loop through the new items in the collection and add our tree specific attributes
var newItems = _.map(item.contentsCollectionModel.collection.slice(index), function(child) {
return _.defaults(child, childAttrs);
});
// set the index pointer to the end of the child collection
index = item.contentsCollectionModel.collection.length;
// splice our new data onto the main grid data array using the scopeIndex we calculated
replaceItems(newItems, scopeIndex);
// see if we need to load more
loadMoreIfNeeded();
}
// try to load page 2 of data (and start the lazy load chain)
loadMoreIfNeeded();
});
}
/**
* Collapses an item by removing it's children from the grid data array
*
* @param item {PIXContainer} the item to collapse
*/
function collapse(item) {
// grab the level of the parent
var parentLevel = item.$$level || 0;
// two variables to track the indexes of where the children start and end
var index;
var startIndex;
// grab the index of the first child and stash it for latter
index = startIndex = items.indexOf(item) + 1;
// grab the first child item
var nextItem = items[index];
// walk through the list until we hit a sibling of the parent (or the end of the list)
while (nextItem && nextItem.$$level > parentLevel) {
// incr our index to keep track of the end
index++;
// if this child is expanded, collapse it
if (nextItem.$$expanded) {
nextItem.$$expanded = false;
}
// grab the next item and go back around
nextItem = items[index];
}
// remove our children using our calculated indexes
items.splice(startIndex, index - startIndex);
}
/**
* Sorts the internal items list
*
* @param accessors {function[]} an array of functions to access the data for the sort
* @param directions {boolean[]} an array of sort directions for each column
*/
function sort(accessors, directions) {
// build an object of rows group by their parent
var hierarchy = _.groupBy(items, function(item) {
return item.$$parentId || 'root';
});
// first sort by the root elements
var sorted = _.sortByOrder(hierarchy.root, accessors, directions);
// remove the root from our list
delete hierarchy.root;
// deal with the children
for (var parentId in hierarchy) {
// grab the list of children under the current parent
var children = hierarchy[parentId];
// sort the child list
children = _.sortByOrder(children, accessors, directions);
// find our parent in the currently sorted list
var parentIndex = _.findIndex(sorted, function(item) {
return item.id === parentId;
});
// add our children after their parent
var args = [parentIndex + 1, 0].concat(children);
Array.prototype.splice.apply(sorted, args)
}
setItems(sorted);
}
/**
* Initialize the data if the service has not already been initialized
* @returns {*}
*/
function init() {
return $q(function(resolve, reject) {
if (isInitialized) {
resolve();
} else {
SessionService
.whenProjectOrSessionEnded
.then(destroy);
$http.get(APP_SETTINGS.HOST + '/' + APP_SETTINGS.API_ROOT + '/folders/root')
.success(function(data) {
// hydrate the server data into a PIXFolder object
root = new PIXFolder(new ItemsCollectionModel());
root.populateSelf(data);
// set our page size to the maximum
root.getCollectionModel().cacheSettings.pageSize = 500;
// expand root folder to get it's children
root.expand()
.then(function() {
setItems(root.contentsCollectionModel.collection);
resolve();
})
.catch(function() {
NotificationsService.flashWarnings($translate('FileBrowser.Error.Expand', root));
reject();
});
})
.error(function() {
NotificationsService.flashWarnings($translate('FileBrowser.Error.Root', root));
reject();
});
}
});
}
/**
* Clear the service cache
*/
function destroy() {
$log.debug('destroying file browser cache: session ended or project changed');
root.collapse(true);
root = null;
items = [];
isInitialized = false;
}
// setup our public read-only properties
Object.defineProperties(this, {
items : {
get: function() {
return items;
}
},
expand: {
value: expand,
writable: false
},
collapse: {
value: collapse,
writable: false
},
sort: {
value: sort,
writable: false
},
init: {
value: init,
writable: false
}
});
}
module.exports = angular
.module('PIX.FileBrowser.Service', [])
.constant('LOADING_OBJECT', {
viewData: {
fields: {
name: 'Loading'
}
}
})
.service('FileBrowserService', FileBrowserService);