Forms

Basic Inputs

Examples of standard form inputs used throughout the Control Portal. Add the .form-control class to each input type to get proper formatting and alignment.

Text

    
<input type="text" class="form-control" placeholder="Text Input">
    
  

Password

Examples of standard form inputs used throughout the Control Portal.

    
<input type="password" class="form-control" placeholder="Password">
    
  

Textarea

    
<textarea class="form-control" rows="3"></textarea>
    
  

Checkbox and Radios

    
<div class="checkbox">
  <label>
    <input type="checkbox" value="" /> Option 1</a>
  </label>
</div>
<div class="checkbox disabled">
  <label>
    <input type="checkbox" value="" disabled>
    Option two is disabled
  </label>
</div>

<div class="radio">
  <label>
    <input type="radio" name="optionsRadios" id="optionsRadios1" value="option1" checked>
    Option one is this and that&mdash;be sure to include why it's great
  </label>
</div>
<div class="radio">
  <label>
    <input type="radio" name="optionsRadios" id="optionsRadios2" value="option2">
    Option two can be something else and selecting it will deselect option one
  </label>
</div>
<div class="radio disabled">
  <label>
    <input type="radio" name="optionsRadios" id="optionsRadios3" value="option3" disabled>
    Option three is disabled
  </label>
</div>
    
  

Select Dropdowns

  • If there is only one item in the select options, than that item should automatically be selected for the user.
  • If there are multiple items in the list but there is a context for how the user has arrived, select the logical choice. For example, a user selects the create server button on the WA1 - US West (Seattle) Data Center Overview page, the logical choice for the Data Center dropdown would be WA1 - US West (Seattle).
  • If there is no logical defaults then the user should be presented with a contextual message that tells them what to do as the first item in the list (e.g. ‘Select a Data Center...’)
    
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
    
  

Hierarchy Picker

When it is important to display the hierarchy of a collection (e.g. servers, groups, or organizations/accounts) in the selection process, use a hierarchy picker.

Single-Select Picker

If you want to allow the user to only pick one item use radio buttons.

    
<div class="picker">
  <ol>
    <li>
      <label class="item-hover">
        <input type="radio" name="group"/>
        <span class="content">
          <svg class="cyclops-icon"><use xlink:href='#icon-folder'></svg> <span>Level 1a</span>
        </span>
      </label>
    </li>
    <li>
      <label class="item-hover">
        <input type="radio" name="group"/>
        <span class="content">
          <svg class="cyclops-icon"><use xlink:href='#icon-folder'></svg> <span>Level 1b</span>
        </span>
      </label>
      <ol>
        <li>
          <label class="item-hover">
            <input type="radio" name="group"/>
            <span class="content">
              <svg class="cyclops-icon"><use xlink:href='#icon-folder'></svg> <span>Level 2a</span>
            </span>
          </label>
        </li>
      </ol>
    </li>
  </ol>
</div>
    
  

Multi-Select Picker

If you want to allow the user pick many items use checkboxes.

      
<div class="picker">
  <ol>
    <li>
      <label class="item-hover">
        <span class="content">
          <svg class="cyclops-icon"><use xlink:href='#icon-folder'></svg> <span>Default Group</span>
        </span>
      </label>
      <ol>
        <li>
          <label class="item-hover">
            <input type="checkbox"/>
            <span class="content">
              <svg class="cyclops-icon"><use xlink:href='#icon-play'></svg> <span>QA1DEVTEST01</span>
            </span>
          </label>
        </li>
        <li>
          <label class="item-hover">
            <input type="checkbox"/>
            <span class="content">
              <svg class="cyclops-icon"><use xlink:href='#icon-play'></svg> <span>QA1DEVTEST02</span>
            </span>
          </label>
        </li>
      </ol>
    </li>
    <li>
      <label class="item-hover">
        <span class="content">
          <svg class="cyclops-icon"><use xlink:href='#icon-folder'></svg> <span>Web Servers</span>
        </span>
      </label>
      <ol>
        <li>
          <label class="item-hover">
            <input type="checkbox" disabled/>
            <span class="content">
              <svg class="cyclops-icon"><use xlink:href='#icon-stop'></svg> <span>QA1DEVSRVR01</span>
            </span>
          </label>
        </li>
        <li>
          <label class="item-hover">
            <input type="checkbox"/>
            <span class="content">
              <svg class="cyclops-icon"><use xlink:href='#icon-play'></svg> <span>QA1DEVSRVR02</span>
            </span>
          </label>
        </li>
        <li>
          <label class="item-hover">
            <input type="checkbox"/>
            <span class="content">
              <svg class="cyclops-icon"><use xlink:href='#icon-play'></svg> <span>QA1DEVSRVR03</span>
            </span>
          </label>
        </li>

      </ol>
    </li>
  </ol>
