Custom blocks and title designs - Atavist/developer Wiki

When creating a project with Atavist, there are all sorts of things you can insert into your project: an image, a video, a Google map, an embedded SoundCloud audio player, and more. These are blocks.

You also have an array of title treatments to choose from when assembling a project and the sections within it, ranging from the simple to the dramatic. These are title designs.

This guide describes how to make your own.

What is a block?

A block is a bundle of stuff that lives within, and is supplementary to, the text content of an Atavist project. A block can be simple, like an image or a video, or complex, like an interactive chart.

When developing blocks, you’ll be developing block types, which can be reused and separately configured within and across projects. A custom image block can be created once and used many times, with many different images, as can a custom quotation block with many different quotations.

A block will exist in two environments: in the Atavist editor and in published Atavist projects. In both cases, under the hood, the block is the same: the same HTML, the same CSS, and the same JavaScript. Blocks don’t look and work exactly the same in these two environments, though. Any configurable properties of a block are adjustable in the editor environment but locked down in published projects. And because blocks inherit some styles from the environment around them — fonts, colors, sizing, and spacing — a given block will look different in the Atavist editor and in a published project.

Blocks are web components, a cutting-edge web technology that allows for the creation of self-contained, reusable elements. Blocks use Google’s Polymer library, which sits atop the web components spec and makes working with custom elements easy. Creating a custom block doesn’t require deep knowledge of Polymer, though — just some basics.

What is a title design?

It’s a layout, appearing at the top of a project or section, whose primary function is to display a title, subtitle, and byline. Title designs can be understated, including only bare essentials, or eye-catching, consisting of images, graphic elements, and animations.

Title designs are built in the same way blocks are, making use of nearly all the same fundamental concepts and components. To cut down on verbiage, this developer guide uses the term “blocks” exclusively, but “blocks and title designs” is almost always intended. Where meaningful distinctions exist, they will be identified.

See Developing title designs for information specific to custom title design creation.

Getting started

To create a custom block, from the My Projects page, click “Develop.” Then, from the left sidebar, click “Block.” Then click “New Block.” (For a new title design, of course, select “Title Design” from the sidebar, and then “New Title Design.”)

When you create a brand new block, you’ll see that we’ve included some sample code to get you started. In fact, it’s a working block. If you click “Save,” open up a new Atavist project, and insert the newly minted block into your project, you’ll see a custom block, containing a simple image element, staring back at you. (The starter title design includes project data — title, subtitle, and byline — as well as an image.) When developing a block of your own, you may find yourself using a similar workflow: making a change to your custom block, saving it, examining your custom block in the Atavist editor, and then previewing your test project to see the block in a reader-facing environment.

A custom block has these constituent parts:

Let’s take them one at a time.

Markup — HTML

The HTML field is straightforward. It contains markup for any elements you’d like to include inside your block. (It should not include markup for the top-level block element itself; we take care of that.) Anything that’s valid HTML is valid here.

You can also include what we call subcomponents, convenience elements we’ve created to streamline the block development process. These include:

For more information about subcomponents and how to use them, see Subcomponents.

Custom element registration — JavaScript

Blocks are web components. In order to create blocks as web components, Atavist uses Google’s Polymer library, which requires that custom elements be defined a certain way via JavaScript. That happens here. This means that, though it sits alongside the HTML and Sass fields, the JavaScript field isn’t merely “the JavaScript field”: it’s where the custom element is registered, which is necessary for it to work. This field can’t be blank.

(If you’re interested in the Polymer library and would like a deeper understanding of custom element registration, it’s worth reading Polymer’s docs, particularly the sections on registration and lifecycle and declared properties.)

Let’s look at a basic example of custom element registration:

Polymer({
	properties: {
		text_color: String,
		value: '#ff0000',
		observer: 'text_colorChanged'
	},
	text_colorChanged: function(val) {
		this.querySelector('p').style.color = val;
	},
	created: function() {
		// `created` lifecycle callback
	},
	ready: function() {
		// 	`ready` lifecycle callback
	},
	attached: function() {
		// `attached` lifecycle callback
	}
});

