UI

Workflow Dependencies

Versionen beachten!

  • node.js (>= 0.10.16)mit npm. Grunt package management + Basis für Grunt
  • sass (>= 3.2.10) mit compass (>= 0.12.2) für sämtliche Styles
  • Grunt (0.1.9) als Taskrunner für standardisierte Frontend Enwicklung
  • bower (1.2.7) als frontend dependency management

Getting Started

Initially all dependencies should be loaded.

cd src/modules/Ui/assets
npm install && bower install

Important: After the installation finishes, the browser_components directory should have the dependencies in asset/source subdirectory and NOT in the assets subdirectory. If this is not the case, the depencies should be manually copied into the proper assets/source subdirectory.

File Structure

The entire source files are located in the directory ui/assets/source.

The Grunt system creates optimised pictures and extensive CSS and Java Script and stores them in ui/assets/build, where they are used by Assetic for further processing.

Grunt - Automation, Configration Management

Athene2/ui has two predefined Grunt tasks:

grunt dev:

  • Executes a watch command on Sass, JS and picture files
  • Makes the Sass files into uncompressed CSS with comments
  • Creates vendor prefixes for CSS3, for instance -webkit-transition
  • Puts all the requirejs modules into a scripts.js
  • Checks the Java Script with jshint

grunt build:

This is the same as 'grunt dev' with the following additions:

  • shrinks files for CSS / Java Script (styles.min.css / scripts.min.js)
  • Creates a 'moderizr' custom build based on all CSS/JS files

Third Party Frameworks and Libraries

See also 'bower.json'

Linting

All JavaScript files are "linted" by grunt using jshint.

JavaScript Module

All JavaScript code is developed using the AMD module system requirejs.

/*global define*/
define('myModule', ['jquery'], function ($) {
    "use strict";
    var MyModule = function () {...};

    return MyModule;
});

