UI

The UI contains web resource files of different content types that the REST layer must set when returning the resource. API Platform supports a default mapping for HTML, JavaScript and JSON files.

The ui element in metadata.json file contains the file names that render the policy and a Javascript file feeds the UI. When creating a metadata.json file for your custom policy follow these guidelines covering all aspect of the policy.

The UI is dynamically loaded and it consists of the following components:
  • edit: rendered for edit mode in the implementation tab.

  • view: rendered for view-only mode, for example as seen in a deployment.

Metadata for each UI component contains three attributes html, js and l10nbundle which are relative paths for the root HTML, JavaScript and l10 bundles.

The UI part of the policy metadata looks like:
   "ui": {
      "edit": {
         "html": "custompolicy-edit.html",
         "js": "custompolicy-edit.js",
         "helpInfo": "#helpInfo",
         "helpUrl": "http://www.oracle.com",
         "helpTopicId": "policies.custompolicy"
      },
      "view": {
         "html": "custompolicy-view.html",
         "js": "custompolicy-view.js",
         "helpInfo": "#helpInfo",
         "helpUrl": "http://www.oracle.com",
         "helpTopicId": "policies.custompolicy"
      },
      "l10nbundle": "L10n/custompolicy.json"
   },

The attribute helpInfo refers to a string to be displayed as is. Presence of the HASH (#) sign in the beginning of the value of “helpInfo” as in #apiReqHelpInfo indicates that the string is to be referenced from the l10nbundle file request.json.

Sample contents for {policy name}-edit.js

Use the sample files as a model while creating HTML and JS files for your policy.

function PolicyConfigurationModel(ko, $, oj, config, additionalParams) {
    self = this;
    self.config = config;
    self.conditions = ko.observableArray();
    self.l10n = ko.observable(additionalParams.l10nbundle);
    self.disableApplyPolicyButton = additionalParams.disableApplyPolicyButton;

    self.opList   = ko.observableArray([
                        {"value" : "=",  "label" : "="},
                        {"value" : "!=", "label" : "!="},
                        {"value" : ">",  "label" : ">"},
                        {"value" : "<",  "label" : "<"},
                        {"value" : ">=", "label" : ">="},
                        {"value" : "<=", "label" : "<="},
                        {"value" : "null",  "label" : self.l10n()['label.isnull']},
                        {"value" : "not null", "label" : self.l10n()['label.isnotnull']}
                    ]);

    self.actions = ko.observableArray([
                            {"value" : "PASS", "label" : self.l10n()['label.pass']},
                            {"value" : "REJECT", "label" : self.l10n()['label.reject']}
                        ]);

    self.conjunctions = ko.observableArray([
                            {"value" : "ANY", "label" : self.l10n()['label.any']},
                            {"value" : "ALL", "label" : self.l10n()['label.all']}
                        ]);

    self.selectedAction = ko.observableArray(["REJECT"]);
    self.selectedConjunction = ko.observableArray(["ANY"]);

    self.initialize = function() {
        if (self.config) {
            for (var i = 0; i < self.config.conditions.length; ++i) {
                var condition = {};

                if (self.isUnary(self.config.conditions[i].operator)) {
                    condition = {
                        "headerName": self.config.conditions[i].headerName,
                        "operator": ko.observableArray([self.config.conditions[i].operator]),
                        "showValueCol": ko.observable(false)
                    };
                } else {
                    condition = {
                        "headerName": self.config.conditions[i].headerName,
                        "operator": ko.observableArray([self.config.conditions[i].operator]),
                        "headerValue": self.config.conditions[i].headerValue,
                        "showValueCol": ko.observable(true)
                    };
                }
                self.conditions.push(condition);
            }
        }

        if (self.config && self.config.action) {
            self.selectedAction([self.config.action]);
        }

        if (self.config && self.config.conjunction) {
            self.selectedConjunction([self.config.conjunction]);
        }

        // we need at least one header to start with
        if (self.conditions().length === 0) {
            self.addCondition();
        } else {
            self.disableApplyButton(false);
        }

        self.populatePassRejectContainer();
    };

    self.addCondition = function() {
        self.conditions.push({"headerName" : "", "operator" : ko.observableArray(["="]), "headerValue" : "", "showValueCol": ko.observable(true)});
        self.disableApplyButton(false);
    };

    self.removeCondition = function(condition) {
        self.conditions.remove(condition);
        self.disableApplyButton(self.conditions().length === 0);
    };

    self.handleOperatorChange = function(index, event, data) {
        if (data.option !== 'value'){
            return;
        }

        if (self.isUnary(self.conditions()[index].operator()[0])) {
            self.conditions()[index].showValueCol(false);
        } else {
            self.conditions()[index].showValueCol(true);
        }
    };

    self.isUnary = function(operator) {
        return operator === "null" || operator === "not null";
    };

    self.populatePassRejectContainer = function() {
        var actionSelect = $('<select id=\'action\' data-bind="ojComponent: { component: \'ojSelect\', options: actions, value: selectedAction, rootAttributes: { style:\'max-width:50px;\'}}"></select>');
        var optionSelect = $('<select id=\'option\' data-bind="ojComponent: { component: \'ojSelect\', options: conjunctions, value: selectedConjunction, rootAttributes: { style:\'max-width:50px;\'}}"></select>');
        var passRejectHtml = apiplatform.utils.substituteParams(self.l10n()['label.passorreject'], [actionSelect[0].outerHTML, optionSelect[0].outerHTML]);
        $("#pass-reject-container").append(passRejectHtml);
    };

    // Begin - Framework methods

    self.getPolicyConfiguration = function(){
        //self.config.operator = self.operator()[0];
        var config = {"action": "", "conjunction" : "", "conditions" : []};
        for (var i = 0; i < self.conditions().length; i++) {
            var condition = {};
            if (self.isUnary(self.conditions()[i].operator()[0])) {
                condition = {
                    "headerName": self.conditions()[i].headerName,
                    "operator"  : self.conditions()[i].operator()[0]
                };
            } else {
                condition = {
                    "headerName" : self.conditions()[i].headerName,
                    "operator"   : self.conditions()[i].operator()[0],
                    "headerValue": self.conditions()[i].headerValue
                };
            }
            config.conditions.push(condition);
        };
        config.action = self.selectedAction()[0];
        config.conjunction  = self.selectedConjunction()[0];
        return config;
    };

    self.disableApplyButton = function(flag) {
        self.disableApplyPolicyButton(flag);
    };

    // End - Framework methods

    self.initialize();
    
}

Sample Contents of {policy name}-edit.html

Top level <div> must have the id policy-ui-contentbecause the Policy UI code uses this div to bind the contents dynamically.

<div id="policy-ui-content" class="oj-form" >
    <div id="pass-reject-container">
    </div>

    <div class="conditions-container-div">
        <table
               data-bind="visible: conditions().length > 0 ">
                <col style="width:35%">
                <col style="width:15%">
                <col style="width:35%">
                <col style="width:15%">
            <tr>
                <th style="text-align: left;"><span data-bind="text: l10n()['label.headername']" 
											class="condition-field-label condition-first-or-last-column"></span></th>
                <th style="text-align: left;"><span data-bind="text: l10n()['label.headeroperator']" 
											class="condition-field-label condition-middle-column"></span></th>
                <th style="text-align: left;"><span data-bind="text: l10n()['label.headervalue']"
											 class="condition-field-label condition-first-or-last-column"></span></th>
                <th></th>
            </tr>
            <tbody data-bind="foreach: {data: conditions, as: 'condition'}">
                <tr class="condition-container-row">
                    <td class="condition-first-or-last-column">
                        <input type="text" required
                               data-bind="attr: {id: 'headerName_' + $index()}, ojComponent: {
                                    component: 'ojInputText', value: condition.headerName,
                                    placeholder: $parent.l10n()['placeholder.headername']}"/>
                    </td>
                    <td class="condition-middle-column">
                        <select data-bind="attr: {id: 'operator_' + $index()}, ojComponent: {
                                  component: 'ojSelect',
                                  options: $parent.opList,
                                  optionChange: function(data, event) {
                                            $parent.handleOperatorChange($index(), data, event);
                                        },
                                  value: condition.operator}">
                        </select>

                    </td>
                    <td class="condition-middle-column">
                        <div data-bind="visible: condition.showValueCol">
                            <input type="text" required
                                   data-bind="attr: {id: 'headerValue_' + $index()}, ojComponent: {
                                        component: 'ojInputText', value: condition.headerValue,
                                        placeholder: $parent.l10n()['placeholder.headervalue']}"/>
                        </div>
                        <div data-bind="visible: !condition.showValueCol()">
                            <span data-bind="text: $parent.l10n()['label.notApplicable']"></span>
                        </div>
                    </td>
                    <td class="condition-last-column">
                        <div>
                            <div style="float:left; vertical-align: top;  display: none;"
                                 class="policy-remove-item-icon-black-withpadding" 
                                 data-bind="attr: {id: 'btn_remove_' + $index()}, click: $parent.removeCondition,
                                            visible: $parent.conditions().length > 1,
                                            attr: {title: $parent.l10n()['tooltip.delcondition']}">
                            </div>
                            <div style="float:left; vertical-align: top;  display: none;"
                                 class="policy-add-item-icon-black"
                                 data-bind="attr: {id: 'btn_add_' + $index()}, click: $parent.addCondition,
                                                visible: $index() === $parent.conditions().length - 1,
                                                attr: {title: $parent.l10n()['tooltip.addcondition']}">
                            </div>
                        </div>
                    </td>
                </tr>
                <tr>
                    <td colspan="3" class="policy-condition-separator"></td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

