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[2]] = 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 var old_widget_values = [];
535 if (this.value_widgets) {
536 this.value_widgets.forEach(
538 if (autowidg.widget) {
539 old_widget_values.push({'value' : autowidg.widget.attr("value"),
540 'type' : autowidg.widget.attr("type") });
541 autowidg.widget.destroy();
545 delete this.value_widgets;
547 this.old_widget_values = old_widget_values;
549 dojo.empty(this.value_slot);
552 this._rebuild_value_widgets = function() {
553 this._clear_value_slot();
555 if (!this.get_selected_operator() || !this.selected_field)
558 this.value_widgets = [];
560 /* This is where find custom widget builders to deploy shortly. */
561 var widget_builder_key = this.selected_field_fm_class + ":" +
562 this.selected_field_fm_field;
564 this.filter_row_manager.widget_builders[widget_builder_key] ||
565 openils.widget.AutoFieldWidget;
567 /* How many value widgets do we need for this operator? */
569 this.operator_selector.store.getValue(
570 this.operator_selector.item, "param_count"
573 if (param_count === null) {
574 /* When param_count is null, we invoke the special case of
575 * preparing widgets for building a dynamic set of values.
576 * All other cases are handled by the else branch. */
577 this._build_set_value_widgets(constr);
579 for (var i = 0; i < param_count; i++) {
580 this.value_widgets.push(
581 this._build_one_value_widget(constr)
583 if (typeof this.old_widget_values != "undefined" &&
584 typeof this.old_widget_values[i] != "undefined" &&
585 this.value_widgets[i].widget.attr("type") == this.old_widget_values[i].type) {
586 this.value_widgets[i].widget.attr("value", this.old_widget_values[i].value);
590 delete this.old_widget_values;
593 this._build_set_value_widgets = function(constr) {
594 var value_widget = dojo.create(
596 "multiple": "multiple",
600 "verticalAlign": "middle",
606 var entry_widget = this._build_one_value_widget(constr);
607 var adder = dojo.create(
609 "href": "javascript:void(0);",
610 "style": {"verticalAlign": "middle", "margin": "0 0.75em"},
611 "innerHTML": "[+]", /* XXX i18n? */
612 "onclick": dojo.hitch(this, function() {
613 _add_or_at_least_select(
614 this._value_for_compile(entry_widget),
617 entry_widget.widget.attr("value", ""); /* clear */
621 this.value_widgets.push(value_widget);
625 /* Create just one value widget (used by higher-level functions
626 * that worry about how many are needed). */
627 this._build_one_value_widget = function(constr) {
628 var widg = new constr({
629 "fmClass": this.selected_field_fm_class,
630 "fmField": this.selected_field_fm_field,
631 "noDisablePkey": true,
632 "parentNode": dojo.create(
634 "style": {"verticalAlign": "middle"}
637 "dijitArgs": {"scrollOnFocus": false}
644 this._value_for_serialize = function(widg) {
645 if (!widg.widget) /* widg is <select> */
648 function(o) { return o.selected; }
650 function(o) { return o.value; }
653 return widg.widget.attr("value");
656 this._value_for_compile = function(widg) {
657 if (!widg.widget) /* widg is <select> */
660 function(o) { return o.selected; }
662 function(o) { return o.value; }
664 else if (widg.useCorrectly)
665 return widg.widget.attr("value");
666 else if (this.selected_field_is_indirect)
667 return widg.widget.attr("displayedValue");
669 return widg.getFormattedValue();
672 /* for ugly special cases in compilation */
673 this._null_clause = function() {
674 var opname = this.get_selected_operator_name();
675 if (opname == "not null")
677 else if (opname == "null")
683 /* wrap s in %'s unless it already contains at least one %. */
684 this._add_like_wildcards = function(s) {
685 return s.indexOf("%") == -1 ? ("%" + s + "%") : s;
688 this.get_selected_operator = function() {
689 if (this.operator_selector)
690 return this.operator_selector.item;
693 this.get_selected_operator_name = function() {
694 var item = this.get_selected_operator();
696 return this.operator_selector.store.getValue(item, "name");
699 "Could not determine selected operator. " +
700 "Something is about to break."
705 this.update_selected_operator = function(value) {
706 this._rebuild_value_widgets();
709 this.update_selected_field = function(value) {
710 if (this.field_selector.item) {
711 this.selected_field = value;
712 this.selected_field_type = this.field_selector.item.type;
714 /* This is really about supporting flattenergrid, of which
715 * we're in the superclass (in a sloppy sad way). From now
716 * on I won't mix this kind of lazy object with Dojo modules. */
717 this.selected_field_fm_field = this.field_selector.item.name;
718 this.selected_field_is_indirect =
719 this.field_selector.item.indirect || false;
720 this.selected_field_fm_class =
721 this.field_selector.item.fmClass ||
722 this.filter_row_manager.fm_class;
724 this._adjust_operator_selector();
725 this._rebuild_value_widgets();
729 this.serialize = function() {
730 if (!this.selected_field)
734 "field": this.selected_field,
735 "operator": this.get_selected_operator_name()
740 if (this.value_widgets) {
741 values = this.value_widgets.map(
744 return this._value_for_serialize(w);
750 /* The following grew organically to be very silly and confusing.
751 * Could use a rethink (PCrudFilterRow.initialize() would also need
752 * matching changes). */
753 if (values.length == 1) {
754 if (dojo.isArray(values[0]))
755 serialized.values = values[0];
757 serialized.value = values[0];
758 } else if (values.length > 1) {
759 serialized.values = values;
765 this.compile = function() {
766 if (this.value_widgets) {
767 var values = this.value_widgets.map(
768 dojo.hitch(this, this._value_for_compile)
771 if (!values.length) {
772 return this._null_clause(); /* null/not null */
775 var op = this.get_selected_operator_name();
777 var prep_function = function(o) {
778 if (dojo.isArray(o) && !o.length)
779 throw new Error(pcFilterLocaleStrings.EMPTY_LIST);
784 if (String(op).match(/like/))
785 prep_function = this._add_like_wildcards;
787 if (values.length == 1)
788 clause[op] = prep_function(values.pop());
790 clause[op] = dojo.map(values, prep_function);
798 this.destroy = function() {
799 this._clear_value_slot();
800 this.field_selector.destroy();
801 if (this.operator_selector)
802 this.operator_selector.destroy();
804 dojo.destroy(this.tr);
807 this.initialize = function(initializer) {
808 /* and_then is a nasty kludge callback called once at onChange */
809 this.field_selector.and_then = dojo.hitch(
811 this.operator_selector.attr("value", initializer.operator);
813 /* Caller supplies value for one value, values (array) for
815 if (typeof initializer.value !== "undefined" &&
816 !initializer.values) {
817 initializer.values = [initializer.value];
819 initializer.values = initializer.values || [];
821 if (initializer.operator.match(/^(not ?)in$/)) {
822 /* "in" and "not in" need special treatement */
824 initializer.values, dojo.hitch(this, function(v) {
825 _add_or_at_least_select(
826 v, this.value_widgets[0]
831 /* other operators work this way: */
832 for (var i = 0; i < initializer.values.length; i++) {
833 this.value_widgets[i].widget.attr(
834 "value", initializer.values[i]
840 this.field_selector.attr("value", initializer.field);
843 this.is_unset = function() {
844 return !Boolean(this.field_selector.attr("value"));
847 this._init.apply(this, arguments);
851 "openils.widget.PCrudFilterPane", [openils.widget.AutoWidget],
853 "useDiv": null, /* should always be null for subclass dialogs */
854 "initializers": null,
855 "widgetBuilders": null,
856 "suppressFilterFields": null,
857 "savedFiltersInterface": null,
859 "constructor": function(args) {
862 this.widgetIndex = 0;
863 this.widgetCache = {};
864 this.compact = Boolean(this.useDiv);
866 /* Meaningless in a pane, but better here than in
867 * PCrudFilterDialog so that we don't need to load i18n
869 this.title = this.title || pcFilterLocaleStrings.DEFAULT_DIALOG_TITLE;
872 "_buildSavedFilterControlsIfPerms": function(holder) {
873 (new openils.User()).getPermOrgList(
874 "SAVED_FILTER_DIALOG_FILTERS",
875 dojo.hitch(this, function(id_list) {
876 this._buildSavedFilterControls(id_list, holder);
882 "_buildSavedFilterControls": function(id_list, holder) {
883 if (!id_list || !id_list.length) {
884 console.info("Not showing saved filter controls; no perm");
888 var fs_list = (new openils.PermaCrud()).search(
890 "owning_lib": id_list,
891 "interface": this.savedFiltersInterface
894 {"class": "cfdfs", "field": "owning_lib"},
895 {"class": "cfdfs", "field": "name"}
898 "oncomplete": dojo.hitch(this, function(r) {
899 if (r = openils.Util.readResponse(r)) {
900 this._buildSavedFilterLoader(r, holder);
906 this._buildSavedFilterSaver(holder);
909 "_buildSavedFilterLoader": function(fs_list, holder) {
911 var load_content = dojo.create(
913 "innerHTML": pcFilterLocaleStrings.CHOOSE_FILTER_TO_LOAD
917 var selector = dojo.create(
919 "multiple": "multiple",
922 "verticalAlign": "middle", "margin": "0 0.75em"
924 }, load_content, "last"
928 fs_list, function(fs) {
931 "innerHTML": fs.name(),
932 "value": dojo.toJson([fs.id(),
933 dojo.fromJson(fs.filters())])
939 var applicator = dojo.create(
941 "href": "javascript:void(0);",
942 "onclick": function() {
945 function(o){return o.selected;}
947 function(o){return dojo.fromJson(o.value)[1];}
952 self.filter_row_manager.add_row(p);
957 dijit.popup.close(self.filter_set_loader.dropDown);
959 "innerHTML": pcFilterLocaleStrings.APPLY
960 }, load_content, "last"
963 this.filter_set_loader = new dijit.form.DropDownButton({
964 "dropDown": new dijit.TooltipDialog({
965 "content": load_content
967 "label": pcFilterLocaleStrings.LOAD_FILTERS
968 }, dojo.create("span", {}, holder));
971 "_buildSavedFilterSaver": function(holder) {
972 this.filter_set_loader = new dijit.form.Button({
973 "onClick": dojo.hitch(
976 /* XXX I know some find prompt() objectionable
977 * somehow, but I can't seem to type into any
978 * text inputs that I put inside TooltipDialog
979 * instances, so meh. */
981 pcFilterLocaleStrings.NAME_SAVED_FILTER_SET
986 "label": pcFilterLocaleStrings.SAVE_FILTERS
987 }, dojo.create("span", {}, holder));
990 "_buildButtons": function() {
993 var button_holder = dojo.create(
995 "className": "oils-pcrudfilterdialog-buttonholder"
999 new dijit.form.Button(
1001 "label": pcFilterLocaleStrings.ADD_ROW,
1002 "scrollOnFocus": false, /* almost always better */
1003 "onClick": function() {
1004 self.filter_row_manager.add_row();
1006 }, dojo.create("span", {}, button_holder)
1009 this._apply_button = new dijit.form.Button(
1011 "label": pcFilterLocaleStrings.APPLY,
1012 "scrollOnFocus": false,
1013 "onClick": function() { self.doApply(); }
1014 }, dojo.create("span", {}, button_holder)
1018 new dijit.form.Button(
1020 "label": pcFilterLocaleStrings.CANCEL,
1021 "scrollOnFocus": false,
1022 "onClick": function() {
1027 }, dojo.create("span", {}, button_holder)
1031 if (this.savedFiltersInterface)
1032 this._buildSavedFilterControlsIfPerms(button_holder);
1035 "_buildFieldStore": function() {
1037 var realFieldList = this.sortedFieldList.filter(
1038 function(item) { return !(item.virtual || item.nonIdl); }
1041 /* Prevent any explicitly unwanted fields from being available
1042 * in our field dropdowns. */
1043 if (dojo.isArray(this.suppressFilterFields)) {
1044 realFieldList = realFieldList.filter(
1048 i < self.suppressFilterFields.length;
1051 if (item.name == self.suppressFilterFields[i])
1059 this.fieldStore = new dojo.data.ItemFileReadStore({
1061 "identifier": "name",
1063 "items": realFieldList.map(
1066 "label": item.label,
1068 "type": item.datatype
1076 "saveFilters": function(name, oncomplete) {
1077 var filters_value = this.filter_row_manager.serialize();
1078 var filter_set = new cfdfs();
1079 filter_set.name(name);
1080 filter_set.interface(this.savedFiltersInterface);
1081 filter_set.owning_lib(openils.User.user.ws_ou());
1082 filter_set.creator(openils.User.user.id()); /* not reliable */
1083 filter_set.filters(filters_value);
1085 (new openils.PermaCrud()).create(
1087 "oncomplete": dojo.hitch(this, function() {
1088 var selector = dojo.query(
1090 this.filter_set_loader.dropDown.domNode
1095 "value": dojo.toJson([-1,
1096 dojo.fromJson(filters_value)])
1099 if (oncomplete) oncomplete();
1105 "hide": function() {
1107 this.inherited(arguments);
1109 /* When using *FilterPane directly (without a *Dialog
1110 * subclass), do nothing. */
1115 /* All we really do here is create a data store out of the fields
1116 * from the IDL for our given class, place a few buttons at the
1117 * bottom of the dialog, and hand off to PCrudFilterRowManager to
1118 * do the actual work.
1121 "startup": function() {
1123 this.domNode = this.useDiv;
1126 this.inherited(arguments);
1128 /* When using *FilterPane directly (without a *Dialog
1129 * subclass), there is no startup method in any ancestor
1130 * class. XXX Refactor?
1137 this._buildFieldStore();
1139 this.filter_row_manager = new PCrudFilterRowManager(
1140 dojo.create("div", {}, this.domNode),
1141 this.fieldStore, this.fmClass, this.compact,
1142 this.widgetBuilders,
1143 Boolean(this.initializers) /* avoid adding empty row */,
1144 dojo.hitch(this, function() { this.doApply(); })
1147 this._buildButtons();
1149 if (this.initializers) {
1150 this.initializers.forEach(
1151 dojo.hitch(this, function(initializer) {
1152 this.filter_row_manager.add_row(initializer);
1158 /* This should just be named 'apply', but that is kind of a special
1159 * word in Javascript, no? */
1160 "doApply": function() {
1161 this._apply_button.attr("disabled", true);
1163 var _E; /* Try pretty hard not to leave the apply button
1164 disabled forever, even if 'apply' blows up. */
1167 this.onApply(this.filter_row_manager.compile());
1172 this._apply_button.attr("disabled", false);