4DSCRIPT usage

classic Classic list List threaded Threaded
23 messages Options
12
Reply | Threaded
Open this post in threaded view
|

4DSCRIPT usage

Randy Jaynes
How do you get something like this tor process properly when making a call to 4D’s web server:
        <!--#4DSCRIPT/WEB_SetJSCode/[ProductionTicket_Form]Form_Number-->

I’m getting the method WEB_SetJSCode to be called, but the literal text "[ProductionTicket_Form]Form_Number” is the value of $1, not the CONTENTS of the 4D field [ProductionTicket_Form]Form_Number.

I’ve already tried
        <!--#4DSCRIPT/WEB_SetJSCode/<!—#4DTEXT [ProductionTicket_Form]Form_Number—>—>

But that doesn’t work because then my method gets (<!—#4DTEXT [ProductionTicket_Form]Form_Number—>

It’s like I need to do double PROCESS HTML TAGS or something in order to get this to work.

Short of making a case statement for every possible value I pass into my method, I don’t see how to do this easily.

Thanks,
Randy

----------------------------------------------------------------------
Randy Jaynes
Senior Programmer and Customer Support

http://printpoint.com • (845) 359-0298 • PrintPoint, Inc • 57 Ludlow Lane • Palisades, NY 10964




**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

RE: 4DSCRIPT usage

Timothy Penner
Looking at the docs it seems 4DSCRIPT is working as intended:

"The 4DSCRIPT tag allows you to execute 4D methods when processing the template.. The presence of the <!--#4DSCRIPT/MyMethod/MyParam--> tag as an HTML comment forces the execution of the MyMethod method with the Param parameter as a string in $1."

The relevant part being "with the Param parameter as a string in $1"

Maybe 4DEVAL would work better for you?
http://doc.4d.com/4Dv15R2/4D/15-R2/4D-Transformation-Tags.300-2543330.en.html#2694782

-Tim




**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

spiffyguy
In reply to this post by Randy Jaynes
I could be wrong but I thought 4DSCRIPT used the same process to handle all calls... so wouldn't the method "WEB_SetJSCode" have the selection for [ProductionTicket_Form] still open and could retrieve the field "Form_Number" without passing it as $1?

- Matt
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Randy Jaynes
In reply to this post by Timothy Penner
4DSCRIPT is working as documented. No doubt about it.

However, I’m trying to make this a generic method that I can pass 4D fields and variables into so that I can be sure the dynamic data contained within the field/variable does not have any CR or CRLF that could wreck the javascript code.

Unfortunately, I'm doing this in our v13 OEM database, so a v14, v15, or v15R2 command will not help me.

It certainly seems from the documentation link you provide that 4DEVAL might do the trick, but it doesn’t appear to be available in 4D v13.

Randy

----------------------------------------------------------------------
Randy Jaynes
Senior Programmer and Customer Support

http://printpoint.com • (845) 359-0298 • PrintPoint, Inc • 57 Ludlow Lane • Palisades, NY 10964


 

> On Jan 25, 2016, at 3:21 PM, Timothy Penner <[hidden email]> wrote:
>
> Looking at the docs it seems 4DSCRIPT is working as intended:
>
> "The 4DSCRIPT tag allows you to execute 4D methods when processing the template.. The presence of the <!--#4DSCRIPT/MyMethod/MyParam--> tag as an HTML comment forces the execution of the MyMethod method with the Param parameter as a string in $1."
>
> The relevant part being "with the Param parameter as a string in $1"
>
> Maybe 4DEVAL would work better for you?
> http://doc.4d.com/4Dv15R2/4D/15-R2/4D-Transformation-Tags.300-2543330.en.html#2694782
>
> -Tim
>
>
>
>
> **********************************************************************
> 4D Internet Users Group (4D iNUG)
> FAQ:  http://lists.4d.com/faqnug.html
> Archive:  http://lists.4d.com/archives.html
> Options: http://lists.4d.com/mailman/options/4d_tech
> Unsub:  mailto:[hidden email]
> **********************************************************************

**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Lee Hinde
In reply to this post by Randy Jaynes
On Jan 25, 2016, at 12:16 PM, Randy Jaynes <[hidden email]> wrote:

>
> How do you get something like this tor process properly when making a call to 4D’s web server:
> <!--#4DSCRIPT/WEB_SetJSCode/[ProductionTicket_Form]Form_Number-->
>
> I’m getting the method WEB_SetJSCode to be called, but the literal text "[ProductionTicket_Form]Form_Number” is the value of $1, not the CONTENTS of the 4D field [ProductionTicket_Form]Form_Number.
>
> I’ve already tried
> <!--#4DSCRIPT/WEB_SetJSCode/<!—#4DTEXT [ProductionTicket_Form]Form_Number—>—>
>
> But that doesn’t work because then my method gets (<!—#4DTEXT [ProductionTicket_Form]Form_Number—>
>
> It’s like I need to do double PROCESS HTML TAGS or something in order to get this to work.
>
> Short of making a case statement for every possible value I pass into my method, I don’t see how to do this easily.
>

Watching this, because the case statement route is what I’ve done in the past.


**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Randy Jaynes
In reply to this post by spiffyguy

> On Jan 25, 2016, at 3:23 PM, spiffyguy <[hidden email]> wrote:
>
> I could be wrong but I thought 4DSCRIPT used the same process to handle all
> calls... so wouldn't the method "WEB_SetJSCode" have the selection for
> [ProductionTicket_Form] still open and could retrieve the field
> "Form_Number" without passing it as $1?
>
Yes. the field would be available to the method, that’s why the only way I can see to make this method generic is to have a case statement testing the actual $1 that gets passed in and then access the field / variable I need.

Not really a ‘generic’ method, but it may be the best I can do in v13.

- Matt


Randy

----------------------------------------------------------------------
Randy Jaynes
Senior Programmer and Customer Support

http://printpoint.com • (845) 359-0298 • PrintPoint, Inc • 57 Ludlow Lane • Palisades, NY 10964

**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

RE: 4DSCRIPT usage

Timothy Penner
In reply to this post by Randy Jaynes
You are correct that 4DEVAL is a feature of v15 (first available in v14R4) so that wouldn’t be available in v13. There is always the option of upgrading though.

-Tim




**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Randy Jaynes
We’re working on upgrading, but as an OEM where this database is used in 100+ sites, it’s not something I can do at the drop of a hat. :-)


Randy

----------------------------------------------------------------------
Randy Jaynes
Senior Programmer and Customer Support

http://printpoint.com • (845) 359-0298 • PrintPoint, Inc • 57 Ludlow Lane • Palisades, NY 10964


 

> On Jan 25, 2016, at 3:36 PM, Timothy Penner <[hidden email]> wrote:
>
> You are correct that 4DEVAL is a feature of v15 (first available in v14R4) so that wouldn’t be available in v13. There is always the option of upgrading though.
>
> -Tim
>
>
>
>
> **********************************************************************
> 4D Internet Users Group (4D iNUG)
> FAQ:  http://lists.4d.com/faqnug.html
> Archive:  http://lists.4d.com/archives.html
> Options: http://lists.4d.com/mailman/options/4d_tech
> Unsub:  mailto:[hidden email]
> **********************************************************************

**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

David Adams-4
In reply to this post by Timothy Penner
Just noticed this thread and I thought I'd offer a few suggestions off the
top of my head. (And I won't talk about parrots.)

First off, check out 4DVAR in V13 as it is probably what you're looking
for. Now on to more points.

Yeah, it's a drag that 4DSCRIPT doesn't resolve expressions, but that's the
way it works. You get a text value in $1 and that's it - it's just text.
You can, however, pack whatever you want into that text value for parsing.


<!--#4DSCRIPT/WEB_SetJSCode/table=PT_Form&field=Form_Num-->

<!--#4DSCRIPT/WEB_SetJSCode/table=PT_Form&field=Form_Num-->

Now you've got some easy-to-parse arguments:

table PT_Form
field Form_Num
You need a lookup to translate those into a workable data source reference.
From there, you can validate that the reference is valid before blowing
everything up. There are a lot of ways to implement your lookup table:

* A hard-coded case statement.
* 4D's built-in data dictionary (use table & field number instead of
aliases like I've done).
* A custom table.
* Hard-coded arrays.
* An external JSON file with all of the details.
* A built-in object
* Etc.

I wouldn't use a hard-coded case statement because it's a pain, you can't
reuse the data for anything else and it's just kind of horrible in a big
hurry. By "reuse the data for anything else", imagine that you want to make
yourself a Web page with a catalog of your available bits of data. With the
case statement...what do you do? Overload the method to return some kind of
expression instead of a value when you pass in a magic parameter? Ugly,
confusing, error-prone, and time-consuming. With
arrays/blobs/documents/records you can just grab the data and format it as
HTML/JSON/XML/Whatever and you're good to go. Locking the references up in
hard-coded assignments (case statement or otherwise) is a dead end.

Move the data into data and search on it as data.

Does it take some work to set all of this up? Yeah, but very little at the
most basic level. For me, I don't even want to see literal table and field
names in Web callbacks. Why?

* You change something in the structure. You compile. No problem. Your Web
pages are broken. No warning and not easy to test for. (If you went to the
trouble of making that testable, you wouldn't be using [Table]Field in the
first place, would be my guess for most of us.)

* Tables and field are so 2009. With a lookup system, you can expose
aliases/names that map back to tables, fields, expressions, etc. You've got
an interface/API, not a literal reference. I don't use 4DACTION and avoid
using

* Need a new feature or attribute for your data? Say, a security system to
restrict access to only some users. Say you want to apply rounding or
formatting consistently. Say you want to log reads on a specific field. Say
you want to...I don't know, there are always reasons to extend table and
field properties. And, again, "table" and "field" are used loosely here.
Since you're dealing with an alias, the real data may be spread across
files, produced through calculation, read from a remote host on demand, etc.

So, a lookup system is less brittle, more reusable, more extensible and
more flexible. Better.

If you're going to the Summit this year, check out Brent Raymond's
presentation. Some of you m ay know him from the Chicago 4DMethod user
group:

http://4dmethod.com/

It sounds like a very interesting talk that covers some of the issues
raised above and then some. Brent also has a series of past presentations
(from himself and various 4D luminaries) for viewing. Check out all of the
cool pictures from the Art Institute of Chicago. Hey, they've got the
largest collection of Impressionist work outside of France. It's amazing.

P.S. I didn't talk about parrots.
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

spiffyguy
In reply to this post by Randy Jaynes
Okay ya I understand what you are trying to do.  Hmm... can i suggest maybe if you change your concept of $1 and just assume a process variable OR a selection is open?

Maybe something like this at the top of your method:

if (records in selection([ProductionTicket_Form])=0)
   $mydata:=$1
else
   $mydata:=[ProductionTicket_Form]Form_Number
end if

That way it could work in a number of ways for whatever you need it to and be generic enough.

Good luck, I hope that might help.

- Matt
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Randy Jaynes
I think I have to either do something like what Dave Adams suggests or just go the hard coded case statement way.

This webpage in question is part of a custom project for 1 out of many of our OEM customers, so right now it’s very specific. The problem is that in 1 of the 2 pages being served, I have to call this method maybe 20 times with various fields from 2 different tables and a few 4D variables. These fields and variables are of different data types as well, so the case statement really seems the only way I can go.

I’ll basically just use the field name or variable name as a flag for the case in the method and the 4D code will handle the translation of the data type to text. This also has the advantage that even if the name of the field or table in 4D changes, the web page itself is protected because the case in the 4D method continues to test for the text of the flag, not the actual field name or table name.

A pain, but I don’t need it to cover all possible eventualities, just the current 20 or so calls. It could be a lot worse.

Randy

----------------------------------------------------------------------
Randy Jaynes
Senior Programmer and Customer Support

http://printpoint.com • (845) 359-0298 • PrintPoint, Inc • 57 Ludlow Lane • Palisades, NY 10964


 

> On Jan 25, 2016, at 4:08 PM, spiffyguy <[hidden email]> wrote:
>
> Okay ya I understand what you are trying to do.  Hmm... can i suggest maybe
> if you change your concept of $1 and just assume a process variable OR a
> selection is open?
>
> Maybe something like this at the top of your method:
>
> if (records in selection([ProductionTicket_Form])=0)
>   $mydata:=$1
> else
>   $mydata:=[ProductionTicket_Form]Form_Number
> end if
>
> That way it could work in a number of ways for whatever you need it to and
> be generic enough.
>
> Good luck, I hope that might help.
>
> - Matt
>
>
>
>
> --
> View this message in context: http://4d.1045681.n5.nabble.com/4DSCRIPT-usage-tp5742363p5742373.html
> Sent from the 4D Tech mailing list archive at Nabble.com.
> **********************************************************************
> 4D Internet Users Group (4D iNUG)
> FAQ:  http://lists.4d.com/faqnug.html
> Archive:  http://lists.4d.com/archives.html
> Options: http://lists.4d.com/mailman/options/4d_tech
> Unsub:  mailto:[hidden email]
> **********************************************************************

**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Chip Scheide
It *will be* a lot worse

FTFY :)
Chip
On Mon, 25 Jan 2016 16:41:17 -0500, Randy Jaynes wrote:
>
> A pain, but I don’t need it to cover all possible eventualities, just
> the current 20 or so calls. It could be a lot worse.
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

David Adams-4
In reply to this post by Randy Jaynes
> A pain, but I don’t need it to cover all possible eventualities, just the
current 20 or so calls.
> It could be a lot worse.

It sounds like you've worked out a compromise that will sort you out for
now...but just a few thoughts. First of, try 4DVAR as it does resolve
expressions and may be all you want to deal with. Now the more thoughts...

First off, most of what you've got to deal with isn't what I'd call a Web
problem. It's easy to see it all as Web stuff because that's what you're
dealing with, but it isn't Web-specific in any way. Here's what's needed:

* Resolve a text reference into a data source reference.
* Convert a value into text. (I've posted a big ugly real-world example at
the bottom of this message.)

If you forget about the Web part (that the 4DSCRIPT tag is kind of lame in
your case), it's pretty vanilla 4D programming. Given the number of
installs you've got, avoiding having to recompile to make an update should
be very attractive. The hard-coded case assignment isn't that. In a
situation like yours, the data file is your friend or an external file that
can be dropped in on a per-customer basis. here's what I mean by the data
file being your friend:

[Web_Data]
Web_Name    ProductionTicketForm.Form_Number
Table_Number 5
Field_Number 17

That's it. From there, you can:

* Generate a pointer.
* Determine the field type.
* Convert the data from it's current type to text.

In my own mind, I think of a lot of data as "programmer data." It's not for
the user and it may or may not make sense to store in relationally. Given
we've got an embedded database, I default to using records to store
programmer data. I was startled that so many people on this thread thought
a "case of" as a sensible approach to solving a problem like this. With all
due respect, etc., a case of statement *is* a solution, it's just the worst
possible one that I can think of.

Case of is a great control structure for *structuring control.* For
example, for dispatching methods to subroutines based on a condition, etc.
For implementing lookup tables, it works - but it's got to be ranked as the
worst solution of all of the obvious solutions. As a simple case, imagine
you need to convert day abbreviations into day names:

C_TEXT($0;$name)
C_TEXT($1;$abbr)

$abbr:=$1
$name:=""

Case of
: ($abbr="Mon")
   $name:="Monday"

: ($abbr="Tue")
   $name:="Tuesday"

: ($abbr="Wed")
   $name:="Wednesday"

: ($abbr="Thu")
   $name:="Thursday"

: ($abbr="Fri")
   $name:="Friday"

: ($abbr="Sat")
   $name:="Saturday"

: ($abbr="Sun")
   $name:="Sunday"

Else
$abbr:="Eh?"
end case

$0:=$name

Everyone can see how terrible this code is, right? With that said, I'm sure
I've got code exactly like this in my databases so I'm not pretending to be
smarter than I am. Also note that this is a trivial example and you can get
away with it for simple stuff like this. But what if you're dealing with
the US states and territories? Country codes? Those lists get very long in
a hurry. I'm also deliberately using simple lookups because that's all
WEB_SetJSCode requires. You need to get a field pointer, but it's still
just a dead-simple lookup. Anyway, now imagine that you want to do any of
the following with the day abbreviation/name data:

* Convert names to abbreviations.
* Print out a catalog of names and abbreviations.
* Test all of the valid inputs to this method and some invalid ones.
* Support getting names in another language.

Every single one of these requirements is either pointlessly hard or
effectively impossible with the hard coded cases statement. Why this
approach has become common in the 4D world? Who knows, examples from V2
that just became habits? Because Case statements are so easy to use in 4D
relative to other languages (break is automatic in 4D...often handy.)
Anyway, you're a whole lot better off with hard-coded arrays. Why?

* Convert names to abbreviations: You write a second function that uses the
same base arrays.
* Print out a catalog of names and abbreviations: You iterate over the
arrays.
* Test all of the valid inputs to this method and some invalid ones: You
use one array as input to test from.
* Support getting names in another language: You add another array and a
language parameter.

Arrays get ugly in a hurry because, well:

* You have to be sure to initialize them as there is no safe way to test in
a compiled database if an array is initialized or not.
* People tend to load up heaps of static arrays like this "just in case",
so you burn memory for no reason. (Not normally a huge worry for me but
this is such an easy problem to avoid...)
* You have to edit the source code to make any changes.
* You have to create a custom interface to display or review the data.

Yeah, I'm not a huge fan. I like records...but I already said that. In your
case, records (or an external file) are pretty solid choices because you
can then _update the system without recompiling_. Not so with hard-coded
arrays or a case of. How long would it take to implement something better?
So little that it's a safer bet than having to push out even one extra full
compile of your system. You need three things:

1) A parser for your WEB_SetJSCode's $1 parameter. I'd suspect you already
have some text-to-array parsers sitting around that you can reuse.

