Report Templates!
authorMike Rylander <mrylander@gmail.com>
Mon, 8 Aug 2016 18:15:09 +0000 (14:15 -0400)
committerMike Rylander <mrylander@gmail.com>
Thu, 18 Aug 2016 19:34:22 +0000 (15:34 -0400)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
15 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/WWW/IDL2js.pm
Open-ILS/src/templates/staff/css/reporter.css [new file with mode: 0644]
Open-ILS/src/templates/staff/reporter/index.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/reporter/share/report_strings.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/reporter/t_edit_template.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/reporter/t_legacy.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/share/t_confirm_dialog.tt2
Open-ILS/src/templates/staff/share/t_select_dialog.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/bower.json
Open-ILS/web/js/ui/default/staff/reporter/services/template.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/reporter/template/app.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/services/idl.js
Open-ILS/web/js/ui/default/staff/services/ui.js
Open-ILS/xsl/fm_IDL2js.xsl

index 2420c0b..b21f7b3 100644 (file)
@@ -9063,7 +9063,7 @@ SELECT  usr,
                        <link field="reports" reltype="has_many" key="folder" map="" class="rr"/>
                </links>
        </class>
-       <class id="rt" controller="open-ils.reporter-store" oils_obj:fieldmapper="reporter::template" oils_persist:tablename="reporter.template" reporter:label="Template">
+       <class id="rt" controller="open-ils.reporter-store open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="reporter::template" oils_persist:tablename="reporter.template" reporter:label="Template">
                <fields oils_persist:primary="id" oils_persist:sequence="reporter.template_id_seq">
                        <field name="id" reporter:datatype="id" />
                        <field name="owner" reporter:datatype="link"/>
@@ -9079,6 +9079,14 @@ SELECT  usr,
                        <link field="folder" reltype="has_a" key="id" map="" class="rtf"/>
                        <link field="reports" reltype="has_many" key="template" map="" class="rr"/>
                </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create   permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+                <retrieve permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+                <update   permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+                <delete   permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+            </actions>
+        </permacrud>
        </class>
        <class id="rr" controller="open-ils.reporter-store" oils_obj:fieldmapper="reporter::report" oils_persist:tablename="reporter.report" reporter:label="Report">
                <fields oils_persist:primary="id" oils_persist:sequence="reporter.report_id_seq">
@@ -10562,20 +10570,20 @@ SELECT  usr,
             -- -- If we expect to use pcrud to query against specific bibs, we probably want to do this.  However, if we're using this to populate a report, we
             -- -- may not.
             -- SELECT
