I don’t mind a subscription model that is reasonably priced if I know what features are being planned in the future. Right now there is no road map and Tap Forms Pro has been out for quite a few months now.
I am definitely not upgrading until iOS layouts are added. I was waiting for those in Tap Forms 5. I know Brenden said he is working on them.
Thanks Branden, I am using the latest TFP update.
Of note, in TF5 you cannot link the form to itself properly unless you use the JOIN relationship. TF5 will display its parent as a child if you use ONE TO MANY. JOIN works, but under a JOIN you cannot use a calculation field to pull record data from the parent record in order to cascade data along the children.
TFPro can use ONE TO MANY and it works properly, but the calculation fields and scripts seem to work intermittently. I’m still trying to figure it out. It could be related to when scripts and calculation fields are executed. I’m assuming they’re executed when the record is viewed? If so then it can be problematic when viewing/ editing child records that depend on data properly being calculated via its parent.
Another intermittent and BIG PROBLEM with TFPro is when you click a child record in a link to field object, it goes to the child record, but doesn’t show any of the fields (it’s blank) as if there are no fields in the form. You have to add a second child record then click back to the original child for it to work properly. Weird. When I have time I’ll send you a video of it.
Related to forms linking to themselves, is there a way to use javascript to pull field data from a parent record? I have only been able to do this with a calculation field and have only seen child records in the javascript API. Javascript seems to only grab data from the field of the current record. So I have been trying to use a calculation field to pull the parent field data, and a script to manipulate it but right now.
-
This reply was modified 4 months ago by
Shane.
-
This reply was modified 4 months ago by
Shane.
Newbie questions:
Does the online pro version of the documentation apply?
I exported a csv file of a table on the iPhone but can’t find where Tapforms puts them?
Has anyone been successful in using ChatGPT to write TapForms scripts?
Thanks much
JASailor
I am a bit confused by the pricing model for Tap Forms Pro. As I see pricing models, one time download apps like Tap Forms that are stand-alone programs untethered from third party service support would fall under a one-time purchase model. An app that requires ongoing back end service like iCloud, or regular data refresh services like, PictureThis or AccuWeather justify monthly or annual subscription. What are the daily support services requiring Tap forms to solicit service fees in support of back end overhead necessary to facilitate purchased end-client functionality?
Tap Forms is truly one of the great pieces of software for Mac products and would be my pick for the Apple hall of fame if there were such a thing. All the same I would rather not pay every month in perpetuity for a stand-alone app I am dependent on and likely to use for a long time to come. That becomes extremely expensive for the average Joe that isn’t a business accustomed to signing ongoing service contracts with their stand-alone software.
Steve
I’ve been testing the fix and I’m happy to report that it’s working.
On my iPad, one thing that took a little time to test is when a script updates a date and that changes the sort order, it takes up to 5-7 seconds until the form syncs and then the sort order is updated correctly. At first I thought that wasn’t working, but a little patience proved otherwise.
For me, everything is working fine now for all my forms.
Your script is executed several times. That’s why you get the repeated prompter.
1) You don’t want to trigger the script when changing the street field. To prevent this, use double quotes in var streetField = orderForm.getFieldNamed("Street");
2) When setting a field value that should not trigger any script activity, use the optional third parameter: targetRecord.setFieldValue(targetStreetField.getId(), selectedStreet, false);
Even with these changes, the script runs more than once.
Cheers, Daniel
---
See https://lab.danielleu.com/tapformspro/ for scripts and tips&tricks
I noticed that you don’t use document.saveAllChanges() in order to save your modifications.
I simplified the script to test the prompter function and it works as expected. Could you share a template file or even better a small document with a few sample records in order to reproduce the issue you see? Thanks!
Cheers, Daniel
---
See https://lab.danielleu.com/tapformspro/ for scripts and tips&tricks
/**
* This script is designed to be run from a button on the Order form layout.
* The button should be configured to “Run Script Field Script”.
*/
function findAndSetStreet() {
// The ‘record’ object is automatically available when a script is
// run from a button on a record’s layout.
var currentRecord = record;
var orderForm = form;
if (!currentRecord) {
Utils.alertWithMessage(‘Error’, ‘This script must be run from a record.’);
return;
}
var companyForm = document.getFormNamed(‘Company’);
if (!companyForm) {
Utils.alertWithMessage(‘Script Error’, ‘Could not find the “Company” form.’);
return;
}
// — CONFIGURATION —
// On the line below, change “Company” to the EXACT name
// of your company pick list field.
var companyField = orderForm.getFieldNamed(‘Company’);
// ———————
var streetField = orderForm.getFieldNamed(‘Street’);
if (!companyField || !streetField) {
Utils.alertWithMessage(‘Script Error’, ‘Could not find your Company Pick List or Street fields.’);
return;
}
var selectedCompanyName = currentRecord.getFieldValue(companyField.getId());
if (!selectedCompanyName || selectedCompanyName === ”) {
Utils.alertWithMessage(‘No Company’, ‘Please select a company before clicking this button.’);
return;
}
var companyNameField = companyForm.getFieldNamed(‘Company Name’);
var companyStreetField = companyForm.getFieldNamed(‘Company Street’);
if (!companyNameField || !companyStreetField) {
Utils.alertWithMessage(‘Script Error’, ‘Could not find “Company Name” or “Company Street” fields in the Company form.’);
return;
}
var companyRecords = companyForm.fetchRecords();
var matchingStreets = [];
for (var i = 0; i < companyRecords.length; i++) {
var companyRecord = companyRecords;
var companyName = companyRecord.getFieldValue(companyNameField.getId());
var street = companyRecord.getFieldValue(companyStreetField.getId());
if (companyName === selectedCompanyName && street && street !== ”) {
if (matchingStreets.indexOf(street) === -1) {
matchingStreets.push(street);
}
}
}
if (matchingStreets.length === 0) {
Utils.alertWithMessage(‘No Streets Found’, ‘No street addresses were found for ‘ + selectedCompanyName + ‘.’);
currentRecord.setFieldValue(streetField.getId(), ”);
return;
}
if (matchingStreets.length === 1) {
currentRecord.setFieldValue(streetField.getId(), matchingStreets[0]);
Utils.alertWithMessage(‘Street Set’, ‘The street address has been automatically set to: ‘ + matchingStreets[0]);
return;
}
// Store references in variables that the callback can access
var targetRecord = currentRecord;
var targetStreetField = streetField;
var prompterCallback = function(continued) {
if (continued) {
// Try to get the value using the global variable approach from your original code
if (typeof selectedStreet !== ‘undefined’ && selectedStreet) {
targetRecord.setFieldValue(targetStreetField.getId(), selectedStreet);
}
}
};
var prompter = Prompter.new();
prompter.cancelButtonTitle = ‘Cancel’;
prompter.continueButtonTitle = ‘Select’;
prompter.addParameter(‘Select Street Address:’, ‘selectedStreet’, ‘popup’, matchingStreets);
prompter.show(‘Multiple Streets Found for ‘ + selectedCompanyName, prompterCallback);
}
// Check if this is running in a Script Field context (automatic execution)
// vs being called from a button (manual execution)
if (typeof record !== ‘undefined’ && record) {
var companyField = form.getFieldNamed(‘Company’);
if (companyField) {
var companyValue = record.getFieldValue(companyField.getId());
// Only run if there’s actually a company selected
if (companyValue && companyValue !== ”) {
findAndSetStreet();
}
}
}
It looks like the file did not attach so the code is below
============================================================
/**
* This script is designed to be run from a button on the Order form layout.
* The button should be configured to "Run Script Field Script".
*/
function findAndSetStreet() {
// The 'record' object is automatically available when a script is
// run from a button on a record's layout.
var currentRecord = record;
var orderForm = form;
if (!currentRecord) {
Utils.alertWithMessage('Error', 'This script must be run from a record.');
return;
}
var companyForm = document.getFormNamed('Company');
if (!companyForm) {
Utils.alertWithMessage('Script Error', 'Could not find the "Company" form.');
return;
}
// --- CONFIGURATION ---
// On the line below, change "Company" to the EXACT name
// of your company pick list field.
var companyField = orderForm.getFieldNamed('Company');
// ---------------------
var streetField = orderForm.getFieldNamed('Street');
if (!companyField || !streetField) {
Utils.alertWithMessage('Script Error', 'Could not find your Company Pick List or Street fields.');
return;
}
var selectedCompanyName = currentRecord.getFieldValue(companyField.getId());
if (!selectedCompanyName || selectedCompanyName === '') {
Utils.alertWithMessage('No Company', 'Please select a company before clicking this button.');
return;
}
var companyNameField = companyForm.getFieldNamed('Company Name');
var companyStreetField = companyForm.getFieldNamed('Company Street');
if (!companyNameField || !companyStreetField) {
Utils.alertWithMessage('Script Error', 'Could not find "Company Name" or "Company Street" fields in the Company form.');
return;
}
var companyRecords = companyForm.fetchRecords();
var matchingStreets = [];
for (var i = 0; i < companyRecords.length; i++) {
var companyRecord = companyRecords;
var companyName = companyRecord.getFieldValue(companyNameField.getId());
var street = companyRecord.getFieldValue(companyStreetField.getId());
if (companyName === selectedCompanyName && street && street !== '') {
if (matchingStreets.indexOf(street) === -1) {
matchingStreets.push(street);
}
}
}
if (matchingStreets.length === 0) {
Utils.alertWithMessage('No Streets Found', 'No street addresses were found for ' + selectedCompanyName + '.');
currentRecord.setFieldValue(streetField.getId(), '');
return;
}
if (matchingStreets.length === 1) {
currentRecord.setFieldValue(streetField.getId(), matchingStreets[0]);
Utils.alertWithMessage('Street Set', 'The street address has been automatically set to: ' + matchingStreets[0]);
return;
}
// Store references in variables that the callback can access
var targetRecord = currentRecord;
var targetStreetField = streetField;
var prompterCallback = function(continued) {
if (continued) {
// Try to get the value using the global variable approach from your original code
if (typeof selectedStreet !== 'undefined' && selectedStreet) {
targetRecord.setFieldValue(targetStreetField.getId(), selectedStreet);
}
}
};
var prompter = Prompter.new();
prompter.cancelButtonTitle = 'Cancel';
prompter.continueButtonTitle = 'Select';
prompter.addParameter('Select Street Address:', 'selectedStreet', 'popup', matchingStreets);
prompter.show('Multiple Streets Found for ' + selectedCompanyName, prompterCallback);
}
// Check if this is running in a Script Field context (automatic execution)
// vs being called from a button (manual execution)
if (typeof record !== 'undefined' && record) {
var companyField = form.getFieldNamed('Company');
if (companyField) {
var companyValue = record.getFieldValue(companyField.getId());
// Only run if there's actually a company selected
if (companyValue && companyValue !== '') {
findAndSetStreet();
}
}
}
-
This reply was modified 4 months, 1 week ago by
Brendan.
Wonderful! Thanks Daniel.
I did some research into JS methods but have to admit the code was way above my level so, elegant or not(!), your solution is just what I need.
I get a console error if the table is blank in a record (because that particular meal has been eaten and the row deleted) but that doesn’t seem to affect execution of the script.
Cheers
Peter
My freezer inventory db (template attached) has a table field in which I record each time I make a batch of a particular recipe. The table field’s fields include things like the quantity of meals frozen, and the date frozen. I have a field script that correctly populates a Total field with the total remaining meals from each record’s table, and I have tried to do the same with a date field – ‘Earliest freeze date’ – to extract the oldest frozen meal from that recipe, using the following script so I can then include the field in a search:
record.getMinOfLinkedFieldForField('fld-7b64bb627a8a4beaadacd625f3c4e46a', 'fld-557ef5d4175949a8a0ac0b7b3cf5f932');
This is the total script – ie there is no function – but for a batch of meals frozen on 21 April 2025 and 14 September 2025 I get ’21 Jan 1970 at 05:46:30′ in the script field. The console shows the result of running the script as 1745190000. The Result Type is set to ‘Date’.
Is getMinOfLinkedField not appropriate for a date calculation?
I’m assuming it’s not possible to script a search through the freeze dates of the table field?
Thanks
Attachments:
You must be
logged in to view attached files.
Hey all,
For some reason TF5 can copy but not paste (see screenshot). I tested it in Tap Forms Pro which works fine. The option to paste is greyed out, nor can I use commond+V. Anyone else notice this problem?
I tried going into the Mac’s security and privacy settings and allowing TF5 to control the computer, then relaunching but it didn’t do anything.
Attachments:
You must be
logged in to view attached files.
Second time I’m trying to post an answer…. the first time caused the forum to crash :-(. So this answer will be a bit shorter.
The field script has an error. The dash - in the field ids is not a dash and therefore, no value is read. I modified the script to call the form script in the Coins form:
(function() {
const ct = record.getFieldValue('fld-1231556e0f234de9b2d85f50225b8135');
const yr = record.getFieldValue('fld-4b84b047badd4d4cbb4a67ef6839522f');
const mm = record.getFieldValue('fld-adb0be16f1a347dbbecc4ef562d779a7');
if (ct && yr && mm) {
console.log("condition met")
document.getFormNamed('Coins').runScriptNamed('Variety Prompt');
}
return '';
})();
When running the Variety Prompt script, I run into an error where the constants were already defined. This has to do with how javascript is handled within Tap Forms Pro. So I moved that section inside your function:
// === Script ===
(function () {
// === CONFIG (your IDs kept as-is) ===
const COINS_FORM_NAME = 'Coins';
const MINTAGE_FORM_NAME = 'Coin Mintage';
const COINS_FIELDS = {
coinType: 'fld-1231556e0f234de9b2d85f50225b8135',
year: 'fld-4b84b047badd4d4cbb4a67ef6839522f',
mintMark: 'fld-adb0be16f1a347dbbecc4ef562d779a7',
variety: 'fld-2a73d81d137e4f718378819f5e4d54e0'
};
const MINTAGE_FIELDS = {
coinType: 'fld-fdf93db206d141bd956d8c9e7634f25e',
year: 'fld-9579d7c74e88414daf27ccbc5d4e0346',
mintMark: 'fld-8d72d0971ed34c2c9de6c03d240e9543',
variety: 'fld-f76a6eb7ed0c4ae28b77fc6fdf055d53'
};
if (typeof record === 'undefined' || !record) {
...
Now it seems to work. Good luck!
Cheers, Daniel
---
See https://lab.danielleu.com/tapformspro/ for scripts and tips&tricks
You definitely can run a form or document script from a field script. I do this all the time. For a form script, you can use document.getFormNamed('demo').runScriptNamed('test');, for a document script use document.runScriptNamed('test');.
Do you have a test file you can share? You can email it if you prefer.
Cheers, Daniel
---
See https://lab.danielleu.com/tapformspro/ for scripts and tips&tricks
Again thanks for your help.
My research says that I can use the prompter in a field script. Also, I can’t run a form script from a field script. This eliminates the process I want to accomplish. I want it to prompt for the variety field from the records in the coin mintage form.