Skip to content
This repository has been archived by the owner on Sep 6, 2018. It is now read-only.

Guidelines

Michael Hadley edited this page Jun 13, 2017 · 2 revisions

Metal-Soy Guidelines

These are some patterns and guidelines (in no particular order) that we try to follow. The goal is to write metal-soy code that is as consistent and maintainable as possible.

Importing Resources

To import components in our .js files split them in two blocks, first the components with absolute paths and after the components with relative paths, both blocks ordered alphabetically:

import Component from 'metal-component';
import core from 'metal';
import Soy from 'metal-soy';
import { debounce } from 'metal-debounce';
import { on } from 'metal-dom';

import templates from './MyComponent.soy';

Declaring Params

Soy has two ways of declaring parameters to a template: in it's soydoc comment or using the @param command. The soydoc comment style has been deprecated (see here), so prefer the latter:

{template myTemplate}
	{@param name: string}

	<h1>Hello {$name}!</h1>
{/template}

Keep all of the parameters in a single block, sorted alphabetically, with optional parameters coming after required. Most text editors make easy work of this since it should be the default order when the lines are run through a sorting routine.

{namespace MyComponent}

/**
 * MyComponent
 */
{template .render}
	{@param backURL: string}
	{@param contactsCardTemplateTypes: ?}
	{@param id: string}
	{@param pathThemeImages: string}
	{@param portletNamespace: string}
	{@param? _handleEditCard: any}
	{@param? _handleHideCreateCard: any}
	{@param? _handleShowCreateCard: any}
	{@param? showCreateCardModal_: bool}

In your javascript file, make sure that all params are listed in the component's STATE declaration as well, even if it could have been omitted since it would only be referenced in the template. Also prefer using the Config helper exported by metal-soy:

import Component, {Config} from 'metal-component';

class MyComponent extends Component {}

MyComponent.STATE = {
	/**
	* Make sure to add the required() flag if the prop does not have a `?` in
	* your template.
	**/
	name: Config.string().required()
};

export default MyComponent;

When specifying types of params, try to be consistent and use the correct types:

  • For primitives, use (string, number, etc.). Maps and Lists should use map<T, U> and list<T>.
  • For functions, use the any type, since there is no native function type for Soy.
  • For records, there is a record syntax that would be useful, however it currently will lead to issues with your javascript component, so for the moment just use ?, which will still allow member access using the . operator. See this issue for the status for record type syntax for metal-soy.

Private attributes

Private attributes should be named with a trailing underscore (count_, myName_, etc.), should use the internal() flag, and will need to be declared as optional in the template (@{param? name: string}).

Default values

Prefer the ?: operator over the ternary (? : ), when declaring default values in your template:

/* Bad */
<h1>{isNonnull($name) ? $name : 'Foo'}</h1>

/* Good */
<h1>{$name ?: 'Foo'}</h1>

Events vs Function Props

Prefer using the events prop and calling this.emit() internally, instead of passing functions as props:

/* Bad */
{call MyEditor.render}
	{param onChange: $_handleChange /}
{/call}

/* Good */
{call MyEditor.render}
	{param events: ['change': $_handleChange] /}
{/call}

Handling class names

It is often the case that a component will need to handle adding class given a variety of different conditions. Instead of trying to cram all of the logic onto a single line with many {if} checks or ternary statements, use a {let} and space them out:

/* bad */
<div class="my-component modifier-class{if $foo} some-class{/if}{if $bar} some-bar{/if}{if $baz} some-baz{/if}"></div>

/* good */
{let $classes kind="text"}
	my-component
	{sp}modifier-class
	{if $foo}
		{sp}some-class
	{/if}
	{if $bar}
		{sp}some-bar
	{/if}
	{if $baz}
		{sp}some-baz
	{/if}
{/let}

<div class="{$classes}"></div>

We also make sure to explicitly add spaces using the {sp} command. It has the nice side-effect of also making it harder to miss one because they stand out visually.

Avoid data="all"

Soy has a feature that allows all parameters in the parent scope to be automatically passed to a template being called ({call .mySubtemplate data="all" /}). There are several reasons to avoid this feature.

Because metal-soy is both a templating and component system, it's very common that calls to other templates are in fact calls to other components as well (as opposed to helper templates declared in the same namespace). This means that if the parent and child template have a prop by the same name, it's likely that they correspond to different things. Even worse if that prop happens to be optional for the child component. It is very easy to find yourself passing bad data to a child template accidentally through data=all, making it hard to debug problems, without any warnings. We know this from experience.

It also makes reading the code much harder. Understanding which props are being used now requires exact knowledge of which params the component takes. Explicitly passing those props is much easier to read. To quote the Zen of Python, "Explicit is better than Implicit".