Construa um gnarly todolist com Meteor.js

Então, analisei como construir um aplicativo barebones, funcional e fullstack com meteoro em Como construir um aplicativo usando Meteor.js . Dito isso, não é impressionante. Na semana passada, fiz o excelente curso do Eventedmind sobre como construir um aplicativo de várias páginas com meteoro de ferro . É um pouco como construir um blog barebones completo, com autenticação.

O DEMO está aqui. Eu usei a implantação embutida de meteoros.

SOURCE CODE está nos githubs

Primeiramente

Em vez de copiar o tutorial passo a passo do evento mental, irei apontar algumas partes interessantes de como o aplicativo funciona.

eu deixo você terminar

O aplicativo é estruturado usando a ferramenta de linha de comando Iron. O ferro é como o yeoman, mas para aplicativos de meteoros. Se isso não ajudar, ignore. Para lidar com o roteamento com meteoros, você provavelmente usará o Iron Router. Este é um andaime de ferro. É uma forma opinativa e estruturada de construir seus aplicativos de meteoros. Para construir um esqueleto vazio você vai como:

$ npm install -g iron-meteor
$ git clone
<repo_url>
$ cd meteor
-todos
$ iron

Em seguida, adicionaremos pacotes úteis e removeremos alguns de baixa qualidade.

$ iron add less
$ iron
add bootstrap
$ iron
add accounts-ui
$ iron
add accounts-meteor-developer
$ iron
add momentjs:moment
$ iron
remove autopublish
$ iron
remove insecure

As contas-interface do usuário e contas-desenvolvedor-meteoro eliminam toda a dor de cabeça de autenticação. Existem também pacotes accounts-facebook, accounts-google, accounts-twitter. Os dois últimos vêm por padrão e queremos desativá-los. O Insecure permite que você grave no banco de dados a partir do console (não quero isso) e a publicação automática torna todos os dados acessíveis. O código do tutorial usa as práticas recomendadas.

Estrutura

Todo o código do aplicativo está na pasta do aplicativo . Este é exatamente o código que seria gerado pela execução meteor create my_app_name. Se você quiser executar qualquer meteorcomando, faça cd na pasta do aplicativo e pronto. (É exatamente assim que implantei [ $ cd app $ meteor deploy]).

Os principais pastas dentro de aplicativo que se preocupar são client, libe server. Conforme especificado na documentação, o cliente funciona apenas no cliente, o servidor apenas no servidor e a lib é executada em ambos.

lib

Vou começar com lib, porque é onde nossas rotas são definidas. Eles parecem:

Router.configure({
layoutTemplate
: 'MasterLayout',
loadingTemplate
: 'Loading',
notFoundTemplate
: 'NotFound'
});

Router.route('/', {
name
: 'home',
controller
: 'HomeController',
action
: 'action',
where: 'client'
});

Router.route('/todos/:_id', {
name
: 'todos.detail',
controller
: 'TodosController',
action
: 'detail',
where: 'client'
});

Router.route('/todos/:_id/edit', {
name
: 'todos.edit',
controller
: 'TodosController',
action
: 'edit',
where: 'client'
});

Router.route('/users/:_id', {
name
: 'users.detail',
controller
: 'UsersController',
action
: 'detail',
where: 'client'
});

Em meu post anterior sobre meteoro, não usamos controladores, agora usamos. O controlador tem esta aparência:

TodosController = RouteController.extend({
subscriptions
: function () {
this.subscribe('todoDetail', this.params._id);
},

// set data context for controller
data
: function () {
return Todos.findOne({_id: this.params._id});
},

detail
: function(){
this.render('TodosDetail', {});
},

edit
: function(){
// reactive state variable saying we're in edit mode
this.state.set('isEditing', true);

this.render('TodosDetail');
}
});

E, finalmente, temos uma coleção em :app/collections/todos.js

Todos = new Mongo.Collection('todos');

// if server define security rules
// server code and code inside methods are not affected by allow and deny
// these rules only apply when insert, update, and remove are called from untrusted client code

