Returns the rendered HTML form for a content template. The response is the complete HTML that the user’s browser can display and submit to create a new document in the specified destination folder.
/srv.asmx/UseFormTemplate
/srv.asmx/UseFormTemplate?authenticationTicket=...&targetFolderPath=...&templatePath=...&submitUrl=.../srv.asmx/UseFormTemplate (form data)http://tempuri.org/UseFormTemplate| Parameter | Type | Required | Description |
|---|---|---|---|
authenticationTicket |
string | Yes | Authentication ticket obtained from AuthenticateUser. |
targetFolderPath |
string | Yes | Full infoRouter path of the destination folder where the new document will be created (e.g. /Finance/Reports). |
templatePath |
string | Yes | Full infoRouter path of the template document (e.g. /Templates/ExpenseForm.htm), or ~D<id> short form (e.g. ~D42). Use ~D999 for the built-in HTML document type. |
submitUrl |
string | Yes | URL to set as the HTML form action attribute. Pass an empty string "" to use the default legacy IRDOC.ASPX handler. Pass the route of a React/SPA page to intercept the submission client-side instead. This parameter must always be present in the request; omitting it entirely from the query string or form body will cause a server error. |
<root success="true"><![CDATA[
<!DOCTYPE html>
<html>
...rendered form HTML...
</html>
]]></root>
The element content is the complete rendered HTML form wrapped in a CDATA section to preserve HTML characters (<, >, &) without XML encoding.
<root success="false" error="Error message" />
targetFolderPath).POST /srv.asmx/UseFormTemplate HTTP/1.1
Content-Type: application/x-www-form-urlencoded
authenticationTicket=3f2504e0-4f89-11d3-9a0c-0305e82c3301
&targetFolderPath=/Finance/Reports
&templatePath=/Templates/ExpenseReport.htm
&submitUrl=
To render the built-in HTML document form (not tied to a specific template file):
POST /srv.asmx/UseFormTemplate HTTP/1.1
Content-Type: application/x-www-form-urlencoded
authenticationTicket=3f2504e0-4f89-11d3-9a0c-0305e82c3301
&targetFolderPath=/Finance/Reports
&templatePath=~D999
&submitUrl=
Do not use submitUrl when embedding inside a React app via iframe — see the React section below. The submitUrl parameter is only useful when an external page submits to a known server-side route.
The rendered HTML is a complete standalone page (<html>, <head>, <style>, <body>). The correct way to embed it in a React application is inside an <iframe srcdoc>, not via dangerouslySetInnerHTML.
Why iframe and not dangerouslySetInnerHTML:
dangerouslySetInnerHTML |
<iframe srcdoc> |
|
|---|---|---|
<script> tags execute |
No | Yes |
<style> isolated from React app |
No — leaks into host page | Yes |
| Full HTML document supported | No — strips <html>/<head>/<body> |
Yes |
| Submit interception | Requires querySelector on container |
contentDocument.querySelector on load |
srcdoc iframes are always same-origin with their parent, so contentDocument access is always permitted regardless of the host the React app is served from.
The rendered HTML has InfoRouter_Ticket set to an empty string. The React app must inject the live session ticket in the onLoad handler before the user submits.
const res = await fetch(
`/srv.asmx/UseFormTemplate?authenticationTicket=${ticket}` +
`&targetFolderPath=${encodeURIComponent(targetFolderPath)}` +
`&templatePath=${encodeURIComponent(templatePath)}` +
`&submitUrl=`
);
const xml = new DOMParser().parseFromString(await res.text(), 'text/xml');
const renderedHtml = xml.querySelector('root').textContent;
import { useRef } from 'react';
function InfoRouterForm({ renderedHtml, authTicket, targetFolderPath, documentName }) {
const iframeRef = useRef(null);
const handleLoad = () => {
const doc = iframeRef.current.contentDocument;
const form = doc.querySelector('form');
if (!form) return;
// Inject the live ticket — the rendered HTML has this field empty
const ticketField = doc.getElementById('InfoRouter_Ticket');
if (ticketField) ticketField.value = authTicket;
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Parse InfoRouter_Fields to discover field names and build xmlContent
const fieldsInput = doc.getElementById('InfoRouter_Fields');
const tokens = fieldsInput ? fieldsInput.value.split(',') : [];
let xmlContent = '<FORMDATA>';
for (let i = 0; i + 3 < tokens.length; i += 4) {
const raw = tokens[i].trim(); // e.g. "'IR_title'"
const name = raw.slice(4, raw.length - 1); // strip 'IR_ prefix and trailing '
const value = (form.elements[name]?.value ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
xmlContent += `<Prompt Name="${name}">${value}</Prompt>`;
}
xmlContent += '</FORMDATA>';
const body = new URLSearchParams({
AuthenticationTicket: authTicket,
Path: targetFolderPath.replace(/\/+$/, '') + '/' + documentName,
TemplatePath: templatePath,
xmlContent,
});
const res = await fetch('/srv.asmx/CreateDocumentUsingTemplate', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});
const xml = new DOMParser().parseFromString(await res.text(), 'text/xml');
const root = xml.querySelector('root') ?? xml.querySelector('response');
if (root?.getAttribute('success') === 'true') {
// handle success — root may have DocumentID and DocumentName attributes
} else {
console.error('Submit failed:', root?.getAttribute('error'));
}
});
};
return (
<iframe
ref={iframeRef}
srcdoc={renderedHtml}
onLoad={handleLoad}
style=
title="Document Form"
/>
);
}
InfoRouter_Fields hidden inputThe rendered HTML always contains a hidden input named InfoRouter_Fields. Its value is a comma-separated list of 4-token descriptors — one group per template field:
'IR_title','CHAR','N','N','IR_author','CHAR','Y','N','IR_duedate','DATE','Y','N'
Each group of 4 tokens:
| Position in group | Meaning |
|---|---|
0 — 'IR_{fieldname}' |
HTML input name with 'IR_ prefix and trailing '. Strip those to get the bare field name. |
1 — 'CHAR' | 'DATE' | 'NUMBER' | 'BOOLEAN' |
Data type. |
2 — 'Y' | 'N' |
Required flag. |
3 — 'N' |
Reserved. |
You must parse this value to know which form inputs to collect and to build the correct xmlContent for CreateDocumentUsingTemplate.
submitUrl must always be included in the request. Pass an empty string "" when embedding via iframe — the iframe’s onLoad handler takes over submit interception entirely, so the form’s action attribute is irrelevant.e.preventDefault() must be called to stop the browser from navigating away when the user clicks Submit.InfoRouter_Ticket hidden field is empty in the rendered HTML by design. Always overwrite it with the current session ticket in handleLoad and again in the submit handler.InfoRouter_Fields from iframeDoc in the onLoad handler to discover all template field names and their data types before building xmlContent.renderedHtml changes (user picks a different template), the iframe re-renders and onLoad fires again, re-attaching the listener cleanly.templatePath accepts either a full document path or the ~D<id> short form. ~D999 is the reserved identifier for the built-in HTML document type and does not correspond to a physical document in the repository.submitUrl must always be present in the request. When passed as an empty string "", the form action defaults to IRDOC.ASPX (legacy handler). This parameter is only meaningful for non-React integrations where a server-side route must receive the POST. In a React app using <iframe srcdoc>, pass submitUrl="" and intercept submit via contentDocument instead.InfoRouter_Ticket hidden field inside the rendered form is always empty. The React app must inject the live session ticket in the iframe onLoad handler.TEMPLATEPATH criterion.| Error | Description |
|---|---|
[900] Authentication failed |
Invalid or missing authentication ticket. |
[901] Session expired or Invalid ticket |
The ticket has expired or does not exist. |
| Folder not found | The targetFolderPath does not exist or is not accessible. |
| Document not found | The templatePath does not resolve to an existing document. |
| Access denied | The user lacks Read permission on the template or Add Document permission on the folder. |
SystemError:... |
An unexpected server-side error occurred. |
Production-ready patterns derived from the reference demo at IRWebCore/wwwRoot/form-template-demo.html.
These functions are used across all three form APIs (UseFormTemplate, GetFormFromDocument, CreateDocumentUsingTemplate). Define them once in a shared module.
// XML-escape a value before placing it inside a <Prompt> element
function escapeXml(s) {
return String(s ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Parse the InfoRouter_Fields hidden input from a rendered form inside an iframe.
// Returns an array of { name, value, dataType, required }.
// InfoRouter_Fields format: 'IR_field1','CHAR','N','N','IR_field2','DATE','Y','N',...
function parseInfoRouterFields(iframeDoc, form) {
const input = iframeDoc.getElementById('InfoRouter_Fields');
if (!input || !input.value.trim()) return [];
const tokens = input.value.split(',');
const fields = [];
for (let i = 0; i + 3 < tokens.length; i += 4) {
const raw = tokens[i].trim(); // e.g. "'IR_title'"
if (raw.length < 5) continue;
const name = raw.slice(4, raw.length - 1); // strip 'IR_ prefix + trailing '
const dataType = tokens[i + 1].trim().replace(/'/g, ''); // CHAR | DATE | NUMBER | BOOLEAN
const required = tokens[i + 2].trim() === "'Y'";
const el = form.elements[name];
const value = el ? el.value : '';
fields.push({ name, value, dataType, required });
}
return fields;
}
// Build the <FORMDATA> XML required by CreateDocumentUsingTemplate
function buildXmlContent(fields) {
if (!fields || fields.length === 0) return '';
return '<FORMDATA>' +
fields.map(f => `<Prompt Name="${f.name}">${escapeXml(f.value)}</Prompt>`).join('') +
'</FORMDATA>';
}
// Parse an infoRouter XML response — handles both <root> and <response> root elements
function parseXml(xmlText) {
const doc = new DOMParser().parseFromString(xmlText, 'text/xml');
const root = doc.querySelector('root') ?? doc.querySelector('response');
if (!root) return { ok: false, error: 'Could not parse response' };
return { ok: root.getAttribute('success') === 'true', error: root.getAttribute('error') ?? '', el: root };
}
async function loadForm(apiBase, ticket, targetFolderPath, templatePath) {
const body = new URLSearchParams({
authenticationTicket: ticket,
targetFolderPath,
templatePath,
submitUrl: '', // Always include; pass '' when using iframe submit interception
});
const res = await fetch(`${apiBase}/UseFormTemplate`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});
const r = parseXml(await res.text());
if (!r.ok) throw new Error(r.error);
return r.el.textContent; // Raw HTML extracted from CDATA
}
r.el.textContent extracts the HTML from inside the CDATA wrapper. submitUrl must always be present — pass '' to let the iframe onLoad handler own submit interception.
import { useState, useRef, useCallback } from 'react';
function CreateDocumentForm({ ticket, targetFolderPath, templatePath, apiBase, onSubmit }) {
const [renderedHtml, setRenderedHtml] = useState('');
const iframeRef = useRef(null);
async function handleLoadForm() {
const html = await loadForm(apiBase, ticket, targetFolderPath, templatePath);
setRenderedHtml(html);
}
// useCallback with full dependency array prevents stale closures in the submit handler
const handleIframeLoad = useCallback(() => {
const iframe = iframeRef.current;
if (!iframe || !renderedHtml) return;
const doc = iframe.contentDocument;
// Always inject the live ticket — the rendered form leaves InfoRouter_Ticket empty
const ticketField = doc.getElementById('InfoRouter_Ticket');
if (ticketField) ticketField.value = ticket;
const form = doc.querySelector('form');
if (!form) return;
form.addEventListener('submit', e => {
e.preventDefault();
e.stopPropagation();
// Collect all template fields and their current values
const fields = parseInfoRouterFields(doc, form);
// Hand off to the parent — it will call CreateDocumentUsingTemplate
onSubmit({ fields, templatePath });
});
}, [renderedHtml, ticket, templatePath]);
return (
<>
<button onClick={handleLoadForm}>Load Form</button>
{renderedHtml && (
<iframe
ref={iframeRef}
srcDoc={renderedHtml}
onLoad={handleIframeLoad}
sandbox="allow-scripts allow-forms allow-same-origin"
style=
title="Document Form"
/>
)}
</>
);
}
| Rule | Reason |
|---|---|
Use srcDoc (not src) on the iframe |
Renders the HTML string directly without a round-trip |
sandbox="allow-scripts allow-forms allow-same-origin" |
Scripts inside the form execute; contentDocument access is permitted |
e.preventDefault() + e.stopPropagation() |
Prevents the browser from navigating away on submit |
Inject InfoRouter_Ticket in onLoad |
The rendered HTML always has this field empty by design |
List [renderedHtml, ticket, templatePath] in useCallback deps |
Prevents the submit handler from capturing stale values when inputs change |
Call CreateDocumentUsingTemplate from the onSubmit handler |
UseFormTemplate only renders the form; the actual save is a separate call |