Skip to content
Tom Oberhauser edited this page Jan 19, 2015 · 25 revisions

meteor

Grundlagen

  • call meteor in the bash in your porjectfolder --> meteor will start
  • Strg+C in the bash --> stops running meteor

Ordnerstruktur

  • /server code only runs on the server
  • /client code only runs on the client
    • /client/compatibility JavaScript libraries that rely on variables declared with var at the top level being exported as globals.
  • /lib gets loaded before anything else
  • /public static assets (images, fonts ...)
  • /private static assets, server only
  • Any main.* file is loaded after everything else

everything else runs on server and client

wo speichert meteor seine MongoDB?

Die Datenbank liegt unter .meteor/local/db. Änderungen werden allerdings nicht von git erkannt weil meteor selbstständig eine .gitignore anlegt:

    # .meteor/.gitignore
    local

Templates

... Template sections, on the other hand, are converted into JavaScript functions, available under the Template namespace. It's a really convenient way to ship HTML templates to the client...

Die meteor Template Engine nennt sich wohl Spacebars. Hier ist die Doku dazu

Generell kann man die Template Tags in irgend welchen .html Dokumenten verwenden und in anderen .js Dateien dann die Helper zu dem jeweiligen Template definieren.

Beispiel-Template:

<template name="postsList">
  <div class="posts">
    {{#each posts}}
      {{> postItem}}
    {{/each}}
  </div>
</template>

Beispiel Helper für postsList:

var postsData = [
    {
        title: 'Introducing Telescope',
        author: 'Sacha Greif',
        url: 'http://sachagreif.com/introducing-telescope/'
    },
    {
        title: 'Meteor',
        author: 'Tom Coleman',
        url: 'http://meteor.com'
    },
    {
        title: 'The Meteor Book',
        author: 'Tom Coleman',
        url: 'http://themeteorbook.com'
    }
];

Template.postsList.helpers({
    posts: postsData
});

innerhalb von <template name="postsList"> wird ein anderes Template postItem angesprochen.

<template name="postItem">
  <div class="post">
    <div class="post-content">
      <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
    </div>
  </div>
</template>

da postItem innerhalb von postList ausgeführt wird erhält es automatisch auch die Helper von postList. Das bedeutet obwohl im dazugehörigen Helper:

Template.postItem.helpers({
  domain: function() {
    var a = document.createElement('a');
    a.href = this.url;
    return a.hostname;
  }
});

kein title und domain definiert werden, hat das Template die Daten vom übergeordneten Template.

Collections

Generelles

  • meteor startet eine MongoDB im Hintergrund
    • Konsole kann via meteor mongo aufgerufen werden
  • eine Collection hält Daten für alle Benutzer gleichzeitig
    • kein Sessionbezug
  • wenn die Collection nicht innerhalb der Ordner /client oder /server initialisiert wird steht sie beiden Komponenten zur Verfügung
  • Das Schlüsselwort var limitiert den Scope des Objekts auf die aktuelle Datei. Ohne var ist es in der kompletten App verfügbar

Mongo vs. MiniMongo

Die Collection auf der Server-Seite entspricht der Collection in der Datenbank und stellt das API zur Datenbank zur Verfügung, während die Collection auf der Client Seite (genannt MiniMongo) einer Teilmenge der realen Datenbank darstellt.

Benutzung

Posts = new Meteor.Collection('posts');
Posts.find().count(); // Anzahl der Objekte
Posts.find();  // LocalCollection Cursor
Posts.find().fetch(); // Alle Daten aus der Collection
Posts.insert({title: "new post"}); // neues Objekt hinzufügen
  • $ meteor reset löscht die Datenbank und setzt das Projekt zurück

Subscribe

Per default ist das meteor-Package autopublish aktiv, welches jede Collection ins Frontend lädt. Die Collection wird dann ungefragt an alle Clients verteilt. Dies lässt sich durch

$ meteor remove autopublish

entfernen. Jetzt muss die Collection vom Server published, und vom Client subscribed werden

Bsp:

// --- server/publications.js ---
Meteor.publish('posts', function() {
  return Posts.find();
});
// ------------------------------

// --- client/main.js ---
Meteor.subscribe('posts');
// ----------------------

In diesem Beispiel wird allerdings eine komplette Kopie der Datenbank an den Client geschickt. Das ist sicherheits- und performancetechnisch doof. Deshalb kann man im serverseitigen Meteor.publish() eine Selektion einbauen.

// on the server
Meteor.publish('posts', function(author) {
return Posts.find({flagged: false, author: author});
});
// on the client
Meteor.subscribe('posts', 'bob-smith');

Absichern von Collections

Zu Beginn eines Meteor-Projekts wird das Package insecure mit geladen. Das sorgt dafür dass es keine Einschränkungen beim insert() in eine Collection gibt. Dies kann durch

meteor remove insecure

entfernt werden. Sinn davon ist es z.B. nur eingeloggten Usern die Möglichkeit zu geben Collections zu verändern. Hier ein Bespiel:

Posts = new Meteor.Collection('posts');

Posts.allow({
  insert: function(userId, doc) {
    // only allow posting if you are logged in
    return !! userId;
  }
});
  • Posts.allow() erhält ein Objekt mit Optionen

  • function(userId, doc) wird hier beim Insert aufgerufen

    • wenn diese Funktion true liefert dann darf inserted werden
    • userId ist die ID des eingeloggten Users
    • doc ist das Element welches inserted werden soll
    • das !! ist sowas wie ein TypeCast auf boolean (erst wird durch !userId getestet ob sie false ist, dann wird durch !!userId getestet ob sie true ist. Macht man wohl so um sicherzustellen dass hier definitiv ein Boolean zurückkommt.)

weitere Notizen zu Collections

  • Collection.insert(...) returns the generated id

Routing

Routing dient dazu spezielle Informationen aus der URL zu extrahieren. Beispiel hierfür ist:

We’d like these pages to be accessible via a permalink, a URL of the form http://myapp.com/posts/xyz (where xyz is a MongoDB _id identifier) that is unique to each post.

Ohne Routing könnte man, für den Fall dass statt http://localhost:3000 z.b. http://localhost:3000/foo/bar eingegeben wird, kein gesondertes Verhalten definieren.

Via Routing können auch load-screens definiert werden, welche angezeigt werden solange auf Daten gewartet wird. Der Iron-Router bringt ebenfalls eine eigene Datenkontext Verwaltung mit. Das bedeutet Datenquellen können über den Router subscribed werden und die Daten stehen dann den geladenen Templates zur Verfügung.

Grundlegende Begriffe

Route

Ist das gesamte Konstukt einer bestimmten Routings. Es sagt der Anwendung wohin gegangen und was getan werden soll sobald eine URL auftaucht.

Path

ist eine konkrete URL innerhalb der App. z.B. /terms_of_service oder /search?keyword=foo

Segment

Ein Segment eines Paths sind die einzelnen Teile zwischen den /

Hooks

Hooks sind Aktionen die vor oder nach einem Routing-Prozess ausgeführt werden sollen. Z.B. überprüfen ob der User genügend Rechte zum Anzeigen der Seite hat.

Filters

Filters sind Hooks die global für eine oder mehrere Routen konfiguriert werden

Route Templates

Jede Route muss auf ein Template zeigen. Sofern kein Template spezifiziert ist sucht der Router eins mit dem gleichen Namen wie die Route

Layouts

Layouts sind der HTML Code der das Template umschließt. Dieser ändert sich nicht.

Controllers

Controller werden benötigt wenn viele Templates die selben Parameter benutzen. Dann kann man, statt Code zu duplizieren, die Routen mit einem einzelnen Controller konfigurieren, der die Routing-Logik beinhaltet.

Routing mit Iron-Router

Iron.Router - GitHub Pages

der {{> yield}} Helper

Das Iron Router Package bringt einen speziellen Template - Helper mit. {{> yield}} sorgt dafür dass an dieser Stelle das, aktuell zur Route passende, Template eingefügt wird.

very basic routing

Im Buch wird empfohlen eine Datei lib/router.js anzulegen. Da der Inhalt des /lib Ordners vor allen anderen ausgeführt wird, empfiehlt es sich hier Konfigurationen anzulegen. In der Datei wird im ersten Schritt folgendes getan:

// legt default Layout für alle Routen fest
Router.configure({
  layoutTemplate: 'layout'
});

// legt fest dass unter "/" die Route `postsList` aktiv werden soll
Router.map(function(){
  this.route('postsList',{path: '/'});
});

By default, Iron Router will look for a template with the same name as the route. In fact, it will even look for a path based on the route name, meaning that if we hadn’t defined a custom path (which we did by providing a path option in our route definition), our template would’ve been accessible at URL /postsList by default.

Ein weiterer Vorteil von Routen ist dass man sie durch ihren Namen auch weiterverwenden kann... z.B. kann man einen Link auf das Ziel der aktuellen postsList-Route durch

<a href="{{pathFor 'postsList'}}">bla</a>

erzeugen.

auf Daten warten

Mit Iron-Router kann man ein loadingTemplate definieren, welches greift solange eine bestimmte Funktion noch nicht ausgeführt ist.

Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});

