Last active
May 26, 2018 05:37
-
-
Save steveAllen0112/181975d06c2977d343b70a233da76a88 to your computer and use it in GitHub Desktop.
JSONGetLayoutData ( fields ; config )
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 ) & "=\"\"];\"\")" ) | |
) |
//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
Lessons learned when composing this:
In bare calculations, you can nest
Let
statements, and thecalculation
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.