Function & Keyword API πŸ”—

Text variables πŸ”—

text.clean πŸ”—

Returns a copy of the text with spaces and any other "whitespaces" (tabs, carriage return, ...) removed from the beginning and end.

>> myText = "	      ...that was good!	    "
>> cleanText = myText.clean

text.count(textToCount) πŸ”—

Returns the number of times that textToCount appears in a given text.

>> myText = "the smell of cookies in the oven is the best thing in the world"
>> totalThe = myText.count("the")

text.decode(encodingScheme) πŸ”—

Decodes a text variable using the specified decoding scheme. Currently the only scheme that is supported is "url" which returns a copy of the given text where the "%"-based escape sequences of characters are replaced with their normal representations.

>> urlParams = "?city=Gen%C3%A8ve&planet=The%20Earth"
>> decodedPar = urlParams.decode("URL")

See: .encode .


text.encode(encodingScheme) πŸ”—

Encodes a text variable using the specified encoding scheme. Currently the only scheme that is supported is "url", which replaces unsafe ASCII characters with a "%" followed by two hexadecimal digits.

>> urlParams = "?city=Genève&planet=The Earth"
>> encodedPar = urlParams.encode("URL")

See: .decode .


text.find(textToFind) πŸ”—

Returns the first position in which the text textToFind appears in a given text.

>> myText = "Did you ever visit Paris?"
>> parisIndex = myText.find("Paris")

text.lowercase πŸ”—

Returns a copy of the text converted to lowercase.

>> myText = "Text with Capital Letters"
>> lowercaseText = myText.lowercase

text.size πŸ”—

Returns the number of characters in a text.

>> myText = "I love going to the movies"
>> textLenght = myText.size

text.split(delimiter) πŸ”—

Splits a text into chunks at every delimiter and returns the chunks in a collection.

>> fruitsILike = "pears and bananas and oranges and mangoes"
>> collectionOfFruits = fruitsILike.split(" and ")
-- collectionOfFruits = ["pears", "bananas", "oranges", "mangoes"]

text.uppercase πŸ”—

Returns a copy of the text converted to uppercase.

>> myText = "Beauty is Truth"
>> uppercaseText = myText.uppercase

Numeric variables πŸ”—

number.round πŸ”—

Rounds a number to the nearest integer. Optionally, a number of decimal places can be specified.

>> pi = 3.14159265359
>> piInteger = pi.round
>> piToFourDecimals = pi.round(4)

number.[duration] πŸ”—

Returns a duration of the relevant size. Valid time units are: seconds, minutes, hours, days, weeks, months, and years.

>> oneYear = 365.days
*wait: 5.seconds
>> threeAndAHalfMinutes = (3.5).minutes

Collections πŸ”—

collection.add(elementToAdd) πŸ”—

Adds elementToAdd to the end of a collection.

>> peopleIKnow = ["Peter", "Lisa"]
>> peopleIKnow.add("Roger")

collection.combine(collectionToAdd) πŸ”—

Adds the elements of collectionToAdd to the end of a given collection.

>> numbers = [1, 2, 3]
>> nextNumbers = [4, 5, 6]
>> numbers.combine(nextNumbers)
-- Now `numbers` = [1, 2, 3, 4, 5, 6]

By the way, the .combine method is useful when you need to make a copy or duplicate of a collection. For example, here's a case where we might want to shuffle a collection but keep a copy of the original, un-shuffled collection:

-- define the original collection
>> x = [1, 2, 3, 4, 5]

-- construct a copy of `x`
>> y = []
>> y.combine(x)

-- shuffle `y` (without affecting `x`)
>> y.shuffle

-- display the results
x: {x}
y: {y}
*button: Yep, that worked!

collection.count(valueToCount) πŸ”—

Returns the number of times that the valueToCount appears in the collection.

>> tripsLastYear = ["Amsterdam", "Madrid", "London", "Amsterdam", "Rome", "London", "Lisbon"]
>> timesInLondon = tripsLastYear.count("London")

collection.erase(valueToErase) πŸ”—

Removes all elements of a collection that match a given value.

>> tripsThisYear = ["Paris", "London", "Rome", "London", "Madrid"]
>> tripsThisYear.erase("London")

See .remove for a comparison of the two methods.


collection.find(valueToFind) πŸ”—

Returns the first position in which the valueToFind appears in the collection.

>> tripsLastYear = ["Amsterdam", "Madrid", "London", "Amsterdam", "Rome", "London", "Lisbon"]
>> indexMadrid = tripsLastYear.find("Madrid")

collection.insert(elementToAdd, position) πŸ”—

Inserts elementToAdd at a given position of a collection.

>> peopleIKnow = ["Peter", "Lisa"]
>> peopleIKnow.insert("Roger", 2)

collection.max πŸ”—

Returns the maximum value in a collection of numbers.

>> dailyPushupsLastWeek = [110, 132, 123, 149, 162, 148, 135]
>> maximumDailyPushups = dailyPushupsLastWeek.max

collection.mean πŸ”—

Returns the mean value of a collection of numbers.