(see http://requirejs.org/)

For modules to be globally available, they have to be registered in the main configuration file main.js.

require.config({
    paths: {
        "myModule": "modules/my_module"
    }
});

The modules or components are available for other modules:

define("ATHENE2", ['jquery', 'referrer_history', 'my_module'], function ($, ReferrerHistory, MyModule) {
    "use strict";
    var myInstance = new MyModule();
});

Athene2

source/scripts/ATHENE2.js

Initializes nearly everything.

Methods:

initialize($context): initializes jQuery and DOM manipulation in given $context (jQuery object)

libs/cache

cache

Client side key-value storage that uses localStorage or jQuery.cookie.

var myCache = new Cache('my-cache-id');

myCache.memorize(obj): saves the given object

myCache.remember(): restores saved data or returns null

myCache.forget(): clears the cache

libs/easing

Extends jQuerys easing functions

libs/eventscope

Function that gives any object or Function custom events.

var myObject = eventScope({});

myObject.addEventListener('event-name', callback);: Adds callback as a listener to a event-name event

myObject.trigger('event-name', some, values): Triggers formerly added callback function

myObject.removeEventListener('event-name', callback); Removes callback as a listener

libs/polyfills

A place to put browser polyfills. Currently only requestAnimationFrame.

modules/serlo_ajax_overlay

See Components

modules/serlo_common

common

Contains many helper methods and static variables:

Common.log: wrapper for console.log

Common.KeyCode: for simpler key-detection in onKey eventScope

Common.CarbonCopy(element): copies arrays and objects

Common.sortArrayByObjectKey(key, array, ascending): sorts an array that contains objects.

Common.findObjectByKey: searches an array or object containing objects

Common.genericError: Global error handling (logs only if console.log is available)

Common.memoize: memoizes a function

Common.expr: simple wrapper for isDefined && isDefined() calls, to satisfy jshint.

Common.trim: trims Strings

Common.setInterval: window.setInterval interface that uses requestAnimationFrame if available

Common.clearInterval: clears intervals set by Common.setInterval

modules/serlo_content

This component is used to initialize DOM parts. Content.init is always called, if some content parts change.

Content.init($context): Calls all formerly added callback functions in context of DOM object $context

Content.add(callback): Adds a callback that.

Example:

// All links should call alert!
Content.add(function ($context) {
    // this callback gets called, whenever there is new HTML
    // added to the main page. $context is the new HTML 
    $('a', $context).click(function () {
        alert('you clicked a link');
    });
})

modules/serlo_i18n

'Grunt' generates this component! It contains all strings that are needed for internationalization.
To add or remove strings, edit a translation file in /src/assets/lang/.

modules/serlo_injections

This component creates a jQuery Plugin.

It parses the DOM for injections, loads them and decides how to treat them (Image/HTML/GeoGebra).

modules/serlo_layout

This component makes sure the collapsable side navigation and context bar are clickable.

modules/serlo_modals

This module creates a jQuery Plugin and provides a small API.

See Components.

modules/serlo_referrer_history

Stores the users last visited pages.

This runs by its own.

ReferrerHistory.isInHistory(path): Checks if given path has been visited

ReferrerHistory.getRage(n): returns the last n visited paths

ReferrerHistory.getOne(i): returns the path on index i

ReferrerHistory.getAll(): returns the complete serlo_referrer_history

modules/serlo_router

Router.navigate(url): sends the user to given url

Router.post(path, params, method): performs a POST request

Router.reload(): reloads the browser

modules/serlo_search

Everything visually related to the Quicksearch.

modules/serlo_side_navigation

Everything visualy related to the side navigation.

modules/serlo_sortable_list

This ia a wrapper for jQuery Nestable.

Performs POST request to save new list order.

modules/serlo_spoiler

This component creates a jQuery Plugin
and handles slideToggles for content spoilers.

modules/supporter

Performs simple checks on the browser API.

Notifies the user if an API is not available.

Supporter.add('APIName'): Adds APIName to the list of variables that must be available in window.

Supporter.add(callback): Adds callback to the list of functions that will be checked on Supporter.check().

If callback returns a String, that string will cause a notification.

Supporter.check(): Checks all formerly added methods and strings.

modules/serlo_system_notification

This module generates and communicates notifications:

SystemNotification.notify(message, status, html, uniqueID);: Creates a notification.

SystemNotification.error(message): Shortcut for error messages.

// your module
define(['system_notification'], function (SystemNotification) {
    SystemNotification.notify('The force is strong with you');
    // or with specified status-level and html set to true
    SystemNotification.notify('The force is <strong>strong</strong> with you', 'success', true);
});

Available status levels:

  • info (default)
  • success
  • warning
  • danger

When a plugin triggers many notifications, it is adviseable to add uniqueID to the method call:

SystemNotification.notify('You typed the wrong answer <strong>again</strong>!', 'warning', true, 'wrong-answer');

modules/serlo_timeago

This creates a jQuery Plugin that automatically updates time fields.

modules/translator

Enables interface translation:

// your module
define(['translator'], function (t) {
    "use strict";
    // normal string
    t('Hello'); // => 'Hallo'
    // placeholders
    t('Hello %s', 'Athene'); // => 'Hallo Athene'
    // numbers
    t('Since %d days', 2); // => 'Seit 2 Tagen'
    // combination
    t('Hello %s, you have %d new messages', 'Athene', 3); // => 'Hallo Athene, du hast 3 neue Nachrichten'
});

At present there is no No singular/plural support. (contribute!)

Updating Localization Files

The grunt task language-update parses all JavaScript files for t(..) calls and updates all lang.json files in src/assets/lang/.

Ready to use functionalities

Overlay

Alle Links mit der Klasse .ajax-content öffnen sich in einem Overlay. Wenn sich im geladenen Inhalt wieder .ajax-content Links befinden öffnen sich diese bei Klick im selben Overlay als neues Tab.
All links with the class '.ajax.content' open in an overlay. If the link contains '.ajax-content' nested links, they open with a click in the same overlay as a new tab.

All available options can be passed to the module ATHENE2 during the AjaxOverlay initiation / instantiation .
Alle verfügbaren Optionen können im Modul ATHENE2 während der Instanzierung des AjaxOverlays übergeben werden:

{
    // die Klasse auf die das Overlay angewandt werden soll
    // The class that is used by the overlay
    linkClass: 'ajax-content',
    // Elemente die das Overlay schließen
    // Elements that close the overlay
    closeClass: 'close-overlay',
    // Standard Context für das initialisieren der .linkClass
    // Standard context for the '.linkclass' initialisation
    context: 'body',
    // active body class
    overlayActiveClass: 'ajax-content-active',
    // der Inhalt Selector, nach dem in per ajax geladenen Inhalt gesucht wird
    // The inhalt selector in which ajax loaded content searches 
    ajaxContentSelector: '#content-container',
    // der Selector der dem Tab den Titel gibt
    // Selector that give titles tabs
    titleSelector: '#pagetitle',
    // Klasse für das aktive Tab
    // Class for the active tab
    activeTabClass: 'active',
    // Wie viele Tabs maximal dargestellt werden sollen
    // Defines the maximum number of tabs to be displayed
    tabLimit: 5,
    // Callbacks
    on: {
        // Wenn neuer Inhalt geladen wurde
        // After new content loads
        contentLoaded: function (AjaxOverlayInstance) {
            // 'this' is the AjaxPage instance
        },
        // Wenn eine AjaxPage (tab) geöffnet wurde
        // After an AjaxPage opens
        contentOpened: function (AjaxOverlayInstance) {
            // 'this' is the AjaxPage instance
        },
        // Wenn ein Ajax Fehler auftritt
        // After an Ajax error occurs
        error: function () {
            // 'this' is the AjaxOverlay instance,
            // arguments are all the arguments from jQuery.ajax.error
        },
        // Bevor das Overlay geschlossen wird
        // Before the Overlay closes
        beforeClose: function () {
            // 'this' is the AjaxOverlay instance
        },
        // Nachdem das Overlay geschlossen wurde
        // After the Overlay closed
        afterClose: function () {
            // 'this' is the AjaxOverlay instance
        },
        // Bevor das Overlay geöffnet wird
        // Before the Overlay opens
        beforeOpen: function () {
            // 'this' is the AjaxOverlay instance
        },
        // Nachdem das Overlay geöffnet wurde
        // After the Overlay opens
        afterOpen: function () {
            // gets called right after the AjaxOverlay has been opened
            // 'this' is the AjaxOverlay instance
        }
    }
}

Modals

Confirm, Alert und Notify Modals können durch bestimmte HTML Klassen automatisch auf Links und Buttons gesetzt werden:
Confirm, Alert und Notify Modals use certain HTML classes to automatically set links and buttons

Optionen:
Options

  • data-type Attribut: primary (default), success, warning, info, danger
  • data-title Attribut (optional): Titel des Modals
  • data-label Attribut (optional): Titel des Okay Buttons
  • data-cancel Attribut (optional): "false", wenn kein Close Button dargestellt werden soll
<button class="dialog" href="/some/action" data-content="Do you really want to delete this item?" data-title="Heads up!" data-type="danger">Delete</button>

Ein Modal kann auch per Javascript erstellt werden:

// Modal.show(options[, uid]);
Modal.show({
    type: 'primary' || 'success' || 'warning' || 'info'
    title: 'Title', // Titel des Modals
    content: '', // Inhalt des Modals
    href: '' // target url for okay button (optional),
    cancel: true, // (optional)
    label: 'Okay' // (optional)
});

Datepicker

Ein Datepicker lässt sich durch folgendes Markup initialisieren:
A Datapicker is initialised using the following markup:

<input type="text" class="datepicker form-control" />

Eine Daterange wie folgt:
A data range as follows:

<div class="input-daterange input-group">
    <input type="text" class="form-control" name="start" />
    <span class="input-group-addon">to</span>
    <input type="text" class="form-control" name="end" />
</div>

(See https://github.com/eternicode/bootstrap-datepicker)

TimeAgo Felder

Um ein Datum im "Vor x"-Format darzustellen, reicht es aus, einem Tag die Klasse .timeago und als title Attribut ein valides Datum zu geben:
To display a date in "days ago" format, use Day in the '.timeago' class and set the 'title' attribute using a valid date as follows:

<span class="timeago" title="Mon Oct 20 2013 12:25:20 GMT+0200 (CEST)">21.10.2013</span>

Wird automatisch zu (etwas ähnlichem wie):
There is an automatic conversion to (something like) as follows:

<span class="timeago" title="20.10.2013">Vor zwei Tagen</span>

Sortable List

Sortierbare Listen sind durch das jQuery Modul Nestable ermöglicht.
Using the jQuery Modul Nestable to create sortable lists as follows:

List Options

  • data-action Attribute (required): the URL to make the sorted list persistent
  • data-depth Attribute: The maximum depth in a nested list Die maximale tiefe einer Verschachtelten Liste (default: 0)
  • data-active: When set to 'false' an event is needed to activate the sort capability (default: 'true') Wenn auf false gesetzt, muss der Benutzer das Sortieren erst aktivieren (default: true)

Item Option

  • data-id Attribute (required): The content ID die ID des jeweiligen Inhaltes

Example Markup:

<!-- das Attribut data-action enthält die URL
    über die die neue Sortierung gespeichert werden kann -->
<!-- the attribute data-action contains the URL
    that points to the place where the new sort will/can be made persistent -->
<div class="sortable" data-action="/save/my/sort" data-depth="5" data-active="false">
    <div class="sortable-actions">
        <!-- (Optional) Button um das Sortieren zu aktivieren -->
        <!-- Nur wichtig wenn data-active="false" -->
        <!-- (Optional) Button to activate the sort -->
        <!-- only important when data-active="false" -->
        <button class="btn btn-success sortable-activate-action">
            Sort Sortieren
        </button>
        <!-- ein Link oder Button, der die aktion "Speichern" auslöst -->
        <!-- wichtig sind die Klassen .sortable-save-action und .is-hidden -->
        <!-- a Link or Button, that initiates the "Save" action -->
        <!-- in this context the classes '.sortable-save-action' and '.is-hidden' are important -->
        <button class="btn btn-success sortable-save-action is-hidden">
            Save list  Reihenfolge speichern
        </button>
        <!-- Ein Button um alle Änderungen rückgängig zu machen -->
        <button class="btn btn-success sortable-abort-action is-hidden">
            Interupt Abbrechen
        </button>
    </div>
    <ol class="sortable-list">
        <!-- das data-id Attribut enthält die ID des jeweiligen Inhaltes -->
        <!-- the data-id attribute contains the ID for the specific content -->
        <li class="sortable-item" data-id="1">
            <!-- das element, das dem Benutzer das Draggen erlaubt -->
            <!-- the element that permits dragging  -->
            <span class="sortable-handle"></span>
            <div class="sortable-item-inner">
                <!-- Inhalt des jeweiligen Items -->
                <!-- Content of the specific sort item -->
                Sortable Item No. 1!
            </div>
            <!-- weitere Verschachtelungen -->
            <!-- further nesting -->
            <ol class="sortable-list">
                <li class="sortable-item" data-id="941">
                    ...
                </li>
            </ol>
        </li>
        <li class="sortable-item" data-id="312">
            ...
        </li>
    </ol>
</div>

Das Handle kann entweder ein eigenes span Element sein (siehe oben), oder mit dem gesamten Listen Inhalt verknüpft sein:
**The handle can either be a span Element (see above), or bound with the entire list content:

...
<li class="sortable-item" data-id="1">
    <div class="sortable-item-inner sortable-handle">
        ...
    </div>
</li>
...

In beiden Fällen muss der .sortable-handle allerdings ein direktes Child des .sortable-items sein.
In both cases .sortable-handle should be a child of .sortable-item.

Verwendete thirdparty Frameworks (siehe auch bower.json)

Namenskonvention

  • Klassen: .my-class
  • IDs: #my-id
  • Zustände: .is-active, .is-hovered, .is-hidden

Struktur

Sass Variablen und base-styles sind im Ordner "styles/base" anzulegen.

Es wird unterschieden zwischen modulen, partials. Partials sind bestimmte bereiche der Website (z.B. die Top Navigation Bar).
Module sind wiederverwendbare Styles, die sowohl von Partials als auch anderen Modulen benutzt werden. Die .nav Klasse von Bootstrap ist ein perfektes Beispiel für ein Modul.
Ansatzweise findet der Smacss Ansatz Anwendung.

Bootstrap

Bootstraps Variablen können überschrieben werden in source/styles/base/_bootstrap_override.scss.