2) A lookup table or external lookup/init file. How long this takes depends
on how you want to do it, but it doesn't need to be a big job. The main
issue is probably how you populate the records which depends a lot on your
circumstances and style.

3) A value-to-text routine. You may already have one, I've included a big
ugly one...I'm sure smaller ones are available. Heck, you could write one
in 10 minutes.

Not a huge job and then you don't have to worry about
update-compile-build-release. Just so long as you've got a way of loading
values into the lookup table or document that doesn't require a recompile.
If that's of interest, I've got a lot of thoughts as I've solved that very
problem more than once. I think that you have too, so you probably already
have code that you can adapt to this situation.



Here is an overly-long field-to-text routine, warts and all. This is pulled
straight out of a database so there's junk that won't apply. I could dig
around for a simpler version of value-to-string...but I'm hoping that
someone else can post a nice, tidy compact one for everyone...

If (False)
WebData_ConvertByPointerToText
  //
  // Takes any pointer and returns contents as a string, when possible.
  //
  // You get an empty string if the routine cannot deal with the pointer
  // you supply.

  // Called by
WebData_GetFieldValue
End if

C_TEXT($0;$result_t)
C_POINTER($1;$object_p)
C_TEXT($2;$default_text)
C_TEXT($3;$formatName_text)  // The formatting text is in 'public' style at
this point, such as "Currency"

C_LONGINT($errorCode_l)
C_LONGINT($objectType_l)

$object_p:=$1
$default_text:=$2  // May be empty.
$formatName_text:=""
If (Count parameters>=3)
$formatName_text:=$3
End if

$result_t:=""

  // Test parameters:
$errorCode_l:=0
Case of
: (Pointer_IsAPointer ($object_p)=False)
$errorCode_l:=-16103

: (Nil($object_p))
$errorCode_l:=-16122
End case

If ($errorCode_l=0)
$objectType_l:=Pointer_GetObjectType ($object_p)

Case of
: (Pointer_IsATable ($object_p))
$result_t:=""

: (Pointer_IsA2DArray ($object_p))  // <-- Note: V11 changes how Type reads
2D array references in a good way. See the   `docs.
$result_t:=""

: (Pointer_IsAnArray ($object_p))
$result_t:=""

: (Pointer_IsAField ($object_p)) | (Pointer_IsAVariable ($object_p)) |
(Pointer_IsAnArrayElement ($object_p))

Case of
: ($objectType_l=Is Boolean)
C_TEXT($labelfTrue_text)  // Set defaults.
C_TEXT($labelfFalse_text)
$labelfTrue_text:="True"
$labelfFalse_text:="False"

Case of
: ($formatName_text="")  // Uses default.
: ($formatName_text="TrueFalse")  // Uses default.

: ($formatName_text="YesNo")
$labelfTrue_text:="Yes"
$labelfFalse_text:="No"

: ($formatName_text="Custom")  // Read in arguments which are *not* error
checked.
$labelfTrue_text:=WebScriptGetParameter ("IfTrue")
$labelfFalse_text:=WebScriptGetParameter ("IfFalse")

Else   // Uses default.
End case

$result_t:=Boolean_ConvertToString
($object_p->;$labelfTrue_text;$labelfFalse_text)

: ($objectType_l=Is Date)

C_DATE($dateToConvert_d)
C_BOOLEAN($shortVersionOfName_b)
C_BOOLEAN($longVersionOfName_b)
$dateToConvert_d:=$object_p->
$shortVersionOfName_b:=True
$longVersionOfName_b:=False

If ($formatName_text="")
$formatName_text:="DD/MM/YY"  // Default.
End if
$result_t:=$formatName_text  // The format "name" in this case is a series
of elements that serve as substitutio  `n targets.