>> dailyPushupsLastWeek = [110, 132, 123, 149, 162, 148, 135]
>> averageDailyPushups = dailyPushupsLastWeek.mean

collection.median πŸ”—

Returns the median value of a collection of numbers.

>> dailyPushupsLastWeek = [110, 132, 123, 149, 162, 148, 135]
>> medianDailyPushups = dailyPushupsLastWeek.median

collection.min πŸ”—

Returns the minimum value in a collection of numbers.

>> dailyPushupsLastWeek = [110, 132, 123, 149, 162, 148, 135]
>> minimumDailyPushups = dailyPushupsLastWeek.min

collection.remove(position) πŸ”—

Removes the element at a given position of a collection.

>> friends = ["Liz", "Ana", "Sam"]
>> friends.remove(2)

Note that .remove differs from .erase . The .remove method allows you to remove a single value by specifying a position, whereas the .erase method allows you to remove all instances of a specified value.

Note also that these two methods perform analogous actions in associations; i.e., .remove removes a single key-value pair from an association by specifying a position, and .erase removes all relevant key-value pairs by specifying a value.


collection.shuffle πŸ”—

Shuffles the elements in a collection.

>> myCollection = [1, 2, 3, 4, 5]
>> myCollection.shuffle

collection.size πŸ”—

Returns the number of elements in a collection.

>> tripsLastYear = ["Amsterdam", "Madrid", "London", "Amsterdam", "Rome", "London", "Lisbon"]
>> totalTrips = tripsLastYear.size

collection.sort(direction) πŸ”—

Sorts a collection where direction is either "increasing" or "decreasing".

>> friends = ["Liz", "Ana", "Sam"]
>> friends.sort("increasing")

collection.unique πŸ”—

Returns a copy of the collection without duplicate values (i.e., returns the set of values in the collection).

>> tripsLastYear = ["Amsterdam", "Madrid", "London", "Amsterdam", "Rome", "London", "Lisbon"]
>> citiesVisited = tripsLastYear.unique

Associations πŸ”—

association.erase(valueToErase) πŸ”—

Removes all elements in an association whose value match a given one.

>> favFoods = {"Pete" -> "Cake", "Lucas" -> "Soup", "Beth" -> "Pizza", "John" -> "Cake"}
>> favFoods.erase("Cake")

See .remove for a comparison of the two methods.


association.keys πŸ”—

Returns a collection of the keys of an association.

>> phoneBook = {"Lucas" -> 223142, "Mara" -> 772632, "Sue" -> 883325}
>> peopleInPhoneBook = phoneBook.keys

association.remove(keyToRemove) πŸ”—

Removes a key and its associated value from an association.

>> phoneBook = {"Lucas" -> 223142, "Mara" -> 772632, "Sue" -> 883325}
>> phoneBook.remove("Lucas")

Note that .remove differs from .erase . The .remove method allows you to remove a single key-value pair by specifying a key, whereas the .erase method allows you to remove all relevant key-value pairs by specifying a value.

All of the keys of an association are unique; i.e., there can't be two keys of the same name in an association. But there can be multiple copies of the same value in an association. So, if you wanted to remove a particular key from an association, then there would only one key-value pair to remove, and you'd use the .remove method to remove it; but if you wanted to remove a particular value from an association, then there could potentially be a lot of key-value pairs to remove, and you'd use the .erase method to remove them.

Note also that these two methods perform analogous actions in collections; i.e., .remove removes a single item from a collection by specifying a position, and .erase removes all instances of a specified value from a collection.


Datetime and duration variables πŸ”—

calendar::date πŸ”—

Returns a datetime. Optionally, an association argument can be accepted. If no argument is given, then the current date is returned.

>> todaysDate = calendar::date
>> otherDate = calendar::date({"year" -> 2021, "month" -> 12, "day" -> 22})

calendar::now πŸ”—

Returns the current datetime.

>> rightNow = calendar::now

calendar::time πŸ”—

Returns a datetime. Optionally, an association argument can be accepted. If no argument is given, then the current time is returned.

>> currentTime = calendar::time
>> otherTime = calendar::time({"hour" -> 13, "minute" -> 42})

duration.to(timeUnit) πŸ”—

Converts a time duration variable into a different time unit. Valid time units are: seconds, minutes, hours, days, weeks, months, and years

>> timeElapsed = calendar::date - calendar::date({ "month" -> 12, "day" -> 30, "year" -> 2020})
>> daysElapsed = timeElapsed.to("days")

Any variable type πŸ”—

any.text πŸ”—

Returns the value of the variable converted to text.

>> today = calendar::date
>> textDate = today.text

any.type πŸ”—

Returns the variable's type (e.g., number, collection, association, etc.).

>> variable = 43.22
>> typeOfVariable = variable.type

Keywords πŸ”—

*audio: audioUrl πŸ”—

Embeds an audio file into the page.

Sub-keywords:

  • *start: yesOrNo = Determines whether or not the audio can auto-play; defaults to "no". This sub-keyword is optional .
  • *hide: yesOrNo = Determines whether or not the audio player controls are visible; defaults to "yes". This sub-keyword is optional .