-            --     bre.id AS "bib_id",
-            --     COALESCE( z.copy_count, 0 ) AS "copy_count",
-            --     COALESCE( z.hold_count, 0 ) AS "hold_count",
-            --     COALESCE( z.copy_hold_ratio, 0 ) AS "hold_copy_ratio"
+            --     bre.id AS bib_id,
+            --     COALESCE( z.copy_count, 0 ) AS copy_count,
+            --     COALESCE( z.hold_count, 0 ) AS hold_count,
+            --     COALESCE( z.copy_hold_ratio, 0 ) AS hold_copy_ratio
             -- FROM (
                 SELECT
-                    y.bre AS "id",
-                    COALESCE( x.copy_count, 0 ) AS "copy_count",
-                    y.hold_count AS "hold_count",
-                    (y.hold_count::REAL / (CASE WHEN x.copy_count = 0 OR x.copy_count IS NULL THEN 0.1 ELSE x.copy_count::REAL END)) AS "hold_copy_ratio"
+                    y.bre AS id,
+                    COALESCE( x.copy_count, 0 ) AS copy_count,
+                    y.hold_count AS hold_count,
+                    (y.hold_count::REAL / (CASE WHEN x.copy_count = 0 OR x.copy_count IS NULL THEN 0.1 ELSE x.copy_count::REAL END)) AS hold_copy_ratio
                 FROM (
                         SELECT
-                            (SELECT bib_record FROM reporter.hold_request_record r WHERE r.id = h.id LIMIT 1) AS "bre",
-                            COUNT(*) AS "hold_count"
+                            (SELECT bib_record FROM reporter.hold_request_record r WHERE r.id = h.id LIMIT 1) AS bre,
+                            COUNT(*) AS hold_count
                         FROM action.hold_request h
                         WHERE
                             cancel_time IS NULL
@@ -10587,8 +10595,8 @@ SELECT  usr,
                             (SELECT id
                                 FROM biblio.record_entry 
                                 WHERE id = (SELECT record FROM asset.call_number WHERE id = call_number and deleted is false)
-                            ) AS "bre"
-                            COUNT(*) AS "copy_count"
+                            ) AS bre
+                            COUNT(*) AS copy_count
                         FROM asset.copy
                             JOIN asset.copy_location loc ON (copy.location = loc.id AND loc.holdable)
                         WHERE copy.holdable 
@@ -11527,7 +11535,7 @@ SELECT  usr,
        </class>
        <class id="hasholdscount" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::has_holds_count" reporter:label="Copy Has Holds Count" oils_persist:readonly="true">
         <oils_persist:source_definition>
-       SELECT ahcm.target_copy AS "id",count(*) AS "count"
+       SELECT ahcm.target_copy AS id,count(*) AS count
         FROM
         action.hold_request ahr,
         action.hold_copy_map ahcm
index 497b0db..a7486e4 100644 (file)
@@ -125,6 +125,11 @@ sub load_IDL {
         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
     }
 
+    $xml =~ s/<!--.*?-->//sg;     # filter out XML comments ...
+    $xml =~ s/(?:^|\s+)--.*$//mg; # and SQL comments ...
+    $xml =~ s/^\s+/ /mg;          # and extra leading spaces ...
+    $xml =~ s/\R*//g;             # and newlines
+
     my $output;
     try {
         my $idl_doc = XML::LibXML->load_xml(string => $xml);
diff --git a/Open-ILS/src/templates/staff/css/reporter.css b/Open-ILS/src/templates/staff/css/reporter.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Open-ILS/src/templates/staff/reporter/index.tt2 b/Open-ILS/src/templates/staff/reporter/index.tt2
new file mode 100644 (file)
index 0000000..ee6e2fc
--- /dev/null
@@ -0,0 +1,21 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Reporter"); 
+  ctx.page_app = "egReporter";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/dojo/opensrf/md5.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/reporter/services/template.js"></script>
+[% INCLUDE 'staff/reporter/share/report_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/reporter/template/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/reporter.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/reporter/share/report_strings.tt2 b/Open-ILS/src/templates/staff/reporter/share/report_strings.tt2
new file mode 100644 (file)
index 0000000..a6d50cb
--- /dev/null
@@ -0,0 +1,180 @@
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+
+s.RPT_BUILDER_CONFIRM_SAVE = '[% l( "Save Template?") %]';
+
+s.SELECT_TFORM = '[% l( "Select Transform") %]';
+s.SELECT_OP = '[% l( "Select Operator") %]';
+
+s.LINK_NULLABLE_DEFAULT = '[% l( "Default") %]';
+s.LINK_NULLABLE_RIGHT = '[% l( "Parent") %]';
+s.LINK_NULLABLE_LEFT = '[% l( "Child") %]';
+s.LINK_NULLABLE_NONE = '[% l( "None") %]';
+
+s.FILTERS_LABEL_EQUALS = '[% l("Equals") %]';
+s.FILTERS_LABEL_LIKE = '[% l( "Contains Matching substring") %]';
+s.FILTERS_LABEL_ILIKE = '[% l( "Contains Matching substring (ignore case)") %]';
+s.FILTERS_LABEL_GREATER_THAN = '[% l( "Greater than") %]';
+s.FILTERS_LABEL_GT_TIME = '[% l( "After (Date/Time)") %]';
+s.FILTERS_LABEL_GT_EQUAL = '[% l( "Greater than or equal to") %]';
+s.FILTERS_LABEL_GTE_TIME = '[% l( "On or After (Date/Time)") %]';
+s.FILTERS_LABEL_LESS_THAN = '[% l( "Less than") %]';
+s.FILTERS_LABEL_LT_TIME = '[% l( "Before (Date/Time)") %]';
+s.FILTERS_LABEL_LT_EQUAL = '[% l( "Less than or equal to") %]';
+s.FILTERS_LABEL_LSE_TIME = '[% l( "On or Before (Date/Time)") %]';
+s.FILTERS_LABEL_IN = '[% l( "In list") %]';
+s.FILTERS_LABEL_NOT_IN = '[% l( "Not in list") %]';
+s.FILTERS_LABEL_BETWEEN = '[% l( "Between") %]';
+s.FILTERS_LABEL_NOT_BETWEEN = '[% l( "Not between") %]';
+s.FILTERS_LABEL_NULL = '[% l( "Is NULL") %]';
+s.FILTERS_LABEL_NOT_NULL = '[% l( "Is not NULL") %]';
+s.FILTERS_LABEL_NULL_BLANK = '[% l( "Is NULL or Blank") %]';
+s.FILTERS_LABEL_NOT_NULL_BLANK = '[% l( "Is not NULL or Blank") %]';
+s.FILTERS_LABEL_EQ_ANY = '[% l( "Equals Any") %]';
+s.FILTERS_LABEL_NE_ANY = '[% l( "Does Not Equal Any") %]';
+
+s.FOLDERS_TEMPLATES = '[% l( "Templates") %]';
+s.FOLDERS_TEMPLATE = '[% l( "Template") %]';
+s.FOLDERS_REPORTS = '[% l( "Reports") %]';
+s.FOLDERS_REPORT = '[% l( "Report") %]';
+s.FOLDERS_OUTPUT = '[% l( "Output") %]';
+
+s.FOLDER_WINDOW_SELECT_ITEM = '[% l( "Please select an item from the list") %]';
+s.FOLDER_WINDOW_CHANGE_FOLDERS = '[% l( "Change Folders") %]';
+s.FOLDER_WINDOW_COLNAME_SELECT = '[% l( "Select") %]';
+s.FOLDER_WINDOW_COLNAME_ALL = '[% l( "All") %]';
+s.FOLDER_WINDOW_COLNAME_NONE = '[% l( "None") %]';
+
+s.REPORT_EDITOR_REPORT_FOLDERS = '[% l( "Report Folders") %]';
+s.REPORT_EDITOR_OUTPUT_FOLDERS = '[% l( "Output Folders") %]';
+s.REPORT_EDITOR_PROVIDE_FOLDER_ALERT = '[% l( "Please provide a report folder") %]';
+s.REPORT_EDITOR_ENTER_NAME_ALERT = '[% l( "Please enter a report name") %]';
+s.REPORT_EDITOR_ENTER_NEW_NAME_ALERT = '[% l( "Please change the report name") %]';
+s.REPORT_EDITOR_INVALID_DATE_ALERT = '[% l( "invalid start date -  YYYY-MM-DD") %]';
+s.REPORT_EDITOR_PROVIDE_OUTPUT_ALERT = '[% l( "Please provide an output folder") %]';
+
+s.TFORMS_LABEL_RAW_DATA = '[% l( "Raw Data") %]';
+s.TFORMS_LABEL_FIRST = '[% l( "First Value") %]';
+s.TFORMS_LABEL_LAST = '[% l( "Last Value") %]';
+s.TFORMS_LABEL_COUNT = '[% l( "Count") %]';
+s.TFORMS_LABEL_COUNT_DISTINCT = '[% l( "Count Distinct") %]';
+s.TFORMS_LABEL_MIN = '[% l( "Min") %]';
+s.TFORMS_LABEL_MAX = '[% l( "Max") %]';
+s.TFORMS_LABEL_LOWER = '[% l( "Lower case") %]';
+s.TFORMS_LABEL_UPPER = '[% l( "Upper case") %]';
+s.TFORMS_LABEL_FIRST5 = '[% l( "First 5 characters (for US ZIP code)") %]';
+s.TFORMS_LABEL_FIRST_WORD = '[% l( "First contiguous non-space string") %]';
+s.TFORMS_LABEL_DOW = '[% l( "Day of Week") %]';
+s.TFORMS_LABEL_DOM = '[% l( "Day of Month") %]';
+s.TFORMS_LABEL_DOY = '[% l( "Day of Year") %]';
+s.TFORMS_LABEL_WOY = '[% l( "Week of Year") %]';
+s.TFORMS_LABEL_MOY = '[% l( "Month of Year") %]';
+s.TFORMS_LABEL_QOY = '[% l( "Quarter of Year") %]';
+s.TFORMS_LABEL_HOD = '[% l( "Hour of day") %]';
+s.TFORMS_LABEL_DATE = '[% l( "Date") %]';
+s.TFORMS_LABEL_MONTH_TRUNC = '[% l( "Year + Month") %]';
+s.TFORMS_LABEL_YEAR_TRUNC = '[% l( "Year") %]';
+s.TFORMS_LABEL_HOUR_TRUNC = '[% l( "Hour") %]';
+s.TFORMS_LABEL_DAY_NAME = '[% l( "Day Name") %]';
+s.TFORMS_LABEL_MONTH_NAME = '[% l( "Month Name") %]';
+s.TFORMS_LABEL_AGE = '[% l( "Age") %]';
+s.TFORMS_LABEL_MONTHS_AGO = '[% l( "Months ago") %]';
+s.TFORMS_LABEL_QUARTERS_AGO = '[% l( "Quarters ago") %]';
+s.TFORMS_LABEL_SUM = '[% l( "Sum") %]';
+s.TFORMS_LABEL_AVERAGE = '[% l( "Average") %]';
+s.TFORMS_LABEL_ROUND = '[% l( "Round") %]';
+s.TFORMS_LABEL_INT = '[% l( "Drop trailing decimals") %]';
+
+s.WIDGET_DAYS = '[% l( "Day(s)") %]';
+s.WIDGET_MONTHS = '[% l( "Month(s)") %]';
+s.WIDGET_YEARS = '[% l( "Year(s)") %]';
+s.WIDGET_QUARTERS = '[% l( "Quarter(s)") %]';
+s.WIDGET_REAL_DATE = '[% l( "Real Date") %]';
+s.WIDGET_RELATIVE_DATE = '[% l( "Relative Date") %]';
+
+s.OPERATORS_EQUALS = '[% l( "Equals") %]';
+s.OPERATORS_LIKE = '[% l( "Contains Matching substring") %]';
+s.OPERATORS_ILIKE = '[% l( "Contains Matching substring (ignore case)") %]';
+s.OPERATORS_GREATER_THAN = '[% l( "Greater than") %]';
+s.OPERATORS_GT_TIME = '[% l( "After (Date/Time)") %]';
+s.OPERATORS_GT_EQUAL = '[% l( "Greater than or equal to") %]';
+s.OPERATORS_GTE_TIME = '[% l( "On or After (Date/Time)") %]';
+s.OPERATORS_LESS_THAN = '[% l( "Less than") %]';
+s.OPERATORS_LT_TIME = '[% l( "Before (Date/Time)") %]';
+s.OPERATORS_LT_EQUAL = '[% l( "Less than or equal to") %]';
+s.OPERATORS_LTE_TIME = '[% l( "On or Before (Date/Time)") %]';
+s.OPERATORS_IN_LIST = '[% l( "In list") %]';
+s.OPERATORS_NOT_IN_LIST = '[% l( "Not in list") %]';
+s.OPERATORS_BETWEEN = '[% l( "Between") %]';
+s.OPERATORS_NOT_BETWEEN = '[% l( "Not between") %]';
+s.OPERATORS_IS_NULL = '[% l( "Is NULL") %]';
+s.OPERATORS_IS_NOT_NULL = '[% l( "Is not NULL") %]';
+s.OPERATORS_NULL_BLANK = '[% l( "Is NULL or Blank") %]';
+s.OPERATORS_NOT_NULL_BLANK = '[% l( "Is not NULL or Blank") %]';
+s.OPERATORS_EQ_ANY = '[% l( "Equals Any") %]';
+s.OPERATORS_NE_ANY = '[% l( "Does Not Equal Any") %]';
+
+s.SOURCE_BROWSE_AGGREGATE = '[% l( "Aggregate") %]';
+s.SOURCE_BROWSE_NON_AGGREGATE = '[% l( "Non-Aggregate") %]';
+
+s.SOURCE_SETUP_CONFIRM_EXIT = '[% l( "You have started building a template! Selecting a new starting source will destroy the current template and start over.  Is this OK?") %]';
+s.SOURCE_SETUP_CORE_SOURCES = '[% l( "Core Sources") %]';
+s.SOURCE_SETUP_ALL_AVAIL_SOURCES = '[% l( "All Available Sources") %]';
+
+s.TEMPLATE_CONF_BARE = '[% l( "Bare") %]';
+s.TEMPLATE_CONF_RAW_DATA = '[% l( "Raw Data") %]';
+s.TEMPLATE_CONF_EQUALS = '[% l( "Equals") %]';
+s.TEMPLATE_CONF_CONFIRM_RESET = '[% l( "You have already added this field. Click OK if you would like to reset this field.") %]';
+s.TEMPLATE_CONF_PROMPT_CHANGE = '[% l( "Change the column header?") %]';
+s.TEMPLATE_FIELD_DOC_PROMPT_CHANGE = '[% l( "Change the field hint to:") %]';
+s.TEMPLATE_CONF_BOOLEAN_VALUE = '[% l( "Boolean Value") %]';
+s.TEMPLATE_CONF_SELECT_CANCEL = '[% l( "Select the value, or cancel:") %]';
+s.TEMPLATE_CONF_TRUE = '[% l( "True") %]';
+s.TEMPLATE_CONF_FALSE = '[% l( "False") %]';
+s.TEMPLATE_CONF_CONFIRM_STATE = '[% l( "Click OK for TRUE and Cancel for FALSE.") %]';
+s.TEMPLATE_CONF_NO_MATCH = '[% l( "Field does not match one of list (comma separated):") %]';
+s.TEMPLATE_CONF_NOT_BETWEEN = '[% l( "Field value is not between (comma separated):") %]';
+s.TEMPLATE_CONF_BETWEEN = '[% l( "Field value is between (comma separated):") %]';
+s.TEMPLATE_CONF_NOT_IN = '[% l( "Field does not match one of list (comma separated):") %]';
+s.TEMPLATE_CONF_IN = '[% l( "Field matches one of list (comma separated):") %]';
+s.TEMPLATE_CONF_DEFAULT = '[% l( "Value:") %]';
+s.TEMPLATE_CONF_CONFIRM_SAVE = '[% l( "Save Template?") %]';
+s.TEMPLATE_CONF_SUCCESS_SAVE = '[% l( "Template was successfully saved.") %]';
+s.TEMPLATE_CONF_FAIL_SAVE = '[% l( "Template save failed.") %]';
+
+s.TRANSFORMS_BARE = '[% l( "Raw Data") %]';
+s.TRANSFORMS_FIRST = '[% l( "First Value") %]';
+s.TRANSFORMS_LAST = '[% l( "Last Value") %]';
+s.TRANSFORMS_COUNT = '[% l( "Count") %]';
+s.TRANSFORMS_COUNT_DISTINCT = '[% l( "Count Distinct") %]';
+s.TRANSFORMS_MIN = '[% l( "Min") %]';
+s.TRANSFORMS_MAX = '[% l( "Max") %]';
+s.TRANSFORMS_SUBSTRING = '[% l( "Substring") %]';
+s.TRANSFORMS_LOWER = '[% l( "Lower case") %]';
+s.TRANSFORMS_UPPER = '[% l( "Upper case") %]';
+s.TRANSFORMS_FIRST5 = '[% l( "First 5 characters (for US ZIP code)") %]';
+s.TRANSFORMS_FIRST_WORD = '[% l( "First contiguous non-space string") %]';
+s.TRANSFORMS_DOW = '[% l( "Day of Week") %]';
+s.TRANSFORMS_DOM = '[% l( "Day of Month") %]';
+s.TRANSFORMS_DOY = '[% l( "Day of Year") %]';
+s.TRANSFORMS_WOY = '[% l( "Week of Year") %]';
+s.TRANSFORMS_MOY = '[% l( "Month of Year") %]';
+s.TRANSFORMS_QOY = '[% l( "Quarter of Year") %]';
+s.TRANSFORMS_HOD = '[% l( "Hour of day") %]';
+s.TRANSFORMS_DATE = '[% l( "Date") %]';
+s.TRANSFORMS_MONTH_TRUNC = '[% l( "Year + Month") %]';
+s.TRANSFORMS_YEAR_TRUNC = '[% l( "Year") %]';
+s.TRANSFORMS_HOUR_TRUNC = '[% l( "Hour") %]';
+s.TRANSFORMS_DAY_NAME = '[% l( "Day Name") %]';
+s.TRANSFORMS_MONTH_NAME = '[% l( "Month Name") %]';
+s.TRANSFORMS_AGE = '[% l( "Age") %]';
+s.TRANSFORMS_MONTHS_AGO = '[% l( "Months ago") %]';
+s.TRANSFORMS_QUARTERS_AGO = '[% l( "Quarters ago") %]';
+s.TRANSFORMS_SUM = '[% l( "Sum") %]';
+s.TRANSFORMS_AVERAGE = '[% l( "Average") %]';
+s.TRANSFORMS_ROUND = '[% l( "Round") %]';
+s.TRANSFORMS_INT = '[% l( "Drop trailing decimals") %]';
+
+}]);
+</script>
diff --git a/Open-ILS/src/templates/staff/reporter/t_edit_template.tt2 b/Open-ILS/src/templates/staff/reporter/t_edit_template.tt2
new file mode 100644 (file)
index 0000000..cd8b74a
--- /dev/null
@@ -0,0 +1,233 @@
+<!-- report template builder -->
+
+<div class="row">
+  <div class="col-md-2">
+    [% l('Template Name') %]
+  </div>
+  <div class="col-md-4">
+    <div><input type="text" class="form-control" ng-model="templateName"/></div>
+  </div>
+  <div class="col-md-2">
+    [% l('Documentation URL') %]
+  </div>
+  <div class="col-md-4">
+    <div><input type="text" class="form-control" ng-model="templateDocURL"/></div>
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-md-2">
+    [% l('Template Description') %]
+  </div>
+  <div class="col-md-10">
+    <div><textarea class="form-control" ng-model="templateDescription"/></div>
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-md-2">
+    <button ng-click="saveTemplate()" class="btn btn-default">[% l('Save Template') %]</button>
+  </div>
+</div>
+
+<hr/>
+
+<div class="row panel" style="max-height: 400px; min-height: 400px;">
+  <div class="col-md-5" style="max-height: 400px; min-height: 400px; overflow-y: scroll;">
+
+    <div class="row">
+      <div class="col-xs-3"><strong>[% l('Core Source') %]</strong></div>
+      <div class="col-xs-6">
+        <div class="source-selector nullable">
+          <select class="form-control" ng-model="coreSource" ng-change="changeCoreSource()"
+            ng-options="s.name as s.label group by s.core_label for s in allSources">
+            <option value="">[% l('-- Select Source --') %]</option>
+          </select>
+        </div>
+      </div>
+      <div class="col-xs-3">
+        <label for="enable_nullability_cb">
+          [% l('Nullability') %]
+        </lable>
+        <input type="checkbox" ng-model="enable_nullability"/>
+      </div>
+    </div>
+
+    <br/>
+
+    <treecontrol
+        class="tree-light"
+        tree-model="class_tree"
+        on-node-toggle="treeExpand(node, expanded)"
+        on-selection="selectSource(node, selected, $path)"
+    >
+      <select
+          ng-show="enable_nullability"
+          ng-model="node.jtype"
+          ng-init="join_types = [{type:'inner',label:'[% l('Default') %]'},{type:'left',label:'[% l('Child nullable') %]'},{type:'right',label:'[% l('Parent nullable') %]'}]"
+          ng-options="j.type as j.label for j in join_types"></select>
+      {{ node.label || n.id }}
+    </treecontrol>
+
+  </div>
+  <div class="col-md-7">
+    <div class="row">
+      <div class="col-md-7" style="max-height: 400px; min-height: 400px; overflow-y: scroll;">
+        <div class="row">
+            <div class="col-xs-3"><strong>[% l('Source Path') %]</strong></div>
+            <div class="col-xs-9"><input type="text" class="form-control" ng-model="currentPathLabel"/></div>
+        </div>
+
+        <br/>
+
+        <treecontrol
+          class="tree-light"
+          tree-model="selected_source_fields"
+          selected-nodes="selected_source_field_list"
+          on-selection="selectFields()"
+          options="field_tree_opts"
+          filter-expression="filterFields"
+        >
+          <span ng-switch="" on="node.datatype">
+            <span ng-switch-when="bool" class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span>
+            <span ng-switch-when="float" class="glyphicon glyphicon-sound-5-1" aria-hidden="true"></span>
+            <span ng-switch-when="id" class="glyphicon glyphicon-barcode" aria-hidden="true"></span>
+            <span ng-switch-when="int" class="glyphicon glyphicon-scale" aria-hidden="true"></span>
+            <span ng-switch-when="interval" class="glyphicon glyphicon-resize-horizontal" aria-hidden="true"></span>
+            <span ng-switch-when="link" class="glyphicon glyphicon-link" aria-hidden="true"></span>
+            <span ng-switch-when="money" class="glyphicon glyphicon-usd" aria-hidden="true"></span>
+            <span ng-switch-when="number" class="glyphicon glyphicon-scale" aria-hidden="true"></span>
+            <span ng-switch-when="org_unit" class="glyphicon glyphicon-tree-conifer" aria-hidden="true"></span>
+            <span ng-switch-when="text" class="glyphicon glyphicon-font" aria-hidden="true"></span>
+            <span ng-switch-when="timestamp" class="glyphicon glyphicon-calendar" aria-hidden="true"></span>
+          </span>
+          {{ node.label || node.name }}
+        </treecontrol>
+      </div>
+      <div class="col-md-5" style="max-height: 400px; min-height: 400px; overflow-y: scroll;">
+        <strong>[% l('Transform') %]</strong>
+        <br/>
+        <br/>
+        <br/>
+        <treecontrol
+          class="tree-light"
+          tree-model="available_field_transforms"
+          selected-node="selected_transform"
+          options="field_transforms_tree_opts"
+        >
+          {{ node.label || node.transform }}
+        </treecontrol>
+      </div>
+    </div>
+      </div>
+
+    <hr/>
+
+    <div class="row">
+      <div class="col-md-12">
+
+      <uib-tabset> 
+        <uib-tab index="0" heading="[% l('Display Fields') %]">
+          <eg-grid
+            id-field="index"
+            features="-sort,-multisort,-multiselect"
+            items-provider="grid_display_fields_provider"
+            grid-controls="display_grid_controls"
+          >
+            <eg-grid-action 
+              handler="changeDisplayLabel"
+              label="[% l('Change Column Label') %]">
+            </eg-grid-action>
+
+            <eg-grid-action 
+              handler="changeDisplayFieldDoc"
+              label="[% l('Change Column Documentation') %]">
+            </eg-grid-action>
+          
+            <eg-grid-action 
+              handler="changeTransform"
+              label="[% l('Change Transform') %]">
+            </eg-grid-action>
+          
+            <eg-grid-action 
+              handler="moveDisplayFieldUp"
+              label="[% l('Move Field Up') %]">
+            </eg-grid-action>
+          
+            <eg-grid-action 
+              handler="moveDisplayFieldDown"
+              label="[% l('Move Field Down') %]">
+            </eg-grid-action>
+          
+            <eg-grid-action 
+              handler="removeDisplayField"
+              label="[% l('Remove Field') %]">
+            </eg-grid-action>
+          
+            <eg-grid-menu-item handler="addDisplayFields"
+              label="[% l('Add Fields') %]"></eg-grid-menu-item>
+          
+            <eg-grid-field path='path_label' label="[% l('Source Path') %]"></eg-grid-field>
+            <eg-grid-field path='name' label="[% l('Column') %]" hidden></eg-grid-field>
+            <eg-grid-field path='doc_text' label="[% l('Documentation') %]" hidden></eg-grid-field>
+            <eg-grid-field path='label' label="[% l('Column Label') %]"></eg-grid-field>
+            <eg-grid-field path='datatype' label="[% l('Data Type') %]"></eg-grid-field>
+            <eg-grid-field path='transform.label' label="[% l('Field Transform') %]"></eg-grid-field>
+          </eg-grid>
+        </uib-tab>
+
+        <uib-tab index="1" heading="[% l('Filters') %]">
+          <eg-grid
+            id-field="index"
+            features="-sort,-multisort,-multiselect"
+            items-provider="grid_filter_fields_provider"
+            grid-controls="filter_grid_controls"
+          >
+            <eg-grid-action 
+              handler="changeFilterFieldDoc"
+              label="[% l('Change Column Documentation') %]">
+            </eg-grid-action>
+
+            <eg-grid-action 
+              handler="changeTransform"
+              label="[% l('Change Transform') %]">
+            </eg-grid-action>
+
+            <eg-grid-action 
+              handler="changeOperator"
+              label="[% l('Change Operator') %]">
+            </eg-grid-action>
+          
+            <eg-grid-action 
+              handler="changeFilterValue"
+              label="[% l('Change Filter Value') %]">
+            </eg-grid-action>
+          
+            <eg-grid-action 
+              handler="removeFilterValue"
+              label="[% l('Remove Filter Value') %]">
+            </eg-grid-action>
+      
+            <eg-grid-action 
+              handler="removeFilterField"
+              label="[% l('Remove Field') %]">
+            </eg-grid-action>
+          
+            <eg-grid-menu-item handler="addFilterFields"
+              label="[% l('Add Fields') %]"></eg-grid-menu-item>
+          
+            <eg-grid-field path='path_label' label="[% l('Source Path') %]"></eg-grid-field>
+            <eg-grid-field path='label' label="[% l('Name') %]"></eg-grid-field>
+            <eg-grid-field path='name' label="[% l('Column') %]"></eg-grid-field>
+            <eg-grid-field path='datatype' label="[% l('Data Type') %]"></eg-grid-field>
+            <eg-grid-field path='operator.label' label="[% l('Operator') %]"></eg-grid-field>
+            <eg-grid-field path='transform.label' label="[% l('Field Transform') %]"></eg-grid-field>
+            <eg-grid-field path='value' label="[% l('Filter Value') %]"></eg-grid-field>
+          </eg-grid>
+        </uib-tab>
+      </uib-tabset>
+
+    </div>
+  </div>
+</div>
+
diff --git a/Open-ILS/src/templates/staff/reporter/t_legacy.tt2 b/Open-ILS/src/templates/staff/reporter/t_legacy.tt2
new file mode 100644 (file)
index 0000000..cbaa379
--- /dev/null
@@ -0,0 +1 @@
+<eg-embed-frame save-space="150" url="rurl"></eg-embed-frame>
index a9d6ac1..c701886 100644 (file)
@@ -11,7 +11,7 @@
   <div class="modal-footer">
     [% dialog_footer %]
     <input type="submit" class="btn btn-primary" 