There are, essentially, two things going on here:

  1. Block initialization logic, as handled by the lifecycle callbacks created, ready, and attached
  2. Declared property management, as handled by the properties object and any associated observer callbacks (text_colorChanged, in the above example)

Block initialization

Before becoming fully functional, a block goes through a series of lifecycle events. It works like this:

Opportunities to perform block initialization logic exist at the created, ready, and attached stages. In general, ready is the most useful callback; it executes earlier than attached, and, unlike created, it allows you to access any of the block’s child elements. Depending on your block’s design, though, each can be useful. All lifecycle callbacks are optional.

Atavist maintains client-side APIs your block can use. At present, there are two: the Library API and the In View API, both useful in block initialization logic. For more information, see Atavist APIs.

Declared properties

A block can declare one or more public properties, defined in the properties object as part of the block’s registration. Public properties essentially define a block’s API, indicating which aspects of the block exist to be edited by users. Properties can also come with observers, or functions that are executed whenever the property changes. If my text_color property changes from red to green, it’s the observer function that’s responsible for updating the block’s appearance.

A map block’s properties object might look like this:

properties: {
	marker_color: {
		type: String,
		value: '#ff0000',
		observer: 'marker_colorChanged'
	},
	points: {
		type: Array,
		value: function() { return []; },
		observer: 'pointsChanged'
	}
}

From this properties object, one can infer the block’s API: the user can add points to the map and can adjust the colors of the map markers.

Individual properties require a type, and may also have a value and observer:

When a property changes and its observer method is called, the new and old value of the property are passed in as arguments:

text_colorChanged: function(newValue, oldValue) {
	this.querySelector('p').style.color = newValue;
	console.log('The old value was', oldValue);
}

From within any of a block’s methods — lifecycle events handlers, property observer callbacks, and any other methods you define on the block’s prototype — you can access the value of a property with this.property_name.

Other properties

When creating a new custom element, you can define values for a handful of Atavist-specific properties. These are defined on the prototype object, alongside the properties object and lifecycle callbacks, like this:

Polymer({
	alignments: ['center', 'left', 'right'],
	properties: {
		marker_color: {
			type: String,
			value: '#ff0000',
			observer: 'marker_colorChanged'
		}
	},
	…
});

Styles — Sass

The Sass field determines your block’s styles. Style rules defined in this field are namespaced. If your block includes a paragraph element, for example, you can safely define a style rule like so, without fear of contaminating the Atavist editor or published project environments in which the block will exist:

p {
	color: #ff0000;
}

This namespacing is achieved via Sass. When a CSS stylesheet for the block is compiled, the contents of the Sass field will be nested within a block-specific selector, like this:

.custom-element-12345 {

	/* contents of Sass field begin here */

	p {
		color: #ff000;
	}

	/* contents of Sass field end here */

}

A few things to keep in mind when writing styles in the Sass field:

Editable field definition — JSON

After inserting a block into a project in the Atavist editor, you can click its “Edit” button to reveal an editing sidebar. Here you’ll find fields you can manipulate to change the block. If it’s an image block, you’ll see an image uploader. If it’s a Giphy block, you’ll see a field where you can type in a search term. The fields you see are defined by the block via JSON. Each field corresponds one-to-one with a public property, defined in the block’s properties object (see Declared Properties in Custom element registration, above). When a user changes the value of a field — e.g., by uploading an image or by entering a new search term — the corresponding property on the block changes. (If an observer method is associated with the property, that method is called. This chain of events accounts for a user’s impression that edits made in the left sidebar are reflected instantly in the block.)

If your block has a title_text property (a string of text) and a text_color property (a color value), your fields definition JSON might look like this:

{
	"title_text": {
		"type": "text",
		"label": "Title Text",
		"placeholder": "Write a title..."
	},
	"text_color": {
		"type": "colorpicker",
		"label": "Text color",
		"default": "#ff0000"
	}
}