if (Meteor.isServer) {
// first argument is id of logged in user. (null if not logged in)
Todos.allow({
// can do anythin if you own the document
insert
: function (userId, doc) {
return userId === doc.userId;
},

update
: function (userId, doc, fieldNames, modifier) {
return userId === doc.userId;
},

remove: function (userId, doc) {
return userId === doc.userId;
}
});

// The deny method lets you selectively override your allow rules
// every deny callback must return false for the database change to happen
Todos.deny({
insert
: function (userId, doc) {
return false;
},

update
: function (userId, doc, fieldNames, modifier) {
return false;
},

remove: function (userId, doc) {
return false;
}
});
}

Isso é um monte de código, mas não é para o que faz.

A coleção de tarefas cria uma coleção do MongoDB no banco de dados que é acessível na Todosvariável global no cliente e no servidor. Os callbacks de permissão e negação lidam com as permissões e segurança do nosso banco de dados.

Servidor

A coisa mais legal do servidor é o código que publica nossos dados:

/**
* Meteor.publish('items', function (param1, param2) {
* this.ready();
* });
*/


var allUsersCursor = Meteor.users.find({}, { fields: { profile: 1 }});
var getCursorForUser = function(id){
return Meteor.users.find({_id: id}, {fields: { profile: 1 }})
};

Meteor.publish('todos', function () {
// no data published if you're not logged in
if(!this.userId) return this.ready();

// only allow people to see their own todos
// this is currently logged in user
return Todos.find({userId: this.userId});
});

Meteor.publish('todoDetail', function (id) {
if(!this.userId) return this.ready();

var todo = Todos.findOne({ _id:id });
// get cursors for user who owns todo and todo itself
// fields specifies what to make available
return [
getCursorForUser
(todo.userId),
Todos.find({_id: id}),
Comments.find({todoId: id}, { sort: {createdAt: 1}})
];
});

Meteor.publish('users', function (/* args */) {
if(!this.userId) return this.ready();

// publish all users but specify which fields to make available
return allUsersCursor;
});

Meteor.publish('user', function (userId) {
if(!this.userId) return this.ready();

// publish user data and their todos
return [
getCursorForUser
(userId),
Todos.find({userId: userId})
];
});

No meteoro, você especifica no servidor quais dados tornar acessíveis por meio das funções de publicação. No cliente, você assina esses dados. Lidamos com nossas assinaturas no controlador. Portanto, role para cima e verifique o código do controlador. Você vai ver:

subscriptions: function () {
this.subscribe('todoDetail', this.params._id);
},

// set data context for controller
data
: function () {
return Todos.findOne({_id: this.params._id});
},

Então, publicamos todoDetail no servidor e, em seguida, o assinamos no controlador. Isso separa onde os dados podem ser acessados ​​em seu aplicativo. O valor dos dados é o que está especificamente disponível em seus modelos. Confira este guia para contextos de dados . A diferença entre contextos de dados e assinaturas me confundiu um pouco. Vamos ver como funciona no …

cliente

Nós geramos templates usando e comandos como esse. Você pode ver que o aplicativo está muito quebrado e cada arquivo html tem um arquivo javascript associado a ele. É aqui que entra o contexto dos dados. Cada modelo tem um nome e funções associadas . Os ajudantes basicamente usam o contexto de dados para mostrar os dados. Os eventos são onde você lida com o envio de formulários, cliques, focos etc. usando basicamente o jQuery direto. Sem diretivas nem nada. Aqui está o código para mostrar um detalhe todos e ser capaz de editá-lo:$ iron g:template todos/todos_listhelpersevents

app / client / templates / todos / todos_detail / todos_detail.js
“ `
Template.TodosDetail.events ({
// submit edit todo form
‘submit form.edit-todo’: function (e, tmpl) {
e.preventDefault () ;

    var subject = tmpl.find('input[name=subject]').value;
var description = tmpl.find('[name=description]').value;
var id = this._id;

Todos.update({_id: id}, {
$set
: {
subject
: subject,
description
: description,
updatedAt
: new Date
}
});

// reroute and pass data context
Router.go('todos.detail', {_id: id})
}

});

Template.TodosDetail.helpers ({
isMyTodo: function () {
return this.userId === Meteor.userId ();
},
todoOwner: function () {
// contexto de dados é o todo
var todo = this;
return Meteor.users .findOne ({_ id: todo.userId});
}
});
“ `