Here's episode 021 of the /Clearer Thinking/ podcast:

*audio: https://dts.podtrac.com/redirect.mp3/1227185674.rsc.cdn77.org/aud/021.mp3
	*start: yes
	*hide: no

*button: textToDisplay πŸ”—

Puts a button with specific text in a page.

Note that buttons are typically used to mark the end of a page, usually after a body of text. For example:

------------
-- PAGE 1 --
------------

Hello!

Here is some text. I'm so glad we're here, aren't you? There's so much to learn!

Okay, now I'm done.

*button: Next

------------
-- PAGE 2 --
------------

Here's some stuff on the next page.

*button: Yes, you're right!

In the above example, everything above *button: Next would be displayed on a single page, and the "Next" button would appear at the bottom. When users click the "Next" button, the program will continue onward to the "Here's some stuff..." page and place a "Yes, you're right" button at the bottom.


*chart: chartName πŸ”—

Displays a chart

Sub-keywords:

  • *data: myDataCollection = Defines the data to be displayed; must be a collection of collections (i.e., a two-dimensional collection). For bar charts, the collection must include pairs of labels and values, like: *data: [["chocolate", 123], ["vanilla", 234]] . For line and scatter charts, the collection must include pairs of x-values and y-values, like: *data: [[23, 45], [67, 89]] . This sub-keyword is required .
  • *type: chartType = Specifies the type of chart. Options include "bar", "line", and "scatter". This sub-keyword is required .
  • *xaxis = Defines the range of values to be displayed on the x-axis. This sub-keyword is optional .
  • *yaxis = Defines the range of values to be displayed on the y-axis. This sub-keyword is optional .
  • *trendline = Computes and draws a trend-line (i.e., the line of best fit) in scatter charts. This sub-keyword is optional .
*chart: My daily push-up count
	*type: scatter
	*data: [[0, 4], [1, 7], [2, 13], [3, 10], [4, 14], [5, 16], [6, 20]]
	*trendline

*clear πŸ”—

Clears text that was kept on the page by *maintain .

*maintain: Please answer honestly!

*question: Have you ever stolen anything?
	Yes
	No

*question: Have you ever told a lie?
	Yes
	No

*clear

*component πŸ”—

Displays a bordered content box.

Sub-keywords:

  • *classes: class1, class2, class3, ... = Defines a list of class names to be applied to the component. Possible class names include Bootstrap class names (like "alert-warning") or class names defined elsewhere (e.g., in <style> tags, in CSS files, etc.) This sub-keyword is optional .
  • *click = Defines code (indented beneath) that will run if the user clicks on the component. This sub-keyword is optional .
  • *with = Used in combination with *click , creates a local variable to generate different click handlers. Useful when a page has multiple components that are dynamically rendered. This sub-keyword is optional .
*component
	*classes: alert-info
	Hey! This is a blue info box!
	*click
		>> someVariable = someValue
		*goto: someLabel

Here is an example of the syntax when using the *with keyword in combination with *click . Follow this link for additional info on its usage.

*label: people
*for: person in people
	*component
		*header: {person["name"]}
		*with: person
		*click
			Name: {it["name"]}
			Age: {it["age"]}
			*button: Back
			*goto: people

*database πŸ”—

Requests user info from the GuidedTrack database.

Sub-keywords:

  • *what: typeOfData = Specifies the kind of data you're requesting. Currently, "email" is the only valid option. This sub-keyword is required .
  • *success = Defines code to run if the request succeeds. Any data returned from the request will exist in a variable called it and will only be available in this block. This sub-keyword is required .
  • *error = Defines code to run if the request fails. Error information will exist in a variable called it (with properties "reason" and "message") and will only be available in this block. This sub-keyword is required .
*database: request
	*what: email
	*success
		>> emailAddress = it["email"]
	*error
		>> errorReason = it["reason"]
		>> errorMessage = it["message"]

*email πŸ”—

Sends an email immediately or at a specified time.

Sub-keywords:

  • *subject: subjectLine = Defines the subject of the email. This sub-keyword is required .
  • *body = Defines the body of the email. This sub-keyword is required .
  • *to: emailAddress = Defines an address to which the email should be sent. If this keyword is omitted, then the email will be sent to the logged-in user or merely ignored if no user is logged in. This sub-keyword is optional .
  • *when: datetime = Defines when the email should be sent. For recurring emails, it defines when the first email should be sent. Note that the specified datetime must be in the future (i.e., it can't be in the past, of course, but it also can't be calendar::now ). This sub-keyword is optional .
  • *every: duration = Defines how often recurring emails should be sent. Note that emails cannot be sent more frequently than once per day. This sub-keyword is optional .
  • *until: datetime = Defines when recurring emails should stop. This sub-keyword is optional .
  • *identifier: someName = Defines a name by which a group of recurring emails should be known, which is useful for cancelling upcoming recurring emails with *cancel: someName . This sub-keyword is optional .
  • *cancel: someName = Cancels a scheduled email with a given identifier. This sub-keyword is optional .