If (Position("DayAbbr";$result_t)>=1)  // Wed
$result_t:=Replace string($result_t;"DayAbbr";Date_GetDayName (Day
number($dateToConvert_d);$shortVersionOfName_b))
End if
If (Position("Day";$result_t)>=1)  // Wednesday
$result_t:=Replace string($result_t;"Day";Date_GetDayName (Day
number($dateToConvert_d);$longVersionOfName_b))
End if

If (Position("MonthAbbr";$result_t)>=1)  // Dec
$result_t:=Replace string($result_t;"MonthAbbr";Date_GetMonthName (Month
of($dateToConvert_d);$shortVersionOfName_b))
End if
If (Position("Month";$result_t)>=1)  // December
$result_t:=Replace string($result_t;"Month";Date_GetMonthName (Month
of($dateToConvert_d);$longVersionOfName_b))
End if

If (Position("YYYY";$result_t)>=1)  // 2008
$result_t:=Replace string($result_t;"YYYY";String(Year
of($dateToConvert_d);"0000"))
End if
If (Position("YY";$result_t)>=1)  // 08
C_TEXT($yy_t)
$yy_t:=Substring(String(Year of($dateToConvert_d));3;2)  // Only include
the last two characters of the year, such as '08'
$result_t:=Replace string($result_t;"YY";$yy_t)
End if
If (Position("MM";$result_t)>=1)  // 2008
$result_t:=Replace string($result_t;"MM";String(Month
of($dateToConvert_d);"00"))
End if
If (Position("DD";$result_t)>=1)  // 2008
$result_t:=Replace string($result_t;"DD";String(Day
of($dateToConvert_d);"00"))
End if

