Skip to content

Latest commit

 

History

History
338 lines (250 loc) · 10.5 KB

step-7.md

File metadata and controls

338 lines (250 loc) · 10.5 KB

Step 7: Player enrollment

In this step, you build a registration form displayed in a modal panel.

Keywords: form, data-binding, custom events

Create a game component

This component will further contain all the components binded together to make the game work. For now you will use it to test the components you will create in this step.

→ Create a new file web/game.html, with the following content:

<link rel="import" href="packages/polymer/polymer.html">

<polymer-element name="risk-game">
  <template>
  </template>

  <script type="application/dart" src="game.dart"></script>
</polymer-element>

→ Create a new file web/game.dart, with the following content:

import 'dart:html';
import 'package:polymer/polymer.dart';

@CustomTag('risk-game')
class RiskGame extends PolymerElement {
  RiskGame.created() : super.created();
}

→ In web/index.html add a <risk-game>:

<link rel="import" href="game.html">
<!-- .... -->
<risk-game></risk-game>

Create a modal panel

The registration form will appear in a modal panel. We will first create a <risk-modal> element that will allow to display some contents in a modal panel.

Basic modal panel

→ Create a new file web/modal.html, with the following content:

<link rel="import" href="packages/polymer/polymer.html">

<polymer-element name="risk-modal">
  <template>
    <style>
      :host {
        position: fixed;
        top: 0;
        bottom: 0;
        right: 0;
        left: 0;
        z-index: 1000;
        background-color: rgba(256,256,256,0.8);
      }

      #title {
        border-bottom: 1px solid lightgray;
      }

      #panel {
        background-color: white;
        border: 1px solid lightgray;
        border-radius: 10px;
        padding: 10px;
        width: 80%;
        max-width: 768px;
        margin: 5% auto 0;
        max-height: 80%;
        overflow-x: hidden;
        overflow-y: auto;
      }

      #close {
        float: right;
        border: 1px solid lightgray;
      }
    </style>

    <div id="panel">
      <h4 id="title">{{ header }}</h4>
      <content></content>
    </div>
  </template>
  <script type="application/dart" src="modal.dart"></script>
</polymer-element>

→ Create a new file web/modal.dart, with the following content:

import 'package:polymer/polymer.dart';

@CustomTag('risk-modal')
class RiskModal extends PolymerElement {
  @published
  String header;

  RiskModal.created(): super.created();
}

Key information:

  • The <content> tag allows to inject the content provided inside <risk-modal></risk-modal>. This <content> may also accept a select attribute to only grab some specific parts of the original HTML content.

→ Use this new component in web/game.html to display some basic text:

<link rel="import" href="modal.html">
<!-- .... -->
<risk-modal header="Title of the modal">Content as <b>HTML</b></risk-modal>

→ Run in Dartium

You should see a modal panel with the title you provided and the original content displayed.

Basic Modal Panel

Closable modal panel

You will now improve the modal panel to allow it to be closable.

→ In web/modal.html replace the <div id="panel"> with the following:

    <div id="panel">
      <template if="{{ closable }}">
        <button id="close" on-click="{{ close }}">&times;</button>
      </template>
      <h4 id="title">{{ header }}</h4>
      <content></content>
    </div>

→ In web/modal.dart add the following members:

  @published
  bool closable;

  // ....

  close() => fire('close');

Key information:

  • The <risk-modal> element can now be closable or not depending on its closable attribute.
  • By clicking on the close button a CustomEvent will be fired on the element. This event can be handled outside of the element with: <risk-modal on-close="...">.

To test this new behaviour add closable="{{ true }}" on-close="{{ close }}" in web/game.html and close(CustomEvent e, var detail, Element target) => target.remove(); in web/game.dart. Run in Dartium.

Closable Modal Panel

Create a basic registration form

→ Create a new file web/registration.html, with the following content:

<link rel="import" href="packages/polymer/polymer.html">