>> wantsToSubscribe = "no"
>> wantsToUnsubscribe = "no"

*question: Would you like to modify your subscription?
	Yes, I want to subscribe to daily reminders to brush my teeth!
		>> wantsToSubscribe = "yes"
	Yes, I want to unsubscribe from the daily reminders to brush my teeth.
		>> wantsToUnsubscribe = "yes"
	No, I want to leave.

*if: wantsToSubscribe = "yes"
	*login
		*required: yes

	*email
		*identifier: toothBrushingEmailSubscription
		*when: calendar::now + 3.seconds
		*every: 1.days
		*until: calendar::now + 1.year
		*subject: Brush your teeth! 🦷 πŸͺ₯ ✨ 😁
		*body
			Don't forget to brush your teeth today!

	You've been subscribed!
	*button: Okay

*if: wantsToUnsubscribe = "yes"
	*login
		*required: yes

	*email
		*identifier: toothBrushingEmailSubscription
		*cancel

	You've been unsubscribed!
	*button: Okay

Note that the unsubscription block in the example above is a little redundant because users can actually unsubscribe at any time by clicking the unsubscribe link that's automatically included in the email itself. But we included it to show how you, the developer, might cancel recurring emails on behalf of a user.


*events πŸ”—

Defines named events that can be executed using *trigger or jQuery's trigger function .

Data sent to events is temporarily stored in a variable called it that is only available inside the event block.

*goto keywords used inside an event block must always include a *reset indented beneath. There's a subtle point here about where the program execution goes when an event is triggered. Take, for example, the functionality of a standard *goto / *label combo: when calling *goto , the program moves to the relevant label and continues (in order) from there. So, if we call *goto: myLabel , and if *label: myLabel appears on line 113, then execution would move to line 113 and continue in order from there. We mention this example because events are sort of like *goto in the way that they move the program execution. If we call *trigger: someEvent , and if "someEvent" is defined on line 2, then execution would move to line 2 and continue in order from there (though skipping over other events). When the *events block is finally exited, execution still continues in order β€” meaning that the program basically starts over (because it moves to the next line of code after the *events block)! This is usually an undesired behavior, so it's very common to use *goto inside of an event block to prevent the program from starting over.

To illustrate this point, consider this example:

*events
	myCoolEvent
		>> x = 5

*trigger: myCoolEvent
Here's the value of x: {x}
*button: Okay

This program is actually badly designed and would cause the browser to freeze and/or crash (much like an out-of-control *while loop) because the *trigger: myCoolEvent on line 5 causes execution to move up to line 2 (where "myCoolEvent" is defined); and then, when "myCoolEvent" has finished running, execution continues from line 4; and since there's nothing on line 4, it progresses to line 5, where it once again triggers "myCoolEvent"; and so on forever.

So, a common pattern to avoid this behavior involves two steps:

  1. Trigger an event and then wait indefinitely.
  2. In the event definition, specify a label to which the program execution should move once the event block has finished running.

Here's how the above example would look after this pattern has been implemented:

*events
	myCoolEvent
		>> x = 5
		*goto: afterMyCoolEventLabel
			*reset

*trigger: myCoolEvent
*wait

*label: afterMyCoolEventLabel
Here's the value of x: {x}
*button: Okay

In embedded programs , events can be triggered with jQuery, like this:

const dataToSend = { name: "John" }
$(window).trigger("myCoolEvent", dataToSend)

In the above example, the data defined in dataToSend would be available in the "myCoolEvent" block as the it variable. For example:

*events
	myCoolEvent
		>> name = it["name"]

*experiment: experimentName πŸ”—

Defines an experiment by name and assigns a user (permanently) to an experiment group.

*experiment is similar to *randomize but differs in two ways:

  1. An experiment guarantees that an equal number of users will encounter each block of code.
  2. An *everytime keyword cannot be applied to experiments because it's expected that a user will be permanently assigned to a particular experiment group.

Optionally, a name can be applied to each group using *group: groupName . We highly recommend using this option since it makes it easier to analyze individual groups later.

*experiment: Sleep Experiment
	*group: Control
		Before bedtime each night, do nothing at all.

	*group: Intervention
		Before bedtime each night, meditate for at least 10 minutes.

*for: indexOrKey, value in variable πŸ”—

Loops through the elements of a collection, association, or string (text) variable.

indexOrKey can be ommited in all three cases, as you can see in the example below where we just retrieve the values of the elements in favRestaurants :

>> favRestaurants = ["Cracker Barrel", "Olive Garden", "Panera Bread", "Just Salad"]

*for: restaurant in favRestaurants
	*question: What is your favorite food at {restaurant}?

Iterate over a collection:

>> names = ["Alice", "Bob", "Charlie"]

*for: index, name in names
	{name}, you're #{index}.

Iterate over an association:

Here are some important female scientists in history:

