steg 02: implementering – intro

Yes, nu var det äntligen dags för implementeringssteget i kursen.

Jag har under morgonen bara börjat med att placera några av de filerna där jag vill ha dem, skapat modulerna och läst på lite mer kring Require.js, som börjar klarna lite mer för mig nu. De filerna som har skapats har nu pushats upp till git och kan ses där.

Jag har nu kommit igång med mitt projekt, och fortsätter med att koda upp de första filerna (main.js, app.js och router.js) men jag upplever problem med require.js när jag ska definera inladdningsordning med hjälp av ”order!” eftersom jag får ett javascript fel som säger att den inte hittar order.js. Visst, den hittar inte filen, men jag har ju inte anget någonstans att den ska använda någon order.js fil? Eller måste man implementera en sådan om man vill använda ”order!” i sökvägen? Fundersam här. Edit: Japp, order.js är tydligen ett plugin som man ska ladda ned, men det framgår tyvärr inte på den platsen där jag läste om Require.js.

Update: 17.57
Nu har jag gjort ingången för applikationen, även läst på en del om hur man använder sig utav underscores templating egenskaper för att skapa olika templates. Jag har dock ännu inte testat modellerna utan jag har endast arbetat med att få ut olika vyer och grundläggande css. Har även skapat en template som innehåller ett formulär för att skapa en ny Task.

create-task-template
Templates i Underscore skrivs inom en script-tagg i head. Man sätter bara type till text/template och även ett ID för att enklare tageta templaten.

1
2
3
4
5
6
7
8
<script type="text/template" id="create-task-template">
<h2>Create Task</h2>
<form action="#" method="POST">
    <label for="task-content">Task:</label>
    <input type="text" name="task-content" />
    <input type="submit" name="task-submit" value="Add task" />
</form>
</script>

De som har tagit fram kommer nu att commitas till Github repositoriet. (Länk i sidebar).

steg 01 : kodorganisering

Då var vi på det sista steget innan det är dags att implementera Todo applikationen.

Innehåll

  1. Filstruktur
  2. Moduler (AMD)
  3. Laddning av moduler (Require.js)

Filstruktur

Då var det dags att bestämma hur filstrukturen i applikationen är tänkt att se ut. Egentligen så tycker jag inte att man behöver fundera speciellt mycket kring det här, huvudsaken tycker jag är att man bara delar upp de olika delarna av applikationen till passande mappar och filer. Figur 1 visar en liten illustration av hur jag har valt att dela upp min filstruktur.

Todo
|     docs
|     |
|     | // Documentation
|
|     js
|     |
|     |-- collections
|     |   | // collection modules.
|     |
|     |-- lib
|     |   | // javascript libs
|     |
|     |-- models
|     |   | // model modules
|     |
|     |-- views
|     |   | // views
|     |
|     |// app.js // main router
|
|-- index.html

Moduler

Jag har under morgonen nu suttit och läst igenom Writing modular javascript av Addy Osmani och försökt förstå hur man bäst kan uppnå loose coupling mellan de javascript moduler jag kommer att använda.

Vad jag kan läsa mig till, och förstå, så är AMD ett rekommenderat mönster för att skapa moduler där man kan definera beroenden (dependencies) och tillsammans med en script loader (t.ex. RequireJS eller curl.js) kan erbjuda dynamisk laddning av resurser (läs scripts). Läs mer min tänkta implementation av require.js.

Mönstret verkar ganska straight forward faktiskt, man använder define() för att definera en modul, innuti define så skapara du bara ett singelton objekt med alla dina funktioner som den här modulen ska ha och längst ner returnerar du singelton objektet.

Exempel på en modul

1
2
3
4
5
6
7
8
9
10
define(
    var myModule = {
        doStuff : function() {
            // Do something here.
        }
 
    }
 
    return myModule;
);