-      ng-click="ok()" value="{{ ok_button_label || '[% l('OK/Continue') %]'}}"/>
+      ng-click="ok()" value="{{ ok_button_label || '[% l("OK/Continue") %]'}}"/>
     <button class="btn btn-warning" 
       ng-click="cancel()">{{ cancel_button_label || "[% l('Cancel') %]"}}</button>
   </div>
diff --git a/Open-ILS/src/templates/staff/share/t_select_dialog.tt2 b/Open-ILS/src/templates/staff/share/t_select_dialog.tt2
new file mode 100644 (file)
index 0000000..37e0f39
--- /dev/null
@@ -0,0 +1,23 @@
+<!--
+  Generic confirmation dialog
+-->
+<div>
+  <div class="modal-header">
+    <button type="button" class="close" 
+      ng-click="cancel()" aria-hidden="true">&times;</button>
+    <h4 class="modal-title alert alert-info">{{message}}</h4> 
+  </div>
+  <div class="modal-body">
+    <div class="row">
+      <div class="col-md-12">
+        <eg-basic-combo-box allow-all="true" list="args.list" selected="args.value" focus-me="focus"></eg-basic-combo-box>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    [% dialog_footer %]
+    <input type="submit" class="btn btn-primary" 
+      ng-click="ok()" value="[% l('OK/Continue') %]"/>
+    <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+  </div>
+</div>
index 6fceb94..dcadeee 100644 (file)
@@ -27,7 +27,7 @@
     "angular-file-saver": "~1.1.0",
     "angular-location-update": "~0.0.2",
     "ngtoast": "~2.0.0",
