I'm setting up a web application using Keystone JS.
I configured some routes, and views, and also set up a "virtual store" structure to add and extend sightseeing tours.
The problem, that due to some configuration, the site is not rendering the page properly.
The following message appears:
Sorry, an error occurred loading the page (500)
/home/pedromagalhaes/Projects/Projeto_Ag2/templates/views/tours.pug:8 6 | .row 7 |
8 | if! data.Tour 9 | h2 Invalid ride 10 | else 11 |
Can not read property 'Tour' of undefined
Some codes from my application.
**
Archive: Models / Tour.js
var keystone = require('keystone');
var Types = keystone.Field.Types;
/**
* User Tour
* ==========
*/
var Tour = new keystone.List('Tour', {
autokey: { from: 'nome_do_passeio', path: 'key', unique: true, Singular:'Passeio', Plural: 'Passeios'},
});
Tour.add({
nome_do_passeio: {type: String, label:"Nome do Passeio", initial:true},
image: { type: Types.CloudinaryImage },
category: { type: Types.Select, options: 'barco, vinícola', label:"Categoria", initial:true},
custo: { type: Types.Money, format: '$0.0,00', label:"Valor", initial:true },
data_do_passeio: { type: Types.Date, yearRange:(2000,2100), format: ('Do MMMM YYYY'), default: Date.now, label:"Data do Passeio", initial:true },
descricao_passeio: { type: Types.Html, wysiwyg: true, height: 200, label: "Descrição do Passeio"},
incluido: { type: Types.Html, wysiwyg: true, height: 200, label: "O que está incluído"},
cidade: { type: String, required: false, index: true, label:"Cidade" }
});
Tour.defaultSort = Tour.data_do_passeio;
Tour.defaultColumns = 'nome_do_passeio, custo, cidade, category, data_do_passeio';
Tour.register();
**
File: Routes / index.js
/**
* This file is where you define your application routes and controllers.
*
* Start by including the middleware you want to run for every request;
* you can attach middleware to the pre('routes') and pre('render') events.
*
* For simplicity, the default setup for route controllers is for each to be
* in its own file, and we import all the files in the /routes/views directory.
*
* Each of these files is a route controller, and is responsible for all the
* processing that needs to happen for the route (e.g. loading data, handling
* form submissions, rendering the view template, etc).
*
* Bind each route pattern your application should respond to in the function
* that is exported from this module, following the examples below.
*
* See the Express application routing documentation for more information:
* http://expressjs.com/api.html#app.VERB
*/
var keystone = require('keystone');
var middleware = require('./middleware');
var importRoutes = keystone.importer(__dirname);
// Common Middleware
keystone.pre('routes', middleware.initLocals);
keystone.pre('render', middleware.flashMessages);
// Import Route Controllers
var routes = {
views: importRoutes('./views'),
};
// Setup Route Bindings
exports = module.exports = function (app) {
// Views
app.get('/', routes.views.index);
app.get('/blog/:category?', routes.views.blog);
app.get('/blog/post/:post', routes.views.post);
app.get('/gallery', routes.views.gallery);
app.get('/tours', routes.views.tours);
app.get('/tours/:tour', routes.views.tour);
app.all('/contact', routes.views.contact);
// NOTE: To protect a route so that only admins can see it, use the requireUser middleware:
// app.get('/protected', middleware.requireUser, routes.views.protected);
};
**
File: Helpers / index.js
var moment = require('moment');
var _ = require('underscore');
var hbs = require('handlebars');
var keystone = require('keystone');
var cloudinary = require('cloudinary');
// Collection of templates to interpolate
var linkTemplate = _.template('<a href="<%= url %>"><%= text %></a>');
var scriptTemplate = _.template('<script src="<%= src %>"></script>');
var cssLinkTemplate = _.template('<link href="<%= href %>" rel="stylesheet">');
module.exports = function () {
var _helpers = {};
/**
* Generic HBS Helpers
* ===================
*/
// standard hbs equality check, pass in two values from template
// {{#ifeq keyToCheck data.myKey}} [requires an else blockin template regardless]
_helpers.ifeq = function (a, b, options) {
if (a == b) { // eslint-disable-line eqeqeq
return options.fn(this);
} else {
return options.inverse(this);
}
};
/**
* Port of Ghost helpers to support cross-theming
* ==============================================
*
* Also used in the default keystonejs-hbs theme
*/
// ### Date Helper
// A port of the Ghost Date formatter similar to the keystonejs - jade interface
//
//
// *Usage example:*
// '{{date format='MM YYYY}}'
// '{{date publishedDate format='MM YYYY''
//
// Returns a string formatted date
// By default if no date passed into helper than then a current-timestamp is used
//
// Options is the formatting and context check this.publishedDate
// If it exists then it is formated, otherwise current timestamp returned
_helpers.date = function (context, options) {
if (!options && context.hasOwnProperty('hash')) {
options = context;
context = undefined;
if (this.publishedDate) {
context = this.publishedDate;
}
}
// ensure that context is undefined, not null, as that can cause errors
context = context === null ? undefined : context;
var f = options.hash.format || 'MMM Do, YYYY';
var timeago = options.hash.timeago;
var date;
// if context is undefined and given to moment then current timestamp is given
// nice if you just want the current year to define in a tmpl
if (timeago) {
date = moment(context).fromNow();
} else {
date = moment(context).format(f);
}
return date;
};
// ### Category Helper
// Ghost uses Tags and Keystone uses Categories
// Supports same interface, just different name/semantics
//
// *Usage example:*
// '{{categoryList categories separator=' - ' prefix='Filed under '}}'
//
// Returns an html-string of the categories on the post.
// By default, categories are separated by commas.
// input. categories:['tech', 'js']
// output. 'Filed Undder <a href="blog/tech">tech</a>, <a href="blog/js">js</a>'
_helpers.categoryList = function (categories, options) {
var autolink = _.isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true;
var separator = _.isString(options.hash.separator) ? options.hash.separator : ', ';
var prefix = _.isString(options.hash.prefix) ? options.hash.prefix : '';
var suffix = _.isString(options.hash.suffix) ? options.hash.suffix : '';
var output = '';
function createTagList (tags) {
var tagNames = _.pluck(tags, 'name');
if (autolink) {
return _.map(tags, function (tag) {
return linkTemplate({
url: ('/blog/' + tag.key),
text: _.escape(tag.name),
});
}).join(separator);
}
return _.escape(tagNames.join(separator));
}
if (categories && categories.length) {
output = prefix + createTagList(categories) + suffix;
}
return new hbs.SafeString(output);
};
/**
* KeystoneJS specific helpers
* ===========================
*/
// block rendering for keystone admin css
_helpers.isAdminEditorCSS = function (user, options) {
var output = '';
if (typeof (user) !== 'undefined' && user.isAdmin) {
output = cssLinkTemplate({
href: '/keystone/styles/content/editor.min.css',
});
}
return new hbs.SafeString(output);
};
// block rendering for keystone admin js
_helpers.isAdminEditorJS = function (user, options) {
var output = '';
if (typeof (user) !== 'undefined' && user.isAdmin) {
output = scriptTemplate({
src: '/keystone/js/content/editor.js',
});
}
return new hbs.SafeString(output);
};
// Used to generate the link for the admin edit post button
_helpers.adminEditableUrl = function (user, options) {
var rtn = keystone.app.locals.editable(user, {
list: 'Post',
id: options,
});
return rtn;
};
// ### CloudinaryUrl Helper
// Direct support of the cloudinary.url method from Handlebars (see
// cloudinary package documentation for more details).
//
// *Usage examples:*
// '{{{cloudinaryUrl image width=640 height=480 crop='fill' gravity='north'}}}'
// '{{#each images}} {{cloudinaryUrl width=640 height=480}} {{/each}}'
//
// Returns an src-string for a cloudinary image
_helpers.cloudinaryUrl = function (context, options) {
// if we dont pass in a context and just kwargs
// then 'this' refers to our default scope block and kwargs
// are stored in context.hash
if (!options && context.hasOwnProperty('hash')) {
// strategy is to place context kwargs into options
options = context;
// bind our default inherited scope into context
context = this;
}
// safe guard to ensure context is never null
context = context === null ? undefined : context;
if ((context) && (context.public_id)) {
options.hash.secure = keystone.get('cloudinary secure') || false;
var imageName = context.public_id.concat('.', context.format);
return cloudinary.url(imageName, options.hash);
}
else {
return null;
}
};
// ### Content Url Helpers
// KeystoneJS url handling so that the routes are in one place for easier
// editing. Should look at Django/Ghost which has an object layer to access
// the routes by keynames to reduce the maintenance of changing urls
// Direct url link to a specific post
_helpers.postUrl = function (postSlug, options) {
return ('/blog/post/' + postSlug);
};
// Direct url link to a specific product
_helpers.tourUrl = function (tourSlug, options) {
return ('/tour/' + tourSlug);
};
// might be a ghost helper
// used for pagination urls on blog
_helpers.pageUrl = function (pageNumber, options) {
return '/blog?page=' + pageNumber;
};
// create the category url for a blog-category page
_helpers.categoryUrl = function (categorySlug, options) {
return ('/blog/' + categorySlug);
};
// ### Pagination Helpers
// These are helpers used in rendering a pagination system for content
// Mostly generalized and with a small adjust to '_helper.pageUrl' could be universal for content types
/*
* expecting the data.posts context or an object literal that has 'previous' and 'next' properties
* ifBlock helpers in hbs - http://stackoverflow.com/questions/8554517/handlerbars-js-using-an-helper-function-in-a-if-statement
* */
_helpers.ifHasPagination = function (postContext, options) {
// if implementor fails to scope properly or has an empty data set
// better to display else block than throw an exception for undefined
if (_.isUndefined(postContext)) {
return options.inverse(this);
}
if (postContext.next || postContext.previous) {
return options.fn(this);
}
return options.inverse(this);
};
_helpers.paginationNavigation = function (pages, currentPage, totalPages, options) {
var html = '';
// pages should be an array ex. [1,2,3,4,5,6,7,8,9,10, '....']
// '...' will be added by keystone if the pages exceed 10
_.each(pages, function (page, ctr) {
// create ref to page, so that '...' is displayed as text even though int value is required
var pageText = page;
// create boolean flag state if currentPage
var isActivePage = ((page === currentPage) ? true : false);
// need an active class indicator
var liClass = ((isActivePage) ? ' class="active"' : '');
// if '...' is sent from keystone then we need to override the url
if (page === '...') {
// check position of '...' if 0 then return page 1, otherwise use totalPages
page = ((ctr) ? totalPages : 1);
}
// get the pageUrl using the integer value
var pageUrl = _helpers.pageUrl(page);
// wrapup the html
html += '<li' + liClass + '>' + linkTemplate({ url: pageUrl, text: pageText }) + '</li>\n';
});
return html;
};
// special helper to ensure that we always have a valid page url set even if
// the link is disabled, will default to page 1
_helpers.paginationPreviousUrl = function (previousPage, totalPages) {
if (previousPage === false) {
previousPage = 1;
}
return _helpers.pageUrl(previousPage);
};
// special helper to ensure that we always have a valid next page url set
// even if the link is disabled, will default to totalPages
_helpers.paginationNextUrl = function (nextPage, totalPages) {
if (nextPage === false) {
nextPage = totalPages;
}
return _helpers.pageUrl(nextPage);
};
// ### Flash Message Helper
// KeystoneJS supports a message interface for information/errors to be passed from server
// to the front-end client and rendered in a html-block. FlashMessage mirrors the Jade Mixin
// for creating the message. But part of the logic is in the default.layout. Decision was to
// surface more of the interface in the client html rather than abstracting behind a helper.
//
// @messages:[]
//
// *Usage example:*
// '{{#if messages.warning}}
// <div class="alert alert-warning">
// {{{flashMessages messages.warning}}}
// </div>
// {{/if}}'
_helpers.flashMessages = function (messages) {
var output = '';
for (var i = 0; i < messages.length; i++) {
if (messages[i].title) {
output += '<h4>' + messages[i].title + '</h4>';
}
if (messages[i].detail) {
output += '<p>' + messages[i].detail + '</p>';
}
if (messages[i].list) {
output += '<ul>';
for (var ctr = 0; ctr < messages[i].list.length; ctr++) {
output += '<li>' + messages[i].list[ctr] + '</li>';
}
output += '</ul>';
}
}
return new hbs.SafeString(output);
};
// ### underscoreMethod call + format helper
// Calls to the passed in underscore method of the object (Keystone Model)
// and returns the result of format()
//
// @obj: The Keystone Model on which to call the underscore method
// @undescoremethod: string - name of underscore method to call
//
// *Usage example:*
// '{{underscoreFormat enquiry 'enquiryType'}}
_helpers.underscoreFormat = function (obj, underscoreMethod) {
return obj._[underscoreMethod].format();
};
return _helpers;
};
**
File: Views / Tours.pug
extends ../layouts/default
block content
.container.col-md-12.col-lg-12
.row
if !data.Tour
h2 Passeio inválido
else
- var qtd_nome_do_passeio = data.Tour
each tr in qtd_nome_do_passeio
.col-md-4.col-lg-4
h2= tr.nome_do_passeio
if tr.image.exists
.image-wrap: img(src=tr._.image.scale(450,200)).img-responsive
p Cidade #{tr.cidade} / Categoria #{tr.category}
p Custo: R$: #{tr.custo},00