As clean and easy as that. Exemplet ovan är ett väldigt enkelt exempel som bara definerar en modul, men man kan även ange beroenden som Addi går igenom i sin artikel.

Exempel på en modul i Backbone

Här har jag försökt, efter att ha läst Addis artikel, försökt ge mig på att definera en modul enligt AMD som använder Backbone. Modulen är en model baserad på Task objektet som finns beskrivet här.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
define( 
  var TaskModel = Backbone.RelationalModel.extends({
    // Define the relations of the object using relational.
    relations : [
      {
        type : Backbone.HasOne,
        key : 'userId',
        relatedModel : 'User',
        includeInJSON : Backbone.Model.prototype.idAttribute,
        collectionType : 'UserCollection'
      },
      {
        type : Backbone.HasOne,
        key : 'categoryId',
        relatedModel : 'Category',
        includeInJSON : Backbone.Model.prototype.idAttribute,
        collectionType : 'CategoryCollection'
      }
    ],
 
    defaults : {
      taskId : null,
      content : null,
      time : null,
      completed : false,
      userId : null,
      categoryId : null  
    },
 
    initialize : function() {
      // Empty function for future need.
    },
 
    /**
      * @param {Array} attrs The attributes to validate.
      * @returns {String} If something does not validate, return string
      * (throw error in backbone.) does not run set or save on model.
      */
    validate : function( attrs ) {
      if (  attrs.taskId === null || attrs.content === null || 
            attrs.time === null || attrs.completed === null || 
            attrs.userId === null || attrs.categoryId === null ) {
             return "The task object does not validate."; 
      }
    }
  })
 
  return TaskModel;
);

Laddning av moduler (Require.js)

Jag förstår nu hur jag ska använda mig utav Require.js för att ladda in filerna vid behov vid körning av applikationen.

För att använda Require.js för att ladda in applikationen efter att Require.js är laddat, så använder man ett speciellt attribut på script-taggen som ser ut så här:

1
<script data-main="scripts/main" src="scripts/require.js"></script>

data-main attributet säger åt Require.js ska ladda filen main.js som finns i scripts katalogen i applikations katalogen efter att Require.js har laddats färdigt.

Sen är det bara att fortsätta och deklarera behov i resten av applikationen genom att använda sig utav Require.js API

steg 01 : moduler och funktioner

Nu är dags att ta fram de moduler/funktioner som min applikation kommer att använda sig utav. Som David säger på kurswebben så kan dessa komma att förändras under implementerings steget så dessa kod-definitioner får man se som ”generella” och kan komma att förändras. De jag har valt att skriva kod-definitioner för är Collection, View och Router.

Jag har valt att skriva alla dessa moduler i Backbone, och har endast identifierat de funktioner/metoder jag måste ha för respektive modul därför ser t.ex. alla vyer och collections likadana ut.

Collection

(Endast valt att definera vilken model respektive collection kommer använda).

TaskCollection

1
2
3
TaskCollection = Backbone.Collection.extends({
  model : Task,
});

UserCollection

1
2
3
UserCollection = Backbone.Collection.extends({
  model : User,
});

CategoryCollection

1
2
3
CategoryCollection = Backbone.Collection.extends({
  model : Category,
});

TodoEventCollection

1
2
3
TodoEventCollection = Backbone.Collection.extends({
  model : TodoEvent,
});

View

CreateUserView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CreateUserView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  /**
    * Renders the view.
    * @returns Returns this instance of the view.
    */
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

CreateTaskView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CreateTaskView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

CreateCategoryView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CreateCategoryView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  /**
    * Renders the view.
    * @returns   CreateUser Returns this instance of the view.
    */
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

CategoryView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CategoryView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  /**
    * Renders the view.
    * @returns   CreateUser Returns this instance of the view.
    */
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

SingleCategoryView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SingleCategoryView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  /**
    * Renders the view.
    * @returns   CreateUser Returns this instance of the view.
    */
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

