Custom functions
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 Clojure programming language, and its close sibling ClojureScript.
How to get started
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 ClojureDoc introduction tutorial.
For a more in-depth introduction, we recommend to read Chapters 3, 4 and 5 of the excellent (and free) book Clojure for the Brave and True.
Technical setup and limitations
Clause9 exposes only a subset of the Clojure / ClojureScript, through the Small Clojure Interpreter. 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
Be aware that the invocation of custom functions is handled as a black box. With the exception of the println function for debugging, and a few exceptions in the Q&A environment (e.g., calls to set-answer, focus, and show-error/warning/info), the execution of Clojure-functions cannot modify the Document/Binder/questions, and will not cause side effects.
Accessing custom functions within a clause
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
.
Using custom functions in the Q&A environment
Custom functions can be used in several places in the Q&A environment.
Condition attached to a card, question, predefined answer or change set
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).
Customising the properties of a question, card or predefined answer
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 andnil
if the question is not a table-based answer) and a map with data that is selected by the end-user.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 receive tags 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.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]
.
Validating answers
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.
Executing actions upon setting an answer
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.
Building a custom question interface
You can build custom interfaces for questions through a custom interface function.
Centralising custom functions
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)
.
Executing custom functions
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
ornil
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
Accessing Clojure functions
All
clojure.core
functions are accessible without a namespace alias.The
clojure.string
functions are aliased asstr
.Printing output to the browser’s console is done using
pprint
.
In addition, see the specific Clause9 functions described below.
Clause9 grammar data structures
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.
Terminology for understanding the functions below: a “token” is a part of a sentence, such as a defined term or some plain text, while a “record” is any kind of element that can be created using the functions below.
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 (seeterm
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
Other data structures
->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
Clause9-specific functions
Debugging
?
prints its argument to the browser console, and then returns its argumentClojure’s standard
println
function is also available for printing to the browser’s console
Data structures
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?
Q&A
card?
checks whether the given identifier refers to an enabled cardquestion?
checks whether the given identifier refers to an enabled questionchange-set?
checks whether the given identifier refers to an enabled change-setanswer
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 invokerecalc
once you are done changing answers (except if theset-answer
was invoked as part of an:action-fn
, because action-fn automatically invokes recalc).show-error
,show-warning
andshow-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 questiongui-lang
andoutput-lang
return the current language used in the cards or output, respectively
In the bullets above, “identifier” refers to the optional identifier that can be given to cards and questions (accessible through the card’s or question’s options). The identifier should be a unique string that allows to immediately pinpoint a certain card or question.
Datafields, defined terms and custom function calls
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 throughdatafield
) allows you to retrieve the current value of a datafieldterm
(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.
Conversion functions
The following functions are part of namespace convert
:
Duration->months
,Duration->days
,Duration->weeks
,Duration->quarters
andDuration->years
take a Duration value, and return the number of months/days/weeks/quarters/years as an integer.FloatValue->int
,FloatValue->IntValue
andFloatValue->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
andCurrencyValue->IntValue
convert to/from a CurrencyValue.
String functions
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 wordscapitalization
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.
Mathematical functions
The following functions are part of namespace math
:
sqrt
returns the square rootlog
returns the logarithmE
returns the constant eacos
returns the arc cosine of a valueasin
returns the arc sine of a valueatan
returns the arc tangent of a valueatan2
returns the angle theta from the conversion of rectangular coordinates to polar coordinatescos
returns the trigonometric cosine of an anglesin
returns the trigonometric sine of an angletan
returns the trigonometric tangent of an angleexp
returns Euler’s number e raised to the power of aPI
returns the constant pipow
returns the value of the first argument raised to the power of the second argumentrandom
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).
Collection-related additional functions
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.i
tems-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 map1keep-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.
AJAX-get
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, thenajax-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:options
is a map of options. See the documentation for ajax-get.
Building Q&A interfaces using “custom blocks”
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).
The interfaces are built using a subset of the Blueprint.js library, combined with ClojureScript’s Hiccup syntax. 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.
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:
Tips & Tricks
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.
Last updated