ux-datagrid-sortModel.js |
|
! ux-angularjs-datagrid v.1.4.11 (c) 2016, Obogo https://github.com/obogo/ux-angularjs-datagrid License: MIT. |
(function (exports, global) {
if (typeof define === "function" && define.amd) {
define(exports);
} else if (typeof module !== "undefined" && module.exports) {
module.exports = exports;
} else {
global.ux = exports;
}
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 api = exports.logWrapper("columnSortStatesModel", {}, "blue", function() {
exports.util.apply($rootScope.$emit, $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 api.getCurrentPath().split("?").shift();
}
|
|
function getPath() {
var path;
if (api.getIgnoreParamsInPath()) {
path = api.getCurrentPathWithoutParams();
} else {
path = api.getCurrentPath();
}
if (path !== lastPath) {
api.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[api.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 || api.getPath();
if (value !== undefined) {
api.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[api.getCurrentPathWithoutParams()] === true;
}
|
function setIgnoreParamsInPath(value) {
api.log("setIgnoreParamsInPath %s", value);
ignoreParamsInPath[api.getCurrentPathWithoutParams()] = value;
}
|
|
function hasPathState(path) {
path = path || api.getPath();
return !!states[path];
}
|
|
function getPathState(path) {
path = path || api.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 = api.getPathState();
api.log("setPathState %s to %s", currentPathState, pathState);
api.setIgnoreParamsInPath(true);
for (columnName in pathState) {
if (exports.util.apply(Object.prototype.hasOwnProperty, pathState, [ columnName ]) && pathState[columnName] !== currentPathState[columnName] && !isPrivate(columnName)) {
api.setState(columnName, pathState[columnName], currentPathState);
}
}
}
|
function getState(columnName) {
var pathState = api.getPathState(api.getPath());
if (pathState[columnName] === undefined) {
pathState[columnName] = sortOptions.NONE;
}
return pathState[columnName];
}
|
|
function setState(columnName, state, pathState) {
var index, prevState;
pathState = pathState || api.getPathState(api.getPath());
if (!api.isPrivate(columnName) && pathState[columnName] !== state) {
prevState = pathState[columnName];
if (api.getAllowMultipleStates(pathState.$path)) {
index = pathState.$order.indexOf(columnName);
if (index !== -1) {
pathState.$order.splice(index, 1);
}
}
if (state !== sortOptions.NONE) {
if (!api.getAllowMultipleStates()) {
api.clear(pathState);
}
pathState.$order.push(columnName);
}
pathState[columnName] = state;
if (!prevState && state === sortOptions.NONE) {
return;
}
api.dirtyState(pathState);
}
}
|
|
converts the path state into a string key. ex("description:asc|index:desc") Params
pathState
Object=
|
function createKeyFromStates(pathState) {
pathState = pathState || api.getPathState(api.getPath());
var combo = {
text: "",
pathState: pathState
};
exports.each(pathState.$order, api.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 (!api.isPrivate(columnName)) {
combo.text += (combo.text.length ? "|" : "") + columnName + ":" + combo.pathState[columnName];
}
}
|
function toggle(columnName) {
var state = api.getState(columnName), nextState = api.getNextState(state);
api.info("toggle %s from %s to %s", columnName, state, nextState);
api.setState(columnName, nextState);
api.dirtyState();
}
|
|
function dirtyState(pathState) {
pathState = pathState || api.getPathState(api.getPath());
api.log("dirtyState %s", pathState);
pathState.$dirty = true;
}
|
|
function clearDirty(pathState) {
pathState = pathState || api.getPathState(api.getPath());
api.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 || api.getPathState(api.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 = api.cleanSortValue(a);
b = api.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 api.sortValueCompare(av, bv);
};
}
|
|
function createDescSort(property) {
return function desc(a, b) {
var av = a[property], bv = b[property];
return -api.sortValueCompare(av, bv);
};
}
|
|
function clear(pathState) {
var i;
pathState = pathState || api.getPathState(api.getPath());
pathState.$order.length = 0;
for (i in pathState) {
if (exports.util.apply(Object.prototype.hasOwnProperty, pathState, [ i ]) && !api.isPrivate(i) && pathState[i] !== sortOptions.NONE) {
pathState[i] = sortOptions.NONE;
}
}
}
|
|
function clearAll() {
states = {};
multipleStates = {};
}
api.getPath = getPath;
api.getCurrentPath = getCurrentPath;
api.getCurrentPathWithoutParams = getCurrentPathWithoutParams;
api.sortValueCompare = sortValueCompare;
api.cleanSortValue = cleanSortValue;
api.getNextState = getNextState;
api.createKeyFromState = createKeyFromState;
api.getAllowMultipleStates = getAllowMultipleStates;
api.setAllowMultipleStates = setAllowMultipleStates;
api.getIgnoreParamsInPath = getIgnoreParamsInPath;
api.setIgnoreParamsInPath = setIgnoreParamsInPath;
api.hasPathState = hasPathState;
api.getPathState = getPathState;
api.setPathState = setPathState;
api.getState = getState;
api.setState = setState;
api.toggle = toggle;
api.dirtyState = dirtyState;
api.clearDirty = clearDirty;
api.sortNone = sortNone;
api.createAscSort = createAscSort;
api.createDescSort = createDescSort;
api.hasDirtySortState = hasDirtySortState;
api.createKeyFromStates = createKeyFromStates;
api.isPrivate = isPrivate;
api.getLocale = getLocale;
api.clear = clear;
api.clearAll = clearAll;
api.sortOptions = sortOptions;
return api;
}();
return exports.datagrid.sortStatesModel;
} ]);
|
|
angular.module("ux").factory("sortModel", [ "sortStatesModel", function(sortStatesModel) {
return [ "inst", 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), sorts = {}, original, cache = {}, options = inst.options.sortModel || {}, 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
clear
Boolean=
|
result.applySorts = function applySorts(ary, sortOptions, clear) {
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;
}
if (clear) {
result.clear();
}
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);
if (options.enableCache === false) {
result.clear();
}
} 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 (!options.groupSort && data.grouped && data.ary.length && data.ary[0].hasOwnProperty(data.grouped)) {
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);
}
}
|
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;
|
|
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();
options.sorts = options.sorts || inst.options.sorts;
if (options.sorts) {
for (i in options.sorts) {
if (typeof options.sorts[i] === "object") {
sortStatesModel.setState(i, options.sorts[i].value, pathState);
|
value is the default sort state. |
methods = options.sorts[i];
} else {
if (!alreadyHasState) {
sortStatesModel.setState(i, options.sorts[i], pathState);
}
methods = {
asc: sortStatesModel.createAscSort(i),
desc: sortStatesModel.createDescSort(i),
none: sortStatesModel.sortNone
};
}
result.addSortColumn(i, methods);
}
}
}
|
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.setSortStateOf = function(name, state) {
if (state === "none" || state === "asc" || state === "desc") {
sortStatesModel.setState(name, state);
}
};
|
|
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;}()));
|