</div>
      
    

One Label with Stacked Inputs

By default, inputs will automatically stack when placed in a single column.

    
<form class="form-horizontal">
  <fieldset>
    <div class="form-group">
      <label class="col-sm-3 control-label">nodes</label>
      <div class="col-sm-6">
        <input type="text" id="node1" class="form-control" placeholder="ip address">
        <input type="text" id="node2" class="form-control" placeholder="ip address">
        <input type="text" id="node3" class="form-control" placeholder="ip address">
        <input type="text" id="node4" class="form-control" placeholder="ip address">
      </div>
    </div>
  </fieldset>
</form>
    
  

One Label with Adjacent Inputs

When it is more appropriate to adjacently align inputs within a .col-*, add a .w* to the .form-control set the width based on a percentage of the parent. Widths are avaialbe in increments of 10, starting at 10%.

    
<form class="form-horizontal">
  <fieldset>
    <div class="form-group">
      <label class="col-sm-3 control-label">nodes</label>
      <div class="col-sm-6">
        <input type="text" class="form-control w70" placeholder="ip address">
        <input type="text" class="form-control w30" placeholder="port">
      </div>
    </div>
  </fieldset>
</form>
    
  

Custom Controls

For more complex controls that require more than simple HTML markup we use Knockout Components. This means that in order for these to work the element, or a parent element, must have bindings applied. These self contained "controls" makes them easy to consume but provides a rich user experience.

Toggle

    
  <toggle params="value: value, disabled: disabled, affirmativeText: 'yes', negativeText: 'no'"></toggle>
    
  

Sliders

    
  <slider params="value: 6, min: 1, max: 16"></slider>    <!-- CPU -->
  <slider params="value: 4, min: 1, max: 128"></slider>   <!-- Memory -->
  <slider params="value: 50, min: 1, max: 1024"></slider> <!-- Disk -->
    
  

Group Picker

    
  <group-picker params="{ selectedGroup: selectedGroup, isLoading: isLoading, groups:groups}"></group-picker>

    
  

Server Picker

    
<server-picker params="{ selectedServers: selectedServers, multiSelect: multiSelect, isLoading: isLoading, groups: groups }"></server-picker>
    
  

Date Time Picker

    
<date-time-picker params="dateTime: myDateTime"></date-time-picker>
    
  

Disabled Inputs

Add the disabled attribute on an input to prevent user interactions. Disabled inputs appear lighter and add a not-allowed cursor.

    
<input type="text" class="form-control" id="disabledInput" placeholder="Disabled Text Input" disabled>
    
  

Disabled Fieldset

    
<form>
  <fieldset disabled>
  <div class="form-group">
    <label for="disabledTextInput">Disabled input</label>
    <input type="text" id="disabledTextInput" class="form-control" placeholder="Disabled input">
  </div>
  <div class="form-group">
    <label for="disabledSelect">Disabled select menu</label>
    <select id="disabledSelect" class="form-control">
      <option>Disabled select</option>
    </select>
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox"> Can't check this
    </label>
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
  </fieldset>
</form>
    
  

Basic Form

Individual form controls automatically receive some global styling. All textual <input>, <textarea>, and <select> elements with .form-control are set to width: 100%; by default. Wrap labels and controls in .form-group for optimum spacing.

  • Labels should have a for attribute that points to the id of an input type. It is only allowed to point to input types not any element
  • The fieldset legend name should be useful not just edit or create
  • Optional fields are denoted not required fields.
  • Tab order should be logical and visit ever valid field (e.g. do not tab to disabled items)
  • Items that are not editable are presented as paragraph tags not disabled inputs

Example block-level help text here.

  
<form>
  <div class="form-group">
   <label for="basicFormEmail">Email address</label>
   <input type="email" class="form-control" id="basicFormEmail" placeholder="Email">
  </div>
  <div class="form-group">
   <label for="basicFormPassword">Password</label>
   <input type="password" class="form-control" id="basicFormPassword" placeholder="Password">
  </div>
  <div class="form-group">
   <label for="basicFormFileInput">File input</label>
   <input type="file" id="basicFormFileInput">
   <p class="help-block">Example block-level help text here.</p>
  </div>
  <div class="checkbox">
   <label>
     <input type="checkbox"> Check me out
   </label>
  </div>
  <button type="submit" class="btn btn-default">Submit</button>
  <button class="btn btn-link">cancel</button>