>> femaleScientists = {"Physics" -> ["Marie Curie", "Maria Goeppert Mayer"], "Computing" -> ["Ada Lovelace"], "Biology" -> ["Rachel Carson", "Rita Levi-Montalcini", "Barbara McClintock"], "Chemistry" -> ["Gertrude Elion", "Rosalind Franklin"], "Medicine" -> ["Elizabeth Blackwell", "Jane Cooke Wright"], "Astronomy" -> ["Vera Rubin"]}

*for: field, scientists in femaleScientists
	*{field}*

	*for: scientist in scientists
		{scientist}

*button: Wow!

Iterate over a text variable:

*page
	>> acronym = "CIA"

	In the acronym "{acronym}"...

	*for: character in acronym
		*question: The "{character}" stands for:

*goto: labelName πŸ”—

Instructs the program to return to a specific label.

Sub-keywords:

  • *reset = Resets the navigation stack in places where navigation potentially becomes especially convoluted. Also, note that a *reset keyword is required in *goto statements that appear under *events . This sub-keyword is optional .
*events
	someEvent
		>> result = it["result"]
		*goto: someLabel
			*reset

*group πŸ”—

Defines a block of code to run, usually used in the context of *randomize or *experiment . Optionally, a name can be applied to the group using: *group: groupName .

The following items will be displayed in a random order:

*randomize: all
	*group
		This is item 1.
	*group
		This is item 2.
	*group
		This is item 3.

*button: By golly, you're right!

See the *randomize and *experiment keywords for examples of using *group .


*html πŸ”—

Inserts arbitrary HTML code into the page.

Virtually any HTML can be injected into the page except <script> tags. The HTML content to be injected must be indented beneath *html .

*html
	<table align="center">
		<thead>
			<tr><th>Name</th><th>Age</th></tr>
		</thead>
		<tbody>
			<tr><td>Sue</td><td>29</td></tr>
		</tbody>
	</table>

Because GuidedTrack uses Bootstrap CSS (with its own styles applied on top), it's possible to apply Bootstrap classes to elements in the *html block. It's also possible to style your own HTML using <style> tags. For example:

*html
	<style>
		.custom-warning-class {
			background-color: orange;
			color: white;
			border-radius: 4px;
			box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.1);
			padding: 1em;
		}
	</style>

	<div class="alert alert-warning">
		This element uses built-in Bootstrap classes.
	</div>

	<div class="custom-warning-class">
		This element is styled with custom CSS.
	</div>

*if: condition πŸ”—

Runs a block of code if condition is a true statement.

*if: age < 18
	You're not eligible to take this survey!
	*quit

*image: imageUrl πŸ”—

Inserts an image into the page.

Sub-keywords:

  • *caption: textToDisplay = Defines a description to be displayed beneath the image. This sub-keyword is optional .
Here's a picture of a cat:

*image: https://images.unsplash.com/photo-1511044568932-338cba0ad803
	*caption: What a cute cat!

*label: labelName πŸ”—

Declares a place in the code to which the program can return at any time.

*label: startLabel

*question: Try to guess the number I'm thinking of:
	7
		Nope! Try again!
		*goto: startLabel
	13
		Nope! Try again!
		*goto: startLabel
	42
		Correct!

*list πŸ”—

Inserts a list into the page. By default, lists are unordered. Use *list: ordered for an ordered list. Use *list: expandable for a list of collapsible / expandable boxes.

Here's an unordered list:

*list
	Lions
	Tigers
	Bears

Here's an ordered list:

*list: ordered
	Lions
	Tigers
	Bears

Here's a list of expandable boxes:

*list: expandable
	Lions
		Lions are scary!
	Tigers
		Tigers are stripey!
	Bears
		Bears are cool.

The above example is rendered like this (though the boxes will be collapsed by default):

*list (1)


*login πŸ”—

Asks the user to log in using a GuidedTrack account. If they don't have an account already, they'll be prompted to create one.

Sub-keywords:

  • *required: yesOrNo = Indicates whether or not users will be required to log in. This sub-keyword is optional .
*login
	*required: yes

*maintain πŸ”—

Keeps text in a gray box at the top of the page across multiple pages (until a *clear ).

*maintain: Please answer honestly!

*question: Have you ever stolen anything?
	Yes
	No

*question: Have you ever told a lie?
	Yes
	No

*clear

Creates a navigation bar.

Items to be listed in the navbar must be indented beneath *navigation . Under each navbar item, indent code that should be executed when a user clicks on that navbar item.

Optionally, each navbar item can have a Font Awesome *icon: fontawesome-icon-class .

*navigation
	Home
		*icon: fa-home
		*goto: homeLabel
	Quiz
		*icon: fa-question-circle
		*goto: quizLabel
	Settings
		*icon: fa-cog
		*goto: settingsLabel

*page πŸ”—

Creates a page of content.

Everything indented under *page will all be included together on a single "page" of content. Note, though, that there are some subtleties around what's allowed beneath a *page . For example, a common way of asking questions is to write code that should execute depending on the answer a users gives. For example:

*question: What's your favorite kind of ice cream?
	chocolate
		>> isCool = "yes"
	vanilla
		>> isCool = "no"
	strawberry
		>> isCool = "unsure"