: ($objectType_l=Is Time)  // <-- 6 Feb 08 - DPA - There are no time fields
published, so the code below is un  `tested and undocumented in the
designer pages.
Case of
: ($formatName_text="HH:MM")  // 1:02:00 AM
$result_t:=String($object_p->;HH MM)

: ($formatName_text="H:MM AM/PM")  // 1:02:00 AM
$result_t:=String($object_p->;HH MM AM PM)

: ($formatName_text="Hours Minutes Seconds")  // 1 hour 2 minutes 3 seconds
$result_t:=String($object_p->;Hour Min Sec)

: ($formatName_text="Hours Minutes")  // 1 hour 2 minutes
$result_t:=String($object_p->;Hour Min)

Else   // Default of HH:MM:SS, bad format string, or no format string. Use
default.
$result_t:=String($object_p->;HH MM SS)
End case


: ($objectType_l=Is Integer) | ($objectType_l=Is LongInt) |
($objectType_l=Is Real)
C_REAL($numberToConvert_r)
$numberToConvert_r:=$object_p->

C_TEXT($formatArguments_t)
$formatArguments_t:=""

Case of
  // The default 'NoDecimals' is handled in the Else of this case statement.

: ($formatName_text="TwoDecimals")
$formatArguments_t:="###,###,###.00;-###,###,###.00;0.00"

: ($formatName_text="Currency")
$formatArguments_t:="$###,###,###.00;-$###,###,###.00;$0.00"

: ($formatName_text="NoDecimalsPositiveOnly")
$formatArguments_t:="###,###,###;;"

: ($formatName_text="TwoDecimalsPositiveOnly")
$formatArguments_t:="##,###,###.00;;"

: ($formatName_text="CurrencyPositiveOnly")
$formatArguments_t:="$###,###,###.00;;"

: ($formatName_text="CurrencyPositiveOrTBA")
$formatArguments_t:="$###,###,###.00;;TBA"

: ($formatName_text="CurrencyPositiveOrZero")
$formatArguments_t:="$###,###,###.00;;Zero"

: ($formatName_text="CurrencyPositiveOrFree")
$formatArguments_t:="$###,###,###.00;;Free"

: ($formatName_text="Custom")  // No error checking is done.
$formatArguments_t:=""
$formatArguments_t:=$formatArguments_t+WebScriptGetParameter ("IfPositive")
$formatArguments_t:=$formatArguments_t+";"
$formatArguments_t:=$formatArguments_t+WebScriptGetParameter ("IfNegative")
$formatArguments_t:=$formatArguments_t+";"
$formatArguments_t:=$formatArguments_t+WebScriptGetParameter ("IfZero")

Else   // Default, no format string, or a bad format string name. Use
default of NoDecimals.
$formatArguments_t:="###,###,###;-###,###,###,0)"
End case

$result_t:=String($numberToConvert_r;$formatArguments_t)

: ($objectType_l=Is Alpha Field) | ($objectType_l=Is Text) |
($objectType_l=Is String Var)
$result_t:=$object_p->
If (($result_t="") & ($default_text#""))
$result_t:=$default_text
End if

: ($objectType_l=Is BLOB)  // Dump blob contents as text.
$result_t:=BLOB to text($object_p->;32000)

: ($objectType_l=Is Picture)  // Return nothing.
$result_t:=""

: ($objectType_l=Is Pointer)  // Don't resolve pointer to pointer.
$result_t:=""

: ($objectType_l=Is Undefined)
$result_t:=""

: ($objectType_l=Is Subtable)
$result_t:=""

Else   // Impossible case or new version of 4D.

End case   // Close case testing field/variable/array element type


End case   // End case testing general pointer category.

End if   // ($errorCode_l=No error )

$0:=$result_t

  // End of routine.


P.S. I just peeked inside of Date_GetDayName and Date_GetMonthName to see
how I implemented them. Hmmm. I will not be posting the code for those
methods here....
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Add Komoncharoensiri-2
In reply to this post by Randy Jaynes
Hi Randy,

I see that there are many great responses to you question already. I¹d
like to offer another workaround to your problem as well. OK this may
sound a little weird but just bare with it.

1. Keep your 4DSCRIPT call as is

   <!--#4DSCRIPT/WEB_SetJSCode/[ProductionTicket_Form]Form_Number-->


2. In the method WEB_SetJSCode, modify it so that it looks something like
the following:

   C_TEXT($0)
   C_TEXT($1;$param_t)
   $param_t:="<!--#4DTEXT "+Replace string($1;"/";"")+"-->"
   PROCESS 4D TAGS($param_t;$param_t)


Please note that we will wrap the field name into 4DTEXT tag, then the tag
will be processed using PROCESS 4D TAGS command. The value of $param_t
should be the current value in [ProductionTicket_Form]Form_Number.

Hope this helps.

Add


On 1/25/16, 12:16 PM, "4D_Tech on behalf of Randy Jaynes"
<[hidden email] on behalf of [hidden email]> wrote:

>How do you get something like this tor process properly when making a
>call to 4D¹s web server:
>       <!--#4DSCRIPT/WEB_SetJSCode/[ProductionTicket_Form]Form_Number-->
>
>I¹m getting the method WEB_SetJSCode to be called, but the literal text
>"[ProductionTicket_Form]Form_Number² is the value of $1, not the CONTENTS
>of the 4D field [ProductionTicket_Form]Form_Number.
>
>I¹ve already tried
>       <!--#4DSCRIPT/WEB_SetJSCode/<!‹#4DTEXT
>[ProductionTicket_Form]Form_Number‹>‹>
>
>But that doesn¹t work because then my method gets (<!‹#4DTEXT
>[ProductionTicket_Form]Form_Number‹>
>
>It¹s like I need to do double PROCESS HTML TAGS or something in order to
>get this to work.
>
>Short of making a case statement for every possible value I pass into my
>method, I don¹t see how to do this easily.
>
>Thanks,
>Randy
>
>----------------------------------------------------------------------
>Randy Jaynes
>Senior Programmer and Customer Support
>
>http://printpoint.com € (845) 359-0298 € PrintPoint, Inc € 57 Ludlow Lane
>€ Palisades, NY 10964
>
>
>
>
>**********************************************************************
>4D Internet Users Group (4D iNUG)
>FAQ:  http://lists.4d.com/faqnug.html
>Archive:  http://lists.4d.com/archives.html
>Options: http://lists.4d.com/mailman/options/4d_tech
>Unsub:  mailto:[hidden email]
>**********************************************************************





**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Tim Nevels-2
In reply to this post by Randy Jaynes
On Jan 25, 2016, at 7:12 PM, David Adams wrote:

> If (False)
>    WebData_ConvertByPointerToText
>     //
>     // Takes any pointer and returns contents as a string, when possible.
>     //
>     // You get an empty string if the routine cannot deal with the pointer
>     // you supply.
>
>     // Called by
>    WebData_GetFieldValue
> End if
>
> C_TEXT($0;$result_t)
> C_POINTER($1;$object_p)
> C_TEXT($2;$default_text)
> C_TEXT($3;$formatName_text)  // The formatting text is in 'public' style at
> this point, such as "Currency"
>
> C_LONGINT($errorCode_l)
> C_LONGINT($objectType_l)

I was surprised to see code written by David Adams that has variable names with variable type suffixes in the name. Seems like we had a discussion about this some time ago and your position was that using type suffixes were silly and really not a good idea. (I could be wrong on that, so please tell me if I’m mistaken.)

Shall I assume that is code is from a past life of yours and that you have since reformed? :)

For me, I like type suffixes in 4D variable names because it allows me to use a macro to create compiler directives to type local variables after I have finished writing a method.

Do you think variable names with type suffixes are a good thing? Just curious what your current point of view is on this subject.

Tim

********************************************
Tim Nevels
Innovative Solutions
785-749-3444
[hidden email]
********************************************

**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

David Adams-4
> Do you think variable names with type suffixes are a good thing?
> Just curious what your current point of view is on this subject.

I've changed my mind over the years a bunch of times. Now I've finally
gotten it right ;-) The truth is, my opinon is no better than anyone else's
that's been programming for as long as we all have here. Whatever makes you
productive is good. Yeah, I'm pretty much for whatever helps a developer
and whoever they work with. Personally, I detest type prefixes as they put
the least interesting information in the most important position. (Well, in
a left-to-right script system like any language I'll ever know.) Plus,
they're just ugly.

Oh, what Code Complete says ;-)

As far as suffixes go, I've used them for ages and use them still. They
don't get in my way and make it pretty easy to see what sort of a thing a
variable represents. I've never used them for fields. Not really rational
or consistent, but there you go.

I've been moving away from worrying about 4D's simple types as much and use
a lot of prefixes to indicate a role - or where type is easily understood
in 4D. Such as "_name", like:

Area_name

There's no need to say that this is an alpha because what else would a name
be? It's implicit in "name" that you've got an alpha. 4D has a few built-in
types like that:

winref   window reference - always a longint
docref  document reference - always a time
lref or list - always a longint in 4D
etc.

I've never gotten that good at naming pointers based on what they point to.
I see and approve of the logic, but with only 31 characters...I just never
got into it. Where possible and necessary, I'll double-check types at the
top of a method and throw a "wrong type of pointer" error. That works well.

When I pick up someone else's code, I'm grateful when I can figure out
what's going on from reading the name of a thing. For comparison:

Customer_Number
Customer_ID_text

Same thing...in this example. I like the look of the top one but it sure is
misleading. Hello vRecNum, I'm looking at you!

Anyway, my convention these days is:

Module_Descriptor_type

But sometimes type isn't there because it's just too obvious from the
descriptor to bother with. I also don't share code with people as much as I
used to so if I break the rules, I generally only hurt myself.

At the end of the day, it depends on how you like to work and who you're
collaborating with. Now that I'm spending more time writing JavaScript,
I've decided to write JS like the other kids. Do I naturally like dense,
weird functions like this fragment:

bar.append ("rect")
.attr ("class", "region_a")
.attr ("x", function (d) {
return regionBStartX - (d.regionACount * speciesBoxWidth);
})
.attr ("y", rectYOffset)
.attr ("width", function (d) {return d.regionACount * speciesBoxWidth;})
.attr ("height", rectHeight)
.on ("mouseover", function (d) {
div.transition ()
.duration (200)
.style ("opacity", .9);
div.html (getFamilyRectTooltipText (regionATitle, d.englishName,
d.regionACount, false))
.style ("left", (d3.event.pageX) + "px")
.style ("top", (d3.event.pageY - 28) + "px");
})
.on ("mouseout", function (d) {
div.transition ()
.duration (500)
.style ("opacity", 0);
});

No, I don't love that per se:

* Anonymous functions: Who thought that was a good idea? Let alone enough
of a good idea to make it into a religion?

* What do parameters make you cry? What's wrong with a few parameters
people?

* Closures. Solving a problem I never knew that I had.

* Function chains to 12 calls. Eh?

...but that's how D3 JavaScript code works and as soon as I try and do it
another way, it looks weird and would be tough to share. So, it looks
*nothing* like how I would code 4D and even violates a huge number of my
rules. I never use a type prefix. Given how loose and weird type is in
JavaScript (strict mode or not), it's often pointless ot get hung up on the
point. Anyway, not meaning to talk about D3....just saying that conventions
are very localized.
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

KirkBrooks
David,
I love it when you go off on stuff like this. It just enables me...

On Mon, Jan 25, 2016 at 6:37 PM, David Adams <[hidden email]> wrote:

> Do I naturally like dense,
> ​ ​
> weird functions like this fragment:
>
> bar.append ("rect")
> .attr ("class", "region_a")
> .attr ("x", function (d) {
> return regionBStartX - (d.regionACount * speciesBoxWidth);
> })
> .attr ("y", rectYOffset)
> .attr ("width", function (d) {return d.regionACount * speciesBoxWidth;})
> .attr ("height", rectHeight)
> .on ("mouseover", function (d) {
> div.transition ()
> .duration (200)
> .style ("opacity", .9);
> div.html (getFamilyRectTooltipText (regionATitle, d.englishName,
> d.regionACount, false))
> .style ("left", (d3.event.pageX) + "px")
> .style ("top", (d3.event.pageY - 28) + "px");
> })
> .on ("mouseout", function (d) {
> div.transition ()
> .duration (500)
> .style ("opacity", 0);
> });
>
> No, I don't love that per se:
>
> * Anonymous functions: Who thought that was a good idea? Let alone enough
> of a good idea to make it into a religion?
>

> * What do parameters make you cry? What's wrong with a few parameters
> people?
>
> * Closures. Solving a problem I never knew that I had.
>
> * Function chains to 12 calls. Eh?
>
​The flavor of JS you run into in D3 is

really powerful​
​once
 you completely know what you are doing.
​Till then it's ​
sort of like trying to learn to drive in a Ferrari at Le Mans. All that
call chaining. Really befuddling. Plus - to a 4D person the idea you can
put a function into a variable and then call the variable to run the
function is really hard to grasp. With respect to D3 the whole architecture
is to build one big function
​that is the graph ​
then call it with the data as a param and *poof* you have a graph. It
doesn't get much more dynamic. I wish I could do that in 4D.


> ...but that's how D3 JavaScript code works and as soon as I try and do it
> another way, it looks weird and would be tough to share. So, it looks
> *nothing* like how I would code 4D and even violates a huge number of my
> rules.

​You know you can break up those long chains. Either into separate vars or
keep building the long one just in steps. Doing that helps me figure out
what's going on. As soon as I have something worth sharing I can chain them
all together again.

​I'm actually writing better code now that I'm using more javascript. For
one reason there are things that javascript does better than 4D (almost
anything web oriented​) so I'm more comfortable handing those tasks over to
a web area. 4D rules at managing data. Now that JSON is a core object
melding the two is quite easy. A 'feature' of 4D from way back is how you
can put so much business logic on forms. Remember when they were competing
with Filemaker to be easy to use and powerful - but few of us actually knew
how to program? 4D made it possible to actually get something done by
putting all the code into object scripts. Sadly it also allowed us to
reduce the MVC approach to tables and forms. I think those habits are still
engrained to some extent. So my point is that seeing how I can take a chunk
of data in a JSON and display it using JS, html and CSS in either a 4D web
area, a web browser or a 4D form caused me to separate the data operations
from the display operations. And make the data operations fairly agnostic
about how they are called. This in turn compels a more focused approach to
what a particular task is trying to accomplish. And these are all hallmarks
of better programming, I think.

--
Kirk Brooks
San Francisco, CA
=======================
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Keisuke Miyako
In reply to this post by Add Komoncharoensiri-2
I have never found a compelling reason to use <!--4dscript _--> at all.

why not

<!--4dtext WEB_SetJSCode([ProductionTicket_Form]Form_Number)-->

or

<!--4dhtml WEB_SetJSCode([ProductionTicket_Form]Form_Number)-->

(
        or, to future-proof, append table numbers and field numbers:
        <!--4dhtml WEB_SetJSCode([ProductionTicket_Form:1]Form_Number:2)-->
)

if the method doesn't have a return value,

just add

$0:=""

at the end.

> 2016/01/26 8:38、Add Komoncharoensiri <[hidden email]> のメール:
>   <!--#4DSCRIPT/WEB_SetJSCode/[ProductionTicket_Form]Form_Number-->





**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: 4DSCRIPT usage

Julio Carneiro
man, you beat me by the time zone difference :)

I’ve been doing 4D web stuff for a very long time and have never ever found the need to use 4DScript…
I only use 4DTEXT (old 4DVAR) or 4DHTML (old 4DHTMLVAR), which work and behave line EXECUTE FORMULA from a 4D tag.

I’ve also created a pretty simple method (code below) to run any 4D statement from a 4D tag, so I can do something like:

<!--4dtext REPOExecutStatement(“Query([ProductionTicket_Form]; [ProductionTicket_Form]Form_Number=123")—>
<!--4dtext REPOExecutStatement(“Relate One([ProductionTicket_Form];[Client])")—>
<!--4dtext [Client]CompanyName)—>

From my experience, there is nothing you can do with 4DSCRIPT that can’t be done with other tags.

hth
julio

> On Jan 26, 2016, at 7:54 AM, Keisuke Miyako <[hidden email]> wrote:
>
> I have never found a compelling reason to use <!--4dscript _--> at all.
>
> why not
>
> <!--4dtext WEB_SetJSCode([ProductionTicket_Form]Form_Number)-->
>
> or
>
> <!--4dhtml WEB_SetJSCode([ProductionTicket_Form]Form_Number)-->
>
> (
>       or, to future-proof, append table numbers and field numbers:
>       <!--4dhtml WEB_SetJSCode([ProductionTicket_Form:1]Form_Number:2)-->
> )
>
> if the method doesn't have a return value,
>
> just add
>
> $0:=""
>
> at the end.
>
>> 2016/01/26 8:38、Add Komoncharoensiri <[hidden email]> のメール:
>> <!--#4DSCRIPT/WEB_SetJSCode/[ProductionTicket_Form]Form_Number-->
>
>

--
Julio Carneiro


 // ----------------------------------------------------
 // REPOExecuteStatement : Called from 4DTEXT or 4DHTML html tag to execute a 4D command
 //
 // Parameters:
 //     $1: 4D command line to execute
 //
 // Return:
 //     $0: always return blank
 //
 // Assumptions:
 //
 //
 // ----------------------------------------------------
 // User name (OS): julio
 // Date and time: 12/02/08, 14:12:05
 // ----------------------------------------------------
 //
C_TEXT($0)
C_TEXT($1;$statement)


 //--- locals

 //--- code
EXECUTE FORMULA($1)

$0:=""


--
Julio Carneiro
[hidden email]



**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Reply | Threaded
Open this post in threaded view
|

Re: Re: 4DSCRIPT usage

DEddy
In reply to this post by Randy Jaynes
David -

On Jan 26, 2016, at 1:44 AM, David Adams <[hidden email] <mailto:[hidden email]>> wrote:

> Anyway, my convention these days is:
>
> Module_Descriptor_type

Lordy… down the rabbit hole… (yet) again.

[Please excuse the rant… blame David Adams… he provoked me.   :-)  ]

I believe the earliest mention of the “data element naming standards” I’ve found was a DoD document dated December 7, 1953.


From my mainframe days (late 1970s) this is an IBM naming convention / style called “The OF Language.”  (Good luck Googling it.)

Mostly known as PRIME-MODIFIER-CLASS.  (Or RIGHT-MIDDLE-LEFT.  LEFT-MIDDLE-RIGHT is ok too as long as consistent.)

Largely used for data elements/fields, but certainly not confined to such.


PRIME is the major thing of interest.  Customer, Invoice, Product… a significant BUSINESS thingy.

MODIFIER - some sort of (potentially optional) modifier(s) to narrow & uniquify the business context.

CLASS - what kind of data it is: CODE, NUMBER, AMOUNT, TEXT, CONSTANT, CONTROL, COUNT, DATE, FLAG, NAME, PERCENT and TITLE.  As David points out… when you have this very simple clue, you can instantly grok situationally incorrect operations.  

Acceptable COBOL example: CUST-ACCT-NO reading, customer account number.  In 4D perhaps [Customer]AccountNr.

CLASS is most often (as you say) “programmer data.”  Necessary for the programmer but invisible to customer.


There’s a huge difference between the capabilities & therefore coding styles of 1970s COBOL & today’s 4D.  In COBOL pretty much everything is global.  In 4D & newer environments the concept of “local” is essential.

This distinction has just dawned over Marble Head… the convention of “bCancel,” “bNext,” for buttons on a Form is good since it sorts like things—buttons—together in the Property List.  Bad idea to have buttons scattered alphabetically throughout the Property List.   Concept of local buttons on a Form—at least in my programming days—was non-existent.


Whereas last week I was still uncomfortable—unseeing?—with the “b” prefix, now I understand.  Makes total sense.


There are at least two additional facets to “good names.”

Good prefixes & modifiers in consistent order to indicate what this thingy (Method) is doing.  Perhaps “INV” for invoice, “PRT” for print.  Eventually every good programmer gravitates to such consistency.  You want to GLANCE at the name & instantly get a good understanding what the thingy is for.  Doesn’t come easily.


Then in [Tables] and fields… consistent—that’s the challenge for humans who are terribly inconsistent—BUSINESS terminology.


Another major difference from the 4D world—I believe—is 4Ders “typically” (duck for exceptions & incoming) work alone on an application.


In my experience in large companies with IBM mainframes, a “typical” application would be written in a minimum of 6 different languages, by different teams of programmers & the languages having different technical & business naming practices.  To the computer—any computer—it’s all about base & displacement—where the thingy is in the byte stream… computer could care less about good names.

For humans to read software for understanding, good names, while technically not essential, are really, really, really helpful.  Bad names make life hell.


Apologies… back to lurking.

____________________________
David Eddy
Babson Park, MA


**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[hidden email]
**********************************************************************
Running 4D v17, OS X v10.13.6, Foundation v5.7.1
12