Here, there's no HTML structure needed at all — just a Dvl'epr component, formsave, along with the insert or update SQL it needs to run.

1 Adding a Contact
emxml_ajxPrcInsertContact.xml
<block>

    <formsave validate="Y">
        <sql>
            <query name="newcontact" success="Contact saved." fail="Could not save contact."><![CDATA[
                INSERT INTO contacts
                    (dept_id, contact_name, email, department, phone, status, created_date)
                VALUES
                    ('%deptid%', '%contact_name%', '%email%', '%department%', '%phone%', 'Active', NOW())
            ]]></query>

            <query name="retInfo" type="data"><![CDATA[
                SELECT '%newcontact_id%' AS contact_id,
                       '%contact_name%'  AS contact_name,
                       'CONTACTADDED'    AS TYPE
                FROM DUAL
            ]]></query>
        </sql>
    </formsave>

</block>
2 Updating a contact

The Update form's processor follows the exact same shape — same <block> root, same <formsave> wrapper, same retInfo pattern — just an UPDATE in place of an INSERT, filtered to the one row being edited:

emxml_ajxPrcUpdateContact.xml
<block>

    <formsave validate="Y">
        <sql>
            <query name="updatedcontact" success="Contact updated." fail="Could not update contact."><![CDATA[
                UPDATE contacts
                SET    contact_name = '%contact_name%',
                       email        = '%email%',
                       department   = '%department%',
                       phone        = '%phone%'
                WHERE  contact_id   = '%contact_id%'
                AND    dept_id      = '%deptid%'
            ]]></query>

            <query name="retInfo" type="data"><![CDATA[
                SELECT '%contact_id%'   AS contact_id,
                       '%contact_name%' AS contact_name,
                       'CONTACTUPDATED' AS TYPE
                FROM DUAL
            ]]></query>
        </sql>
    </formsave>

</block>
WHERE contact_id
The one genuinely new piece here — the %contact_id% that post 6's hidden field carried out of the Edit form comes back in as the filter, so the update lands on exactly the row that was opened, and no other.

A step in the middle doesn't have to be SQL. A query marked type="function" runs a PHP function instead, in document order, inside the same transaction as everything around it — useful for anything the database can't do on its own, like sending a welcome email once the row exists:

emxml_ajxPrcInsertContact.xml — with a function step
<formsave validate="Y">
    <sql>
        <!-- Load the file holding the PHP function, before any query runs -->
        <code><include>eminc_contactfunctions.inc</include></code>

        <query name="newcontact" success="Contact saved." fail="Could not save contact."><![CDATA[
            INSERT INTO contacts (dept_id, contact_name, email, status, created_date)
            VALUES ('%deptid%', '%contact_name%', '%email%', 'Active', NOW())
        ]]></query>

        <!-- The query's own name IS the PHP function called -->
        <query name="sendEmail" type="function" fail="Contact saved, but the welcome email failed." />

        <query name="retInfo" type="data"><![CDATA[
            SELECT '%newcontact_id%' AS contact_id, 'CONTACTADDED' AS TYPE
            FROM DUAL
        ]]></query>
    </sql>
</formsave>

And sendEmail() itself, inside eminc_contactfunctions.inc:

PHP function contract
<?php
    // The & matters: it is what lets anything written back here reach
    // the queries that run after this one.
    function sendEmail(&$parmlist) {
        // $parmlist holds every token in scope, including newcontact_id from the query above
        $email = $parmlist['email'];
     
        // ... send the email ...
     
        return '';  // empty string = success
        // throw new Exception('Could not send welcome email');  // failure
    }

Return an empty string for success, throw an exception for failure — that's the entire contract. (Return a number instead and it becomes %sendEmail_id%, the same way an INSERT's new id becomes %newcontact_id%.) Everything else — the response, the message shown to the user, the transaction rolling back on failure — works exactly the same whether the step was a query or a function.

3 What happens, start to finish
1
User clicks Save Contact in the form.
2
The processor is called.
3
The SQL — and any plugin functions — execute.
4
The processor sends the response back.
5
The user sees a confirmation, and the contacts list reflects the new row — zero hand-written JavaScript for any of it.
4 Rendered live
app.example.com/manager/contacts
Contact saved.
Sarah Whitfield  ·  Operations
David Okonkwo  ·  Procurement
Priya Anand  ·  Customer Service Just added
Two forms, two processors, an insert and an update — built across two posts, without writing a single AJAX call by hand.

That's the full read-and-write loop the platform runs on: a list or table to browse, a detail screen to focus on one record, forms to add and update it, processors to save it. Every other screen on the platform is some combination of these same pieces. The last post in this arc puts all of them on one page together.