EditTaskView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
EditTaskView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  /**
    * Renders the view.
    * @returns   CreateUser Returns this instance of the view.
    */
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

DeleteTaskView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DeleteTaskView = Backbone.View.extends({
  el : '', // The corresponding DOM element for the view.
 
  initialize : function() {
    // On instanciating render.
    this.render();
  },
 
  events : {
    // Bind events to the view.  
  },
 
  /**
    * Renders the view.
    * @returns   CreateUser Returns this instance of the view.
    */
  render : function() {
    // Do some stuff before rendering view.
    return this;
  }
});

Router

App

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app = Backbone.Router.extends({
  routes : {
    // Eventually more routes will be defined.
    '' : 'main',
    '*actions' : 'default'
  },
 
  default : function( actions ) {
    // Show an error message if route is not defined.
  },
 
  main : function( ) {
    // Starts the main application.
    // Instanciate Model, view and collections.
 
    // Check if localStorage store(s) exists.
      // If not, create the storage.
      // Load createUserView to create a user.
 
    // Check if user exists.
      // If not, load createuserView to create a user.
 
    // Execute rest of application .. (Dont know details yet.)
  }
 
});

bear css – bygger upp din css mail

Bear CSS är en väldigt smidig tjänst jag just stötte på som skapar en grundläggande css mall för din webbplats. Det som du behöver göra är att du skapar din html sida med de taggar och element du tänkt använda dig utav, med dess attribut satta så som id och klasser. Nästa steg är helt enkelt att bara gå till bearcss.com och ladda upp din htmlfil. Bearcss går igenom filen med en php dom parser och skapar en css mall för dig som du kan ladda ned.

Väldigt smidig tjänst måste jag säga, snabbare ju upp uppstarten av ett projekt ganska mycket genom att man inte behöva skriva ”grund css:en” själv. Me like!

steg 01 : objekt och data

Här kommer information om de ingående objekt som min To-do applikation kommer att använda. Kommer även lägga upp klassdiagram som är skapade med hjälp av http://www.yuml.me, som jag ett flertal gånger tidigare har använt till både klassdiagram och aktivitetsdiagram. Längre ned finns även kod-exempel på hur dessa modeller/objekt ser ut i Backnone. (Notera, dessa är endast ”skal” och kan komma att förändras lite vid implementerings-steget) Gå direkt dit.

Task

1
2
3
4
5
6
7
8
Task = {
    taskId : 1, // Integer.
    content : "Go to school.", // String.
    time : {}, // Date.
    completed : false, // Boolean.
    userId : 1, // Integer.
    categoryId : 1 // Integer.
}

Task objektet är mer eller mindre huvud objektet i To-do applikationen då det kommer att innehålla själva ”att göra uppgiften”. Det kommer att innehålla ett taskId för enklare borttagning och editering av uppgiften. Content som självklart innehåller själva uppgiften. Time egenskapen ska innehålla en tidsstämpel t.ex. 2012-01-20 19:38. Completed som är en boolean som kommer att ändras om man har slutfört ( kryssat checkboxen) uppgiften och självklart ett userId  som kopplar en task till en viss användare. Task objektet innehåller även en egenskaper, categoryId, som kopplar tasken till en specifik kategori.

User

1
2
3
4
User = {
    userId : 2, // Integer.
    name : "Anders", // String.
}

User är, som namnet säger, ett objekt som har två egenskaper. Den första egenskapen, userId är ett Id för användaren och det andra är name som är namnet på användaren.

Category

1
2
3
4
Category = {
    categoryId : 1, // Integer.
    label : "Work", // String.
}

Category är ett objekt som även den endast innehåller två egenskaper, ett Id och en label som ska fungera som namnet för kategorin.

TodoEvent

1
2
3
4
5
TodoEvent = {
    name : "Deleted", // String.
    time : {}, // Date.
    userId : 1, // Integer.
}

 Event, är tänkt att användas som ett objekt för händelseloggen där den ska presentera senaste händelser t.ex att användaren Anders tog bort uppgiften ”Go to school”.