</form>
  

Inline Forms

Use .inline-form to display labels and form inputs on a single row. This is useful for confirmation dialogs that offer additional options (e.g. delete a database, but create a backup before it's deleted).

    
<form class="form-inline">
  <div class="form-group">
    <label for="inputEmail">email</label>
      <input type="text" class="form-control" id="inputEmail" placeholder="email">
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox"> remember me
    </label>
  </div>
  <div class="radio">
    <label>
      <input type="radio"> radio me
    </label>
  </div>
  <button type="submit" class="btn btn-primary">sign in</button>
</form>
    
  

Horizontal Form

Horizontal forms rely on the Grid System to align form labels and inputs horizontally. These forms used throughout the Control Portal to create new entities, such as servers, appfog apps, and policies.

Add a visual separator to the form submission buttons by adding .form-submit to the .form-group container.

    
<form class="form-horizontal">
  <div class="form-group">
    <label for="horizontalFormEmail" class="col-sm-3 control-label">Email</label>
    <div class="col-sm-9">
      <input type="email" class="form-control" id="horizontalFormEmail" placeholder="Email">
    </div>
  </div>
  <div class="form-group">
    <label for="horizontalFormPassword" class="col-sm-3 control-label">Password</label>
    <div class="col-sm-9">
      <input type="password" class="form-control" id="horizontalFormPassword" placeholder="Password">
    </div>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-3 col-sm-9">
      <div class="checkbox">
        <label>
          <input type="checkbox"> Remember me
        </label>
      </div>
    </div>
  </div>
  <div class="form-group form-submit">
    <div class="col-sm-offset-3 col-sm-9">
      <button type="submit" class="btn btn-default">sign in</button>
      <button class="btn btn-link">cancel</button>
    </div>
  </div>
</form>
    
  

Async Forms

Async calls can be used to fet values for input on calls that can take a long time. Doing this give the effect of faster render times since the user starts seeing UI faster than just watching the browser spin.

In the bellow example server name is not dependent on the data center choice so it is always enabled, but the avaliblity of hyperscale does change based on data center so it is disabled until the data centers are loaded.


Dynamic Forms

Sometimes when you are creating a form a user selection may influence the form fields themselves. For instance depending on what data center is chosen the user may have the option to provision a server as bare metal.

In the example bellow GB1 supports bare metal and the other two do not.

Some settings specific to bare metal stuff.

Static Text

When you need to place plain text next to a form label within a horizontal form, use the .form-control-static class on a <p>.

email@example.com

    
<p class="form-control-static">email@example.com</p>
    
  

Form Submission

When submitting a form we need to give the user some feedback that something is taking place. In some cases it can take several seconds for the request to return from the server. During that time we want to indicate something is happening and prevent the form from being changed or submitted again.

Bellow is a simple example that uses one observable to disable the form and also change the text of the button once the user has submitted the form.

    
var formModel = function(){
  var self = this;
  self.submitting = ko.observable(false);
  self.submitButtonText = ko.pureComputed(function(){
    return self.submitting() ? "submitting": "submit";
  });
  self.submit = function(){
    self.submitting(true);
    setTimeout(function(){
      // This simulates a long running AJAX call. When using jQuery xhr requests you can use
      // the deferred objects (https://api.jquery.com/category/deferred-object/) always handler
      // to set sumitting back to false all the time.
      self.submitting(false);
    }, 5000);
  };
  return self;
}
    
    
<form>
  <fieldset data-bind="disable: submitting">
    <div class="form-group">
      <label for="input">Input</label>
      <input type="text" id="input" class="form-control" placeholder="some text">
    </div>
    <button type="submit" class="btn btn-primary" data-bind="text: submitButtonText, click: submit">submit</button>
  </fieldset>
</form>
    

  

Validation

We suggest using knockout-validation and have created several bindings and validators that will make creating an awesome user experience simple and painless.

Getting Started

Becuase we use knockout-validation we suggest heading over to there wiki and reading the Getting Started Guide. Bellow is the set of options, validators, and custom validators.

Global Configuration

There are three global configuration options that need to be set on knockout validation. Due do backwards compatability issues we are unable to automatically set these for you. You may set yourself globally or use the validationOptions binding to set them on each form.

insertMessages=false: Turns off the inertion of a text node next to the input, instead use the validationMsg binding.

allowHtmlMessages=true: Do not html encode the validation message, note that if you are inserting any kind of user input into your validation message that text must me HTML encoded. see, How and when to encode for the web

errorsAsTitle=false: Do not append a title attribute with the validation message, this is a problem when you are using HTML messages because titles do not support HTML.

Demo

A demo that includes all the options and configuration needed. See the list of native and custom validators for more complex validation needs. As a rule of thumb only optional fields are marked.

ValidationOptions is used to set some common configuration that is needed to adhere to out ux guidelines. This can be set on a global level but is not in control due to the need for backwards compatablity with "old ui" pages.

validationMsg is a custom binding that we created to show error messages that follow our ux guidelines without requiring additional work from the developer. Use this binding on a p tag to display error messages

ko.validation.group is used to select which properties on your model will be validated. In general our models tend to have a lot of properties (computeds, etc.) that don't need to be tracked for validation so we use validations groups. more information about validation groups can be found on the knockout-validation page

showAllMessages is required when using validation groups to tigger the showing of error messages for the tracked properties.

      
self.header = ko.observable();
self.text = ko.observable().extend({
    required: { params: true, message: "some text is required." },
});
self.errors = ko.validation.group([self.text])
self.submit = function() {
  if (self.errors().length < 1) {
    alert("The Form is Valid!");
  } else {
    self.errors.showAllMessages();
  }
};
      
      
<form class="form-horizontal" data-bind="validationOptions: { insertMessages: false, allowHtmlMessages: true, errorsAsTitle: false }">
  <div class="form-group">
    <label class="col-sm-3 control-label" for="header">Header (optional)</label>
    <div class="col-sm-6">
      <input class="form-control" type="text" id="header" data-bind="value: header" >
    </div>
  </div>
  <div class="form-group">
    <label class="col-sm-3 control-label" for="requiredText">Message</label>
    <div class="col-sm-6">
      <input class="form-control" type="text" id="requiredText" data-bind="value: text, validationElement: text" >
      <p class="hide" data-bind="validationMsg: text"></p>
    </div>
  </div>
  <div class="form-group form-submit">
    <div class="col-sm-offset-3 col-sm-9">
      <button class="btn btn-primary" data-bind="click: submit">Submit</button>
    </div>
  </div>
</form>
      
    

Native Validators

A full list of navtive validators and their documentation can be found on the knockout-validation wiki page.

Custom Validators

A full list of custom validators and their documentation can be found on the validators page.

Password Strength

In addition to the passwordStrength validator we have created a widget (named passwordStrength) that should be used in conjuction with the validator to provide the end user with realtime information about the strength of their password. More information about the options of this widget can be found on the widgets page.

    
self.password = ko.observable().extend({
    required: { params: true, message: "A password is required." },
    passwordStrength: { requiredStrength: 4, dissallowedCharacters: "[}" }
});
self.errors = ko.validation.group([self.password])
self.submit = function() {
  if (self.errors().length < 1) {
    alert("The Password meets the requires strength!");
  } else {
    self.errors.showAllMessages();
  }
};
          
          
  <form class="form-horizontal" data-bind="validationOptions: { insertMessages: false, allowHtmlMessages: true, errorsAsTitle: false }">
  <div class="form-group">
      <label class="col-sm-3 control-label" for="password">password</label>
      <div class="col-sm-6">
          <input class="form-control" type="password" id="password" data-bind="value: password, validationElement: password, widget: 'passwordStrength'" >
          <p class="hide" data-bind="validationMsg: password"></p>
      </div>
  </div>
  <div class="form-group form-submit">
      <div class="col-sm-offset-3 col-sm-9">
          <button class="btn btn-primary">Submit</button>
      </div>
  </div>
  </form>
    
  

Remote / Async

Remote validation is done using inline async validators. The bellow code example fakes out an AJAX call using setTimeout. asyncMessage is a custom binding that was writen to display notification to the end user that validation is occuring. This is to be used in conjuction with validationMsg. For More information about either of these bindings please consult the bindings documentation.

Additionally it is of note that your validator may be called again before the AJAX call has returned so you will need to properly abort the previous call and issue a new one.

Compare (confirm password)

When validating a confirm password field it should only show and error message if there is a password typed. So you will want to use the onlyIf parameter.

    
self.confirmPassword.extend({
  required: { message: "Retype the password to confirm.", onlyIf: self.password },
  equal: { params: self.password, message: "Passwords do not match." }
});