sortModelGroups.js |
|
uxDatagrid v.1.0.3 (c) 2014, WebUX https://github.com/webux/ux-angularjs-datagrid License: MIT. |
(function(exports, global){
exports.datagrid.events.ON_BEFORE_SORT = "datagrid:onBeforeSort";
exports.datagrid.events.ON_AFTER_SORT = "datagrid:onAfterSort";
exports.datagrid.events.ON_BEFORE_TOGGLE_SORT = "datagrid:onBeforeToggleSort";
exports.datagrid.events.ON_AFTER_TOGGLE_SORT = "datagrid:onAfterToggleSort";
exports.datagrid.events.CLEAR_SORTS = "datagrid:clearSorts";
exports.datagrid.events.CLEAR_ALL_SORTS = "datagrid:clearAllSorts";
angular.module("ux").service("sortStatesModel", [ "$location", "$rootScope", function($location, $rootScope) {
|
ColumnStates for sorting. Singleton. Keeps track of weather a column is sorted or not. It persists based on the path. Type
Object
************************************************************************************
|
exports.datagrid.sortStatesModel = function() {
var result = exports.logWrapper("columnSortStatesModel", {}, "blue", function() {
$rootScope.$emit.apply($rootScope, arguments);
}), sortOptions = {
ASC: "asc",
DESC: "desc",
NONE: "none"
|
}, lastPath = "", states = {}, multipleStates = {}, ignoreParamsInPath = {}, Type
Boolean
|
defaultAllowMultipleStates = false;
|
function isPrivate(prop) {
return prop.charAt(0) === "$";
}
|
|
function getCurrentPath() {
return $location.path();
}
|
|
function getCurrentPathWithoutParams() {
return getCurrentPath().split("?").shift();
}
|
|
function getPath() {
var path;
if (getIgnoreParamsInPath()) {
path = getCurrentPathWithoutParams();
} else {
path = getCurrentPath();
}
if (path !== lastPath) {
result.log("getPath changed from %s to %s", lastPath, path);
lastPath = path;
}
return path;
}
|
|
Check for allowing of multipleStates on a per path basis or fall back on the defaultAllowMultipleStates Returns
Boolean
{Boolean}
|
function getAllowMultipleStates() {
var result = multipleStates[getPath()];
if (result === true || result === false) {
return result;
}
return defaultAllowMultipleStates;
}
|
Set the allow multiple states on a per path basis. Params
value
Boolean
path
String=
|
function setAllowMultipleStates(value, path) {
path = path || getPath();
if (value !== undefined) {
result.log("setAllowMultipleStates %s", value);
multipleStates[path] = value === true;
}
return !!multipleStates[path];
}
|
Return weather params are ignored in storing path values. Returns
Boolean
{Boolean}
|
function getIgnoreParamsInPath() {
return ignoreParamsInPath[getCurrentPathWithoutParams()] === true;
}
|
function setIgnoreParamsInPath(value) {
result.log("setIgnoreParamsInPath %s", value);
ignoreParamsInPath[getCurrentPathWithoutParams()] = value;
}
|
|
function hasPathState(path) {
path = path || getPath();
return !!states[path];
}
|
|
function getPathState(path) {
path = path || getPath();
if (!states[path]) {
states[path] = {
$dirty: false,
$path: path,
$order: []
};
}
return states[path];
}
|
|
Override all of the path states. Any columnNames not passed are set to none if not allow multiples. Params
pathState
Object
|
function setPathState(pathState) {
var columnName, currentPathState = getPathState();
result.log("setPathState %s to %s", currentPathState, pathState);
setIgnoreParamsInPath(true);
for (columnName in pathState) {
if (Object.prototype.hasOwnProperty.apply(pathState, [ columnName ]) && pathState[columnName] !== currentPathState[columnName] && !isPrivate(columnName)) {
setState(columnName, pathState[columnName], currentPathState);
}
}
}
|
function getState(columnName) {
var pathState = getPathState(getPath());
if (pathState[columnName] === undefined) {
pathState[columnName] = sortOptions.NONE;
}
return pathState[columnName];
}
|
|
function setState(columnName, state, pathState) {
var index, prevState;
pathState = pathState || getPathState(getPath());
if (!isPrivate(columnName) && pathState[columnName] !== state) {
prevState = pathState[columnName];
if (getAllowMultipleStates(pathState.$path)) {
index = pathState.$order.indexOf(columnName);
if (index !== -1) {
pathState.$order.splice(index, 1);
}
}
if (state !== sortOptions.NONE) {
if (!getAllowMultipleStates()) {
clear(pathState);
}
pathState.$order.push(columnName);
}
pathState[columnName] = state;
if (!prevState && state === sortOptions.NONE) {
return;
}
dirtyState(pathState);
}
}
|
|
converts the path state into a string key. ex("description:asc|index:desc") Params
pathState
Object=
|
function createKeyFromStates(pathState) {
pathState = pathState || getPathState(getPath());
var combo = {
text: "",
pathState: pathState
};
exports.each(pathState.$order, createKeyFromState, combo);
return combo.text;
}
|
converts the state into a key string. ex("description:asc") Params
columnName
index
list
combo
|
function createKeyFromState(columnName, index, list, combo) {
if (!isPrivate(columnName)) {
combo.text += (combo.text.length ? "|" : "") + columnName + ":" + combo.pathState[columnName];
}
}
|
function toggle(columnName) {
var state = getState(columnName), nextState = getNextState(state);
result.info("toggle %s from %s to %s", columnName, state, nextState);
setState(columnName, nextState);
dirtyState();
}
|
|
function dirtyState(pathState) {
pathState = pathState || getPathState(getPath());
result.log("dirtyState %s", pathState);
pathState.$dirty = true;
}
|
|
function clearDirty(pathState) {
pathState = pathState || getPathState(getPath());
result.log("clearDirty %s", pathState);
pathState.$dirty = false;
}
|
|
calculate the next state would be given a currentState. Used for toggling states. Params
state
String
Returns
String
{String}
|
function getNextState(state) {
var result = sortOptions.ASC;
switch (state) {
case sortOptions.NONE:
result = sortOptions.ASC;
break;
case sortOptions.ASC:
result = sortOptions.DESC;
break;
case sortOptions.DESC:
result = sortOptions.NONE;
break;
}
return result;
}
|
function hasDirtySortState(pathState) {
pathState = pathState || getPathState(getPath());
return pathState.$dirty;
}
|
|
function cleanSortValue(value) {
|
|
undefined and null should be compared as an empty string. |
var result = value === undefined || value === null ? "" : value;
if (typeof result === "string") {
return result.toLowerCase();
}
return result;
}
|
function getLocale() {
return "en";
}
|
|
function sortValueCompare(a, b) {
a = cleanSortValue(a);
b = cleanSortValue(b);
return a > b ? 1 : a < b ? -1 : 0;
}
|
|
function sortNone(a, b) {
return 0;
}
|
|
function createAscSort(property) {
return function asc(a, b) {
var av = a[property], bv = b[property];
return sortValueCompare(av, bv);
};
}
|
|
function createDescSort(property) {
return function desc(a, b) {
var av = a[property], bv = b[property];
return -sortValueCompare(av, bv);
};
}
|
|
function clear(pathState) {
var i;
pathState = pathState || getPathState(getPath());
pathState.$order.length = 0;
for (i in pathState) {
if (Object.prototype.hasOwnProperty.apply(pathState, [ i ]) && !isPrivate(i) && pathState[i] !== sortOptions.NONE) {
pathState[i] = sortOptions.NONE;
}
}
}
|
|
function clearAll() {
states = {};
multipleStates = {};
}
result.getPath = getPath;
result.getAllowMultipleStates = getAllowMultipleStates;
result.setAllowMultipleStates = setAllowMultipleStates;
result.getIgnoreParamsInPath = getIgnoreParamsInPath;
result.setIgnoreParamsInPath = setIgnoreParamsInPath;
result.hasPathState = hasPathState;
result.getPathState = getPathState;
result.setPathState = setPathState;
result.getState = getState;
result.setState = setState;
result.toggle = toggle;
result.dirtyState = dirtyState;
result.clearDirty = clearDirty;
result.sortNone = sortNone;
result.createAscSort = createAscSort;
result.createDescSort = createDescSort;
result.hasDirtySortState = hasDirtySortState;
result.createKeyFromStates = createKeyFromStates;
result.isPrivate = isPrivate;
result.getLocale = getLocale;
result.clear = clear;
result.clearAll = clearAll;
result.sortOptions = sortOptions;
return result;
}();
return exports.datagrid.sortStatesModel;
} ]);
|
|
angular.module("ux").factory("sortModel", [ "sortStatesModel", function(sortStatesModel) {
return function sortModel(inst) {
|
|
cache is the stored sort values. It needs to be cleared if the data changes. |
var result = exports.logWrapper("sortModel", {}, "blue", inst.dispatch), sorts = {}, original, cache = {}, lastSortResult;
|
add a column so that it's sort state can be toggled and used. Params
name
String
methods
Object
|
result.addSortColumn = function addSortColumn(name, methods) {
sorts[name] = methods;
var pathState = sortStatesModel.getPathState();
pathState[name] = pathState[name] || sortStatesModel.sortOptions.NONE;
};
|
result.getCache = function getCache(key) {
return cache[key];
};
|
|
result.setCache = function setCache(key, value) {
cache[key] = value;
};
|
|
Apply the sorts to the array. Pull from cache if it exists. Params
ary
Array
sortOptions
Object
|
result.applySorts = function applySorts(ary, sortOptions) {
var pathStateRef = sortStatesModel.getPathState(), currentPathState = angular.copy(pathStateRef);
if (sortOptions) {
result.log("apply sortOptions");
sortStatesModel.setPathState(sortOptions);
}
if (original !== ary || sortStatesModel.hasDirtySortState(pathStateRef)) {
original = ary;
if (!original) {
lastSortResult = original;
return lastSortResult;
}
result.setCache("", original);
|
the original is always without any sort options. |
if (!result.$processing) {
result.$processing = true;
var key = sortStatesModel.createKeyFromStates(pathStateRef), event, pathState = angular.copy(pathStateRef);
|
clone so they cannot mess with the data directly. |
result.info("applySorts %s", key);
event = inst.dispatch(exports.datagrid.events.ON_BEFORE_SORT, key, currentPathState, pathState);
|
prevent default on event to prevent sort. |
if (!event.defaultPrevented) {
if (!result.getCache(key) || result.getCache(key).length !== original.length) {
result.log(" store sort %s", key);
result.setCache(key, original && original.slice(0) || []);
|
clone it |
ux.each(pathState.$order, applyListSort, {
grouped: inst.grouped,
pathState: pathState,
ary: result.getCache(key)
});
} else {
result.log(" pull sort from cache");
}
lastSortResult = result.getCache(key);
sortStatesModel.clearDirty(pathStateRef);
} else {
|
TODO: need to unit test this to make sure it works with async sort. |
lastSortResult = original;
}
result.$processing = false;
inst.dispatch(exports.datagrid.events.ON_AFTER_SORT, key, pathState, currentPathState);
}
}
return lastSortResult;
};
|
function sortArray(ary, columnName, pathState) {
var state = pathState[columnName];
if (state && sorts[columnName]) {
ux.util.array.sort(ary, sorts[columnName][state]);
}
return ary;
}
|
|
Take into consideration for grouped data and apply the appropriate sorts for the array. Params
columnName
String
index
Number
list
Array
data
Object
|
function applyListSort(columnName, index, list, data) {
var i, len;
if (data.grouped && data.ary.length && data.ary[0].hasOwnProperty(data.grouped)) {
if (!result.sortGroups) {
len = data.ary.length;
for (i = 0; i < len; i += 1) {
data.ary[i] = angular.extend({}, data.ary[i]);
|
shallow copy |
data.ary[i][data.grouped] = sortArray(data.ary[i][data.grouped].slice(0), columnName, data.pathState);
}
} else {
sortArray(data.ary, columnName, data.pathState);
}
} else {
sortArray(data.ary, columnName, data.pathState);
}
}
|
result.isApplied = function isApplied(name, methodName) {
return sortStatesModel.getPathState()[name] === methodName;
};
|
|
result.getSortStateOf = function getSortStateOf(name) {
return sortStatesModel.getPathState()[name];
};
|
|
result.multipleSort = sortStatesModel.setAllowMultipleStates;
|
|
result.getSortKey = sortStatesModel.createKeyFromStates;
|
|
result.sortGroups = false;
|
|
Based on the options passed, automatically add sort columns for those and create states. If the states already exist apply those states on the render. |
function addSortsFromOptions() {
var i, methods, alreadyHasState = sortStatesModel.hasPathState(), pathState = sortStatesModel.getPathState();
if (inst.options.sorts) {
for (i in inst.options.sorts) {
if (typeof inst.options.sorts[i] === "object") {
sortStatesModel.setState(i, inst.options.sorts[i].value, pathState);
|
value is the default sort state. |
methods = inst.options.sorts[i];
} else {
if (!alreadyHasState) {
sortStatesModel.setState(i, inst.options.sorts[i], pathState);
}
methods = {
asc: sortStatesModel.createAscSort(i),
desc: sortStatesModel.createDescSort(i),
none: sortStatesModel.sortNone
};
}
result.addSortColumn(i, methods);
}
}
if (inst.options.sortGroups) {
result.sortGroups = true;
}
}
|
result.toggleSort = function toggleSort(name) {
result.log("toggleSort %s", name);
inst.dispatch(exports.datagrid.events.ON_BEFORE_TOGGLE_SORT, name);
if (inst.creepRenderModel) {
inst.creepRenderModel.stop();
}
sortStatesModel.toggle(name);
|
|
|
result.applySorts(original);
inst.dispatch(exports.datagrid.events.ON_AFTER_TOGGLE_SORT, name);
};
|
result.clear = function clear() {
cache = {};
};
|
|
result.destroy = function destroy() {
result = null;
lastSortResult = null;
sorts = null;
cache = null;
original = null;
inst.sortModel = null;
inst = null;
};
inst.unwatchers.push(inst.scope.$on(exports.datagrid.events.CLEAR_SORTS, exports.datagrid.sortStatesModel.clear));
inst.unwatchers.push(inst.scope.$on(exports.datagrid.events.CLEAR_ALL_SORTS, exports.datagrid.sortStatesModel.clearAll));
inst.sortModel = result;
addSortsFromOptions();
return inst;
};
} ]);
}(this.ux = this.ux || {}, function() {return this;}()));
|