The keys used here — title_text and text_color — match exactly the respective property names of the block. Within each field definition, the type property defines what type of form element is displayed in the block editing sidebar and label property defines what helper label appears below the field. Beyond that, each field type supports its own properties.

To learn what field types are supported and how to use them, see Field types.

Most field types support a default property, which sets the field’s value if none is present. Because fields correspond with block properties, and because block properties set their own defaults, default isn’t of much use. As soon as a user clicks a block’s “Edit” button and the sidebar appears, the fields present will pull in values from the block’s various properties. If a user hasn’t touched a particular property, the block’s default, not the form element’s default, will take effect.

Field definition validation

The field definition JSON must be valid. To check if yours is, click the “Is Your JSON valid?” button. To see a preview of what your fields will look like, click the “Preview Fields” button, and a modal with fields as you’ve defined them will appear.

Images and other assets

It’s possible to include images and other assets in your block by uploading them in the “Images” or “Other assets” areas of the block editor. These assets, unlike any user-uploaded assets associated with something like the atavist-image subcomponent, will be the same for each instantiation of the block. You can then reference paths to the assets from within the HTML or Sass fields.

When uploading an asset, specify a unique key. Then, in your HTML or Sass, use {{= =}} tags to reference the path to the asset. If your unique key is logo_svg, you’d reference its path like this: {{= logo_svg =}}.

When referencing an uploaded image file, you can reference the raw asset in the same way — {{= my_image =}}or you could use Atavist’s image-processing service to generate a cropped or scaled asset. An image cropped to 500 x 500 pixels, for example, would look like this:

{{= mykey_crop~500x500 =}}

Cropped and scaled images can be produced only from assets uploaded to the “Images” area. To learn more about cropping and scaling assets, see Images Derivatives.

Developing title designs

Title design development employs nearly all the same concepts as block development, from the way markup is written, custom element registration is handled, and fields are defined. Title design development differs from the development of blocks, though, in two important ways:

  1. Project and section data binding. Title designs can use the atavist-story-data subcomponent to bind text that appears on title designs to project data: a project’s title, subtitle, byline, or description. Likewise, images can use the data_key='title_page' attribute to indicate an image is to be used to represent a project in certain contexts, like the My Projects page. To learn more, see atavist-story-data and Binding to project and section data with data_key.
  2. Defining certain styles. A title design appears within two environments: in a published reader project, and in the title designer previewer within the Atavist editor. Some additional work must be done to make title designs take stylistic cues from a user’s chosen theme, as well as to ensure a title design looks right when previewed in the Atavist editor. See Defining title design styles below.

Defining title design styles

There are two things to keep in mind when defining styles for a title design. First, certain accommodations need to be made for the title design to be rendered correctly in the title design previewer within the Atavist editor. Second, any title design styles that should be inherited from or defined by a user’s chosen theme should be implemented not with hardcoded Sass style rules, but with helper classes set on elements in the title design’s markup.

Supporting the title design previewer

The Atavist editor’s title design previewer, which renders a scaled-down but accurate depiction of a user’s chosen title design, isn’t an iframe as you might expect. It’s a regular <div>, drawn at roughly the size of a viewport, and shrunk down with transform: scale(). That means viewport-specific style rules won’t be properly applied. This has a few common implications:

Title design helper classes

Title designs are meant to work across user-defined themes, inheriting typographical styles, colors, spacing, and alignment values from the projects in which they’re deployed. That means, in certain cases, that style properties shouldn’t be hardcoded as a style rule defined in a title design’s Sass. A font-family specified there, for example, might clash with the type styles of a project’s theme.

Instead, a helper class can be applied to the appropriate element. Helper classes exist for a variety of styles: text color, background color, heading sizes, text column width and alignment, and more.

To use a helper class, apply it within your title design’s markup. Making an h1 element’s color match the color of body text within a project, for example, might look like this:

<h1 class="atavist-cover-text-color">
	<atavist-story-data key="the_title" data_key="title" placeholder="Write a title…"></atavist-story-data>
