- Bootstrap page skeleton with an onload call
- A remote script behind a page button
- Inline contact form with text, mail, tel, textarea and hidden fields
- Modal booking form with date, datetime, time and number pickers
- Preferences form with color, range, password and file upload
- Article editor with CKEditor and a category select
- Filter panel with checkboxes, radios and a multiple select
- Approve button: refresh, alert and JavaScript from one action
- Clickable table rows and a Reload button
- Sales dashboard: KPI cards, bar chart, gauge and sparkline
Bootstrap page skeleton with an onload call
Lay out a sales dashboard page: a container, two rows, a title column with custom HTML and two empty columns that a script fills after the load. The script checks the generated grid and decodes the onload call.
#1. The page HTML is built in [page] by the skeleton helpers; -> "[page]" ""; -> "[on_load_functions]" ""; include "app.100.obj.sajax.skeleton.container.exe" "[id]" "sales" "[name]" "" "[class]" "" "[style]" ""; include "app.100.obj.sajax.skeleton.row.exe" "[id]" "" "[name]" "" "[class]" "" "[style]" ""; include "app.100.obj.sajax.skeleton.col.exe" "[id]" "sales_title" "[name]" "" "[class]" "col-12" "[style]" ""; #Custom HTML inside a column; concat_var "[page]" "<h3>Sales <small>" (date curdate) "</small></h3>"; include "app.100.obj.sajax.skeleton.div.end.exe"; include "app.100.obj.sajax.skeleton.div.end.exe"; include "app.100.obj.sajax.skeleton.row.exe" "[id]" "" "[name]" "" "[class]" "" "[style]" "margin-top:8px"; include "app.100.obj.sajax.skeleton.col.exe" "[id]" "sales_kpi" "[name]" "" "[class]" "col-12 col-lg-8" "[style]" ""; include "app.100.obj.sajax.skeleton.div.end.exe"; include "app.100.obj.sajax.skeleton.col.exe" "[id]" "sales_gauge" "[name]" "" "[class]" "col-12 col-lg-4" "[style]" ""; include "app.100.obj.sajax.skeleton.div.end.exe"; include "app.100.obj.sajax.skeleton.div.end.exe"; include "app.100.obj.sajax.skeleton.div.end.exe"; #2. After the load, the browser runs the script that fills the empty columns, client_data is a JS expression; include "app.100.obj.sajax.skeleton.onload.exe" "[scriptname]" "tuto.p4k.sales.onload.exe" "[client_data]" "({\"region\": \"North\"})" ; #3. Check the HTML: balanced divs, the ids of the empty targets, the decoded onload call; -> "[open]" (string regexp_count [page] "<div"); -> "[close]" (string regexp_count [page] "</div>"); -> "[call_script]" (string decode_b64 (string regexp_extract [on_load_functions] "execute_script\('([^']+)'" 1)); -> "[call_data]" (string decode_b64 (string regexp_extract [on_load_functions] ", '([^']+)'\);" 1)); concat [page] [_n_] "divs: " [open] " open / " [close] " closed" [_n_] "onload: " [on_load_functions] [_n_] "runs " [call_script] " with " [call_data];
<div id='sales' class='container-fluid'><div class='row'><div id='sales_title' class='col-12'><h3>Sales <small>2026-11-01</small></h3></div></div><div class='row' style='margin-top:8px'><div id='sales_kpi' class='col-12 col-lg-8'></div><div id='sales_gauge' class='col-12 col-lg-4'></div></div></div>
divs: 6 open / 6 closed
onload: execute_script('dHV0by5wNGsuc2FsZXMub25sb2FkLmV4ZQ==', 'KHsicmVnaW9uIjogIk5vcnRoIn0p');
runs tuto.p4k.sales.onload.exe with ({"region": "North"})- The onload script then fills
sales_kpiandsales_gaugewith REFRESH commands, see the chart recipe. - Use responsive classes such as
col-12 col-sm-6 col-lgto put 5 cards on one line on large screens only.
A remote script behind a page button
Create the server action a page calls in ajax (one OBJ_OVERWRITE_B64 parameter), put a button on the page that sends a product code and the value of a quantity box, then simulate two clicks and read the alerts.
#1. A remote script: one parameter OBJ_OVERWRITE_B64, it answers with ajax commands; script create exe "tuto.p4k.stock.check" false 1 (param (var "[OBJ_OVERWRITE_B64]" {true} "Parameters in base 64" is_null:false is_empty:false "{}") ) "Check the stock of a product" { json load "tuto_p4k_param" (string decode_b64 [OBJ_OVERWRITE_B64]); -> "[sku]" (json select "tuto_p4k_param" "/sku"); -> "[qty]" (json select "tuto_p4k_param" "/qty"); json unload "tuto_p4k_param"; -> "[left]" (- 40 [qty]); if (>= [left] 0) { include "app.100.obj.sajax.alert.exe" "[type]" "ALERT_SUCCESS" "[strong]" "In stock" "[msg]" (concat [qty] " x " [sku] " reserved, " [left] " left."); } { include "app.100.obj.sajax.alert.exe" "[type]" "ALERT_WARNING" "[strong]" "Short" "[msg]" (concat "Only 40 " [sku] " available."); }; } "Ajax commands"; #2. The page: a button whose onclick sends the sku and the value of the box qty_box; -> "[page]" "<input id='qty_box' type='number' value='3'>"; concat_var "[page]" "<button class='btn btn-primary' onclick=" (include "app.100.obj.sajax.client.exe" "[scriptname]" "tuto.p4k.stock.check.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"sku\": \"BOLT-M8\"}") "[getElementById]" "qty=qty_box" "[data]" "" ) ">Check stock</button>"; #3. Simulate the clicks: the browser adds qty to the object, then includes the script; json load "result" "[]"; include "tuto.p4k.stock.check.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"sku\": \"BOLT-M8\", \"qty\": 3}"); include "tuto.p4k.stock.check.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"sku\": \"BOLT-M8\", \"qty\": 55}"); -> "[answers]" (string regexp_replace (json doc "result") "\n *" ""); json unload "result"; json unload "o"; script delete "tuto.p4k.stock.check.exe"; concat [page] [_n_] "Answers: " [answers] [_n_] "Script left: " (script exist "tuto.p4k.stock.check.exe");
<input id='qty_box' type='number' value='3'><button class='btn btn-primary' onclick="execute_script('dHV0by5wNGsuc3RvY2suY2hlY2suZXhl', 'eyJza3UiOiAiQk9MVC1NOCJ9', 'qty=qty_box', '');return false;">Check stock</button>
Answers: [{"msg": "3 x BOLT-M8 reserved, 37 left.","strong": "In stock","type": "ALERT_SUCCESS"},{"msg": "Only 40 BOLT-M8 available.","strong": "Short","type": "ALERT_WARNING"}]
Script left: 0[getElementById]takeskey=elementIdpairs separated by commas: the browser copies each element value into the object before the call.- Grant the action to the group of the app users with
group grant script.
Inline contact form with text, mail, tel, textarea and hidden fields
Generate a contact form shown inside a page container, with a hidden source field, required name, e-mail and message, and an optional phone with a pattern. The script parses the generated HTML to list the fields and the Send button call.
#1. A contact form shown inline in the container contact_box; -> "[app_lang]" "en"; -> "[object]" ""; json load "result" "[]"; include "app.100.obj.form.begin.exe" "[form_id]" "form_contact" "[modal]" false "[action]" "index.jsp" "[enctype]" false "[title]" "Contact us" "[subTitle]" "answer within 24 h" "[method]" "post" "[widthIfModal]" "modal-lg" "[html]" ""; include "app.100.obj.form.control.hidden.exe" "[control_id]" "source_page" "[value]" "pricing"; include "app.100.obj.form.control.textbox.text.exe" "[control_id]" "full_name" "[class]" "col-12 col-md-6" "[label]" "Full name" "[description]" "" "[maxlength]" "80" "[placeholder]" "Jane O'Neil" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" ""; include "app.100.obj.form.control.textbox.mail.exe" "[control_id]" "email" "[class]" "col-12 col-md-6" "[label]" "E-mail" "[description]" "We never share it" "[maxlength]" "120" "[placeholder]" "name@company.com" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" ""; include "app.100.obj.form.control.textbox.tel.exe" "[control_id]" "phone" "[class]" "col-12 col-md-6" "[label]" "Phone" "[description]" "" "[maxlength]" "20" "[placeholder]" "+262 692 00 00 00" "[readonly]" false "[required]" false "[disabled]" false "[html]" "pattern='[+0-9 ]+'" "[value]" ""; include "app.100.obj.form.control.textarea.exe" "[control_id]" "message" "[class]" "col-12" "[label]" "Message" "[description]" "2000 characters max" "[maxlength]" "2000" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "rows='5'" "[value]" ""; include "app.100.obj.form.end.exe" "[form_id]" "form_contact" "[modal]" false "[OBJ_OVERWRITE_B64]" (string encode_b64 "{}") "[container_id]" "contact_box" "[mql_script]" "tuto.p4k.contact.send.exe" "[data_eval]" "$('#form_contact').serializeObject()" "[closeTitle]" "Close" "[submitTitle]" "Send" "[submitType]" "success"; #2. The answer is one REFRESH: parse its HTML and list the fields; -> "[html]" (json select "result" "/[0]/innerHtml"); html load_from_str "tuto_p4k_dom" [html]; -> "[fields]" ""; html parse "tuto_p4k_dom" "tuto_p4k_el" "SELECT" "input,textarea" "" { concat_var "[fields]" [_n_] " " (json select "tuto_p4k_el" "/attributes/name") " <" (json select "tuto_p4k_el" "/tagName") " " (json select "tuto_p4k_el" "/attributes/type") ">" (if (> (string regexp_count (json select "tuto_p4k_el" "/outerHtml") " required") 0) {" required"} {""}); }; -> "[button]" ""; html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "button" "" { -> "[button]" (concat (json select "tuto_p4k_el" "/text") " -> " (json select "tuto_p4k_el" "/attributes/onclick")); }; html close "tuto_p4k_dom"; json unload "tuto_p4k_el"; -> "[out]" (concat (json select "result" "/[0]/type") " of #" (json select "result" "/[0]/target") ", " (string length [html]) " chars" [_n_] "Fields:" [fields] [_n_] "Button: " [button]); json unload "result"; json unload "o"; [out];
REFRESH of #contact_box, 2348 chars
Fields:
source_page <input hidden>
full_name <input text> required
email <input email> required
phone <input tel>
message <textarea text> required
Button: Send -> if (check_form_values('form_contact')) {execute_script('dHV0by5wNGsuY29udGFjdC5zZW5kLmV4ZQ==', 'e30=', '', 'JCgnI2Zvcm1fY29udGFjdCcpLnNlcmlhbGl6ZU9iamVjdCgp');$('#modal_form_contact').modal('hide');};return false;- The Send button first runs
check_form_values: empty required fields get a red border and nothing is sent. - Use
_app_sajax_3_in_loading___form_control_textbox_textwith[readonly]true to show a value the user cannot change.
Modal booking form with date, datetime, time and number pickers
Book a meeting room in a modal form whose date pickers follow the language of the user: dd/MM/yyyy in French, yyyy-MM-dd in English. The default slot is prefilled with date functions, and the summary shows the ajax commands, the picker formats and the values.
#1. Default slot proposed to the user; -> "[start]" "2026-11-16 09:30:00"; -> "[out]" ""; json load "tuto_p4k_langs" "[\"fr\", \"en\"]"; json parse_array "tuto_p4k_langs" "/" "[app_lang]" { #2. A modal booking form, date pickers in the format of the language; -> "[object]" ""; -> "[app_object_id]" 0; json load "result" "[]"; include "app.100.obj.form.begin.exe" "[form_id]" "form_room" "[modal]" true "[action]" "index.jsp" "[enctype]" false "[title]" "Book a meeting room" "[subTitle]" "Room B12" "[method]" "post" "[widthIfModal]" "modal-lg" "[html]" ""; if (equal [app_lang] "fr") { include "app.100.obj.form.control.textbox.date_fr.exe" "[control_id]" "day" "[class]" "col-6" "[label]" "Day" "[description]" "" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" (date format [start] "yyyy-MM-dd HH:mm:ss" "dd/MM/yyyy"); include "app.100.obj.form.control.textbox.datetime_fr.exe" "[control_id]" "reminder" "[class]" "col-6" "[label]" "Reminder" "[description]" "" "[placeholder]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "" "[value]" (date format (date addt [start] "HOUR" -1) "yyyy-MM-dd HH:mm:ss" "dd/MM/yyyy HH:mm:ss"); } { include "app.100.obj.form.control.textbox.date_en.exe" "[control_id]" "day" "[class]" "col-6" "[label]" "Day" "[description]" "" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" (string left [start] 10); include "app.100.obj.form.control.textbox.datetime_en.exe" "[control_id]" "reminder" "[class]" "col-6" "[label]" "Reminder" "[description]" "" "[placeholder]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "" "[value]" (date addt [start] "HOUR" -1); }; include "app.100.obj.form.control.textbox.time.exe" "[control_id]" "start_time" "[class]" "col-6" "[label]" "Start" "[description]" "" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" (string right [start] 8); include "app.100.obj.form.control.textbox.number.exe" "[control_id]" "hours" "[class]" "col-6" "[label]" "Duration in hours" "[description]" "" "[maxlength]" "" "[min]" 0.5 "[max]" 8 "[step]" 0.5 "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" 1.5; include "app.100.obj.form.end.exe" "[form_id]" "form_room" "[modal]" true "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"room\": \"B12\"}") "[container_id]" "" "[mql_script]" "tuto.p4k.room.book.exe" "[data_eval]" "$('#form_room').serializeObject()" "[closeTitle]" "Cancel" "[submitTitle]" "Book" "[submitType]" "primary"; #3. Summary: the commands, the picker formats, the prefilled values; -> "[html]" (json select "result" "/[0]/innerHtml"); -> "[formats]" (string regexp_replace (string regexp_replace [html] "(?s).*?format : '([^']+)'" "$1 | ") "(?s)\| [^|]*$" ""); html load_from_str "tuto_p4k_dom" [html]; -> "[values]" ""; html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "input" "" { concat_var "[values]" (json select "tuto_p4k_el" "/attributes/name") "=" (json select "tuto_p4k_el" "/attributes/value") (if (is null or empty (json select "tuto_p4k_el" "/attributes/id")) {" -no id-"} {""}) " "; }; html close "tuto_p4k_dom"; concat_var "[out]" "[" [app_lang] "] " (json select "result" "/[0]/type") " target='" (json select "result" "/[0]/target") "' then " (json select "result" "/[1]/type") " " (json select "result" "/[1]/javascript") [_n_] " pickers: " [formats] [_n_] " values: " (string trim [values]) [_n_]; }; json unload "tuto_p4k_langs"; json unload "tuto_p4k_el"; json unload "result"; json unload "o"; [out];
[fr] REFRESH target='' then JAVASCRIPT object_onload1(); pickers: DD/MM/YYYY | DD/MM/YYYY HH:mm:ss | HH:mm:ss values: day=16/11/2026 reminder=16/11/2026 08:30:00 -no id- start_time=09:30:00 hours=1.5 [en] REFRESH target='' then JAVASCRIPT object_onload1(); pickers: YYYY-MM-DD | YYYY-MM-DD HH:mm:ss | HH:mm:ss values: day=2026-11-16 reminder=2026-11-16 08:30:00 start_time=09:30:00 hours=1.5
- The datetime_fr control writes no id on its input: read it by its name, or through the form serialization, not with
[getElementById]. - A modal form needs
[app_object_id]set before: each modal gets its ownobject_onloadNfunction.
Preferences form with color, range, password and file upload
Build a user preferences form with a theme color, a font size slider, a new password and an avatar upload (multipart form), then check on the server side the values the browser posts without ever echoing the password.
#1. A preferences form with an upload, so enctype is multipart; -> "[app_lang]" "en"; -> "[object]" ""; json load "result" "[]"; include "app.100.obj.form.begin.exe" "[form_id]" "form_prefs" "[modal]" false "[action]" "upload.jsp" "[enctype]" true "[title]" "My preferences" "[subTitle]" "" "[method]" "post" "[widthIfModal]" "modal-lg" "[html]" ""; include "app.100.obj.form.control.textbox.color.exe" "[control_id]" "theme_color" "[class]" "col-4" "[label]" "Theme color" "[description]" "" "[maxlength]" "7" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" "#FFB547"; include "app.100.obj.form.control.textbox.range.exe" "[control_id]" "font_size" "[class]" "col-8" "[label]" "Font size" "[description]" "12 to 20 px" "[min]" 12 "[max]" 20 "[step]" 1 "[placeholder]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "oninput='font_px.value=this.value'" "[value]" 14; include "app.100.obj.form.control.textbox.password.exe" "[control_id]" "new_pwd" "[class]" "col-6" "[label]" "New password" "[description]" "10 characters, one digit" "[maxlength]" "64" "[placeholder]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "autocomplete='new-password'" "[value]" ""; include "app.100.obj.form.control.textbox.file.exe" "[control_id]" "avatar" "[class]" "col-6" "[label]" "Avatar" "[description]" "PNG or JPG" "[maxlength]" "255" "[placeholder]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "accept='.png,.jpg'" "[value]" ""; include "app.100.obj.form.end.exe" "[form_id]" "form_prefs" "[modal]" false "[OBJ_OVERWRITE_B64]" (string encode_b64 "{}") "[container_id]" "prefs_box" "[mql_script]" "tuto.p4k.prefs.save.exe" "[data_eval]" "$('#form_prefs').serializeObject()" "[closeTitle]" "Close" "[submitTitle]" "Save" "[submitType]" "primary"; #2. Read the form tag and each input as the browser will see it; html load_from_str "tuto_p4k_dom" (json select "result" "/[0]/innerHtml"); html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "form" "" { -> "[form]" (concat (json select "tuto_p4k_el" "/attributes/method") " " (json select "tuto_p4k_el" "/attributes/action") " " (json select "tuto_p4k_el" "/attributes/enctype")); }; -> "[inputs]" ""; html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "input" "" { concat_var "[inputs]" [_n_] " " (json select "tuto_p4k_el" "/attributes/type") " " (json select "tuto_p4k_el" "/attributes/name") " value='" (json select "tuto_p4k_el" "/attributes/value") "'" (if (equal (json select "tuto_p4k_el" "/attributes/type") "range") {concat " " (json select "tuto_p4k_el" "/attributes/min") ".." (json select "tuto_p4k_el" "/attributes/max")} {""}); }; html close "tuto_p4k_dom"; json unload "tuto_p4k_el"; #3. Server side: validate what the browser posts, never echo the password; json load "tuto_p4k_post" "{\"theme_color\": \"#1E88E5\", \"font_size\": \"16\", \"new_pwd\": \"Lagoon2026x\"}"; -> "[pwd]" (json select "tuto_p4k_post" "/new_pwd"); -> "[checks]" (concat "color ok: " (string regexp_count (json select "tuto_p4k_post" "/theme_color") "^#[0-9A-Fa-f]{6}$") ", size ok: " (and (>= (json select "tuto_p4k_post" "/font_size") 12) (<= (json select "tuto_p4k_post" "/font_size") 20)) ", password ok: " (and (>= (string length [pwd]) 10) (> (string regexp_count [pwd] "[0-9]") 0)) ", salted hash: " (string left (string sha_256 (concat "jane.doe:" [pwd])) 12) "..."); json unload "tuto_p4k_post"; json unload "result"; json unload "o"; concat "Form: " [form] [_n_] "Inputs:" [inputs] [_n_] [checks];
Form: post upload.jsp multipart/form-data Inputs: color theme_color value='#FFB547' range font_size value='14' 12..20 password new_pwd value='' file avatar value='' color ok: 1, size ok: 1, password ok: 1, salted hash: c84f49f69990...
[html]adds any attribute to the input:accept,autocomplete,oninput,pattern...- Store a password with
string sha_512and a per-user salt, never in clear text.
Article editor with CKEditor and a category select
Open an existing news article in a modal editor: a title box, a single-choice category select with codes and labels, and a CKEditor rich text body. The script shows the selected option, how the body is escaped for the textarea, and cleans the HTML the editor posts back.
#1. Edit an existing news article: its current values; -> "[title]" "Port closed on Friday"; -> "[body]" "<p>The <b>port</b> of Le Port is closed on Friday.</p>"; -> "[category]" "ops"; #2. Modal editor: title, category select, rich text body; -> "[app_lang]" "en"; -> "[object]" ""; -> "[app_object_id]" 0; json load "result" "[]"; include "app.100.obj.form.begin.exe" "[form_id]" "form_news" "[modal]" true "[action]" "index.jsp" "[enctype]" false "[title]" "Edit article" "[subTitle]" "#118" "[method]" "post" "[widthIfModal]" "modal-xl" "[html]" ""; include "app.100.obj.form.control.textbox.text.exe" "[control_id]" "news_title" "[class]" "col-8" "[label]" "Title" "[description]" "" "[maxlength]" "120" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" [title]; include "app.100.obj.form.control.select_mono.exe" "[control_id]" "news_cat" "[class]" "col-4" "[label]" "Category" "[description]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[inValues]" "[\"hr\", \"ops\", \"it\"]" "[optionValues]" "[\"Human resources\", \"Operations\", \"IT\"]" "[value]" [category]; include "app.100.obj.form.control.textarea_cke.exe" "[control_id]" "news_body" "[class]" "col-12" "[label]" "Body" "[description]" "" "[maxlength]" "20000" "[placeholder]" "" "[readonly]" false "[required]" true "[disabled]" false "[html]" "" "[value]" [body]; include "app.100.obj.form.end.exe" "[form_id]" "form_news" "[modal]" true "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"news_id\": 118}") "[container_id]" "" "[mql_script]" "tuto.p4k.news.save.exe" "[data_eval]" "$('#form_news').serializeObject()" "[closeTitle]" "Cancel" "[submitTitle]" "Publish" "[submitType]" "success"; #3. What the browser gets: the selected option, the escaped body, the CKEditor init; html load_from_str "tuto_p4k_dom" (json select "result" "/[0]/innerHtml"); -> "[options]" ""; html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "option" "" { concat_var "[options]" (json select "tuto_p4k_el" "/attributes/value") "=" (json select "tuto_p4k_el" "/text") (if (> (string regexp_count (json select "tuto_p4k_el" "/outerHtml") "selected") 0) {" *"} {""}) ", "; }; html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "textarea" "" { -> "[raw]" (string regexp_extract (json select "tuto_p4k_el" "/outerHtml") ">(.*)</textarea>" 1); -> "[text]" (json select "tuto_p4k_el" "/text"); }; html close "tuto_p4k_dom"; -> "[cke]" (string regexp_extract (json select "result" "/[0]/innerHtml") "CKEDITOR\.replace\( '[a-z_]+'" 0); #4. Server side on Publish: strip scripts and inline handlers from the posted HTML; -> "[posted]" "<p>Closed on <b>Friday</b>.</p><script>steal()</script><img src=x onerror='steal()'>"; -> "[clean]" (string regexp_replace (string regexp_replace [posted] "(?is)<script.*?</script>" "") "(?i) on[a-z]+='[^']*'" ""); json unload "tuto_p4k_el"; json unload "inValues"; json unload "optionValues"; json unload "result"; json unload "o"; concat "Options: " (string regexp_replace [options] ", $" "") [_n_] "Textarea HTML: " [raw] [_n_] "Editor sees: " [text] [_n_] "Init: " [cke] " ..." [_n_] "Posted cleaned: " [clean];
Options: hr=Human resources, ops=Operations *, it=IT Textarea HTML: <p>The <b>port</b> of Le Port is closed on Friday.</p> Editor sees: <p>The <b>port</b> of Le Port is closed on Friday.</p> Init: CKEDITOR.replace( 'news_body' ... Posted cleaned: <p>Closed on <b>Friday</b>.</p><img src=x>
- The helpers load the json keys inValues and optionValues: unload them at the end of the page.
- For a real sanitizer, keep a whitelist of tags with
html parserather than a blacklist of regular expressions.
Filter panel with checkboxes, radios and a multiple select
Build the filter panel of a ticket list with every choice control: status checkboxes inline, priority radios inline, a multiple select of teams, sort radios one per line and column checkboxes one per line, preselected from the saved filter of the user. The summary reads back each fieldset: kind, number of choices and what is on.
#1. Filter panel of a ticket list, the saved filter of the user preselects values; -> "[app_lang]" "en"; -> "[object]" ""; json load "result" "[]"; include "app.100.obj.form.begin.exe" "[form_id]" "form_filter" "[modal]" false "[action]" "index.jsp" "[enctype]" false "[title]" "Filter tickets" "[subTitle]" "" "[method]" "post" "[widthIfModal]" "modal-lg" "[html]" ""; include "app.100.obj.form.control.checkbox_inline.exe" "[control_id]" "status" "[class]" "col-12" "[label]" "Status" "[description]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "" "[inValues]" "[\"open\", \"pending\", \"closed\"]" "[optionValues]" "[\"Open\", \"Pending\", \"Closed\"]" "[values]" "{\"open\": 1, \"pending\": 1}"; include "app.100.obj.form.control.radio_inline.exe" "[control_id]" "priority" "[class]" "col-12" "[label]" "Priority" "[description]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "" "[inValues]" "[\"any\", \"P1\", \"P2\", \"P3\"]" "[optionValues]" "[\"Any\", \"P1\", \"P2\", \"P3\"]" "[value]" "P1"; include "app.100.obj.form.control.select_multiple.exe" "[control_id]" "teams" "[class]" "col-6" "[label]" "Teams" "[description]" "Ctrl+click for several" "[readonly]" false "[required]" false "[disabled]" false "[html]" "size='4'" "[inValues]" "[\"net\", \"db\", \"web\", \"desk\"]" "[optionValues]" "[\"Network\", \"Database\", \"Web\", \"Service desk\"]" "[values]" "{\"db\": 1, \"web\": 1}"; include "app.100.obj.form.control.radio_line.exe" "[control_id]" "sort" "[class]" "col-6" "[label]" "Sort by" "[description]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "" "[inValues]" "[\"created\", \"updated\", \"sla\"]" "[optionValues]" "[\"Creation date\", \"Last update\", \"SLA left\"]" "[value]" "sla"; include "app.100.obj.form.control.checkbox_line.exe" "[control_id]" "cols" "[class]" "col-12" "[label]" "Columns" "[description]" "" "[readonly]" false "[required]" false "[disabled]" false "[html]" "" "[inValues]" "[\"id\", \"subject\", \"owner\", \"sla\"]" "[optionValues]" null "[values]" "{\"id\": 1, \"subject\": 1, \"sla\": 1}"; include "app.100.obj.form.end.exe" "[form_id]" "form_filter" "[modal]" false "[OBJ_OVERWRITE_B64]" (string encode_b64 "{}") "[container_id]" "filter_box" "[mql_script]" "tuto.p4k.tickets.list.exe" "[data_eval]" "$('#form_filter').serializeObject()" "[closeTitle]" "Close" "[submitTitle]" "Apply" "[submitType]" "primary"; #2. Summary per control: kind and preselected values; html load_from_str "tuto_p4k_dom" (json select "result" "/[0]/innerHtml"); -> "[sum]" "Filter panel, one fieldset per control:"; html parse "tuto_p4k_dom" "tuto_p4k_fs" "TAG" "fieldset" "" { html load_from_str "tuto_p4k_sub" (json select "tuto_p4k_fs" "/html"); -> "[on]" ""; html parse "tuto_p4k_sub" "tuto_p4k_el" "SELECT" "input[checked],option[selected]" "" { concat_var "[on]" (json select "tuto_p4k_el" "/attributes/value") " "; }; -> "[h]" (json select "tuto_p4k_fs" "/html"); -> "[kind]" (concat (case (> (string regexp_count [h] "type=\"radio\"") 0) {"radio"} (> (string regexp_count [h] "type=\"checkbox\"") 0) {"checkbox"} {"select multiple"};) (if (> (string regexp_count [h] "form-check-inline") 0) {" inline"} {""})); -> "[label]" ""; html parse "tuto_p4k_sub" "tuto_p4k_el" "TAG" "label" "" { if (is empty [label]) {-> "[label]" (json select "tuto_p4k_el" "/text");}; }; concat_var "[sum]" [_n_] " " [label] ": " [kind] ", " (string regexp_count [h] "<(input|option) ") " choices, on: " (string trim [on]); html close "tuto_p4k_sub"; }; html close "tuto_p4k_dom"; json unload "tuto_p4k_fs"; json unload "tuto_p4k_el"; json unload "inValues"; json unload "optionValues"; json unload "values"; json unload "result"; json unload "o"; [sum];
Filter panel, one fieldset per control: Status: checkbox inline, 3 choices, on: open pending Priority: radio inline, 4 choices, on: P1 Teams: select multiple, 4 choices, on: db web Sort by: radio, 3 choices, on: sla Columns: checkbox, 4 choices, on: id subject sla
- Single values use
[value]; multiple values use[values], a JSON object whose keys are the selected values. [optionValues]null shows the raw values as labels, like the Columns control.- The helpers use the loop variable
[i]: do not wrap them in a loop on[i]in your page.
Approve button: refresh, alert and JavaScript from one action
Put an Approve button into the actions container of an invoice page while it loads, and write its server action: under 1500 EUR or for a manager it refreshes the status badge, shows a success alert and disables the button with JavaScript; otherwise it only warns.
#1. The action of the Approve button: refresh the status, alert, run some JavaScript; script create exe "tuto.p4k.invoice.approve" false 1 (param (var "[OBJ_OVERWRITE_B64]" {true} "Parameters in base 64" is_null:false is_empty:false "{}") ) "Approve an invoice" { json load "tuto_p4k_inv" (string decode_b64 [OBJ_OVERWRITE_B64]); -> "[inv]" (json select "tuto_p4k_inv" "/invoice"); -> "[amount]" (json select "tuto_p4k_inv" "/amount"); -> "[role]" (json select "tuto_p4k_inv" "/role"); json unload "tuto_p4k_inv"; if (or (<= [amount] 1500) (equal [role] "manager")) { include "app.100.obj.sajax.refresh.exe" "[target]" "inv_status" "[innerHtml]" (concat "<span class='badge badge-success'>Approved " (date curdate) "</span>"); include "app.100.obj.sajax.alert.exe" "[type]" "ALERT_SUCCESS" "[strong]" [inv] "[msg]" (concat "approved, " (math round [amount] 2) " EUR sent to payment."); include "app.100.obj.sajax.javascript.exe" "[javascript]" "$('#inv_actions button').prop('disabled', true);"; } { include "app.100.obj.sajax.alert.exe" "[type]" "ALERT_WARNING" "[strong]" [inv] "[msg]" "over 1500 EUR: a manager must approve it."; }; } "Ajax commands"; #2. While the page loads: put the Approve button into the container inv_actions; json load "result" "[]"; include "app.100.obj.sajax.button.exe" "[container_id]" "inv_actions" "[submitTitle]" "Approve" "[submitType]" "success" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"invoice\": \"INV-2026-0412\", \"amount\": 1840.5}") "[mql_script]" "tuto.p4k.invoice.approve.exe" "[data_eval]" "" "[html]" "style='min-width:120px'" ; -> "[load]" (concat (json select "result" "/[0]/type") " #" (json select "result" "/[0]/target") ": " (json select "result" "/[0]/innerHtml")); #3. A clerk clicks, then a manager clicks; json load "result" "[]"; include "tuto.p4k.invoice.approve.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"invoice\": \"INV-2026-0412\", \"amount\": 1840.5, \"role\": \"clerk\"}"); -> "[clerk]" (concat (json count "result" "/") " command: " (json select "result" "/[0]/type") " " (json select "result" "/[0]/msg")); json load "result" "[]"; include "tuto.p4k.invoice.approve.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"invoice\": \"INV-2026-0412\", \"amount\": 1840.5, \"role\": \"manager\"}"); -> "[manager]" ""; for (-> "[k]" 0) (< [k] (json count "result" "/")) (++ "[k]") { -> "[c]" (concat "/[" [k] "]/"); concat_var "[manager]" [_n_] " " (json select "result" (concat [c] "type")) " " (case (equal (json select "result" (concat [c] "type")) "REFRESH") {concat "#" (json select "result" (concat [c] "target")) " <- " (json select "result" (concat [c] "innerHtml"))} (equal (json select "result" (concat [c] "type")) "JAVASCRIPT") {json select "result" (concat [c] "javascript")} {concat (json select "result" (concat [c] "strong")) " " (json select "result" (concat [c] "msg"))};); }; json unload "result"; json unload "o"; script delete "tuto.p4k.invoice.approve.exe"; concat "On load: " [load] [_n_] "Clerk click: " [clerk] [_n_] "Manager click:" [manager];
On load: REFRESH #inv_actions: <button type='submit' style='min-width:120px' class='btn btn-success' onClick="execute_script('dHV0by5wNGsuaW52b2ljZS5hcHByb3ZlLmV4ZQ==', 'eyJpbnZvaWNlIjogIklOVi0yMDI2LTA0MTIiLCAiYW1vdW50IjogMTg0MC41fQ==', '', '');return false;">Approve</button>
Clerk click: 1 command: ALERT_WARNING over 1500 EUR: a manager must approve it.
Manager click:
REFRESH #inv_status <- <span class='badge badge-success'>Approved 2026-11-01</span>
ALERT_SUCCESS INV-2026-0412 approved, 1840.5 EUR sent to payment.
JAVASCRIPT $('#inv_actions button').prop('disabled', true);- In production, read the role from the session with
app is_granted_ainstead of trusting a value sent by the browser. - The browser runs the commands in the order of the result array: refresh first, then the JavaScript that needs the new HTML.
Clickable table rows and a Reload button
While a page loads, refresh a container with an orders table whose rows call a detail action on click, and a toolbar with a Reload button that also sends the value of a status filter. The script then reads the onclick of the second row and replays the call exactly as the browser sends it.
#1. The detail action: shows one order in the panel order_detail; script create exe "tuto.p4k.order.detail" false 1 (param (var "[OBJ_OVERWRITE_B64]" {true} "Parameters in base 64" is_null:false is_empty:false "{}") ) "Detail of an order" { json load "tuto_p4k_od" (string decode_b64 [OBJ_OVERWRITE_B64]); include "app.100.obj.sajax.refresh.exe" "[target]" "order_detail" "[innerHtml]" (concat "<h5>Order " (json select "tuto_p4k_od" "/id") "</h5>" (string html_escape (json select "tuto_p4k_od" "/customer")) " - " (json select "tuto_p4k_od" "/total") " EUR"); json unload "tuto_p4k_od"; } "Ajax commands"; #2. While loading: a table, each row has an onclick event that calls the action; json load "tuto_p4k_orders" "[{\"id\": 5101, \"customer\": \"Hoarau SARL\", \"total\": 320}, {\"id\": 5102, \"customer\": \"Payet & fils\", \"total\": 1290.4}]"; -> "[rows]" ""; json parse_array "tuto_p4k_orders" "/" "[o]" { json load "tuto_p4k_row" [o]; concat_var "[rows]" "<tr style='cursor:pointer' onclick=" (include "app.100.obj.sajax.client.exe" "[scriptname]" "tuto.p4k.order.detail.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 [o]) "[getElementById]" "" "[data]" "" ) "><td>" (json select "tuto_p4k_row" "/id") "</td><td>" (string html_escape (json select "tuto_p4k_row" "/customer")) "</td></tr>"; }; json load "result" "[]"; include "app.100.obj.sajax.refresh.exe" "[target]" "orders_list" "[innerHtml]" (concat "<table class='table table-hover'>" [rows] "</table>"); #3. And a Reload button refreshed into the toolbar; include "app.100.obj.sajax.refresh.exe" "[target]" "orders_toolbar" "[innerHtml]" (concat "<button class='btn btn-light' onclick=" (include "app.100.obj.sajax.client.exe" "[scriptname]" "tuto.p4k.orders.list.exe" "[OBJ_OVERWRITE_B64]" (string encode_b64 "{\"page\": 1}") "[getElementById]" "status=status_filter" "[data]" "" ) ">Reload</button>") ; -> "[loading]" (concat (json count "result" "/") " commands: " (json select "result" "/[0]/type") " #" (json select "result" "/[0]/target") ", " (json select "result" "/[1]/type") " #" (json select "result" "/[1]/target")); #4. Replay the click on the second row exactly as the browser sends it; html load_from_str "tuto_p4k_dom" (json select "result" "/[0]/innerHtml"); -> "[onclick]" ""; html parse "tuto_p4k_dom" "tuto_p4k_el" "TAG" "tr" "" { -> "[onclick]" (json select "tuto_p4k_el" "/attributes/onclick"); }; html close "tuto_p4k_dom"; -> "[script]" (string decode_b64 (string regexp_extract [onclick] "execute_script\('([^']*)'" 1)); -> "[b64]" (string regexp_extract [onclick] "execute_script\('[^']*', '([^']*)'" 1); json load "result" "[]"; include [script] "[OBJ_OVERWRITE_B64]" [b64]; -> "[click]" (concat (json select "result" "/[0]/type") " #" (json select "result" "/[0]/target") " <- " (json select "result" "/[0]/innerHtml")); json unload "result"; json unload "o"; json unload "tuto_p4k_orders"; json unload "tuto_p4k_row"; json unload "tuto_p4k_el"; script delete "tuto.p4k.order.detail.exe"; concat "While loading: " [loading] [_n_] "Row 2 onclick: " [onclick] [_n_] "Browser sends: include \"" [script] "\" with " (string decode_b64 [b64]) [_n_] "Answer: " [click];
While loading: 2 commands: REFRESH #orders_list, REFRESH #orders_toolbar
Row 2 onclick: execute_script('dHV0by5wNGsub3JkZXIuZGV0YWlsLmV4ZQ==', 'eyJ0b3RhbCI6MTI5MC40LCJpZCI6NTEwMiwiY3VzdG9tZXIiOiJQYXlldCAmIGZpbHMifQ==', '', '');return false;
Browser sends: include "tuto.p4k.order.detail.exe" with {"total":1290.4,"id":5102,"customer":"Payet & fils"}
Answer: REFRESH #order_detail <- <h5>Order 5102</h5>Payet & fils - 1290.4 EUR- The payload of each row is visible in the page (base 64 is not encryption): send only ids and check the rights again in the action.
- Add
[getElementById]pairs such asstatus=status_filterto send the current value of a field with the click.
Sales dashboard: KPI cards, bar chart, gauge and sparkline
Fill the columns of a sales dashboard while it loads: two KPI cards, a Chart.js bar chart of the monthly sales, a gauge of the yearly target reached and a trend sparkline, all from data computed in MQL. The summary lists the ajax commands and the library each one uses.
#1. Monthly sales of the year and the yearly target; json load "tuto_p4k_sales" "[42100, 39800, 45250, 47900]"; -> "[goal_eur]" 230000; -> "[done]" (+ 42100 39800 45250 47900); -> "[pct]" (math round (* (/ [done] [goal_eur]) 100) 1); #2. Onload of the dashboard, each object sends a REFRESH of its column - they set the variable target, so do not use that name for your own data; -> "[app_object_id]" 0; -> "[app_object_id_no_load]" 0; json load "result" "[]"; -> "[kpi_data]" (concat "[{\"class\": \"col-6\", \"title\": \"Sales to date\", \"titleColor\": \"#857B77\", \"value\": \"" [done] " EUR\", \"valueColor\": \"#C9731F\", \"faIcon\": \"fa-euro\"}, {\"class\": \"col-6\", \"title\": \"Best month\", \"titleColor\": \"#857B77\", \"value\": \"April\", \"valueColor\": \"#8C2F28\", \"faIcon\": \"fa-trophy\"}]"); include "app.100.obj.kpi.exe" "[container_id]" "sales_kpi"; -> "[chart_labels]" "[\"Jan\", \"Feb\", \"Mar\", \"Apr\"]"; -> "[chart_title]" "Sales by month"; -> "[chart_data]" (concat "[{\"label\": \"2026\", \"backgroundColor\": \"#FBE3B8\", \"borderColor\": \"#C9731F\", \"borderWidth\": 1, \"data\": " (string regexp_replace (json doc "tuto_p4k_sales") "\s+" "") "}]"); include "app.100.obj.chartjs.bar_vertical.exe" "[container_id]" "sales_bar"; -> "[gauge_value]" [pct]; -> "[gauge_title]" "Yearly target"; -> "[gauge_unit]" "%"; include "app.100.obj.gauge_meter.exe" "[container_id]" "sales_gauge"; include "app.100.obj.sparkline.exe" "[container_id]" "sales_trend" "[type]" "line"; #3. Summary of the answer: one line per command, with the chart library it uses; -> "[sum]" ""; for (-> "[k]" 0) (< [k] (json count "result" "/")) (++ "[k]") { -> "[c]" (concat "/[" [k] "]/"); -> "[t]" (json select "result" (concat [c] "type")); -> "[h]" (if (equal [t] "REFRESH") {json select "result" (concat [c] "innerHtml")} {json select "result" (concat [c] "javascript")}); concat_var "[sum]" [_n_] " " [t] " " (if (equal [t] "REFRESH") {concat "#" (json select "result" (concat [c] "target")) ", " (string length [h]) " chars, " (case (> (string regexp_count [h] "new Chart") 0) {"Chart.js"} (> (string regexp_count [h] "JustGage") 0) {"JustGage"} (> (string regexp_count [h] "sparkline") 0) {"Sparkline"} {"HTML"};)} {[h]}); }; -> "[bar]" (string regexp_extract (json select "result" "/[1]/innerHtml") "datasets: (.*)" 1); -> "[gauge]" (string regexp_extract (json select "result" "/[3]/innerHtml") "value: ([0-9.]+)" 1); -> "[left]" (env exist var "[chart_data]"); json unload "tuto_p4k_sales"; json unload "result"; json unload "o"; json unload "data"; concat "Sales " [done] " EUR = " [pct] "% of " [goal_eur] [_n_] "Commands:" [sum] [_n_] "Bar datasets: " [bar] [_n_] "Gauge value: " [gauge] ", chart_data still set: " [left];
Sales 175050 EUR = 76.1% of 230000
Commands:
REFRESH #sales_kpi, 766 chars, HTML
REFRESH #sales_bar, 1252 chars, Chart.js
JAVASCRIPT object_onload1();
REFRESH #sales_gauge, 693 chars, JustGage
REFRESH #sales_trend, 1087 chars, Sparkline
Bar datasets: [{"label": "2026", "backgroundColor": "#FBE3B8", "borderColor": "#C9731F", "borderWidth": 1, "data": [42100,39800,45250,47900]}]
Gauge value: 76.1, chart_data still set: 0- Every chart object of
_app_sajax_3_in_loading___import_all_chartreads[chart_data],[chart_labels],[chart_title]and deletes them after use, so set them again before each chart. - Chart.js objects answer REFRESH then JAVASCRIPT
object_onloadN();: set[app_object_id]to 0 at the start of the onload script. - The skeleton for all these columns is
_app_sajax_1_skeleton___import_all_charts.
