Custom functions

While ClauseBase 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, ClauseBase 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 ClauseBase.

Technical setup and limitations

ClauseBase 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 ClauseBase-specific functions are available to you (see below), to make use of the advanced features of ClauseBase.

The following Clojure features are either irrelevant, or simply not available to you, when developing in Clojure from within ClauseBase. When reading about Clojure, you can therefore completely skip any discussions about them:

  • namespaces — within ClauseBase, 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. (ClauseBase 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 ClauseBase 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, ClauseBase 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

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.

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, ClauseBase 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 the 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 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 ClauseBase 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 as str.
  • Printing output to the browser’s console is done using pprint.

In addition, see the specific ClauseBase functions described below.

ClauseBase 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 ClauseBase 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 construction in 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.
    • ->LineBreak creates a line-break. It does not take any parameters.
    • ->PlainText creates a plain text element. Its single parameter is a string.
    • ->Superfluous creates a superfluous record, corresponding to a part of a paragraph between square brackets in a clause. Parameters:
      • the superfluousness amount, i.e. the number of square brackets (integer)
      • a snippet
    • ->Tab creates a tab element. Parameters:
      • the type of tab — :left, :left-underscored, :left-dotted, :right, :right-underscored, :right-dotted or :signature
      • the tab position: either an integer between 0 and 10, or the keyword :right-margin
    • ->Voidness creates a a null-element. This can be useful when you want to avoid having to return something specific. No parameter.
  • 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.
  • table construction
    • ->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 ClauseBase is hosted, this will be :en, :fr, :nl, :de, :es or :lt

ClauseBase-specific functions

Debugging

  • ? 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

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 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
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 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.

Conversion functions

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.

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 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.

Mathematical functions

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).

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? 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-leafis 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.

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, 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
    • :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:

[:div 
 
 {:style {:padding "10px" :border "2px solid blue" :margin "5px"}}
 
 [:p 
  {:style {:color "red"}} 
  "I am a paragraph"]
 
 [:> Button {:text "I'm a button..." 
             :on-click (fn [event] 
                           (println "I'm clicked!"))}]]

Tips & Tricks

  • You can reformat the Clojure-code you have written by pressing the Ctrl-B shortcut.
  • When developing in the Q&A environment, you probably want to show the test cards pane, to interactively check what your Clojure code is doing. By pressing Ctrl-U, you can refresh the cards (saving you from a manual click on ).
  • When developing in Assemble Document, you probably want to activate the Focus mode , so you can interactively see the changes to your Clojure-code in the currently focused clause.
Was this article helpful?
Dislike
Example custom functions