├── .gitignore
├── filter.css
├── package.js
├── filter.html
├── README.md
└── filter.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .versions
3 |
--------------------------------------------------------------------------------
/filter.css:
--------------------------------------------------------------------------------
1 | .filter_flex{
2 | display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
3 | display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
4 | display: -ms-flexbox; /* TWEENER - IE 10 */
5 | display: -webkit-flex; /* NEW - Chrome */
6 | display: flex;
7 | padding-bottom: 20px;
8 | }
9 | .btn .caret {
10 | margin-left: 10px;
11 | }
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: "loredanacirstea:meteor-tabular-filter",
3 | summary: "Collection filter based on aldeed:simple-schema structure.Output: Mongo Selector.",
4 | version: "0.2.2",
5 | git: "https://github.com/loredanacirstea/meteor-tabular-filter.git"
6 | });
7 |
8 | Package.onUse(function(api) {
9 | api.versionsFrom('1.2.1');
10 |
11 | api.use(['jquery', 'deps', 'templating', 'ui', 'blaze', 'reactive-var', 'session'], 'client');
12 |
13 | api.addFiles([
14 | 'filter.html',
15 | 'filter.js',
16 | 'filter.css'
17 | ], 'client');
18 | });
19 |
--------------------------------------------------------------------------------
/filter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{> filter_fields}}
9 | {{> filter_extra}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{title}}
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {{first}}
43 |
44 |
45 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {{#each options}}
60 | {{this}}
61 | {{/each}}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | ...
80 | {{#each operators}}
81 | {{label}}
82 | {{/each}}
83 |
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # meteor-tabular-filter
2 |
3 | https://atmospherejs.com/loredanacirstea/meteor-tabular-filter
4 |
5 |
6 | It only works with bootstrap now, so you will have to add:
7 |
8 | meteor add twbs:bootstrap
9 | meteor add loredanacirstea:meteor-tabular-filter
10 |
11 |
12 | Recommended only for personal use: making quick querries on your data. For this, see demo at http://simple-filter.meteor.com , with source at: https://github.com/loredanacirstea/simple-filter.
13 |
14 | This package is not actively maintained as a default, but I will make small changes if there are easy to fix issues.
15 |
16 | ## Version
17 |
18 | 0.2.2
19 |
20 | - added property exactMatch which means show only = in filter dropdown
21 |
22 | 0.2.1 - Changes:
23 |
24 | You can have custom labels, so `Session.set("schema", "Table");` is now:
25 |
26 | ```
27 | Session.set("tabular-filter", {
28 | schema: "Table", // the only one required
29 | label: "",
30 | and_label: "",
31 | or_label: "",
32 | input_value_placeholder: "",
33 | regex_value: "",
34 | contains_value: ""
35 | });
36 | ```
37 |
38 | You can use i18n and do: `label: i18n('On Field')`
39 |
40 |
41 |
42 |
43 | ## Description
44 |
45 | This package offers a filter which gets data from an [aldeed:simple-schema](https://github.com/aldeed/meteor-simple-schema) object (collection structure). It outputs a `Mongo Selector` based on the chosen options from the `meteor-tabular-filter` UI. It is compatible with [aldeed:tabular](https://github.com/aldeed/meteor-tabular)'s `selector`.
46 |
47 | Basically, meteor-tabular-filter just creates a filter UI from the colection structure and outputs a `Mongo Selector` (such as `{ $and: [ { priority: "medium" }, { state: true } ] }`) after the user chooses the options.
48 |
49 | ## What to expect
50 |
51 | The `AND` / `OR` operations are done in the added order. Example:
52 |
53 | 
54 |
55 | Priority = medium AND Repetitions > 4 OR Deadline > 01/17/2015 AND State = true
56 |
57 | will compute the first AND (Priority AND Repetitions), use the result with OR (result1 OR Deadline) and finally use the result with the second AND (result2 AND State).
58 |
59 | ## Bugs
60 |
61 | When getting an unwanted result (ex. when deleting the first filter), refresh the page.
62 |
63 |
64 | ## How to Use
65 |
66 | (see examples/simple-filter)
67 |
68 | Set a Session variable with the name of your Simple Schema object.
69 |
70 | //examples/simple-filter/client/client.js
71 |
72 | Template.view_table.rendered = function(){
73 | Session.set("tabular-filter", {schema: "Table", label: 'On Field'});
74 | }
75 |
76 | Set a template helper (or another method) from the filter's `filter_selector` Session variable.
77 |
78 | //examples/simple-filter/client/client.js
79 |
80 | Template.view_table.helpers({
81 | selector: function (){
82 | var select = Session.get("filter_selector");
83 | return select;
84 | }
85 | });
86 |
87 | The Simple Schema object should have the form:
88 |
89 | //examples/simple-filter/lib/schema.js
90 |
91 | Schemas = {};
92 | Schemas.Table = new SimpleSchema({});
93 |
94 |
95 | Add the `{{> expression_filter}}` template to your .html file
96 |
97 | //examples/simple-filter/client/client.html
98 |
99 |
100 | {{> expression_filter}}
101 | {{> view_table}}
102 |
103 |
104 | The `selector` template helper can be used with [aldeed:meteor-tabular](https://github.com/aldeed/meteor-tabular):
105 |
106 | //examples/simple-filter/client/client.html
107 |
108 |
109 |
110 | {{> tabular table=TabularTables.Table selector=selector class="table table-striped table-bordered table-hover table-condensed"}}
111 |
112 |
113 |
114 | ## Omit Schema Fields
115 |
116 | If you have schema fields that you don't want to include, [extend SimpleSchema](https://github.com/aldeed/meteor-simple-schema#extending-the-schema-options) with:
117 |
118 | SimpleSchema.extendOptions({
119 | tabularFilterOmit: Match.Optional(Boolean)
120 | });
121 |
122 | and add `tabularFilterOmit: true` to your field, like this:
123 |
124 | deadline: {
125 | type: Date,
126 | label: "Deadline",
127 | tabularFilterOmit: true
128 | },
129 |
130 | By [Loretek](http://loretek.ro)
131 |
--------------------------------------------------------------------------------
/filter.js:
--------------------------------------------------------------------------------
1 | if(Meteor.isClient){
2 |
3 | Session.set("filter_counter", [1]);
4 | Session.set("filter_selector", '');
5 | Session.set("filter_queries", {});
6 | Session.set("filter_ops", {});
7 |
8 |
9 | var object_values = function(obj){
10 | var values = [];
11 | var keys = Object.keys(obj);
12 | for(var i in keys){
13 | values.push(obj[keys[i]]);
14 | }
15 | return values;
16 | }
17 |
18 | dropdown_select = function(elem, htmlid){
19 | $("#"+htmlid + ":first-child").text(elem.label).append(' ');
20 | $("#"+htmlid + ":first-child").val(elem.value);
21 | }
22 |
23 | create_selector = function(field, operator, value, type, options){
24 | var select = {};
25 | switch(operator){
26 | case "=":
27 | if(type == "Number")
28 | value = Number(value);
29 | if(type == "Date")
30 | value = new Date(value);
31 | if(type == "Boolean")
32 | value = Boolean(value);
33 | select[field] = value;
34 | return select;
35 | break;
36 | case ">":
37 | if(type == "Number")
38 | value = Number(value);
39 | if(type == "Date")
40 | value = new Date(value);
41 | select[field] = {$gt: value};
42 | return select;
43 | break;
44 | case "<":
45 | if(type == "Number")
46 | value = Number(value);
47 | if(type == "Date")
48 | value = new Date(value);
49 | select[field] = {$lt: value};
50 | return select;
51 | break;
52 | case containsValue:
53 | select[field] = {$regex: value, $options: 'i'};
54 | return select;
55 | break;
56 | case regExpValue:
57 | select[field] = {$regex: value, $options: options};
58 | return select;
59 | break;
60 | /*
61 | case "has_key":
62 | select["$where"] = function() { return this.object.attribute };
63 | return select;
64 | break;
65 | case "has_value":
66 | select["$where"] = function() { return this.object[Object.keys(this.object)[0]] == "complex"};
67 | return select;
68 | break;
69 | */
70 | default:
71 | return {};
72 | }
73 | }
74 |
75 | Template.expression_filter.rendered = function(){
76 | var no = Session.get("filter_counter");
77 | no = no[no.length-1];
78 | Blaze.renderWithData(Template.simple_filter, {"no": no}, document.getElementById('expression_filter'));
79 | }
80 |
81 | Template.filter_fields.helpers({
82 | title: function() {
83 | var sess = Session.get("tabular-filter")
84 | if(sess && (sess.label || sess.label == ''))
85 | return sess.label
86 | return 'On Field'
87 | },
88 | fields: function(){
89 | var input = Session.get("tabular-filter")
90 | if(!input)
91 | input = Session.get("schema")
92 | if(input){
93 | if(typeof input == 'object')
94 | var schema = input.schema
95 | else
96 | var schema = input
97 | var fieldObj = Schemas[schema].schema();
98 | var keys = Object.keys(fieldObj);
99 | var fieldArr = [], ind;
100 | for(k in keys){
101 | if(!fieldObj[keys[k]].tabularFilterOmit && keys[k].substring([keys[k].length-2]) != '.$') {
102 | ind = keys[k].indexOf('.$.')
103 | if(ind !== -1)
104 | fieldObj[keys[k]].value = keys[k].substring(0,ind) + keys[k].substring(ind+2)
105 | else
106 | fieldObj[keys[k]].value = keys[k];
107 | fieldArr.push(fieldObj[keys[k]]);
108 | }
109 | }
110 | return fieldArr;
111 | }
112 | }
113 | });
114 |
115 | Template.filter_fields.onCreated(function(){
116 | if ((ref = Session.get('tabular-filter')) != null ? ref.regex_value : void 0){
117 | regExpValue = Session.get('tabular-filter').regex_value
118 | }
119 | else{
120 | regExpValue = "regex"
121 | }
122 | if ((ref = Session.get('tabular-filter')) != null ? ref.contains_value : void 0){
123 | containsValue = Session.get('tabular-filter').contains_value
124 | }
125 | else{
126 | containsValue = "contains"
127 | }
128 | operators = {
129 | "StringExact":[
130 | "="
131 | ],
132 | "String":[
133 | "=",
134 | containsValue,
135 | regExpValue
136 | ],
137 | "Number":[
138 | "=",
139 | ">",
140 | "<"
141 | ],
142 | "Boolean": [
143 | "="
144 | ],
145 | "Date": [
146 | "=",
147 | "<",
148 | ">"
149 | ],
150 | "Object": [
151 | "has value",
152 | "has key"
153 | ]
154 | }
155 | //filter_operators = ["true", "false", "AND", "OR", "FieldExpr"];
156 | var andLabel, orLabel;
157 | if ((ref = Session.get('tabular-filter')) != null ? ref.and_label : void 0){
158 | andLabel = Session.get('tabular-filter').and_label
159 | }
160 | else{
161 | andLabel = 'AND'
162 | }
163 |
164 | if ((ref = Session.get('tabular-filter')) != null ? ref.or_label : void 0){
165 | orLabel = Session.get('tabular-filter').or_label
166 | }
167 | else{
168 | orLabel = 'OR'
169 | }
170 |
171 | filter_operators = [{key: "AND", label: andLabel}, {key: "OR", label: orLabel}];
172 | });
173 |
174 | Template.filter_fields.events({
175 | 'click .dropdown-menu li a': function(event){
176 | var no = Session.get("filter_counter");
177 | no = no[no.length-1];
178 |
179 | if($(event.target.parentElement.parentElement).attr("aria-labelledby") == "field" + no ){
180 | if(!$('#field_button').length)
181 | Blaze.render(Template.button_locations, document.getElementById('simple_filter' + no ));
182 | Session.set("autoform_options","{}");
183 | dropdown_select($(this)[0], "field" + no);
184 | var type = $(this)[0].type.name;
185 |
186 | if ($(this)[0].exactMatch){
187 | type = 'StringExact';
188 | }
189 |
190 | $("#field" + no + ":first-child").attr("field_type", type);
191 | $('#field_operations' + no ).html('');
192 | Blaze.renderWithData(Template.filter_operations, {first: operators[type][0], all: operators[type], no: no}, document.getElementById('field_operations' + no ));
193 | $('#field_value' + no ).html('');
194 | $("#operator" + no + ":first-child").val(operators[type][0]);
195 | if($(this)[0].allowedValues){
196 | Blaze.renderWithData(Template.filter_value_select, {options: $(this)[0].allowedValues, no: no}, document.getElementById('field_value' + no ));
197 | }
198 | else
199 | if(type == "Date")
200 | Blaze.renderWithData(Template.filter_value_datepicker, {no: no}, document.getElementById('field_value' + no ));
201 | else
202 | if($(this)[0].autoform && $(this)[0].autoform.options){
203 | var options = $(this)[0].autoform.options();
204 | var values = [];
205 | var map = {};
206 | for(o in options){
207 | values.push(options[o].label);
208 | map[options[o].label] = options[o].value;
209 | }
210 | Session.set("autoform_options", JSON.stringify(map));
211 | Blaze.renderWithData(Template.filter_value_select, {options: values, no: no}, document.getElementById('field_value' + no ));
212 | }
213 | else
214 | Blaze.renderWithData(Template.filter_value_input, {no:no}, document.getElementById('field_value' + no ));
215 |
216 | if($('#filter_operators' + no ).html() == '')
217 | Blaze.renderWithData(Template.filter_operators, {operators: filter_operators, no:no}, document.getElementById('filter_operators' + no ));
218 | if($('#filter_button_delete' + no ).html() == '')
219 | Blaze.renderWithData(Template.filter_button_delete, {no: no}, document.getElementById('filter_button_delete' + no));
220 | if($('#field_button_reset').html() == '')
221 | Blaze.render(Template.filter_button_reset, document.getElementById('field_button_reset'));
222 | if($('#field_button').html() == '')
223 | Blaze.render(Template.filter_button, document.getElementById('field_button'));
224 | }
225 | }
226 | });
227 |
228 | Template.filter_operations.events({
229 | 'click .dropdown-menu li a': function(event){
230 | var no = Session.get("filter_counter");
231 | no = no[no.length-1];
232 | if($(event.target.parentElement.parentElement).attr("aria-labelledby") == "operator" + no ){
233 | var val = event.target.textContent;
234 | dropdown_select({"label": val, "value": val}, "operator" + no);
235 | }
236 | }
237 | });
238 |
239 | op_val = function(op){
240 | switch(op){
241 | case "AND":
242 | return "$and";
243 | break;
244 | case "OR":
245 | return "$or";
246 | break;
247 | default:
248 | return "$and";
249 | }
250 | }
251 |
252 | create_filter_selector = function(q, op, no){
253 | var operator = op_val(op[no]);
254 |
255 | var result = {};
256 | if(no > 0){
257 | var query1 = create_filter_selector(q, op, no-1);
258 | result[operator] = [ query1 , q[no+1] ];
259 | }
260 | else
261 | result[operator] = [ q[no], q[no+1] ] ;
262 |
263 | return result
264 | }
265 |
266 | set_filter_selector = function(no){
267 | if($('#filter_value' + no ).val()){
268 | if(Session.get("autoform_options") && Session.get("autoform_options") != "{}"){
269 | var map = Session.get("autoform_options");
270 | map = JSON.parse(map);
271 | var value = map[$('#filter_value' + no ).val()];
272 | }
273 | else
274 | var value = $('#filter_value' + no ).val();
275 | var field = $("#field" + no + ":first-child").val();
276 | var op = $("#operator" + no + ":first-child").val();
277 | var type = $("#field" + no + ":first-child").attr("field_type");
278 | var selector = create_selector(field, op, value, type);
279 | var queries = Session.get("filter_queries");
280 | //queries.push(selector);
281 | queries[no] = selector;
282 | Session.set("filter_queries", queries);
283 | }
284 | }
285 |
286 | Template.filter_button.events({
287 | 'click #call_filter': function(){
288 |
289 | var no = Session.get("filter_counter");
290 | no = no[no.length-1];
291 | set_filter_selector(no);
292 |
293 | if(Object.keys(Session.get("filter_queries")).length == 1){
294 | Session.set("filter_selector", Session.get("filter_queries")[no]);
295 | }
296 | else{
297 | var ops = object_values(Session.get("filter_ops"));
298 | Session.set("filter_selector", create_filter_selector(object_values(Session.get("filter_queries")), ops, ops.length-1));
299 | }
300 | }
301 | });
302 |
303 | Template.filter_button_delete.events({
304 | 'click .delete_filter': function(event){
305 | var no = Number(event.currentTarget.id.match(/[0-9]+$/)[0]);
306 | $('#simple_filter' + no ).remove();
307 |
308 | Session.set("autoform_options","{}");
309 | var counter = Session.get("filter_counter");
310 | counter.splice(counter.indexOf(no),1);
311 |
312 | Session.set("filter_counter", counter);
313 | var queries = Session.get("filter_queries");
314 |
315 | delete queries[no];
316 | Session.set("filter_queries", queries);
317 |
318 | var ops = Session.get("filter_ops");
319 | delete ops[no];
320 | delete ops[no-1];
321 |
322 | Session.set("filter_ops", ops);
323 | $('#filter_operators_values' + counter[counter.length-1] ).val('...');
324 |
325 | if(counter.length == 0){
326 | Session.set("filter_counter", [1]);
327 | Blaze.renderWithData(Template.simple_filter, {"no": 1}, document.getElementById('expression_filter'));
328 | Session.set("filter_selector",'');
329 | }
330 | else
331 | if(Object.keys(Session.get("filter_queries")).length == 1){
332 | Session.set("filter_selector", Session.get("filter_queries")[no-1]);
333 | }
334 | else
335 | Session.set("filter_selector", create_filter_selector(object_values(Session.get("filter_queries")), object_values(ops), Object.keys(ops).length-1));
336 |
337 | var counter = Session.get("filter_counter");
338 |
339 | Blaze.render(Template.button_locations, document.getElementById('simple_filter' + counter[counter.length-1] ));
340 | Blaze.render(Template.filter_button, document.getElementById('field_button'));
341 | }
342 | });
343 |
344 | Template.filter_operators.events({
345 | 'change .filter_operators': function(event){
346 | // make sure all options are completed ( should disable operators until they are)
347 | var no = Number(event.currentTarget.id.match(/[0-9]+$/)[0]);
348 | set_filter_selector(no);
349 | var add = true;
350 |
351 | var op = $('#filter_operators_values' + no).val();
352 | var ops = Session.get("filter_ops");
353 | if(ops[no])
354 | add = false;
355 | ops[no] = op;
356 | Session.set("filter_ops", ops);
357 |
358 | if(add){
359 | var counter = Session.get("filter_counter");
360 | counter.push(counter[counter.length-1]+1);
361 | Session.set("filter_counter", counter);
362 |
363 | Blaze.renderWithData(Template.simple_filter, {"no": counter[counter.length-1]}, document.getElementById('expression_filter'));
364 | $('#field_button').remove();
365 | Blaze.render(Template.button_locations, document.getElementById('simple_filter' + counter[counter.length-1] ));
366 | }
367 | }
368 | });
369 |
370 | Template.filter_value_input.helpers({
371 | input_value_placeholder: function() {
372 | var sess = Session.get("tabular-filter")
373 | if (sess && (sess.input_value_placeholder || sess.input_value_placeholder == ''))
374 | return sess.input_value_placeholder
375 | return 'Input Value'
376 | }
377 | });
378 |
379 |
380 | }
--------------------------------------------------------------------------------