Here, there's no HTML structure needed at all — just a Dvl-epr component, formajaxsave, along with the insert or update SQL it needs to run.
<Block tag='none'> <Element type='formajaxsave'> <n>insertcontact</n> <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> </Element> </Block>
The Update form's processor follows the exact same shape — same <Block tag='none'>, same formajaxsave wrapper, same retInfo pattern — just an UPDATE in place of an INSERT, filtered to the one row being edited:
<Block tag='none'> <Element type='formajaxsave'> <n>updatecontact</n> <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> </Element> </Block>
%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.And sendEmail() itself, inside eminc_contactfunctions.inc:
<?php 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. Everything else (the response, the message shown to the user) works exactly the same whether the step in between was a query or a function.
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.
Putting It Together: one complete page from list to form
Post 8 is the payoff — the module file that wires the list, the table, the detail screen, and this form into a single working Contacts page.