abstracted 0.0.6 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +11 -8
  4. data/app/assets/javascripts/abstract_resources.js +29 -25
  5. data/app/assets/javascripts/crud.js.coffee +174 -29
  6. data/app/assets/javascripts/initializers.js.coffee +403 -58
  7. data/app/assets/javascripts/pagescrolling.js.coffee +92 -0
  8. data/app/assets/stylesheets/abstract_resources.css +5 -0
  9. data/app/assets/stylesheets/scaffold.css +4 -2
  10. data/app/assets/stylesheets/selectize.css.scss +0 -0
  11. data/app/controllers/abstract_resources_controller.rb +473 -134
  12. data/app/controllers/application_controller.rb +22 -7
  13. data/app/helpers/abstract_resources_helper.rb +515 -9
  14. data/app/helpers/fab_button_helper.rb +82 -0
  15. data/app/models/abstract_resource.rb +141 -89
  16. data/app/models/ancestry_abstract_resource.rb +27 -0
  17. data/app/models/concerns/roleable.rb +1 -1
  18. data/app/policies/abstract_resource_policy.rb +25 -0
  19. data/app/views/abstract_resources/_title.html.haml +2 -0
  20. data/app/views/abstract_resources/activate.html.haml +5 -0
  21. data/app/views/abstract_resources/attach.html.haml +6 -0
  22. data/app/views/abstract_resources/deactivate.html.haml +5 -0
  23. data/app/views/abstract_resources/defer.html.haml +5 -0
  24. data/app/views/abstract_resources/destroy.js.haml +2 -1
  25. data/app/views/abstract_resources/detach.html.haml +5 -0
  26. data/app/views/abstract_resources/edit.html.haml +4 -9
  27. data/app/views/abstract_resources/error.html.haml +2 -0
  28. data/app/views/abstract_resources/error.js.haml +2 -0
  29. data/app/views/abstract_resources/index.html.haml +27 -0
  30. data/app/views/abstract_resources/index.js.haml +2 -0
  31. data/app/views/abstract_resources/new.html.haml +4 -10
  32. data/app/views/abstract_resources/prefer.html.haml +5 -0
  33. data/app/views/abstract_resources/print.html.haml +2 -0
  34. data/app/views/abstract_resources/print.js.haml +2 -0
  35. data/app/views/abstract_resources/show.html.haml +5 -11
  36. data/app/views/layouts/_messages.html.haml +1 -0
  37. data/app/views/layouts/_navigation.html.haml +13 -0
  38. data/bower.json +34 -0
  39. data/config/initializers/exception.rb +19 -0
  40. data/config/initializers/pundit.rb +22 -0
  41. data/config/routes.rb +24 -0
  42. data/lib/abstracted.rb +3 -0
  43. data/lib/abstracted/version.rb +1 -1
  44. data/lib/abstracted_responder.rb +1 -0
  45. data/lib/exceptions.rb +61 -0
  46. data/vendor/assets/components/jquery/.bower.json +38 -0
  47. data/vendor/assets/components/jquery/MIT-LICENSE.txt +21 -0
  48. data/vendor/assets/components/jquery/bower.json +28 -0
  49. data/vendor/assets/components/jquery/dist/jquery.js +9210 -0
  50. data/vendor/assets/components/jquery/dist/jquery.min.js +5 -0
  51. data/vendor/assets/components/jquery/dist/jquery.min.map +1 -0
  52. data/vendor/assets/components/jquery/src/ajax.js +786 -0
  53. data/vendor/assets/components/jquery/src/ajax/jsonp.js +89 -0
  54. data/vendor/assets/components/jquery/src/ajax/load.js +75 -0
  55. data/vendor/assets/components/jquery/src/ajax/parseJSON.js +13 -0
  56. data/vendor/assets/components/jquery/src/ajax/parseXML.js +28 -0
  57. data/vendor/assets/components/jquery/src/ajax/script.js +64 -0
  58. data/vendor/assets/components/jquery/src/ajax/var/nonce.js +5 -0
  59. data/vendor/assets/components/jquery/src/ajax/var/rquery.js +3 -0
  60. data/vendor/assets/components/jquery/src/ajax/xhr.js +136 -0
  61. data/vendor/assets/components/jquery/src/attributes.js +11 -0
  62. data/vendor/assets/components/jquery/src/attributes/attr.js +141 -0
  63. data/vendor/assets/components/jquery/src/attributes/classes.js +158 -0
  64. data/vendor/assets/components/jquery/src/attributes/prop.js +94 -0
  65. data/vendor/assets/components/jquery/src/attributes/support.js +35 -0
  66. data/vendor/assets/components/jquery/src/attributes/val.js +161 -0
  67. data/vendor/assets/components/jquery/src/callbacks.js +205 -0
  68. data/vendor/assets/components/jquery/src/core.js +502 -0
  69. data/vendor/assets/components/jquery/src/core/access.js +60 -0
  70. data/vendor/assets/components/jquery/src/core/init.js +123 -0
  71. data/vendor/assets/components/jquery/src/core/parseHTML.js +39 -0
  72. data/vendor/assets/components/jquery/src/core/ready.js +97 -0
  73. data/vendor/assets/components/jquery/src/core/var/rsingleTag.js +4 -0
  74. data/vendor/assets/components/jquery/src/css.js +450 -0
  75. data/vendor/assets/components/jquery/src/css/addGetHookIf.js +22 -0
  76. data/vendor/assets/components/jquery/src/css/curCSS.js +57 -0
  77. data/vendor/assets/components/jquery/src/css/defaultDisplay.js +70 -0
  78. data/vendor/assets/components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  79. data/vendor/assets/components/jquery/src/css/support.js +96 -0
  80. data/vendor/assets/components/jquery/src/css/swap.js +28 -0
  81. data/vendor/assets/components/jquery/src/css/var/cssExpand.js +3 -0
  82. data/vendor/assets/components/jquery/src/css/var/getStyles.js +12 -0
  83. data/vendor/assets/components/jquery/src/css/var/isHidden.js +13 -0
  84. data/vendor/assets/components/jquery/src/css/var/rmargin.js +3 -0
  85. data/vendor/assets/components/jquery/src/css/var/rnumnonpx.js +5 -0
  86. data/vendor/assets/components/jquery/src/data.js +178 -0
  87. data/vendor/assets/components/jquery/src/data/Data.js +181 -0
  88. data/vendor/assets/components/jquery/src/data/accepts.js +20 -0
  89. data/vendor/assets/components/jquery/src/data/var/data_priv.js +5 -0
  90. data/vendor/assets/components/jquery/src/data/var/data_user.js +5 -0
  91. data/vendor/assets/components/jquery/src/deferred.js +149 -0
  92. data/vendor/assets/components/jquery/src/deprecated.js +13 -0
  93. data/vendor/assets/components/jquery/src/dimensions.js +50 -0
  94. data/vendor/assets/components/jquery/src/effects.js +648 -0
  95. data/vendor/assets/components/jquery/src/effects/Tween.js +114 -0
  96. data/vendor/assets/components/jquery/src/effects/animatedSelector.js +13 -0
  97. data/vendor/assets/components/jquery/src/event.js +868 -0
  98. data/vendor/assets/components/jquery/src/event/ajax.js +13 -0
  99. data/vendor/assets/components/jquery/src/event/alias.js +39 -0
  100. data/vendor/assets/components/jquery/src/event/support.js +9 -0
  101. data/vendor/assets/components/jquery/src/exports/amd.js +24 -0
  102. data/vendor/assets/components/jquery/src/exports/global.js +32 -0
  103. data/vendor/assets/components/jquery/src/intro.js +44 -0
  104. data/vendor/assets/components/jquery/src/jquery.js +37 -0
  105. data/vendor/assets/components/jquery/src/manipulation.js +580 -0
  106. data/vendor/assets/components/jquery/src/manipulation/_evalUrl.js +18 -0
  107. data/vendor/assets/components/jquery/src/manipulation/support.js +32 -0
  108. data/vendor/assets/components/jquery/src/manipulation/var/rcheckableType.js +3 -0
  109. data/vendor/assets/components/jquery/src/offset.js +207 -0
  110. data/vendor/assets/components/jquery/src/outro.js +1 -0
  111. data/vendor/assets/components/jquery/src/queue.js +142 -0
  112. data/vendor/assets/components/jquery/src/queue/delay.js +22 -0
  113. data/vendor/assets/components/jquery/src/selector-native.js +172 -0
  114. data/vendor/assets/components/jquery/src/selector-sizzle.js +14 -0
  115. data/vendor/assets/components/jquery/src/selector.js +1 -0
  116. data/vendor/assets/components/jquery/src/serialize.js +111 -0
  117. data/vendor/assets/components/jquery/src/sizzle/dist/sizzle.js +2067 -0
  118. data/vendor/assets/components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
  119. data/vendor/assets/components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
  120. data/vendor/assets/components/jquery/src/traversing.js +199 -0
  121. data/vendor/assets/components/jquery/src/traversing/findFilter.js +100 -0
  122. data/vendor/assets/components/jquery/src/traversing/var/rneedsContext.js +6 -0
  123. data/vendor/assets/components/jquery/src/var/arr.js +3 -0
  124. data/vendor/assets/components/jquery/src/var/class2type.js +4 -0
  125. data/vendor/assets/components/jquery/src/var/concat.js +5 -0
  126. data/vendor/assets/components/jquery/src/var/hasOwn.js +5 -0
  127. data/vendor/assets/components/jquery/src/var/indexOf.js +5 -0
  128. data/vendor/assets/components/jquery/src/var/pnum.js +3 -0
  129. data/vendor/assets/components/jquery/src/var/push.js +5 -0
  130. data/vendor/assets/components/jquery/src/var/rnotwhite.js +3 -0
  131. data/vendor/assets/components/jquery/src/var/slice.js +5 -0
  132. data/vendor/assets/components/jquery/src/var/strundefined.js +3 -0
  133. data/vendor/assets/components/jquery/src/var/support.js +4 -0
  134. data/vendor/assets/components/jquery/src/var/toString.js +5 -0
  135. data/vendor/assets/components/jquery/src/wrap.js +79 -0
  136. data/vendor/assets/components/microplugin/.bower.json +38 -0
  137. data/vendor/assets/components/microplugin/.gitignore +2 -0
  138. data/vendor/assets/components/microplugin/.npmignore +3 -0
  139. data/vendor/assets/components/microplugin/README.md +115 -0
  140. data/vendor/assets/components/microplugin/bower.json +22 -0
  141. data/vendor/assets/components/microplugin/package.json +15 -0
  142. data/vendor/assets/components/microplugin/src/microplugin.js +135 -0
  143. data/vendor/assets/components/selectize/.bower.json +60 -0
  144. data/vendor/assets/components/selectize/.gitignore +5 -0
  145. data/vendor/assets/components/selectize/LICENSE +202 -0
  146. data/vendor/assets/components/selectize/README.md +105 -0
  147. data/vendor/assets/components/selectize/bower.json +40 -0
  148. data/vendor/assets/components/selectize/dist/css/selectize.bootstrap2.css +487 -0
  149. data/vendor/assets/components/selectize/dist/css/selectize.bootstrap3.css +401 -0
  150. data/vendor/assets/components/selectize/dist/css/selectize.css +317 -0
  151. data/vendor/assets/components/selectize/dist/css/selectize.default.css +387 -0
  152. data/vendor/assets/components/selectize/dist/css/selectize.legacy.css +364 -0
  153. data/vendor/assets/components/selectize/dist/js/selectize.js +3058 -0
  154. data/vendor/assets/components/selectize/dist/js/selectize.min.js +3 -0
  155. data/vendor/assets/components/selectize/dist/js/standalone/selectize.js +3667 -0
  156. data/vendor/assets/components/selectize/dist/js/standalone/selectize.min.js +3 -0
  157. data/vendor/assets/components/selectize/dist/less/plugins/drag_drop.less +16 -0
  158. data/vendor/assets/components/selectize/dist/less/plugins/dropdown_header.less +20 -0
  159. data/vendor/assets/components/selectize/dist/less/plugins/optgroup_columns.less +17 -0
  160. data/vendor/assets/components/selectize/dist/less/plugins/remove_button.less +37 -0
  161. data/vendor/assets/components/selectize/dist/less/selectize.bootstrap2.less +161 -0
  162. data/vendor/assets/components/selectize/dist/less/selectize.bootstrap3.less +150 -0
  163. data/vendor/assets/components/selectize/dist/less/selectize.default.less +84 -0
  164. data/vendor/assets/components/selectize/dist/less/selectize.legacy.less +75 -0
  165. data/vendor/assets/components/selectize/dist/less/selectize.less +295 -0
  166. data/vendor/assets/components/selectize/karma.conf.js +107 -0
  167. data/vendor/assets/components/sifter/.bower.json +44 -0
  168. data/vendor/assets/components/sifter/.gitignore +3 -0
  169. data/vendor/assets/components/sifter/.npmignore +4 -0
  170. data/vendor/assets/components/sifter/README.md +149 -0
  171. data/vendor/assets/components/sifter/bower.json +23 -0
  172. data/vendor/assets/components/sifter/package.json +48 -0
  173. data/vendor/assets/components/sifter/sifter.js +471 -0
  174. data/vendor/assets/components/sifter/sifter.min.js +2 -0
  175. data/vendor/assets/components/sweetalert/.bower.json +36 -0
  176. data/vendor/assets/components/sweetalert/.editorconfig +11 -0
  177. data/vendor/assets/components/sweetalert/.gitignore +5 -0
  178. data/vendor/assets/components/sweetalert/.jshintrc +11 -0
  179. data/vendor/assets/components/sweetalert/.travis.yml +6 -0
  180. data/vendor/assets/components/sweetalert/LICENSE +22 -0
  181. data/vendor/assets/components/sweetalert/README.md +157 -0
  182. data/vendor/assets/components/sweetalert/bower.json +25 -0
  183. data/vendor/assets/components/sweetalert/dev/gulpfile-wrap-template.js +18 -0
  184. data/vendor/assets/components/sweetalert/dev/ie9.css +23 -0
  185. data/vendor/assets/components/sweetalert/dev/loader-animation.css +209 -0
  186. data/vendor/assets/components/sweetalert/dev/modules/default-params.js +26 -0
  187. data/vendor/assets/components/sweetalert/dev/modules/handle-click.js +128 -0
  188. data/vendor/assets/components/sweetalert/dev/modules/handle-dom.js +161 -0
  189. data/vendor/assets/components/sweetalert/dev/modules/handle-key.js +73 -0
  190. data/vendor/assets/components/sweetalert/dev/modules/handle-swal-dom.js +148 -0
  191. data/vendor/assets/components/sweetalert/dev/modules/injected-html.js +69 -0
  192. data/vendor/assets/components/sweetalert/dev/modules/set-params.js +221 -0
  193. data/vendor/assets/components/sweetalert/dev/modules/utils.js +71 -0
  194. data/vendor/assets/components/sweetalert/dev/sweetalert.es6.js +311 -0
  195. data/vendor/assets/components/sweetalert/dev/sweetalert.scss +648 -0
  196. data/vendor/assets/components/sweetalert/dist/sweetalert-dev.js +1281 -0
  197. data/vendor/assets/components/sweetalert/dist/sweetalert.css +932 -0
  198. data/vendor/assets/components/sweetalert/dist/sweetalert.min.js +1 -0
  199. data/vendor/assets/components/sweetalert/example/example.css +442 -0
  200. data/vendor/assets/components/sweetalert/example/example.scss +580 -0
  201. data/vendor/assets/components/sweetalert/example/images/logo_big.png +0 -0
  202. data/vendor/assets/components/sweetalert/example/images/logo_big@2x.png +0 -0
  203. data/vendor/assets/components/sweetalert/example/images/logo_small.png +0 -0
  204. data/vendor/assets/components/sweetalert/example/images/logo_small@2x.png +0 -0
  205. data/vendor/assets/components/sweetalert/example/images/te-logo-small.svg +12 -0
  206. data/vendor/assets/components/sweetalert/example/images/thumbs-up.jpg +0 -0
  207. data/vendor/assets/components/sweetalert/example/images/vs_icon.png +0 -0
  208. data/vendor/assets/components/sweetalert/example/images/vs_icon@2x.png +0 -0
  209. data/vendor/assets/components/sweetalert/gulpfile.js +97 -0
  210. data/vendor/assets/components/sweetalert/index.html +584 -0
  211. data/vendor/assets/components/sweetalert/package.json +45 -0
  212. data/vendor/assets/components/sweetalert/sweetalert.gif +0 -0
  213. data/vendor/assets/components/sweetalert/test/index.html +20 -0
  214. data/vendor/assets/components/sweetalert/test/tests.js +143 -0
  215. data/vendor/assets/components/sweetalert/themes/facebook/facebook.css +111 -0
  216. data/vendor/assets/components/sweetalert/themes/facebook/facebook.scss +146 -0
  217. data/vendor/assets/components/sweetalert/themes/google/google.css +115 -0
  218. data/vendor/assets/components/sweetalert/themes/google/google.scss +148 -0
  219. data/vendor/assets/components/sweetalert/themes/twitter/twitter.css +140 -0
  220. data/vendor/assets/components/sweetalert/themes/twitter/twitter.scss +177 -0
  221. metadata +214 -5
  222. data/abstracted-0.0.4.gem +0 -0
  223. data/abstracted.gemspec +0 -51
  224. data/app/views/abstract_resources/index.html.erb +0 -1
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "sifter",
3
+ "keywords": [
4
+ "search",
5
+ "filter",
6
+ "sift",
7
+ "data",
8
+ "results",
9
+ "match",
10
+ "sort",
11
+ "autocomplete"
12
+ ],
13
+ "description": "A library for textually searching arrays and hashes of objects by property (or multiple properties). Designed specifically for autocomplete.",
14
+ "version": "0.4.3",
15
+ "license": "Apache License, Version 2.0",
16
+ "readmeFilename": "README.md",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git://github.com/brianreavis/sifter.js.git"
20
+ },
21
+ "main": [
22
+ "sifter.js"
23
+ ],
24
+ "ignore": [
25
+ "Makefile",
26
+ "test",
27
+ "lib",
28
+ "bin",
29
+ "benchmark",
30
+ "node_modules",
31
+ ".travis.yml"
32
+ ],
33
+ "dependencies": {},
34
+ "homepage": "https://github.com/brianreavis/sifter.js",
35
+ "_release": "0.4.3",
36
+ "_resolution": {
37
+ "type": "version",
38
+ "tag": "v0.4.3",
39
+ "commit": "bdd2badbce30396ed9a12951b13e8e23d2adf43a"
40
+ },
41
+ "_source": "git://github.com/brianreavis/sifter.js.git",
42
+ "_target": "0.4.x",
43
+ "_originalSource": "sifter"
44
+ }
@@ -0,0 +1,3 @@
1
+ .DS_store
2
+ node_modules
3
+ benchmark/report.json
@@ -0,0 +1,4 @@
1
+ *.md
2
+ .git*
3
+ benchmark/
4
+ test/
@@ -0,0 +1,149 @@
1
+ # sifter.js
2
+ [![NPM version](http://img.shields.io/npm/v/sifter.svg?style=flat)](https://www.npmjs.org/package/sifter)
3
+ [![Installs](http://img.shields.io/npm/dm/sifter.svg?style=flat)](https://www.npmjs.org/package/sifter)
4
+ [![Build Status](http://img.shields.io/travis/brianreavis/sifter.js/master.svg?style=flat)](https://travis-ci.org/brianreavis/sifter.js)
5
+ [![Coverage Status](http://img.shields.io/coveralls/brianreavis/sifter.js/master.svg?style=flat)](https://coveralls.io/r/brianreavis/sifter.js)
6
+
7
+ Sifter is a client and server-side library (via [UMD](https://github.com/umdjs/umd)) for textually searching arrays and hashes of objects by property – or multiple properties. It's designed specifically for autocomplete. The process is three-step: *score*, *filter*, *sort*.
8
+
9
+ * **Supports díåcritîçs.**<br>For example, if searching for "montana" and an item in the set has a value of "montaña", it will still be matched. Sorting will also play nicely with diacritics.
10
+ * **Smart scoring.**<br>Items are scored / sorted intelligently depending on where a match is found in the string (how close to the beginning) and what percentage of the string matches.
11
+ * **Multi-field sorting.**<br>When scores aren't enough to go by – like when getting results for an empty query – it can sort by one or more fields. For example, sort by a person's first name and last name without actually merging the properties to a single string.
12
+
13
+ ```sh
14
+ $ npm install sifter # node.js
15
+ $ bower install sifter # browser
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```js
21
+ var sifter = new Sifter([
22
+ {title: 'Annapurna I', location: 'Nepal', continent: 'Asia'},
23
+ {title: 'Annapurna II', location: 'Nepal', continent: 'Asia'},
24
+ {title: 'Annapurna III', location: 'Nepal', continent: 'Asia'},
25
+ {title: 'Eiger', location: 'Switzerland', continent: 'Europe'},
26
+ {title: 'Everest', location: 'Nepal', continent: 'Asia'},
27
+ {title: 'Gannett', location: 'Wyoming', continent: 'North America'},
28
+ {title: 'Denali', location: 'Alaska', continent: 'North America'}
29
+ ]);
30
+
31
+ var result = sifter.search('anna', {
32
+ fields: ['title', 'location', 'continent'],
33
+ sort: [{field: 'title', direction: 'asc'}],
34
+ limit: 3
35
+ });
36
+ ```
37
+
38
+ Seaching will provide back meta information and an "items" array that contains objects with the index (or key, if searching a hash) and a score that represents how good of a match the item was. Items that did not match will not be returned.
39
+
40
+ ```
41
+ {"score": 0.2878787878787879, "id": 0},
42
+ {"score": 0.27777777777777773, "id": 1},
43
+ {"score": 0.2692307692307692, "id": 2}
44
+ ```
45
+
46
+ Items are sorted by best-match, primarily. If two or more items have the same score (which will be the case when searching with an empty string), it will resort to the fields listed in the "sort" option.
47
+
48
+ The full result comes back in the format of:
49
+
50
+ ```js
51
+ {
52
+ "options": {
53
+ "fields": ["title", "location", "continent"],
54
+ "sort": [
55
+ {"field": "title", "direction": "asc"}
56
+ ],
57
+ "limit": 3
58
+ },
59
+ "query": "anna",
60
+ "tokens": [{
61
+ "string": "anna",
62
+ "regex": /[aÀÁÂÃÄÅàáâãäå][nÑñ][nÑñ][aÀÁÂÃÄÅàáâãäå]/
63
+ }],
64
+ "total": 3,
65
+ "items": [
66
+ {"score": 0.2878787878787879, "id": 0},
67
+ {"score": 0.27777777777777773, "id": 1},
68
+ {"score": 0.2692307692307692,"id": 2}
69
+ ]
70
+ }
71
+ ```
72
+
73
+ ### API
74
+
75
+ #### #.search(query, options)
76
+
77
+ Performs a search for `query` with the provided `options`.
78
+
79
+ <table width="100%">
80
+ <tr>
81
+ <th align="left">Option</th>
82
+ <th>Type</th>
83
+ <th align="left" width="100%">Description</th>
84
+ </tr>
85
+ <tr>
86
+ <td valign="top">"fields"</td>
87
+ <td valign="top">array</td>
88
+ <td valign="top">An array of property names to be searched.</td>
89
+ </tr>
90
+ <tr>
91
+ <td valign="top">"limit"</td>
92
+ <td valign="top">integer</td>
93
+ <td valign="top">The maximum number of results to return.</td>
94
+ </tr>
95
+ <tr>
96
+ <td valign="top">"sort"</td>
97
+ <td valign="top">array</td>
98
+ <td valign="top">An array of fields to sort by. Each item should be an object containing at least a "field" property. Optionally, "direction" can be set to "asc" or "desc". The order of the array defines the sort precedence.<br><br>Unless present, a special "$score" property will be automatically added to the beginning of the sort list. This will make results sorted primarily by match quality (descending).</td>
99
+ </tr>
100
+ <tr>
101
+ <td valign="top">"sort_empty"</td>
102
+ <td valign="top">array</td>
103
+ <td valign="top">Optional. Defaults to "sort" setting. If provided, these sort settings are used when no query is present.</td>
104
+ </tr>
105
+ <tr>
106
+ <td valign="top">"filter"</td>
107
+ <td valign="top">boolean</td>
108
+ <td valign="top">If <code>false</code>, items with a score of zero will <em>not</em> be filtered out of the result-set.</td>
109
+ </tr>
110
+ <tr>
111
+ <td valign="top">"conjunction"</td>
112
+ <td valign="top">string</td>
113
+ <td valign="top">Determines how multiple search terms are joined ("and" or "or").</td>
114
+ </tr>
115
+ </table>
116
+
117
+ ## CLI
118
+
119
+ ![CLI](http://i.imgur.com/fSQBnWZ.png)
120
+
121
+ Sifter comes with a command line interface that's useful for testing on datasets. It accepts JSON and CSV data, either from a file or from stdin (unix pipes). If using CSV data, the first line of the file must be a header row.
122
+ ```sh
123
+ $ npm install -g sifter
124
+ ```
125
+
126
+ ```sh
127
+ $ cat file.csv | sifter --query="ant" --fields=title
128
+ $ sifter --query="ant" --fields=title --file=file.csv
129
+ ```
130
+
131
+ ## Contributing
132
+
133
+ Install the dependencies that are required to build and test:
134
+
135
+ ```sh
136
+ $ npm install
137
+ ```
138
+
139
+ First build a copy with `make` then run the test suite with `make test`.
140
+
141
+ When issuing a pull request, please exclude "sifter.js" and "sifter.min.js" in the project root.
142
+
143
+ ## License
144
+
145
+ Copyright &copy; 2013–2015 [Brian Reavis](http://twitter.com/brianreavis) & [Contributors](https://github.com/brianreavis/sifter.js/graphs/contributors)
146
+
147
+ 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
148
+
149
+ 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.
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "sifter",
3
+ "keywords": ["search","filter","sift","data","results","match","sort","autocomplete"],
4
+ "description": "A library for textually searching arrays and hashes of objects by property (or multiple properties). Designed specifically for autocomplete.",
5
+ "version": "0.4.3",
6
+ "license": "Apache License, Version 2.0",
7
+ "readmeFilename": "README.md",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git://github.com/brianreavis/sifter.js.git"
11
+ },
12
+ "main": ["sifter.js"],
13
+ "ignore": [
14
+ "Makefile",
15
+ "test",
16
+ "lib",
17
+ "bin",
18
+ "benchmark",
19
+ "node_modules",
20
+ ".travis.yml"
21
+ ],
22
+ "dependencies": {}
23
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "sifter",
3
+ "keywords": [
4
+ "search",
5
+ "filter",
6
+ "sift",
7
+ "data",
8
+ "results",
9
+ "match",
10
+ "sort",
11
+ "autocomplete"
12
+ ],
13
+ "description": "A library for textually searching arrays and hashes of objects by property (or multiple properties). Designed specifically for autocomplete.",
14
+ "version": "0.4.3",
15
+ "author": "Brian Reavis <brian@thirdroute.com>",
16
+ "main": "./sifter.js",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git://github.com/brianreavis/sifter.js.git"
20
+ },
21
+ "scripts": {
22
+ "test": "mocha -R list"
23
+ },
24
+ "bin": {
25
+ "sifter": "./bin/sifter.js"
26
+ },
27
+ "dependencies": {
28
+ "optimist": "0.6.x",
29
+ "cardinal": "0.4.x",
30
+ "async": "0.2.x",
31
+ "humanize": "0.0.x",
32
+ "node-csv": "https://github.com/voodootikigod/node-csv/tarball/master"
33
+ },
34
+ "devDependencies": {
35
+ "mocha": "1.12.x",
36
+ "mocha-istanbul": "0.2.x",
37
+ "mocha-lcov-reporter": "0.0.x",
38
+ "coveralls": "2.3.x",
39
+ "uglify-js": "2.4.x",
40
+ "istanbul": "0.1.x"
41
+ },
42
+ "optionalDependencies": {
43
+ "microtime": "~1.4.2"
44
+ },
45
+ "engines": {
46
+ "node": "*"
47
+ }
48
+ }
@@ -0,0 +1,471 @@
1
+ /**
2
+ * sifter.js
3
+ * Copyright (c) 2013 Brian Reavis & contributors
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6
+ * file except in compliance with the License. You may obtain a copy of the License at:
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ * ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ *
14
+ * @author Brian Reavis <brian@thirdroute.com>
15
+ */
16
+
17
+ (function(root, factory) {
18
+ if (typeof define === 'function' && define.amd) {
19
+ define(factory);
20
+ } else if (typeof exports === 'object') {
21
+ module.exports = factory();
22
+ } else {
23
+ root.Sifter = factory();
24
+ }
25
+ }(this, function() {
26
+
27
+ /**
28
+ * Textually searches arrays and hashes of objects
29
+ * by property (or multiple properties). Designed
30
+ * specifically for autocomplete.
31
+ *
32
+ * @constructor
33
+ * @param {array|object} items
34
+ * @param {object} items
35
+ */
36
+ var Sifter = function(items, settings) {
37
+ this.items = items;
38
+ this.settings = settings || {diacritics: true};
39
+ };
40
+
41
+ /**
42
+ * Splits a search string into an array of individual
43
+ * regexps to be used to match results.
44
+ *
45
+ * @param {string} query
46
+ * @returns {array}
47
+ */
48
+ Sifter.prototype.tokenize = function(query) {
49
+ query = trim(String(query || '').toLowerCase());
50
+ if (!query || !query.length) return [];
51
+
52
+ var i, n, regex, letter;
53
+ var tokens = [];
54
+ var words = query.split(/ +/);
55
+
56
+ for (i = 0, n = words.length; i < n; i++) {
57
+ regex = escape_regex(words[i]);
58
+ if (this.settings.diacritics) {
59
+ for (letter in DIACRITICS) {
60
+ if (DIACRITICS.hasOwnProperty(letter)) {
61
+ regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
62
+ }
63
+ }
64
+ }
65
+ tokens.push({
66
+ string : words[i],
67
+ regex : new RegExp(regex, 'i')
68
+ });
69
+ }
70
+
71
+ return tokens;
72
+ };
73
+
74
+ /**
75
+ * Iterates over arrays and hashes.
76
+ *
77
+ * ```
78
+ * this.iterator(this.items, function(item, id) {
79
+ * // invoked for each item
80
+ * });
81
+ * ```
82
+ *
83
+ * @param {array|object} object
84
+ */
85
+ Sifter.prototype.iterator = function(object, callback) {
86
+ var iterator;
87
+ if (is_array(object)) {
88
+ iterator = Array.prototype.forEach || function(callback) {
89
+ for (var i = 0, n = this.length; i < n; i++) {
90
+ callback(this[i], i, this);
91
+ }
92
+ };
93
+ } else {
94
+ iterator = function(callback) {
95
+ for (var key in this) {
96
+ if (this.hasOwnProperty(key)) {
97
+ callback(this[key], key, this);
98
+ }
99
+ }
100
+ };
101
+ }
102
+
103
+ iterator.apply(object, [callback]);
104
+ };
105
+
106
+ /**
107
+ * Returns a function to be used to score individual results.
108
+ *
109
+ * Good matches will have a higher score than poor matches.
110
+ * If an item is not a match, 0 will be returned by the function.
111
+ *
112
+ * @param {object|string} search
113
+ * @param {object} options (optional)
114
+ * @returns {function}
115
+ */
116
+ Sifter.prototype.getScoreFunction = function(search, options) {
117
+ var self, fields, tokens, token_count;
118
+
119
+ self = this;
120
+ search = self.prepareSearch(search, options);
121
+ tokens = search.tokens;
122
+ fields = search.options.fields;
123
+ token_count = tokens.length;
124
+
125
+ /**
126
+ * Calculates how close of a match the
127
+ * given value is against a search token.
128
+ *
129
+ * @param {mixed} value
130
+ * @param {object} token
131
+ * @return {number}
132
+ */
133
+ var scoreValue = function(value, token) {
134
+ var score, pos;
135
+
136
+ if (!value) return 0;
137
+ value = String(value || '');
138
+ pos = value.search(token.regex);
139
+ if (pos === -1) return 0;
140
+ score = token.string.length / value.length;
141
+ if (pos === 0) score += 0.5;
142
+ return score;
143
+ };
144
+
145
+ /**
146
+ * Calculates the score of an object
147
+ * against the search query.
148
+ *
149
+ * @param {object} token
150
+ * @param {object} data
151
+ * @return {number}
152
+ */
153
+ var scoreObject = (function() {
154
+ var field_count = fields.length;
155
+ if (!field_count) {
156
+ return function() { return 0; };
157
+ }
158
+ if (field_count === 1) {
159
+ return function(token, data) {
160
+ return scoreValue(data[fields[0]], token);
161
+ };
162
+ }
163
+ return function(token, data) {
164
+ for (var i = 0, sum = 0; i < field_count; i++) {
165
+ sum += scoreValue(data[fields[i]], token);
166
+ }
167
+ return sum / field_count;
168
+ };
169
+ })();
170
+
171
+ if (!token_count) {
172
+ return function() { return 0; };
173
+ }
174
+ if (token_count === 1) {
175
+ return function(data) {
176
+ return scoreObject(tokens[0], data);
177
+ };
178
+ }
179
+
180
+ if (search.options.conjunction === 'and') {
181
+ return function(data) {
182
+ var score;
183
+ for (var i = 0, sum = 0; i < token_count; i++) {
184
+ score = scoreObject(tokens[i], data);
185
+ if (score <= 0) return 0;
186
+ sum += score;
187
+ }
188
+ return sum / token_count;
189
+ };
190
+ } else {
191
+ return function(data) {
192
+ for (var i = 0, sum = 0; i < token_count; i++) {
193
+ sum += scoreObject(tokens[i], data);
194
+ }
195
+ return sum / token_count;
196
+ };
197
+ }
198
+ };
199
+
200
+ /**
201
+ * Returns a function that can be used to compare two
202
+ * results, for sorting purposes. If no sorting should
203
+ * be performed, `null` will be returned.
204
+ *
205
+ * @param {string|object} search
206
+ * @param {object} options
207
+ * @return function(a,b)
208
+ */
209
+ Sifter.prototype.getSortFunction = function(search, options) {
210
+ var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
211
+
212
+ self = this;
213
+ search = self.prepareSearch(search, options);
214
+ sort = (!search.query && options.sort_empty) || options.sort;
215
+
216
+ /**
217
+ * Fetches the specified sort field value
218
+ * from a search result item.
219
+ *
220
+ * @param {string} name
221
+ * @param {object} result
222
+ * @return {mixed}
223
+ */
224
+ get_field = function(name, result) {
225
+ if (name === '$score') return result.score;
226
+ return self.items[result.id][name];
227
+ };
228
+
229
+ // parse options
230
+ fields = [];
231
+ if (sort) {
232
+ for (i = 0, n = sort.length; i < n; i++) {
233
+ if (search.query || sort[i].field !== '$score') {
234
+ fields.push(sort[i]);
235
+ }
236
+ }
237
+ }
238
+
239
+ // the "$score" field is implied to be the primary
240
+ // sort field, unless it's manually specified
241
+ if (search.query) {
242
+ implicit_score = true;
243
+ for (i = 0, n = fields.length; i < n; i++) {
244
+ if (fields[i].field === '$score') {
245
+ implicit_score = false;
246
+ break;
247
+ }
248
+ }
249
+ if (implicit_score) {
250
+ fields.unshift({field: '$score', direction: 'desc'});
251
+ }
252
+ } else {
253
+ for (i = 0, n = fields.length; i < n; i++) {
254
+ if (fields[i].field === '$score') {
255
+ fields.splice(i, 1);
256
+ break;
257
+ }
258
+ }
259
+ }
260
+
261
+ multipliers = [];
262
+ for (i = 0, n = fields.length; i < n; i++) {
263
+ multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
264
+ }
265
+
266
+ // build function
267
+ fields_count = fields.length;
268
+ if (!fields_count) {
269
+ return null;
270
+ } else if (fields_count === 1) {
271
+ field = fields[0].field;
272
+ multiplier = multipliers[0];
273
+ return function(a, b) {
274
+ return multiplier * cmp(
275
+ get_field(field, a),
276
+ get_field(field, b)
277
+ );
278
+ };
279
+ } else {
280
+ return function(a, b) {
281
+ var i, result, a_value, b_value, field;
282
+ for (i = 0; i < fields_count; i++) {
283
+ field = fields[i].field;
284
+ result = multipliers[i] * cmp(
285
+ get_field(field, a),
286
+ get_field(field, b)
287
+ );
288
+ if (result) return result;
289
+ }
290
+ return 0;
291
+ };
292
+ }
293
+ };
294
+
295
+ /**
296
+ * Parses a search query and returns an object
297
+ * with tokens and fields ready to be populated
298
+ * with results.
299
+ *
300
+ * @param {string} query
301
+ * @param {object} options
302
+ * @returns {object}
303
+ */
304
+ Sifter.prototype.prepareSearch = function(query, options) {
305
+ if (typeof query === 'object') return query;
306
+
307
+ options = extend({}, options);
308
+
309
+ var option_fields = options.fields;
310
+ var option_sort = options.sort;
311
+ var option_sort_empty = options.sort_empty;
312
+
313
+ if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
314
+ if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
315
+ if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
316
+
317
+ return {
318
+ options : options,
319
+ query : String(query || '').toLowerCase(),
320
+ tokens : this.tokenize(query),
321
+ total : 0,
322
+ items : []
323
+ };
324
+ };
325
+
326
+ /**
327
+ * Searches through all items and returns a sorted array of matches.
328
+ *
329
+ * The `options` parameter can contain:
330
+ *
331
+ * - fields {string|array}
332
+ * - sort {array}
333
+ * - score {function}
334
+ * - filter {bool}
335
+ * - limit {integer}
336
+ *
337
+ * Returns an object containing:
338
+ *
339
+ * - options {object}
340
+ * - query {string}
341
+ * - tokens {array}
342
+ * - total {int}
343
+ * - items {array}
344
+ *
345
+ * @param {string} query
346
+ * @param {object} options
347
+ * @returns {object}
348
+ */
349
+ Sifter.prototype.search = function(query, options) {
350
+ var self = this, value, score, search, calculateScore;
351
+ var fn_sort;
352
+ var fn_score;
353
+
354
+ search = this.prepareSearch(query, options);
355
+ options = search.options;
356
+ query = search.query;
357
+
358
+ // generate result scoring function
359
+ fn_score = options.score || self.getScoreFunction(search);
360
+
361
+ // perform search and sort
362
+ if (query.length) {
363
+ self.iterator(self.items, function(item, id) {
364
+ score = fn_score(item);
365
+ if (options.filter === false || score > 0) {
366
+ search.items.push({'score': score, 'id': id});
367
+ }
368
+ });
369
+ } else {
370
+ self.iterator(self.items, function(item, id) {
371
+ search.items.push({'score': 1, 'id': id});
372
+ });
373
+ }
374
+
375
+ fn_sort = self.getSortFunction(search, options);
376
+ if (fn_sort) search.items.sort(fn_sort);
377
+
378
+ // apply limits
379
+ search.total = search.items.length;
380
+ if (typeof options.limit === 'number') {
381
+ search.items = search.items.slice(0, options.limit);
382
+ }
383
+
384
+ return search;
385
+ };
386
+
387
+ // utilities
388
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
389
+
390
+ var cmp = function(a, b) {
391
+ if (typeof a === 'number' && typeof b === 'number') {
392
+ return a > b ? 1 : (a < b ? -1 : 0);
393
+ }
394
+ a = asciifold(String(a || ''));
395
+ b = asciifold(String(b || ''));
396
+ if (a > b) return 1;
397
+ if (b > a) return -1;
398
+ return 0;
399
+ };
400
+
401
+ var extend = function(a, b) {
402
+ var i, n, k, object;
403
+ for (i = 1, n = arguments.length; i < n; i++) {
404
+ object = arguments[i];
405
+ if (!object) continue;
406
+ for (k in object) {
407
+ if (object.hasOwnProperty(k)) {
408
+ a[k] = object[k];
409
+ }
410
+ }
411
+ }
412
+ return a;
413
+ };
414
+
415
+ var trim = function(str) {
416
+ return (str + '').replace(/^\s+|\s+$|/g, '');
417
+ };
418
+
419
+ var escape_regex = function(str) {
420
+ return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
421
+ };
422
+
423
+ var is_array = Array.isArray || ($ && $.isArray) || function(object) {
424
+ return Object.prototype.toString.call(object) === '[object Array]';
425
+ };
426
+
427
+ var DIACRITICS = {
428
+ 'a': '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',
429
+ 'c': '[cÇçćĆčČ]',
430
+ 'd': '[dđĐďĎ]',
431
+ 'e': '[eÈÉÊËèéêëěĚĒēęĘ]',
432
+ 'i': '[iÌÍÎÏìíîïĪī]',
433
+ 'l': '[lłŁ]',
434
+ 'n': '[nÑñňŇńŃ]',
435
+ 'o': '[oÒÓÔÕÕÖØòóôõöøŌō]',
436
+ 'r': '[rřŘ]',
437
+ 's': '[sŠšśŚ]',
438
+ 't': '[tťŤ]',
439
+ 'u': '[uÙÚÛÜùúûüůŮŪū]',
440
+ 'y': '[yŸÿýÝ]',
441
+ 'z': '[zŽžżŻźŹ]'
442
+ };
443
+
444
+ var asciifold = (function() {
445
+ var i, n, k, chunk;
446
+ var foreignletters = '';
447
+ var lookup = {};
448
+ for (k in DIACRITICS) {
449
+ if (DIACRITICS.hasOwnProperty(k)) {
450
+ chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
451
+ foreignletters += chunk;
452
+ for (i = 0, n = chunk.length; i < n; i++) {
453
+ lookup[chunk.charAt(i)] = k;
454
+ }
455
+ }
456
+ }
457
+ var regexp = new RegExp('[' + foreignletters + ']', 'g');
458
+ return function(str) {
459
+ return str.replace(regexp, function(foreignletter) {
460
+ return lookup[foreignletter];
461
+ }).toLowerCase();
462
+ };
463
+ })();
464
+
465
+
466
+ // export
467
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
468
+
469
+ return Sifter;
470
+ }));
471
+