// wird ausgeführt bevor das Routing aktiv wird
Router.onBeforeAction('loading');

Wenn man das spin package installiert hat, kann man hiermit einen einfachen Spinner auf der Website erzeugen der den Ladevorgang verdeutlicht.

<template name="loading">
  {{> spinner}}
</template>

dynamisches Routing

Mit dynamischen Routen können Parameter aus den Segmenten des Path extrahiert werden, wie hier z.B. eine id

Router.map(function(){
  this.route('postsList',{path: '/'});
  this.route('postPage', {
    path: '/posts/:_id',
    data: function() { return Posts.findOne(this.params._id); }
  });
});

die unter data: definierten Daten stehen dem geladenen Template dann direkt zur Verfügung. Die Daten können dann innerhalb des Templates via this angesprochen werden.

dynamic named route helper

Ok, jetzt wirds magisch. Im obrigen Beispiel wurde ja eine dynamische Route definiert. Wenn man jetzt via

<a href="{{pathFor 'postPage'}}"...

einen Link zur postPage erzeugen lässt erkennt der Router automatisch das dafür wohl eine id benötigt wird und schaut im geladenen Datenkontext der postPage nach!

weitere Notizen zu Routing

Sessions

Im Session-Objekt kann man Daten speichern die nur in der aktuellen Sitzung erhalten bleiben. (Bis zum Browser-Reload, beim HotCode Reload bleiben die Daten erhalten) Das ganze ist reaktiv, d.h. wenn sich etwas ändert werden auch alle Helper aktualisiert und neu gerendert. Das Session - Objekt ist global verfügbar, d.h. Änderungen können von überall aus geschehen.