Backbone reprsentation av modellerna

Task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Task = Backbone.RelationalModel.extends({
  // Define the relations of the object using relational.
  relations : [
    {
      type : Backbone.HasOne,
      key : 'userId',
      relatedModel : 'User',
      includeInJSON : Backbone.Model.prototype.idAttribute,
      collectionType : 'UserCollection'
    },
    {
      type : Backbone.HasOne,
      key : 'categoryId',
      relatedModel : 'Category',
      includeInJSON : Backbone.Model.prototype.idAttribute,
      collectionType : 'CategoryCollection'
    }
  ],
 
  defaults : {
    taskId : null,
    content : null,
    time : null,
    completed : false,
    userId : null,
    categoryId : null  
  },
 
  initialize : function() {
    // Empty function for future need.
  },
 
  /**
    * @param {Array} attrs The attributes to validate.
    * @returns {String} If something does not validate, return string
    * (throw error in backbone.) does not run set or save on model.
    */
  validate : function( attrs ) {
    if (  attrs.taskId === null || attrs.content === null || 
          attrs.time === null || attrs.completed === null || 
          attrs.userId === null || attrs.categoryId === null ) {
           return "The task object does not validate."; 
    }
  }
});

Category

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Category = Backbone.Model.extends({
  defaults : {
    categoryId : null,
    label : null;
  },
 
  initialize : function() {
    // Empty function for future need.
  },
 
  /**
    * @param {Array} attrs The attributes to validate.
    * @returns {String} If something does not validate, return string
    * (throw error in backbone.) does not run set or save on model.
    */
  validate : function( attrs ) {
    if (  attrs.categoryId === null || attrs.label === null ) {
           return "The category object does not validate."; 
    }
  }
});

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User = Backbone.Model.extends({
  defaults : {
    userId : null,
    name : null
  },
 
  initialize : function() {
    // Empty function for future need.
  },
 
  /**
    * @param {Array} attrs The attributes to validate.
    * @returns {String} If something does not validate, return string
    * (throw error in backbone.) does not run set or save on model.
    */
  validate : function( attrs ) {
    if (  attrs.userId === null || attrs.name === null ) {
           return "The User object does not validate."; 
    }
  }
});

TodoEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
TodoEvent = Backbone.Model.extends({
  // Define the relations of the object using relational.
  relations : [
    {
      type : Backbone.HasOne,
      key : 'userId',
      relatedModel : 'User',
      includeInJSON : Backbone.Model.prototype.idAttribute,
      collectionType : 'UserCollection'
    }
  ],
 
  defaults : {
    name : null,
    time : null,
    userId : null
  },
 
  initialize : function() {
    // Empty function for future need.
  },
 
  /**
    * @param {Array} attrs The attributes to validate.
    * @returns {String} If something does not validate, return string
    * (throw error in backbone.) does not run set or save on model.
    */
  validate : function( attrs ) {
    if (  attrs.name === null || attrs.time === null || 
          attrs.userId === null ) {
           return "The ToDoEvent object does not validate."; 
    }
  }
});

Återigen, notera gärna att dessa modeller är framtagna för att jag ska ha en god förståelse över hur mina modeller/objekt kommer att se ut. Jag har även försökt att definera relationerna genom ”relations:” attributet i varje model. Som ni säkert ser så extendar modellerna med relationer from RelationlModel istället för från Model, detta är ett krav för att relationerna ska fungera i Backbone. Eftersom jag använder localStorage så kan modellerna komma att förändras, men eftersom backbone-localStorage.js skriver över Backbone.sync() så borde inte mycket alls förändras. Mer om förändringarna kommer att kommenteras i det här inlägget, men även i framtida inlägg som relaterar till ämnet/problemet/lösningen.