1 if (!dojo._hasResource['openils.widget.PCrudFilterPane']) {
3 /* openils.widget.PCrudFilterPane is a dijit that, given a fieldmapper
4 * class, provides a pane in which users can define inclusionary
5 * filters based on fields selected from the fieldmapper class and values
6 * for those fields. Operators can be selected so that not only equality
7 * comparisons are possible in the filter, but also inequality filters,
8 * likeness (for text fields only) betweenness, and nullity tests. *
9 * The dijit yields its result in the form of a JSON query suitable for
10 * use as the where clause of a pcrud search, via the onApply callback.
12 * In addition to its fmClass paramter, note the useful parameter
13 * suppressFilterFields. Say for instance you're using this dijit
14 * on an fmClass like "brt" which has a field "record" that points to the
15 * bre class. The AutoWidget provided for users to enter values for
16 * comparisons on the record field would be a dropdown containing all
17 * the bre ID's in the system! That would be unusable in any realistic
18 * system, unless/until we teach AutoWidget to use a lazy-loading store
21 * The comparisons in each filter row are "and-ed" together in the JSON
22 * query yielded, except for repetitions of the same field, which are
23 * "or-ed" together /within/ the overall "and" group. Look at comments
24 * within PCrudFilterRowManager.compile() for more information.
26 * AutoGrid has some ability to use this dijits based on this to offer a
27 * filtering dialog, but be aware that the filtering dialog is /not/ aware
28 * of other fitering measures in place in a given AutoGrid-based interface,
29 * such as (typically) context org unit selectors, and therefore using the
30 * context org unit selector will not respect selected filters in this
31 * dijit, and vice-versa.
34 dojo.provide('openils.widget.PCrudFilterPane');
35 dojo.require('openils.widget.AutoFieldWidget');
36 dojo.require('dijit.form.FilteringSelect');
37 dojo.require('dijit.form.Button');
38 dojo.require('dijit.form.DropDownButton');
39 dojo.require('dijit.TooltipDialog');
40 dojo.require('dojo.data.ItemFileReadStore');
41 dojo.require('openils.Util');
43 dojo.requireLocalization("openils.widget", "PCrudFilterPane");
45 var pcFilterLocaleStrings = dojo.i18n.getLocalization(
46 "openils.widget", "PCrudFilterPane"
49 /* These are the operators that make up the central dropdown in each
50 * row of the widget. When fields of different datatypes are selected,
51 * some of these operators may be masked via the "minimal" and "strict"
54 var _operator_store = new dojo.data.ItemFileReadStore(
61 "label": pcFilterLocaleStrings.OPERATOR_EQ,
67 "label": pcFilterLocaleStrings.OPERATOR_NE,
73 "label": pcFilterLocaleStrings.OPERATOR_IS_NULL,
79 "label": pcFilterLocaleStrings.OPERATOR_IS_NOT_NULL,
85 "label": pcFilterLocaleStrings.OPERATOR_GT,
90 "label": pcFilterLocaleStrings.OPERATOR_LT,
95 "label": pcFilterLocaleStrings.OPERATOR_GTE,
100 "label": pcFilterLocaleStrings.OPERATOR_LTE,
105 "label": pcFilterLocaleStrings.OPERATOR_IN,
106 "param_count": null, /* arbitrary number, special */
111 "label": pcFilterLocaleStrings.OPERATOR_NOT_IN,
112 "param_count": null, /* arbitrary number, special */
117 "label": pcFilterLocaleStrings.OPERATOR_BETWEEN,
121 "name": "not between",
122 "label": pcFilterLocaleStrings.OPERATOR_NOT_BETWEEN,
127 "label": pcFilterLocaleStrings.OPERATOR_LIKE,
131 "label": pcFilterLocaleStrings.OPERATOR_NOT_LIKE,
139 /* The text datatype supports all the above operators for comparisons. */
140 var _store_query_by_datatype = {"text": {}};
142 /* These three datatypes support only minimal operators. */
143 ["bool", "link", "org_unit"].forEach(
145 _store_query_by_datatype[type] = {"minimal": true};
149 /* These datatypes support strict operators (everything save [not] like). */
150 ["float", "id", "int", "interval", "money", "number", "timestamp"].forEach(
152 _store_query_by_datatype[type] = {"strict": true};
156 /* This helps convert things that pcrud won't accept ("not between", "not
157 * like") into proper JSON query expressions.
158 * It returns false if a clause doesn't have any such negative operator,
159 * or it returns true AND gets rid of the "not " part in the clause
160 * object itself. It's up to the caller to wrap it in {"-not": {}} in
161 * the right place. */
162 function _clause_was_negative(clause) {
163 /* clause objects really only ever have one property */
164 if (clause === null) return false; /* early out for special operator */
166 var ops = openils.Util.objectProperties(clause);
168 var matches = op.match(/^not [lb].+$/); /* "not in" needs no change */
170 clause[matches[1]] = clause[op];
177 /* Given a value, add it to selector options if it's not already there,
179 function _add_or_at_least_select(value, selector) {
182 for (var i = 0; i < selector.options.length; i++) {
183 var option = selector.options[i];
184 if (option.value == value) {
186 option.selected = true;
195 "selected": "selected"
201 /* This is not the dijit per se. Search further in this file for
202 * "dojo.declare" for the beginning of the dijit.
204 * This is, however, the object that represents a collection of filter
205 * rows and knows how to compile a filter from those rows. */
206 function PCrudFilterRowManager() {
209 this._init = function(
210 container, field_store, fm_class, compact, widget_builders,
211 skip_first_add_row, do_apply
213 this.container = container;
214 this.field_store = field_store;
215 this.fm_class = fm_class;
216 this.compact = compact;
217 this.widget_builders = widget_builders || {};
218 this.skip_first_add_row = skip_first_add_row;
219 this.do_apply = do_apply;
227 this._build_table = function() {
228 this.table = dojo.create(
230 "className": "oils-pcrudfilterdialog-table"
234 var tr = dojo.create(
236 "id": "pcrudfilterdialog-empty",
237 "className": "hidden"
244 "innerHTML": pcFilterLocaleStrings[
245 this.compact ? "EMPTY_CASE_COMPACT" : "EMPTY_CASE"
250 if (!this.skip_first_add_row)
254 this._compile_second_pass = function(first_pass) {
256 var result = {"-and": and};
258 for (var field in first_pass) {
259 var list = first_pass[field];
260 if (list.length == 1) {
262 var clause = list.pop();
263 if (_clause_was_negative(clause)) {
265 obj["-not"][field] = clause;
274 if (_clause_was_negative(clause)) {
276 obj["-not"][field] = clause;
283 and.push({"-or": or});
290 this._validate_initializer = function(initializer, onsuccess) {
291 this.field_store.fetchItemByIdentity({
292 "identity": initializer.field,
293 "onItem": dojo.hitch(this, function(item) {
298 "skipping initializer for field " +
299 initializer.field + " not present here"
306 this._proceed_add_row = function(initializer) {
307 var row_id_list = openils.Util.objectProperties(this.rows);
309 /* Kill initial empty row when adding pre-initialized rows. */
310 if (row_id_list.length == 1 && initializer) {
311 var existing_row_id = row_id_list.shift();
312 if (this.rows[existing_row_id].is_unset())
313 this.remove_row(existing_row_id, true /* no_apply */);
316 this.hide_empty_placeholder();
317 var row_id = this.row_index++;
318 this.rows[row_id] = new PCrudFilterRow(this, row_id, initializer);
321 this.add_row = function(initializer) {
323 this._validate_initializer(
325 dojo.hitch(this, function() {
326 this._proceed_add_row(initializer);
330 this._proceed_add_row(initializer);
334 this.remove_row = function(row_id, no_apply) {
335 this.rows[row_id].destroy();
336 delete this.rows[row_id];
338 if (openils.Util.objectProperties(this.rows).length < 1)
339 this.show_empty_placeholder();
341 if (this.compact && !no_apply)
345 this.hide_empty_placeholder = function() {
346 openils.Util.hide("pcrudfilterdialog-empty");
349 this.show_empty_placeholder = function() {
350 openils.Util.show("pcrudfilterdialog-empty");
353 this.compile = function() {
354 /* We'll prepare a first-pass data structure that looks like:
356 * field1: [{"op": "one value"}],
357 * field2: [{"op": "a value"}, {"op": "b value"}],
358 * field3: [{"op": "first value"}, {"op": ["range start", "range end"]}]
361 * which will be passed to _compile_second_pass() to yield an
362 * actual filter suitable for pcrud (with -and and -or in all the
363 * right places) so the above example would come out like:
366 * {"field1": {"op": "one value"}},
367 * {"-or": [ {"field2": {"op": "a value"}}, {"field2": {"op": "b value"}} ] },
369 * {"field3": {"op": "first value"}},
370 * {"field3": {"op": ["range start", "range end"]}}
376 for (var row_id in this.rows) {
377 var row = this.rows[row_id];
378 var value = row.compile();
379 var field = row.selected_field;
381 if (typeof(value) != "undefined" &&
382 typeof(field) != "undefined") {
383 if (!first_pass[field])
384 first_pass[field] = [];
385 first_pass[field].push(value);
389 /* Don't return an empty filter: pcrud can't use that. */
390 if (openils.Util.objectProperties(first_pass).length < 1) {
392 result[fieldmapper[this.fm_class].Identifier] = {"!=": null};
395 return this._compile_second_pass(first_pass);
399 /* This is for generating a data structure so that we can store
400 * a representation of the state of the filter rows. Not for
401 * generating a query to be used in search. You want compile() for
403 this.serialize = function() {
405 for (var rowkey in this.rows) { /* row order doesn't matter */
406 var row_ser = this.rows[rowkey].serialize();
408 serialized.push(row_ser);
410 return dojo.toJson(serialized);
413 this._init.apply(this, arguments);
416 /* As the name implies, objects of this class manage a single row of the
417 * query. Therefore they know about their own field dropdown, their own
418 * selector dropdown, and their own value widget (or widgets in the case
419 * of between searches, which call for two widgets to define a range),
420 * and not much else. */
421 function PCrudFilterRow() {
424 this._init = function(filter_row_manager, row_id, initializer) {
425 this.filter_row_manager = filter_row_manager;
426 this.row_id = row_id;
428 if (this.filter_row_manager.compact)
429 this._build_compact();
434 this.initialize(initializer);
437 this._build = function() {
438 this.tr = dojo.create("tr", {}, this.filter_row_manager.table);
440 this._create_field_selector();
441 this._create_operator_selector();
442 this._create_value_slot();
443 this._create_remover();
446 this._build_compact = function() {
447 this.tr = dojo.create("tr", {}, this.filter_row_manager.table);
449 var td = dojo.create("td", {}, this.tr);
451 this._create_field_selector(td);
452 this._create_operator_selector(td);
454 dojo.create("br", {}, td);
455 this._create_value_slot(td);
459 {"className": "oils-pcrudfilterdialog-remover-holder"},
463 this._create_remover(td);
466 this._create_field_selector = function(use_element) {
467 var td = use_element || dojo.create("td", {}, this.tr);
469 this.field_selector = new dijit.form.FilteringSelect(
471 "labelAttr": "label",
472 "searchAttr": "label",
473 "scrollOnFocus": false,
474 "onChange": function(value) {
475 self.update_selected_field(value);
476 if (this.and_then) { /* ugh. also, self != this. */
477 var once = this.and_then;
478 delete this.and_then;
482 "store": this.filter_row_manager.field_store
483 }, dojo.create("span", {}, td)
487 this._create_operator_selector = function(use_element) {
488 var td = use_element || dojo.create("td", {}, this.tr);
490 this.operator_selector = new dijit.form.FilteringSelect(
492 "labelAttr": "label",
493 "searchAttr": "label",
494 "scrollOnFocus": false,
495 "onChange": function(value) {
496 self.update_selected_operator(value);
498 "store": _operator_store
499 }, dojo.create("span", {}, td)
503 this._adjust_operator_selector = function() {
504 this.operator_selector.attr(
505 "query", _store_query_by_datatype[this.selected_field_type]
507 this.operator_selector.reset();
510 this._create_value_slot = function(use_element) {
511 var how = {"innerHTML": "-"};
514 this.value_slot = dojo.create("span", how, use_element);
516 this.value_slot = dojo.create("td", how, this.tr);
519 this._create_remover = function(use_element) {
520 var td = use_element || dojo.create("td", {}, this.tr);
521 var anchor = dojo.create(
523 "className": "oils-pcrudfilterdialog-remover",
526 "onclick": function() {
527 self.filter_row_manager.remove_row(self.row_id);
533 this._clear_value_slot = function() {
534 if (this.value_widgets) {
535 this.value_widgets.forEach(
538 autowidg.widget.destroy();
541 delete this.value_widgets;
544 dojo.empty(this.value_slot);
547 this._rebuild_value_widgets = function() {
548 this._clear_value_slot();
550 if (!this.get_selected_operator() || !this.selected_field)
553 this.value_widgets = [];
555 /* This is where find custom widget builders to deploy shortly. */
556 var widget_builder_key = this.selected_field_fm_class + ":" +
557 this.selected_field_fm_field;
559 this.filter_row_manager.widget_builders[widget_builder_key] ||
560 openils.widget.AutoFieldWidget;
562 /* How many value widgets do we need for this operator? */
564 this.operator_selector.store.getValue(
565 this.operator_selector.item, "param_count"
568 if (param_count === null) {
569 /* When param_count is null, we invoke the special case of
570 * preparing widgets for building a dynamic set of values.
571 * All other cases are handled by the else branch. */
572 this._build_set_value_widgets(constr);
574 for (var i = 0; i < param_count; i++) {
575 this.value_widgets.push(
576 this._build_one_value_widget(constr)
582 this._build_set_value_widgets = function(constr) {
583 var value_widget = dojo.create(
585 "multiple": "multiple",
589 "verticalAlign": "middle",
595 var entry_widget = this._build_one_value_widget(constr);
596 var adder = dojo.create(
598 "href": "javascript:void(0);",
599 "style": {"verticalAlign": "middle", "margin": "0 0.75em"},
600 "innerHTML": "[+]", /* XXX i18n? */
601 "onclick": dojo.hitch(this, function() {
602 _add_or_at_least_select(
603 this._value_for_compile(entry_widget),
606 entry_widget.widget.attr("value", ""); /* clear */
610 this.value_widgets.push(value_widget);
614 /* Create just one value widget (used by higher-level functions
615 * that worry about how many are needed). */
616 this._build_one_value_widget = function(constr) {
617 var widg = new constr({
618 "fmClass": this.selected_field_fm_class,
619 "fmField": this.selected_field_fm_field,
620 "noDisablePkey": true,
621 "parentNode": dojo.create(
623 "style": {"verticalAlign": "middle"}
626 "dijitArgs": {"scrollOnFocus": false}
633 this._value_for_serialize = function(widg) {
634 if (!widg.widget) /* widg is <select> */
637 function(o) { return o.selected; }
639 function(o) { return o.value; }
642 return widg.widget.attr("value");
645 this._value_for_compile = function(widg) {
646 if (!widg.widget) /* widg is <select> */
649 function(o) { return o.selected; }
651 function(o) { return o.value; }
653 else if (widg.useCorrectly)
654 return widg.widget.attr("value");
655 else if (this.selected_field_is_indirect)
656 return widg.widget.attr("displayedValue");
658 return widg.getFormattedValue();
661 /* for ugly special cases in compilation */
662 this._null_clause = function() {
663 var opname = this.get_selected_operator_name();
664 if (opname == "not null")
666 else if (opname == "null")
672 /* wrap s in %'s unless it already contains at least one %. */
673 this._add_like_wildcards = function(s) {
674 return s.indexOf("%") == -1 ? ("%" + s + "%") : s;
677 this.get_selected_operator = function() {
678 if (this.operator_selector)
679 return this.operator_selector.item;
682 this.get_selected_operator_name = function() {
683 var item = this.get_selected_operator();
685 return this.operator_selector.store.getValue(item, "name");
688 "Could not determine selected operator. " +
689 "Something is about to break."
694 this.update_selected_operator = function(value) {
695 this._rebuild_value_widgets();
698 this.update_selected_field = function(value) {
699 if (this.field_selector.item) {
700 this.selected_field = value;
701 this.selected_field_type = this.field_selector.item.type;
703 /* This is really about supporting flattenergrid, of which
704 * we're in the superclass (in a sloppy sad way). From now
705 * on I won't mix this kind of lazy object with Dojo modules. */
706 this.selected_field_fm_field = this.field_selector.item.name;
707 this.selected_field_is_indirect =
708 this.field_selector.item.indirect || false;
709 this.selected_field_fm_class =
710 this.field_selector.item.fmClass ||
711 this.filter_row_manager.fm_class;
713 this._adjust_operator_selector();
714 this._rebuild_value_widgets();
718 this.serialize = function() {
719 if (!this.selected_field)
723 "field": this.selected_field,
724 "operator": this.get_selected_operator_name()
729 if (this.value_widgets) {
730 values = this.value_widgets.map(
733 return this._value_for_serialize(w);
739 /* The following grew organically to be very silly and confusing.
740 * Could use a rethink (PCrudFilterRow.initialize() would also need
741 * matching changes). */
742 if (values.length == 1) {
743 if (dojo.isArray(values[0]))
744 serialized.values = values[0];
746 serialized.value = values[0];
747 } else if (values.length > 1) {
748 serialized.values = values;
754 this.compile = function() {
755 if (this.value_widgets) {
756 var values = this.value_widgets.map(
757 dojo.hitch(this, this._value_for_compile)
760 if (!values.length) {
761 return this._null_clause(); /* null/not null */
764 var op = this.get_selected_operator_name();
766 var prep_function = function(o) {
767 if (dojo.isArray(o) && !o.length)
768 throw new Error(pcFilterLocaleStrings.EMPTY_LIST);
773 if (String(op).match(/like/))
774 prep_function = this._add_like_wildcards;
776 if (values.length == 1)
777 clause[op] = prep_function(values.pop());
779 clause[op] = dojo.map(values, prep_function);
787 this.destroy = function() {
788 this._clear_value_slot();
789 this.field_selector.destroy();
790 if (this.operator_selector)
791 this.operator_selector.destroy();
793 dojo.destroy(this.tr);
796 this.initialize = function(initializer) {
797 /* and_then is a nasty kludge callback called once at onChange */
798 this.field_selector.and_then = dojo.hitch(
800 this.operator_selector.attr("value", initializer.operator);
802 /* Caller supplies value for one value, values (array) for
804 if (typeof initializer.value !== "undefined" &&
805 !initializer.values) {
806 initializer.values = [initializer.value];
808 initializer.values = initializer.values || [];
810 if (initializer.operator.match(/^(not ?)in$/)) {
811 /* "in" and "not in" need special treatement */
813 initializer.values, dojo.hitch(this, function(v) {
814 _add_or_at_least_select(
815 v, this.value_widgets[0]
820 /* other operators work this way: */
821 for (var i = 0; i < initializer.values.length; i++) {
822 this.value_widgets[i].widget.attr(
823 "value", initializer.values[i]
829 this.field_selector.attr("value", initializer.field);
832 this.is_unset = function() {
833 return !Boolean(this.field_selector.attr("value"));
836 this._init.apply(this, arguments);
840 "openils.widget.PCrudFilterPane", [openils.widget.AutoWidget],
842 "useDiv": null, /* should always be null for subclass dialogs */
843 "initializers": null,
844 "widgetBuilders": null,
845 "suppressFilterFields": null,
846 "savedFiltersInterface": null,
848 "constructor": function(args) {
851 this.widgetIndex = 0;
852 this.widgetCache = {};
853 this.compact = Boolean(this.useDiv);
855 /* Meaningless in a pane, but better here than in
856 * PCrudFilterDialog so that we don't need to load i18n
858 this.title = this.title || pcFilterLocaleStrings.DEFAULT_DIALOG_TITLE;
861 "_buildSavedFilterControlsIfPerms": function(holder) {
862 (new openils.User()).getPermOrgList(
863 "SAVED_FILTER_DIALOG_FILTERS",
864 dojo.hitch(this, function(id_list) {
865 this._buildSavedFilterControls(id_list, holder);
871 "_buildSavedFilterControls": function(id_list, holder) {
872 if (!id_list || !id_list.length) {
873 console.info("Not showing saved filter controls; no perm");
877 var fs_list = (new openils.PermaCrud()).search(
879 "owning_lib": id_list,
880 "interface": this.savedFiltersInterface
883 {"class": "cfdfs", "field": "owning_lib"},
884 {"class": "cfdfs", "field": "name"}
887 "oncomplete": dojo.hitch(this, function(r) {
888 if (r = openils.Util.readResponse(r)) {
889 this._buildSavedFilterLoader(r, holder);
895 this._buildSavedFilterSaver(holder);
898 "_buildSavedFilterLoader": function(fs_list, holder) {
900 var load_content = dojo.create(
902 "innerHTML": pcFilterLocaleStrings.CHOOSE_FILTER_TO_LOAD
906 var selector = dojo.create(
908 "multiple": "multiple",
911 "verticalAlign": "middle", "margin": "0 0.75em"
913 }, load_content, "last"
917 fs_list, function(fs) {
920 "innerHTML": fs.name(),
921 "value": dojo.toJson([fs.id(),
922 dojo.fromJson(fs.filters())])
928 var applicator = dojo.create(
930 "href": "javascript:void(0);",
931 "onclick": function() {
934 function(o){return o.selected;}
936 function(o){return dojo.fromJson(o.value)[1];}
941 self.filter_row_manager.add_row(p);
946 dijit.popup.close(self.filter_set_loader.dropDown);
948 "innerHTML": pcFilterLocaleStrings.APPLY
949 }, load_content, "last"
952 this.filter_set_loader = new dijit.form.DropDownButton({
953 "dropDown": new dijit.TooltipDialog({
954 "content": load_content
956 "label": pcFilterLocaleStrings.LOAD_FILTERS
957 }, dojo.create("span", {}, holder));
960 "_buildSavedFilterSaver": function(holder) {
961 this.filter_set_loader = new dijit.form.Button({
962 "onClick": dojo.hitch(
965 /* XXX I know some find prompt() objectionable
966 * somehow, but I can't seem to type into any
967 * text inputs that I put inside TooltipDialog
968 * instances, so meh. */
970 pcFilterLocaleStrings.NAME_SAVED_FILTER_SET
975 "label": pcFilterLocaleStrings.SAVE_FILTERS
976 }, dojo.create("span", {}, holder));
979 "_buildButtons": function() {
982 var button_holder = dojo.create(
984 "className": "oils-pcrudfilterdialog-buttonholder"
988 new dijit.form.Button(
990 "label": pcFilterLocaleStrings.ADD_ROW,
991 "scrollOnFocus": false, /* almost always better */
992 "onClick": function() {
993 self.filter_row_manager.add_row();
995 }, dojo.create("span", {}, button_holder)
998 this._apply_button = new dijit.form.Button(
1000 "label": pcFilterLocaleStrings.APPLY,
1001 "scrollOnFocus": false,
1002 "onClick": function() { self.doApply(); }
1003 }, dojo.create("span", {}, button_holder)
1007 new dijit.form.Button(
1009 "label": pcFilterLocaleStrings.CANCEL,
1010 "scrollOnFocus": false,
1011 "onClick": function() {
1016 }, dojo.create("span", {}, button_holder)
1020 if (this.savedFiltersInterface)
1021 this._buildSavedFilterControlsIfPerms(button_holder);
1024 "_buildFieldStore": function() {
1026 var realFieldList = this.sortedFieldList.filter(
1027 function(item) { return !(item.virtual || item.nonIdl); }
1030 /* Prevent any explicitly unwanted fields from being available
1031 * in our field dropdowns. */
1032 if (dojo.isArray(this.suppressFilterFields)) {
1033 realFieldList = realFieldList.filter(
1037 i < self.suppressFilterFields.length;
1040 if (item.name == self.suppressFilterFields[i])
1048 this.fieldStore = new dojo.data.ItemFileReadStore({
1050 "identifier": "name",
1052 "items": realFieldList.map(
1055 "label": item.label,
1057 "type": item.datatype
1065 "saveFilters": function(name, oncomplete) {
1066 var filters_value = this.filter_row_manager.serialize();
1067 var filter_set = new cfdfs();
1068 filter_set.name(name);
1069 filter_set.interface(this.savedFiltersInterface);
1070 filter_set.owning_lib(openils.User.user.ws_ou());
1071 filter_set.creator(openils.User.user.id()); /* not reliable */
1072 filter_set.filters(filters_value);
1074 (new openils.PermaCrud()).create(
1076 "oncomplete": dojo.hitch(this, function() {
1077 var selector = dojo.query(
1079 this.filter_set_loader.dropDown.domNode
1084 "value": dojo.toJson([-1,
1085 dojo.fromJson(filters_value)])
1088 if (oncomplete) oncomplete();
1094 "hide": function() {
1096 this.inherited(arguments);
1098 /* When using *FilterPane directly (without a *Dialog
1099 * subclass), do nothing. */
1104 /* All we really do here is create a data store out of the fields
1105 * from the IDL for our given class, place a few buttons at the
1106 * bottom of the dialog, and hand off to PCrudFilterRowManager to
1107 * do the actual work.
1110 "startup": function() {
1112 this.domNode = this.useDiv;
1115 this.inherited(arguments);
1117 /* When using *FilterPane directly (without a *Dialog
1118 * subclass), there is no startup method in any ancestor
1119 * class. XXX Refactor?
1126 this._buildFieldStore();
1128 this.filter_row_manager = new PCrudFilterRowManager(
1129 dojo.create("div", {}, this.domNode),
1130 this.fieldStore, this.fmClass, this.compact,
1131 this.widgetBuilders,
1132 Boolean(this.initializers) /* avoid adding empty row */,
1133 dojo.hitch(this, function() { this.doApply(); })
1136 this._buildButtons();
1138 if (this.initializers) {
1139 this.initializers.forEach(
1140 dojo.hitch(this, function(initializer) {
1141 this.filter_row_manager.add_row(initializer);
1147 /* This should just be named 'apply', but that is kind of a special
1148 * word in Javascript, no? */
1149 "doApply": function() {
1150 this._apply_button.attr("disabled", true);
1152 var _E; /* Try pretty hard not to leave the apply button
1153 disabled forever, even if 'apply' blows up. */
1156 this.onApply(this.filter_row_manager.compile());
1161 this._apply_button.attr("disabled", false);