TinySort.tsort

A jQuery plugin to sort child nodes by (sub) contents or attributes.

Detta skript bör inte installeras direkt. Det är ett bibliotek för andra skript att inkludera med meta-direktivet // @require https://update.greatest.deepsurf.us/scripts/6883/27466/TinySorttsort.js

  1. /*! TinySort.tsort
  2. * Copyright (c) 2008-2013 Ron Valstar http://tinysort.sjeiti.com/
  3. *
  4. * Dual licensed under the MIT and GPL licenses:
  5. * http://www.opensource.org/licenses/mit-license.php
  6. * http://www.gnu.org/licenses/gpl.html
  7. *//*
  8. * Description:
  9. * A jQuery plugin to sort child nodes by (sub) contents or attributes.
  10. *
  11. * Contributors:
  12. * brian.gibson@gmail.com
  13. * michael.thornberry@gmail.com
  14. *
  15. * Usage:
  16. * $("ul#people>li").tsort();
  17. * $("ul#people>li").tsort("span.surname");
  18. * $("ul#people>li").tsort("span.surname",{order:"desc"});
  19. * $("ul#people>li").tsort({place:"end"});
  20. * $("ul#people>li").tsort("span.surname",{order:"desc"},span.name");
  21. *
  22. * Change default like so:
  23. * $.tinysort.defaults.order = "desc";
  24. *
  25. */
  26. ;(function($,undefined) {
  27. 'use strict';
  28. // private vars
  29. var fls = !1 // minify placeholder
  30. ,nll = null // minify placeholder
  31. ,prsflt = parseFloat // minify placeholder
  32. ,mathmn = Math.min // minify placeholder
  33. ,rxLastNr = /(-?\d+\.?\d*)$/g // regex for testing strings ending on numbers
  34. ,rxLastNrNoDash = /(\d+\.?\d*)$/g // regex for testing strings ending on numbers ignoring dashes
  35. ,aPluginPrepare = []
  36. ,aPluginSort = []
  37. ,isString = function(o){return typeof o=='string';}
  38. ,loop = function(array,func){
  39. var l = array.length
  40. ,i = l
  41. ,j;
  42. while (i--) {
  43. j = l-i-1;
  44. func(array[j],j);
  45. }
  46. }
  47. // Array.prototype.indexOf for IE (issue #26) (local variable to prevent unwanted prototype pollution)
  48. ,fnIndexOf = Array.prototype.indexOf||function(elm) {
  49. var len = this.length
  50. ,from = Number(arguments[1])||0;
  51. from = from<0?Math.ceil(from):Math.floor(from);
  52. if (from<0) from += len;
  53. for (;from<len;from++){
  54. if (from in this && this[from]===elm) return from;
  55. }
  56. return -1;
  57. }
  58. ;
  59. //
  60. // init plugin
  61. $.tinysort = {
  62. id: 'TinySort'
  63. ,version: '1.5.6'
  64. ,copyright: 'Copyright (c) 2008-2013 Ron Valstar'
  65. ,uri: 'http://tinysort.sjeiti.com/'
  66. ,licensed: {
  67. MIT: 'http://www.opensource.org/licenses/mit-license.php'
  68. ,GPL: 'http://www.gnu.org/licenses/gpl.html'
  69. }
  70. ,plugin: (function(){
  71. var fn = function(prepare,sort){
  72. aPluginPrepare.push(prepare); // function(settings){doStuff();}
  73. aPluginSort.push(sort); // function(valuesAreNumeric,sA,sB,iReturn){doStuff();return iReturn;}
  74. };
  75. // expose stuff to plugins
  76. fn.indexOf = fnIndexOf;
  77. return fn;
  78. })()
  79. ,defaults: { // default settings
  80.  
  81. order: 'asc' // order: asc, desc or rand
  82.  
  83. ,attr: nll // order by attribute value
  84. ,data: nll // use the data attribute for sorting
  85. ,useVal: fls // use element value instead of text
  86.  
  87. ,place: 'start' // place ordered elements at position: start, end, org (original position), first
  88. ,returns: fls // return all elements or only the sorted ones (true/false)
  89.  
  90. ,cases: fls // a case sensitive sort orders [aB,aa,ab,bb]
  91. ,forceStrings:fls // if false the string '2' will sort with the value 2, not the string '2'
  92.  
  93. ,ignoreDashes:fls // ignores dashes when looking for numerals
  94.  
  95. ,sortFunction: nll // override the default sort function
  96. }
  97. };
  98. $.fn.extend({
  99. tinysort: function() {
  100. var i,j,l
  101. ,oThis = this
  102. ,aNewOrder = []
  103. // sortable- and non-sortable list per parent
  104. ,aElements = []
  105. ,aElementsParent = [] // index reference for parent to aElements
  106. // multiple sort criteria (sort===0?iCriteria++:iCriteria=0)
  107. ,aCriteria = []
  108. ,iCriteria = 0
  109. ,iCriteriaMax
  110. //
  111. ,aFind = []
  112. ,aSettings = []
  113. //
  114. ,fnPluginPrepare = function(_settings){
  115. loop(aPluginPrepare,function(fn){
  116. fn.call(fn,_settings);
  117. });
  118. }
  119. //
  120. ,fnPrepareSortElement = function(settings,element){
  121. if (typeof element=='string') {
  122. // if !settings.cases
  123. if (!settings.cases) element = toLowerCase(element);
  124. element = element.replace(/^\s*(.*?)\s*$/i, '$1');
  125. }
  126. return element;
  127. }
  128. //
  129. ,fnSort = function(a,b) {
  130. var iReturn = 0;
  131. if (iCriteria!==0) iCriteria = 0;
  132. while (iReturn===0&&iCriteria<iCriteriaMax) {
  133. var oPoint = aCriteria[iCriteria]
  134. ,oSett = oPoint.oSettings
  135. ,rxLast = oSett.ignoreDashes?rxLastNrNoDash:rxLastNr
  136. ;
  137. //
  138. fnPluginPrepare(oSett);
  139. //
  140. if (oSett.sortFunction) { // custom sort
  141. iReturn = oSett.sortFunction(a,b);
  142. } else if (oSett.order=='rand') { // random sort
  143. iReturn = Math.random()<0.5?1:-1;
  144. } else { // regular sort
  145. var bNumeric = fls
  146. // prepare sort elements
  147. ,sA = fnPrepareSortElement(oSett,a.s[iCriteria])
  148. ,sB = fnPrepareSortElement(oSett,b.s[iCriteria])
  149. ;
  150. // maybe force Strings
  151. if (!oSett.forceStrings) {
  152. // maybe mixed
  153. var aAnum = isString(sA)?sA&&sA.match(rxLast):fls
  154. ,aBnum = isString(sB)?sB&&sB.match(rxLast):fls;
  155. if (aAnum&&aBnum) {
  156. var sAprv = sA.substr(0,sA.length-aAnum[0].length)
  157. ,sBprv = sB.substr(0,sB.length-aBnum[0].length);
  158. if (sAprv==sBprv) {
  159. bNumeric = !fls;
  160. sA = prsflt(aAnum[0]);
  161. sB = prsflt(aBnum[0]);
  162. }
  163. }
  164. }
  165. iReturn = oPoint.iAsc*(sA<sB?-1:(sA>sB?1:0));
  166. }
  167.  
  168. loop(aPluginSort,function(fn){
  169. iReturn = fn.call(fn,bNumeric,sA,sB,iReturn);
  170. });
  171.  
  172. if (iReturn===0) iCriteria++;
  173. }
  174.  
  175. return iReturn;
  176. }
  177. ;
  178. // fill aFind and aSettings but keep length pairing up
  179. for (i=0,l=arguments.length;i<l;i++){
  180. var o = arguments[i];
  181. if (isString(o)) {
  182. if (aFind.push(o)-1>aSettings.length) aSettings.length = aFind.length-1;
  183. } else {
  184. if (aSettings.push(o)>aFind.length) aFind.length = aSettings.length;
  185. }
  186. }
  187. if (aFind.length>aSettings.length) aSettings.length = aFind.length; // todo: and other way around?
  188.  
  189. // fill aFind and aSettings for arguments.length===0
  190. iCriteriaMax = aFind.length;
  191. if (iCriteriaMax===0) {
  192. iCriteriaMax = aFind.length = 1;
  193. aSettings.push({});
  194. }
  195.  
  196. for (i=0,l=iCriteriaMax;i<l;i++) {
  197. var sFind = aFind[i]
  198. ,oSettings = $.extend({}, $.tinysort.defaults, aSettings[i])
  199. // has find, attr or data
  200. ,bFind = !(!sFind||sFind==='')
  201. // since jQuery's filter within each works on array index and not actual index we have to create the filter in advance
  202. ,bFilter = bFind&&sFind[0]===':'
  203. ;
  204. aCriteria.push({ // todo: only used locally, find a way to minify properties
  205. sFind: sFind
  206. ,oSettings: oSettings
  207. // has find, attr or data
  208. ,bFind: bFind
  209. ,bAttr: !(oSettings.attr===nll||oSettings.attr==='')
  210. ,bData: oSettings.data!==nll
  211. // filter
  212. ,bFilter: bFilter
  213. ,$Filter: bFilter?oThis.filter(sFind):oThis
  214. ,fnSort: oSettings.sortFunction
  215. ,iAsc: oSettings.order=='asc'?1:-1
  216. });
  217. }
  218. //
  219. // prepare oElements for sorting
  220. oThis.each(function(i,el) {
  221. var $Elm = $(el)
  222. ,mParent = $Elm.parent().get(0)
  223. ,mFirstElmOrSub // we still need to distinguish between sortable and non-sortable elements (might have unexpected results for multiple criteria)
  224. ,aSort = []
  225. ;
  226. for (j=0;j<iCriteriaMax;j++) {
  227. var oPoint = aCriteria[j]
  228. // element or sub selection
  229. ,mElmOrSub = oPoint.bFind?(oPoint.bFilter?oPoint.$Filter.filter(el):$Elm.find(oPoint.sFind)):$Elm;
  230. // text or attribute value
  231. aSort.push(oPoint.bData?mElmOrSub.data(oPoint.oSettings.data):(oPoint.bAttr?mElmOrSub.attr(oPoint.oSettings.attr):(oPoint.oSettings.useVal?mElmOrSub.val():mElmOrSub.text())));
  232. if (mFirstElmOrSub===undefined) mFirstElmOrSub = mElmOrSub;
  233. }
  234. // to sort or not to sort
  235. var iElmIndex = fnIndexOf.call(aElementsParent,mParent);
  236. if (iElmIndex<0) {
  237. iElmIndex = aElementsParent.push(mParent) - 1;
  238. aElements[iElmIndex] = {s:[],n:[]}; // s: sort, n: not sort
  239. }
  240. if (mFirstElmOrSub.length>0) aElements[iElmIndex].s.push({s:aSort,e:$Elm,n:i}); // s:string/pointer, e:element, n:number
  241. else aElements[iElmIndex].n.push({e:$Elm,n:i});
  242. });
  243. //
  244. // sort
  245. loop(aElements, function(oParent) { oParent.s.sort(fnSort); });
  246. //
  247. // order elements and fill new order
  248. loop(aElements, function(oParent) {
  249. var aSorted = oParent.s
  250. ,aUnsorted = oParent.n
  251. ,iSorted = aSorted.length
  252. ,iUnsorted = aUnsorted.length
  253. ,iNumElm = iSorted+iUnsorted
  254. ,aOriginal = [] // list for original position
  255. ,iLow = iNumElm
  256. ,aCount = [0,0] // count how much we've sorted for retrieval from either the sort list or the non-sort list (oParent.s/oParent.n)
  257. ;
  258. switch (oSettings.place) {
  259. case 'first': loop(aSorted,function(obj) { iLow = mathmn(iLow,obj.n); }); break;
  260. case 'org': loop(aSorted,function(obj) { aOriginal.push(obj.n); }); break;
  261. case 'end': iLow = iUnsorted; break;
  262. default: iLow = 0;
  263. }
  264. for (i=0;i<iNumElm;i++) {
  265. var bFromSortList = contains(aOriginal,i)?!fls:i>=iLow&&i<iLow+iSorted
  266. ,iCountIndex = bFromSortList?0:1
  267. ,mEl = (bFromSortList?aSorted:aUnsorted)[aCount[iCountIndex]].e;
  268. mEl.parent().append(mEl);
  269. if (bFromSortList||!oSettings.returns) aNewOrder.push(mEl.get(0));
  270. aCount[iCountIndex]++;
  271. }
  272. });
  273. oThis.length = 0;
  274. Array.prototype.push.apply(oThis,aNewOrder);
  275. return oThis;
  276. }
  277. });
  278. // toLowerCase // todo: dismantle, used only once
  279. function toLowerCase(s) {
  280. return s&&s.toLowerCase?s.toLowerCase():s;
  281. }
  282. // array contains
  283. function contains(a,n) {
  284. for (var i=0,l=a.length;i<l;i++) if (a[i]==n) return !fls;
  285. return fls;
  286. }
  287. // set functions
  288. $.fn.TinySort = $.fn.Tinysort = $.fn.tsort = $.fn.tinysort;
  289. })(jQuery);