<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>FileMaker Inspirations</title>
	<atom:link href="http://filemakerinspirations.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://filemakerinspirations.com</link>
	<description>Advanced FileMaker tips and techniques.</description>
	<lastBuildDate>Sat, 14 Dec 2024 17:51:17 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>How To Merge FileMaker Files</title>
		<link>http://filemakerinspirations.com/2011/10/how-to-merge-filemaker-files/</link>
					<comments>http://filemakerinspirations.com/2011/10/how-to-merge-filemaker-files/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Fri, 28 Oct 2011 06:00:48 +0000</pubDate>
				<category><![CDATA[Custom Functions]]></category>
		<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Scripting]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=428</guid>

					<description><![CDATA[You may already know that merging FileMaker Pro database files can be a tedious and challenging process. After copying, you could spend hours fixing broken links, missing fields, remapping buttons, etc. For this reason, many people simply choose to maintain multiple files even when that may not be the ideal or preferred setup. With the [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><img fetchpriority="high" decoding="async" src="http://filemakerinspirations.com/images/graphic_mergefolder.jpg" alt="" width="480" height="360" /></p>
<p style="text-align: left;">You may already know that merging FileMaker Pro database files can be a tedious and challenging process. After copying, you could spend hours fixing broken links, missing fields, remapping buttons, etc. For this reason, many people simply choose to maintain multiple files even when that may not be the ideal or preferred setup.</p>
<p>With the method outlined below, you can merge databases with a minimal amount of broken links. There may still be some tedious grunt work to do, but by following these steps in the order given, you can significantly reduce the time and tedious tasks involved.</p>
<div class="note"><strong>Note:</strong> This article was based on FileMaker Pro 11 Advanced, and the same process still applied for FileMaker Pro 14 Advanced. If you have an older version of FileMaker or do not have the Advanced version, you may need to work a little harder.</div>
<h1>The Quick Step-by-Step List</h1>
<p>In general, when copying functionality from one FileMaker file to another, you will want to do so in the following order:</p>
<ol>
<li>Rename table occurrences and fields in source file (throwaway file) to match target file.</li>
<li>Copy any necessary custom functions</li>
<li>Create any necessary value lists. These can be blank for now.</li>
<li>Copy tables and relationships.</li>
<li>Copy any necessary fields into existing tables.</li>
<li>Check Calculated Fields (check the Import.log file or use the <a title="Import File Interpreter for FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/import-log-interpreter-for-filemaker-pro" target="_blank">Import Log Interpreter</a> tool)</li>
<li>Copy the value lists</li>
<li>Create Blank Layouts</li>
<li>Copy Scripts</li>
<li>Copy Layout Objects and Recreate Layout-Based Script Triggers</li>
</ol>
<p>This will insure that any buttons, calculations or other objects that rely on other objects will maintain their links. See more details below.</p>
<h2>Step 1: Rename table occurrences and fields</h2>
<p>The main idea behind this step is that you want to make sure that the two files have matching table occurrence and field names. This way when you copy scripts and other objects from one file to another, it will properly map the tables and fields. You don&#8217;t want to have to go through each layout and script and fix all of the &#8220;&lt;field missing&gt;&#8221; or &#8220;&lt;table missing&gt;&#8221; errors manually.</p>
<p>At this point, you are not copying any fields or tables yet, you simply want to make sure than any of the table occurrences and fields that already exist in your target database match the corresponding table occurrences and fields in the source file (the one you will be throwing away).</p>
<h2>Step 2: Copy custom functions</h2>
<p>If there are any custom functions in the source file that do not exist in the target file, copy or import them from the source to the the target file. We do this now in case custom functions are referenced in calculated fields or script calculations. We don&#8217;t want to have to fix &#8220;&lt;function missing&gt;&#8221; errors in our calculations.</p>
<p>There are two methods to achieve this:</p>
<p style="padding-left: 30px;"><strong>Method 1: </strong></p>
<ol>
<li>Open the <em>Manage Custom Functions</em> dialog in the target file.</li>
<li>Click the Import button.</li>
<li>Browse to source file.</li>
<li>Check the functions you want to import.</li>
<li>Click OK.</li>
</ol>
<p style="padding-left: 30px;"><strong>Method 2:</strong></p>
<ol>
<li>Open the <em>Manage Custom Functions</em> dialog in the source file.</li>
<li>Select the custom functions you want to copy (use CMD or CNTRL click to select multiple).</li>
<li>Select Edit-&gt;Copy or CMD or CNTRL-C to copy.</li>
<li>Close the dialog and optn eht <em>Manage Custom Functions</em> dialog in the target file.</li>
<li>Select Edit-&gt;Paste or CMD or CNTRL-V to paste.</li>
</ol>
<div class="note">
<p><strong>Important Tip:</strong> Whenever you import or copy functions, scripts, script steps, tables or fields from one database file to another, FileMaker will create a file called Import.log. For local files, it will place this file in the same folder/directory as the database file that is being copied or imported to. For remote files, it will place the file in your documents folder (ever wonder why those little Import.log buggers keep appearing in your folders?).</p>
<p>Open this file and scroll to the end to see the results of your import and whether any errors had occurred. Alternatively, you can use the <a title="Import File Interpreter for FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/import-log-interpreter-for-filemaker-pro" target="_blank">Import Log Interpreter</a> tool to easily browse through the import results, see exactly what was imported and see the errors associated with each object in an easy to read format.</p>
<p>I can&#8217;t stress enough the importance of reading the little Import.log file, whether you do it manually or using the Import Log Interpreter tool. It will be you&#8217;re friend throughout this process. Although, you can check all your calculations and script steps manually, not only will that take you a really, really long time for any complex database, but the likelihood is very high that you will miss at least one little problem that produces troubleshooting headaches for you in the future.</p>
</div>
<h2>Step 3: Create value lists</h2>
<p>If there are any values lists in the source file that do not exist in the target file, create them in the target file. It is not necessary at this point to define the values for the value lists; they can be empty for now. For value lists that are populated from table data, it won&#8217;t be possible to define the values. At this point, empty value lists will work fine. We want to do this now before we copy tables and fields in case any of the fields use value lists for validation.</p>
<p>I&#8217;ve found that the easiest way to do this is to grab a screenshot of the <em>Define Value Lists</em> dialog in the source file and open the <em>Define Value Lists</em> dialog in the target file. Create the value lists one by one while going down the list in the screen shot.</p>
<h2>Step 4: Copy tables and create relationships</h2>
<p>Now, we&#8217;re ready to copy any tables that exist in the source database, but not in the target database, and set up the table occurrence relationships for those tables. Copying the tables is the easy part, but unfortunately, there is no shortcut for replicating the relationships. Here&#8217;s my favorite method:</p>
<h3>Inspect and take notes on the existing relationships:</h3>
<ol>
<li>Open the <em>Define Database</em> dialog in the source file.</li>
<li>Print the relationship graph from the source table.</li>
<li>Manually inspect the relationships in the source file relating to the tables to be moved moved.
<div class="note"><strong>Hint:</strong> Select a table occurrence of your source table and press CMD or CNTRL-U on your keyboard. FileMaker will automatically select all occurrences of that source table in the relationship graph so you can quickly see all the places that table is referenced in the graph.</div>
</li>
<li>Take notes on the printed relationship graph regarding which relationships need to be copied and the properties of those relationships.</li>
</ol>
<h3>Copy the tables:</h3>
<p>The easy part! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<ol>
<li>Under the tables tab, select the tables to copy.</li>
<li>Click the <em>Copy</em> button near the bottom of the window</li>
<li>Close that dialog and open the <em>Define Database</em> dialog in the target file</li>
<li>Click the <em>Paste</em> button.</li>
</ol>
<h3>Create relationship:</h3>
<p>Now, set up the relationships to match those in the source file:</p>
<ol>
<li>With the <em>Manage Database</em> dialog still open, add each of the table occurrences and relationships that you noted on the printout to the target file.</li>
</ol>
<p>It may only be one step, but depending on the complexity of the relationships you need to replicate, it could be the most time consuming part of this whole process.</p>
<h2>Step 5: Copy necessary fields into existing tables</h2>
<p>If there are tables in your target database that will be used in place of tables that existed in the source table, copy them from the table in the source database to the table in your target database.</p>
<p>For each table that has fields that need to be copied. Do the following:</p>
<ol>
<li>In the source file, open the Manage Database dialog and go to the field list of the table with fields to be copied.</li>
<li>Click to select each field you need to copy. CNTRL or CMD-Click to select multiple.</li>
<li>Click the Copy button in the lower right area of the window.</li>
<li>Close the dialog (you can click Cancel since you made no changes).</li>
<li>In the target file, open the Manage Database dialog and go to the field list of the table for the fields to be pasted into.</li>
<li>Click the Paste button.</li>
</ol>
<h2>Step 6: Check Calculated Fields</h2>
<p>The tables and fields copied in steps 4 and 5 could have problems if any of the following occurs:</p>
<ul>
<li>Field name mismatch</li>
<li>Table occurrence name mismatch</li>
<li>Missing relationship</li>
<li>Missing custom function</li>
<li>Mismatch in custom function name</li>
</ul>
<p>The easiest way to find and fix these is to check the Import.log file. You can do this manually with a text editor or with the <a title="Import File Interpreter for FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/import-log-interpreter-for-filemaker-pro" target="_blank">Import Log Interpreter</a> tool.</p>
<p>Alternatively (though not recommended &#8211; see the Important Tip in Step 2), you could use the brute force method and simply check every field and table that you copied. Double check any fields that use calculations or lookups for table missing or field missing warnings. Also check for calculations that are completely surrounded by block comments: i.e. &#8220;/*&#8221; at the beginning and &#8220;*/&#8221; at the end (without the quotes). This indicates that FileMaker could not resolve all of the references in the calculation. Remove the block comments and click OK to save the calculation. At this point, FileMaker will probably give you an error message highlighting the problem with the calculation. You can then do some troubleshooting.</p>
<div class="note"><strong>Alert</strong>: You may be tempted to glaze over this step, but don&#8217;t give in to that temptation. One small mismatch can break your buttons, layout objects and scripts. Believe me when I say that, fixing these problems now is much easier than fixing them after you&#8217;ve copied your scripts and layouts over, even if that means backing up a few steps before moving on.</div>
<h2>Step 7: Copy the value lists</h2>
<p>Now that the table occurrences and fields are in place, you can set up the value lists that depend on them. Depending on how many you have, this can be a tedious process. Here is the most efficient method I&#8217;ve come up with:</p>
<ol>
<li>Open the Define Value Lists dialog in the source database.</li>
<li>Open each value list you want to copy.</li>
<li>Take notes in a simple text document about each value list. Either note the relationship used and the appropriate settings or copy the list of custom values. Alternatively, you can take a screen shot of each value list dialog and the corresponding &#8220;Specify Field&#8230;&#8221; dialog and paste it into your notes document. If you choose this route, be sure to also copy and paste custom values text so you can easily copy and paste them into the target file.</li>
<li>Open the <em>Define Value Lists</em> dialog in the target database.</li>
<li>Go through each of the value lists you added here in Step 3 and set them up based on your notes in the notes file.</li>
</ol>
<p>Using this method is much more efficient than going back and forth between the files to copy each value list separately.</p>
<h2>Step 8: Create Blank Layouts</h2>
<p>Create a blank layout in the target file for each layout that exists in the source file if it does not already exist in the target file. Do <span style="text-decoration: underline;">not</span> copy the objects from the layouts at this time. Many layout objects will depend on scripts and other layouts which have not yet been copied. The most important factors at this point are the layout names and the table occurrence.</p>
<p>One efficient way to accomplish this is to open the <em>Manage Layouts</em> window in the both files and position them side-by-side. From there, you can go down the list in the source file and create the layouts in the target file.</p>
<p>Be on the lookout for duplicate layout names and change them where necessary. You want them all to be unique. And, if you change any of the layout names, be sure to do it in both files. They <span style="text-decoration: underline;">must</span> match.</p>
<h2>Step 9: Copy Scripts</h2>
<p>Now we&#8217;re ready to copy the scripts. At the surface, this process is quite simple, though you will want to spend some time double checking the Import.log file or using the <a title="Import File Interpreter for FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/import-log-interpreter-for-filemaker-pro" target="_blank">Import Log Interpreter</a> tool.</p>
<p>There are two methods.</p>
<ul>
<li>Method one: open the <em>Manage Scripts</em> windows in both files, select and copy the scripts from the source file and paste them in the target file.</li>
<li>Method two: click the import button near the bottom of the <em>Manage Scripts</em> window in the target file, navigate to the source file, check the scripts to copy and click <em>OK</em>.</li>
</ul>
<p>Whichever method you use, be sure to copy all of the necessary scripts <span style="text-decoration: underline;">at one time</span>. Doing so will assure that any scripts that call other scripts will find their subscripts.</p>
<p>Once the scripts are copied, you&#8217;ll want to go through the Import.log file to make sure that everything copied correctly. Things to look out for are broken layout, field and table references.</p>
<h2>Step 10: Copy Layout Objects and Recreate Layout-Based Script Triggers</h2>
<p>This step is one of the more tedious steps in the process. You will need to go through each layout you added in Step 8, and modify the layout in the target file to match those in the source file.</p>
<p>As in Step 8, you can open the <em>Manage Layouts</em> window in both files and position them side-by-side. Then, go down the list and open each matching layout side-by-side.</p>
<p>For each layout, you will do the following:</p>
<ol>
<li>Add and adjust all the layout parts in each layout to match the source file. Using the Inspector, you&#8217;ll want to make sure the header, body, footer, etc. are all the exact same size: to the pixel. And create the necessary sub-summary parts to match those in the source file.</li>
<li>Copy and paste all the objects from each layout in the source file to the corresponding layout in the target file. Simply select all in the source file. In the inspector window, notice the exact Left and Top positions of the selected objects. Copy. Paste into the layout in the target file and enter the Left and Top coordinates in the inspector window to match what was in the source file.</li>
<li>Open the Layout Setup dialog in the source file and check the Script Triggers tab. If there are any defined, add them to the target file as well.</li>
</ol>
<p>Do this for each layout and then&#8230;</p>
<h2>You&#8217;re Done!</h2>
<p>You should now have a single FileMaker file with all the functionality that existed in the two separate files. However, I would strongly recommend you do some serious testing at this point to make absolute certain everything copied over properly.</p>
<h2>The Future: Will This Get Any Easier?</h2>
<p>This article is based on the tools included in FileMaker Pro 11 Advanced. The ability to import custom functions, tables and scripts is a recent addition to the FileMaker features list. One can only assume that it will just be a matter of time before tools are included to import layouts, value lists and possibly even relationships. But, we won&#8217;t know until that happens. So, for now, this is the process. It&#8217;s easier than it used to be, but harder than it might someday be.</p>
<h2>And finally&#8230;</h2>
<p>As always, if you have any questions or comments or could use some help, feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contact-us/contact-form?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2011/10/how-to-merge-filemaker-files/feed/</wfw:commentRss>
			<slash:comments>19</slash:comments>
		
		
			</item>
		<item>
		<title>Instant Messaging In FileMaker &#8211; No Plugins or Internet Access Required</title>
		<link>http://filemakerinspirations.com/2011/08/instant-messaging-in-filemaker-no-plugins-or-internet-access-required/</link>
					<comments>http://filemakerinspirations.com/2011/08/instant-messaging-in-filemaker-no-plugins-or-internet-access-required/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Fri, 05 Aug 2011 13:15:56 +0000</pubDate>
				<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Web Viewers]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=347</guid>

					<description><![CDATA[Note: This article was written many years ago (2011), and although the basic principles still apply, the solution may not work perfectly with the newest versions of FileMaker. It shows you how to build a product that we sell: Triple-i Messenger. The product has since been updated and is now more feature rich, cleaner and [&#8230;]]]></description>
										<content:encoded><![CDATA[<div class="note"><strong>Note:</strong> This article was written many years ago (2011), and although the basic principles still apply, the solution may not work perfectly with the newest versions of FileMaker. It shows you how to build a product that we sell: <a title="Triple-i Messenger - Internal Instant Messaging in FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/triple-i-messenger">Triple-i Messenger</a>. The product has since been updated and is now more feature rich, cleaner and easier to integrate. If you are interested in seeing the updated version of this solution along with integration instructions, visit the <a title="Triple-i Messenger - Internal Instant Messaging in FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/triple-i-messenger">Triple-i Messenger</a> page. If you&#8217;re running an older version of FileMaker (11 for instance) and are interested in the exact solution featured in this article, click here to <a class="ec_ejc_thkbx" title="Triple-i Messenger - Purchase &amp; Download Immediately" href="https://www.e-junkie.com/ecom/gb.php?c=cart&amp;i=IBIIM270&amp;cl=190732&amp;ejc=2" target="ej_ejc" rel="noopener">download the legacy version now</a>.</div>
<div style="width: 593px" class="wp-caption aligncenter"><img decoding="async" title="Messenger Window" src="http://filemakerinspirations.com/images/messengerpopup.gif" alt="With a FileMaker Pro Web Viewer and a some JavaScript, we can create a FileMaker Pro native internal instant messaging system that updates every second: no plugins, no distracting refreshes, no affect on performance and no internet access required." width="583" height="523" /><p class="wp-caption-text">With a FileMaker Pro Web Viewer and some JavaScript, we can create a FileMaker Pro native internal instant messaging system that updates every second: no plugins, no distracting refreshes, no affect on performance and no internet access required.</p></div>
<p>This article follows up on 2 previous articles discussing dynamic multi-threading with web viewers: <a title="Countdown Timers – Multi-Threading With Web Viewers" href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">Timers</a> and <a title="Alarm Clocks – Dynamic Multi-Threading with Web Viewers" href="http://filemakerinspirations.com/2011/07/alarm-clocks-dynamic-multi-threading-with-web-viewers/">Alarm Clocks</a>. I recommend that you read those articles first if you have not already, especially <a title="Countdown Timers – Multi-Threading With Web Viewers" href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the first one on Timers</a>, as it covers the basics of this technique using web viewers, URIs and JavaScript.</p>
<h1><a name="basics"></a>The Basics</h1>
<p>In <a title="Countdown Timers – Multi-Threading With Web Viewers" href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the Timer article</a>, I discuss the three main components that are the foundation of this technique: data URIs, including FileMaker Pro data in a web viewer and JavaScript. Click here to view the basics of this technique of <a title="Countdown Timers – Multi-Threading With Web Viewers" href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">dynamic multi-threading with web viewers</a>.</p>
<p>In short, we will use a Data URI to specify the HTML and JavaScript we want to display in the web viewer, and we&#8217;ll include data from our FileMaker Pro database in that code. We will use JavaScript to build and refresh the HTML dynamically.</p>
<h1><a name="project"></a>The Instant Messenger Project</h1>
<div class="note"><strong>Note:</strong> This is a rather lengthy and somewhat advanced project, especially for a blog post. You may want to print this (or print to a PDF) if that is easier for you. When printing articles on this site, there will be no sidebar or other irrelevant stuff, and the article will fill the full width of the page. You will, however, want to keep this page open (or bookmarked), so you can copy the calculations. They can sometimes get clipped from the printout.<br />
I also recommend that you skim through the entire article before beginning so you know what you&#8217;re getting into and can plan accordingly.</div>
<h2>Step 1 &#8211; Create Messenger Table</h2>
<p>In this step, we create a simple table in our FileMaker Pro database that stores our messenger communications.</p>
<p>Our Messenger table will have the following fields:</p>
<p><strong>__pk_MessengerID</strong> &#8211; Number &#8211; Auto enter Serial. This is the primary key of the table. Though we don&#8217;t actually use this field for any relationships or other functionality, it is always good practice to include a primary key field in every table that you create. This way, if you ever need to isolate a record it in a relationship or script, you will have a way.</p>
<p><strong>_fk_Username~from</strong> &#8211; Text. This holds the user account name of the logged in user who sent the message.</p>
<p><strong>_fk_Username~to</strong> &#8211; Text. This holds the user account name of the message recipient.</p>
<p><strong>_fmk_Usernames</strong> &#8211; Calculation (Text). This field is used for relationships and holds the values of both of the above usernames in a list. The calculation is as follows:</p>
<pre>_fk_Username~from &amp; ¶ &amp; _fk_Username~to</pre>
<p><strong>Message</strong> &#8211; Text. The text of the message.</p>
<p><strong>Status</strong> &#8211; Text. This field will be used to flag the message as &#8220;New&#8221; or &#8220;Read&#8221;. You could simply use a number field as a flag (with values 1 or 0), but I like using the text field to allow for future expansion.</p>
<p><strong>IsNew</strong> &#8211; Calculation (Number). This field will be one if Status=&#8221;New&#8221;. It is used for relationships so FileMaker knows when you have received new messages. If you choose, as mentioned above, you could simply combine this field with the Status field. The calculation is simply as follows:</p>
<pre>Status = "New"</pre>
<p><strong>Timestamp</strong> &#8211; Timestamp &#8211; Auto-Enter Creation Date. This is the time that the message was sent.</p>
<h2>Step 2 &#8211; The User Table</h2>
<p>The basic messenger functionality could work without a user table; however, this table allows us to add a number of features such as status updates, a dynamic pop-up lists of users we can chat with and a list of user from whom we have received new messages.</p>
<p>The user table will hold information about each user who can log in to the database and use the chat functionality.</p>
<div class="note">Note: In many databases I create, I manage my users from within my FileMaker database and create the FileMaker Pro account from a script I call from the user layout. This is where I manage user permissions, set/reset passwords, etc. For the purposes of this project, we&#8217;ll just include the fields in the user table that we need for the chat functionality.</div>
<p>Our User table will have the following fields:</p>
<p><strong>Username</strong> &#8211; Text. This will essentially be the primary key of the user table record. It holds the FileMaker account name of the user.</p>
<p><strong>MessengerStatus</strong> &#8211; Text. This field holds the status of the user. We will set this in our open script and log in script (if one exists), as well as our close and log out scripts.</p>
<p><strong>MessengerDisplay</strong> &#8211; Calculation (Text). We&#8217;ll use this one in a value list of users and their statuses. The calculation is as follows:</p>
<pre>If ( not IsEmpty ( Username ) ;
 Username &amp; " (" &amp; If ( not IsEmpty ( MessengerStatus ) ; MessengerStatus ; "Status Unknown" ) &amp; ")"
)</pre>
<h2>Step 3 -The Globals Table</h2>
<p>You will need four global fields. Theoretically, you could put them in any table since they are global, but I strongly recommend that you put these in a table designated to hold only global variables.</p>
<p><strong>_gk_Username</strong> &#8211; Text (global). This will hold the user account name of the currently logged in user. In your open script and log in script (if you have one), this field should be set with the get function: <em>get ( AccountName )</em>.</p>
<p><strong>_gk_ChatUsername</strong> &#8211; Text (global). This is the user with whom the current user is currently chatting.</p>
<p><strong>gc_1</strong> &#8211; Calculation(Number). Simply holds the number 1 for relationship purposes. In the calculation field, simply enter &#8220;1&#8221; (without quotes). Note that this field should <span style="text-decoration: underline;">not</span> be set to global.</p>
<p><strong>MessengerText</strong> &#8211; Text (global). This field is used for entry of the text message before it gets sent to the recipient.</p>
<p>Once this table is created, you will need to create a new record in the table.</p>
<h2>Step 4 &#8211; Relationships</h2>
<div class="note"><strong>Note:</strong> The relationships are based on global fields and need to be set up so they are accessible from any context from which you want to allow the messenger to alert the user. I recommend creating a table for global fields that is related to your other tables based on a Cartesian join (the &#8216;x&#8217; operator).</div>
<div style="width: 597px" class="wp-caption aligncenter"><img decoding="async" class="  " title="Relationship graph." src="http://filemakerinspirations.com/images/messengerrelationships.gif" alt="In its simplest form, this is how your relationship graph will look." width="587" height="321" /><p class="wp-caption-text">In it&#8217;s simplest form, this is how your relationship graph will look.</p></div>
<p>There will be 4 occurrences of the Messenger table with three of them related to your global table (or some other table based on global fields). There will also be two occurrences of the User table related to the global table.</p>
<p>Create a relationship occurrence of the Messenger table called <strong>Messenger~new</strong>. This relationship will give us a list of messages sent to the current logged in user that have not yet been read. It is a multi-predicate relationship (2 to be exact) based on the following two pairs of fields:</p>
<p style="padding-left: 30px;">Globals::_gk_Username = Messenger~new::_fk_Username~to<br />
Globals::gc_1 = Messenger~new::IsNew</p>
<p>Create another relationship occurrence of the Messenger table called <strong>Messenger~activechat</strong>. This will give us a list of all the message interactions between the current logged in user and the user with whom the current user is chatting. It is another 2 predicate relationship based on the following two pairs of fields:</p>
<p style="padding-left: 30px;">Globals::_gk_Username = Messenger~activechat::_fmk_Usernames<br />
Globals::_gk_ChatUsername = Messenger~activechat::_fmk_Usernames</p>
<p>Finally, create one more relationship occurrence of the Messenger table called <strong>Messenger~activereceived</strong>. This will give us a list of all the messages to the current logged in user from the user with whom the current user is chatting. We will use this to mark messages as read. It is another multi-predicate relationship based on the following two pairs of fields:</p>
<p style="padding-left: 30px;">Globals::_gk_Username = Messenger~activereceived::_fk_Username~to<br />
Globals::_gk_ChatUsername = Messenger~activereceived::_fk_Username~from</p>
<p>Now we can set up the User table relationships:</p>
<p>Create a relationship between the globals table and an occurrence of the User table called <strong>User~loggedin</strong>.</p>
<p style="padding-left: 30px;">Globals::_gk_Username = User~loggedin::Username</p>
<p>Create a relationship between the globals table and an occurrence of the User table called <strong>User~other</strong>.</p>
<p style="padding-left: 30px;">Globals::_gk_Username ≠ User~other::Username</p>
<p>Note the <strong>not equal</strong> operator above.</p>
<p>Optionally, you can create a relationship between the Messenger~new table and an occurrence of the User table in order to produce a list of users from whom the current user has received new messages. We can call the new User table occurrence <strong>Messenger~new_User~from</strong> (or whatever your conventions suggest). That relationship will be based on the following:</p>
<p style="padding-left: 30px;">Messenger~new::_fk_Username~from = Messenger~new_User~from::Username</p>
<h2>Step 5 &#8211; Create Layouts</h2>
<h3>Chat Window Layout</h3>
<div style="width: 593px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" " title="Messenger layout for the messenger popup window." src="http://filemakerinspirations.com/images/messengerpopup.gif" alt="We create this layout to be used as a chat window." width="583" height="523" /><p class="wp-caption-text">We create this layout to be used as a chat window.</p></div>
<p>Our layout has three fields, four buttons, a portal (optional) and two web viewers. The context of the layout is our Globals table. We&#8217;ll give it the name &#8220;Messenger.&#8221;</p>
<h3>The 3 fields:</h3>
<p><strong>User~loggedin::MessengerStatus</strong> &#8211; In the upper left under the label, &#8220;Your Status&#8221;, is the MessengerStatus field from the User table. It is a popup menu with a value list that has a few suggested status messages: Logged In, Busy, Away, etc. And selected &#8220;Allow entry of other values.&#8221;</p>
<p><strong>Globals::_GK_ChatUsername</strong> &#8211; This field is placed to the right of the text, &#8220;Chatting with.&#8221; It is using a popup window using a value list (see below) defined to use values from Globals_User~other::Username, also displaying values from Globals_User~other::MessengerDisplay, include only related records from Globals and show only values from the second field.</p>
<div style="width: 648px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" src="http://filemakerinspirations.com/images/messengeruserlist.gif" alt="Define a value list for this field as shown above." width="638" height="642" /><p class="wp-caption-text">Define a value list for the Globals::_GK_ChatUsername field as shown above.</p></div>
<p><strong>Globals::MessengerText</strong> &#8211; This is the large text field near the bottom, to the left of the &#8220;Send&#8221; button. This is where users will type their message.</p>
<h3>The Portal (Optional)</h3>
<p>Under the yellow &#8220;Messenger/2 New Messages&#8221; box (which is flashing, btw), there is a portal showing related records from Messenger~new_User~from. The portal has a text object (just text, not a field) with the following text containing a merge field:</p>
<pre>New Msg From &lt;&lt;Messenger~new_User~from::Username&gt;&gt;</pre>
<div class="note">
<p><strong>Tip:</strong> In order to get all that text to fit inside that small space, you can shrink the font inside the merge field without affecting how it will display. The font size displayed will be that of the first &#8216;&lt;&#8216; character and the last &#8216;&gt;&#8217;. For example:</p>
<pre>New Msg From &lt;<span style="font-size: 3pt;">&lt;Messenger~new_User~from::Username&gt;</span>&gt;</pre>
<p>Also, as long as the upper left corner of the text object is within the bounds of the first portal row, it will display properly. However, if the text wraps or overflows, it may end up overlapping text in the other portal rows.</p>
</div>
<h3>4 Buttons and 2 Web Viewers</h3>
<p>These are covered in the steps 4 and 6. BTW: in case you&#8217;re wondering where the third and fourth buttons are, one is the web viewer displaying &#8220;Messenger / 2 New Messages,&#8221; the other in the text object in the portal.</p>
<h3>Dev &#8211; Messenger Layout</h3>
<p>Create a layout showing records from the Messenger table. The layout can be blank. It&#8217;s only purpose is for scripting. Call the layout &#8220;Dev &#8211; Messenger.&#8221;</p>
<h2>Step 6 &#8211; Web Viewer Setup</h2>
<p>We&#8217;ll be setting up two web viewers. One, we&#8217;ll call the <em>messenger widget</em>, the other is the <em>conversation viewer.</em></p>
<h3>The Messenger Widget</h3>
<p>Create a web viewer sized 116&#215;40 (you can do any size, but the HTML code below is optimized for this size).</p>
<div style="width: 253px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="   " title="The Messenger Widget." src="http://filemakerinspirations.com/images/messengerwidget.gif" alt="" width="243" height="43" /><p class="wp-caption-text">The widget shows the current status or the logged in user or flashes the count of incoming messages.</p></div>
<p>We can place this widget on any layout with the appropriate context and the user will be alerted to incoming messages.</p>
<p>In the web viewer setup dialog, deselect all of the check-boxes near the bottom of the dialog. For the web address, use the following code:</p>
<pre>"data:text/html,&lt;html&gt;
&lt;head&gt;
&lt;script type='text/javascript'&gt;

/*Variables set with FileMaker Pro data*/
var msgCount = '" &amp; Count ( Messenger~new::__pk_MessengerID ) &amp; "';
var recentMsgTimeTxt = '" &amp; Max ( Messenger~new::Timestamp ) &amp; "';
var status = '" &amp; User~loggedin::MessengerStatus &amp; "';
/*End FileMaker Pro Data*/

var recentMsgTime = new Date(recentMsgTimeTxt);

function setMsg()
{
	var currentTime = new Date();
	var msgobj = document.getElementById('msg');

	if(msgCount&gt;0 &amp;&amp; Math.floor(currentTime/1000)%2==0 )
	{
		document.body.style.backgroundColor='#FFFF00';
		msgobj.innerHTML=msgCount + ' New Message';

		if ( msgCount &gt; 1 ) msgobj.innerHTML=msgobj.innerHTML + 's';
		msgobj.style.fontWeight='bold';

	}
	else
	{
		document.body.style.backgroundColor='#FFFFFF';
		msgobj.style.fontWeight='normal';
	}
	if(msgCount==0)
	{
		msgobj.innerHTML=status;
	}

	setTimeout('setMsg();',1000);
}

&lt;/script&gt;
&lt;/head&gt;
&lt;body style='margin:4px;padding:0;font-size:11px;font-weight:normal;font-family: Arial, Helvetica, sans-serif;text-align:center;background-color:#FFFFFF;' onload='setMsg();'&gt;
&lt;div style='font-size:10px;font-weight:bold;padding-bottom:3px;'&gt;Messenger&lt;/div&gt;
&lt;div id='msg'&gt;&lt;/div&gt;
&lt;/body&gt;&lt;/html&gt;"</pre>
<p>As with the other projects, don&#8217;t worry too much about understanding all of the code above. It will work even if you don&#8217;t fully understand. The main thing to note is the series of <em>var</em> statements near the top. These are setting JavaScript variables with FileMaker data fields which are later used in the JavaScript function.</p>
<pre>var msgCount = '" &amp; Count ( <strong>Messenger~new::__pk_MessengerID</strong> ) &amp; "';
var recentMsgTimeTxt = '" &amp; Max ( <strong>Messenger~new::Timestamp</strong> ) &amp; "';
var status = '" &amp; <strong>User~loggedin::MessengerStatus</strong> &amp; "';</pre>
<p>If your field and/or table occurrence are named differently, you will need to adjust these lines accordingly.</p>
<h3>The Conversation Viewer</h3>
<p>This is the larger web viewer in the messenger window which shows the messages. When new messages appear, the messages is added to the bottom, highlighted, and the viewer automatically jumps to the bottom.</p>
<p>You can size this web viewer to your tastes. In the screenshot above, it is 440&#215;220 and anchored on all four sides such that if the window is re-sized, the viewer will stretch or shrink to fill the space.</p>
<p>In the web viewer setup, select &#8220;Allow interaction with web viewer content.&#8221; Deselect all other check-boxes. For the web address, use the following code:</p>
<pre>"data:text/html,&lt;html&gt;
&lt;head&gt;
&lt;script type='text/javascript'&gt;

/*Variables set with FileMaker Data*/
var msgtxt = new Array ( '" &amp; Substitute ( Substitute ( List ( Messenger~activechat::Message ) ; "'" ; "&amp;#39;" ); ¶ ; "','" ) &amp; "');
var msgtime = new Array ( '" &amp; Substitute ( List ( Messenger~activechat::Timestamp ) ; ¶ ; "','" ) &amp; "');
var msguser = new Array ( '" &amp; Substitute ( List ( Messenger~activechat::_fk_Username~from ) ; ¶ ; "','" ) &amp; "');
var msgstatus = new Array ( '" &amp; Substitute ( List ( Messenger~activechat::Status ) ; ¶ ; "','" ) &amp; "');
var thisuser = '" &amp; GLobals::_GK_Username &amp; "';
/*End FileMaker Data*/

function setMsg(newmsg)
{
	var currentTime = new Date();
	var msgobj = document.getElementById('msg');
	var labelobj = document.getElementById('label');
	var content = '';
	msgobj.innerHTML = '';

	for ( var i=0; i&lt; msgtxt.length ; i++ )
	{
		if (msgtxt[i]!='')
		{
			content=\"&lt;span style='font-weight:bold;'&gt;[\" + msgtime[i] + '] ';
			if ( thisuser.toLowerCase()==msguser[i].toLowerCase() ) content +='you';
			else content +=msguser[i];
			content += \" said:&lt;/span&gt; &lt;span style=''&gt;\" + msgtxt[i] + '&lt;/span&gt;';
			var pstyle='';
			if ( msgstatus[i]=='New' &amp;&amp; msguser[i].toLowerCase()==thisuser.toLowerCase())
			{
				pstyle='background: #ffffcc;';
			}
			else if ( msgstatus[i]=='New' &amp;&amp; msguser[i].toLowerCase()!=thisuser.toLowerCase())
			{
				pstyle='background: #ccccff;';
				newmsg=true;
			}
			msgobj.innerHTML += \"&lt;p style='\" + pstyle + \"'&gt;\" + content + '&lt;/p&gt;';
		}
	}

	dh=document.body.scrollHeight;
	ch=document.body.clientHeight;
	if(newmsg &amp;&amp; dh&gt;ch)
	{
		bottom=dh-ch;
		window.scrollTo(0,bottom);
	}

	setTimeout('setMsg(false);',1000);
}

&lt;/script&gt;
&lt;/head&gt;
&lt;body style='margin:4px;padding:0;font-size:11px;font-weight:normal;font-family: Arial, Helvetica, sans-serif;text-align:left;background-color:#FFFFFF;' onload='setMsg(true);'&gt;
&lt;div id='label' style='font-size:10px;font-weight:bold;padding-bottom:3px;'&gt;&lt;/div&gt;
&lt;div id='msg'&gt;Loading...&lt;/div&gt;
&lt;/body&gt;&lt;/html&gt;"</pre>
<p>Again, don&#8217;t worry too much about understanding all of this. The <em>var</em> statements setting JavaScript variables with FileMaker data are as follows:</p>
<pre>var msgtxt = new Array ( '" &amp; Substitute ( Substitute ( List ( <strong>Messenger~activechat::Message</strong> ) ; "'" ; "&amp;#39;" ); ¶ ; "','" ) &amp; "');
var msgtime = new Array ( '" &amp; Substitute ( List ( <strong>Messenger~activechat::Timestamp</strong> ) ; ¶ ; "','" ) &amp; "');
var msguser = new Array ( '" &amp; Substitute ( List ( <strong>Messenger~activechat::_fk_Username~from</strong> ) ; ¶ ; "','" ) &amp; "');
var msgstatus = new Array ( '" &amp; Substitute ( List ( <strong>Messenger~activechat::Status</strong> ) ; ¶ ; "','" ) &amp; "');
var thisuser = '" &amp; <strong>Globals::_GK_Username</strong> &amp; "';</pre>
<p>Most of these are producing FileMaker lists and converting them to JavaScript arrays.</p>
<h2>Step 7 &#8211; Scripting</h2>
<p>We will be creating four scripts!</p>
<ol>
<li><strong>Mark As Read</strong> &#8211; Marks incoming messages in the active conversation as &#8220;Read.&#8221;</li>
<li><strong>Send Message</strong> &#8211; Creates a new message record with the text typed in the MessengerText field, clears that field and refocuses on that field.</li>
<li><strong>Go To Messenger</strong> &#8211; Opens the messenger layout in a new window (selects existing window if already open). If there are unread messages, selects a user from whom messages were received to display the new message(s) and marks as read.</li>
<li><strong>Trigger &#8211; Message Keystroke</strong> &#8211; Called on each keystroke in the MessengerText field, this simple script checks the character code. If the return key was pressed, it sends the message.</li>
</ol>
<h3>Mark As Read</h3>
<p>The <em>Mark As Read</em> script simply sets all the related Messenger~activereceived records to &#8220;Read.&#8221; It uses the <em>Install OnTimer Script</em> to add a 1 second delay so the new messages will remain highlighted for a second.</p>
<div style="padding-left: 00px;"><strong>If [</strong> Get ( ScriptParameter ) = &#8220;now&#8221; <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>Replace Field Contents [</strong> Messenger~activereceived::Status; Replace with calculation: &#8220;Read&#8221; <strong>][</strong> No dialog <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>Commit Records/Requests [</strong> No dialog <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>Go To Field</strong> [ Globals::MessengerText ]</div>
<div style="padding-left: 30px;"><strong>Install OnTimer Script [</strong> “Mark As Read” <strong>]</strong></div>
<div style="padding-left: 00px;"><strong>Else</strong></div>
<div style="padding-left: 30px;"><strong>Install OnTimer Script [</strong> “Mark As Read”; Parameter: &#8220;now&#8221;; Interval: 1 <strong>]</strong></div>
<div style="padding-left: 00px;"><strong>End If</strong></div>
<h3 style="padding-left: 00px;">Send Message</h3>
<p>This script opens a hidden window, creates a new messenger record, then populates the record with values from our global fields, including the messenger text. It then closes the window, clears out the messenger text, calls a script to mark incoming messages as read, then returns the focus to the MessengerText field.</p>
<div style="padding-left: 0px;"><strong>Commit Records/Requests </strong>[ No dialog ]</div>
<div style="padding-left: 0px;"><strong>If </strong>[ not IsEmpty ( Globals::MessengerText ) ]</div>
<div style="padding-left: 0px;"><strong>Set Variable</strong> [ $windowname; Value:&#8221;dev &#8211; send message&#8221; ]</div>
<div style="padding-left: 0px;"><em>#Open a hidden window to go to messenger layout and add new record</em></div>
<div style="padding-left: 0px;"><strong>New Window</strong> [ Name: $windowname; Height: 100; Width: 100; Top: -200; Left: -200 ]</div>
<div style="padding-left: 0px;"><strong>Go to Layout</strong> [ “Dev &#8211; Messenger” (Messenger) ]</div>
<div style="padding-left: 0px;"><strong>New Record/Request</strong></div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Messenger::_fk_Username~from; Globals::_GK_Username ]</div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Messenger::_fk_Username~to; Globals::_GK_ChatUsername ]</div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Messenger::Message; Globals::MessengerText ]</div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Messenger::Status; &#8220;New&#8221; ]</div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Messenger::Timestamp; Get ( CurrentTimeStamp ) ]</div>
<div style="padding-left: 0px;"><strong>Close Window</strong> [ Name: $windowname; Current ﬁle ]</div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Globals::MessengerText; &#8220;&#8221; ]</div>
<div style="padding-left: 0px;"><strong>Perform Script</strong> [ “Mark As Read” ]</div>
<div style="padding-left: 0px;"><strong>Go to Field</strong> [ Globals::MessengerText ]</div>
<div style="padding-left: 0px;"><strong>End If</strong></div>
<h3 style="padding-left: 0px;">Go To Messenger</h3>
<p>This is the script that opens the messenger window, activates a chat of unread messages if one exists, and marks as read. We will be calling it from the messenger widget.</p>
<div style="padding-left: 0px;"><strong>Set Variable</strong> [ $windowname; Value:&#8221;Messenger&#8221; ]</div>
<div style="padding-left: 0px;"><em>#Attempt to select the messenger window. If unsuccessful, it must not exist, so create it.</em></div>
<div style="padding-left: 0px;"><strong>Select Window</strong> [ Name: $windowname; Current ﬁle ]</div>
<div style="padding-left: 0px;"><strong>If</strong> [ Get ( WindowName ) ≠ $windowname ]</div>
<div style="padding-left: 30px;"><strong>New Window</strong> [</div>
<div style="padding-left: 60px;">Name: $windowname;</div>
<div style="padding-left: 60px;">Height: 650;</div>
<div style="padding-left: 60px;">Width: 600;</div>
<div style="padding-left: 60px;">Top: Get ( WindowTop ) + 50;</div>
<div style="padding-left: 60px;">Left: Get ( WindowLeft ) + Get ( WindowWidth ) &#8211; 550</div>
<div style="padding-left: 30px;">]</div>
<div style="padding-left: 30px;"><strong>Show/Hide Status Area</strong> [ Lock; Hide ]</div>
<div style="padding-left: 0px;"><strong>End If</strong></div>
<div style="padding-left: 0px;"><strong>Go to Layout</strong> [ “Messenger” (Globals) ]</div>
<div style="padding-left: 0px;"><em>#If there are unread messages, set the active chat user to one who sent a message.</em></div>
<div style="padding-left: 0px;"><strong>If</strong> [ Count ( Messenger~new::__pk_MessengerID ) &gt; 0 ]</div>
<div style="padding-left: 30px;"><em>#This will trigger the script, Mark As Read</em></div>
<div style="padding-left: 30px;"><strong>Insert Calculated Result</strong> [ Globals::_GK_ChatUsername; Messenger~new::_fk_Username~from ] [ Select ]</div>
<div style="padding-left: 0px;"><strong>End If</strong></div>
<div style="padding-left: 0px;"><strong>Commit Records/Requests</strong> [ No dialog ]</div>
<h3>Trigger &#8211; Message Keystroke</h3>
<p>This script every time a key is pressed on the keyboard while in the MessageText field. It will ignore every keystroke except the return key (you could also include the enter key on the keypad by adding Code ( Get(TriggerKeystroke) ) =10 )</p>
<div style="padding-left: 0px;"><strong>If</strong> [ not $$noTrigger and Code ( Get(TriggerKeystroke) ) = 13 ]</div>
<div style="padding-left: 30px;"><strong>Commit Records/Requests</strong> [ No dialog ]</div>
<div style="padding-left: 30px;"><strong>Perform Script</strong> [ “Send Message” ]</div>
<div style="padding-left: 30px;"><em>#We need to return false in order to cancel the keystroke. Failure to do this can result in inﬁnite recursion.</em></div>
<div style="padding-left: 30px;"><strong>Exit Script</strong> [ Result: False //cancel the carriage return ]</div>
<div style="padding-left: 0px;"><strong>End If</strong></div>
<h2 style="padding-left: 0px;">Step 8 &#8211; Buttons and Script Triggers</h2>
<p>Now, we&#8217;re going to set up buttons and triggers using the scripts we created in the previous step.</p>
<h3>The Four Buttons</h3>
<p><strong>The Messenger Widget</strong> &#8211; On the messenger layout, you will define a button for the messenger widget web viewer (the smaller web viewer on the left). Right click on the web viewer and select &#8220;Button Setup&#8230;&#8221;.</p>
<div style="width: 593px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" src="http://filemakerinspirations.com/images/messengerwidgetbutton.gif" alt="Button definition for The Messenger Widget." width="583" height="308" /><p class="wp-caption-text">Button definition for The Messenger Widget.</p></div>
<p>In the Button Setup window, choose <em>Insert Calculated Result</em>. Click the Specify button to the right of &#8220;Go to target field&#8221; and select the field <em>Globals::_gk_ChatUsername</em>. Click the Specify button to the right of &#8220;Calculated result:&#8221; and enter the following:</p>
<pre>If (
	not IsEmpty ( Messenger~new::_fk_Username~from ) ;
	Messenger~new::_fk_Username~from ;
	Globals::_GK_ChatUsername
)</pre>
<p>This will set the active chat to the sender of a new message if one exists which effectively triggers the <em>Mark As Read</em> script (we&#8217;ll set that trigger up in a bit).</p>
<p>When including the Messenger Widget on other layouts in your solution, set the button to call the <em>Go To Messenger</em> script.</p>
<div style="width: 592px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" src="http://filemakerinspirations.com/images/messengergotobutton.gif" alt="" width="582" height="234" /><p class="wp-caption-text">Button definition for The Messenger Widget on other layouts within your solution.</p></div>
<p><strong>The portal text object</strong> &#8211; This is the text object you placed in the portal under The Messenger Widget. Set the button to perform a similar <em>Insert Calculated Result</em> operation as The Messenger Widget. The difference is that it will specify the <em>from</em> user based on which portal row was clicked. So, if there is more than one sender, the user can choose the sender to read next. The result calculation is as follows:</p>
<pre>Messenger~new_Employee~from::UserName</pre>
<p><strong>The Send button</strong> &#8211; This button performs the script, <em>Send Message</em>. It provides a second way to send a message besides pressing return.</p>
<p><strong>The Close button</strong> &#8211; Simply closes the window.</p>
<h3>Script Triggers</h3>
<p><strong>_gk_ChatUsername field</strong> &#8211; This is the drop down menu to the right of the text, &#8220;Chatting with.&#8221; As suggested above, we set a script trigger on this field to trigger <em>OnObjectModify</em> which calls the script <em>Mark As Read</em>.</p>
<div style="width: 513px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" src="http://filemakerinspirations.com/images/messengertrigger.gif" alt="Script trigger for the Chatting with ... drop down menu." width="503" height="386" /><p class="wp-caption-text">Script trigger for the &#8220;Chatting with &#8230;&#8221; drop down menu.</p></div>
<p><strong>MessengerText field</strong> &#8211; We set a script trigger on this field <em>OnObjectKeystroke</em> which calls <em>Trigger &#8211; Message Keystroke</em>.</p>
<div style="width: 513px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" src="http://filemakerinspirations.com/images/messengertexttrigger.gif" alt="Script trigger for messenger text field." width="503" height="386" /><p class="wp-caption-text">Script trigger for messenger text field.</p></div>
<h1>Step 9 &#8211; Final File Setup</h1>
<p>Now that we have the instant messaging structure in place, we need to prepare our file.</p>
<h2>Open/Login Script</h2>
<p>Create an open script if you do not already have one. This is a script that runs when the file opens. If you already have one, simply add the following two script steps to the file. If you do not already have one, create a new script, give it any name (perhaps &#8220;On Open&#8221;), and add the following steps:</p>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ Globals::_GK_Username; Get ( AccountName ) ]</div>
<div style="padding-left: 0px;"><strong>Set Field</strong> [ User~loggedin::MessengerStatus; &#8220;Logged In&#8221; ]</div>
<p>If you have a login script, add the same two steps. If you don&#8217;t have one, don&#8217;t worry about it right now.</p>
<h2>Close/Logout Script</h2>
<p>Create an close script if you do not already have one. This is a script that runs when the file closes. If you already have one, simply add the following script step to the file. If you do not have one, create a new script, give it any name (perhaps &#8220;On Close&#8221;), and add the following step:</p>
<p><strong>Set Field</strong> [ User~loggedin::MessengerStatus; &#8220;Logged Out&#8221; ]</p>
<p>If you have a logout script, add the same step. If you don&#8217;t have one, don&#8217;t worry about it right now.</p>
<h2>Users and Accounts</h2>
<p>Using FileMaker&#8217;s Manage Security feature, create an account for each user who will be using the messenger program (if such accounts do not already exist). Each user will have to log in using a different user account.</p>
<p>Then create a record in the user table for each user account. Be sure the usernames match. Alternatively, you can simply set up the relationship between Globals and User to allow creation of records in the User~loggedin table via this relationship. By doing this, the User record will be created automatically by the startup/login script when the user logs in.</p>
<h2>File Options</h2>
<p>Set up your file options to match the following:</p>
<div style="width: 484px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" src="http://filemakerinspirations.com/images/messengerfileoptions.gif" alt="File Options for the messenger file." width="474" height="445" /><p class="wp-caption-text">File Options for the messenger file. Deselect &#8220;Log in using:&#8221;, and designate the appropriate scripts for open and close.</p></div>
<p>If your file previously logged in automatically to the default admin account, be sure you know the account name and password for and admin account.</p>
<h1>Done!</h1>
<p>And now we&#8217;re ready to chat! (After some testing and debugging perhaps <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> )</p>
<div class="note"><strong>Note:</strong> A completed updated version of this project along with integration instructions is available for purchase. Visit the <a title="Triple-i Messenger - Internal Instant Messaging in FileMaker Pro" href="http://www.inspirationssoftwaredesign.com/products/triple-i-messenger">Triple-i Messenger</a> page for an updated and improved version of this solution, or click here to <a class="ec_ejc_thkbx" title="Triple-i Messenger - Purchase &amp; Download Immediately" href="https://www.e-junkie.com/ecom/gb.php?c=cart&amp;i=IBIIM270&amp;cl=190732&amp;ejc=2" target="ej_ejc" rel="noopener">download the legacy messenger solution</a>.</div>
<h1><a name="basics"></a></h1>
<h1>What Else Is Possible?</h1>
<p>With these three projects, we&#8217;re still just scratching the surface of possibilities with this technique. With the power of HTML and JavaScript, the possibilities are practically infinite: message tickers; powerful, flexible and interactive reporting; calendar alerts; the list goes on&#8230;.</p>
<p>Think about it for a minute. What comes to your mind? Please share your ideas below!</p>
<h1><a name="debugging"></a>Debugging</h1>
<p>Take a look at the debugging section of <a title="Countdown Timers – Multi-Threading With Web Viewers" href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the Timer article</a> where I discuss debugging web viewers with data URIs. Some additional things you can do with this project include adding fields to your Dev &#8211; Messenger layout and viewing the data that is being generated in the Messenger table. This project is also heavy on the scripting, so the Script Debugger will be quite useful.</p>
<p>And of course, if you are still stuck or you have any questions or comments, feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contact-us/contact-form?topic=support">contact me directly</a>.<br />
<script type="text/javascript" language="javascript">// <![CDATA[
function EJEJC_lc(th) { return false; }
// ]]&gt;</script><br />
<script type="text/javascript" src="http://www.e-junkie.com/ecom/box.js"></script></p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2011/08/instant-messaging-in-filemaker-no-plugins-or-internet-access-required/feed/</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title>Alarm Clocks &#8211; Dynamic Multi-Threading with Web Viewers</title>
		<link>http://filemakerinspirations.com/2011/07/alarm-clocks-dynamic-multi-threading-with-web-viewers/</link>
					<comments>http://filemakerinspirations.com/2011/07/alarm-clocks-dynamic-multi-threading-with-web-viewers/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Fri, 29 Jul 2011 15:30:25 +0000</pubDate>
				<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Script Triggers]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Web Viewers]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=342</guid>

					<description><![CDATA[With a FileMaker Pro Web Viewer and a little bit of JavaScript code, you can create a FileMaker Pro native alarm clock that updates every second: no plugins, no distracting refreshes and no affect on performance. This article follows up on the previous article, Countdown Timers &#8211; Dynamic Multi-Threading with Web Viewers. I recommend that [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>With a FileMaker Pro Web Viewer and a little bit of JavaScript code, you can create a FileMaker Pro native alarm clock that updates every second: no plugins, no distracting refreshes and no affect on performance.</p>
<p>This article follows up on the previous article, <a href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">Countdown Timers &#8211; Dynamic Multi-Threading with Web Viewers</a>. I recommend that you read that article first if you have not already, as it covers the basics of how this technique works.</p>
<p>We&#8217;ll cover another simple example of what can be done using Web Viewers, and we&#8217;re still just scratching the surface of what can be done. For instance, in the next article, we&#8217;ll discuss creation of an interactive, internal instant messaging program with nothing but FileMaker Pro and web viewers: no plugins and no external internet access required.</p>
<h2><a name="basics"></a>The Basics</h2>
<p>In the <a title="Countdown Timers - Dynamic Multi-Threading with Web Viewers" href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">previous article</a>, I discuss the three main concepts that are the foundation of this technique: data URIs, including FileMaker Pro data in a web viewer and JavaScript. Click here to view the basics of this technique of <a href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">dynamic multi-threading with web viewers</a>.</p>
<p>In short, we will use a Data URI to specify the HTML and JavaScript we want to display in the web viewer, and we&#8217;ll include data from our FileMaker Pro database in that code. We will use JavaScript to build and refresh the HTML dynamically.</p>
<h2><a name="project"></a>The Alarm Clock Project</h2>
<h3>Step 1 &#8211; Create Alarm Table</h3>
<p>In this step, we create a simple table in our FileMaker Pro database that stores our alarm clock parameters. This table is very similar to the one created for the Countdown Timer. And similarly, there are a number of ways this could be set up depending on how you want to use the alarm clock. For instance, for this example, we will set it up so there is one alarm per user. In this way, if a user closes the database and opens it again (whether on the same workstation or a different workstation), their alarm clock setting will persist. Alternatively, the alarm clock could be workstation based, function based or session based. You may also choose to allow a user to set multiple alarms at one time.</p>
<p>For the user-based alarm clock, our table will have the following fields:</p>
<p><strong>__pk_AlarmID</strong> &#8211; Number &#8211; Auto enter Serial. This is the primary key of the table. Though we don&#8217;t actually use this field for any relationships or other functionality, it is always good practice to include a primary key field in every table that you create. This way, if you ever need to isolate a record it in a relationship or script, you will have a way.</p>
<p><strong>_fk_User</strong> &#8211; Text. In my database, this is the id of a record in the User table that corresponds to the currently logged in user. However, if such a table does not exist in your database, it could simply be the account name of the currently logged in user.</p>
<p><strong>Label</strong> &#8211; Text. This field is optional and allows one to give the alarm a label. For instance, if you want to remember to eat lunch at 12:30PM, set the label to &#8220;Eat Lunch,&#8221; and when the alarm goes off, it will flash, &#8220;Eat Lunch.&#8221; This is especially useful if you set it up to allow multiple simultaneous alarms.</p>
<p><strong>AcknowledgeTimeStamp</strong> &#8211; Timestamp. This field will be used to flag the alarm clock as &#8220;Acknowledged&#8221; so it stops flashing and beeping. We use a timestamp so that we know when the alarm was acknowledged, so for daily alarms, the alarm will go off at the next appropriate time.</p>
<p><strong>Hour</strong> &#8211; Number &#8211; Restricted to 1-12 (12 hour clock). This is a data entry field where the user enters the hour the alarm clock should go off.</p>
<p><strong>Minute</strong> &#8211; Number &#8211; Restricted to 0-59. Another data entry field for entering the minute.</p>
<p><strong>AMPM</strong> &#8211; Text &#8211; Restricted to value list containing values AM and PM.</p>
<p><strong>Date</strong> &#8211; take a guess. Another data entry field for entering the date of the alarm or left blank for a daily alarm.</p>
<p><strong>Timestamp</strong> &#8211; Calculation (Timestamp). This is the time that the alarm will go off. The calculation is built from the data entry fields above. Here it is:</p>
<pre>If ( IsEmpty ( Date ) ;
 Let (
   theTime = Get ( CurrentDate ) &amp; " " &amp; Hour &amp; ":" &amp; Minute &amp; " " &amp; AMPM ;
   If ( AcknowledgeTimeStamp ≥ GetAsTimestamp ( theTime ) ;
     ( Get ( CurrentDate ) + 1 ) &amp; " " &amp; Hour &amp; ":" &amp; Minute &amp; " " &amp; AMPM ;
     theTime
   )
 ) ;
 Date &amp; " " &amp; Hour &amp; ":" &amp; Minute &amp; " " &amp; AMPM
)</pre>
<p>This calculation allows for daily alarms where no date is specified. Once the alarm for one date is acknowledged, it is ready for the next day.</p>
<h3>Step 2 &#8211; Relationships</h3>
<p>The relationship setup is nearly identical to that in the Timer. Only one relationship is needed and there are two possible ways to handle it:</p>
<ul>
<li>If you have a user table, such as I do in my database, create a relationship between the Alarm table and the table occurrence of the User table that represents the currently logged-in user. The relationship will be based on this: Alarm ::_fk_User = User::__pk_UserID (__pk_EmployeeID in my case).</li>
<li>If no user table and current user occurrence exists, simply create a global field in one of your tables (preferably a Globals table) that gets set upon log in to the current account user name. Then create a relationship between that table and the Alarm table using your global account name field = Alarm::_fk_User field.</li>
</ul>
<p>In order to keep the scripting simple, select the check box to allow creation via this relationship:</p>
<div style="width: 585px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Relationship definition for the timer." src="http://filemakerinspirations.com/images/timer_relationship.gif" alt="Select allow relationships in this table via this relationship to simplify the scripting process." width="575" height="408" /><p class="wp-caption-text">Select &#8220;allow creation of records in this table via this relationship&#8221; to simplify the scripting process.</p></div>
<h3>Step 3 &#8211; Web Viewer Setup</h3>
<p>Create a web viewer sized 230&#215;25 (you can do any size, but the HTML code below is optimized for this size).</p>
<div style="width: 240px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="  " title="Timer Web Viewer" src="http://filemakerinspirations.com/images/alarm.gif" alt="" width="230" height="25" /><p class="wp-caption-text">The Alarm Clock.</p></div>
<p>For the web address, use the following code:</p>
<pre>"data:text/html,&lt;html&gt;
&lt;head&gt;
&lt;script type='text/javascript'&gt;

/*Setting JavaScript Variables to value of FileMaker fields*/
var alarmTimeTxt = '" &amp; Alarm::Timestamp &amp; "';
var acknowledgeTimeTxt = '" &amp; Alarm::AcknowledgeTimeStamp &amp; "';
var alarmLabel = '" &amp; Alarm::Label &amp; "';
/*End FileMaker Fields*/

if (alarmLabel=='') alarmLabel='Alarm';
var alarmTime = new Date(alarmTimeTxt);
var acknowledgeTime = new Date(acknowledgeTimeTxt);

function setClock()
{
	var currentTime = new Date();
	var clock = document.getElementById('clock');

	if (alarmTimeTxt=='' || alarmTime &gt;= currentTime || (acknowledgeTimeTxt !='' &amp;&amp; acknowledgeTime &gt;= alarmTime) || Math.floor(currentTime/1000)%2==0 )
	{
		document.body.style.backgroundColor='#FFFFFF';
		clock.innerHTML=currentTime.toLocaleDateString() + ' ' + currentTime.toLocaleTimeString();
	}
	else
	{
		document.body.style.backgroundColor='#FFFF00';
		clock.innerHTML=alarmLabel + \"&lt;embed src='file:///System/Library/Sounds/Glass.aiff' autostart='true'  id='sound1'
enablejavascript='true' width='0' height='0'&gt;\";
	}

	setTimeout('setClock();',1000);
}

&lt;/script&gt;
&lt;/head&gt;
&lt;body style='margin:4px;padding:0;font-size:14px;font-weight:bold;font-family: Arial, Helvetica, sans-serif;text-align:center;background-color:#FFFFFF;' onload='setClock();'&gt;
&lt;p id='clock'&gt;
&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;"</pre>
<p>It&#8217;s not necessary for you to understand all of this code in order for you to get this to work. However, near the top, there are a series of <em>var</em> statements. These are setting JavaScript variables which are later used in the JavaScript function. Many of these variables are being set with FileMaker Pro data. For instance,</p>
<pre>var alarmTimeTxt = '" &amp; <strong>Alarm::Timestamp</strong> &amp; "';
var acknowledgeTimeTxt = '" &amp; <strong>Alarm::AcknowledgeTimeStamp</strong> &amp; "';
var alarmLabel = '" &amp; <strong>Alarm::Label</strong> &amp; "';</pre>
<p>If your field and/or table occurrence are named differently, you will need to adjust these lines accordingly.</p>
<p>Also, take note of the following JavaScript code:</p>
<pre>clock.innerHTML=alarmLabel + \"&lt;embed src='<strong>file:///System/Library/Sounds/Glass.aiff</strong>' autostart='true'  id='sound1'
enablejavascript='true' width='0' height='0'&gt;\";</pre>
<p>This specifies the sound that will be used when the timer goes off. The file URL after the <em>scr=</em> statement is the path on a Macintosh workstation to the Glass.aiff sound effect file. If you are using Windows, this path will have to be adjusted. If you are in a mixed environment, you may want to use a sound file that is located on the web.</p>
<div class="note"><strong>Note:</strong> You may have noticed the method we&#8217;re using for the sound is slightly different in this project than in <a href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the Timer project</a>. Both methods are valid and would work in both cases. I chose to use different methods to demonstrate different ways of approaching this. In <a href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the timer solution</a>, we permanently embed the sound in the HTML. In this project, we embed it into the HTML only when needed. This method is slightly more efficient when the clock is running and the alarm is not going off, but is slightly less efficient when the alarm is going off.</div>
<div class="note"><strong>Enhancement:</strong> You could paste this little web viewer to every layout in your solution. In this way, no matter what the user is doing, they will be able to see the alarm clock and will be alerted when the alarm goes off. You can adjust the font attributes in the body tag to make it fit.</div>
<h3>Step 4 &#8211; Create Settings Layout</h3>
<div style="width: 358px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Alarm Settings Layout" src="http://filemakerinspirations.com/images/alarmsettings.gif" alt="Create a layout for use as a popup window where the alarm can be set." width="348" height="341" /><p class="wp-caption-text">Create a layout for use as a popup window where the alarm can be set.</p></div>
<p>Create a layout that shows records from your Alarm table. As in the the screen shot above, include the label field, hour, minute, AMPM (with a value list with values AM and PM), and date.</p>
<p>The <em>Clear</em> button runs a script that simply runs a series of Set Field steps to clear the date from the Date, Hour, Minute, AMPM, AcknowledgeTimeStamp and Label fields, followed by a Commit Records step.</p>
<p>The <em>Done</em> button simply closes the window.</p>
<h3>Step 5 &#8211; Scripting</h3>
<p>We will be creating one script that will fire when clicking on the timer web viewer. Depending on the state of the alarm, it will perform one of two tasks:</p>
<ul>
<li>When the alarm is going off, it will set the &#8220;AcknowledgeTimeStamp&#8221; field, thus stopping the alarm.</li>
<li>When the alarm is idle, it will bring up the alarm settings layout in a popup window.</li>
</ul>
<p>Here are the script steps:</p>
<div style="padding-left: 0px;"><strong>If [</strong> Alarm::Timestamp &gt; Alarm::AcknowledgeTimeStamp and Alarm::Timestamp ≤ Get ( CurrentTimeStamp ) <strong>]</strong></div>
<div style="padding-left: 30px;"><em>#If the alarm is going off, set &#8220;Acknowledged&#8221; timestamp.</em></div>
<div style="padding-left: 30px;"><strong>Set Field [</strong> Alarm::AcknowledgeTimeStamp; Get ( CurrentTimeStamp ) <strong>]</strong></div>
<div style="padding-left: 0px;"><strong>Else</strong></div>
<div style="padding-left: 30px;"><strong>Set Variable [</strong> $windowname; Value:&#8221;Set Alarm&#8221; <strong>]</strong></div>
<div style="padding-left: 30px;"><em>#If the alarm is not going off, bring up alarm settings.</em></div>
<div style="padding-left: 30px;"><strong>If [</strong> Count ( Alarm::__pk_AlarmID ) = 0 <strong>]</strong></div>
<div style="padding-left: 60px;"><em>#If no alarm record exists for this user, force one to be created through the relationship.</em></div>
<div style="padding-left: 60px;"><em>#This is not the best practice, but is simple and portable for this exercise.</em></div>
<div style="padding-left: 60px;"><strong>Set Field [</strong> Alarm::Label; &#8220;Alarm&#8221; <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>End If</strong></div>
<div style="padding-left: 30px;"><strong>Select Window [</strong> Name: $windowname; Current ﬁle <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>If [</strong> Get ( WindowName ) ≠ $windowname <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>New Window [</strong></div>
<div style="padding-left: 90px;">Name: &#8220;Set Alarm&#8221;;</div>
<div style="padding-left: 90px;">Height: 390;</div>
<div style="padding-left: 90px;">Width: 370;</div>
<div style="padding-left: 90px;">Top: Get ( WindowTop ) + ( Get ( WindowHeight ) &#8211; 390 ) / 2;</div>
<div style="padding-left: 90px;">Left: Get ( WindowLeft ) + ( Get ( WindowWidth ) &#8211; 370 ) / 2 <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>Show/Hide Status Area [</strong> Hide <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>End If</strong></div>
<div style="padding-left: 30px;"><strong>Go to Related Record [</strong></div>
<div style="padding-left: 60px;">From table: “Alarm”;</div>
<div style="padding-left: 60px;">Using layout: “DLG &#8211; Alarm” (Alarm) <strong>]</strong></div>
<div style="padding-left: 70px;">[ Show only related records ]</div>
<div style="padding-left: 0px;"><strong>End If </strong></div>
<div style="padding-left: 0px;"><strong>Commit Records/Requests [</strong> No dialog <strong>]</strong></div>
<p style="padding-left: 0px;">Finally, define a button for the web viewer that calls the script you just created.</p>
<p style="padding-left: 0px;">That&#8217;s it. The timer should now work for you. If it doesn&#8217;t (as most programming project don&#8217;t the first time through), take a look at the notes on debugging in <a href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the timer article</a>.</p>
<h2>What Else Is Possible?</h2>
<p>In the next article, we&#8217;ll do something a little more fun, and challenging, and perhaps more practical: an internal <strong>instant messaging system</strong>. This project is considerably more complex, but most of the complexity lies in FileMaker Pro development, rather than HTML and JavaScript. Arguably, the project could be accomplished exclusively with native FileMaker Pro tools, but I find it to be much more responsive, robust and dynamic when using Web Viewers.</p>
<h2><a name="debugging"></a>Debugging</h2>
<p>Take a look at the debugging section of <a href="http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/">the Timer article</a> where I discuss debugging web viewers with data URIs. And of course, if you are still stuck or you have any questions or comments, feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contact-us/contact-form?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2011/07/alarm-clocks-dynamic-multi-threading-with-web-viewers/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Countdown Timers &#8211; Multi-Threading With Web Viewers</title>
		<link>http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/</link>
					<comments>http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Fri, 22 Jul 2011 10:00:05 +0000</pubDate>
				<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Web Viewers]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=325</guid>

					<description><![CDATA[In FileMaker Pro 10, FileMaker introduced OnTimer scripts which allow us to trigger a script to be run after a specified period of time. OnTimer scripts are great for implementing delays, timed refreshes and a whole host of possibilities yet to be discovered. However, one drawback that I have run into is that when the [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>In FileMaker Pro 10, FileMaker introduced OnTimer scripts which allow us to trigger a script to be run after a specified period of time. OnTimer scripts are great for implementing delays, timed refreshes and a whole host of possibilities yet to be discovered. However, one drawback that I have run into is that when the script runs, the user&#8217;s activities can be interrupted. At the very least, you will notice mouse pointer flashes, and worse, depending on the nature of the script, it could cause the user to be exited from a field or position on the layout. So, while I have found OnTimer scripts to be great for delaying script execution, or periodic status checks, I have not found it to be as useful in cases where continuous, dynamic interface updates are required.</p>
<p>The multitasking capabilities in FileMaker Pro are limited in this way. Web viewers, however, run in different threads, and when fed with data from your FileMaker Pro database, they allow us to create rich, up-to-the-second, content, complete with animation, sounds, popup alerts, or whatever else we want to put in there. And, the best part: it has no noticeable affect on FileMaker&#8217;s performance!</p>
<p>This article covers a simple example of what can be done using Web Viewers, and it just scratches the surface of what can be done. For instance, I have also created an alarm clock as well as an interactive, internal instant messaging program with nothing but FileMaker Pro and web viewers: no plugins and no external internet access required. I&#8217;ll be discussing these in another article soon.</p>
<h2><a name="basics"></a>The Basics</h2>
<div class="note"><strong>Note:</strong> Feel free to skip right to <a href="#project">The Countdown Timer Project</a> section below if you are not interested in understanding the fundamentals of this process. You can make it work without a clear understanding, but if you wish to make enhancements or need to debug problems, you will want to read through this.</div>
<h3>Data URI</h3>
<p>When you set up a web viewer, you normally specify a URL (web address) indicating what web page the web viewer should display. With a data URI, we can specify the HTML (and JavaScript) we want to display in the web viewer directly in place of the URL itself. This is very powerful in that it allows us to easily build a web page to display using our FileMaker data. It is a great way to create custom HTML reports that are not so easily created within the structure of FileMaker Pro. I have also used it with Instant Web Publishing to achieve greater flexibility.</p>
<p>While the basic format of a URL is <em>http://&lt;address&gt;</em>, the basic format of the data URI is as follows:</p>
<pre>data:[&lt;MIME-type&gt;][;charset=&lt;encoding&gt;][;base64],&lt;data&gt;</pre>
<p>Don&#8217;t worry too much if that doesn&#8217;t make sense to you. You can copy and paste the code from the articles for these projects. All we will be doing is specifying a mime type (text/html), and including the HTML and JavaScript that we need. So our Data URI will look something like this:</p>
<pre>data:text/html,&lt;some_html_code&gt;</pre>
<p>If you are interested in general information about the data URI, check out the <a href="http://en.wikipedia.org/wiki/Data_URI_scheme" target="_blank" rel="noopener">Wikipedia Data URI</a> article.</p>
<h3>Including FileMaker Pro Data</h3>
<p>With the data URI, we can build dynamic HTML content within FileMaker Pro using the calculation engine. This allows us to easily include data from our FileMaker database within our HTML.</p>
<h3>JavaScript</h3>
<p>In these sample projects, we will be using JavaScript to create dynamic content. JavaScript is a powerful, object-oriented scripting language used extensively on nearly every website that you visit. If you are not familiar with JavaScript, don&#8217;t be too concerned. You can copy and paste the code below. Hopefully, you will get a sense of what the code is doing if you want to make some enhancements.</p>
<p>One of the key JavaScript functions we will be using is the <em>setTimeout</em> function. This function allows us to specify a function to be called after a given period of time. It is similar to FileMaker Pro&#8217;s, OnTimer Script, script step, but it does not tie up the FileMaker Pro scripting engine. We will be creating a JavaScript function to update the web viewer content, and that function will use the <em>setTimeout</em> function to call itself again in 1 second. So, the content will refresh continually.</p>
<h2><a name="project"></a>The Countdown Timer Project</h2>
<h3>Step 1 &#8211; Create Timer Table</h3>
<p>In this step, we create a simple table in our FileMaker Pro database that stores our timer parameters. There are a number of ways this could be set up depending on how you want to use the timer. For instance, for this example, we will set it up so there is one timer per user. In this way, if a user closes the database and opens it again (whether on the same workstation or a different workstation), their countdown timer will persist. Alternatively, the timer could be workstation based, function based or session based. You may also choose to allow a user to run multiple timers at one time.</p>
<p>For the user-based timer, our table will have the following fields:</p>
<p><strong>__pk_TimerID</strong> &#8211; Number &#8211; Auto enter Serial. This is the primary key of the table. Though we don&#8217;t actually use this field for any relationships or other functionality, it is always good practice to include a primary key field in every table that you create. This way, if you ever need to isolate a record it in a relationship or script, you will have a way.</p>
<p><strong>_fk_User</strong> &#8211; Text. In my database, this is the id of a record in the User table that corresponds to the currently logged in user. However, if such a table does not exist in your database, it could simply be the account name of the currently logged in user.</p>
<p><strong>Label</strong> &#8211; Text. This field is optional and allows one to give the timer a label. For instance, if you want to remember to call someone in 1/2 hour, set the label to &#8220;Call Fred,&#8221; and when the timer goes off, it will flash, &#8220;Call Fred.&#8221; This is especially useful if you set it up to allow multiple simultaneous timers.</p>
<p><strong>Status</strong> &#8211; Text. This field will be used to flag the timer as &#8220;Acknowledged&#8221; so it stops flashing and beeping. You could simply use a number field as a flag (with values 1 or 0), but I like using the text field to allow for future expansion.</p>
<p><strong>Timestamp_End</strong> &#8211; Timestamp. This is the time that the timer will go off. This field will be set by a script that asks the user for a timer duration. It then simply adds the duration to the current time, and that will be the timestamp when the timer will go off.</p>
<p><strong>Duration</strong> &#8211; Number. For data entry. This field stores the number of minutes before the timer should go off.</p>
<h3>Step 2 &#8211; Relationships</h3>
<p>Only one relationship is needed and there are two possible ways to handle it:</p>
<ul>
<li>If you have a user table, such as I do in my database, create a relationship between the Timer table and the table occurrence of the User table that represents the currently logged-in user. The relationship will be based on this: Timer::_fk_user = User::UserID</li>
<li>If no user table and current user occurrence exists, simply create a global field in one of your tables (preferably a Globals table) that gets set upon log in to the current account user name. Then create a relationship between that table and the Timer table using your global account name field = Timer::_fk_User field.</li>
</ul>
<p>In order to keep the scripting simple, select the checkbox to allow creation via this relationship:</p>
<div style="width: 585px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Relationship definition for the timer." src="http://filemakerinspirations.com/images/timer_relationship.gif" alt="Select allow relationships in this table via this relationship to simplify the scripting process." width="575" height="408" /><p class="wp-caption-text">Select &#8220;allow creation of records in this table via this relationship&#8221; to simplify the scripting process.</p></div>
<h3>Step 3 &#8211; Web Viewer Setup</h3>
<p>Create a web viewer sized 115&#215;40 (you can do any size, but the HTML code below is optimized for this size.</p>
<div style="width: 125px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Timer Web Viewer" src="http://filemakerinspirations.com/images/timer.gif" alt="" width="115" height="38" /><p class="wp-caption-text">The Timer.</p></div>
<p>For the web address, use the following code:</p>
<pre>"data:text/html,&lt;html&gt;
&lt;head&gt;
&lt;script type='text/javascript'&gt;

/*Setting JavaScript Variables to value of FileMaker fields*/
var targetTimeTxt = '" &amp; Timer::Timestamp_End &amp; "';
var status = '" &amp; Timer::Status &amp; "';
var labelTxt = '" &amp; Timer::Label &amp; "';
/*End FileMaker Fields*/

var currentTime = new Date();
var targetTime = new Date(targetTimeTxt);
var remaining = Math.floor((targetTime - currentTime)/1000);

function setClock()
{
	var currentTime = new Date();
	var clock = document.getElementById('clock');
	var labelobj = document.getElementById('label1');
	var secondsRemaining=0;

	if(labelTxt=='')
	{
		labelobj.innerHTML='Timer';
	}
	else
	{
		labelobj.innerHTML=labelTxt;
	}

	if (targetTime&gt;currentTime)
	{
		secondsRemaining=Math.floor((targetTime - currentTime)/1000);
	}

	var hours = Math.floor( secondsRemaining / 3600 );
	var minutes = Math.floor((secondsRemaining%3600) / 60 );
	if(minutes&lt;10)minutes='0' + minutes;
	var seconds = secondsRemaining%60;
	if(seconds&lt;10)seconds='0'+seconds;

	clock.innerHTML=hours + ':' + minutes + ':' + seconds;

	if(targetTimeTxt=='' || status=='Acknowledged' || ( secondsRemaining==0 &amp;&amp; Math.floor(currentTime/1000)%2==0 ) )
	{
		document.body.style.backgroundColor='#FFFFFF';
		if ( targetTimeTxt=='' || status=='Acknowledged' )
		{
			clock.innerHTML='--:--:--';
		}
	}
	else if(secondsRemaining==0)
	{
		document.body.style.backgroundColor='#FFFF00';
		document.getElementById('sound1').Play();
	}

	setTimeout('setClock();',1000);
}

&lt;/script&gt;
&lt;/head&gt;
&lt;body style='margin:4px;padding:0;font-size:14px;font-weight:bold;font-family: Arial, Helvetica, sans-serif;text-align:center;background-color:#FFFFFF;' onload='setClock();'&gt;
&lt;div id='label1' style='font-size:10px;font-weight:bold;'&gt;
&lt;/div&gt;
&lt;div id='clock'&gt;
&lt;/div&gt;
&lt;embed src='file:///System/Library/Sounds/Glass.aiff' autostart='false'  id='sound1'
enablejavascript='true' width='0' height='0'&gt;
&lt;/body&gt;&lt;/html&gt;"</pre>
<p>It&#8217;s not necessary for you to understand all of this code in order for you to get this to work. However, near the top, there are a series of <em>var</em> statements. These are setting JavaScript variables which are later used in the JavaScript function. Many of these variables are being set with FileMaker Pro data. For instance,</p>
<pre>var targetTimeTxt = '" &amp; <strong>Timer::Timestamp_End</strong> &amp; "';
var status = '" &amp; <strong>Timer::Status</strong> &amp; "';
var labelTxt = '" &amp; <strong>Timer::Label</strong> &amp; "';</pre>
<p>These line set JavaScript variables to the value of these FileMaker fields. If your fields and/or table occurrence are named differently, you will need to adjust these lines accordingly.</p>
<p>Also, take note of the following HTML code:</p>
<pre>&lt;embed src='file:///System/Library/Sounds/Glass.aiff' autostart='false'  id='sound1'
enablejavascript='true' width='0' height='0'&gt;</pre>
<p>This specifies the sound that will be used when the timer goes off. The file URL after the <em>scr=</em> statement is the path on a Macintosh workstation to the Glass.aiff sound effect file. If you are using Windows, this path will have to be adjusted. If you are in a mixed environment, you may want to use a sound file that is located on the web.</p>
<div class="note"><strong>Enhancement:</strong> You could paste this little web viewer to every layout in your solution. In this way, no matter what the user is doing, they will be able to see the timer and will be alerted when the timer goes off. You can adjust the font attributes in the body tag to make it fit.</div>
<h3>Step 4 &#8211; Scripting</h3>
<p>We will be creating one script that will fire when clicking on the timer web viewer. Depending on the state of the timer, it will perform one of three tasks:</p>
<ul>
<li>When the timer is idle, it will prompt the user for a timer duration and optional label, then start the timer.</li>
<li>When the timer is running, it will ask the user if the timer should be stopped. If so, it stops and clears the timer.</li>
<li>When the timer is going off (the countdown has reached 0), it will flag the timer as acknowledged, thus causing the JavaScript function to stop flashing and dinging.</li>
</ul>
<p>Here are the script steps:</p>
<div style="padding-left: 0px;"><strong>If [</strong> Timer::Status = &#8220;Acknowledged&#8221; or IsEmpty ( Timer::Timestamp_End ) <strong>]</strong></div>
<div style="padding-left: 30px;"><em>#If the timer is not running, prompt for timer settings and run timer.</em></div>
<div style="padding-left: 30px;"><strong>Show Custom Dialog [ </strong></div>
<div style="padding-left: 60px;">Title: &#8220;Set Timer&#8221;; Message: &#8220;Enter the number of minutes before the timer should go off:&#8221;;</div>
<div style="padding-left: 60px;">Buttons: “OK”, “Cancel”;</div>
<div style="padding-left: 60px;">Input #1: Timer::Duration, &#8220;Minutes&#8221;;</div>
<div style="padding-left: 60px;">Input #2: Timer::Label, &#8220;Label (optional)&#8221;</div>
<div style="padding-left: 30px;"><strong> ] </strong></div>
<div style="padding-left: 30px;"><strong>If [</strong> Get ( LastMessageChoice ) = 1 <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>Set Field [</strong>Timer::Timestamp_End; Get ( CurrentTimeStamp ) + Timer::Duration * 60 <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>Set Field [</strong> Timer::Status; &#8220;Running&#8221; <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>End If </strong></div>
<div style="padding-left: 0px;"><strong>Else If [</strong> Timer::Timestamp_End &lt; Get ( CurrentTimeStamp ) <strong>]</strong></div>
<div style="padding-left: 30px;"><em>#If the timer is going off, set as &#8220;Acknowledged&#8221;.</em></div>
<div style="padding-left: 30px;"><strong>Set Field [</strong> Timer::Status; &#8220;Acknowledged&#8221; <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>Set Field [</strong> Timer::Label; &#8220;&#8221; <strong>]</strong></div>
<div style="padding-left: 0px;"><strong>Else </strong></div>
<div style="padding-left: 30px;"><em>#Otherwise, the timer is running. Ask to stop it.</em></div>
<div style="padding-left: 30px;"><strong>Show Custom Dialog [</strong></div>
<div style="padding-left: 60px;">Title: &#8220;Stop Timer&#8221;;</div>
<div style="padding-left: 60px;">Message: &#8220;Would you like to stop the timer?&#8221;;</div>
<div style="padding-left: 60px;">Buttons: “Yes”, “No”</div>
<div style="padding-left: 30px;"><strong>] </strong></div>
<div style="padding-left: 30px;"><strong>If [</strong> Get ( LastMessageChoice ) = 1 <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>Set Field [</strong> Timer::Timestamp_End; Get ( CurrentTimeStamp ) <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>Set Field [</strong> Timer::Status; &#8220;Acknowledged&#8221; <strong>]</strong></div>
<div style="padding-left: 60px;"><strong>Set Field [</strong> Timer::Label; &#8220;&#8221; <strong>]</strong></div>
<div style="padding-left: 30px;"><strong>End If</strong></div>
<div style="padding-left: 0px;"><strong>End If</strong></div>
<div style="padding-left: 0px;"><strong>Commit Records/Requests [</strong> No dialog <strong>]</strong></div>
<p style="padding-left: 0px;">Finally, define a button for the web viewer that calls the script you just created.</p>
<p style="padding-left: 0px;">That&#8217;s it. The timer should now work for you. If it doesn&#8217;t, take a look at the note on debugging below.</p>
<h2>What Else Is Possible?</h2>
<p>The possibilities are only limited by your imagination and HTML and JavaScript skills.</p>
<p>In the next article, I&#8217;ll be talking about a similar, but slightly more complex project to create an alarm clock.</p>
<p>After that, we&#8217;ll so something a little more fun, and perhaps more practical: an internal instant messaging system. This project is considerably more complex, but most of the complexity lies in FileMaker Pro development, rather than HTML and JavaScript. Arguably, the project could be accomplished exclusively with native FileMaker Pro tools, but I find it to be much more responsive, robust and dynamic when using Web Viewers.</p>
<h2><a name="debugging"></a>Debugging</h2>
<p>This technique can be challenging to debug, because if something it not working correctly, the only indication you generally receive is an empty web viewer.</p>
<div class="note"><strong>Note:</strong> The debugging technique I describe here uses features specific to FileMaker Pro Advanced (specifically, the data viewer). If you do not have FileMaker Pro Advanced, you will have to create a calculated field to achieve the same result (see alternative steps in parentheses below).</div>
<ol>
<li>Copy the contents of the &#8220;web address&#8221; field from the web viewer.</li>
<li>Open the data viewer, click the &#8220;Watch&#8221; tab and click the &#8220;+&#8221; button to add a new watch expression. (If not using FileMaker Pro Advanced, go to Manage Database and add a new calculated field.)</li>
<li>Paste the code you copied from the web viewer into the calculation field.</li>
<li>Click the Evaluate Now button in the lower left of the window. (If not using FileMaker Pro Advanced, add the calculated field to a layout.)</li>
<li>Inspect the contents, especially the <em>var</em> expressions to see that the FileMaker data is being captured properly.</li>
<li>If you were not able to see any problems, copy the result into your clipboard.</li>
<li>Open your web browser (I prefer FireFox, because I like the error log).</li>
<li>Paste the result into your browser&#8217;s location field (the place you normally type web addresses). Then press enter.</li>
<li>Open your browser&#8217;s error log. In FireFox, go to Tools-&gt;Error Log. In Safari, go to Window-&gt;Activity. In Internet Explorer, double click the little error icon in the status bar at the bottom.</li>
</ol>
<p>Hopefully, the errors reported by the browser will give you a clear enough indication of what went wrong. BTW: you may need to scroll all the way to the bottom of a long error log to see the most recent error.</p>
<p>Some other ideas:</p>
<p>Use the <em>document.print</em> JavaScript function to print messages to the web viewer or the <em>document.alert</em> function to bring up a dialog box. For instance, if you type the following in your JavaScript code:</p>
<pre>document.print('hello world');</pre>
<p>It will print <em>hello world</em> in the web viewer.</p>
<p>Or</p>
<pre>document.alert('hello world');</pre>
<p>This will bring up a dialog box displaying <em>hello world</em>.</p>
<p>You can also put variables (the terms defined in the <em>var</em> statements):</p>
<pre>document.print(targetTimeTxt);</pre>
<p>To concatenate strings, use the &#8216;+&#8217; operator the same way you use the &#8216;&amp;&#8217; in FileMaker Pro calculations. For instance:</p>
<pre>document.alert('This is the target time: ' + targetTimeTxt);</pre>
<p>And of course, if you are still stuck or you have any questions or comments, feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contact-us/contact-form?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2011/07/countdown-timers-multi-threading-with-web-viewers/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>Checkbox Record Selection In A Multi-User Environment</title>
		<link>http://filemakerinspirations.com/2011/06/checkbox-record-selection-in-a-multi-user-environment/</link>
					<comments>http://filemakerinspirations.com/2011/06/checkbox-record-selection-in-a-multi-user-environment/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Fri, 03 Jun 2011 18:03:05 +0000</pubDate>
				<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Search Tips]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=274</guid>

					<description><![CDATA[Click checkboxes to manually select records from a list. The ability to manually select individual records for filtering is useful and often necessary, but is not as straight forward as you might think. Your first thought may be to create a field in the table to act as a flag and place it on a [&#8230;]]]></description>
										<content:encoded><![CDATA[<div class="wp-caption alignright" style="width: 137px;"><img loading="lazy" decoding="async" class="wp-caption" style="padding: 0px 2px;" title="Manual Record Selection With Checkboxes" src="http://filemakerinspirations.com/images/checkboxselect.gif" alt="" width="127" height="99" align="top" /></p>
<p class="wp-caption-text aligncenter">Click checkboxes to manually select records from a list.</p>
</div>
<p>The ability to manually select individual records for filtering is useful and often necessary, but is not as straight forward as you might think.</p>
<p>Your first thought may be to create a field in the table to act as a flag and place it on a list layout as a checkbox allowing users to check the records they want. The problem with this is that when one user selects a checkbox, that record is flagged for all users. So, if you have more than one user wishing to select different records, their selections will interfere with one another.</p>
<p>With a global field, however, the value of the field is session-based: meaning what one user does will not affect other users. But, how do you use a global field, which by definition allows only one value per table, to allow you to select multiple records? With the technique explained below, we will do just that.</p>
<h2>Quick Overview</h2>
<p>There are four basic steps which are outlined in detail below.</p>
<ol>
<li>Create a global field to hold a list of selected IDs.</li>
<li>Created layered layout objects for the checkbox and use conditional formatting to show or hide the checkbox based on whether the current record&#8217;s ID exists in the global field.</li>
<li>Define a button on the layered layout objects that toggles the checkbox by adding or removing the current record&#8217;s ID to or from the list in the global field.</li>
<li>Create a filter button, &#8220;Show Selected,&#8221; that either goes to related records based on a relationship built with the global field, or run a find script that loops through the IDs in the global field and finds each record.</li>
</ol>
<p>See the details of each of these steps below as well as some alternative methods and enhancements.</p>
<h2>Step 1 &#8211; Create a Global Field</h2>
<div class="note"><strong>Deja vu!</strong> This step is exactly the same as Step One in many previous articles, including <a title="Dynamic Portal Filtering While You Type" href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/">Dynamic Portal Filtering While You Type</a>.</div>
<p>Create a text field in any table. In the field options, set the new field to use global storage as shown in the figure below. The purpose of this field is to hold a list of primary keys of the selected records.</p>
<div class="wp-caption aligncenter" style="width: 357px;"><img loading="lazy" decoding="async" class=" " title="Global Storage Screenshot" src="http://filemakerinspirations.com/images/globalStorage.gif" alt="Place a global search field with a script trigger in the header of your list layout for live search-as-you-type functionality" width="347" height="172" /></p>
<p class="wp-caption-text">Under the storage tab in the field options, select the &#8220;Use global storage (one value for all records)&#8221; check box.</p>
</div>
<p><strong>Note:</strong> This technique could also be performed with a global variable rather than a global field. In this way, no database definition changes would be necessary. However, you would not be able to use the global variable in relationships. So, in Step 4 below, you will need to choose the script option..</p>
<h2>Step 2 &#8211; Add the Checkbox to the Layout</h2>
<p>This is a little bit more complex than it sounds, and there are a number of ways to approach this. The approach we&#8217;ll take is to use conditional formatting to show or hide a checkbox based on whether the current record is in the list of selected records. The method I outline here layers a couple of layout objects on top of one another. For a simpler option, see the note at the bottom of this step.</p>
<p><strong>Step 2a</strong> &#8211; Place a checkbox image on the layout. This could be an image with a checkmark, an X, or you could simply use a text object with an X or √ character. Use the &#8220;engraved&#8221; effect and give it a border if you wish.</p>
<div class="wp-caption alignright" style="width: 60px;"><img loading="lazy" decoding="async" style="padding: 0px 2px;" title="Manual Record Selection With Checkboxes" src="http://filemakerinspirations.com/images/checkboxlayers.gif" alt="" width="52" height="55" align="top" /></div>
<p><strong>Step 2b</strong> &#8211; Place a text object or button with nothing but a single space character over the image. Make the object slightly smaller vertically and horizontally than the object from 2a and center it vertically and horizontally such that it covers the whole object except the border and/or engrave effect. Format the object to have a clear background with no border.</p>
<p><strong>Step 2c</strong> &#8211; Set conditional formatting for the item in 2b with the following formula:</p>
<pre>not ValueCount ( FilterValues ( Globals::SelectedItems ; Table::RecordID ) ) &gt; 0</pre>
<p>Replace Globals::SelectedItems with the name of the global field you created in Step 1.</p>
<p>Replace Table::RecordID primary key from the table from which you will be selecting records.</p>
<p>For the conditional format, set the background color to be the same as the background color on your layout.</p>
<div style="width: 411px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" " title="Conditional Formatting to hide the checkbox" src="http://filemakerinspirations.com/images/checkboxconditionalformat.gif" alt="Conditional formatting for the object layered on top of the checkbox." width="401" height="252" /><p class="wp-caption-text">Conditional formatting for the object layered on top of the checkbox.</p></div>
<p>The result of this step is that when the primary key of the current record exists in the list of keys in the global field, the checkbox image will be hidden, making it appear &#8220;unchecked&#8221;.</p>
<div class="note"><strong>Alternate Method:</strong> For a simpler, but perhaps not as pretty, method, simply place a single text object with a single &#8216;X&#8217; or &#8216;√&#8217; character and use conditional formatting to set the text color to the same as the background color with the formula from Step 2c.</div>
<h2>Step 3 &#8211; Create the Button</h2>
<p>Define a button for the object you created in 2b above. The button simply needs to perform a Set Field step to either add or remove the ID of the current record to or from the global field.</p>
<p>The target field will be the field you created in Step 1.</p>
<p>The calculation will be as follows:</p>
<pre>Case (
 ValueCount ( FilterValues ( Globals::SelectedItems ; Table::RecordID ) ) &gt; 0;
  Substitute ( Globals::SelectedItems ; Table::RecordID &amp; ¶ ; "" ) ;
  Globals::SelectedItems &amp; Table::RecordID &amp; ¶
)</pre>
<p>Replace Globals::SelectedItems with the name of the global field you created in Step 1.</p>
<p>Replace Table::RecordID primary key from the table from which you will be selecting records.</p>
<h2>Step 4 &#8211; Filter Button</h2>
<p>Now you will want to have a way to find all of that user&#8217;s checked records. We&#8217;ll look at two approaches. One uses a find script; the other uses a relationship.</p>
<p><strong>Option 1 &#8211; The Script Method</strong> &#8211; Create a script that enters find mode, loops through all the IDs in the global field and creates a find request for each. The script would look something like this:</p>
<pre>Enter Find Mode[]
Set Variable [$i;1]
Loop
  Set Field[ Table::RecordID ; GetValue ( Globals::SelectedItems ; $i ) ]
  Set Variable [ $i ; $i+1 ]
  Exit Loop If [ $i &gt; ValueCount ( Globals::SelectedItems ) ]
  New Record/Request[]
End Loop
Perform Find[]</pre>
<p><strong>Option 2 &#8211; The Relationship Method</strong> &#8211; Create a relationship to the table with records being selected based on the global field = record id. Then create a button that runs the script step Go To Related Record.</p>
<h2>&#8230; And You&#8217;re Done</h2>
<h2>Enhancements</h2>
<h3>User-based rather than session-based</h3>
<p>The method above will cause all selections to be cleared whenever the database is closed. You may want to set this up to be persistent across a user&#8217;s sessions. In other words, if a user logs out, then logs back in later, you may want their selections to remain. There are three possible options.</p>
<p><strong>Option 1 &#8211;</strong> Instead of using a global field, use a non-global text field in the table you are filtering. Instead of storing a list of IDs in the field, store a list of account names. When the checkbox is clicked, add or remove the current account name to or from the list. When searching for selected records, do a simple find on the current account name in that field. One drawback to this method is that it will trigger any modification date/time/user fields to be updated which may not be desirable for auditing purposes.</p>
<p><strong>Option 2</strong> requires that your database be set up with a user table and a way to access the current user&#8217;s records from the current context. Then, instead of using a global field as created in Step 1 above, you will use a field in the user table in a similar manner.</p>
<p><strong>Option 3 &#8211;</strong> Run a script on exit that saves the contents of the global field along with the current account name to a record in a table you create to store such things. Then upon opening or logging in, a script will run to search for a record in that table with the current account name and repopulate the global field with the contents of the record you populated on exit.</p>
<h3>Select All and Select None buttons</h3>
<p>For Select All, create a script that loops through each record in the current found set and adds the ID and a carriage return (¶) to the global field if it didn&#8217;t already exist.</p>
<p>For Select None, simply clear the global field:</p>
<pre>Set Field [ Globals::SelectedItems ; "" ]</pre>
<p>That&#8217;s all!</p>
<p>As always, if you have any questions or comments feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contact-us/contact-form?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2011/06/checkbox-record-selection-in-a-multi-user-environment/feed/</wfw:commentRss>
			<slash:comments>19</slash:comments>
		
		
			</item>
		<item>
		<title>Dynamic Portal Filters With Multiple Criteria</title>
		<link>http://filemakerinspirations.com/2010/10/dynamic-portal-filters-with-multiple-criteria/</link>
					<comments>http://filemakerinspirations.com/2010/10/dynamic-portal-filters-with-multiple-criteria/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Tue, 12 Oct 2010 04:01:30 +0000</pubDate>
				<category><![CDATA[Recursion]]></category>
		<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Script Triggers]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Search Tips]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=233</guid>

					<description><![CDATA[Create a dynamically filtered portal with multiple filter criteria with two (or more) global fields, portal filtering and a script trigger calling a simple script to refresh the page. This article expands on the technique used in the previous article, Dynamic Portal Filtering While You Type.]]></description>
										<content:encoded><![CDATA[<p>Create a dynamically filtered portal with multiple filter criteria.</p>
<div style="width: 395px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Live Search Field" alt="" src="http://filemakerinspirations.com/images/live-portal-search.gif" width="385" height="173" /><p class="wp-caption-text">With two (or more) global fields, portal filtering, a script trigger calling a simple script to refresh the page, you have a dynamically filtered portal with multiple filter criteria that updates as you type.</p></div>
<div class="note"><strong>Note:</strong> This article builds on a previous article, <a title="Dynamic Portal Filtering While You Type" href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/">Dynamic Portal Filtering While You Type</a>. It&#8217;s recommended that you read that article first if you haven&#8217;t already.</div>
<p>This project consists of 5 basic steps which are similar to those in the <a title="Dynamic Portal Filtering While You Type" href="/2010/10/dynamic-portal-filtering-while-you-type/">Dynamic Portal Filtering While You Type</a> article. The main differences are that the filter calculation and script become slightly more complex. The steps are as follows:</p>
<p>The project consists of 5 basic steps:</p>
<ol>
<li>Create the global search fields.</li>
<li>Create the custom function (copy and paste).</li>
<li>Set the portal filter calculation (copy and paste with some modifications).</li>
<li>Create the script (6 or more script steps depending on number of criteria).</li>
<li>Set a script trigger.</li>
</ol>
<h2>Step One: Create the Global Search Fields</h2>
<p>This step is almost exactly the same as <a href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#step1">Step One in Dynamic Portal Filtering While You Type</a>. Except that you will create one global search field for each filter criteria. Alternatively, you can create a single search field with multiple repetitions.</p>
<h2>Step Two: Create the Custom Function</h2>
<p>This step is exactly the same as <a href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#step2">Step Two in Dynamic Portal Filtering While You Type</a>. If you have already created this function in your solution, you can skip this step.</p>
<h2>Step Three: Set the portal filter calculation</h2>
<p>The calculation will be an expanded version of the one in <a href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#step3">Step Three in Dynamic Portal Filtering While You Type</a>. You will expand this calculation by adding a conditional statement for each of the filter fields and join all of these statements with the &#8220;and&#8221; operator. So, the entire expression will only evaluate to true if all filters contain a match.</p>
<p>This calculation will look different depending on the number of filters and the type of filters that make up your criteria. Here are a couple of examples:</p>
<h3>Example 1: Calculation for One Pop-up field and one open text field:</h3>
<p>The screen shot at the beginning of this article shows a portal of tasks with dynamic filter criteria to filter results by project status (a popup menu with fixed values) and an open text field which searches multiple fields. We will assume that the pop-up menu field is named <em>Globals::Search_Status</em> and the text field is named <em>Globals::Search_Text</em>.</p>
<p>In this case, we will want our filter calculation to evaluate to true if the <em>Globals::Search_Status</em> field exactly matches our status field in the task table <em>(Task::Status)</em>, <strong>and</strong> our <em>Globals::Search_Text</em> field matches one of the words in the task title <em>(Task::Title)</em> <strong>or</strong> the task description <em>(Task::Description)</em>.</p>
<p>Here&#8217;s the calculation:</p>
<pre style="overflow: auto; width: 550px; border: 1px solid black;">( IsEmpty ( Globals::Search_Status )
or
Globals::SearchStatus = Task::Status )
and
( IsEmpty ( Globals::Search_Text )
or
FindWordPartsInText ( Globals::Search_Text; Task::Title &amp; ¶ &amp; Task::Description ; 1 ) )</pre>
<p>Notice the use of the <em>IsEmpty</em> function to effectively ignore the filter if it is empty. The result is that if the filters are empty, we will see all of the records. Or if, for instance, <em>Globals::Search_Status</em> is set to &#8220;Open&#8221; and <em>Globals::Search_Text</em> is empty, we will see all task records with a status of open.</p>
<h3>Example 2: One Pop-up and Two Open Text Searches</h3>
<p>Taking the above example one step further, lets say we want to have a popup menu for status, and separate search fields for task name and task description.</p>
<div style="width: 398px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" " title="Live Search Field" alt="" src="/images/live-portal-search2.gif" width="388" height="145" /><p class="wp-caption-text">With three filter criteria, we simply add another global field, and add another expression to our portal filter calculation.</p></div>
<p>In this case, we will have three conditional statements joined with the &#8220;and&#8221; operator:</p>
<pre style="overflow: auto; width: 550px; border: 1px solid black;">( IsEmpty ( Globals::Search_Status )
or
Globals::SearchStatus = Task::Status )
and
( IsEmpty ( Globals::Search_Text1 )
or
FindWordPartsInText ( Globals::Search_Text1; Task::Title ; 1 ) )
and
( IsEmpty ( Globals::Search_Text2 )
or
FindWordPartsInText ( Globals::Search_Text2; Task::Description ; 1 ) )</pre>
<h2>Step Four: Create the script</h2>
<p>Starting with the script we created in <a href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#step4">Step Four in Dynamic Portal Filtering While You Type</a>, we will add support for a script parameter that indicates which field we are typing in. Then at the end of the script, we go back to the appropriate field.</p>
<p>Using Example 2 above, the script would look something like this:</p>
<div style="padding-left: 60px;"><strong>Set Variable</strong> [ $cursorpos ; Get ( ActiveSelectionStart ) ]<br />
<strong>Commit Records/Requests</strong> [ No dialog ]</div>
<div style="padding-left: 60px;"><strong>Refresh Window</strong> [ Flush cached join results ]</div>
<div style="padding-left: 60px;"><strong>If </strong>[ Get ( ScriptParameter ) = &#8220;Text1&#8221; ]</div>
<div style="padding-left: 90px;"><strong>Set Selection</strong> [ Globals::Search_Text1 ; Start Position: $cursorpos ; End Position: 0 ]</div>
<div style="padding-left: 60px;"><strong>Else If </strong>[ Get ( ScriptParameter ) = &#8220;Text2&#8221; ]</div>
<div style="padding-left: 90px;"><strong>Set Selection</strong> [ Globals::Search_Text2 ; Start Position: $cursorpos ; End Position: 0 ]</div>
<div style="padding-left: 60px;"><strong>End If</strong></div>
<p>Notice that we don&#8217;t do anything special if we&#8217;re editing the Status filter criteria. Since it is a pop-up menu, there is no cursor position to return to.</p>
<h2>Step Five: Set a script trigger</h2>
<p>As with all the other steps, we will start with the trigger we created in <a href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#step5">Step Five in Dynamic Portal Filtering While You Type</a>. We will do this with each of the search criteria fields. In addition, we will include the script parameter indicating which field we are in.</p>
<p>Continuing to build on the example 2 above, we will add the parameter &#8220;Text1&#8221; to the <em>Globals::Text1</em> script trigger:</p>
<div style="width: 375px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="  " title="Live Search Field" alt="" src="http://filemakerinspirations.com/images/trigger-parameter.gif" width="365" height="526" /><p class="wp-caption-text">Right click on the search field to get the &#8220;Set Script Triggers&#8221; window. Check the OnObject Modify and/or select it and click the &#8220;Select&#8230;&#8221; button, and the &#8220;Specify Script&#8221; dialog will appear. Select the script you created in Step Four and enter your script parameter in the &#8220;Optional script parameter:&#8221; field.</p></div>
<h2>&#8230; And You&#8217;re Done</h2>
<p>Now you can search for data in your portal using multiple criteria and have results appear while you are typing.</p>
<div class="note">
<h2 style="text-align: center;">Performance Consideration</h2>
<p>Prior to FileMaker 11, portal filters were generally built using relationships (see <a title="Google-Like Searches Through Relationship Filtering" href="http://filemakerinspirations.com/2009/01/google-like-search-through-relationship-filtering/">Google-Like Searches Through Relationship Filtering</a>). These tended to be more complex and difficult to build, and they had some serious limitations that can now be transcended with FileMaker 11 Portal Filters as outlined in this article. However, they had one advantage: they took advantage of FileMaker Pro&#8217;s built in indexes to retrieve the appropriate results.</p>
<p>With portal filters, FileMaker is checking each related record individually and applying the filter criteria to it. In cases where there are very large numbers of related records and/or the portal filter calculation is very complex, you may notice some performance issues.</p>
<p>Here are a couple of things you can do to maintain better performance:</p>
<ol>
<li>In your filter calculation, put the simpler conditional statements at the beginning of your &#8220;and&#8221; statements. Notice in the calculation examples I provide in Step Three above, that I first compare the <em>Globals::Status</em> field before checking the open text fields. This is because this is a very simple match statement. If this fails, the whole statement fails and FileMaker Pro moves on to the next record. (Note that this is based on speculation on my part. There are some programming languages that will evaluate an entire logic statement before determining its value; however, based on my experience, I am almost 100% certain that FileMaker Pro&#8217;s calculation engine does not.)</li>
<li>Implement the <a href="http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#delayedsearch">Delayed Search For Fast Typing</a> discussed in the previous article.</li>
</ol>
</div>
<p>As always, if you have any questions or comments feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contactForm.php?topic=support">contact me directly</a>.</p>
<p class="note">
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2010/10/dynamic-portal-filters-with-multiple-criteria/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Dynamic Portal Filtering While You Type</title>
		<link>http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/</link>
					<comments>http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Sun, 03 Oct 2010 03:04:12 +0000</pubDate>
				<category><![CDATA[Custom Functions]]></category>
		<category><![CDATA[Recursion]]></category>
		<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Script Triggers]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Search Tips]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=189</guid>

					<description><![CDATA[With portal filters, you can now show filtered views of related records in portals without having to define new relationships. What’s better than that? How about dynamically filtering your portal data while you’re typing letters into your search field! With a very simple script (4 steps), a custom function and a script trigger, you can search data in your FileMaker portals the same way you search for music in iTunes!]]></description>
										<content:encoded><![CDATA[<p>Using FileMaker 11&#8217;s new Portal Filtering feature, you can now show filtered views of related records in portals without having to define new relationships. What&#8217;s better than that? How about dynamically filtering your portal data while you&#8217;re typing letters into your search field! With a very simple script (4 steps), a custom function and a script trigger, <strong>you can search data in your FileMaker portals the same way you search for music in iTunes!</strong></p>
<div style="width: 397px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="  " title="Live Search Field" src="/images/live-portal-search1.gif" alt="" width="387" height="175" /><p class="wp-caption-text">Place a global search field above your portal. Add portal filtering that uses a custom function you will create. Add a script trigger calling a script that simply refreshes the page. And, you have a dynamically filtered portal that updates as you type.</p></div>
<p>The project consists of 5 basic steps:</p>
<ol>
<li>Create a global search field.</li>
<li>Create the custom function (copy and paste).</li>
<li>Set the portal filter calculation (copy and paste with some modifications).</li>
<li>Create the script (4 script steps).</li>
<li>Set a script trigger.</li>
</ol>
<div class="note"><strong>BTW: </strong>Don&#8217;t let the length of this article intimidate you too much. There is a great deal of technical description of the custom function which you can safely skip if you choose. The custom function can easily be copied with no understanding of how it works. However; reading the description may be useful to you if you wish to make changes or just get a better understanding of custom functions and recursion.</div>
<h2><a name="step1"></a>Step One: Create a Global Search Field</h2>
<div class="note"><strong>Note:</strong> This step is the same as Step One in my article, <a href="/2009/10/live-as-you-type-search-in-filemaker/">Live, As-You-Type Search in FileMaker</a>, which discusses a similar technique for list views.</div>
<p>Create a text field in any table. In the field options, set the new field to use global storage as shown in the figure below.</p>
<div style="width: 357px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" " title="Global Storage Screenshot" src="/images/globalStorage.gif" alt="Place a global search field with a script trigger in the header of your list layout for live search-as-you-type functionality" width="347" height="172" /><p class="wp-caption-text">Under the storage tab in the field options, select the &quot;Use global storage (one value for all records)&quot; check box.</p></div>
<div class="note"><strong>Tip:</strong> To keep your data tables clean and better organized, I recommend having a separate table to hold global fields that are not directly associated with a particular table. Global fields can be accessed from any context whether or not a relationship exists between the tables.</div>
<h2><a name="step2"></a>Step Two: Create the Custom Function</h2>
<p>To achieve the affect we are looking for, we will set a portal filter which will use a custom function that we will write that will find records that have words that begin with the search criteria typed by the user. The reason we need a custom function is that we will need to iterate (loop) through each word in the fields we want to search and look for a match on any word entered as search criteria. For instance, if the user types &#8220;port art&#8221;, we want to find records with the text &#8220;<em>art</em>icle about <em>port</em>al filtering&#8221;, but not &#8220;air<em>port</em> <em>art</em>&#8220;.<br />
Our custom function will take three parameters:</p>
<ul>
<li><strong>needles</strong>: This is the search criteria (the needles we are looking for in the haystack).</li>
<li><strong>haystack</strong>: This is the text we are searching. It will most likely be the contents of one or more fields.</li>
<li><strong>start</strong>: This is a number representing the word within haystack where we will start searching. When calling this function, we would almost always pass the number 1. This will be used to iterate through all the words in the haystack.</li>
</ul>
<p>The function will return <em>true</em> if, for each word in <em>needles</em>, there is at least one word in <em>haystack</em> that begins with that word. Otherwise, it will return false.</p>
<p>The function, which I am calling <em>FindWordPartsInText</em>, is as follows:</p>
<pre style="overflow: auto; width: 550px; border: 1px solid black;">If (
	start &gt; WordCount ( haystack ) ; 0 ;
	Let ( [
		needle = LeftWords ( needles ; 1 ) ;
		word = MiddleWords ( haystack ; start ; 1 )
		];

		Case (
			Left ( word ; Length ( needle ) ) = needle ;
			If (
				WordCount ( needles ) &lt; 2 ; 1 ;
				FindWordPartsInText ( RightWords ( needles ; WordCount ( needles ) - 1 ) ; haystack ; 1 )
			) ;
			FindWordPartsInText ( needles ; haystack ; start + 1 )
		)
	)
)</pre>
<p>At this point, you can copy and paste the above function and skip to step three if you wish. Don&#8217;t forget to add the three parameters listed above in the order listed and name the function <em>FindWordPartsInText</em>. If you give the function a different name, you will need to change the two calls to that function in the function text above.</p>
<p>If you are curious to know just how this function works, read on&#8230;</p>
<h3>Function Details</h3>
<p>In short, we start by comparing the first word in the <em>needle</em> with the first characters in the first word in <em>haystack</em>. If that match fails, we move on to the next word in <em>haystack</em>. Once a match is found, we move on to the next word in <em>needle</em>. If a match is not found and we reach the end of the <em>haystack</em>, we return <em>0</em> or <em>false</em>. If we reach the last word in <em>needle</em> and find a match, then we return <em>1</em> or <em>true</em>. This is done recursively by calling the function within itself.</p>
<div class="note">
<h3 style="text-align: center;">Quick Introduction to Recursion</h3>
<p>A <strong>recursive function</strong> is a function that calls itself in order to iterate or loop through a problem. There are two main features that are necessary for an effective recursive function:</p>
<ol>
<li>A <strong>base condition</strong> that returns a simple value and does not call itself;</li>
<li><strong> Defining condition</strong> that breaks down the complex problem into simpler problems, then calls itself with simpler values leading it closer to the base condition.</li>
</ol>
<p>The defining condition must always lead to the base condition eventually in order to avoid infinite recursion.</p>
</div>
<p>Our function, <em>FindWordPartsInText</em>, is somewhat complex in that it has double recursion. It will loop through each word in <em>haystack</em> for each word in <em>needles</em>.</p>
<p>There are two base conditions:</p>
<ul>
<li>The first base condition is <em>start &gt; WordCount ( haystack )</em>. Which tells us that we have reached the last word in <em>haystack</em>, and did not find a match, so we return false.</li>
<li>The second base condition is when both <em>Left ( word ; Length ( needle ) ) = needle</em> and <em>WordCount ( needles ) &lt; 2</em> evaluate to true. This indicates that we have found a match and reached the last word in the search criteria (<em>needle</em>).</li>
</ul>
<p>There are also two defining conditions:</p>
<ul>
<li>The first defining condition exists when we set our <em>Let</em> variables, <em>needle</em> (note <em>needle</em> is different than our parameter <em>needles</em>) and <em>word</em>. Then if a match is not found, we make the recursive call with the parameters <em>needles, haystack</em>, and <em>start + 1</em>. By adding 1 to the <em>start</em> parameter, we are telling the function to look at the next word and getting closer to the base condition of <em>start &gt; WordCount ( haystack )</em>.</li>
<li>The second defining condition exists when we find a match <em>Left ( word ; Length ( needle ) ) = needle</em>, which tells us that the first letters in the word we are searching are the same as the the search criteria. If this evaluates to true, and there are still words left in <em>needle</em> (<em>WordCount ( needles ) &lt; 2</em> evaluates to false), then we make the recursive call with the remaining words in <em>needles</em>, thus bringing us closer to our second base condition.</li>
</ul>
<h2><a name="step3"></a>Step Three &#8211; Set the Portal Filter Calculation</h2>
<div class="note">
<h3 style="text-align: center;">Quick Introduction to Portal Filtering</h3>
<p>FileMaker introduced a new feature in FileMaker 11 which allows us to specify a calculation to filter the records that appear in a portal. <img loading="lazy" decoding="async" class="aligncenter" title="Portal Filter Screenshot" src="/images/portal-filtering.gif" alt="Portal Filter Screenshot" width="265" height="159" />When selecting the check box to &#8220;Filter portal records&#8221; or clicking the &#8220;Specify&#8230;&#8221; button, we are presented with the standard calculation window. The calculation entered will be evaluated as true or false for each related record that would normally appear in the portal. If true, the record will appear, if false, the record will not appear. A simple example of how this can be useful is in a Task Management system where you want a portal in the project detail layout which shows only open task records. In this case, the calculation might look something like this: tmProject_tmTask = &#8220;Open&#8221;.</p>
</div>
<p>To set up the portal filter, simply open the calculation window for your portal filter mentioned above and enter a simple calculation that calls the custom function we created in the previous step:</p>
<pre style="overflow: auto; width: 550px; border: 1px solid black;">IsEmpty ( Globals::SearchField )
or
FindWordPartsInText ( Globals::SearchField ; RelatedTable::Field1 &amp; ¶ &amp; RelatedTable::Field2 ; 1 )</pre>
<p>You will replace <em>Globals::SearchField</em> with your global search field that you created in Step One, and replace the <em>RelatedTable::Fieldx</em> text with fields from the table in your portal. You can have as many fields there as you want, just join them with <em>&amp; ¶ &amp;</em>.</p>
<h2><a name="step4"></a>Step Four &#8211; Create the Script</h2>
<p>Our script needs to do three things: commit, refresh and put the cursor back where it was found. It will look something like this:</p>
<div style="padding-left: 30px;"><strong>Set Variable</strong> [ $cursorpos ; Get ( ActiveSelectionStart ) ]<br />
<strong>Commit Records/Requests</strong> [ No dialog ]</div>
<div style="padding-left: 30px;"><strong>Refresh Window</strong> [ Flush cached join results ]</div>
<div style="padding-left: 30px;"><strong>Set Selection</strong> [ Globals::SearchField ; Start Position: $cursorpos ; End Position: 0 ]</div>
<p>Of course, your global search field will take the place of Globals::SearchField.</p>
<div>
<div class="note"><strong>Tip:</strong> Maintaining performance and responsiveness is critical in this script since it will be run at each keystroke and will not take advantage of FileMaker indexing. To help with this, you may want to limit the amount of data that is being searched, and consider the &#8220;Delayed Search For Fast Typing&#8221; technique described below.</div>
<h2><a name="step5"></a>Step Five &#8211; Set the Script Trigger</h2>
</div>
<p>In layout mode, right click on the search field and click &#8220;Set Script Triggers &#8230;&#8221; A dialog will appear:</p>
<div style="width: 513px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" " title="Script Trigger Dialog" src="/images/scriptTriggerDialog2.gif" alt="In the script trigger dialog, select the action (or event) that you want to trigger the script, and select the script to be triggered." width="503" height="386" /><p class="wp-caption-text">In the script trigger dialog, select the action (or event) that you want to trigger the script, and select the script to be triggered.</p></div>
<p>Select the OnObjectModify action which will run the specified script any time text is entered in or deleted from the field. Click Select and choose the script that you created in Step Four.</p>
<div class="note"><strong>FYI:</strong> For the purposes of this script, OnObjectKeystroke would give us the same result. Using OnObjectKeystroke would also give you the flexibility to refine your script to handle different keystrokes differently: i.e. enter or return keystrokes. However, OnObjectKeystroke will not trigger when using the Insert Calculated Result script step.</div>
<h2>&#8230;And You&#8217;re Done!</h2>
<p>Now anyone can simply begin typing in the field and their portal contents will be refined as they type without even click a button.</p>
<p>Do you want to make it even better? . . . .</p>
<h2>Enhancements</h2>
<h3><a name="delayedsearch"></a>Delayed Search for Fast Typing</h3>
<p>One of the drawbacks of having scripts and calculations run every time you type a character is that it can cause serious lag time while you are typing. Especially for fast typist, this can be very annoying, especially when doing searches on large data sets and/or working over a slow network connection.</p>
<p>This can be easily remedied using the <strong>Install OnTimer Script</strong> script step introduced in FileMaker 10. Simply create a new script that sets an OnTimer script to call the script you made in step four (we&#8217;ll call it &#8220;Task Filter Utility&#8221;). The script will simply have one step:</p>
<p style="padding-left: 30px;"><strong>Install OnTimer Script</strong> [ &#8220;Task Filter Utility&#8221; ; Parameter: ; Interval: .3 ]</p>
<p>Now call this script from the script trigger instead of calling the <em>Task Filter Utility</em> script directly. Every time a user types another character, the script will be called again, thus resetting the interval. So the <em>Task Filter Utility</em> script will not be called until the user stops typing for 0.3 seconds. You can of course adjust that interval. I have found .3 and .4 to be good numbers.</p>
<p><strong>One more thing!!!</strong> You must also add a script step to the <em>Task Filter Utility </em>script. Otherwise it will run over and over again every .3 seconds:</p>
<p style="padding-left: 30px;"><strong>Install OnTimer Script</strong> [Interval: &#8220;&#8221; ]</p>
<p>Call the <em>Install OnTimer Script</em> step with no script or interval specified will halt the timer.</p>
<h3>Multiple Search Criteria</h3>
<div style="width: 395px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Live Search Field" src="/images/live-portal-search.gif" alt="" width="385" height="173" /><p class="wp-caption-text">What if you want to use multiple search fields? You can do that too!</p></div>
<p style="text-align: left;">To accomplish this, the filter calculation and script become slightly more complex. The calculation will need a condition for each field joined by the &#8220;and&#8221; operator, and you will want to set up the script to take a parameter indicating the field name. This way it will know which field to return to once it has committed and refreshed.</p>
<p>To continue reading about this, see <a title="Dynamic Portal Filters With Multiple Criteria" href="http://filemakerinspirations.com/2010/10/dynamic-portal-filters-with-multiple-criteria/">Dynamic Portal Filters With Multiple Criteria</a> where I walk through this in detail.</p>
<p>If you have any questions or comments feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contactForm.php?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2010/10/dynamic-portal-filtering-while-you-type/feed/</wfw:commentRss>
			<slash:comments>87</slash:comments>
		
		
			</item>
		<item>
		<title>Live, As-You-Type Search in FileMaker</title>
		<link>http://filemakerinspirations.com/2009/10/live-as-you-type-search-in-filemaker/</link>
					<comments>http://filemakerinspirations.com/2009/10/live-as-you-type-search-in-filemaker/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Wed, 14 Oct 2009 16:35:48 +0000</pubDate>
				<category><![CDATA[Script Triggers]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[Search Tips]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=158</guid>

					<description><![CDATA[FileMaker 10&#8217;s Script Triggers feature opens the door to create richer, more dynamic and more responsive user interfaces.  For example, it is now possible to create as-you-type search functionality similar to that found in iTunes and Mac OS X Spotlight where the list of results updates dynamically as the user types. This is a slick [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>FileMaker 10&#8217;s Script Triggers feature opens the door to create richer, more dynamic and more responsive user interfaces.  For example, it is now possible to create as-you-type search functionality similar to that found in iTunes and Mac OS X Spotlight where the list of results updates dynamically as the user types. This is a slick feature. I&#8217;ve added this functionality to a few of my client&#8217;s databases, and it has never failed to elicit a glowing response.</p>
<div style="width: 260px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Live Search Field" alt="Place a global search field with a script trigger in the header of your list layout for live search-as-you-type functionality" src="/images/live-search.gif" width="250" height="30" /><p class="wp-caption-text">Place a global search field with a script trigger in the header of your list layout for live search-as-you-type functionality</p></div>
<h2>Step One: Create a Global Search Field</h2>
<p>This step is the same as Step One in my article, <a title="Google-Like Searches In FileMaker - FileMaker Inspirations" href="/2008/10/google-like-searches-in-filemaker/">Google-Like Searches In FileMaker</a>. If you have already done this for that project, you can skip this step and use the same field. Otherwise, create a text field in any table. In the field options, set the new field to use global storage as shown in the figure below.</p>
<div style="width: 357px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" " title="Global Storage Screenshot" alt="Place a global search field with a script trigger in the header of your list layout for live search-as-you-type functionality" src="/images/globalStorage.gif" width="347" height="172" /><p class="wp-caption-text">Under the storage tab in the field options, select the &#8220;Use global storage (one value for all records)&#8221; check box.</p></div>
<div class="note"><strong>Tip:</strong> To keep your data tables clean, I recommend having a separate table to hold global fields, such as this one, that are not directly associated with a particular table. Global fields can be accessed from any context whether or not a relationship exists between the tables.</div>
<h2>Step Two: Create the Triggered Find Script</h2>
<p>Create a script that will be triggered when the user enters text. The script will be similar to the one created in the <a title="Google-Like Searches In FileMaker - Non-Contiguous - FileMaker Inspirations" href="/2008/10/google-like-searches-in-filemaker-non-contiguous/">Google-Like Searches In Filemaker &#8211; Non Contiguous</a> article except that it will also capture the cursor position within the find field and return it there when complete, and it will not present an error dialog if the found set is empty. Here&#8217;s an overview:</p>
<ul>
<li>Set variables to store the position of the cursor in the search field and the selection size.</li>
<li>Enter find mode (do not pause or specify find request).</li>
<li>Loop through each search term in the global field, create a find request and use the &#8220;Set Field&#8221; script step to set the find criteria in each field to search. (For more information on understanding this looping process, see <a title="Google-Like Searches In FileMaker - Non-Contiguous - FileMaker Inspirations" href="/2008/10/google-like-searches-in-filemaker-non-contiguous/">Google-Like Searches In Filemaker &#8211; Non Contiguous</a>.)</li>
<li>Perform Find in first loop iteration and Constrain Found Set in subsequent iterations.</li>
<li>Use the Set Selection script step to place the cursor back in the search field at the position stored at the beginning of the script.</li>
</ul>
<p>The script will look something like this:</p>
<div style="padding-left: 30px;"><strong>Set Variable</strong> [ $selectionStart ; Get ( ActiveSelectionStart ) ]<br />
<strong>Set Variable</strong> [ $selectionSize ; Get ( ActiveSelectionSize ) ]</div>
<div style="padding-left: 30px;"><strong>Commit Records/Requests</strong> [ No dialog ]<br />
<strong>Set Variable</strong> [ $searchList ; Substitute ( g_SearchField ; &#8221; &#8221; ; ¶ ) ]<br />
<strong>Set Variable</strong> [ $i ; 1 ]<br />
<strong>Loop</strong></div>
<div style="padding-left: 60px;"><strong>Exit Loop If</strong> [ $i &gt; ValueCount ( $searchList ) ]<br />
<strong>Set Variable</strong> [ $thisTerm ; GetValue ( $searchList ; $i ) ]<br />
<strong>Enter Find Mode</strong> [ ]<br />
<strong>Set Field</strong> [ Contacts::First Name; $thisTerm ]<br />
<strong>New Record/Request </strong><br />
<strong>Set Field</strong> [ Contacts::Last Name; $thisTerm ]<br />
<strong>New Record/Request </strong><br />
<strong>Set Field</strong> [ Contacts::Street Address 1; $thisTerm ]<br />
<strong>New Record/Request </strong><br />
<strong>Set Field</strong> [ Contacts::City 1; $thisTerm ]<br />
<strong>New Record/Request </strong><br />
<strong>Set Field</strong> [ Contacts::State Province 1; $thisTerm ]<br />
<strong>New Record/Request </strong><br />
<strong>Set Field</strong> [ Contacts::Postal Code 1; $thisTerm ]<br />
<strong>Set Error Capture</strong> [ On ]<br />
<strong>If</strong> [ $i = 1 ]</div>
<div style="padding-left: 90px;"><strong>Perform Find</strong> [ ]</div>
<div style="padding-left: 60px;"><strong>Else</strong></div>
<div style="padding-left: 90px;"><strong>Constrain Found Set</strong> [ ]</div>
<div style="padding-left: 60px;"><strong>End If</strong><br />
<strong>Set Error Capture</strong> [ Off ]</div>
<div style="padding-left: 60px;"><strong>Exit Loop If</strong> [ Get ( FoundCount ) = 0 ]<br />
<strong>Set Variable</strong> [ $i ; $i + 1 ]</div>
<div style="padding-left: 30px;"><strong>End Loop</strong></div>
<div style="padding-left: 30px;"><strong>Set Selection</strong> [ Globals::g_SearchField; Start Position: $selectionStart; End Position: $selectionSize ]</div>
<div>
<div class="note"><strong>Tip:</strong> Maintaining performance and responsiveness is critical in this script since it will be run at each keystroke. To help with this, I recommend the following:</p>
<ul>
<li>Add a <strong>Freeze Window</strong> script step to the beginning of this script and a <strong>Refresh Window</strong> script to the end. Doing so will help to improve performance and eliminate any possible screen flashes. This is a good general procedure to practice in any of your scripts that change modes, found sets or layouts.</li>
<li>Use only fields that can be indexed in the search. Avoid related fields or calculations that include them.</li>
</ul>
</div>
<h2>Step 3 &#8211; Set the Script Trigger</h2>
</div>
<p>In layout mode, right click on the search field and click &#8220;Set Script Triggers &#8230;&#8221; A dialog will appear:</p>
<div style="width: 470px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Script Trigger Dialog" alt="In the script trigger dialog, select the action (or event) that you want to trigger the script, and select the script to be triggered." src="/images/scriptTriggerDialog.gif" width="460" height="394" /><p class="wp-caption-text">In the script trigger dialog, select the action (or event) that you want to trigger the script, and select the script to be triggered.</p></div>
<p>Select the OnObjectModify action which will run the specified script any time text is entered in or deleted from the field. Click Select and choose the script that you created in Step Two.</p>
<h2>&#8230;And You&#8217;re Done!</h2>
<p>Now the user can simply begin typing in the field and their search results will be refined as they type without requiring that the user enter find mode, know which fields to search or even click a button.</p>
<p>If you have any questions or comments feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contactForm.php?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2009/10/live-as-you-type-search-in-filemaker/feed/</wfw:commentRss>
			<slash:comments>60</slash:comments>
		
		
			</item>
		<item>
		<title>Faster, Maintainable Scripts with Variables</title>
		<link>http://filemakerinspirations.com/2009/02/faster-maintainable-scripts-with-variables/</link>
					<comments>http://filemakerinspirations.com/2009/02/faster-maintainable-scripts-with-variables/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Thu, 26 Feb 2009 06:15:21 +0000</pubDate>
				<category><![CDATA[Scripting]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=70</guid>

					<description><![CDATA[9 FileMaker Pro Tips using the Set Variable script step to make your scripts more efficient, portable and easier to maintain. Use variables instead of global fields. Variables are stored in active system memory while fields are stored on disk. Therefore, accessing a variable is much faster than accessing fields. So, use global variables instead [&#8230;]]]></description>
										<content:encoded><![CDATA[<h2>9 FileMaker Pro Tips using the Set Variable script step to make your scripts more efficient, portable and easier to maintain.</h2>
<ol class='paddedList'>
<li><strong>Use variables instead of global fields</strong>. Variables are stored in active system memory while fields are stored on disk. Therefore, accessing a variable is much faster than accessing fields. So, use global variables instead of global fields wherever possible except under the following circumstances:
<ul>
<li>global data is needed to define a relationship,</li>
<li>a user must input or modify the global data.</li>
</ul>
</li>
<li><strong>Improving performance in looping calculations.</strong> The best way to improve the performance of a complex, time-consuming script is to make the loops more efficient. One way to do this is to use local variables to store data to be manipulated in a loop. When the manipulations are complete, copy the data back to a field if it is needed there.</li>
<li><strong>Managing script parameter contents.</strong> When writing a script that accepts a parameter, set a variable with a descriptive name at the beginning of the script with the contents of the parameter (or multiple variables if input is being parsed for multiple delimited values). Use that variable in the script where the data is needed instead of using <em>Get ( ScriptParameter )</em>. This will make the script much easier to understand and future maintenance much easier.</li>
<li><strong>Use variables with global fields.</strong> When writing a script that grabs data from one or more global fields, copy the value of the global fields to variables as the first step or steps in the script. This improves the script&#8217;s portability and maintainability, because if you ever have to change the data source, you will  only have to change the first few steps in the script. It improves readability of the script, because you can instantly see what fields the script is using by glancing at the first steps.</li>
<li><strong>More flexible portal looping.</strong> When looping through a portal, instead of using <strong>Go to Portal Row [Next; Exit after last]</strong>, consider using an incrementing variable with <strong>Go to Portal Row</strong> by calculation. This allows you to Commit records and/or move to different layouts or objects without losing the portal row you are on. The script would appear as follows:
<div style="padding-left: 30px;"><strong>Set Variable</strong> [ $i ; 1 ]<br />
<strong>Loop</strong></div>
<div style="padding-left: 60px;"><strong>Exit Loop If</strong> [ $i &gt;<em> Count ( /* primary key of related record */</em> ) ]<br />
<strong>Go to Object</strong> [ <em>/* portal object name */</em> ] (needed if layout has multiple portals)<br />
<strong>Go to Portal Row</strong> [ No Dialog ; $i ]<br />
&#8230;<br />
<em>Do loop operations.</em><br />
&#8230;<br />
<strong>Set Variable</strong> [ $i ; $i + 1 ]</div>
<div style="padding-left: 30px;"><strong>End Loop</strong></div>
</li>
<li><strong>More flexible record looping. </strong>Use the above technique for looping through records as well, thus allowing you to jump to different layouts, records and/or found sets without losing your place or having to open multiple windows.</li>
<li><strong>Manage smarter sorting operations. </strong>Use global variables to store and manage sorting operations. In a future article, I will describe a method of producing column headers that toggle sort order when clicked that uses global variables rather than global fields.</li>
<li><strong>Create new records more efficiently.</strong> When scripting the creation of new related records, copy the value of the primary key to a variable, go to a layout of the related table, create a new record and copy the contents of the variable to the foreign key field. Finally, return to the original layout.  With the use of the Freeze Window step, this process can be completely invisible to the user, and you will not have to use the &#8220;<em>Allow creation of records in this table via this relationship</em>&#8221; feature which causes a blank record at the bottom of your portals which may be awkward and confusing to some users.</li>
<li><strong>Parsing lists or other conglomerate data.</strong> Scripts become difficult to read and maintain when field and/or parameter data is parsed within script steps, especially when the parsed data will be used more than once. Use variables to hold parsed data. Consider the readability of the following script clips which perform the exact same function:
<ol>
<li>
<div style="padding-top:12px;"><strong>Set Variable</strong> [ $FirstName ; LeftWords ( Contacts::Name ; 1 ) ]<br />
<strong>Set Variable</strong> [ $LastName ; RightWords ( Users::Name ; WordCount ( Contacts::Name ) &#8211; 1 ) ]<br />
<strong>Show Custom Dialog</strong> [&#8220;Hello &#8221; &amp; $FirstName ; &#8220;Dear Mr/Ms &#8221; &amp; $LastName &amp; &#8220;, Do you want to add yourself to the &#8221; &amp;  $LastName &amp; &#8221; family group?&#8221;]</div>
</li>
<li>
<div style="padding-top:12px;"><strong>Show Custom Dialog</strong> [&#8220;Hello &#8221; &amp; LeftWords ( Contacts::Name ; 1 ) ; &#8220;Dear Mr/Ms &#8221; &amp; RightWords ( Users::Name ; WordCount ( Contacts::Name ) &#8211; 1 ) &amp; &#8220;, Do you want to add yourself to the &#8221; &amp; RightWords ( Users::Name ; WordCount ( Contacts::Name ) &#8211; 1 ) &amp; &#8221; family group?&#8221;]</div>
</li>
</ol>
</li>
</ol>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2009/02/faster-maintainable-scripts-with-variables/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title>Google-like Search Through Relationship Filtering</title>
		<link>http://filemakerinspirations.com/2009/01/google-like-search-through-relationship-filtering/</link>
					<comments>http://filemakerinspirations.com/2009/01/google-like-search-through-relationship-filtering/#comments</comments>
		
		<dc:creator><![CDATA[Danny]]></dc:creator>
		<pubDate>Wed, 14 Jan 2009 18:50:51 +0000</pubDate>
				<category><![CDATA[Connectivity]]></category>
		<category><![CDATA[Relationships]]></category>
		<category><![CDATA[Search Tips]]></category>
		<guid isPermaLink="false">http://filemakerinspirations.com/?p=110</guid>

					<description><![CDATA[This article describes a script-less technique for using dynamic relationships and portals to search values in a related table using a single global field. It is a part of a series on Advanced Search Interfaces. This technique produces an interface similar to the Google-like Search described in previous articles in this series, but instead of [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>This article describes a script-less technique for using dynamic relationships and portals to search values in a related table using a single global field. It is a part of a series on <a title="Advanced Search Interfaces - FileMaker Inspirations" href="http://filemakerinspirations.com/?p=24">Advanced Search Interfaces</a>.</p>
<p>This technique produces an interface similar to the Google-like Search described in previous articles in this series, but instead of using a script and find mode, it uses calculated fields and relationships. This can be quite useful in portal views and requires no scripting.</p>
<div style="width: 450px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Search Result Example" src="/images/portalSearch.gif" alt="Place a global search field and button above a portal for Spotlight-like search functionality. " width="440" height="114" /><p class="wp-caption-text">Place a global search field and button above a portal for Google-like search functionality through a portal. </p></div>
<p>To create the filter, we will build a relationship between a table with a global search field (we&#8217;ll call this the <em>base</em> table) and the table in which we want to view filtered records (we&#8217;ll call this the <em>target</em> table). This relationship will be based on three calculated fields: two in the base table and one in the target table.</p>
<h2>Step One: Create a Global Search Field</h2>
<p>Create a text field, <em>GlobalSearch</em>, in the base table. In the field options, set the new field to use global storage as shown in the figure below.</p>
<div style="width: 357px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Global Storage Screenshot" src="/images/globalStorage.gif" alt="text" width="347" height="172" /><p class="wp-caption-text">Under the storage tab in the field options, select the &quot;Use global storage (one value for all records)&quot; check box.</p></div>
<h2>Step Two: Create Calculated Fields For The Relationship</h2>
<h3>Field 1: FilterStart</h3>
<p>In the base table, we will create a calculated field, <em>FilterStart</em>. This field will contain a calculation that will produce the &#8216;lowest&#8217; value in the possible set of desired results.</p>
<div style="width: 445px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="FilterStart Field Definition" src="/images/filterstart.gif" alt="Place a global search field and button above a portal for Spotlight-like search functionality. " width="435" height="190" /><p class="wp-caption-text">The FilterStart calculated field will produce a Text result of a single underscore(_) when GlobalSearch is empty, otherwise it will simply be the value of the GlobalSearch field. In a sort, this value would always appear at the beginning of the desired search results.</p></div>
<ul>
<li>The FilterStart calculation, as shown in the image above, will be as follows: <code>Case ( Isempty ( GlobalSearch ) ; "_" ; GlobalSearch )</code></li>
<li>The calculation result is Text</li>
<li>The &#8220;Do not evaluate &#8230;&#8221; check box should be checked if you want to show nothing when the search field is empty, unchecked if you want to show all records when the search field is empty.</li>
</ul>
<h3>Field 2: Filter End</h3>
<p>Also in the base table, we will create another calculated field, <em>FilterEnd</em>. This field will contain a calculation that will produce the &#8216;highest&#8217; value in the possible set of desired results.</p>
<div style="width: 432px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="FilterEnd Field Definition" src="/images/filterend.gif" alt="" width="422" height="189" /><p class="wp-caption-text">The FilterEnd calculated field will produce a Text result of the contents of GlobalSearch followed by the repeated letter z. In a sort, this value would always appear at the end of the desired search results.</p></div>
<ul>
<li>The FilterEnd calculation, as shown in the image above, will be as follows: <code>GlobalSearch &amp; "zzz"<br />
</code></li>
<li>The calculation result is Text</li>
<li>The &#8220;Do not evaluate &#8230;&#8221; check box should be checked if you want to show nothing when the search field is empty, unchecked if you want to show all records when the search field is empty.</li>
</ul>
<h3>Field 3: FilterTarget</h3>
<p>In the target table, we will create a calculated field, <em>FilterTarget</em>, which will contain a value list of values from each of the fields we wish to search on. For instance, if we have the two fields, <em>Name</em> and <em>Notes</em>, and we want search on each of these, our calculation would be as follows:</p>
<ul>
<li>The FilterTarget calculation will be as follows: <code>Name &amp; ¶ &amp; Notes<br />
</code></li>
<li>The calculation result is Text</li>
</ul>
<h2>Step Three: Build The Relationship</h2>
<p>Add a new table occurrence of the target table to the relationship graph.</p>
<p>Define a relationship between the base table and the new occurrence of the target table.</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter" title="Base-Target Relationship definition." src="/images/basetargetrelationship.gif" alt="" width="403" height="111" /></p>
<p style="text-align: left;">The relationship will be based on two criteria:</p>
<ul>
<li>FilterStart in the base table is less than or equal to FilterTarget in the target table.</li>
<li>FilterEnd in the base table is greater than or equal to FilterTarget in the target table.</li>
</ul>
<p>The result in the relationship graph should look something like this:</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter" title="Base-Target Relationship graph." src="/images/basetargetoccurrence.gif" alt="" width="366" height="72" /></p>
<h2>Step Four: Add Layout Objects</h2>
<p>You can then place the global field and a search button which simply performs a commit.</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter" title="Single Search Field Sample" src="/images/searchField.gif" alt="" width="262" height="33" /></p>
<div style="width: 467px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" title="Commit Button Definition" src="/images/commitbutton.gif" alt="In the Button Setup Dialog, simply select Commit Records/Requests. No script necessary." width="457" height="186" /><p class="wp-caption-text">In the Button Setup Dialog for the search button, simply select Commit Records/Requests. No script necessary.</p></div>
<p>Below the search field, add a portal to the new table occurrence you just created in the relationship graph.</p>
<p>That’s it! Now the user can simply enter search terms in the search field and view their results in the portal.</p>
<h2>Limitations</h2>
<p>One limitation of this method is that it will only match on the first word or words of the target table&#8217;s fields. For instance, typing &#8220;Doe&#8221; in the search field will not match a record with Name &#8220;John Doe&#8221;. One possible solution would be to substitute spaces in the FilterTarget calculation with carriage returns:</p>
<p><code>Substitute ( Name ; " " ; ¶ ) &amp; ¶ &amp; Substitute ( Notes ; " " ; ¶ )</code></p>
<p>This allows searching on any word. However, this introduces a new limitation; You may now only match on single search terms. Any muliple word searches will result in no matches. A possible solution to this would be to perform the same substitution in the FilterStart and FilterEnd fields. The problem with this solution is that our results will be based on an OR search rather than an AND search. In other words, if &#8220;John Doe&#8221; is typed as the search term, the resulting set will be all records with &#8220;John&#8221; and all records with &#8220;Doe&#8221;.</p>
<p>To achieve a true Google-like search showing all records that contain a search phrase within a field, we would either have to introduce some scripting, perhaps using triggers, or produce calculations with much greater complexity which could greatly increase file size (due to indexing) and goes beyond the scope of this particular article.</p>
<p>So, use this method when matching from the beginning of the target field or matching on a single term is sufficient.</p>
<p>For a much more robust and streamlined solution that eliminates all of these limitations using the Portal Filtering feature introduced in FileMaker 11, see <a href="/2010/10/dynamic-portal-filtering-while-you-type/">Dynamic Portal Filtering While You Type</a>.</p>
<p>If you have any questions or comments feel free to post a comment below or <a href="http://www.inspirationssoftwaredesign.com/contactForm.php?topic=support">contact me directly</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://filemakerinspirations.com/2009/01/google-like-search-through-relationship-filtering/feed/</wfw:commentRss>
			<slash:comments>21</slash:comments>
		
		
			</item>
	</channel>
</rss>
