Skip to content

Commit 37047ca

Browse files
committed
cleanup code, more comments
1 parent 83809d6 commit 37047ca

File tree

1 file changed

+88
-63
lines changed

1 file changed

+88
-63
lines changed

src/minicharts/querybuilder.js

Lines changed: 88 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,24 @@ var d3 = require('d3');
44
var LeafValue = require('mongodb-language-model').LeafValue;
55
var ListOperator = require('mongodb-language-model').ListOperator;
66
var Range = require('mongodb-language-model').helpers.Range;
7+
var debug = require('debug')('scout:minicharts:querybuilder');
78

89
var MODIFIERKEY = 'shiftKey';
910

1011
module.exports = {
1112
/**
12-
* Extract a value that can be compared
13+
* Extract a value that can be ordered (e.g. number, date, ...)
1314
* @param {Object} d event data object triggered by the minichart
1415
* @return {Any} value to be returned that can be used for comparisons < and >
1516
*/
16-
_getComparableValue: function(d) {
17+
_getOrderedValue: function(d) {
1718
return d.value._bsontype === 'ObjectID' ? d.value.getTimestamp() : d.value;
1819
},
1920

2021
/**
2122
* build new distinct ($in) query based on current selection and store as this.refineValue.
2223
*/
23-
buildDistinctQuery: function() {
24+
buildQuery_distinct: function() {
2425
// build new refineValue
2526
if (this.selectedValues.length === 0) {
2627
// no value
@@ -42,27 +43,69 @@ module.exports = {
4243
}
4344
},
4445

46+
/**
47+
* build new range ($gte, $lt(e)) query based on current selection and store as this.refineValue.
48+
*/
49+
buildQuery_range: function() {
50+
var firstSelected = this.selectedValues[0];
51+
var getOrderedValue = this._getOrderedValue.bind(this);
52+
53+
if (!firstSelected) {
54+
this.unset('refineValue');
55+
return;
56+
}
57+
var first = _.min(this.selectedValues, function(el) {
58+
return getOrderedValue(el);
59+
});
60+
var last = _.max(this.selectedValues, function(el) {
61+
return getOrderedValue(el);
62+
});
63+
var upperInclusive = last.dx === 0;
64+
65+
var lower = first.value;
66+
var upper = last.value;
67+
if (this.model.getType() === 'Number') {
68+
upper += last.dx;
69+
}
70+
if (lower === upper) {
71+
this.refineValue = new LeafValue({
72+
content: lower
73+
});
74+
} else {
75+
this.refineValue = new Range(lower, upper, upperInclusive);
76+
}
77+
},
78+
4579
/**
4680
* Handler for query builder events that result in distinct selection, e.g. string and unique
4781
* type. Single click selects individual element, shift-click adds to selection.
48-
* @param {Object} data the contains information about the event, @see handleQueryBuilderEvent
82+
*
83+
* For distinct-type minicharts, this.selectedValues contains an entry for each selected value.
84+
*
85+
* @param {Object} data contains information about the event, @see handleQueryBuilderEvent
4986
*/
50-
handleDistinctEvent: function(data) {
87+
handleEvent_distinct: function(data) {
5188
// update selectedValues
5289
if (!data.evt[MODIFIERKEY]) {
5390
if (this.selectedValues.length === 1 && this.selectedValues[0].value === data.d.value) {
91+
// case where 1 element is selected and it is clicked again (need to unselect)
5492
this.selectedValues = [];
5593
} else {
94+
// case where multiple or no elements are selected (need to select that one item)
5695
this.selectedValues = [data.d];
5796
}
5897
} else if (_.contains(_.pluck(this.selectedValues, 'value'), data.d.value)) {
98+
// case where selected element is shift-clicked (need to remove from selection)
5999
_.remove(this.selectedValues, function(d) {
60100
return d.value === data.d.value;
61101
});
62102
} else {
103+
// case where unselected element is shift-clicked (need to add to selection)
63104
this.selectedValues.push(data.d);
64105
}
65106

107+
debug('selectedValues', this.selectedValues);
108+
66109
// visual updates
67110
_.each(data.all, function(el) {
68111
var elData = data.source === 'unique' ? el.innerText : d3.select(el).data()[0].value;
@@ -80,51 +123,26 @@ module.exports = {
80123
}.bind(this));
81124
},
82125

83-
/**
84-
* build new range ($gte, $lt(e)) query based on current selection and store as this.refineValue.
85-
*/
86-
buildRangeQuery: function() {
87-
var firstSelected = this.selectedValues[0];
88-
var getComparableValue = this._getComparableValue.bind(this);
89-
90-
if (!firstSelected) {
91-
this.unset('refineValue');
92-
return;
93-
}
94-
var first = _.min(this.selectedValues, function(el) {
95-
return getComparableValue(el);
96-
});
97-
var last = _.max(this.selectedValues, function(el) {
98-
return getComparableValue(el);
99-
});
100-
var upperInclusive = last.dx === 0;
101-
102-
var lower = first.value;
103-
var upper = last.value;
104-
if (this.model.getType() === 'Number') {
105-
upper += last.dx;
106-
}
107-
if (lower === upper) {
108-
this.refineValue = new LeafValue({
109-
content: lower
110-
});
111-
} else {
112-
this.refineValue = new Range(lower, upper, upperInclusive);
113-
}
114-
},
115-
116126
/**
117127
* Handler for query builder events that result in range selection, e.g. number type.
118128
* single click selects individual element, shift-click extends to range (the single click is
119129
* interpreted as one end of the range, shift-click as the other).
130+
*
131+
* For range-type minicharts, this.selectedValues contains two values, the lower and upper bound.
132+
* The 0th value is the one selected via regular click, the 1st value is the shift-clicked one.
133+
* If only a single value is selected ($eq), is stored at the 0th index.
134+
*
120135
* @param {Object} data the contains information about the event, @see handleQueryBuilderEvent
121136
*/
122-
handleRangeEvent: function(data) {
137+
handleEvent_range: function(data) {
123138
if (data.evt[MODIFIERKEY]) {
139+
// shift-click modifies the value at index 1
124140
this.selectedValues[1] = data.d;
125141
} else if (this.selectedValues[0] && this.selectedValues[0].value === data.d.value) {
142+
// case where single selected item is clicked again (need to unselect)
126143
this.selectedValues = [];
127144
} else {
145+
// case where multiple or no elements are selected (need to just select one item)
128146
this.selectedValues = [data.d];
129147
}
130148
var firstSelected = this.selectedValues[0];
@@ -137,21 +155,18 @@ module.exports = {
137155
el.classList.add('unselected');
138156
}
139157
});
140-
if (!firstSelected) {
141-
// no value
142-
this.unset('refineValue');
143-
} else {
144-
var getComparableValue = this._getComparableValue.bind(this);
158+
if (firstSelected) {
159+
var getOrderedValue = this._getOrderedValue.bind(this);
145160
var first = _.min(this.selectedValues, function(el) {
146-
return getComparableValue(el);
161+
return getOrderedValue(el);
147162
});
148163
var last = _.max(this.selectedValues, function(el) {
149-
return getComparableValue(el);
164+
return getOrderedValue(el);
150165
});
151166

152-
// use getComparableValue to determine what elements should be selected
153-
var lower = getComparableValue(first);
154-
var upper = getComparableValue(last);
167+
// use getOrderedValue to determine what elements should be selected
168+
var lower = getOrderedValue(first);
169+
var upper = getOrderedValue(last);
155170
if (this.model.getType() === 'Number') {
156171
upper += last.dx;
157172
}
@@ -165,15 +180,22 @@ module.exports = {
165180
*/
166181
var upperInclusive = last.dx === 0;
167182
_.each(data.all, function(el) {
168-
var elData = getComparableValue(d3.select(el).data()[0]);
183+
var elData = getOrderedValue(d3.select(el).data()[0]);
169184
if (elData >= lower && (upperInclusive ? elData <= upper : elData < upper)) {
170185
el.classList.add('selected');
171186
el.classList.remove('unselected');
172187
}
173188
});
174189
}
175190
},
176-
handleDragEvent: function(data) {
191+
/**
192+
* Handler for query builder events created with a click-drag mouse action. The visual updates
193+
* are handled by d3 directly, so all we have to do is update the selected values based on the
194+
* selected elements.
195+
*
196+
* @param {Object} data the contains information about the event, @see handleQueryBuilderEvent
197+
*/
198+
handleEvent_drag: function(data) {
177199
this.selectedValues = d3.selectAll(data.selected).data();
178200
},
179201
/**
@@ -199,37 +221,40 @@ module.exports = {
199221
*
200222
*/
201223
handleQueryBuilderEvent: function(data) {
202-
var distinctEvent = data.type === 'drag' ? this.handleDragEvent : this.handleDistinctEvent;
203-
var rangeEvent = data.type === 'drag' ? this.handleDragEvent : this.handleRangeEvent;
224+
var queryType;
204225

205226
if (data.type === 'click') {
206227
data.evt.stopPropagation();
207228
data.evt.preventDefault();
208229
}
209230

231+
// determine what kind of query this is (distinct or range)
210232
switch (this.model.getType()) {
211233
case 'Boolean': // fall-through to String
212234
case 'String':
213-
distinctEvent.call(this, data);
214-
this.buildDistinctQuery();
235+
queryType = 'distinct';
215236
break;
216237
case 'Number':
217238
if (data.source === 'unique') {
218-
distinctEvent.call(this, data);
219-
this.buildDistinctQuery();
239+
queryType = 'distinct';
220240
} else {
221-
rangeEvent.call(this, data);
222-
this.buildRangeQuery();
241+
queryType = 'range';
223242
}
224243
break;
225244
case 'ObjectID': // fall-through to Date
226245
case 'Date':
227-
// @todo: for dates, data.all is not sorted, so this is not yet working
228-
rangeEvent.call(this, data);
229-
this.buildRangeQuery();
246+
queryType = 'range';
230247
break;
231248
default: // @todo other types not implemented yet
232-
break;
249+
throw new Error('unsupported querybuilder type ' + this.model.getType());
250+
}
251+
252+
// now call appropriate event handlers and query build methods
253+
if (data.type === 'drag') {
254+
this.handleEvent_drag(data);
255+
} else {
256+
this['handleEvent_' + queryType](data);
233257
}
258+
this['buildQuery_' + queryType]();
234259
}
235260
};

0 commit comments

Comments
 (0)