Sample contents for {policy name}-view.js

function PolicyConfigurationModel(ko, $, oj, config, additionalParams){
    self = this;
    self.config = config;
    self.l10n = ko.observable(additionalParams.l10nbundle);
    self.conditions = ko.observableArray();
    self.selectedAction = ko.observable();
    self.selectedConjunction = ko.observable();

    self.operatorsMap   = [];

    self.initialize = function() {

        self.operatorsMap["="] = "=";
        self.operatorsMap["!="] = "!=";
        self.operatorsMap[">"] = ">";
        self.operatorsMap["<"] = "<";
        self.operatorsMap[">="] = ">=";
        self.operatorsMap["<="] = "<=";
        self.operatorsMap["null"] = self.l10n()['label.isnull'];
        self.operatorsMap["not null"] = self.l10n()['label.isnotnull'];

        if (self.config) {
            for (var i = 0; i < self.config.conditions.length; ++i) {
                var condition = {};

                if (self.isUnary(self.config.conditions[i].operator)) {
                    condition = {
                        "headerName": self.config.conditions[i].headerName,
                        "operator": self.operatorsMap[self.config.conditions[i].operator],
                        "showValueCol": ko.observable(false)
                    };
                } else {
                    condition = {
                        "headerName": self.config.conditions[i].headerName,
                        "operator": self.operatorsMap[self.config.conditions[i].operator],
                        "headerValue": self.config.conditions[i].headerValue,
                        "showValueCol": ko.observable(true)
                    };
                }
                self.conditions.push(condition);
            }
        }

        if (self.config && self.config.action) {
            self.selectedAction(self.config.action);
        }

        if (self.config && self.config.conjunction) {
            self.selectedConjunction(self.config.conjunction);
        }

        self.populatePassRejectContainer();
    };

    self.isUnary = function(operator) {
        return operator === "null" || operator === "not null";
    };

    self.populatePassRejectContainer = function() {
        var actionHtml = $("<span id='action' data-bind='text: selectedAction' 
						style='padding:0px 35px 0px 0px; color: #959595'></span>");
        var conjunctionHtml = $("<span id='action' data-bind='text: selectedConjunction' 
						style='padding:0px 35px 0px 35px; color: #959595'></span>");
        var passRejectHtml = apiplatform.utils.substituteParams(self.l10n()['label.passorreject'],
						 [actionHtml[0].outerHTML, conjunctionHtml[0].outerHTML]);
        $("#pass-reject-container").append(passRejectHtml);
    };

    self.initialize();
}

Sample contents for {policy name}-view.html

<div id="policy-ui-content" class="oj-form" >
    <div id="pass-reject-container" class="condition-field-label-2">
    </div>

    <div class="conditions-container-div">
        <table style="width:100%; padding:0px;"
               data-bind="visible: conditions().length > 0 ">
                <col style="width:40%">
                <col style="width:20%">
                <col style="width:40%">
                
            <tbody data-bind="foreach: {data: conditions, as: 'condition'}">
                <tr data-bind="visible: $index() === 0">
                    <td class="condition-first-or-last-column">
                        <label data-bind="text: $parent.l10n()['label.headername']" class="condition-field-label"></label>
                    </td>
                    <td class=" condition-middle-column">
                        <label data-bind="text: $parent.l10n()['label.headeroperator']" class="condition-field-label"></label>
                    </td>                    
                    <td class="condition-first-or-last-column">
                        <label data-bind="text: $parent.l10n()['label.headervalue']" class="condition-field-label"></label>
                    </td>
                </tr>
                
                <tr class="condition-container-row">
                    <td class="condition-first-or-last-column">
                        
                        <label data-bind="text:condition.headerName" class="policy-readonly-text"></label>
                    </td>
                    <td class="condition-middle-column">
                        
                        <label data-bind="text:condition.operator" class="policy-readonly-text"></label>

                    </td>
                    <td class="condition-first-or-last-column">
                        <div data-bind="visible: condition.showValueCol">
                            
                            <label data-bind="text: condition.headerValue" class="policy-readonly-text"></label>
                        </div>
                        <div data-bind="visible: !condition.showValueCol()">
                            <span data-bind="text: $parent.l10n()['label.notApplicable']" class="policy-readonly-text"></span>
                        </div>
                    </td>
                    
                </tr>
                <tr>
                    <td colspan="3" class="policy-condition-separator"></td>
                </tr>
            </tbody>
        </table>
    </div>
    
</div>