</h1>

For a full list of available helper classes, see Title design helper classes reference.

Subcomponents

Subcomponents are Atavist-created Polymer elements designed to make working with media types and story data easier. To use a subcomponent, include it in your block’s markup, like this:

<atavist-image key='primary_image' use_as_background='true'></atavist-image>

Or like this:

<p is='atavist-caption' key='the_caption'></p>

Some subcomponents, like atavist-image above, are custom elements with their own tag names. Others, like atavist-caption above, are custom elements that extend existing elements, like the humble <p>. Custom elements that extend existing elements are declared in markup using the is= syntax.

Each type has a set of public properties, which serve as an API. These properties can be defined in your block’s markup as attributes, as above, or they can be defined on the fly using JavaScript.

Key attributes and notify: true

Most of these elements require a key attribute be set. This key, used as an identifier by the block, ensures that data related to the subcomponent persists properly. When a key is required, it must match the name of a property of the block.

Furthermore, if the subcomponent supports editing, from within the Atavist editor, on the element itself, the corresponding property must define a special property of its own: notify: true. This applies, for example, to atavist-caption, whose text can be edited live by users from within the editor, and atavist-image, onto which a user can drag and drop an image file to be uploaded. This property ensures the Atavist editor environment is notified when a block’s data changes.

Take the example of a caption, which might be defined from within a block’s markup like this:

<p is='atavist-caption' key='the_caption'></p>

Within the block’s custom element registration, that caption must have a corresponding property, including a notify: true key-value pair, like this:

properties: {
	the_caption: {
		type: String,
		notify: true
	}
}

Employing an atavist-image element is similar. The markup might look like this:

<atavist-image key='primary_image' use_as_background='true'></atavist-image>

And a corresponding property, defined within the block’s custom element registration, might look like this:

properties: {
	primary_image: {
		type: Object,
		notify: true
	}
}

An image will typically have an associated file uploader within its editing sidebar. Defining that uploader field might look like this (notice the use, again, of the primary_image key):

"primary_image": {
	"label": "Image",
	"type": "file",
	"file_type": "image",
	"focalpoint": "true"
} 

Binding to project and section data with data_key

A subset of subcomponents — atavist-story-data, atavist-image, and atavist-multi-background — can be bound to project- or section-level data. The special atavist-story-data subcomponent can be bound to the following data:

For these text-based pieces of data, the binding works two ways. If a user sets a project or section title and then opens the title design editor, any title designs with elements that bind to title will display the user-defined title. If the user then edits the title from within the title design editor, the binding works the other way, redefining the title saved at the project or section level.

Binding an h1 element to a project’s title might look like this in markup:

<h1>
	<atavist-story-data key="the_title" data_key="title"></atavist-story-data>
</h1>

Note that both data_key and key are used. Even with data_key present, the key attribute is still necessary to facilitate the title design’s storing of data.

The atavist-image and atavist-multi-background elements support binding to title_page, a special key that represents the primary image associated with a project or section. For both projects and sections, the key is used within the Atavist editor to persist an image across title designs as you browse them. For projects, the image is used to represent the story on the My Projects page and on the public-facing Collection page.

Binding an atavist-image element to the title_page key might look like this in markup:

<atavist-image use_as_background="true" key="background_image" data_key="title_page"></atavist-image>

Subcomponent reference

atavist-image

A basic image subcomponent, designed to work together with Atavist’s file uploader form element. Images are loaded to the element responsively, based on the width of the viewport.

Properties:

atavist-audio

An audio player subcomponent, designed to work together with Atavist’s file uploader form element.

Properties:

atavist-video

An HTML 5 video player subcomponent, designed to work together with Atavist’s file uploader form element.

Properties:

atavist-multi-video

A video player subcomponent that accepts HTML 5 video as well as embedded Vimeo and YouTube video, designed to work together with Atavist’s multi video form element.

Properties:

atavist-multi-background