-    "angular-tree-control": "~0.2.23",
+    "angular-tree-control": "~0.2.28",
     "angular-animate": "~1.5.3",
     "angular-hotkeys": "cfp-angular-hotkeys#^1.7.0"
   },
diff --git a/Open-ILS/web/js/ui/default/staff/reporter/services/template.js b/Open-ILS/web/js/ui/default/staff/reporter/services/template.js
new file mode 100644 (file)
index 0000000..1c3b4e0
--- /dev/null
@@ -0,0 +1,440 @@
+/**
+ * Report templates
+ */
+
+angular.module('egReportMod', ['egCoreMod', 'ui.bootstrap'])
+.factory('egReportTemplateSvc',
+
+       ['$uibModal','$q','egCore','egConfirmDialog','egAlertDialog',
+function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
+
+    //dojo.requireLocalization("openils.reports", "reports");
+    //var egCore.strings = dojo.i18n.getLocalization("openils.reports", "reports");
+
+    var OILS_RPT_DTYPE_ARRAY = 'array';
+    var OILS_RPT_DTYPE_STRING = 'text';
+    var OILS_RPT_DTYPE_MONEY = 'money';
+    var OILS_RPT_DTYPE_BOOL = 'bool';
+    var OILS_RPT_DTYPE_INT = 'int';
+    var OILS_RPT_DTYPE_ID = 'id';
+    var OILS_RPT_DTYPE_OU = 'org_unit';
+    var OILS_RPT_DTYPE_FLOAT = 'float';
+    var OILS_RPT_DTYPE_TIMESTAMP = 'timestamp';
+    var OILS_RPT_DTYPE_INTERVAL = 'interval';
+    var OILS_RPT_DTYPE_LINK = 'link';
+    var OILS_RPT_DTYPE_NONE = '';
+    var OILS_RPT_DTYPE_NULL = null;
+    var OILS_RPT_DTYPE_UNDEF;
+    
+    var OILS_RPT_DTYPE_ALL = [
+       OILS_RPT_DTYPE_STRING,
+       OILS_RPT_DTYPE_MONEY,
+       OILS_RPT_DTYPE_INT,
+       OILS_RPT_DTYPE_ID,
+       OILS_RPT_DTYPE_FLOAT,
+       OILS_RPT_DTYPE_TIMESTAMP,
+       OILS_RPT_DTYPE_BOOL,
+       OILS_RPT_DTYPE_OU,
+       OILS_RPT_DTYPE_NONE,
+       OILS_RPT_DTYPE_NULL,
+       OILS_RPT_DTYPE_UNDEF,
+       OILS_RPT_DTYPE_INTERVAL,
+       OILS_RPT_DTYPE_LINK
+    ];
+    var OILS_RPT_DTYPE_NOT_ID = [OILS_RPT_DTYPE_STRING,OILS_RPT_DTYPE_MONEY,OILS_RPT_DTYPE_INT,OILS_RPT_DTYPE_FLOAT,OILS_RPT_DTYPE_TIMESTAMP];
+    var OILS_RPT_DTYPE_NOT_BOOL = [OILS_RPT_DTYPE_STRING,OILS_RPT_DTYPE_MONEY,OILS_RPT_DTYPE_INT,OILS_RPT_DTYPE_FLOAT,OILS_RPT_DTYPE_TIMESTAMP,OILS_RPT_DTYPE_ID,OILS_RPT_DTYPE_OU,OILS_RPT_DTYPE_LINK];
+
+    var service = {
+        display_fields : [],
+        filter_fields  : [],
+
+        Filters : {
+               '=' : {
+                       label : egCore.strings.OPERATORS_EQUALS
+               },
+        
+               'like' : {
+                       label : egCore.strings.OPERATORS_LIKE
+               }, 
+        
+               ilike : {
+                       label : egCore.strings.OPERATORS_ILIKE
+               },
+        
+               '>' : {
+                       label : egCore.strings.OPERATORS_GREATER_THAN,
+                       labels : { timestamp : egCore.strings.OPERATORS_GT_TIME }
+               },
+        
+               '>=' : {
+                       label : egCore.strings.OPERATORS_GT_EQUAL,
+                       labels : { timestamp : egCore.strings.OPERATORS_GTE_TIME }
+               }, 
+        
+        
+               '<' : {
+                       label : egCore.strings.OPERATORS_LESS_THAN,
+                       labels : { timestamp : egCore.strings.OPERATORS_LT_TIME }
+               }, 
+        
+               '<=' : {
+                       label : egCore.strings.OPERATORS_LT_EQUAL, 
+                       labels : { timestamp : egCore.strings.OPERATORS_LTE_TIME }
+               },
+        
+               'in' : {
+                       label : egCore.strings.OPERATORS_IN_LIST
+               },
+        
+               'not in' : {
+                       label : egCore.strings.OPERATORS_NOT_IN_LIST
+               },
+        
+               'between' : {
+                       label : egCore.strings.OPERATORS_BETWEEN
+               },
+        
+               'not between' : {
+                       label : egCore.strings.OPERATORS_NOT_BETWEEN
+               },
+        
+               'is' : {
+                       label : egCore.strings.OPERATORS_IS_NULL
+               },
+        
+               'is not' : {
+                       label : egCore.strings.OPERATORS_IS_NOT_NULL
+               },
+        
+               'is blank' : {
+                       label : egCore.strings.OPERATORS_NULL_BLANK
+               },
+        
+               'is not blank' : {
+                       label : egCore.strings.OPERATORS_NOT_NULL_BLANK
+               },
+        
+               '= any' : {
+                       labels : { 'array' : egCore.strings.OPERATORS_EQ_ANY }
+               },
+        
+               '<> any' : {
+                       labels : { 'array' : egCore.strings.OPERATORS_NE_ANY }
+               }
+        },
+
+        Transforms : {
+           Bare : {
+                datatype : OILS_RPT_DTYPE_ALL,
+                label : egCore.strings.TRANSFORMS_BARE
+            },
+        
+            first : {
+                datatype : OILS_RPT_DTYPE_NOT_ID,
+                label : egCore.strings.TRANSFORMS_FIRST
+            },
+        
+            last : {
+                datatype : OILS_RPT_DTYPE_NOT_ID,
+                label : egCore.strings.TRANSFORMS_LAST
+            },
+        
+            count : {
+                datatype : OILS_RPT_DTYPE_NOT_BOOL,
+                aggregate : true,
+                label :  egCore.strings.TRANSFORMS_COUNT
+            },
+        
+            count_distinct : {
+                datatype : OILS_RPT_DTYPE_NOT_BOOL,
+                aggregate : true,
+                label : egCore.strings.TRANSFORMS_COUNT_DISTINCT
+            },
+        
+            min : {
+                datatype : OILS_RPT_DTYPE_NOT_ID,
+                aggregate : true,
+                label : egCore.strings.TRANSFORMS_MIN
+            },
+        
+            max : {
+                datatype : OILS_RPT_DTYPE_NOT_ID,
+                aggregate : true,
+                label : egCore.strings.TRANSFORMS_MAX
+            },
+        
+            /* string transforms ------------------------- */
+        
+            substring : {
+                datatype : [ OILS_RPT_DTYPE_STRING ],
+                params : 2,
+                label : egCore.strings.TRANSFORMS_SUBSTRING
+            },
+        
+            lower : {
+                datatype : [ OILS_RPT_DTYPE_STRING ],
+                label : egCore.strings.TRANSFORMS_LOWER
+            },
+        
+            upper : {
+                datatype : [ OILS_RPT_DTYPE_STRING ],
+                label : egCore.strings.TRANSFORMS_UPPER
+            },
+        
+            first5 : {
+                datatype : [ OILS_RPT_DTYPE_STRING ],
+                label : egCore.strings.TRANSFORMS_FIRST5
+            },
+        
+                first_word : {
+                        datatype : [OILS_RPT_DTYPE_STRING, 'text'],
+                        label : egCore.strings.TRANSFORMS_FIRST_WORD
+                },
+        
+            /* timestamp transforms ----------------------- */
+            dow : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_DOW,
+                cal_format : '%w',
+                regex : /^[0-6]$/
+            },
+            dom : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_DOM,
+                cal_format : '%e',
+                regex : /^[0-9]{1,2}$/
+            },
+        
+            doy : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_DOY,
+                cal_format : '%j',
+                regex : /^[0-9]{1,3}$/
+            },
+        
+            woy : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_WOY,
+                cal_format : '%U',
+                regex : /^[0-9]{1,2}$/
+            },
+        
+            moy : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_MOY,
+                cal_format : '%m',
+                regex : /^\d{1,2}$/
+            },
+        
+            qoy : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_QOY,
+                regex : /^[1234]$/
+            }, 
+        
+            hod : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_HOD,
+                cal_format : '%H',
+                regex : /^\d{1,2}$/
+            }, 
+        
+            date : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_DATE,
+                regex : /^\d{4}-\d{2}-\d{2}$/,
+                hint  : 'YYYY-MM-DD',
+                cal_format : '%Y-%m-%d',
+                input_size : 10
+            },
+        
+            month_trunc : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_MONTH_TRUNC,
+                regex : /^\d{4}-\d{2}$/,
+                hint  : 'YYYY-MM',
+                cal_format : '%Y-%m',
+                input_size : 7
+            },
+        
+            year_trunc : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_YEAR_TRUNC,
+                regex : /^\d{4}$/,
+                hint  : 'YYYY',
+                cal_format : '%Y',
+                input_size : 4
+            },
+        
+            hour_trunc : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_HOUR_TRUNC,
+                regex : /^\d{2}$/,
+                hint  : 'HH',
+                cal_format : '%Y-%m-$d %H',
+                input_size : 2
+            },
+        
+            day_name : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                cal_format : '%A',
+                label : egCore.strings.TRANSFORMS_DAY_NAME
+            }, 
+        
+            month_name : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                cal_format : '%B',
+                label : egCore.strings.TRANSFORMS_MONTH_NAME
+            },
+            age : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_AGE
+            },
+        
+            months_ago : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_MONTHS_AGO
+            },
+        
+            quarters_ago : {
+                datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+                label : egCore.strings.TRANSFORMS_QUARTERS_AGO
+            },
+        
+            /* int  / float transforms ----------------------------------- */
+            sum : {
+                datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT, OILS_RPT_DTYPE_MONEY ],
+                label : egCore.strings.TRANSFORMS_SUM,
+                aggregate : true
+            }, 
+        
+            average : {
+                datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT, OILS_RPT_DTYPE_MONEY ],
+                label : egCore.strings.TRANSFORMS_AVERAGE,
+                aggregate : true
+            },
+        
+            round : {
+                datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT ],
+                label : egCore.strings.TRANSFORMS_ROUND,
+            },
+        
+            'int' : {
+                datatype : [ OILS_RPT_DTYPE_FLOAT ],
+                label : egCore.strings.TRANSFORMS_INT
+            }
+        }
+    };
+
+    service.addFields = function (type, fields, transform, source_label, source_path, op) {
+        fields.forEach(function(f) {
+            var l = f.label ? f.label : f.name;
+            var new_field = angular.extend(
+                {},
+                f,
+                { index : service[type].length,
+                  label : l,
+                  path  : source_path,
+                  path_label : source_label,
+                  operator   : op,
+                  transform  : transform,
+                  doc_text   : ''
+                }
+            );
+
+            var add = true;
+            service[type].forEach(function(e) {
+                if (e.name == new_field.name && e.path == new_field.path) add = false;
+            });
+            if (add) service[type].push(new_field);
+        });
+    }
+
+    service.moveFieldUp = function (type, field) {
+        var new_list = [];
+        while (service[type].length) {
+            var f = service[type].pop();
+            if (field.index == f.index && f.index > 0)
+                new_list.unshift(f,service[type].pop());
+            else
+                new_list.unshift(f);
+        }
+        new_list.forEach(function(f) {
+            service[type].push(angular.extend(f, { index : service[type].length}));
+        });
+    }
+
+    service.moveFieldDown = function (type, field) {
+        var new_list = [];
+        var start_len = service[type].length - 1;
+        while (service[type].length) {
+            var f = service[type].shift();
+            if (field.index == f.index && f.index < start_len)
+                new_list.push(service[type].shift(),f);
+            else
+                new_list.push(f);
+        }
+        new_list.forEach(function(f) {
+            service[type].push(angular.extend(f, { index : service[type].length}));
+        });
+    }
+
+    service.removeField = function (type, field) {
+        var new_list = [];
+        while (service[type].length) {
+            var f = service[type].pop();
+            if (field.index != f.index ) new_list.push(f);
+        }
+        new_list.forEach(function(f) {
+            service[type].push(angular.extend(f, { index : service[type].length}));
+        });
+    }
+
+    service.getTransformByLabel = function (l) {
+        for( var key in service.Transforms ) {
+            var t = service.Transforms[key];
+            if (l == t.label) return key;
+            if (angular.isArray(t.labels) && t.labels.indexOf(l) > -1) return key;
+        }
+        return null;
+    }
+
+    service.getFilterByLabel = function (l) {
+        for( var key in service.Filters ) {
+            var t = service.Filters[key];
+            if (l == t.label) return key;
+            if (angular.isArray(t.labels) && t.labels.indexOf(l) > -1) return key;
+        }
+        return null;
+    }
+
+    service.getTransforms = function (args) {
+        var dtype = args.datatype;
+        var agg = args.aggregate;
+        var nonagg = args.non_aggregate;
+        var label = args.label;
+    
+        var tforms = [];
+    
+        for( var key in service.Transforms ) {
+            var obj = service.Transforms[key];
+            if( agg && !nonagg && !obj.aggregate ) continue;
+            if( !agg && nonagg && obj.aggregate ) continue;
+            if( !dtype && obj.datatype.length > 0 ) continue;
+            if( dtype && obj.datatype.length > 0 && transformIsForDatatype(key,dtype).length == 0 ) continue;
+            tforms.push(key);
+        }
+    
+        return tforms;
+    }
+
+
+    service.transformIsForDatatype = function (tform, dtype) {
+        var obj = service.Transforms[tform];
+        return obj.datateype.filter(function(d) { return (d == dtype) })[0];
+    }
+
+    return service;
+}])
+;
+
diff --git a/Open-ILS/web/js/ui/default/staff/reporter/template/app.js b/Open-ILS/web/js/ui/default/staff/reporter/template/app.js
new file mode 100644 (file)
index 0000000..272a0eb
--- /dev/null
@@ -0,0 +1,600 @@
+/*
+ * Report template builder
+ */
+
+angular.module('egReporter',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egReportMod', 'treeControl', 'ngToast'])
+
+.config(['ngToastProvider', function(ngToastProvider) {
+  ngToastProvider.configure({
+    verticalPosition: 'bottom',
+    animation: 'fade'
+  });
+}])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    $routeProvider.when('/reporter/template/clone/:folder/:id', {
+        templateUrl: './reporter/t_edit_template',
+        controller: 'ReporterTemplateEdit',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/reporter/legacy/template/clone/:folder/:id', {
+        templateUrl: './reporter/t_legacy',
+        controller: 'ReporterTemplateLegacy',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/reporter/template/new/:folder', {
+        templateUrl: './reporter/t_edit_template',
+        controller: 'ReporterTemplateEdit',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/reporter/legacy/main', {
+        templateUrl: './reporter/t_legacy',
+        controller: 'ReporterTemplateLegacy',
+        resolve : resolver
+    });
+
+    // default page
+    $routeProvider.otherwise({redirectTo : '/reporter/legacy/main'});
+})
+
+/**
+ * controller for legacy template stuff
+ */
+.controller('ReporterTemplateLegacy',
+       ['$scope','$routeParams','$location','egCore',
+function($scope , $routeParams , $location , egCore) {
+
+    var template_id = $routeParams.id;
+    var folder_id = $routeParams.folder;
+
+    $scope.rurl = '/reports/oils_rpt.xhtml?ses=' + egCore.auth.token();
+
+    if (folder_id) {
+        $scope.rurl = '/reports/oils_rpt_builder.xhtml?ses=' +
+                        egCore.auth.token() + '&folder=' + folder_id;
+
+        if (template_id) $scope.rurl += '&ct=' + template_id;
+    }
+
+}])
+
+/**
+ * Uber-controller for template editing
+ */
+.controller('ReporterTemplateEdit',
+       ['$scope','$q','$routeParams','$location','$timeout','$window','egCore','$uibModal','egPromptDialog',
+        'egGridDataProvider','egReportTemplateSvc','$uibModal','egConfirmDialog','egSelectDialog','ngToast',
+function($scope , $q , $routeParams , $location , $timeout , $window,  egCore , $uibModal , egPromptDialog ,
+         egGridDataProvider , egReportTemplateSvc , $uibModal , egConfirmDialog , egSelectDialog , ngToast) {
+
+    function values(o) { return Object.keys(o).map(function(k){return o[k]}) };
+
+    var template_id = $routeParams.id;
+    var folder_id = $routeParams.folder;
+
+    $scope.grid_display_fields_provider = egGridDataProvider.instance({
+        get : function (offset, count) {
+            return this.arrayNotifier(egReportTemplateSvc.display_fields, offset, count);
+        }
+    });
+    $scope.grid_filter_fields_provider = egGridDataProvider.instance({
+        get : function (offset, count) {
+            return this.arrayNotifier(egReportTemplateSvc.filter_fields, offset, count);
+        }
+    });
+
+    var dgrid = $scope.display_grid_controls = {};
+    var fgrid = $scope.filter_grid_controls = {};
+
+    var default_filter_obj = {
+        op : '=',
+        label     : egReportTemplateSvc.Filters['='].label
+    };
+
+    var default_transform_obj = {
+        transform : 'Bare',
+        label     : egReportTemplateSvc.Transforms.Bare.label,
+        aggregate : false
+    };
+
+    function mergePaths (items) {
+        var tree = {};
+
+        items.forEach(function (item) {
+            var t = tree;
+            var join_path = '';
+
+            item.path.forEach(function (p, i, a) {
+                var alias; // unpredictable hashes are fine for intermediate tables
+
+                if (i) { // not at the top of the tree
+                    if (i == 1) join_path = join_path.split('-')[0];
+
+                    var uplink = p.uplink.name;
+                    join_path += '-' + uplink;
+                    alias = hex_md5(join_path);
+
+                    var uplink_alias = uplink + '-' + alias;
+
+                    if (!t.join) t.join = {};
+                    if (!t.join[uplink_alias]) t.join[uplink_alias] = {};
+
+                    t = t.join[uplink_alias];
+
+                    var djtype = 'inner';
+                    if (p.uplink.reltype != 'has_a') djtype = 'left';
+
+                    t.type = p.jtype || djtype;
+                    t.key = p.uplink.key;
+                } else {
+                    join_path = p.classname + '-' + p.classname;
+                    alias = hex_md5(join_path);
+                }
+
+                if (!t.alias) t.alias = alias;
+                t.path = join_path;
+
+                t.table = p.struct.source ? p.struct.source : p.table;
+                t.idlclass = p.classname;
+
+                if (a.length == i + 1) { // end of the path array, need a predictable hash
+                    t.label = item.path_label;
+                    t.alias = hex_md5(item.path_label);
+                }
+
+            });
+        });
+
+        return tree;
+    }
+
+    $scope.constructTemplate = function () {
+        var param_counter = 0;
+        return {
+            version     : 5,
+            doc_url     : $scope.templateDocURL,
+            core_class  : egCore.idl.classTree.top.classname,
+            select      : dgrid.allItems().map(function (i) {
+                            return {
+                                alias     : i.label,
+                                path      : i.path[i.path.length - 1].classname + '-' + i.name,
+                                field_doc : i.doc_text,
+                                relation  : hex_md5(i.path_label),
+                                column    : {
+                                    colname         : i.name,
+                                    transform       : i.transform ? i.transform.transform : '',
+                                    transform_label : i.transform ? i.transform.label : '',
+                                    aggregate       : !!i.transform.aggregate
+                                }
+                            }
+                          }),
+            from        : mergePaths( dgrid.allItems().concat(fgrid.allItems()) ),
+            where       : fgrid.allItems().filter(function(i) {
+                            return !i.transform.aggregate;
+                          }).map(function (i) {
+                            var cond = {};
+                            if (
+                                i.operator.op == 'is' ||
+                                i.operator.op == 'is not' ||
+                                i.operator.op == 'is blank' ||
+                                i.operator.op == 'is not blank'
+                            ) {
+                                cond[i.operator.op] = null;
+                            } else {
+                                if (i.value === undefined) {
+                                    cond[i.operator.op] = '::P' + param_counter++;
+                                }else {
+                                    cond[i.operator.op] = i.value;
+                                }
+                            }
+                            return {
+                                alias     : i.label,
+                                path      : i.path[i.path.length - 1].classname + '-' + i.name,
+                                field_doc : i.doc_text,
+                                relation  : hex_md5(i.path_label),
+                                column    : {
+                                    colname         : i.name,
+                                    transform       : i.transform.transform,
+                                    transform_label : i.transform.label,
+                                    aggregate       : 0
+                                },
+                                condition : cond // constructed above
+                            }
+                          }),
+            having      : fgrid.allItems().filter(function(i) {
+                            return !!i.transform.aggregate;
+                          }).map(function (i) {
+                            var cond = {};
+                            cond[i.operator.op] = '::P' + param_counter++;
+                            return {
+                                alias     : i.label,
+                                path      : i.path[i.path.length - 1].classname + '-' + i.name,
+                                field_doc : i.doc_text,
+                                relation  : hex_md5(i.path_label),
+                                column    : {
+                                    colname         : i.name,
+                                    transform       : i.transform.transform,
+                                    transform_label : i.transform.label,
+                                    aggregate       : 1
+                                },
+                                condition : cond // constructed above
+                            }
+                          }),
+            display_cols: angular.copy( dgrid.allItems() ).map(strip_item),
+            filter_cols : angular.copy( fgrid.allItems() ).map(strip_item)
+        };
+
+        function strip_item (i) {
+            delete i.children;
+            i.path.forEach(function(p){
+                delete p.children;
+                delete p.fields;
+                delete p.links;
+                delete p.struct.permacrud;
+                delete p.struct.field_map;
+                delete p.struct.fields;
+            });
+            return i;
+        }
+
+    }
+
+    function loadTemplate () {
+        if (!template_id) return;
+        egCore.pcrud.retrieve( 'rt', template_id)
+        .then( function(template) {
+            template.data = angular.fromJson(template.data());
+            if (template.data.version < 5) { // redirect to old editor...
+                $window.location.href = egCore.env.basePath + 'reporter/legacy/template/clone/'+folder_id + '/' + template_id;
+            // } else if (template.data.version < 5) { // redirect to old editor...
+            } else {
+                $scope.templateName = template.name() + ' (clone)';
+                $scope.templateDescription = template.description();
+                $scope.templateDocURL = template.data.doc_url;
+
+                $scope.changeCoreSource( template.data.core_class );
+
+                egReportTemplateSvc.display_fields = template.data.display_cols;
+                egReportTemplateSvc.filter_fields = template.data.filter_cols;
+
+                $timeout(function(){
+                    dgrid.refresh();
+                    fgrid.refresh();
+                });
+            }
+        });
+
+    }
+
+    $scope.saveTemplate = function () {
+        var tmpl = new egCore.idl.rt();
+        tmpl.name( $scope.templateName );
+        tmpl.description( $scope.templateDescription );
+        tmpl.owner(egCore.auth.user().id());
+        tmpl.folder(folder_id);
+        tmpl.data(angular.toJson($scope.constructTemplate()));
+
+        egConfirmDialog.open(tmpl.name(), egCore.strings.TEMPLATE_CONF_CONFIRM_SAVE,
+            {ok : function() {
+                return egCore.pcrud.create( tmpl )
+                .then(
+                    function() {
+                        ngToast.create(egCore.strings.TEMPLATE_CONF_SUCCESS_SAVE);
+                        return $timeout(
+                            function(){
+                                $window.location.href = egCore.env.basePath + 'reporter/legacy/main';
+                            },
+                            1000
+                        );
+                    },
+                    function() {
+                        ngToast.warning(egCore.strings.TEMPLATE_CONF_FAIL_SAVE);
+                    }
+                );
+            }}
+        );
+    }
+
+    $scope.addDisplayFields = function () {
+        var t = $scope.selected_transform;
+        if (!t) t = default_transform_obj;
+
+        egReportTemplateSvc.addFields(
+            'display_fields',
+            $scope.selected_source_field_list, 
+            t,
+            $scope.currentPathLabel,
+            $scope.currentPath
+        );
+        dgrid.refresh();
+    }
+
+    $scope.addFilterFields = function () {
+        var t = $scope.selected_transform;
+        if (!t) t = default_transform_obj;
+        f = default_filter_obj;
+
+        egReportTemplateSvc.addFields(
+            'filter_fields',
+            $scope.selected_source_field_list, 
+            t,
+            $scope.currentPathLabel,
+            $scope.currentPath,
+            f
+        );
+        fgrid.refresh();
+    }
+
+    $scope.moveDisplayFieldUp = function (items) {
+        items.reverse().forEach(function(item) {
+            egReportTemplateSvc.moveFieldUp('display_fields', item);
+        });
+        dgrid.refresh();
+    }
+
+    $scope.moveDisplayFieldDown = function (items) {
+        items.forEach(function(item) {
+            egReportTemplateSvc.moveFieldDown('display_fields', item);
+        });
+        dgrid.refresh();
+    }
+
+    $scope.removeDisplayField = function (items) {
+        items.forEach(function(item) {egReportTemplateSvc.removeField('display_fields', item)});
+        dgrid.refresh();
+    }
+
+    $scope.changeDisplayLabel = function (items) {
+        items.forEach(function(item) {
+            egPromptDialog.open(egCore.strings.TEMPLATE_CONF_PROMPT_CHANGE, item.label || '',
+                {ok : function(value) {
+                    if (value) egReportTemplateSvc.display_fields[item.index].label = value;
+                }}
+            );
+        });
+        dgrid.refresh();
+    }
+
+    $scope.changeDisplayFieldDoc = function (items) {
+        items.forEach(function(item) {
+            egPromptDialog.open(egCore.strings.TEMPLATE_FIELD_DOC_PROMPT_CHANGE, item.doc_text || '',
+                {ok : function(value) {
+                    if (value) egReportTemplateSvc.display_fields[item.index].doc_text = value;
+                }}
+            );
+        });
+        dgrid.refresh();
+    }
+
+    $scope.changeFilterFieldDoc = function (items) {
+        items.forEach(function(item) {
+            egPromptDialog.open(egCore.strings.TEMPLATE_FIELD_DOC_PROMPT_CHANGE, item.doc_text || '',
+                {ok : function(value) {
+                    if (value) egReportTemplateSvc.filter_fields[item.index].doc_text = value;
+                }}
+            );
+        });
+        fgrid.refresh();
+    }
+
+    $scope.changeFilterValue = function (items) {
+        items.forEach(function(item) {
+            var l = null;
+            egPromptDialog.open(egCore.strings.TEMPLATE_CONF_DEFAULT, item.value || '',
+                {ok : function(value) {
+                    if (value) egReportTemplateSvc.filter_fields[item.index].value = value;
+                }}
+            );
+        });
+        fgrid.refresh();
+    }
+
+    $scope.changeTransform = function (items) {
+
+        var f = items[0];
+
+        var tlist = [];
+        angular.forEach(egReportTemplateSvc.Transforms, function (o,n) {
+            if ( o.datatype.indexOf(f.datatype) > -1) {
+                if (tlist.indexOf(o.label) == -1) tlist.push( o.label );
+            }
+        });
+        
+        items.forEach(function(item) {
+            egSelectDialog.open(
+                egCore.strings.SELECT_TFORM, tlist, item.transform.label,
+                {ok : function(value) {
+                    if (value) {
+                        var t = egReportTemplateSvc.getTransformByLabel(value);
+                        item.transform = {
+                            label     : value,
+                            transform : t,
+                            aggregate : egReportTemplateSvc.Transforms[t].aggregate ? true : false
+                        };
+                    }
+                }}
+            );
+        });
+
+        fgrid.refresh();
+    }
+
+    $scope.changeOperator = function (items) {
+
+        var flist = [];
+        Object.keys(egReportTemplateSvc.Filters).forEach(function(k){
+            var v = egReportTemplateSvc.Filters[k];
+            if (flist.indexOf(v.label) == -1) flist.push(v.label);
+            if (v.labels && v.labels.length > 0) {
+                v.labels.forEach(function(l) {
+                    if (flist.indexOf(l) == -1) flist.push(l);
+                })
+            }
+        });
+
+        items.forEach(function(item) {
+            var l = item.operator ? item.operator.label : '';
+            egSelectDialog.open(
+                egCore.strings.SELECT_OP, flist, l,
+                {ok : function(value) {
+                    if (value) {
+                        var t = egReportTemplateSvc.getFilterByLabel(value);
+                        item.operator = { label: value, op : t };
+                    }
+                }}
+            );
+        });
+
+        fgrid.refresh();
+    }
+
+    $scope.removeFilterValue = function (items) {
+        items.forEach(function(item) {delete egReportTemplateSvc.filter_fields[item.index].value});
+        fgrid.refresh();
+    }
+
+    $scope.removeFilterField = function (items) {
+        items.forEach(function(item) {egReportTemplateSvc.removeField('filter_fields', item)});
+        fgrid.refresh();
+    }
+
+    $scope.allSources = values(egCore.idl.classes).sort( function(a,b) {
+        if (a.core && !b.core) return -1;
+        if (b.core && !a.core) return 1;
+        aname = a.label ? a.label : a.name;
+        bname = b.label ? b.label : b.name;
+        if (aname > bname) return 1;
+        return -1;
+    });
+
+    $scope.class_tree = [];
+    $scope.selected_source = null;
+    $scope.selected_source_fields = [];
+    $scope.selected_source_field_list = [];
+    $scope.available_field_transforms = [];
+    $scope.coreSource = null;
+    $scope.coreSourceChosen = false;
+    $scope.currentPathLabel = '';
+
+    $scope.treeExpand = function (node, expanding) {
+        if (expanding) node.children.map(egCore.idl.classTree.fleshNode);
+    }
+
+    $scope.filterFields = function (n) {
+        return n.virtual ? false : true;
+        // should we hide links?
+        return n.datatype && n.datatype != 'link'
+    }
+
+    $scope.field_tree_opts = {
+        multiSelection: true,
+        equality      : function(node1, node2) {
+            return node1.name == node2.name;
+        }
+    }
+
+    $scope.field_transforms_tree_opts = {
+        equality : function(node1, node2) {
+            if (!node2) return false;
+            return node1.transform == node2.transform;
+        }
+    }
+
+    $scope.selectFields = function () {
+        while ($scope.available_field_transforms.length) {
+            $scope.available_field_transforms.pop();
+        }
+
+        angular.forEach( $scope.selected_source_field_list, function (f) {
+            angular.forEach(egReportTemplateSvc.Transforms, function (o,n) {
+                if ( o.datatype.indexOf(f.datatype) > -1) {
+                    var include = true;
+
+                    angular.forEach($scope.available_field_transforms, function (t) {
+                        if (t.transform == n) include = false;
+                    });
+
+                    if (include) $scope.available_field_transforms.push({
+                        transform : n,
+                        label     : o.label,
+                        aggregate : o.aggregate ? true : false
+                    });
+                }
+            });
+        });
+
+    }
+
+    $scope.selectSource = function (node, selected, $path) {
+
+        while ($scope.selected_source_field_list.length) {
+            $scope.selected_source_field_list.pop();
+        }
+        while ($scope.selected_source_fields.length) {
+            $scope.selected_source_fields.pop();
+        }
+
+        if (selected) {
+            $scope.currentPath = angular.copy( $path().reverse() );
+            $scope.selected_source = node;
+            $scope.currentPathLabel = $scope.currentPath.map(function(n,i){
+                var l = n.label
+                if (i) l += ' (' + n.jtype + ')';
+                return l;
+            }).join( ' -> ' );
+            angular.forEach( node.fields, function (f) {
+                $scope.selected_source_fields.push( f );
+            });
+        } else {
+            $scope.currentPathLabel = '';
+        }
+
+        // console.log($scope.selected_source);
+    }
+
+    $scope.changeCoreSource = function (new_core) {
+        console.log('changeCoreSource: '+new_core);
+        function change_core () {
+            if (new_core) $scope.coreSource = new_core;
+            $scope.coreSourceChosen = true;
+
+            $scope.class_tree.pop();
+            $scope.class_tree.push(
+                egCore.idl.classTree.setTop($scope.coreSource)
+            );
+
+            while ($scope.selected_source_fields.length) {
+                $scope.selected_source_fields.pop();
+            }
+
+            while ($scope.available_field_transforms.length) {
+                $scope.available_field_transforms.pop();
+            }
+
+            $scope.currentPathLabel = '';
+        }
+
+        if ($scope.coreSourceChosen) {
+            egConfirmDialog.open(
+                egCore.strings.FOLDERS_TEMPLATE,
+                egCore.strings.SOURCE_SETUP_CONFIRM_EXIT,
+                {ok : change_core}
+            );
+        } else {
+            change_core();
+        }
+    }
+
+    loadTemplate();
+}])
+
+;
index 7e45256..3e41715 100644 (file)
@@ -100,6 +100,9 @@ angular.module('egCoreMod')
          */
         function mkclass(cls, fields) {
 
+            service.classes[cls].core_label = service.classes[cls].core ? 'Core sources' : 'Non-core sources';
+            service.classes[cls].classname = cls;
+
             service[cls] = function(seed) {
                 this.a = seed || [];
                 this.classname = cls;
@@ -228,6 +231,81 @@ angular.module('egCoreMod')
         return hash;
     }
 
-    return service;
-}]);
+    // Using IDL links, allow construction of a tree-ish data structure from
+    // the IDL2js web service output.  This structure will be directly usable
+    // by the <treecontrol> directive
+    service.classTree = {
+        top : null
+    };
+
+    function _sort_class_fields (a,b) {
+        var aname = a.label || a.name;
+        var bname = b.label || b.name;
+        return aname > bname ? 1 : -1;
+    }
+
+    service.classTree.buildNode = function (cls, args) {
+        if (!cls) return null;
+
+        var n = service.classes[cls];
+        if (!n) return null;
+
+        if (!args)
+            args = { label : n.label };
+
+        args.id = cls;
+        if (args.from)
+            args.id = args.from + '.' + args.id;
+
+        return angular.extend( args, {
+            idl     : service[cls],
+            jtype   : 'inner',
+            uplink  : args.link,
+            classname: cls,
+            struct  : n,
+            table   : n.table,
+            fields  : n.fields.sort( _sort_class_fields ),
+            links   : n.fields
+                .filter( function(x) { return x.type == 'link'; } )
+                .sort( _sort_class_fields ),
+            children: []
+        });
+    }
+
+    service.classTree.fleshNode = function ( node ) {
+        if (node.children.length > 0)
+            return node; // already done already
 
+        angular.forEach(
+            node.links.sort( _sort_class_fields ),
+            function (n) {
+                var nlabel = n.label ? n.label : n.name;
+                node.children.push(
+                    service.classTree.buildNode(
+                        n["class"],
+                        {   label : nlabel,
+                            from  : node.id,
+                            link  : n
+                        }
+                    )
+                );
+            }
+        );
+
+        return node;
+    }
+
+    service.classTree.setTop = function (cls) {
+        console.debug('setTop: '+cls);
+        return service.classTree.top = service.classTree.fleshNode(
+            service.classTree.buildNode(cls)
+        );
+    }
+
+    service.classTree.getTop = function () {
+        return service.classTree.top;
+    }
+
+    return service;
+}])
+;
index cbccf9a..6b4e325 100644 (file)
@@ -185,6 +185,51 @@ function($uibModal, $interpolate) {
 }])
 
 /**
+ * egSelectDialog.open(
+ *    "message goes {{here}}", 
+ *    list,           // ['values','for','dropdown'],
+ *    selectedValue,  // optional
+ *    {
+ *      here : 'foo',
+ *      ok : function(value) {console.log(value)}, 
+ *      cancel : function() {console.log('prompt denied')}
+ *    }
+ *  );
+ */
+.factory('egSelectDialog', 
+    
+       ['$uibModal','$interpolate',
+function($uibModal, $interpolate) {
+    var service = {};
+
+    service.open = function(message, inputList, selectedValue, msg_scope) {
+        return $uibModal.open({
+            templateUrl: './share/t_select_dialog',
+            controller: ['$scope', '$uibModalInstance',
+                function($scope, $uibModalInstance) {
+                    $scope.message = $interpolate(message)(msg_scope);
+                    $scope.args = {
+                        list  : inputList,
+                        value : selectedValue
+                    };
+                    $scope.focus = true;
+                    $scope.ok = function() {
+                        if (msg_scope.ok) msg_scope.ok($scope.args.value);
+                        $uibModalInstance.close()
+                    }
+                    $scope.cancel = function() {
+                        if (msg_scope.cancel) msg_scope.cancel();
+                        $uibModalInstance.dismiss();
+                    }
+                }
+            ]
+        })
+    }
+
+    return service;
+}])
+
+/**
  * Warn on page unload and give the user a chance to avoid navigating
  * away from the current page.  
  * Only one handler is supported per page.
@@ -268,7 +313,8 @@ function($window , egStrings) {
         scope: {
             list: "=", // list of strings
             selected: "=",
-            egDisabled: "="
+            egDisabled: "=",
+            allowAll: "@",
         },
         template:
             '<div class="input-group">'+
@@ -277,33 +323,50 @@ function($window , egStrings) {
                     '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
                     '<ul class="dropdown-menu dropdown-menu-right">'+
                         '<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
-                        '<li ng-if="all" class="divider"><span></span></li>'+
-                        '<li ng-if="all" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
+                        '<li ng-if="complete_list" class="divider"><span></span></li>'+
+                        '<li ng-if="complete_list" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
                     '</ul>'+
                 '</div>'+
             '</div>',
         controller: ['$scope','$filter',
             function( $scope , $filter) {
 
-                $scope.all = false;
+                $scope.complete_list = false;
                 $scope.isopen = false;
+                $scope.clickedopen = false;
+                $scope.clickedclosed = null;
 
                 $scope.showAll = function () {
-                    if ($scope.selected.length > 0)
-                        $scope.all = true;
+
+                    $scope.clickedopen = !$scope.clickedopen;
+
+                    if ($scope.clickedclosed === null) {
+                        if (!$scope.clickedopen) {
+                            $scope.clickedclosed = true;
+                        }
+                    } else {
+                        $scope.clickedclosed = !$scope.clickedopen;
+                    }
+
+                    if ($scope.selected.length > 0) $scope.complete_list = true;
+                    if ($scope.selected.length == 0) $scope.complete_list = false;
+                    $scope.makeOpen();
                 }
 
                 $scope.makeOpen = function () {
-                    $scope.isopen = $filter('filter')(
+                    $scope.isopen = $scope.clickedopen || ($filter('filter')(
                         $scope.list,
                         $scope.selected
-                    ).length > 0 && $scope.selected.length > 0;
-                    $scope.all = false;
+                    ).length > 0 && $scope.selected.length > 0);
+                    if ($scope.clickedclosed) $scope.isopen = false;
                 }
 
                 $scope.changeValue = function (newVal) {
                     $scope.selected = newVal;
                     $scope.isopen = false;
+                    $scope.clickedclosed = null;
+                    $scope.clickedopen = false;
+                    if ($scope.selected.length == 0) $scope.complete_list = false;
                 }
 
             }
index da1c2b8..6c7709f 100644 (file)
@@ -39,7 +39,7 @@ for (var c in _preload_fieldmapper_IDL) {
         </xsl:choose>
     </xsl:template>
  
-    <xsl:template match="idl:class"><xsl:value-of select="@id"/>:{name:"<xsl:value-of select="@id"/>",<xsl:if test="@reporter:label">label:"<xsl:value-of select="@reporter:label"/>",</xsl:if><xsl:if test="@oils_persist:restrict_primary">restrict_primary:"<xsl:value-of select="@oils_persist:restrict_primary"/>",</xsl:if><xsl:if test="@oils_persist:virtual = 'true'">virtual:true,</xsl:if><xsl:if test="idl:fields/@oils_persist:primary">pkey:"<xsl:value-of select="idl:fields/@oils_persist:primary"/>",</xsl:if><xsl:if test="idl:fields/@oils_persist:sequence">pkey_sequence:"<xsl:value-of select="idl:fields/@oils_persist:sequence"/>",</xsl:if><xsl:apply-templates select="idl:fields"/><xsl:apply-templates select="permacrud:permacrud"/>}</xsl:template>
+    <xsl:template match="idl:class"><xsl:value-of select="@id"/>:{name:"<xsl:value-of select="@id"/>",<xsl:if test="@reporter:label">label:"<xsl:value-of select="@reporter:label"/>",</xsl:if><xsl:if test="@oils_persist:restrict_primary">restrict_primary:"<xsl:value-of select="@oils_persist:restrict_primary"/>",</xsl:if><xsl:if test="@oils_persist:tablename">table:"<xsl:value-of select="@oils_persist:tablename"/>",</xsl:if><xsl:if test="@reporter:core = 'true'">core:true,</xsl:if><xsl:if test="@oils_persist:virtual = 'true'">virtual:true,</xsl:if><xsl:if test="oils_persist:source_definition">source:"(<xsl:value-of select="oils_persist:source_definition/text()"/>)",</xsl:if><xsl:if test="idl:fields/@oils_persist:primary">pkey:"<xsl:value-of select="idl:fields/@oils_persist:primary"/>",</xsl:if><xsl:if test="idl:fields/@oils_persist:sequence">pkey_sequence:"<xsl:value-of select="idl:fields/@oils_persist:sequence"/>",</xsl:if><xsl:apply-templates select="idl:fields"/><xsl:apply-templates select="permacrud:permacrud"/>}</xsl:template>
  
     <xsl:template match="idl:fields">fields:[<xsl:for-each select="idl:field"><xsl:call-template name="printField"><xsl:with-param name='pos' select="position()"/></xsl:call-template><xsl:if test="not(position() = last())">,</xsl:if></xsl:for-each>]</xsl:template>