This is a follow on to "APEX row selector" posted 5 days ago.
The problem was collecting multiple values from an interactive grid. From the excellent links to post supplied I was able to achieve this. However, the next part of the project is to open an edit dialog page and update multiple values.
I added this code to the attribute of the interactive grid:
function (config)
{
var $ = apex.jQuery,
toolbarData = $.apex.interactiveGrid.copyDefaultToolbar(),
toolbarGroup = toolbarData.toolbarFind("actions3");
toolbarGroup.controls.push(
{
type: "BUTTON",
action: "updateCar",
label: "Edit Selected Cars",
hot: true,
});
config.toolbarData = toolbarData;
config.initActions = function (actions)
{
// Defining the action for activate button
actions.add(
{
name: "updateCar",
label: "Edit Selected Cars",
action: updateCar
});
}
function updateCar(event, focusElement)
{
var i, records, model, record,
view = apex.region("ig_car").widget().interactiveGrid("getCurrentView");
var vid = "";
model = view.model;
records = view.getSelectedRecords();
if (records.length > 0)
{
for (i = 0; i < records.length; i++)
{
record = records[i];
alert("Under Development " + record[1]);
vid = vid + record[1] + "||";
apex.item("P18_CAR").setValue(vid);
// need to open next page here and pass parameters
}
}
}
return config;
}
I need to know how to open a form and have the parameter values available to pass to an oracle update script.
Thank you for any help you can provide. I did find some posts but I really need a good example. I have tried everything to no avail.
There are various ways you could do this. Here's one way, perhaps someone else will offer a more efficient option.
The JavaScript options for navigation in APEX are documented here:
https://docs.oracle.com/en/database/oracle/application-express/19.1/aexjs/apex.navigation.html
Since you're trying to open a separate page, you probably want to use apex.navigation.dialog, which is what APEX automatically uses when opening modal pages from reports, buttons, etc.
However, as noted in the doc, the URL for the navigation must be generated server-side for security purposes. You need a dynamic URL (one not known when the page renders), so you'll need a workaround to generate it. Once you have the URL, navigating to it is easy. So how do you get the URL? Ajax.
Create an Ajax process to generate the URL
Under the processing tab of the report/grid page, right-click Ajax Callback and select Create Process.
Set Name to GET_FORM_URL.
Set PL/SQL code to the following
code:
declare
l_url varchar2(512);
begin
l_url := apex_page.get_url(
p_application => :APP_ID,
p_page => 3,
p_items => 'P3_ITEM_NAME',
p_values => apex_application.g_x01
);
apex_json.open_object();
apex_json.write('url', l_url);
apex_json.close_object();
end;
Note that I'm using apex_item.get_url to get the URL, this is an alternative to apex_util.prepare_url. I'm also using apex_json to emit JSON for the response to the client.
Also, the reference to apex_application.g_x01 is important, as this will contain the selected values from the calling page. You'll see how this was set in the next step.
Open the URL with JavaScript
Enter the following code in the Function and Global Variable Declaration attribute of the calling page:
function openFormPage(ids) {
apex.server.process(
'GET_FORM_URL',
{
x01: ids.join(':')
},
{
success: function (data) {
var funcBody = data.url.replace(/^"javascript:/, '').replace(/\"$/,'');
new Function(funcBody).call(window);
},
error: function (jqXHR, textStatus, errorThrown) {
console.error(errorThrown);
// handle error
}
}
);
}
In this case, I'm using apex.server.process to call the server-side PL/SQL process. Note that I'm passing the value of ids.join(':') to x01. That value will become accessible in the PL/SQL code as apex_application.g_x01. You can use additional items, or you can pass a colon-delimited string of values to just one item (as I'm doing).
The URL that's returned to the client will not be a standard URL, it will be a JavaScript snippet that includes the URL. You'll need to remove the leading and trailing parts and use what's left to generate a dynamic function in JavaScript.
This is generally frowned upon, but I believe it's safe enough in this context since I know I can trust that the response from the process call is not malicious JavaScript code.
Add a security check!!!
Because you're creating a dynamic way to generate URLs to open page 3 (or whatever page you're targeting), you need to ensure that the modal page is protected. On that page, create a Before Header process that validates the value of P3_ITEM_NAME. If the user isn't supposed to be able to access those values, then throw an exception.
Related
var a = $v('P1995_LUMBER');
if ((a = '1')) {
apex.submit({
request: "CREATE",
set: {
LUMBER: "P1995_LUMBER",
LST_NME: "P1995_LST_NME",
FST_NME: "P1995_FST_NME",
},
});
} else if (a != '1') {
apex.submit({
request: "Update",
set: {
LUMBER: "P1995_LUMBER",
LST_NME: "P1995_LST_NME",
FST_NME: "P1995_FST_NME",
},
});
} else {
alert("bang bang");
}
Couple of things:
JavaScript's equality check is either == or === (more details here). (a = '1') assign '1' to the variable.
It seems like you're not using the apex.submit process correctly. Typically, you would set the item's value
e.g.:
apex.page.submit({
request: "SAVE",
set: {
"P1_DEPTNO": 10,
"P1_EMPNO": 5433
}
} );
Although, by looking at your JavaScript code, I would say you don't even need to use JavaScript.
Whenever you submit a page, all items on it are automatically sent to the server-side. You can then reference them using bind variables. You could then simply have two process, one for the Create and one for the Update, each having the corresponding insert/update statement using the different items on your page.
Usually what you will see is a page with two buttons for Create/Edit. They will have a server-side condition so that only the correct one is displayed.
Try creating a Form type page (form with report) using the wizard, and you'll see how everything is done.
Without seeing the page and the code you're using it's hard to tell what your issue really is, more details would be required.
That code does not have any sql in it so it is impossible to diagnose why you are encountering a TOO_MANY_ROWS exception. Run the page in debug mode and check the debug data - it should show you what statement is throwing the exception. If you need more help, post a proper reproducible case, not a single snipped of code without any context.
I'm running Apex 19.2
I have a page with some items created dynamically as follows :
HTML clob;
Html := APEX_ITEM.textarea(p_idx=>32, p_value=>'MyValue',p_item_id=>'MyId',p_attributes=>'class="textarea"');
htp.p(HTML);
The page items are generated correctly :
<textarea name="f32" rows="4" cols="40" wrap="VIRTUAL" class="textarea" id="MyId"></textarea>
I'm also adding the item wrapper to match the static Items layout created from the designer.
<div class="t-Form-inputContainer col">
<div class="t-Form-itemWrapper">
<textarea name="f32" rows="4" cols="40" wrap="VIRTUAL" class="textarea" id="MyId"></textarea>
</div>
<span id="MyId_error_placeholder" class="a-Form-error"></span>
</div>
In the validation, I'm checking some rules from apex_application.g_fn arrays and I would like to show an error on the item created via :
apex_error.add_error(p_message => 'error', p_display_location => apex_error.c_inline_with_field_and_notif, p_page_item_name=> 'MyId');
After validation, the error is not shown next to the item created. Notification also appears but it's empty. However If I try to show the same error on a static item created in the designer. The error is shown properly.
Can anyone help please ?
Thanks.
As you've found, APEX_ITEM doesn't work with APEX_ERROR in the way that you'd like it to. Marc's comments here indicate that APEX_ITEM will likely not be developed further, so it probably never will. https://stackoverflow.com/a/61737128/3010084
Your best option might be to move your validation logic to a stored procedure. Do all the validation in one call via parameters. In addition to the regular parameters, add a parameter that indicates if the response should be JSON or not. If so, just return a JSON document with the errors, otherwise use apex_error. This will allow you to call the validation logic via Ajax to show the errors where you like, but also on submit/page processing (because client-side validation can't be trusted).
Here are some steps you can follow to see how this works... First, compile the following procedure in your schema:
create or replace procedure validate_thing(
p_description in varchar2,
p_return_json in boolean,
p_json_result out json_object_t
)
is
l_errors_arr json_array_t := json_array_t();
l_error_obj json_object_t := json_object_t();
l_item_id varchar2(30);
l_error_message varchar2(255);
begin
if length(p_description) > 10
then
l_item_id := 'description';
l_error_message := 'Description should be less than 10 characters.';
if p_return_json
then
l_error_obj := json_object_t();
l_error_obj.put('pageItem', l_item_id);
l_error_obj.put('message', l_error_message);
l_errors_arr.append(l_error_obj);
else
-- Server-side code will not worry about displaying the error with the item as
-- this is just a backup for the client-side validation
apex_error.add_error(
p_message => l_error_message,
p_display_location => apex_error.c_inline_in_notification
);
end if;
end if;
if p_return_json
then
p_json_result := json_object_t();
if l_errors_arr.get_size() > 0
then
p_json_result.put('status', 'error');
p_json_result.put('errors', l_errors_arr);
else
p_json_result.put('status', 'success');
end if;
end if;
end;
As you can see, the procedure has logic to do client-side validations (JSON) or server-side validation (APEX_ERROR). You would need to add additional parameters and logic as needed for the form.
Create a new blank page in your app and go to the Page Designer for the new page. Right-click Content Body (under Regions) and select Create Region. Set the region's Type to PL/SQL Dynamic Content and add the following code to the PL/SQL Code attribute:
declare
html clob;
begin
-- The div and fieldset wrappers are needed so that APEX will generate an error
-- message template automatically to display the error inline.
html := '<div><fieldset>';
html := html || APEX_ITEM.textarea(p_idx=>32, p_value=>'MyValue',p_item_id=>'description',p_attributes=>'class="textarea apex-item-textarea"');
html := html || '</fieldset></div>';
htp.p(html);
end;
That code uses apex_item to add an item to the page dynamically. Note, the value passed to p_item_id, as that's important. The apex-item-textarea class is needed for error styling and the div and fieldset wrappers are needed to display error messages inline.
Select the Processing tab in Page Designer. Right-click Ajax Callback and select Create Process. Set Name to DO_VALIDATIONS and enter the following code in the PL/SQL Code field.
declare
l_result json_object_t;
begin
validate_thing(
p_description => apex_application.g_x01,
p_return_json => true,
p_json_result => l_result
);
htp.p(l_result.to_string());
end;
That is the code that will call validate_thing with p_return_json set to true. Note that the value of "description" is being passed in via apex_application.g_x01. You have g_x01 - g_x20 to work within this way. There are various options you could leverage to sent values in via Ajax, this is just one example. See see the doc on apex.server.process (used next) for more info.
Return to the rendering tab, right-click the new region, and select Create Button. Set the Button Name to SUBMIT. Right-click the SUBMIT button and select Create Dynamic Action. Set the Name to SUBMIT clicked. Select the default Show action, set its Action to Execute JavaScript Code, then add the following code to the Code field:
apex.server.process(
'DO_VALIDATIONS',
{
x01: $x('description').value
},
{
success: function(result) {
apex.message.clearErrors();
if (result.status === 'error') {
for (var idx = 0; idx < result.errors.length; idx++) {
result.errors[idx].type = 'error';
result.errors[idx].location = ['page', 'inline'];
result.errors[idx].unsafe = false;
}
apex.message.showErrors(result.errors);
} else if (result.status === 'success') {
apex.page.submit('SUBMIT');
}
},
error: function( jqXHR, textStatus, errorThrown ) {
console.log(jqXHR, textStatus, errorThrown)
}
}
);
This is the JavaScript code that will invoke the new DO_VALIDATIONS Ajax process. If errors are returned from the server, apex.message.showErrors will display them. Otherwise, the page is submitted for processing.
Select the Processing tab, right-click Processing, and select Create Process. Set Name to Do Validations and enter the following code in the PL/SQL Code attribute:
declare
-- Only needed to call validate_thing, not used.
l_result json_object_t;
begin
validate_thing(
p_description => apex_application.g_f32(1), -- This is where the item's value will be when submitting normally
p_return_json => false, -- This tells validate_thing to use apex_error
p_json_result => l_result
);
end;
That code will invoke validate_thing with p_return_json set to false. This will rerun the validations on the server-side to ensure they are enforced there. As it's just a backup for the client-side call, I don't worry about displaying errors inline with the items (the JS will do that).
Right-click Processing again and select Create Process. Set Name to Do Work and just enter null; for the PL/SQL Code Attribute. Set Success Message to It ran.. Under Server-side Condition, set Type to PL/SQL Expression and enter not apex_error.have_errors_occurred in the PL/SQL Expression field.
This process represents the actual business logic you want to run after validations have passed. You will only see the success message after clicking submit if both the Ajax and server-side validations have passed.
If you wish the test the server-side validations, add this line of JavaScript code in the Dynamic Action, just before the line that submits the page:
$x('description').value = '12345678910';
That will update the value of the text area to exceed the limit enforced by the server.
I am working with O365 SharePoint Online platform with SharePoint lists around 300 items in All Items View. For the first 30 items Text to Html Javascript function successfully converts text code to Html and displays status in HTML color format, but when I am trying to select next 31 items and go ahead using the pagination the function does not able to convert Html and display only text codes. I also changed the calculated column value type to "Number" to get the HTML to render in the list view. But not being changed yet. Does anyone please who have the code handy to make this work easy? Below is the Text to HTML code used in O365 platform.
<script type="text/javascript">
function TextToHTML(NodeSet, HTMLregexp) {
var CellContent = "";
var i=0;
while (i < NodeSet.length){
try {
CellContent = NodeSet[i].innerText || NodeSet[i].textContent;
if (HTMLregexp.test(CellContent)) {NodeSet[i].innerHTML = CellContent;}
}
catch(err){}
i=i+1;
}
}
// Calendar views
var regexpA = new RegExp("\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*");
TextToHTML(document.getElementsByTagName("a"),regexpA);
// List views
var regexpTD = new RegExp("^\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*$");
TextToHTML(document.getElementsByTagName("TD"),regexpTD);
// This function is call continuesly every 100ms until the length of the main field changes
// after which the convert text to HTML is executed.
//
var postElemLength = 0;
function PostConvertToHtml()
{
if (postElemLength == document.getElementsByTagName("TD").length)
{
setTimeout(PostConvertToHtml,100);
}
else
{
var regexpTD = new RegExp("^\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*$");
TextToHTML(document.getElementsByTagName("TD"),regexpTD);
}
}
// Grouped list views
ExpGroupRenderData = (function (old) {
return function (htmlToRender, groupName, isLoaded) {
var result = old(htmlToRender, groupName, isLoaded);
var regexpTD = new RegExp("^\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*$");
TextToHTML(document.getElementsByTagName("TD"),regexpTD);
// start the periodic callback to check when the element has been changed
if(isLoaded == 'false')
{
postElemLength = document.getElementsByTagName("TD").length;
setTimeout(PostConvertToHtml,100);
}
};
})(ExpGroupRenderData);
// Preview pane views
if (typeof(showpreview1)=="function") {
showpreview1 = (function (old) {
return function (o) {
var result = old(o);
var regexpTD = new RegExp("^\\s*<([a-zA-Z]*)(.|\\s)*/\\1?>\\s*$");
TextToHTML(document.getElementsByTagName("TD"),regexpTD);
};
})(showpreview1);
}</script>
Below is the generated text code which needs to be converted to Html used in calculated columns. Thanks.
=IF([Trend]="Cancelled","DarkGray",IF([Trend]="Completed","DodgerBlue",IF([Trend]="Declining","DarkOrange",IF([Trend]="Improving","ForestGreen",IF([Trend]="No Change","ForestGreen",IF([Trend]="Not Started","White",IF([Trend]="On Hold","DarkGray","")))))))
And..
="<div style='position:relative;display:inline-block;width:100%;'>
<div style='width:100%;display:inline-block;text-align:center;border:1px solid "&[VPN provisioning_Clr]&";position:absolute;color:"&[VPN provisioning_Clr]&";'> "&[VPN provisioning]&"
</div>
<div style='display:inline-block;width: 100%;background-color:"&[VPN provisioning_Clr]&";text-align:center;border:1px solid;z-index:-1;filter:alpha(opacity=20);opacity:0.2;'>"&[VPN provisioning]&"
</div>
</div>"
Assuming you are using the Classic UI in SharePoint Online...
Two possible issues:
Check to see if the site has the Minimal Download Strategy enabled.
If so disable it and test your code. MDS often is the cause for JavaScript running only once. (The page is not reloaded, only the data area.)
The loading of the next page of the list is via a Web Service call and that may not be triggering your JavaScript. (Again, the page is not reloaded, only the data area.) You may need to intercept the paging link to insure your code is run. (Also check to see if the "Asynchronous Load" option has been enabled. Edit the page, edit the web part, and expand the "AJAX Options" section.)
You may want to take a look at a workflow plus a Calculated column solution to add the color coding. See: http://techtrainingnotes.blogspot.com/2018/01/adding-html-to-sharepoint-columns-color.html
I'm looking for informations about security on Qooxdoo.
I want to check my app vs OWASP top 10
A point to review is the XSS OWASP A3 XSS
How can I be sure that Qooxdoo is secure against XSS attacks ?
Does Qooxdoo use some sanitizer tools ?
SOLVED
A short answer from all the discussions. Yes Qooxdoo is XSS safe. By default, no javascript value in any field will be executed.
But, if you use rich=true, you have to check input/output
A common XSS attack vector are situations where an attacker somehow inputs JS code into a web application, such that this code then shows up in the DOM of a webpage and gets thus activated.
To protect against this kind of XSS, you must make sure that the backend server does not send user generated (un-cleaned) html towards the browser ... (this has nothing to do with qooxdoo).
That said, the regular qooxdoo widgets do not in general display data as html so you are reasonably safe even without a clever server. The exception is the qx.ui.basic.Label widget and its descendants. The Label widget has the ability to display HTML directly if you set the rich property. The rich property is set to false by default, but if you enable it, you have to make sure you don't display 'dangerous' html content.
Only very few (non essential) qooxdoo widgets allow you to insert HTML code into the DOM. In these instance you have to take care to sanitize the data. The widgets in question are:
qx.ui.embed.Html
qx.ui.table.cellrenderer.Html
qx.ui.progressive.renderer.table.cell.Html
qx.ui.virtual.cell.Html
qx.ui.virtual.layer.HtmlCell
qx.ui.virtual.layer.HtmlCellSpan
If you do use qx.html.* and qx.bom.*and qx.dom.* objects to work with the DOM directly, you are beyond the reach of qooxoo and have to take care to act accordingly.
Another important attack vector are authentication cookies. Most of the attacks work by getting the browser to send a request together with the cookie to its server without the user being aware it.
Qooxdoo itself does not require you to use cookies at all. Since qooxdoo applications by design run in a single browser window, you can work without ever using cookies. An easy way of implementing something like this is to have a 'server access singleton' which takes care of all the communication with the backend and supplies the access token in a special header added to every request.
The code below could serve as a guide ... for the cookie problem.
qx.Class.define('myapp.Server', {
extend : qx.io.remote.Rpc,
type : "singleton",
construct : function() {
this.base(arguments);
this.set({
timeout : 60000,
url : 'QX-JSON-RPC/',
serviceName : 'default'
});
},
properties: {
sessionCookie: {
init: null,
nullable: true
}
},
members : {
/**
* override the request creation, to add our 'cookie' header
*/
createRequest: function() {
var req = this.base(arguments);
var cookie = this.getSessionCookie();
if (cookie){
req.setRequestHeader('X-Session-Cookie',this.getSessionCookie());
}
return req;
}
}
});
and if you provide a login popup window in myapp.uiLogin you could replace
the standard callAsync by adding the following to popup a login window if the backend is unhappy with your request.
/**
* A asyncCall handler which tries to
* login in the case of a permission exception.
*
* #param handler {Function} the callback function.
* #param methodName {String} the name of the method to call.
* #return {var} the method call reference.
*/
callAsync : function(handler, methodName) {
var origArguments = arguments;
var origThis = this;
var origHandler = handler;
var that = this;
var superHandler = function(ret, exc, id) {
if (exc && exc.code == 6) {
var login = myapp.uiLogin.getInstance();
login.addListenerOnce('login', function(e) {
var ret = e.getData();
that.setSessionCookie(ret.sessionCookie);
origArguments.callee.base.apply(origThis, origArguments);
});
login.open();
return;
}
origHandler(ret, exc, id);
};
if (methodName != 'login') {
arguments[0] = superHandler;
}
arguments.callee.base.apply(this, arguments);
},
take a look at the CallBackery application to see how this works in a real application.
The issue I have is that people in my group are using a link to an Entry Form to post new itmes to a SharePoint list. Everytime they click 'submit' to post new item, SharPoint redirects them to the list.
I need a solution for SharePoint to direct them to the empty Entry form instead, no matter how many times they need to use it.
Is there such solution? Thanks,
I already have this "/EntryForm.aspx?Source=http://" in the link to the Entry form, but works only 2 times, after that will direct to the list.
Essentially you need to ensure that the Source parameter is always set to EntryForm.aspx so that no matter how often you loop through the form you always get redirected back to a new one at the end. You knew this, but I am just clarifying!
Simplest method would be some javascript to test this source parameter and if its not what you want then redirect the request so it is.
If you can edit the EntryForm.aspx page in SharePoint Designer then add this javascript to the page somewhere:
<script type="text/javascript">
if (gup("ok") != 1) {
if (gup("source") != window.location.href) {
window.location = window.location.href + "?&source=" + window.location.href + "&ok=1";
}
}
function gup( name ){
//This function returns the URL parameter specified
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( results == null )
return "";
else
return results[1];
}
</script>
Essentially this is just redirecting your requests to this page so the source is always itself. The ok parameter is just to ensure that it only does it once.
This is not perfect code, but it demonstrates the idea (and it works!)
gup (Get URL Parameter) function is taken from here and I find it really useful.
Hope it helps
Charlie