<polymer-element name="risk-registration">
  <template>
    <link rel="stylesheet" href="packages/bootstrap_for_pub/3.1.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="packages/bootstrap_for_pub/3.1.0/css/bootstrap-theme.min.css">

    <div class="form-horizontal">
      <div class="form-group">
        <label class="col-sm-3 control-label">Name</label>
        <div class="col-sm-9">
          <input class="form-control" value="{{ name }}">
        </div>
      </div>
      <div class="form-group">
        <label class="col-sm-3 control-label">Avatar</label>
        <div class="col-sm-9">
          <select selectedIndex="{{ avatarSelectedIndex }}">
            <option template repeat="{{ avatar in avatars }}">{{ avatar }}</option>
          </select>
          <br>
          <br>
          <img alt="avatar" class="img-rounded" _src="img/avatars/{{ avatar }}">
        </div>
      </div>
      <div class="form-group">
        <label class="col-sm-3 control-label">Country color</label>
        <div class="col-sm-9">
          <input type="color" value="{{ color }}">
        </div>
      </div>
      <hr>
      <div class="form-group">
        <div class="col-sm-offset-3 col-sm-9">
          <button class="btn btn-primary">Join</button>
        </div>
      </div>
    </div>

  </template>
  <script type="application/dart" src="registration.dart"></script>
</polymer-element>

→ Create a new file web/registration.dart, with the following content:

import 'dart:math';
import 'package:polymer/polymer.dart';

final _random = new Random();

@CustomTag('risk-registration')
class RiskRegistration extends PolymerElement {
  final List<String> avatars = ['ahmadi-nejad.png', 'bachar-el-assad.png',
      'caesar.png', 'castro.png', 'hitler.png', 'kadhafi.png', 'kim-jong-il.png',
      'mao-zedong.png', 'mussolini.png', 'napoleon.png', 'pinochet.png',
      'saddam-hussein.png', 'staline.png'];

  @observable
  String name;

  @observable
  String avatar;

  @observable
  String color;

  RiskRegistration.created(): super.created() {
    avatar = (avatars.toList()..shuffle(_random)).first;
    color = '#' + new List.generate(6, (_) => (6 + _random.nextInt(10)).toInt(
        ).toRadixString(16)).join();
  }

  int get avatarSelectedIndex => avatars.indexOf(avatar);
  set avatarSelectedIndex(int index) {
    avatar = avatars[index];
  }
}

→ Use this new <risk-registration> in web/game.html:

    <link rel="import" href="registration.html">
    <!-- .... -->
    <risk-modal header="Player registration">
      <risk-registration></risk-registration>
    </risk-modal>

→ Run in Dartium.

Registration form

Key information:

  • The two-way data binding makes forms really easy to handle. Any change in the input is reflected directly on the property of the class.
  • <select> binding is done with its selectedIndex attribute. The get avatarSelectedIndex/set avatarSelectedIndex are used for translation between selectedIndex and value.
  • <option> elements use a specific syntax for repeat: <option template repeat="{{ avatar in avatars }}" instead of <template repeat="{{ avatar in avatars }}"><option>{{ avatar }}</option></template>. Some elements (like <option>, <tr>, <td>) have parsing rules that prohibit a <template> tag.

Add validation

To improve the form submission you will enable the Join button only when all the informations are provided.

→ In web/registration.html replace the Join button with the following code:

<button class="btn btn-primary" disabled?="{{ !isValid }}">Join</button>

→ In web/registration.dart add the following code :

class RiskRegistration extends PolymerElement {

  // .....

  RiskRegistration.created(): super.created() {
    // .....

    // notif isValid change
    notifyIsValid() => notifyPropertyChange(#isValid, null, isValid);
    onPropertyChange(this, #name, notifyIsValid);
    onPropertyChange(this, #avatar, notifyIsValid);
    onPropertyChange(this, #color, notifyIsValid);
  }

  // .....

  bool get isValid => [name, avatar, color].every((v) => v != null && v.trim(
      ).isNotEmpty);
}

→ Run in Dartium.

Key information:

  • The ?= syntax in disabled?="{{ !isValid }}" allows to make the presence of disabled conditionnal to the expression provided.
  • #name is equivalent to const Symbol('name'). A Symbol object represents an operator or identifier declared in a Dart program (mainly needed for minification).
  • onPropertyChange is used to observe changes on a particular member and to trigger actions.
  • notifyPropertyChange is used to inform that the field name of this object has been changed.
  • notifyIsValid is an inline function only available in the constructor. It's like defining var notifyIsValid = () => notifyPropertyChange(#isValid, null, isValid);.

Submitting form

→ In web/registration.html add on-click="{{ join }}" to the Join button.

→ In web/registration.dart add the following join method that will be called on Join click:

  join() => fire('done', detail: {
    'name': name,
    'avatar': avatar,
    'color': color,
  });

Unlike the close event fired by <risk-modal> this done event comes with additionnal informations. The optional named parameter detail allows to pass any kind of contextual datas associated with the event.

→ In web/game.html add on-done='{{ joinGame }}' and implement joinGame in web.game.dart to print the detail in the console.

Problems?

Check your code against the files in s7_enrollment (diff).