radiant-fabulator_exhibit-extension 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,579 @@
1
+ /*
2
+ * (c) Copyright Texas A&M University 2010. All rights reserved.
3
+ *
4
+ * Portions of this code are copied from The SIMILE Project:
5
+ * (c) Copyright The SIMILE Project 2006. All rights reserved.
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions
9
+ * are met:
10
+ *
11
+ * 1. Redistributions of source code must retain the above copyright
12
+ * notice, this list of conditions and the following disclaimer.
13
+ *
14
+ * 2. Redistributions in binary form must reproduce the above copyright
15
+ * notice, this list of conditions and the following disclaimer in the
16
+ * documentation and/or other materials provided with the distribution.
17
+ *
18
+ * 3. The name of the author may not be used to endorse or promote products
19
+ * derived from this software without specific prior written permission.
20
+ *
21
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ *
32
+ */
33
+
34
+ Fabulator.namespace('Exhibit');
35
+
36
+ (function($, Exhibit) {
37
+
38
+ Exhibit.FacetUtilities = { };
39
+
40
+ Exhibit.FacetUtilities.constructFacetFrame = function(
41
+ container, view, options
42
+ ) {
43
+ var that = { }, myid = $(container).attr("id");
44
+
45
+ $(container).addClass("exhibit-facet");
46
+
47
+ $("<div class='header'>" +
48
+ (options.onClearAllSelections ? (
49
+ "<div class='filterControl' id='" + myid + "-clearSelectionsDiv' title='Clear Selection'>" +
50
+ "<span id='" + myid + "-filterCountSpan'></span>" +
51
+ "</div>" ) : ("") ) +
52
+ "<span class='title'>" + options.facetLabel + "</span>" +
53
+ "</div>" +
54
+ "<div class='body-frame' id='" + myid + "-frame'>" +
55
+ "<div class='body' id='" + myid + "-values'></div>" +
56
+ "</div>").appendTo(container);
57
+
58
+ /* if(options.resizable) {
59
+ $('#' + myid + '-frame').resizable("enable");
60
+ } */
61
+ that.valuesContainer = $('#' + myid + '-values');
62
+
63
+ that.setSelectionCount = function(count) {
64
+ $('#' + myid + '-filterCountSpan').innerHTML = count;
65
+ if( count > 0 ) {
66
+ $('#' + myid + '-filterSelectionsDiv').show();
67
+ }
68
+ else {
69
+ $('#' + myid + '-filterSelectionsDiv').hide();
70
+ }
71
+ };
72
+
73
+ if(options.onClearAllSelections) {
74
+ $('#' + myid + '-clearSelectionsDiv').bind("click", onClearAllSelections);
75
+ }
76
+
77
+ return that;
78
+ };
79
+
80
+ Exhibit.FacetUtilities.constructFacetItem = function(
81
+ label,
82
+ count,
83
+ color,
84
+ selected,
85
+ facetHasSelection,
86
+ onSelect,
87
+ onSelectOnly,
88
+ view
89
+ ) {
90
+ var dom = $("<div></div>");
91
+
92
+ $(dom).addClass("exhibit-facet-value");
93
+ $(dom).addClass("ui-corner-all");
94
+
95
+ if(typeof(label) == "string") {
96
+ $(dom).attr("title", label);
97
+ $(dom).text(label + " (" + count + ")");
98
+ }
99
+ else {
100
+ $(label).appendTo($(dom));
101
+ }
102
+
103
+ if( facetHasSelection ) {
104
+ if( selected ) {
105
+ $(dom).addClass("exhibit-facet-selected");
106
+ $("<span class='selection'>&#x2713;</span>").prependTo($(dom));
107
+ }
108
+ else {
109
+ $(dom).addClass("exhibit-facet-not-selected");
110
+ }
111
+ }
112
+
113
+ if(color != null) {
114
+ $(dom).style.color = color;
115
+ }
116
+
117
+ if(facetHasSelection) {
118
+ $(dom).bind("click", onSelect);
119
+ }
120
+ else {
121
+ $(dom).bind("click", onSelectOnly);
122
+ }
123
+
124
+ return dom;
125
+ };
126
+
127
+ Exhibit.FacetUtilities.constructFlowingFacetItem = function(
128
+ ) {
129
+ };
130
+
131
+ Exhibit.Facets = function(container, options) {
132
+ var that = fluid.initView("Fabulator.Exhibit.Facets", container, options),
133
+ facetClass, facetClassObj;
134
+ options = that.options;
135
+
136
+ that.facets = new Array();
137
+
138
+ $(container).children().each(function(idx, el) {
139
+ if($(el).attr('ex:role') == 'facet') {
140
+ facetClass = $(el).attr('ex:facetClass');
141
+ facetClassObj = Fabulator.Exhibit.Facets[facetClass];
142
+
143
+ if(typeof(facetClassObj) != "undefined") {
144
+ that.facets.push(facetClassObj(el, { viewPanel: options.viewPanel }));
145
+ }
146
+ }
147
+ });
148
+
149
+ $(options.trigger).attr('rel', '#' + $(container).attr('id'));
150
+ $(container).addClass('facets-overlay');
151
+ $(options.trigger).overlay();
152
+ $(container).addClass('ui-corner-all');
153
+ $("#" + $(container).attr("id") + " a.close").addClass("ui-icon");
154
+ $("#" + $(container).attr("id") + " a.close").addClass("ui-icon-circle-close");
155
+
156
+ return that;
157
+ };
158
+
159
+ Exhibit.Facets.Base = function(type, container, options) {
160
+ var that = fluid.initView("Fabulator.Exhibit.Facets." + type, container, options);
161
+
162
+ var parseSetting = function(s, type, spec) {
163
+ var sType = typeof s, f, i, n, choices;
164
+
165
+ if(type == "text") {
166
+ return s;
167
+ }
168
+ else if( type == "float" ) {
169
+ if( sType == "number" ) {
170
+ return s;
171
+ }
172
+ else if( sType == "string" ) {
173
+ f = parseFloat(s);
174
+ if( !isNaN(f) ) {
175
+ return f;
176
+ }
177
+ }
178
+ throw new Error("Expected a floating point number but got " + s);
179
+ }
180
+ else if( type == "int" ) {
181
+ if( sType == "number" ) {
182
+ return Math.round(s);
183
+ }
184
+ else if( sType == "string" ) {
185
+ n = parseInt(s);
186
+ if( !isNaN(n) ) {
187
+ return n;
188
+ }
189
+ }
190
+ throw new Error("Expected an integer but got " + s);
191
+ }
192
+ else if(type == "boolean" ) {
193
+ if( sType == "boolean" ) {
194
+ return s;
195
+ }
196
+ else if( sType == "string" ) {
197
+ s = s.toLowerCase();
198
+ if( s == "true" || s == "on" || s == "yes" ) {
199
+ return true;
200
+ }
201
+ else if( s == "false" || s == "off" || s == "no" ) {
202
+ return false;
203
+ }
204
+ }
205
+ throw new Error("Expected either 'true' or 'false' but got " + s);
206
+ }
207
+ else if( type == "function" ) {
208
+ if( sType == "function" ) {
209
+ return s;
210
+ }
211
+ else if( sType == "string" ) {
212
+ try {
213
+ f = eval(s);
214
+ if( typeof f == "function") {
215
+ return f;
216
+ }
217
+ }
218
+ catch(e) {
219
+ // silent
220
+ }
221
+ }
222
+ throw new Error("Expected a function or the name of a function but got " + s);
223
+ }
224
+ else if( type == "enum" ) {
225
+ choices = spec.choices;
226
+ for(i = 0, n = choices.length; i < n; i += 1 ) {
227
+ if(choices[i] == s) {
228
+ return s;
229
+ }
230
+ }
231
+ throw new Error("Expected one of " + choices.join(", ") + " but got " + s);
232
+ }
233
+ else if( type == "expression" ) {
234
+ return Exhibit.ExpressionParser().parse(s);
235
+ }
236
+ else {
237
+ throw new Error("Unknown setting type " + type);
238
+ }
239
+ };
240
+
241
+ that.collectSettingsFromDOM = function(specs) {
242
+ var field, spec, name, settings, type, value, dimensions,
243
+ separator, a, i, n;
244
+
245
+ that.options.facet = that.options.facet || { };
246
+
247
+ settings = that.options.facet;
248
+
249
+ for(field in specs) {
250
+ spec = specs[field];
251
+ name = field;
252
+ if( "name" in spec ) {
253
+ name = spec.name;
254
+ }
255
+ if( !(name in settings) && "defaultValue" in spec) {
256
+ settings[name] = spec.defaultValue;
257
+ }
258
+
259
+ value = $(container).attr("ex:" + field);
260
+ if( value == null ) {
261
+ continue;
262
+ }
263
+
264
+ if( typeof value == "string") {
265
+ value = value.trim();
266
+ if( value.length == 0 ) {
267
+ continue;
268
+ }
269
+ }
270
+
271
+ type = "text";
272
+ if( "type" in spec ) {
273
+ type = spec.type;
274
+ }
275
+
276
+ dimensions = 1;
277
+ if( "dimensions" in spec ) {
278
+ dimensions = spec.dimensions;
279
+ }
280
+
281
+ try {
282
+ if( dimensions > 1 || dimensions == '*') {
283
+ separator = ",";
284
+ if( "separator" in spec ) {
285
+ separator = spec.separator;
286
+ }
287
+
288
+ a = value.split(separator);
289
+ if( dimensions != '*' && a.length != dimensions ) {
290
+ throw new Error("Expected a tuple of " + dimensions + " dimensions separated with " + separator + " but got " + value);
291
+ }
292
+ else {
293
+ for(i = 0, n = a.length; i < n; i += 1 ) {
294
+ a[i] = parseSetting(a[i].trim(), type, spec);
295
+ }
296
+
297
+ settings[name] = a;
298
+ }
299
+ }
300
+ else {
301
+ settings[name] = parseSetting(value, type, spec);
302
+ }
303
+ }
304
+ catch(e) {
305
+ console.log(e);
306
+ }
307
+ }
308
+
309
+ that.options.facet = settings;
310
+ };
311
+
312
+ options = that.options;
313
+
314
+ that.events = { };
315
+ that.events.onFilterChange = fluid.event.getEventFirer();
316
+
317
+ options.viewPanel.registerFilter(that);
318
+
319
+ if( "settingSpec" in options ) {
320
+ that.collectSettingsFromDOM(options.settingSpec);
321
+ }
322
+
323
+ return that;
324
+ };
325
+
326
+ Exhibit.Facets.TextSearch = function(container, options) {
327
+ var that = Exhibit.Facets.Base("TextSearch", container, options),
328
+ dom, input_id;
329
+ options = that.options;
330
+
331
+ if( !( that.options.facet.expressions instanceof Array ) ) {
332
+ that.options.facet.expressions = [
333
+ that.options.facet.expressions
334
+ ];
335
+ }
336
+
337
+ that.eventFilterItem = function(dataSource, id) {
338
+ /* check if the expressions yield something that matches the
339
+ text in the input field */
340
+ var values, i, n;
341
+
342
+ if( that.text && that.options.facet.expression ) {
343
+ values = [ ];
344
+ $(that.options.facet.expression).each(function(idx, ex) {
345
+ var items = ex.evaluateOneItem(id, dataSource);
346
+ values = values.concat(items.values.items());
347
+ });
348
+ n = values.length;
349
+ for(i = 0; i < n; i += 1) {
350
+ if(values[i].toLowerCase().indexOf(that.text) >= 0) {
351
+ return; // at least one value matches at least one expression
352
+ }
353
+ }
354
+ return false;
355
+ }
356
+ };
357
+
358
+ that.eventModelChange = function(dataView) {
359
+ // we don't do anything
360
+ };
361
+
362
+ dom = Exhibit.FacetUtilities.constructFacetFrame(container, null, { facetLabel: that.options.facet.facetLabel });
363
+
364
+ input_id = $(container).attr("id") + "-input";
365
+
366
+ $("<input type='text' id='" + input_id + "'>").appendTo($(dom.valuesContainer));
367
+
368
+ $("#" + input_id).keyup(function() {
369
+ that.text = $("#" + input_id).val().toLowerCase().trim();
370
+ that.events.onFilterChange.fire();
371
+ });
372
+
373
+ return that;
374
+ };
375
+
376
+ Exhibit.Facets.List = function(container, options) {
377
+ var that = Exhibit.Facets.Base("List", container, options),
378
+ valueSet = Exhibit.Set(),
379
+ counts = { },
380
+ entries = [ ],
381
+ dom, filter, populateEntriesFunction, constructFacetItemFunction;
382
+
383
+ options = that.options;
384
+
385
+ dom = Exhibit.FacetUtilities.constructFacetFrame(container, null, { facetLabel: that.options.facet.facetLabel, resizable: true });
386
+
387
+ constructFacetItemFunction = Exhibit.FacetUtilities[
388
+ options.facet.scroll ? "constructFacetItem" : "constructFlowingFacetItem"
389
+ ];
390
+
391
+ if( "selection" in options.facet ) {
392
+ filter = Exhibit.Set(options.facet.selection);
393
+
394
+ populateEntriesFunction = function(dataView) {
395
+ var items = dataView.itemSet(),
396
+ i, n, facetValueResult, path,
397
+ valueType = "text";
398
+
399
+ entries = [ ];
400
+
401
+ path = options.facet.expression;
402
+ facetValueResult = path.walkForward(items, "item", dataView.dataSource);
403
+ valueType = facetValueResult.valueType;
404
+
405
+ if( facetValueResult.size > 0 ) {
406
+ facetValueResult.forEachValue(function(facetValue) {
407
+ var itemSubcollection;
408
+ if( filter.contains(facetValue) ) {
409
+ itemSubcollection = path.evaluateBackward(facetValue, valueType, Exhibit.Set(items), dataView.dataSource);
410
+ entries.push({ value: facetValue, count: itemSubcollection.size, selectionLabel: facetValue });
411
+ }
412
+ });
413
+ }
414
+
415
+ return valueType;
416
+ };
417
+ }
418
+ else {
419
+ populateEntriesFunction = function(dataView) {
420
+ var items = dataView.items(),
421
+ i, n, facetValueResult, path,
422
+ valueType = "text";
423
+
424
+ entries = [ ];
425
+
426
+ path = options.facet.expression;
427
+ facetValueResult = path.walkForward(items, "item", dataView.dataSource);
428
+ valueType = facetValueResult.valueType || "text";
429
+
430
+ if( facetValueResult.size() > 0 ) {
431
+ facetValueResult.forEachValue(function(facetValue) {
432
+ var itemSubcollection = path.evaluateBackward(facetValue, valueType, items, dataView.dataSource);
433
+ entries.push({ value: facetValue, count: itemSubcollection.size(), selectionLabel: facetValue, selected: valueSet.contains(facetValue) });
434
+ });
435
+ }
436
+
437
+ return valueType;
438
+ };
439
+ }
440
+
441
+ var computeFacet = function(dataView) {
442
+ var selection, labeler, entry, count, valueType, sortValueFunction,
443
+ orderMap, sortFunction, sortDirectionFunction;
444
+
445
+ valueType = populateEntriesFunction(dataView);
446
+
447
+ if( entries.length > 0 ) {
448
+ sortValueFunction = function(a, b) { return (a.selectionLabel < b.selectionLabel ? -1 : (a.selectionLabel > b.selectionLabel ? 1 : 0)); };
449
+ if(valueType == "number") {
450
+ sortValueFunction = function(a, b) {
451
+ a = parseFloat(a.value);
452
+ b = parseFloat(b.value);
453
+ return a < b ? -1 : a > b ? 1 : 0;
454
+ };
455
+ }
456
+
457
+ sortFunction = sortValueFunction;
458
+ if(options.facet.sortMode == "count") {
459
+ sortFunction = function(a,b) {
460
+ var c = b.count - a.count;
461
+ return c != 0 ? c : sortValueFunction(a,b);
462
+ };
463
+ }
464
+
465
+ sortDirectionFunction = sortFunction;
466
+ if(options.facet.sortDirection == "reverse") {
467
+ sortDirectionFunction = function(a,b) {
468
+ return sortFunction(b,a);
469
+ }
470
+ }
471
+
472
+ entries.sort(sortDirectionFunction);
473
+ }
474
+ };
475
+
476
+ that.eventModelChange = function(dataView) {
477
+ var facetHasSelection = valueSet.size() > 0 || options.facet.selectMissing,
478
+ constructValue,
479
+ j, n;
480
+
481
+ computeFacet(dataView);
482
+
483
+ constructValue = function(entry) {
484
+ var onSelect = function(elmt, evt, target) {
485
+ // we need to manage class settings
486
+ if( valueSet.contains(entry.value) ) {
487
+ valueSet.remove(entry.value);
488
+ }
489
+ else {
490
+ valueSet.add(entry.value);
491
+ }
492
+ that.events.onFilterChange.fire();
493
+ return false;
494
+ },
495
+ onSelectOnly = function(elmt, evt, target) {
496
+ // we need to manage class settings
497
+ if( valueSet.contains(entry.value) ) {
498
+ valueSet = Exhibit.Set();
499
+ }
500
+ else {
501
+ valueSet = Exhibit.Set();
502
+ valueSet.add(entry.value);
503
+ }
504
+ that.events.onFilterChange.fire();
505
+ return false;
506
+ },
507
+ elmt = constructFacetItemFunction(
508
+ entry.selectionLabel,
509
+ entry.count,
510
+ null,
511
+ entry.selected,
512
+ facetHasSelection,
513
+ onSelect,
514
+ onSelectOnly,
515
+ dataView
516
+ );
517
+
518
+ $(elmt).appendTo($(dom.valuesContainer));
519
+ };
520
+
521
+ $(dom.valuesContainer).hide();
522
+
523
+ $(dom.valuesContainer).empty();
524
+
525
+ for(j = 0, n = entries.length; j < n; j += 1) {
526
+ constructValue(entries[j]);
527
+ }
528
+ $(dom.valuesContainer).show();
529
+ dom.setSelectionCount(valueSet.size());
530
+ };
531
+
532
+ // that.eventModelChange(that.viewPanel.dataView);
533
+
534
+ that.eventFilterItem = function(dataSource, id) {
535
+ var values = options.facet.expression.evaluateOneItem(id, dataSource),
536
+ i, n;
537
+
538
+
539
+ if(valueSet.size() == 0) { return; }
540
+
541
+ values = values.values.items();
542
+
543
+ for(i = 0, n = values.length; i < n; i += 1) {
544
+ if(valueSet.contains(values[i])) {
545
+ return;
546
+ }
547
+ }
548
+
549
+ return false;
550
+ };
551
+
552
+
553
+ };
554
+ })(jQuery, Fabulator.Exhibit);
555
+
556
+ fluid.defaults("Fabulator.Exhibit.Facets", {
557
+ });
558
+
559
+ fluid.defaults("Fabulator.Exhibit.Facets.TextSearch", {
560
+ settingSpec: {
561
+ "facetLabel": { type: "text", defaultValue: "Search" },
562
+ "expression": { type: "expression", defaultValue: [ Fabulator.Exhibit.ExpressionParser().parse(".label") ], dimensions: '*' },
563
+ "queryParamName": { type: "text" },
564
+ "requiresEnter": { type: "boolean", defaultValue: false }
565
+ }
566
+ });
567
+
568
+ fluid.defaults("Fabulator.Exhibit.Facets.List", {
569
+ settingSpec: {
570
+ "facetLabel": { type: "text", defaultValue: "Search" },
571
+ "expression": { type: "expression", defaultValue: Fabulator.Exhibit.ExpressionParser().parse(".label") },
572
+ "sortMode": { type: "text", defaultValue: "value" },
573
+ "sortDirection": { type: "text", defaultValue: "forward" },
574
+ "showMissing": { type: "boolean", defaultValue: false },
575
+ "scroll": { type: "boolean", defaultValue: true },
576
+ "height": { type: "numeric" },
577
+ "collapsed": { type: "boolean", defaultValue: false }
578
+ }
579
+ });