Portanto, especifique eventos para o modelo com seletores jquery como os valores, como uma função que obtém o modelo e o evento como argumentos. Escrevemos no banco de dados usando a coleção global . e são do MongoDB. Meteor usa mongodb, então familiarize-se. A razão pela qual podemos fazer esta atualização é porque permitimos quando definimos a coleção todos. No controlador todos (mostrado no diretório lib), definimos o valor como o todo específico cujo id é um parâmetro da barra de url.submit form.edit-todoTodosupdate()$setdata

O controlador todos define o contexto dos dados:
data: function () { return Todos.findOne({_id: this.params._id}); },

Portanto, em nosso auxiliar de modelo thisestá o contexto de dados que especificamos no controlador. É meio louco e fácil de se confundir. É por isso que damos a ele um nome de variável. Somos humanos, não máquinas.

O código do qual estou falando é onde usamos o contexto de dados do controlador:
todoOwner: function(){ // data context is the todo var todo = this; return Meteor.users.findOne({_id: todo.userId}); }

Dê uma olhada no código. Eu recomendo o screencast eventedmind, foi assim que eu construí isso. Se você tiver mais perguntas, entre em contato comigo no twitter .

Outras coisas úteis

Autenticação

{{> loginButons }} no modelo ajuda a configurar a autenticação.

Configure programaticamente o provedor OAuth:$ iron add service-configuration

Comece

server/bootstrap.jsé para onde vai qualquer código que precise ser executado quando inicializamos o servidor. Acesse variáveis ​​de ambiente usandoconfig/development/env.shprocess.env['variable_name']

Base de dados

Ative um shell mongo para o banco de dados atual com $ iron mongo. Como são os registros do usuário no banco de dados:

$ meteor mongo
MongoDB shell version: 2.6.7
connecting to
: 127.0.0.1:3001/meteor

meteor
:PRIMARY> show collections
meteor_accounts_loginServiceConfiguration
meteor_oauth_pendingCredentials
system
.indexes
users
meteor
:PRIMARY> db.meteor_accounts_loginServiceConfiguration.find().pretty()
{
"_id" : "MJRdreKXQ8NCPnhH7",
"service" : "meteor-developer",
"clientId" : "Dyv7PX6SWqQXhLCxZ",
"secret" : "hDePgZTKMMw6ksGKbGj3gC75TwXRpkhECh",
"loginStyle" : "popup"
}

O texto acima é o que o meteoro usa para registrar nossos usuários. A seguir está a aparência dos registros do usuário.

meteor:PRIMARY> db.users.find().pretty()
{
"_id" : "kj8iq7dpzbMGseDPy",
"createdAt" : ISODate("2015-05-21T21:59:46.840Z"),
"services" : {
"meteor-developer" : {
"accessToken" : "EzYgczXRvYRR6AyMG",
"expiresAt" : NaN,
"username" : "connorleech",
"emails" : [
{
"address" : "connorleech@gmail.com",
"primary" : true,
"verified" : true
}
],
"id" : "zkS6ewa9nyYcPAgah"
},
"resume" : {
"loginTokens" : [ ]
}
},
"profile" : {
"name" : "connorleech"
}
}

Usuários de acesso no console do navegador, digitando: .Meteor.users.find().fetch()

.find()retorna um cursor. transforma o cursor em uma matriz.fetch()

regras de segurança para evitar gravações não autorizadas no banco de dados.lib/collections/todos.js

comandos de ferro

Adicionar uma função de publicação: Criar coleção no banco de dados mongo:$ iron g:publish todos
$ iron g:collection todos

Assine as publicações em seu controlador.

Heroku

Tentei implantar com isto:

$ heroku login
$ heroku git
:remote -a <name-of-heroku-app>
$ heroku config
:set BUILDPACK_URL=https://github.com/lirbank/meteor-buildpack-horse.git
$ heroku config
:set ROOT_URL=https://<yourapp>.herokuapp.com
$ git push heroku master
$ heroku open

Acho que algo estava errado com minhas variáveis ​​de ambiente. A plataforma de implantação de meteoros fornecida funciona para mim por enquanto.

Espero que isso seja útil! sim estou no twitter