Session.set('mySessionProperty','foo');
Session.get('mySessionProperty');

Auf die Daten kann dann innerhalb des HTMLs mit {{mySessionProperty}} zugegriffen werden denn diese als Template Helper hinzugefügt wurden:

Template.layout.helpers({
  pageTitle: function() { return Session.get('pageTitle'); }
});

Autorun mit Deps

Deps.autorun(function() {
  alert(Session.get('message'));
});

wird immer ausgeführt wenn sich an einer beteiligten Datenquelle etwas ändert. Es macht wohl Sinn das ganze in Meteor.startup(function(){...}) zu kapseln, da dieser Code erst ausgeführt wird wenn alles geladen ist (und auch sichergestellt ist dass eventuelle Collections geladen sind.)

User Accounts

Durch das hinzufügen des accounts packages legt Meteor eine neue Collection namens Meteor.users an. Zum Registrieren von Usern gibt es ein gefühltes Kilo an Packages auf atmospherejs. Im Tutorial wird mrt:accounts-ui-bootstrap-dropdown verwendet. Dies kann dann via {{> loginButtons}} überall ins Template eingebaut werden und bringt alle Funktionalitäten zum Login und zum erstellen eines neuen Accounts mit. Im Beispiel sieht dass dann auf der Javascript Konsole so aus:

Meteor.users.findOne();
Object {_id: "EirZiT75izQ44L47n", username: "tom"}
Meteor.user(); //gibt das aktuell eingeloggte Nutzerobjekt zurück
Object {_id: "EirZiT75izQ44L47n", username: "tom"}

Das accounts Package published nur den aktuellen Nutzer automatisch. Das bedeutet für den aktuellen Client sieht es immer so aus als ob Meteor.users.find().count() nur 1 ist. (Macht ja auch Sinn, ansonsten könnte jeder Client alle User durchschauen). In der mongodb selber findet man allerdings via db.users.find().count() die korrekte Anzahl der User heraus.


meteorite wird seid Node 0.9.0 nicht mehr benötigt

Auf Seite 11 wird empfohlen das npm-package meteorite zu installieren. Laut der Doku zu meteorite wird das allerdings nicht mehr benötigt. Es sieht auf den ersten Blick erstmal so aus als ob es keinen wirklichen Unterschied gibt ob man das Projekt mit

$ mrt create microscope

oder

$ meteor create microscope

anlegt.

Auf Seite 12 wird durch mrt add bootstrap ein Atmosphere-Bootstrap-Package installiert. Das lässt sich ohne mrt via meteor add bootstrap tun.

iron-router ohne mrt installieren

iron:router package | Atmosphere

$ meteor add iron:router

Seite 122 Fehler beim Routing

Der Code auf Seite 122 wirft folgenden Fehler:

Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?

Grund dafür ist eine Änderung im iron:router Package seit Version 1.0. Der falsche Code lautet:

var requireLogin = function(pause) {
  if (!Meteor.user()) {
    if (Meteor.loggingIn()) {
      this.render(this.loadingTemplate);
    } else {
      this.render('accessDenied');
    }
    pause();
  }
}

richtig ist:

var requireLogin = function() {
  if (!Meteor.user()) {
    if (Meteor.loggingIn()) {
      this.render(this.loadingTemplate);
    } else {
      this.render('accessDenied');
    }
  } else {
    this.next();
  }
}

Generelles

Technologien / Tools

meteor

Lösungen

etc

Sprints

Clone this wiki locally