A subcomponent that allows for configurable background-appropriate fills, like images, looping videos, and solid colors. It’s useful in title designs.

Properties:

atavist-caption

A basic text subcomponent, which accepts a single paragraph of text and supports basic text editing capabilities (like bolded, italicized, and hyperlinked text).

Properties:

atavist-text

A text subcomponent, which accepts multiple paragraphs of richly editable text.

Properties:

atavist-story-data (within title designs only)

A special subcomponent, meant only for title designs, which binds its text contents to project- or section-level data.

Properties:

Field types

Custom blocks support several input types. Below are usage examples.

Text input

{
	"label": "Text label",
	"type": "text",
	"placeholder": "Write something…" // optional
}

Text area

{
    "label": "Text area label",
    "type": "textarea",
    "placeholder": "Write something longer…" // optional
}

Select menu

{
    "type": "select",
    "label": "Dropdown menu label",
    "data": { 
      "key_data_value1": "Key Label 1",  
      "key_data_value2": "Key Label 2"
    },
    "default": "key_data_value2"
}

Checkbox

The value of the checkbox will be transmitted as 0 or 1.

{
	"label": "Checkbox Label",
	"type": "checkbox"
}

Number input

{
	"label": "Number label",
	"type": "number",
	"min": 0, // minimum number. (default: 0)
	"max": 6, // maximum number. (default: 6)
	"step": "any" // optional numeric step. (default: "any")
}

Range/slider

{
	"label": "Range/slider element",
	"type": "range",
	"min": "1", // minimum value for range. (default: 0)
	"max": "4", // maximum value for range. (default: 100)
	"step": 1, // step interval, e.g. how many draggable positions exist on the slider. (default: 1)
	"use_ticks": true, // optional: whether or not to show ticks on the slider. (default: true)
	"tick_min_label": "Small",  // optional: tick_min_label & tick_max_label, which together trigger unitless labels at the left and right positions
	"tick_max_label": "Large",
	"tick_interval": "2" // optional: when current step modulo this number is zero, creates a tick. (default: tick_interval is automatically generated.)
}

Color picker

Returns a hex code string by default.

{
	"label": "Color label",		
	"type": "colorpicker",
	"use_rgba": false // whether to display an opacity slider and return RGBA values (default: false)
}

File uploader

{
	"label": "Image label",
	"type": "file",
	"file_type" : "image", // can be "image", "document", "audio", "video". default is "image"
	"focalpoint" : true // optional: for image uploaders, whether or not to include a focal point picker UI
}

Multi-video

Designed to be used with the atavist-multi-video subcomponent.

{
	"label": "Multi video label",
	"type": "video"
}

Atavist APIs

Library API

Fetch an array of projects from your organization based on search terms or tags. You can specify a pagination limit (how many results to return) and a pagination offset (how many initial results to skip).

To explore the Library API and to test real library queries, click the “Library API Reference” button, located under the JavaScript field. A helper will appear that allows you to formulate queries and that displays usage examples.

In View API

With the In View API, your block can know whether it’s been scrolled into view on a reader’s screen. This allows for the triggering of initial animations (an image that fades in when it first appears) and the management of active vs. inactive states (a video that plays only when on screen). The API is a layer on top of Waypoints library, and uses similar syntax.

The In View API has two methods: onceInView and whenInView.

onceInView

The onceInView method, called on the block itself, takes one parameter: either a callback function, to be executed once the block is scrolled into view, or an options object, consisting of a handler (the callback function) and an offset value.

The offset value sets a tripwire, effectively, determining where the top of the block is in relationship to the top of the scrolling context when the callback is fired. A value of 0 means the top of the block hits the top of the scrolling context. The offset property can accept a few types of values:

If a function is passed into the onceInView method, a default offset value is used, which corresponds to the height of the scrolling context minus half the height of the block. In other words, by default, the callback fires when half the block’s height has been scrolled into view.

Using the onceInView method by passing in a function might look like this:

this.onceInView(function() {
	this.style.opacity = 1;
});