A *page gives the ability to include multiple questions on a single page. But it's not possible to write questions like the one above in a *page because a person must click a "Next" button in order to leave the page, and the above question includes code designed to run when a particular answer is clicked. To be successfully included in a *page with other content, the above question can be refactored using a pattern like this:

*page
	-- Pretend that there's other stuff on this page, please.
	-- Then:

	*question: What's your favorite kind of ice cream?
		*save: favoriteIceCream
		chocolate
		vanilla
		strawberry

*if: favoriteIceCream = "chocolate"
	>> isCool = "yes"

*if: favoriteIceCream = "vanilla"
	>> isCool = "no"

*if: favoriteIceCream = "strawber"
	>> isCool = "unsure"

*points: numberOfPoints πŸ”—

Gives or takes away points from users' scores. Optionally, a tag can be included as a way of grouping related points, like: *points: numberOfPoints tagName .

*question: What's 2 + 2?
	3
		*points: -1 mathPoints
	4
		*points: 1 mathPoints
	5
		*points: -1 mathPoints
	I don't know
		*points: 0 mathPoints

*points: 100 forNoReasonPoints

You have {mathPoints} math points and {forNoReasonPoints} for-no-reason points!
*button: Okay

*program: programName πŸ”—

Seamlessly includes another GT program in the current program.

We tend to refer to these included programs as "subprograms" because they're included inside of an "outer" or "parent" or "main" program.

The *program keyword causes a specified subprogram to run immediately, resuming execution of the main program as soon as the subprogram finishes. Use this keyword to run code that you want to run temporarily (and that will necessarily complete) before you continue on, but not when you want to permanently switch to something else.

Note that *program is similar to *switch , but there's at least one significant difference between the two: *switch causes another specified subprogram to run immediately and permanently pauses the current program (unless another *switch is used in the future to resume it). Use *switch to switch to running some other portion of code that you may or may not ever come back from, or when you want to pause progress with one thing as you switch to do another thing and want to force the target subprogram to make an explicit choice about where to switch to next (instead of merely completing and returning automatically to a parent program).

-- https://www.guidedtrack.com/programs/11336
*program: 5 Factors of Happiness

*progress: percent πŸ”—

Displays a progress bar filled percent %.

*progress: 50%
*progress: {someVariable}%

*purchase πŸ”—

Allows you to process in-app purchases in your program.

Sub-keywords:

  • *status = Used for checking if a user has a subscription and its status. This sub-keyword is required if *frequency and *management are not used .
  • *frequency = Used to generate a subscription. This sub-keyword is required if *status and *management are not used .
  • *management = Used to open a new window or tab where the user can manage their subscription. This sub-keyword is required if *status and *frequency are not used .
  • *success = Defines code to run if the request succeeds. Any data returned from the request will exist in a variable called it and will only be available in this block. This sub-keyword is required if *status or *frequency are used .
  • *error = Defines code to run if the request fails. Error information will exist in a variable called it (with properties "reason" and "message") and will only be available in this block. This sub-keyword is required if *status or *frequency are used .

See the In-app purchases section of the manual for instructions on how to configure your program to process subscriptions.

You must use exactly one of these sub-keywords: *status , *frequency , or *management . If the *status or *frequency keywords are used, then *success and *error keywords must also be used.

Sample code to check if a user has a subscription:

>> purchaseStatusError = 0

*purchase
	*status
	*success
		>> subscriptionPaid = it["paid"]
		>> subscriptionOngoing = it["ongoing"]
		>> subscriptionExpiration = it["expiration"]
	*error
		>> purchaseStatusError = 1
		>> purchaseStatusErrorText = it["message"]

Sample code for subscribing a new user:

*purchase: trialPlan
	*frequency: recurring
	*success
		>> userSubscribed = 1
	*error
		>> userSubscribed = 0

Sample code to allow users to manage their subscriptions:

*purchase
	*management

This code will attempt to open a new tab or window for the user to manage their subscription. Some browsers may keep your program from automatically opening a new page. This happens because most modern browsers block events (like opening new tabs or auto-playing videos) that are not initiated by a user via a mouse or keyboard event. So, to help ensure that the browser allows a new window or tab to open, we recommend placing the above code in a clickable *component , like this:

*component
	Click here to manage your subscription.
	*click
		*purchase
			*management

*question: questionText πŸ”—

Asks a question.

