Custom functions
Last updated
Last updated
While Clause9 provides an array of powerful features to build documents, it can sometimes be useful to resort to a full-fledged programming language.
For such situations, Clause9 allows you to develop functions and conditions in (a subset of) the , and its close sibling .
Clojure is one of the most powerful programming languages in the world. Although it is much less popular than the mainstream programming languages — Java, C#, C, Python, etc. — and its LISP-based, functional nature will be quite unfamiliar to many developers, it is actually not so difficult to get started with in the context of Clause9.
For a quick introduction, we recommend to read the .
For a more in-depth introduction, we recommend to read Chapters 3, 4 and 5 of the excellent (and free) book .
Clause9 exposes only a subset of the Clojure / ClojureScript, through the . What you write, will run in either a browser environment (for the graphical user interface), or in a server environment (when exporting documents, e.g. to .DOCX). For the sake of conciseness, we will however only refer to Clojure in the remainder of this document.
Most of the core functions of Clojure are supported. In addition, many other Clause9-specific functions are available to you (see below), to make use of the advanced features of Clause9.
The following Clojure features are either irrelevant, or simply not available to you, when developing in Clojure from within Clause9. When reading about Clojure, you can therefore completely skip any discussions about them:
namespaces — within Clause9, your functions are hosted in more confined containers that are given a specific name
the so-called “REPL” — instead you can get some interactive output through your browser’s development tools
multi-threading tools, such as “futures”, “promises”, threads, pmaps, etc.
calls to the host environment (the JDK at server side, the DOM/JS environment at browser-side)
any asynchronous function calls, with the exception of some HTTP calls in the Q&A mode at browser-side
most functions that cause side-effects
If your user profile allows you to do so, you can add one or more custom functions to any clause. A custom function must be given a unique name within the file, and can then be called from the content title, content body or enabled-condition, using either the special function @clj
or @cljr
.
The only difference between those @functions is that the @cljr
will always re-execute upon any recalculation of the underlying Document/Binder, which can be useful if the output of the custom function does not exclusively depend on the other arguments passed to @clj
. (Clause9 will automatically recalculate when one of the arguments passed to @clj
would change, but since the Clojure-functions act as a kind of “black box” towards the recalculation engine, it cannot know in which other situations it should recalculate — hence the need for @cljr
).
The optional first argument should be a hashtag that refers to a file. When left out, the invoked custom function will be presumed to be present in the same clause.
The second argument should be the name of the custom function.
The optional other arguments act as parameters to the custom function.
For example, @clj(#xxx, "alpha", 5, 1 day)
would call custom function “alpha” within the file referenced by hashtag #xxx, passing as arguments the number 5 and the duration 1 day to that custom function. Conversely, @clj("beta", 6)
would call custom function “beta” in the same clause file, passing argument 6 to that custom function.
Within the custom function (i.e., within the Clojure environment), arguments passed to it can be accessed through the vars $1
, $2
, $3
, etc. If several parameters are used, you probably want to define another var for the sake of clarity — e.g., (def contract-value $1)
— and then use Clojure-var contract-value instead of the cryptic $1
.
Custom functions can be used in several places in the Q&A environment.
Any (sub)condition can refer to a custom function in Clojure. You simply choose “custom function” in the condition type, and insert some Clojure code. This code eventually needs to return a “truthy” value in the traditional Clojure sense (i.e., anything besides false
or nil
will result in true).
While Clause9 allows you to finetune the properties of a card(e.g., its title, color or indentation), a question (e.g., its title or help text) or predefined answer (e.g., its label or value), it is not possible to change these properties dynamically. Custom functions can provide relief, however.
The customisations, if set, are calculated right before the card/question/predefined-answer is being shown. The Clojure code gets passed the current record (card, question or predefined answer), can optionally modify that record, and return it.
As is customary in Clojure, Clause9 does not hide the fact that there are several other fields present in the record besides the fields mentioned below. Please refrain from changing these properties, as they are internal implementation details that are crucial for a correctly functioning Q&A.
You customise a card by showing the card’s options and clicking on the add card customization button. The Clojure code you insert, should then return a modified version of the predefined card
var that is made available to the Clojure runtime.
The following card properties can be changed:
:title
, an i18-map
:separator?
to show a dividing line above a card
:color
to determine the color of the card — :green
, :red
, :orange
, :grey
, :black
, :brown
, :purple
, :magenta
, :light-blue
, or :dark-blue
:indent-left
, which can be set to 1, 2 or 3 to indent the card
Similarly, you can customise a question by showing the question’s options and clicking on the add question customization button. The Clojure code you insert should then return a modified version of the predefined question
var that is made available to the Clojure runtime.
The following question properties can be changed:
:title
and :help
are i18-maps
(number questions only) the :min-value
and :max-value
integer properties
(text questions only) the :textarea?
boolean property, which corresponds to the show a large answer box setting of a question
:storage-only?
hides a question, even if it is active (i.e., its own condition and the condition of its card, if any, is met)
:disallow-free-answering?
can be used to disable free answers, allowing only predefined answers
:on-apply-service-instance
is a function that takes the following arguments: the question from which a service-integration (e.g., spreadbase) is being launched; the row-index (zero-based, nil
if the question is not a table-based answer), the col-index (also zero-based and nil
if the question is not a table-based answer) and a map with data that is selected by the end-user.
If this custom function returns a truthy value, the normal distribution of the selected value will follow its course (i.e., copying the relevant value to those questions that have an appropriate receive tag). If instead a non-truthy value is returned, the normal distribution is skipped.
In addition, the following properties can be set for table-based questions:
:force-direction
can be set to :horizontal
or :vertical
to force the table to be shown in a horizontal or vertical manner. Please note that when you set :column-pred-fn
or :column-caption-fn
, the direction will be automatically forced to :vertical
.
:column-pred-fn
is a function that receives as arguments a question, row-index (integer), column-index (integer) and answers-map (map from row-index to (map of col-index to value)). It should return true/false to indicate whether the column should be shown.
:column-caption-fn
is a function that receives as arguments a question, row-index (integer), column-index (integer), answers-map (map from row-index to (map of col-index to value)), the default caption, and the language (keyword) in which the questions are currently displayed. It should return a string.
:on-add-row-fn
is triggered when the user clicks the green “+” button to add a new row. It receives as arguments the current question, the new number of rows (i.e., the old number of rows + 1) and the answers-map. It is expected to return a (probably updated) version of the answers map in which, presumable, at least one new row is inserted.
You can customise a predefined answer by clicking on the customisation icon at the right side. The Clojure code you insert, should then return a modified version of the predefined predef
var that is made available to the Clojure runtime.
The following properties can be set:
the :value
field, for which the datatype depends on the type of question (boolean, integer, FloatValue, CurrencyValue, string, i18-map, Date or Duration)
the :title
, which is an i18-map. Note that if no :title
is available, Clause9 will use a string-representation of the value as the title (label) to be presented towards the end-user.
Instead of customising an already-defined predefined answer, you can also dynamically change the list of predefined answers that should be shown. This is done at the level of the question, by setting the :predef-values|labels-fn
field of the question
var that is passed to the Clojure runtime.
The arguments it receives are the Question, row-index (integer), column-index (integer), answers-map (map from row-index to (map of col-index to value)), default predefined values and default predefined labels.
It should return a tuple of [predefined-values, predefined-labels]
.
It is possible to validate “free text” answers of by setting the :validate-fn
field of a question. When set, this function receives as its arguments the question and the new value. It should then return a “truthy” value to indicate whether or not the value should be accepted.
If the value is not accepted, it is probably also a good idea to invoke the (show-error …) or (show-warning …) functions discussed below, to alert the user that the value was rejected.
When an answer is set (optionally preceded by a validation phase), it is possible to execute a custom function. The typical scenario is that some other answers should also be changed, e.g. to avoid inconsistency.
Such additional action can be triggered by setting the :action-fn
field of a question. The arguments it receives are the question that was answered, as well as the value that was set. After the function has executed, recalculation is automatically performed, so it is not necessary to invoke (recalc)
manually.
You can build custom interfaces for questions through a custom interface function.
When a custom function should be invoked by different parts of a Q&A, it is probably a good idea to store it centrally, to avoid having to manually change the same function in multiple locations.
Custom functions are centrally managed in the repository pane, subsection custom functions.
Once centrally stored, custom functions can be invoked through (call ...)
. For example, to invoke the custom function called “custom-function2” in the screen shot above, with arguments 5 and 33, you would use (call "custom-function2", 5, 33)
.
Within the custom function you can write Clojure functions however you like — within the limitations set forth above. The result of the custom function should be one of the following:
in the Q&A environment for conditions: a “truthy” value in the traditional Clojure sense (i.e., anything besides false
or nil
will result in true)
in the Q&A environment for a custom interface: a Hiccup-style vector (see explanations below)
in the clause environment:
nil
a string
an IntValue, FloatValue or CurrencyValue
Clojure integer or double
a Date or Duration
any of the Clause9 grammar data structures (see below)
a vector of any of the above
All clojure.core
functions are accessible without a namespace alias.
The clojure.string
functions are aliased as str
.
Printing output to the browser’s console is done using pprint
.
In addition, see the specific Clause9 functions described below.
The following data structures for constructing grammars are available, to allow you to programmatically define paragraphs, bullets, tokens etc. — as if you would be constructing them using the regular Clause9 grammar style.
paragraph construction
->Paragraph
creates a plain paragraph block without a number or bullet. Parameters: one or more tokens.
->Paragraphs
creates a collection of (plain, numbered or bulleted) paragraphs. Parameters: one or more paragraphs.
->NrParagraph
creates a numbered paragraph. Parameters:
either a number, or a collection of numbers
one or more tokens
->BulletParagraph
creates a bulleted paragraph. Parameters:
the bullet-level (integer)
one or more tokens
->Snippet
creates a snippet, which is a container for various records. The parameters consist of one or more tokens.
->InsertionGroup
is a container for various other records. Parameters:
a vector of elements
->Endnote
and ->Footnote
create an endnote or footnote, respectively. The sole parameter is an insertion-group.
token constructionin general
->Alert
creates an alert record, e.g. for warnings. Its sole parameter is the body of the alert (string).
->DynamicNr
creates a dynamic number, i.e. a number that is either printed as a number or spell out in full. The sole argument is an integer.
->ImageFile
creates an image token that refers to an existing file. The sole parameter is a defined-term that links to that file (see term
below).
->ImageURL
creates an image token that refers to some URL on the internet. The sole parameter is a string with the URL.
references creation
->ConceptImplReference
and ->ConceptDefReference
create a reference, respectively to a Concept’s implementation clause or definition. Parameters:
a defined term, retrieved through term
the capitalization — either :none
, :initial
, :all-words
or :all
->MainBodyReference
creates a reference to the main body of a Binder. No parameter.
->TagReference
creates a reference to a clause that implements a tag. The sole parameter is this tag (string).
token modification
->Capitalizator
modifies the capitalisation of the text within its element. Parameters:
some record
one of the following keywords: :none, :initial, :all-words or :all
->Bold
, ->Italic
and ->Underlined
creates a special element in MS Word. The parameters should consist of one or more tokens.
tableconstruction
->Table
constructs a table. It takes one or more table-rows as its parameters.
->Row
constructs a table-row. It takes one or more cells as its parameters.
->Cell
constructs a table cell. It takes one or more tokens as its parameters.
predefined answers in Q&A
The following functions create predefined answers, for pre-populating a question. For each of those functions, the ID parameter must be a negative integer between 0 and -100000, and this ID must be unique among the predefined values of that question.
->BoolAnswer
takes an ID and true/false
->IntAnswer
takes an ID and an integer
->FloatAnswer
takes an ID and a FloatValue
->CurrencyAnswer
takes an ID and a CurrencyValue
->StringAnswer
takes an ID and a string
->i18StringAnswer
takes an ID and an i18-map
->DateAnswer
takes an ID and a Date value
->DurationAnswer
takes an ID and a Duration value
->IntValue
creates an IntValue. The sole parameter is an integer.
->FloatValue
creates a FloatValue. The sole parameter is an integer that represents the floating value with 4 decimals — e.g. 123456 represents 123.456
->CurrencyValue
creates a CurrencyValue. The parameters are:
an integer that represents the value with 4 decimals — e.g. 123456 represents 123.456
a keyword that refers to the currency — currently :EUR
, :GBP
, :USD
, :JPY
, :INR
or :CAD
->Duration
creates a Duration value. The parameters are:
an amount, a positive integer
a time-unit: either :year
, :month
, :day
, :week
or :quarter
->Date
creates a Date value. The parameters are three integers, respectively for year / month / day.
an “i18-map” is a Clojure map in which strings are stored for multiple languages. The key is a two-letter keyword: depending on the subdomain on which Clause9 is hosted, this will be :en
, :fr
, :nl
, :de
, :es
or :lt
?
prints its argument to the browser console, and then returns its argument
Clojure’s standard println
function is also available for printing to the browser’s console
check for identity with any of the following functions:
Alert?
Bold?
BoolAnswer?
BulletReference?
Capitalizator?
Cell?
ConceptDefReference?
ConceptImplReference?
Conjugation?
CurrencyAnswer?
CurrencyValue?
Date?
DateAnswer?
DefinedTerm?
Duration?
DurationAnswer?
DynamicNr?
Enumeration?
EnumSnippet?
FloatAnswer?
FloatValue?
i18StringAnswer?
Image?
IntAnswer?
IntValue?
Italic?
LineBreak?
MainBodyReference?
NrsetReference?
Paragraph?
PlainText?
Row?
Snippet?
StringAnswer?
Superfluous?
Tab?
Table?
TagReference?
TermField?
ThisArticleReference?
Underlined?
card?
checks whether the given identifier refers to an enabled card
question?
checks whether the given identifier refers to an enabled question
change-set?
checks whether the given identifier refers to an enabled change-set
answer
results in the current value (answer) of the question referred to by the given identifier. Note that the answer to a table-based question is structured as {row-index {col-index value}}
.
set-answer
allows you to modify the change the answer to a given question. The first argument should be the question-identifier (string), the second parameter either a value or an update-function on the current value. You should also invoke recalc
once you are done changing answers (except if the set-answer
was invoked as part of an :action-fn
, because action-fn automatically invokes recalc).
show-error
, show-warning
and show-info
allow you to show alerts at the bottom of the browser screen, in different styles. Their sole argument is the message (string).
focus
(sole argument is the question’s identifier) allows you to highlight and then focus on a specific question
gui-lang
and output-lang
return the current language used in the cards or output, respectively
export-docx
(no arguments) renders the document at the server and then downloads the document as DOCX in the user's browser
export-pdf
(no arguments) renders the document at the server and then downloads the document as PDF in the user's browser
export-docx-in-browser
(no arguments) renders the document within the browser and downloads the document as DOCX in the user's browser
export-pdf-in-browser
(no arguments) renders the document within the browser downloads the document as PDF in the user's browser
export-post
renders the document at the server and subsequently sends the document to some webhook. The following arguments must be passed in a map:
url
mandatory, the URL where to perform the POST
format
mandatory: either :docx
or :pdf
additional-data
optional, a map with data with some custom internal information that will be passed on to the webhook. You may, for example, want to pass on some internal identifier as a string, or perhaps a map with numbers, or ... Note that the exportIdentifier
that you can include when creating a magic-link through the API (called the identifier for reporting purposes in the GUI dialog box for creating a magic-link) will automatically be included as the :export-identifier in this map)
on-success
optional, a function (no params required) that will be executed client-side when the rendering & submission is successful
Example of how you may want to use export-post
:
The POST-call that will be performed, will have the following structure:
document
contains the Base64-version of the document (PDF or DOCX)
additional-data
contains the additional data described above
qna-id
contains the file-ID of the Q&A
call
allows you to call another custom Clojure-function. The first argument should be a simple argument (see below), the rest of the arguments are the actual parameters for the called function.
datafield
(only available within the environment of a clause) allows you to retrieve a certain datafield. Its argument should be a compound argument.
value
(with, as its sole argument, a datafield obtained through datafield
) allows you to retrieve the current value of a datafield
term
(only available within the environment of a clause) allows you to refer to a certain defined term. Its argument should be a string with the defined term.
simple and compound arguments are always Clojure strings
in the environment of a clause:
a simple argument should refer to one of the custom functions defined in the same file — for example, (call "xxx", :alpha)
would call custom function “xxx” (defined in the same file) with as its single argument the keyword :alpha
.
a compound argument should consist of the name of the referenced file, a slash (/) or circonflex (^), and the name of the custom function or datafield within the referenced file. For example, (call "alpha/xxx", :alpha)
would call the custom function “xxx”, with the single argument keyword :alpha
.
in the Q&A environment:
a simple argument should refer to one of the custom functions defined in the Q&A’s repository pane
a compound argument should consist of the name of the file (as defined in the file links of the Q&A’s repository pane), a slash (/) or circonflex (^) and the name of the custom function within that file. For example, "alpha/beta"
would refer to the custom function called “beta” within file “alpha”, whereby file “alpha” would be present in the file links repository.
The following functions are part of namespace convert
:
Duration->months
, Duration->days
, Duration->weeks
, Duration->quarters
and Duration->years
take a Duration value, and return the number of months/days/weeks/quarters/years as an integer.
FloatValue->int
, FloatValue->IntValue
and FloatValue->float
convert a FloatValue.
float->FloatValue
, IntValue->FloatValue
, Amount->FloatValue
convert to a FloatValue. (An Amount can be either a FloatValue or an IntValue).
int->CurrencyValue
, IntValue->CurrencyValue
, CurrencyValue->float
, float->CurrencyValue
, CurrencyValue->FloatValue
, CurrencyValue->int
and CurrencyValue->IntValue
convert to/from a CurrencyValue.
The following functions are part of namespace ustr
:
quoted
takes a string and returns that string surrounded by double (straight) quotes.
one-else
takes nr, one-string and else-string als parameters. Returns one-string if nr is equal to 1, otherwise the else-string.
one-else-nr
takes nr, one-string and else-string als parameters. Returns nr concatenated by either one-string (if nr is equal to 1), otherwise the else-string.
sub
takes a string, a start (integer) and an optional end (also integer). Return substring. Ignores indexes out of bounds.
upper-case?
returns true if the string parameter is entirely in upper-case.
initial-cap?
returns true if string has an initial cap, but is not fully capitalized (e.g., ‘Dog’ but not ‘DOG’).
initial-cap
puts an initial cap on s. Note that all letters after the first one are left untouched, so that if those contain uppercase letters, they will remain in the result.
capitalize-words
capitalize each of the separate words
capitalization
returns whether string is not capitalized in any way (:none
), or has initial caps (:initial
), or is all-caps (:all
).
apply-capitalization
takes a capitalization keyword (:none
, :initial
, :all-words
or :all
) and applies that capitalization style to the second parameter (string).
uncapitalize
returns a string with the first letter forced to lower (the rest is untouched).
clean-curly-quotes
replaces curly quotes and guillemets by straight ones.
str->int
converts a string to an integer.
str->float
converts a string to a float.
float->str
converts a float to a string. The arguments are a float, metric style? (boolean) and the decimals count (positive integer).
str->bool
converts ‘true’, ‘false’, ‘1’ or ‘0’ strings to boolean. nil if some other input was given.
extract-int
extracts the first occurrence of some integer from anywhere in the string.
int->str takes
as arguments an integer and a minimum-numbers-count. Convert the integer to a string, padding the result with zeros up to the minimum-numbers-count. Note that no thousands-grouping is applied.
comma-join
takes as arguments a combiner string and a collection of strings. Joins the different elements of the collection as strings with each other, separated by comma’s. The last element is joined with the specified combiner argument.
group-by-thousands
takes either an integer or a string, as well as a thousands-separator character. It returns a string in which the thousand-separators are added.
month->string
takes the number of the month (integer) and a language keyword, and returns the month in the specified language.
weekday->string
takes a weekday-number (integer: Monday is 1, Sunday is 7) and a language keyword, and returns the weekday as a string in the specified language.
date->str
takes year (integer), month (integer), day (integer), a date-style-kw and a language keyword, and returns the formatted date in the specified language. The date-style-kw can be any of the following: :d-m-yyyy :slashed-d-m-yyyy :m-d-yyyy :slashed-m-d-yyyy :dd-mm-yyyy :slashed-dd-mm-yyyy :mm-dd-yyyy :slashed-mm-dd-yyyy :dd-mm-yy :slashed-dd-mm-yy :mm-dd-yy :slashed-mm-dd-yy :yyyy-mm-dd :slashed-yyyy-mm-dd :d-mmmm-yyyy :mmmm-d-yyyy :mmmm-d-comma-yyyy :dd-mmmm-yyyy :wwww-dd-mmmm-yyyy :wwww-d-mmmm-yyyy :wwww-comma-d-mmmm-yyyy :wwww-mmmm-dd-yyyy
.
ellipsis
takes two arguments (string input and a max integer) and truncates string with … after specified number of characters.
drop-last-character
drops the last character of a string. Returns empty string if nothing is left in the string.
drop-surrounding-characters
drops the characters surrounding a string. Returns empty string if nothing is left in the string.
drop-last-n-chars
takes an input string and a number of characters (integer). Returns empty string if nothing is left in the string, or n is simply too high.
get-last-n-chars
takes an input string and a number n (integer). Returns string with last N characters.
trim-if-string
returns its sole argument if it is not a string, otherwise returns the trimmed argument.
lower-if-string
returns its sole argument if it is not a string, otherwise returns the lower-cased argument.
trim|lower-if-string
returns its sole argument if it is not a string, otherwise returns the lower-cased & trimmed argument.
when-s
returns the string if it is not empty, else return nil.
The following functions are part of namespace math
:
sqrt
returns the square root
log
returns the logarithm
E
returns the constant e
acos
returns the arc cosine of a value
asin
returns the arc sine of a value
atan
returns the arc tangent of a value
atan2
returns the angle theta from the conversion of rectangular coordinates to polar coordinates
cos
returns the trigonometric cosine of an angle
sin
returns the trigonometric sine of an angle
tan
returns the trigonometric tangent of an angle
exp
returns Euler’s number e raised to the power of a
PI
returns the constant pi
pow
returns the value of the first argument raised to the power of the second argument
random
returns a random float value between 0 and 1. (You will probably want to invoke the host function using @cljr
, otherwise the value will not be updated).
The following functions are part of namespace coll
:
i18sv
takes an i18-map and a language-keyword, and extracts the string value for the requested language. If the requested language is not available, then the next successive language is tried (hence the name: -sv refers to successive). Only returns a string if it is non-empty.
find-first
takes a predicate and a collection, and returns the first item that meets the predice. Essentially it is an optimized version of (first (filter ...))
intersects?
takes two collections and returns true if there is at least one common element between the two collections.
keepv
is similar to Clojure core’s keep function, but returns a vector.
indexed
returns a collection that consists of [index, item] of the original coll.
index-of
returns index of first item that meets the predicate. Nil if not found.
index-of-value
returns first index of value within the collection. Nil if not found.
present?
takes a value and a collection, and returns true if value is present in the collection. Otherwise false.
singleton?
returns true if collection contains exactly one item.
dissoc-in
(which takes a map and a collection of keys) dissociates an entry from a nested associative structure returning a new nested structure. Note that entire branches may get wiped out. For example, (dissoc-in {:a {:b {:c {:d 5}}}} [:a :b :c :d])
results in {}
.
dissoc-in-leaf
is like dissoc-in, but only wipes out the “leaf”, preventing intermediary branches from being removed. For example, (dissoc-in {:a {:b {:c {:d 5}}}} [:a :b :c :d])
results in {:a {:b {:c {}}}}
remove-element-v
takes a vector and an item. It removes item from vector, and returns updated vector.
remove-elements-v
takes a vector and one or more items. Removes those items from vector. Returns updated vector.
remove-atindex-v
takes a collection and a position. Removes the item with the specified index from the vector. Returns updated vector.
insert-v
takes a vector, a position and an item. It inserts item into vector at position. If the position is invalid, then the item is simply conj’ed. Returns updated vector.
cons-v
is like Clojure core’s cons, but returns a vector.
concat-v
is like Clojure core’s concat, but returns a vector.
items-before-pred
takes a predicate and a collection. Returns the vector of all items positioned before the first item that meets the pred (excluding the pred-meeting item itself). If pred is never met, then the entire coll is returned.
items-before-or-at-pred
takes a predicate and a collection. Returns the vector of all items positioned before the first item that meets the pred, including the pred-meeting item itself. If pred is never met, then the entire coll is returned.
items-after-pred
takes a predicate and a collection. Returns the vector of all items positioned after the last item that meets the pred (excluding the pred-meeting item itself). If pred is never met, then the entire coll is returned.
items-at-or-after-pred
takes a predicate and a collection. Returns the vector of all items positioned after the last item that meets the pred, including the pred-meeting item itself. If pred is never met, then the entire coll is returned.
split-around
takes a predicate and a collection. Splits collection into a tuple of three vectors, where:
the first vector contains all elements up to (but not including) the first element that matches pred;
the second item is the matching element itself;
the third item is a vector that contains all elements after the matching element (which may contain other matching elements, but this is not checked).
If no match exists, then the second item and third item will be nil.
split-in-two-vectors
takes a predicate and a collection. Splits collection into a tuple of two vectors:
the first vector contains all elements up to (but not including) the first element that matches pred;
the second vector is the rest of the items.
If no match exists, then the second item will be nil.
remove-fields
takes a predicate and a map. Remove those map fields for which (pred value) meets the pred. If no keys are left, an empty map is returned.
de-nil
takes a map. Remove keys with nil-values from the map. If no keys are left, nil is returned.
de-nil-map
takes a map. Remove keys with nil-values from the map. If no keys are left, empty map is returned.
nnmerge
takes one or more maps. Merges the first map with the de-nil’ed other maps.
merge-vectors
takes vec1, vec2 and extract-identity-fn. Merge elements of two vectors. If two values conflict, then the latter vector’s element will take precedence. extract-identity-fn should be a function that takes one parameter, and extracts the ‘identity’ of a value, to determine conflicts.
merge-missing
takes two maps. Merge those fields of map2 that are missing in map1, into map1
keep-first
takes a function and a collection. Like keep/keepv, but immediately stops upon the first hit, and returns that one. Returns nil when no match.
mapcat-v
is like Clojure core’s mapcat, but returns a vector.
flatten-one
flattens the given collection one level deep.
update-el
takes v, pred and f. Updates the first el of the vector that matches pred (leaves the rest untouched).
replace-el
takes v, pred and new-el. Replaces the first el of the vector that matches pred, with the new element.
replace-or-add
takes v, pred and replacement-el. Replaces the first element of the vector that matches pred, by the specified replacement. If pred does not match anywhere, then conj the replacement to the vector.
any-field-equal?
takes a map, fields (collection of keywords) and ref-value. Returns true if any of the specified fields in map m is equal to ref-value.
any-field?
takes a map m, fields (collection of keywords) and a predicate. Returns logical true if (pred field) is true for any of the specified fields in map m.
drop-until-nth-pred
takes n (integer), pred (function) and a collection. Drop items until pred is met for the nth time.If successful, the first item of the returned coll will meet n. Returns nil if pred is not met at least n times.
index-of-subcoll
takes a large collection and a sub-collection. Returns the position (0-based) of the sub-coll within big-coll. nil if not found.
initial-subvec?
takes a vector and a subvector. Returns true if subv is a subcollection of v, at index 0. Note that if v and subv are equal, the result will also be true.
distinct-by
takes a function and a collection. Returns a lazy sequence of the elements of coll, removing any elements that return duplicate values when passed to a function f.
remove-v
like Clojure core’s remove, but returns a vector.
removev-with-id
takes an ID and a collection. Removes all elements that have the specified ID.
remove-first-v
takes a predicate and a collection. Removes first element from the collection that meets the specified pred. Unlike remove-v, it does not continue looking once a match is found. Always returns a vector.
sort-by-other-coll
takes a collection to sort, a reference collection and an optional key-function. Sorts the items in the given collection on the basis of the ordering of items in reference collection. If an item is not present in the reference collection, then it will be appended to the end of the sorted-result-so-far. If a key-fn is given, it will be used to extract the key from each item, for use in the ordering vis-à-vis the reference collection. Examples:
(sort-by-other-coll [:c :b :a :d] [:a :b :c]) => (:a :b :c :d)
(sort-by-other-coll [{:key :c} {:key :a}] [:a :b :c] :key) => ({:key :a} {:key :c})
conj-set
like Clojure core’s conj, but creates a set if the set-to-add-to is nil.
conj-vec
like Clojure core’s conj, but creates a vector if the vec-to-add-to is nil.
find-first-with-id
takes an ID and a collection. Returns the first element from the collection whose :id field matches the given id.
find-first-equalto
takes an object and a collection. Returns the first element from the collection that is equal to the given object.
swap-elements
takes a vector, and two indices. Swap the vector’s elements with the two specified indices.
submap?
takes two maps. Returns true if a is a submap of b, i.e.: a only has fields that are also present in b, and those fields are identical to those of b.
update-map-values
takes a map and a function. loops through the map, and call f with the key & value on each iteration. The value will then be updated with the result of f.
update-mapv
takes a map, a field-keyword and a function f. Updates the vector in map m’s field-kw, by calling f on each of its elements.
update-kv
takes a map, a field-keyword and a function f. Updates the map in map m’s field-kw. f should be a function to be used in a reduce-kv, so taking three elements.
The ajax/get
function allows you to fetch information from external servers, using a GET call for HTTP. Note that, due to the asynchronous nature, it is currently only available in the Q&A environment. The parameters are:
a URL (string)
a map with the following optional keys:
:on-success
is a function that is called with the result of the call. If it is nil, then ajax-get
will simply return the success-result.
:on-error
is a function that is passed a map (see cljs-ajax documentation on ‘Error Responses’) and should either return :error
, or some other value
You can build custom mini-interfaces by adding a new “custom block” question in the Q&A, and inserting code. You can develop these interfaces interactively by testing the cards (showing the test cards pane in your design Q&A environment and either hitting the refresh button, or shortcut Ctrl-U).
For example, the following would create a DIV (10 pixel padded, with a 2 pixel blue border and 5 pixel external margin) with a single paragraph with red text, and a button that prints “hello” to the console when clicked:
You can reformat the Clojure-code you have written by pressing the Ctrl-B shortcut.
When developing in the Design Q&A environment, you probably want to show the Test cards pane, to interactively check what your Clojure code is doing.
This custom function is useful to modify how the value that is selected by the end-user through the service)-integration, gets “distributed” (pasted onto) the other answers in the Q&A — e.g., for situations where the standard mechanics are not sufficient for your purposes. In such case, you could create a custom-function that directly performs several set-answer
to modify the answer.
answers
contains the answers given by the end-user for the non-disabled questions. The structure is equivalent to the QnaValue
data structure described in .
:options
is a map of options. See the .
The interfaces are built using a subset of the library, combined with ClojureScript’s . The following Blueprint-widgets are available: button, callout, checkbox, menu, menuItem, menu-divider, radio-button, slider, switch, tag, table and tooltip. In addition, you can create any DOM-element you like through the standard Hiccup syntax.
When developing in Assemble Document, you probably want to activate the , so you can interactively see the changes to your Clojure-code in the currently focused clause.