Using it with an object might look like this:

this.onceInView({
	handler: function() {
		this.style.opacity = 1;
	},
	offset: '50%'
});

whenInView

Unlike onceInView, the whenInView method is designed to fire callback functions whenever a block is scrolled into or out of view. It’s useful for blocks that need to do something whenever they appear onscreen, or for blocks that have distinct on- and off-screen states.

The method takes two parameters:

The method laid bare — with the callbacks stubbed out and the default offset array in place — looks like this:

this.whenInView({
	enter: function() {
		// Executed when the block is _just about_ to be scrolled into view
	},
	entered: function() {
		// The entirety of the block’s height has been scrolled into view
	},
	exit: function() {
		// The block is just starting to be scrolled out of view
	},
	exited: function() {
		// The entirety of the block’s height has been scrolled out of view
	}
}, [{
		down: 'enter',
		up: 'exited',
		offset: '100%'
	}, {
		down: 'entered',
		up: 'exit',
		offset: 'bottom-in-view'
	}, {
		down: 'exit',
		up: 'entered',
		offset: 0
	}, {
		down: 'exited',
		up: 'enter',
		offset: function() {
			return -this.adapter.outerHeight();
		}
}]);

When calling whenInView, you can specify only those callbacks that you need. To execute a function whenever a block appears in view, you can do something like this:

this.whenInView({
	enter: function() {
		console.log('I\'m in view!');
	}
});

If you wanted to execute a function with a custom offset — say, whenever the first 10 pixels of a block appear in view — you could do something like this:

this.whenInView({
	enter: function() {
		console.log('I\'m in view!');
	}
}, [{
		down: 'enter',
		offset: function() {
			return this.context.innerHeight() - 10;
		}
	},{
		up: 'enter',
		offset: function() {
			return -this.adapter.outerHeight() + 10;
		}
}]);

A few things to keep in mind when defining a custom offset array:

Title design helper classes reference

.atavist-cover-background-color

Sets the background-color property to match the default background color of the user’s chosen theme.

.atavist-cover-text-color

Sets the color property to match the default body text color of the user’s chosen theme.

.atavist-cover-font-sans-serif

Sets the font-family property to match the primary font family — often a sans-serif font, but not always — of the user’s chosen theme.

.atavist-cover-font-serif

Sets the font-family property to match the secondary font family — often a serif font, but not always — of the user’s chosen theme.

.atavist-cover-paragraph-font-size

Sets the font-size property to match the default paragraph size, in ems, of the user’s chosen theme.

.atavist-cover-h1

Sets the font-size property to a value appropriate for an h1 element, defined in ems, and calculated using the default paragraph size of the user’s chosen theme. The value used is slightly larger for project title designs than for section title designs.

.atavist-cover-h2

Sets the font-size property to a value appropriate for an h2 element, defined in ems, and calculated using the default paragraph size of the user’s chosen theme. The value used is slightly larger for project title designs than for section title designs.

.atavist-cover-byline

Sets the font-size property to a value appropriate for an h2 element, defined in ems, and calculated using the default paragraph size of the user’s chosen theme.

.atavist-cover-column-width-and-alignment

Sets width, max-width, margin-left, and margin-right properties so that an element shares its size and alignment with the text column of a user’s chosen theme.

.atavist-cover-left-gutter-left

Sets the left value to match the width of the left gutter of the user’s chosen theme.

.atavist-cover-left-gutter-padding-left

Sets the padding-left value to match the width of the left gutter of the user’s chosen theme.

.atavist-cover-left-gutter-margin-left

Sets the margin-left value to match the width of the left gutter of the user’s chosen theme.

.atavist-cover-right-gutter-right

Sets the right value to match the width of the right gutter of the user’s chosen theme.

.atavist-cover-right-gutter-padding-right

Sets the padding-right value to match the width of the right gutter of the user’s chosen theme.

.atavist-cover-right-gutter-margin-right

Sets the margin-right value to match the width of the right gutter of the user’s chosen theme.