Sub-keywords:

  • *type: typeOfQuestion = Determines the type of question. Valid values are "choice", "checkbox", "text", "number", "paragraph", "slider", and "calendar". This sub-keyword is optional .
  • *shuffle = Randomizes the order of answer options in a multiple-choice or checkbox question. This sub-keyword is optional .
  • *save: variableName = saves the response to a variable This sub-keyword is optional .
  • *tip: textToDisplay = Inserts smaller text under the question. Usually, this is used for providing hints or extra help to users. This sub-keyword is optional .
  • *confirm = Requires users to click a "Next" button after selecting their answers (as opposed to automatically progressing as soon as they select an option) in multiple-choice questions. This sub-keyword is optional .
  • *throwaway = Prevents the question and responses from being stored in the CSV. This sub-keyword is optional .
  • *countdown: duration = Requires that an answer be given in a certain amount of time (e.g., *countdown: 1.minute + 30.seconds ). This sub-keyword is optional .
  • *tags: tagList = Enables grouping related questions. Multiple tags can be separated by commas. All responses to questions with a given tag can be summarized with *summary: tagName . This sub-keyword is optional .
  • *answers: myAnswerList = Injects multiple-choice answer options using a collection variable (e.g., *answers: myAnswerOptions ). Note that this collection can be two-dimensional (i.e., a collection of collections) as a way of defining both a text to display and a value to be recorded (e.g., [['Agree', 1], ['Neither agree nor disagree', 0], ['Disagree', -1]]). This sub-keyword is optional .
  • *blank = Allows users to continue without answering the question. This sub-keyword is optional .
  • *multiple = Allows users to enter multiple answers to text questions. This sub-keyword is optional .
  • *default: myDefaultAnswers = Pre-enters or pre-selects default answers. The variable myDefaultAnswers should match the type of question. For number questions, it should be a number; for text questions, it should be a text value; for multiple-choice or checkbox questions, it should be a collection of values; etc. This sub-keyword is optional .
  • *before: textToDisplay = Puts text to the left of the answer box in text or number questions (e.g., *before: $ puts a dollar sign to the left of the text box). This sub-keyword is optional .
  • *after: textToDisplay = Puts text to the right of the answer box in text or number questions (e.g., *after: donuts per month puts "donuts per month" to the right of the text box). This sub-keyword is optional .
  • *min: minValue = Sets a minimum value for a continuous range of values in slider questions. This sub-keyword is optional .
  • *max: maxValue = Sets a maximum value for a continuous range of values in slider questions. This sub-keyword is optional .
  • *time: yesOrNo = Determines whether or not users can specify a time in calendar questions. This sub-keyword is optional .
  • *date: yesOrNo = Determines whether or not users can specify a date in calendar questions. This sub-keyword is optional .
  • *placeholder: placeholderText = Inserts placeholder text in the field of a text or paragraph question. The text is not selectable and will disappear when the user starts typing in the field. This sub-keyword is optional .

Normally, *question will create a single page, and the only thing on that page will be the question. However, it's possible to put multiple questions on a page by indenting them under a *page keyword.

In multiple-choice questions, note that answer options listed manually (i.e., not using *answers ) may optionally include a Font Awesome *icon: fontawesome-icon-class or an *image: imageUrl .

If no sub-keywords are used at all, then the question will default to a text question.


*quit πŸ”—

Ends the entire program (including all subprograms) immediately.

*if: age < 18
	You're not eligible to take this survey!
	*quit

*randomize πŸ”—

Randomly selects blocks of code to run. A number of items to select and randomize can be specified with *randomize: someNumber or *randomize: all .

Sub-keywords:

  • *everytime = Re-randomizes the selection every time a user passes the *randomize . By default, users will receive the exact same items each time they pass the same *randomize unless an *everytime keyword is indented beneath *randomize . This sub-keyword is optional .
  • *name: someName = Defines a name for the randomized selection. This sub-keyword is optional .

By default, only a single block is randomly chosen from among the blocks indented beneath *randomize . However, a number of items to be randomly selected can optionally be specified, like: *randomize: 7 . Or, if all items should be selected (in a random order), use: *randomize: all .

Also note that while it's likely that blocks will be selected a roughly equal number of times, it's not guaranteed . If you need a guarantee that each block will be seen by an equal number of users, we recommend using *experiment .

*label: startLabel
*Here are random three idioms related to cats:*

*randomize: 3
	*name: catIdioms
	*everytime
	Cat got your tongue?
	Like a cat on a hot tin roof
	Curiosity killed the cat
	Like herding cats
	Look what the cat dragged in.
	It's raining cats and dogs.
	The cat's pajamas
	Let the cat out of the bag
	When the cat's away, the mice will play.
	Fat cat

*button: Get three more!
*goto: startLabel

In the above example, each "block" of code is actually just a single line of text. But sometimes it's useful to randomize entire blocks of code. In such cases, we recommend using *group , like this:

*randomize
	*group
		You're in the Pandas group!
		>> group = "pandas"

	*group
		You're in the Dolphins group!
		>> group = "dolphins"

	*group
		You're in the Kittens group!
		>> group = "kittens"

*repeat: numberOfTimes πŸ”—

Repeats a block of code numberOfTimes times.

*repeat: 3
	For he's a jolly good fellow,

Which nobody can deny!

*return πŸ”—

Ends the current subprogram and returns to the main / parent / outer program.

*if: age < 18
	You're not eligible to take this part of the survey! However, you can still take the other parts of it.
	*return

*service: serviceName πŸ”—

Makes an HTTP request.

Sub-keywords:

  • *path: /some/path = Defines the path to be appended to the URL defined in the service settings (e.g., if the service URL is https://example.com and the path is /hello/world, then the service request will be sent to https://example.com/hello/world). This sub-keyword is required .
  • *method: methodType = Defines which HTTP method to use. Valid options are GET, POST, PUT, and DELETE. This sub-keyword is required .
  • *send: associationToSend = Sends an association along in the request. This sub-keyword is optional .
  • *success = Defines code to run if the request succeeds. Any data returned from the request will exist in a variable called it and will only be available in this block. This sub-keyword is required .
  • *error = Defines code to run if the request fails. Any error information will exist in a variable called it and will only be available in this block. This sub-keyword is required .

Once a service has been defined in the "Services" settings for a program, that service can be invoked by name via the *service keyword.

Unlike *trigger , *service calls are synchronous , meaning that the program will wait for the request's success or failure before continuing to subsequent parts of the program.

This example shows how to use the Free Dictionary API in a service call:

*label: startLabel

*question: Word:
	*save: word
	*type: text

>> response = "No results."
>> error = ""

*service: Free Dictionary API
	*path: /{word}
	*method: GET
	*success
		>> response = it[1]["meanings"][1]["definitions"][1]["definition"]
	*error
		>> error = it

*if: error.size > 0
	*ERROR* = {error}

*if: error.size = 0
	*{word}* = {response}

*button: Start over
*goto: startLabel

*set: variableName πŸ”—

Sets a variable's value to true .

*question: Have you ever cheated on a school assignment?
	Yes
		*set: isACheater
	No

*if: isACheater
	Cheater!

*settings πŸ”—

Applies certain settings to the program.

Sub-keywords:

  • *back: yesOrNo = Gives users the ability to navigate backward through a program by clicking a "back" arrow. This sub-keyword is optional .
  • *menu: yesOrNo = Hides the run menu from the top left of the screen. This sub-keyword is optional .
*settings
	*back: yes
	*menu: no

*share πŸ”—

Inserts a button that enables users to share the program on Facebook.

*share

*summary πŸ”—

Summarizes user responses. Optionally, a tag can be specified with *summary: tagName .

-- Summarize all responses:
*summary

-- Summarize only responses to personality questions:
*summary: personality

*switch: programName πŸ”—

Switches to another program.

Sub-keywords:

  • *reset = Indicates that the target program should be restarted from the beginning (instead of being resumed from its last state). This sub-keyword is optional .

See the *program section for more about the differences between *switch and *program .

*question: Which lesson would you like to view (from the beginning)?
	Lesson 1
		*switch: Lesson 1
			*reset
	Lesson 2
		*switch: Lesson 2
			*reset
	Lesson 3
		*switch: Lesson 3
			*reset

*trigger: eventName πŸ”—

Triggers an event by name.

Sub-keywords:

  • *send: associationToSend = Defines data to be sent to the event listener. This sub-keyword is optional .

Events can either be defined in *events at the beginning of the program or in JavaScript code in embedded GT programs. Triggered events run asynchronously, meaning that calling *trigger will initiate the relevant event, but the program will not wait for the event to finish its execution; instead, the program will continue right on to the next line of code after triggering the event. It's important to keep this in mind if subsequent parts of your program rely on the result of an event's execution.

Here's an example of triggering an event that has been defined within the program itself (under *events ):

*events
	incrementMyNumber
		>> myNumber = myNumber + 1
		*goto: startLabel
			*reset

>> myNumber = 0

*label: startLabel
Currently, your number is: {myNumber}.
*button: Increment it, please!
*trigger: incrementMyNumber
*wait

(That final *wait is included because otherwise the program would end! Since triggered events run asynchronously, the program would keep going after *trigger: incrementMyNumber ...but since there's nothing after it, the program would end.)

And here's an example of triggering an event in an embedded program where the relevant event is defined in the embedded page itself.

*trigger: showAnAlert
	*send: {"message" -> "Danger, Will Robinson!"}

The "showAnAlert" event would be defined somewhere in the JavaScript code in the embedding page. Read more about defining JS events here .


*video: youtubeUrl πŸ”—

Embeds a YouTube video into the page.

Here's a video of Vi Hart talking about the dragon curve fractal:
*video: https://www.youtube.com/watch?v=EdyociU35u8

*wait πŸ”—

Causes the program to wait forever ( *wait ), for a certain amount of time ( *wait: duration ), or until data has been sent to the GT server(s) ( *wait: data ).

Take 30 seconds now to ponder the meaning of life.

*wait: 30.seconds

*question: Would you like to know what /we/ think is the meaning of life?
	Yes
		*wait: data
		*goto: https://en.wikipedia.org/wiki/42_(number)#The_Hitchhiker's_Guide_to_the_Galaxy
	No

*while: condition πŸ”—

Runs a block of code as long as condition is a true statement.

Be aware that failing to make the condition eventually become false can cause the browser to freeze up and/or crash! For example, the condition in the following *while loop will always be true, so the loop will never exit, so the browser will freeze up and/or crash.

*while: 0 < 1
	All work and no play makes Jack a dull boy.