Tap Forms app icon half
Tap Forms Forum text image
Blue gradient background

Exchange tips and ideas with the Tap Forms community

Search Results for 'script'

Viewing 15 results - 2,041 through 2,055 (of 2,989 total)
  • Author
    Search Results
  • #39677
    Brendan
    Keymaster

    Hi Guillaume,

    Those two shortcut actions were accidentally left in there. I’ve removed them for the next update. I tried to make them work, but because of the dynamic nature of Tap Forms, I couldn’t get it to ask you for values for the fields, so I couldn’t make that work. At least not until Apple enhances Shortcuts to allow dynamically assignable parameters. So right now the only shortcut that works is the one for running form scripts.

    But it might be interesting to integrate Tap Forms directly with HealthKit so you could import all your HealthKit data into Tap Forms. I workout every day myself and use the Activity app to view my health data. So it would benefit a lot of people I think. I’ll think about it.

    Thanks,

    Brendan

    #39658
    Sam Moffatt
    Participant

    I wrote a script a while back to combine multiple values from Link to Form field for precisely the reason of being able to search for those records. You can just have the script field contain the data and it’ll show up in search, it’s exactly how I use it. I use them on Table fields in some places as well to help roll up aggregates.

    #39656
    Edu Neve
    Participant

    Hello,
    I have zero experience with Javascript and I’m struggling to understand the provided documentation regarding scripts. The reason I’m considering scripts is because I really want to be able to search and sort fields that link to other forms, but from what I’ve read in the forums this is unfortunately not possible yet (this is absolutely my number one request for future development).

    So, I figured an alternative would be to create a script that copies the contents of a field that links to another form, into a regular field. This way, when I sort or create saved searches/filters, I can point to the regular field that would have copied the relevant contents.

    So, all I want is a simple automated copy-paste of the text or numeric contents of a field (that links to other forms) into another field of my form. I haven’t been able to find a step by step guide in the documentation or in the forums.

    I see that I’d use “get field value” and “set field value” or “set multiple field values”, but I don’t know how to go about it. Do I create the script as a field, or do I simply add it to the Scripts pane? And once I create the script, what would I type there?

    Any help would be much appreciated. Thank you.

    #39655
    Max Heart
    Participant

    OMG, Sam, I feel so bad and like a complete idiot … I appreciate your help so much but I am not able to translate it to my database.

    If you still have patience to advise I would highly appreciate it. I will try to describe precisely where I am:

    – I have two forms: “Inventar-Boxen” which has all the boxes and “Inventar-Inhalte” which has all the individual items. The field “Inhalte” in Inventar-Boxen is linked as one to many to the form Inventar-Inhalte. “Show reverse link” is enabled
    – The form “Inventar-Boxen” has the following fields: “Boxname” (Calc Field: Box number, box name, box type), Place, box number, box type, contents (“Inhalte”: the linked field), Barcode (the QR code field – a calc field “Box+box number”)
    – The form “Inventar-Inhalte” has “Foto”, “Item” (Calc Field: Manufacturer + item name), Boxen (re-link from the “Inventar-Boxen” form), “Barcode-ID” (this is the bar code identifier), “category”, “label”, “notes”, “invoice”, “purchase date”, …

    What I did:
    – I created the script manager form and copy pasted your “Prompter functions”
    – I copy pasted your second script into the script section of “Inventar-Boxen” and named it “Bulk Add”. I assigned Alt+Cmd+Y as a keyboard short cut.
    – I tried to exchange your fields with mine. However, I am already lost with the field IDs

    Is there a chance to have a look at your tap forms file with just one set of sample data? Maybe I would be able to understand the scripting concept better then.

    Again thank you very much and best regards, Max

    #39652
    Sam Moffatt
    Participant

    So for the second script, I have two forms, one is “Boxes” which stores the details about the physical box and then I have another form for “Purchases”. “Purchases” was the name of my original Bento library from what feels like a decade ago now and was mostly flat and denormalised. As I’ve gone along I’ve started to normalise everything but it’s all over the place. I had added ages ago “Bag Barcode” and “Storage Box” to the “Purchases” form but didn’t really do much with them. When I started moving, I decided I wanted to have a first class ‘Boxes’ form.

    The ‘Boxes’ form looks like this:

    Boxes: (frm-8ba92c77b5e8476a987945f40ff2b41a)
            'Title' text (fld-e289240daccc40409d75aee26493110c)
            'Box ID' text (fld-f25492cf56384369b278f7ea41ee258f)
            'Note' note (fld-69754dee7e9247e0b3087e2526881626)
            'Dimensions' text (fld-3e5e30f1d352495f989cc0dc1b306128)
            'Box Type' text (fld-97b2ab2bdbb1400d9384c7634e7c6d48)
            'Box Photos' photo (fld-72a520cff50a4bbd92f1e8ff6eec91db)
            'Purchases' form (fld-482c4f5c71ba4cd7a06c32d1c1d7d983) join 'Purchases' (frm-efc0199a2b1543f79e722383014533b0)
                             ON Boxes.Box ID == Purchases.Storage Box
            'Containing Box' text (fld-fe868108cdd844d895ea982e820b346c)
            'Boxes' form (fld-74466a4b96dc43b4bcbe153646b76911) join 'Boxes' (frm-8ba92c77b5e8476a987945f40ff2b41a)
                             ON Boxes.Box ID == Boxes.Containing Box
            'Tracking Number' text (fld-54af5c1e20e14d20a7c82a40679a2a8a)
    

    It has some metadata about the box such as a title, it’s Box ID (which maps to the box_id_id variable) which is the unique identifier tied to the barcode, it has some other details that are useful and then it has a link to my existing ‘Purchases’ form. There is a ‘Link to Form’ as “JOIN” field here to tie the box record to the purchases record. I also have an extra field for when this box is inside of another box plus a ‘Link to Form’ self-JOIN field. Since some of these boxes are being shipped, they are also tied to a tracking number (I should probably have made this a link to the shipments form I have in this document but didn’t).

    My Purchases form looks a little bit like this, I’m going to cut out some fields because it has 121 individual fields in it that aren’t particularly relevant. Here are the first few.

    Purchases: (frm-efc0199a2b1543f79e722383014533b0)
            'Image 01' photo (fld-e631165b67374734a3b8f384708b5922)
            'Title' text (fld-0d0edd2552ea461e929f806a4e5552b5)
            'Subtitle' calc (fld-45ef928f87e24bcd93e6751c8c21a6cb)
                    Referenced Fields: 
                     - State (fld-9402f8c0d53c43b986fee4ebc3468929)
                     - Colour (fld-a8626656cc90455ea9336dd2488d4aef)
                     - Category (fld-6fdd09891a8c4d73be1b24aa07d077be)
    
            === 'State Details' section (fld-21cdfa78aaf44d3b83e5c0e16c8d40d1) ===
            'State' text (fld-9402f8c0d53c43b986fee4ebc3468929)
            'Previous State' text (fld-636a7a4671c14877b1b17ea1b579cef5)
            'State Watcher' script (fld-45463af0b409465ea78ad7c498ee896d)
            'Colour' text (fld-a8626656cc90455ea9336dd2488d4aef)
            'Category' text (fld-6fdd09891a8c4d73be1b24aa07d077be)
    
            === 'Main' section (fld-76597ce17f924c25bbcb195df984331c) ===
            'Date Created' date_created (fld-0d049abe706b41afb680ab9a1bf99d46)
            'Date Modified' date_modified (fld-59a06347614e48e8bf547a855b781582)
            'Purchase Date' date (fld-ccbd9a8f51d34246bebfb31aa4e397dd)
            'Ship Date' date (fld-cb3a9886ac7f4ec487447801a3911a1a)
            'Received Date' date (fld-bb17d48e41c7423692ab586f6c884d05)
            'Order ID' text (fld-e3e66a0f2e5c4df7b9496f65355e0bcf)
            'Marketplace' text (fld-c163aba17ae64c4d93b5a53819a139dc)
            'Store Name' text (fld-3d98dc6cdcae4a88909c97c80dde7bfb)
            'Brand' text (fld-1e250019d7b249f282cc572814d3e71d)
            'Source' web_site (fld-da9d866bf3ca4d47aade04a77efd7301)
            'Source Scraped?' check_mark (fld-a0557d4c58334bbc81b356b99f6f0a1d)
            'Source Scraper Script' script (fld-429e3e7ca20a49d38b26417e25e6db26)
            'Item Key' text (fld-ae7379d699e9473aa2ab16a2a2f002d4)
            'Price' number (fld-08129d71ab0f4fa4a2749456281fca07)
            'Shipping Tracking Number' text (fld-6ea45a9c141343628940bfbcfa38ee90)
            'Shipping Carrier' text (fld-12644a7a4ae24ed8a7926123832d3557)
    
            === 'Storage' section (fld-f99f779335f54b9cb0a4179a90bb97dd) ===
            'Bag Barcode' text (fld-32d459f0b5fb4dc4974795c484832af1)
            'Storage Box' text (fld-c08e3a9eb7784d7f8ee3a5576c0adffa)
    

    Reasonably generic listing of fields for an object you’d buy, again it’s a little flat because of how I modelled it in Bento but you can see at the end in the storage section the two fields: ‘Bag Barcode’ (which maps to the bag_barcode_id field) and ‘Storage Box’ (which maps to the storage_box_id field). These are simple text fields that have the unique barcode for the bag storing the item as ‘Bag Barcode’ and then ‘Storage Box’ is the barcode of the box that this item is currently contained in.

    #39650
    Max Heart
    Participant

    Hi Sam,

    thank you so much for your two long posts with explanations. I will try to understand them and modify the variables so that they fit to my forms.

    So far
    – I imported your Script Manager form
    – I added the Prompter script to the Script Manager form
    – I created a script field in my box form and pasted your full script from the second post
    (… and I bought a barcode scanner for my Mac)

    Now I have to understand and adapt the script. The field which contains my QR code of the box is called “Barcode”. Is this field called “box_id_id” in your script?

    #39649
    jolinwarren
    Participant

    TJ, I just read your blog post on Bobby (an app which I installed a few years ago and then never used), and after reading about its limitations, thought, “I should set up a Tap Forms database to track my subscriptions instead”. Your blog post helped clarify in my mind what I would want to track for subscriptions, and why (which match yours). I’ve been using Tap Forms for many years, so it’s the place I would naturally want to do this. I headed over to the forums here to look into a couple of things and came across your post, a nice little circle!

    However, you would probably need a couple of fields to store the subscription costs based on whether they’re annual fees or monthly fees. Because Tap Forms wouldn’t be able to detect if you typed in “$2/month” or “$24/year”.

    I was thinking of handling this a different way, basically having a ‘price’ currency field and then a tickbox for “annual” (default being monthly for me, but you could reverse that). Or there could be a choice field if weekly, biannual, or other frequencies need to be supported. Then I would have two calculation fields, “monthly cost” and “annual cost”, and using the price and tickbox fields, it would be possible to calculate and display both.

    I haven’t built anything yet, but I’m happy to share it when I do if you’re still interested.

    Cheers,
    Jolin

    #39648

    In reply to: Finding earliest date

    Larry Stoter
    Participant

    I actually want to find the earliest date in a field as a step in a more complex script.

    However, I have an apparently easy solution :-) Create a new calculation field Date#, simply put the Date field into the calculation and then set the Result Type to Number. The Date# field then shows numbers – not sure what numbers but the earliest date has the smallest number in the Date# field.

    Which seems to do what I want.

    #39645
    Sam Moffatt
    Participant

    Ok, so I found the other one which is actually a variation of the one I posted though simplified in a way. It works on two forms: one for boxes and a second for purchases that has a “box ID” field in it as a text field.

    This uses the prompter functions again and also getRecordFromFormWithKey to find matching records.

    Let’s get started!

    var PARENT_SCRIPT = 'Boxes::Bulk Add To Box';
    document.getFormNamed('Script Manager').runScriptNamed('Prompter Functions');
    document.getFormNamed('Script Manager').runScriptNamed('getRecordFromFormWithKey');
    

    Define the PARENT_SCRIPT to prevent stuff like Profiler running it’s self tests. My internal version of getRecordFromFormWithKey currently has profiler support, I’ll update it later. We need the prompter functions again from earlier as mentioned.

    // Boxes
    var box_id_id = 'fld-f25492cf56384369b278f7ea41ee258f';
    
    var box_id = record.getFieldValue(box_id_id);
    

    This form I have this in is called “Boxes” and it has a field called “Box ID” which we grab when we start the form script. These are those variables.

    // Purchases
    var state_id = 'fld-9402f8c0d53c43b986fee4ebc3468929';
    var bag_barcode_id = 'fld-32d459f0b5fb4dc4974795c484832af1';
    var storage_box_id = 'fld-c08e3a9eb7784d7f8ee3a5576c0adffa';
    var title_id = 'fld-0d0edd2552ea461e929f806a4e5552b5';
    

    Purchases is the track of things I bought, it has it’s own barcode (in “Bag Barcode”) and it also has a text field for the storage box. There are also state and title fields for it as well.

    async function start(){
    	try {
    		while (1)
    		{
    			let barcode = '';
    			let scanBarcode = await promptText("Scan Bag", "Barcode:");
    			
    			if (!scanBarcode)
    			{
    				break;
    			}
    			
    			console.log(scanBarcode);
    

    If this looks familiar, it should because it’s the previous code without the zipcode and carrier removed. Also “Shipment” is now “Bag”. Cool beans!

    let correctBarcode = await promptConfirm(`Is ${scanBarcode} correct?`);
    			
    			if (!correctBarcode)
    			{
    				console.log('Incorrect barcode, skipping');
    				continue;
    			}			
    

    This part is new, it asks if the scanned bar code is correct. This lets you hit cancel if it’s wrong. If you’re using this with a hand held scanner (which I am), then generally it’ll insert an “enter” at the end of the input. This means you can scan a barcode, validate it on screen and scan the barcode again to confirm this dialog. It gives you an escape key to abort from.

    
    			let targetRecord = getRecordFromFormWithKey('Purchases', bag_barcode_id, scanBarcode, false, false);
    				
    			if (targetRecord)
    			{
    

    This looks for a record in my Purchases form looking in the bag_barcode_id field for the value of scanBarcode that we just scanned. The last two prevent some optimisations and also prevents autocreating records.

    
    				let title = targetRecord.getFieldValue(title_id);
    				
    				let correctTitle = await promptConfirm(`Is ${title} expected?`);
    				
    				if (!correctTitle)
    				{
    					console.log('Incorrect title, skipping');
    					continue;
    				}
    

    We do an extra confirmation to check that the title matches what we expect. Again if you are using a barcode scanner you can scan again to progress.

    
    				// Set the delivered flag just in case it isn't set already.
    				targetRecord.setFieldValue(state_id, 'Delivered');
    

    My purchases has a state field and we just update this to delivered when we’re scanning something. My state tracking was a later addition so this helps to clean up anything I touch.

    
    				let boxId = targetRecord.getFieldValue(storage_box_id);
    				
    				if (!boxId)
    				{
    				targetRecord.setFieldValue(storage_box_id, box_id);
    				}
    				else
    				{
    					Utils.alertWithMessage('Box ID Already Set', `This barcode already has already been scanned with ${boxId}`);
    				}
    

    We then check to see if we have a box ID already and if it isn’t set we set it. If it is set then we flag this with the user. In your case you could change this up to confirm with another prompt (like shown earlier) and confirm reassigning the boxes.

    
    				document.saveAllChanges();
    				console.log("Updated existing record for " + scanBarcode);
    			}
    

    Save all the changes and I make a log message. I like log messages.

    
    			else
    			{
    				Utils.alertWithMessage('Unable to find record', `No matching record for ${scanBarcode}`);
    			}
    		}
    

    The else is to prompt a message saying we can’t find the barcode.

    
    	} catch (error) {
    		console.log("Error: " + error);
    	}
    }
    

    Just like the last one, we catch the error and log it as a fall back.

    
    if (box_id)
    {
    	start();
    }
    else
    {
    	Utils.alertWithMessage('Invalid box', `Invalid box ID found: ${box_id}`);
    }

    This is a little different, it checks to see if you have a record in the Box form before starting up. The intent is that you are in a box and scanning items into said box.

    Here’s the script in full:

    var PARENT_SCRIPT = 'Boxes::Bulk Add To Box';
    document.getFormNamed('Script Manager').runScriptNamed('Prompter Functions');
    document.getFormNamed('Script Manager').runScriptNamed('getRecordFromFormWithKey');
    
    // Boxes
    var box_id_id = 'fld-f25492cf56384369b278f7ea41ee258f';
    
    var box_id = record.getFieldValue(box_id_id);
    
    // Purchases
    var state_id = 'fld-9402f8c0d53c43b986fee4ebc3468929';
    var bag_barcode_id = 'fld-32d459f0b5fb4dc4974795c484832af1';
    var storage_box_id = 'fld-c08e3a9eb7784d7f8ee3a5576c0adffa';
    var title_id = 'fld-0d0edd2552ea461e929f806a4e5552b5';
    
    async function start(){
    	try {
    		while (1)
    		{
    			let zipcode = undefined;
    			let carrier = '';
    			let barcode = '';
    			let scanBarcode = await promptText("Scan Bag", "Barcode:");
    			
    			if (!scanBarcode)
    			{
    				break;
    			}
    			
    			console.log(scanBarcode);
    			
    			let correctBarcode = await promptConfirm(`Is ${scanBarcode} correct?`);
    			
    			if (!correctBarcode)
    			{
    				console.log('Incorrect barcode, skipping');
    				continue;
    			}			
    			
    			let targetRecord = getRecordFromFormWithKey('Purchases', bag_barcode_id, scanBarcode, false, false);
    				
    			if (targetRecord)
    			{
    				let title = targetRecord.getFieldValue(title_id);
    				
    				let correctTitle = await promptConfirm(`Is ${title} expected?`);
    				
    				if (!correctTitle)
    				{
    					console.log('Incorrect title, skipping');
    					continue;
    				}
    			
    				// Set the delivered flag just in case it isn't set already.
    				targetRecord.setFieldValue(state_id, 'Delivered');
    				
    				let boxId = targetRecord.getFieldValue(storage_box_id);
    				
    				if (!boxId)
    				{
    				targetRecord.setFieldValue(storage_box_id, box_id);
    				}
    				else
    				{
    					Utils.alertWithMessage('Box ID Already Set', `This barcode already has already been scanned with ${boxId}`);
    				}
    				document.saveAllChanges();
    				console.log("Updated existing record for " + scanBarcode);
    			}
    			else
    			{
    				Utils.alertWithMessage('Unable to find record', `No matching record for ${scanBarcode}`);
    			}
    		}
    	} catch (error) {
    		console.log("Error: " + error);
    	}
    }
    
    if (box_id)
    {
    	start();
    }
    else
    {
    	Utils.alertWithMessage('Invalid box', `Invalid box ID found: ${box_id}`);
    }
    #39644

    Topic: Profiler Script

    in forum Script Talk
    Sam Moffatt
    Participant

    One of the scripts I use a lot is my profiler. I use it to figure out timing data for how long something takes for my scripts. It’s useful for understanding where your bottlenecks are in code and for figuring out what the true slow downs are.

    If you look at the very bottom of the script, there is an example/test for the script so you can see how it works. I use this with a Script Manager form which makes including easy like this:

    document.getFormNamed('Script Manager').runScriptNamed('Profiler');
    

    Due to the way the tests are coded, you want to define a variable called PARENT_SCRIPT to prevent the test from firing when being included. Generally I do this in the format of Form Name::Script Name, e.g.:

    var PARENT_SCRIPT = 'Boxes::Bulk Add To Box';

    Using it is easy once you’ve included it:

    profiler.start('Start time');
    ...
    your code that does stuff!
    ...
    profiler.end();
    

    Profiler internally uses a stack so end closes the most recent start. If you do a profiler.dump() you will get back a string with the profiling information formatted with indentation. Here is a copy of the test running as an example:

    24/2/20, 9:59:19 pm / Script Manager / Profiler
    Caught expected error from dump: Error: Profiler imbalance: started called 1 more times than end.
    Uneven profiler.end called (are you missing a start?)
    Dump called with index 7 and indent 0
    0ms  	Test 1
        0ms  	Test 2
        0ms  	Test 3
            0ms  	Test 4
    0ms  	Function start
        0ms  	Complex logic
    0ms  	Mismatch
    

    Here’s the full script:

    // ========== Profiler Start ========== //
    // NAME: Profiler
    // VERSION: 1.0.0
    /**
     * Profiler module stores timing data.
     */
    if (profiler == undefined)
    
    var profiler = (function() {
    	let stack = [];
    	let entries = [];
    	let index = 0;
    	let indent = 0;
    	let current = undefined;
    	return {
    		custom: function(start, end, message)
    		{
    			entries[index] = {'start': start, 'end': end, 'message': message, 'index': index, 'indent': indent};
    			index++;
    		},
    		/**
    		 * Start a timer.
    		 *
    		 * Takes a <code>message</code> and adds it to the internal <code>logdata</code> variable
    		 *   and also logs it to the console with a timestamp.
    		 */
    		start: function(message)
    			{
    				let now = new Date();
    				stack.push({'start': now.getTime(), 'message': message, 'index': index, 'indent': indent});
    				index++;
    				indent++;
    			},
    
    		/**
    		 * Log an error
    		 *
    		 * Takes a <code>message</code> and <code>error</code> object and then formats into an error
    		 *   via <code>logMessage</code>.
    		 */
    		end: function(endCount = 1)
    			{
    				for (let i = 0; i < endCount; i++)
    				{
    					let now = new Date();
    					let entry = stack.pop();
    					if (entry)
    					{
    						entry['end'] = now.getTime();
    						entries[entry.index]= entry;
    						indent--;
    					}
    					else
    					{
    						console.log('Uneven profiler.end called (are you missing a start?)');
    					}
    				}
    			},
    
    		/**
    		 * Get the internal log buffer
    		 *
    		 * Returns the raw log buffer to output elsewhere.
    		 */
    		dump: function(autoClose = true)
    			{
    				let returnValue = [];
    				returnValue.push(<code>Dump called with index ${index} and indent ${indent}</code>);
    				let dumpIndent = indent;
    				
    				if (indent > 0)
    				{
    					if (!autoClose)
    					{
    						throw Error("Profiler imbalance: started called " + indent + " more times than end.");
    					}
    					else
    					{
    						// Close out any mismatched items.
    						this.end(indent);
    					}
    				}
    				
    				let entry = null;
    				for(entry of entries)
    				{
    					let message = (entry.end - entry.start) + 'ms  \t' + entry.message;
    					returnValue.push(message.padStart(message.length + (entry.indent * 4)));
    				}
    				return returnValue.join('\n');
    			},
    
    		/**
    		 * Clear the internal state.
    		 */
    		reset: function()
    			{
    				stack = [];
    				entries = [];
    				index = 0;
    				indent = 1;
    			}
    	}
    })();
    
    // Testing section. Fake guard for testing.
    if (typeof PARENT_SCRIPT === 'undefined')
    {
    	profiler.start('Test 1');
    		profiler.start('Test 2');
    		profiler.end();
    		profiler.start('Test 3');
    			profiler.start('Test 4');
    			profiler.end();
    		profiler.end();
    	profiler.end();
    	profiler.start('Function start');
    	profiler.start('Complex logic');
    	profiler.end(2);
    	profiler.start('Mismatch');
    	try 
    	{
    		profiler.dump(false);
    	}
    	catch (e)
    	{
    		console.log('Caught expected error from dump: ' + e);
    	}
    	// Call end one too many times.
    	profiler.end(2);
    	
    	console.log(profiler.dump());
    	profiler.reset();
    }
    // ========== Profiler End ========== //
    

    Latest version of this script is available on GitHub: https://github.com/pasamio/tftools/blob/master/scripts/js/profiler.js

    #39643
    Sam Moffatt
    Participant

    I’ll walk through this a little as this is my bulk scan script. It’s probably a bit much but it has a bunch of piece that might help.

    document.getFormNamed('Script Manager').runScriptNamed('Prompter Functions');
    

    This is pulling in my Prompter Functions via the Script Manager method. Basically Script Manager is a Tap Forms form named ‘Script Manager’ and then it uses ‘Form Scripts’.

    var tracking_number_id = 'fld-c487390743c947969cbe661cff596855';
    var received_date_id = 'fld-e3e3539ee04f4cc7971c7098c572104d';
    var confirmed_id = 'fld-2adb9ba8cdd048bbbb614d46b415ada5';
    var alternate_tracking_numbers_id = 'fld-cf8718051bea4cc2aba0069ae76f32b7';
    var alternate_tracking_number_id = 'fld-7342203d8f36415191bf8419fb6f70dc';
    var carrier_id = 'fld-0950c430cb0c41f79c51d43a544b366b';
    var zip_code_id = 'fld-4f73faa8937446a0a3b24e6dd4624d6b';
    var shipper_id = 'fld-1789ae45fd1f4f588294eff0c2fb6045';
    

    These are all of the field ID’s I care about, you’ll see them spliced through. Obviously your fields will be different.

    function findRecord(targetTrackingNumber)
    {
    	var targetRecord = null;
    	MainLoop:
    	for(candidateRecord of form.getRecords())
    	{
    		if (targetTrackingNumber == candidateRecord.getFieldValue(tracking_number_id))
    		{
    			targetRecord = candidateRecord;
    			break MainLoop;
    		}
    		
    		
    		for (alternateRecord of candidateRecord.getFieldValue(alternate_tracking_numbers_id))
    		{
    			if (targetTrackingNumber == alternateRecord.getFieldValue(alternate_tracking_number_id))
    			{
    				targetRecord = candidateRecord;
    				break MainLoop;
    			}
    		}
    	}
    	
    	return targetRecord;
    }
    

    This is a quick method to basically brute force scan all of the records and search for them. I mentioned earlier that Brendan is adding a function to make this a little less relevant however what it’s doing is getting a list of all of the records, checking if the tracking number matches (in your case it’ll be your box barcode or item barcode) and it also checks the values of a table field that is in my shipments form. Because there are two levels of loops in this, I use a trick to make sure I break out of them easily. Shipping is fun where a package might have more than one tracking number, hence the crazy setup.

    async function start(){
    	try {
    		while (1)
    		{
    

    This is the start of the meat and it has three things to be interested in. The async keyword is required to get my prompter functions to work properly and enable you to do blocking calls without having to manage the callbacks all yourself. The try is a fallback for an exceptions that might escape and the while (1) is our infinite loop.

    			let zipcode = undefined;
    			let carrier = '';
    			let barcode = '';
    			let scanBarcode = await promptText("Scan Shipment", "Barcode:");
    

    This sets up some variables, in my case I’m tracking zipcode for the package (USPS embed this into their tracking number), a carrier that I can automatically infer (USPS and UPS) and then obviously the core barcode. This uses the promptText function which is in that Prompter Functions I mentioned earlier. It has await prefixed to make it block until we get a response back.

    			if (!scanBarcode)
    			{
    				break;
    			}
    

    This is pretty straight forward, if we don’t get anything back from the barcode then we abort the loop. This is our exit criteria.

    			let matches = scanBarcode.match(/420(9[0-9]{4})(.*)/);
    			if (matches)
    			{
    				carrier = 'USPS';
    				zipcode = matches[1];
    				barcode = matches[2];
    			}
    			else
    			{
    				barcode = scanBarcode;
    			}
    

    This isn’t relevant to you as much but it basically looks for a USPS barcode and extracts the useful information out of it. It resets the barcode to be what the label and tracking number actually is versus the full details that are encoded into the barcode. I’m in California so my zipcode is prefixed with a 9 which is why it looks the way it does.

    			matches = scanBarcode.match(/^1Z/);
    			if (matches)
    			{
    				carrier = 'UPS';
    			}
    

    This also isn’t as relevant but it looks for a UPS style barcode and sets it automatically. Depending on how your barcode generation is done, this might be something you can apply where you have different barcode prefixes for stuff (or not).

    			console.log(barcode);
    			
    			let targetRecord = findRecord(barcode);
    

    I logged the barcode because I wanted to see what it was in the console. This is useful for understanding when something weird happens. This is then fed into that findRecord method before to find a record that matches, or not.

    			if (targetRecord)
    			{
    				// Flip confirmed flag but otherwise leave it alone.
    				targetRecord.setFieldValue(confirmed_id, true);
    				document.saveAllChanges();
    				console.log("Updated existing record for " + barcode);
    			}
    

    For me I’m looking to confirm or create a record that I’m scanning. In this case if for some reason the shipping number already exists and I found a matching record, I just toggle a flag saying that I know it exists and move on.

    			else
    			{
    				let payload = {
    					[tracking_number_id]: barcode,
    					[confirmed_id]: true
    				};
    

    Ok, the else case means this tracking number doesn’t exist already so we need to create it. I start to create a new record here. This syntax with the square brackets is to get the value of tracking_number_id instead of using tracking_number_id as the key.

    				if (carrier)
    				{
    					payload[carrier_id] = carrier;
    					payload[zip_code_id] = zipcode;
    				}
    

    If there is a carrier set then this gets set up as well including the zipcode (USPS).

    				let shipper = await promptText("Enter Shipper Name", "Shipper: ");
    				console.log(shipper);
    				
    				if (shipper)
    				{
    					payload[shipper_id] = shipper;
    				}
    

    I ask for the shipper name in case that’s obvious, again with the promptText method. That’s useful for knowing where something is from if I want to add it in.

    				console.log(JSON.stringify(payload));
    
    				let newRecord = form.addNewRecord();
    				newRecord.setFieldValues(payload);
    				document.saveAllChanges();
    

    I log out what I’m about to create to see what it is during debugging. I then create a new record, use setFieldValues to set up the values and then save the changes. Too easy!

    			}
    		}
    	} catch (error) {
    		console.log("Error: " + error);
    	}
    }
    
    start();

    This is closing everything out and then triggering the script to begin with. The catch is the follow up to the try and is a fail safe to log the message and go from there. It’s closing out the loop so assuming you enter in a valid barcode, it’ll keep looping until it’s done.

    I thought I had another script that handled a little closer to what you were doing but I can’t find where I put it.

    Here’s the script in full:

    document.getFormNamed('Script Manager').runScriptNamed('Prompter Functions');
    
    var tracking_number_id = 'fld-c487390743c947969cbe661cff596855';
    var received_date_id = 'fld-e3e3539ee04f4cc7971c7098c572104d';
    var confirmed_id = 'fld-2adb9ba8cdd048bbbb614d46b415ada5';
    var alternate_tracking_numbers_id = 'fld-cf8718051bea4cc2aba0069ae76f32b7';
    var alternate_tracking_number_id = 'fld-7342203d8f36415191bf8419fb6f70dc';
    var carrier_id = 'fld-0950c430cb0c41f79c51d43a544b366b';
    var zip_code_id = 'fld-4f73faa8937446a0a3b24e6dd4624d6b';
    var shipper_id = 'fld-1789ae45fd1f4f588294eff0c2fb6045';
    
    function findRecord(targetTrackingNumber)
    {
    	var targetRecord = null;
    	MainLoop:
    	for(candidateRecord of form.getRecords())
    	{
    		if (targetTrackingNumber == candidateRecord.getFieldValue(tracking_number_id))
    		{
    			targetRecord = candidateRecord;
    			break MainLoop;
    		}
    		
    		
    		for (alternateRecord of candidateRecord.getFieldValue(alternate_tracking_numbers_id))
    		{
    			if (targetTrackingNumber == alternateRecord.getFieldValue(alternate_tracking_number_id))
    			{
    				targetRecord = candidateRecord;
    				break MainLoop;
    			}
    		}
    	}
    	
    	return targetRecord;
    }
    
    async function start(){
    	try {
    		while (1)
    		{
    			let zipcode = undefined;
    			let carrier = '';
    			let barcode = '';
    			let scanBarcode = await promptText("Scan Shipment", "Barcode:");
    			
    			if (!scanBarcode)
    			{
    				break;
    			}
    
    			let matches = scanBarcode.match(/420(9[0-9]{4})(.*)/);
    			if (matches)
    			{
    				carrier = 'USPS';
    				zipcode = matches[1];
    				barcode = matches[2];
    			}
    			else
    			{
    				barcode = scanBarcode;
    			}
    			
    			matches = scanBarcode.match(/^1Z/);
    			if (matches)
    			{
    				carrier = 'UPS';
    			}
    			console.log(barcode);
    			
    			let targetRecord = findRecord(barcode);
    				
    			if (targetRecord)
    			{
    				// Flip confirmed flag but otherwise leave it alone.
    				targetRecord.setFieldValue(confirmed_id, true);
    				document.saveAllChanges();
    				console.log("Updated existing record for " + barcode);
    			}
    			else
    			{
    				let payload = {
    					[tracking_number_id]: barcode,
    					[confirmed_id]: true
    				};
    				
    				if (carrier)
    				{
    					payload[carrier_id] = carrier;
    					payload[zip_code_id] = zipcode;
    				}
    
    				let shipper = await promptText("Enter Shipper Name", "Shipper: ");
    				console.log(shipper);
    				
    				if (shipper)
    				{
    					payload[shipper_id] = shipper;
    				}
    				
    				console.log(JSON.stringify(payload));
    
    				let newRecord = form.addNewRecord();
    				newRecord.setFieldValues(payload);
    				document.saveAllChanges();
    			}
    		}
    	} catch (error) {
    		console.log("Error: " + error);
    	}
    }
    
    start();
    #39641
    Sam Moffatt
    Participant

    Credit to @daniel_leu for this originally and I think a variant of them will be coming in the sample scripts. These are the two that I use personally and I figured I’d share them. I added this in my “Script Manager” form as “Prompter Functions”, so if you have that included already then you can add this as a new one.

    If you’re going to use these, the calling function needs to be prefixed with async to make it work properly.

    // ========== Prompter Functions Start ========== //
    // NAME: Prompter Functions
    // VERSION: 1.0
    /**
     * Prompter Functions for using the async system to handle some requests.
     */
     
    // Temporary variable to store callback details.
    var prompterTempVar = undefined;
    
    /**
     * Prompt a confirmation dialog with a simple message.
     *
     * NOTE: This method uses async, please make sure your calling method is declared 'async'.
     *
     * @param {string} text - The text to display in the dialog for the confirmation.
     * @param {string} continueTitle - The text for the continue button.
     * @param {string} cancelTitle - The text for the cancel button.
     *
     * @return {boolean} Boolean result if the user confirmed or declined the dialog.
     */
    function promptConfirm(text, continueTitle = 'Continue', cancelTitle = 'Cancel') {
    	return new Promise(function(resolve, reject) {
    	  	let prompter = Prompter	.new();
      		prompter.cancelButtonTitle = cancelTitle;
    		prompter.continueButtonTitle = continueTitle;
    		prompter.show(text, ((status) => {
    			if (status == true) {
    				resolve(true);
    			} else {
    				resolve(false);
    			}		
    		}));
    	});
    }
    
    /**
     * Prompt 
     *
     * NOTE: This method uses async, please make sure your calling method is declared 'async'.
     *
     * @param {string} text - The text to display in the dialog for the confirmation.
     * @param {string} popupName - The prefix to display for the text box of the prompt.
     * @param {string} [continueTitle=Continue] - The text for the continue button.
     * @param {string} [cancelTitle=Cancel] - The text for the cancel button.
     *
     * @return {(string|boolean)} The input text or false if cancelled.
     */
    function promptText(text, popupName, continueTitle = 'Continue', cancelTitle = 'Cancel'){
    	return new Promise(function(resolve, reject) {
    		prompterTempVar = undefined;
    	  	let prompter = Prompter	.new();
    	  	prompter.addParameter(popupName, 'prompterTempVar')
      		prompter.cancelButtonTitle = cancelTitle;
    		prompter.continueButtonTitle = continueTitle;
    		prompter.show(text, ((status) => {
    			if (status == true && prompterTempVar) {
    				resolve(prompterTempVar);
    			} else {
    				resolve(false);
    			}		
    		}));
    	});
    }
    // ========== Prompter Functions End ========== //
    

    You can also get a copy of this file here: https://github.com/pasamio/tftools/blob/master/scripts/js/prompter.js.

    #39640
    Max Heart
    Participant

    Hi again,

    I just followed your advise and for the manual quick linking of items from box to checkout and back it works. Thank you very much for this part.

    Now it would be awesome to run this procedure script based: Scan a box and keep assigning items to this box by batch scanning their barcodes until the script is stopped.

    So the script should do this:
    1) Ask for a box to scan
    2) Pull the box record from the box form
    3) Prompt for barcode
    4) Read barcode, assign the item to box, best with short feedback for 3-5 seconds “Item XX added to box”.
    5) Ask for next barcode
    6) Repeat procedure until stopped.

    @Sam: Is this what your shipment script does?

    #39639
    Max Heart
    Participant

    Hi Sam,

    thank you very much for the advise. I am happy to hear that my objective should be reachable. When you post your script can you please give me a hint on how to get it running? I haven’t worked with scripts in Tapform yet. I know that “script” is a field type. Is it enough to just paste a script there?

    Best, Max

    #39638
    Sam Moffatt
    Participant

    I have recently been working on something like that to scan a bunch of items into boxes and similar. There are a couple of ways of handling it.

    I’m going to rephrase some of your questions:

    Can I pull up a box by scanning it’s barcode?

    This one is relatively straight forward application of the search functionality, that’s easy out of the box regardless of what platform you’re on. On the desktop you’ll need a barcode scanner to make that work but on iOS the built in barcode scanning functionality will make that work for you there.

    Can I scan barcodes of items as I place them into the box?

    The way I did this in January when I was doing a lot of box packing for moving was to create a second form called “Box Items” that is linked from my main “Box” form and then created child records. I was taking pictures of the item and adding some extra metadata in this case so I just used the built in Tap Forms barcode scanner to scan items and used voice to text to quick enter titles (worked enough times to save me having to type).

    Now if you invest in a barcode scanner, you can take it a little bit further and create a prompter loop to scan barcodes and automatically create new child records. You can also pivot and if you’re confident an item will only be in a single box, you can make it’s box barcode a property of the item. I have a prompter loop I use for bulk scanning shipments, I’ll post that on the Script Talk forum later.

    Can I checkout of a box?

    If you go with the Link to Form and enable show inverse relationship (or what ever that checkbox is called), then when you scan in your item in Tap Forms using the search feature it will show you the record and the “Link From Form” will show you the details of the box that it thinks it should be in. In Tap Forms on the desktop you can select this and change it to search for a new box which you should be able to do by barcode for your checkout box or just have a box named “checkout”. By changing the value of the Link from Form field parent will register it to a new box. The iOS version has a similar interaction here but I feel that it isn’t quite as smooth as the desktop more due to the limitations of iOS rather than Tap Forms (the desktop shows a popover which makes it much easier to interact with).

    If you go for the field based approach, you just need to update the field to where ever you put the item.

    In both cases you can script it to handle it easier. Scripting the Link to Form/Link from Form is a little more difficult because in the current production release of Tap Forms there is no search by text via the script interface. Brendan mentioned that he’s adding a form.getRecordsForSearchTerm() to the next beta which would make that easier because the JavaScript API for the link fields works with records. Until then, it’s a little easier to update a simple text field based on a search.

    I have a dedicated barcode scanner for the desktop, I bought a NETUM Bluetooth scanner that has both a USB cable for input and can pair via Bluetooth. I’m not sure I’d recommend it, it works fine in Bluetooth mode but when I plug in the USB cable it mangles the numbers some how (it’s like it’s pressing on the keypad numbers upside down). On the Mac I use Karabiner Elements whose mere existence seemed to make the scanner behave properly.

Viewing 15 results - 2,041 through 2,055 (of 2,989 total)