Skip to content

Instantly share code, notes, and snippets.

@steveAllen0112
Last active May 26, 2018 05:37
Show Gist options
  • Save steveAllen0112/181975d06c2977d343b70a233da76a88 to your computer and use it in GitHub Desktop.
Save steveAllen0112/181975d06c2977d343b70a233da76a88 to your computer and use it in GitHub Desktop.
JSONGetLayoutData ( fields ; config )
// JSONGetLayoutData ( fields ; config )
/****
* PURPOSE
* To grab all the fields off the current layout and process their values into a JSON object.
*
* PARAMETERS
* OPTIONAL : fields : List : Can be used instead of allowing the fields to be grabbed from the layout.
* OPTIONAL : config : JSONObject : Can be used to override the default handling configuration for the various use cases.
* ignore : JSONObject
* containers : JSONBoolean
* True (default) = pass over containers without handling them or including them.
* False = include Containers as Base64Encoded text.
* related : JSONBoolean
* True = pass over Related fields without handling them or including them.
* False (default) = include FIRST Related field value according to the handling instructions.
* repetitions : JSONBoolean
* True = pass over repeated fields (i.e. rep > 1) without handling them or including them.
* False (default) = include repeated fields according to the handling instructions.
* summaries : JSONBoolean
* True (default) = pass over Summary fields without handling them or including them.
* False = include Summary field values using GetSummary() .
* handle : JSONObject
* related : JSONString
* array (default) = include related data as elements in an array assigned to the related table name as the parent property.
* object = include related data as an object assigned to the related table name as the parent property,
* with each related field (no table name) as a property under the related table object.
* repetitions : JSONBoolean
* array (default) = include each repeated value as a value in an array assigned to the field name as the parent property.
* list = include all values from the repeated field as a return-delimited list.
*
* RETURNS
* OnSuccess : JSONObject : A JSON Object containing the data from the fields of the Current Record on the Current Layout, ignored or handled as specified.
* OnError : JSONString : Whatever String is returned from the various Evaluate() runs.
*
* AUTHOR
* Steve Allen <[email protected]>
* GitHub: steveAllen0112
*
* VERSION CONTROL
* Original: https://gist.github.com/steveAllen0112/181975d06c2977d343b70a233da76a88
*
* LICENSE
* MIT: https://choosealicense.com/licenses/mit/
*
***/
Let ([b=""
/* PARAMS (uncomment to test in Data Viewer) */
//; fields = ""
//; config = JSONSetElement ( ""
// /* ignore : True | False */
// ;[ "ignore.containers" ; True ; JSONBoolean ]
// ;[ "ignore.related" ; False ; JSONBoolean ]
// ;[ "ignore.summaries" ; False ; JSONBoolean ]
//
// /* handle : array | list */
// ;[ "handle.related" ; "array" ; JSONString ]
// ;[ "handle.repetitions" ; "array" ; JSONString ]
// )
/* CONFIG/SET UP DEFAULTS */
; alpha = "abcdefghijklmnopqrstuvwxyz"
; $json.types = JSONSetElement ( ""
;[ "text" ; "JSONString" ; JSONString ]
;[ "number" ; "JSONNumber" ; JSONString ]
;[ "date" ; "JSONString" ; JSONString ]
;[ "time" ; "JSONString" ; JSONString ]
;[ "timestamp" ; "JSONString" ; JSONString ]
;[ "container" ; "JSONString" ; JSONString ]
)
; $json.setLine = ";[ {{key}} ; {{value}} ; {{type}} ]"
; $file.name = Get ( FileName )
; $layout.name = Get ( LayoutName )
; $table.name = Get ( LayoutTableName )
; fields = Case ( IsEmpty ( fields ) ; FieldNames ( $file.name ; $layout.name ) ; fields )
/* ignore : True | False */
; $config.ignore.containers = True AND (NOT Exact ( Quote ( JSONGetElement ( config ; "ignore.containers" ) ) ; Quote ( 0 ) ))
; $config.ignore.repetitions = False OR JSONGetElement ( config ; "ignore.repetitions" )
; $config.ignore.summaries = True AND (NOT Exact ( Quote ( JSONGetElement ( config ; "ignore.summaries" ) ) ; Quote ( 0 ) ))
; $config.ignore.related = False or JSONGetElement ( config ; "ignore.related" )
/* handle : array | list */
; $config.handle.related = Filter ( Lower ( JSONGetElement ( config ; "handle.related" ) ) ; alpha )
; $config.handle.related = Case (0;0
; PatternCount ( "array|object" ; $config.handle.related )
; $config.handle.related
; "array"
)
; $config.handle.repetitions = Filter ( Lower ( JSONGetElement ( config ; "handle.repetitions" ) ) ; alpha )
; $config.handle.repetitions = Case (0;0
; PatternCount ( "array|list" ; $config.handle.repetitions )
; $config.handle.repetitions
; "array"
)
; $fields.added = ""
/* TEMPLATES */
/* *** TEMPLATE: HANDLE (BASE) *** */
; $template.handle.base = Substitute (
"
¶ Let([b=''
¶ ; json.key.table = ''
¶ ; json.key.field = $field.name
¶ {{template.handle.related}}
¶ {{template.handle.repetitions}}
¶ {{template.handle.summary}}
¶ {{template.handle.standard}}
¶ ; output = Substitute ( $json.setLine
¶ ;[ '{{key}}' ; Quote ( json.key.table & json.key.field ) ]
¶ ;[ '{{value}}' ; Case ( $json.type = 'JSONNumber' AND IsEmpty ( field.value ) ; Quote ( 'null' ) ; field.value ) ]
¶ ;[ '{{type}}' ; Case ( $json.type = 'JSONNumber' AND IsEmpty ( field.value ) ; Quote ( 'JSONNull' ) ; $json.type ) ]
¶ )
¶ ];
¶ output
¶ )
"
; "'" ; "\"" ) //end template.standard substitute
/* *** TEMPLATE: HANDLE STANDARD *** */
; $template.handle.standard = Substitute (
"
¶ ; field.value = GetField ( $field.name.fullyQualified )
¶ ; field.value = Case (0;0
¶ ; $field.dataType = 'text'
¶ ; Quote ( field.value )
¶ ; PatternCount ( 'number|date|time|timestamp' ; $field.dataType )
¶ ; GetAsNumber ( field.value )
¶ ; $field.dataType = 'container'
¶ ; Quote ( Base64Encode ( field.value ) )
¶ )
"
; "'" ; "\"" ) //end template.handle.standard substitute
/* *** /TEMPLATE: HANDLE STANDARD *** */
/* *** TEMPLATE: HANDLE SUMMARY *** */
; $template.handle.summary = Substitute (
"
¶ ; field.value = GetSummary( GetField ( $field.name.fullyQualified ) ; GetField ( $field.name.fullyQualified ) )
¶ ; field.value = Case (0;0
¶ ; $field.dataType = 'text'
¶ ; Quote ( field.value )
¶ ; PatternCount ( 'number|date|time|timestamp' ; $field.dataType )
¶ ; GetAsNumber ( field.value )
¶ ; ''
¶ )
"
; "'" ; "\"" ) //end template.handle.standard substitute
/* *** /TEMPLATE: HANDLE STANDARD *** */
/* *** TEMPLATE: HANDLE RELATED *** */
; $template.handle.related = Substitute (
"
¶ ; json.key.table = $field.table & Case ( $config.handle.related = 'array' ; '[0].' ; '.' )
¶ ; json.key.field = $field.name
¶ ; field.value = GetField ( $field.name.fullyQualified )
¶ ; field.value = Case (0;0
¶ ; $field.dataType = 'text'
¶ ; Quote ( field.value )
¶ ; PatternCount ( 'number|date|time|timestamp' ; $field.dataType )
¶ ; GetAsNumber ( field.value )
¶ ; Quote ( field.value )
¶ )
"
; "'" ; "\"" ) //end template.handle.related substitute
/* *** /TEMPLATE: HANDLE RELATED *** */
/* *** TEMPLATE: HANDLE REPEATING (BASE) *** */
; $template.handle.repetitions.base = Substitute (
"
¶ ; indices = JSONListKeys ( JSONSetElement ( '[]' ; $field.maxReps - 1 ; 'null' ; JSONRaw ) ; '' )
¶ ; first = '; GetRepetition ( {{field.name}} ; '
¶ ; last = ' + 1 )'
¶ ; middle = last & '\¶' & first
¶ ; str = Substitute ( 'List (\'\'\¶' & first & Substitute ( indices ; '\¶' ; middle ) & last & '\¶)' ; [ \"{{field.name}}\" ; $field.name ] )
¶ {{template.handle.repetitions.formatter}}
"
;[ "'" ; "\"" ]
) //end template.handle.repetitions base substitute
/* *** /TEMPLATE: HANDLE REPEATING (BASE) *** */
/* *** TEMPLATE: HANDLE REPEATING -- ARRAY *** */
; $template.handle.repetitions.formatter.array = Substitute (
"
¶ ; field.value = Quote ( Substitute ( '[\'{{values}}\']'
¶ ;[ '{{values}}' ; Evaluate ( str ) ]
¶ ;[ '?' ; 'null' ]
¶ ;[ '\¶' ; '\',\'' ]
¶ ) )
¶ ; $json.type = 'JSONArray'
"
; "'" ; "\"" ) //end template.handle.repetitions.formatter.array substitute
/* *** TEMPLATE: HANDLE REPEATING -- LIST *** */
; $template.handle.repetitions.formatter.list = Substitute (
"
¶ ; field.value = Quote ( Substitute ( '{{values}}'
¶ ;[ '{{values}}' ; Evaluate ( str ) ]
¶ ;[ '?' ; 'null' ]
¶ ) )
¶ ; $json.type = 'JSONString'
"
; "'" ; "\"" ) //end template.handle.repetitions.formatter.list substitute
/* *** /TEMPLATE: HANDLE REPEATING -- ARRAY *** */
/* *** TEMPLATE: BASE *** */
; $template.base =
"
¶ Case ( ValueCount ( FilterValues ( $fields.added ; '{{field.name}}' ) ) ; '' ; Let([b=''
¶ ; $field.name = '{{field.name}}'
¶ ; $fields.added = List ( $fields.added ; $field.name )
¶ ; field.isFullyQualified = PatternCount ( $field.name ; '::' ) > 0
¶ ; $field.name = Case ( field.isFullyQualified ; Substitute ( $field.name ; '::' ; '\¶' ) ; $field.name )
¶ ; $field.table = Case ( field.isFullyQualified ; GetValue ( $field.name ; 1 ) ; $table.name )
¶ ; $field.name = Case ( field.isFullyQualified ; GetValue ( $field.name ; 2 ) ; $field.name )
¶ ; $field.name.fullyQualified = $field.table & '::' & $field.name
¶ ; field.isRelated = field.isFullyQualified AND $field.table <> $table.name
¶ ; field.info = FieldType ( $file.name ; $field.name.fullyQualified )
¶ ; field.info = Substitute ( Lower ( field.info ) ; ' ' ; '\¶' )
¶ ; field.storage = GetValue ( field.info ; 1 )
¶ ; $field.dataType = GetValue ( field.info ; 2 )
¶ ; field.indexing = GetValue ( field.info ; 3 )
¶ ; $field.maxReps = GetValue ( field.info ; 4 )
¶ ; field.isRepeating = $field.maxReps > 1
¶ ; field.isSummary = PatternCount ( 'summary' ; field.storage )
¶ ; field.isContainer = $field.dataType = 'container'
¶ ; field.isGlobal = field.storage = 'global'
¶ ; use.related = field.isRelated
¶ ; use.summary = field.isSummary
¶ ; use.repeating = field.isRepeating
¶ ; use.standard = (not field.isSummary)
¶ AND (not field.isRepeating)
¶ AND (not field.isRelated)
¶ ; $json.type = JSONGetElement ( $json.types ; $field.dataType )
¶ ; handler.related = Case ( use.related ; $template.handle.related ; '' )
¶ ; handler.standard = Case ( use.standard ; $template.handle.standard ; '' )
¶ ; handler.summary = Case ( use.summary ; $template.handle.summary ; '' )
¶ ; handler.repetition = Case ( use.repeating ; Substitute ( $template.handle.repetitions.base
¶ ;[ '{{template.handle.repetitions.formatter}}' ; $template.handle.repetitions.formatter." & $config.handle.repetitions & " ]
¶ ) ; '' )
¶ ; handler.base = Substitute ( $template.handle.base
¶ ;[ '{{template.handle.related}}' ; handler.related ]
¶ ;[ '{{template.handle.repetitions}}' ; handler.repetition ]
¶ ;[ '{{template.handle.summary}}' ; handler.summary ]
¶ ;[ '{{template.handle.standard}}' ; handler.standard ]
¶ )
¶ ; baseCalcStr = Case (0;0
¶ ; field.isGlobal
¶ OR (field.isContainer AND $config.ignore.containers)
¶ OR (field.isSummary AND $config.ignore.summaries)
¶ OR (field.isRelated AND $config.ignore.related)
¶ OR (field.isRepeating AND $config.ignore.repetitions)
¶ ; ''
¶ ; handler.base
¶ )
¶ ; result = Case ( IsEmpty ( baseCalcStr ) ; '' ; Evaluate ( baseCalcStr ) )
¶ ];
¶ result
¶ )
¶ )
"
/* *** /TEMPLATE: BASE *** */
/* BUILD CALC STRINGS */
; first = "; Evaluate ( Substitute ( $template.base ; [ \"'\" ; \"\\\"\" ] ; [ \"{{field.name}}\" ; GetValue ( Substitute ( \""
; last = "\" ; \"[\" ; \"\¶\" ) ; 1 ) ] ) )"
; middle = List ( last ; first )
; allFieldsStr = List (""
; "List (\"\""
; first &
Substitute ( fields ; "¶" ; middle )
& last
; ")"
)
/* EVALUATE & ERROR TRAP */
; allFieldsJSON = Evaluate ( allFieldsStr )
; finalJSONStr = "JSONSetElement ( \"\"¶" & allFieldsJSON & "¶)"
; output = Evaluate ( finalJSONStr )
; errorCheck = Case ( output = "?" ; "ERROR" ; JSONFormatElements ( output ) )
];
/* RETURN */
errorCheck
)
/* CLEAN UP VARIABLES */
& Let ([b=""
; vars = "
json.types
json.setLine
file.name
layout.name
table.name
config.ignore.containers
config.ignore.repetitions
config.ignore.summaries
config.ignore.related
config.handle.related
config.handle.repetitions
fields.added
template.handle.base
template.handle.standard
template.handle.summary
template.handle.repetitions.base
template.handle.repetitions.formatter.array
template.handle.repetitions.formatter.list
json.type
template.base
field.name
field.name.fullyQualified
field.dataType
field.maxReps
"
];
Evaluate ( "Let ([b" & Substitute ( Left ( vars ; Length ( vars ) )
;[ " " ; "¶=\"\";$"]
) & "a" & Middle ( Random ; 3 ; 7 ) & "=\"\"];\"\")" )
)
@steveAllen0112
Copy link
Author

steveAllen0112 commented May 24, 2018

Lessons learned when composing this:

  • In bare calculations, you can nest Let statements, and the calculation variables set in the parent functions will be present in the children. HOWEVER, apparently you cannot expect this when the function is composed from strings first -- at least not the way I'm doing it here, with template strings. Script variables (i.e. $variable) are the way to go. It's a scoping issue, of all things. (I thought FM was pretty much immune to that, but I guess not.)

  • There is a certain necessity to doing things verbosely and very separately. In addition to it being beautiful. :)

  • Apparently FileMaker thinks it's funny to include a field twice if it's on the layout with different repetition starts, once for each start, with the noted repetition for the entry being the starting repetition of that instance. UNLESS, of course, the starting rep is 1, in which case it looks like a regular field, even if there are multiple reps showing for it.

@steveAllen0112
Copy link
Author

steveAllen0112 commented May 24, 2018

//TODO:

  • Implement Summary field handling
  • Implement Related Set grabbing (currently only grabs first one).
  • Implement handling repeating fields as return-delimited lists.
  • Implement edge case handling where no repetition set on the layout starts at 1 for the field.
  • Implement performance enhancement: only handle each field once. (keep track & ignore if already handled)
  • Implement performance measurements.
  • Build another version with separate functions for sub-tasks (not templates, but functions).
    • Do speed comparison
  • Do a version where the sub-functions are included in the parent function and identified by a Case statement and selected by parameter. (Because I can! lol )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment