Custom Guidebook Template Development
This guide provides technical documentation for developers looking to create custom guidebook templates. It covers the template structure, available variables, and best practices for building secure, functional templates.
Template Architecture
SendSquared guidebooks use a Pug (formerly Jade) template engine with Tailwind CSS for styling. Templates are server-side rendered with dynamic data interpolation.
Core Template Structure
doctype html
html(lang="en")
head
// Meta tags, fonts, external libraries
title #{company.name} | #{reservation.units[0].name}
body
nav
// Navigation structure
main
// Content sections
footer
// Footer content
// JavaScript functionality
Variable Interpolation System
The template system uses a secure interpolation function to replace variables in content blocks:
Available Context Objects
The interpolation system provides access to three main context objects:
company
Object
company.name
- Company namecompany.address_1
- Primary addresscompany.address_2
- Secondary addresscompany.locality
- Citycompany.region
- State/Provincecompany.postal
- ZIP/Postal codecompany.country
- Country
contact
Object
contact.first_name
- Guest first namecontact.last_name
- Guest last namecontact.mobile_phone
- Phone numbercontact.primaryEmail.email_address
- Primary email
reservation
Object
reservation.reservation_number
- Booking referencereservation.uuid
- Unique reservation IDreservation.arrival_date
- Check-in datereservation.departure_date
- Check-out datereservation.nights
- Number of nightsreservation.adults
- Adult guest countreservation.children
- Child guest countreservation.pets
- Pet countreservation.total_revenue
- Total booking value (in cents)reservation.units[0].name
- Property namereservation.units[0].address_1
- Property addressreservation.units[0].address_2
- Secondary property addressreservation.units[0].locality
- Property cityreservation.units[0].region
- Property state/provincereservation.units[0].postal
- Property ZIP/postal codereservation.units[0].country
- Property country
guidebook
Object
guidebook.name
- Guidebook titleguidebook.description
- Guidebook description (supports HTML)guidebook.public_name
- Public display name for the guidebookguidebook.primary_color
- Primary theme color (hex)guidebook.secondary_color
- Secondary theme color (hex)guidebook.background_color
- Background color (hex)guidebook.backgroundAsset
- Background image asset objectguidebook.backgroundAsset.url
- Background image URLguidebook.collapsible_headers
- Boolean for collapsible section headersguidebook.default_collapsed
- Boolean for default collapsed stateguidebook.show_all_images
- Boolean to show all images vs. single imageguidebook.pdf_exportable
- Boolean for PDF export availabilityguidebook.trip_advisor_link
- TripAdvisor URLguidebook.google_local_link
- Google Reviews URLguidebook.blocks
- Array of guidebook blocks
guidebook.blocks
Array Objects
Each block in the guidebook.blocks
array contains:
Basic Block Properties:
block.id
- Unique block identifierblock.name
- Block title/nameblock.description
- Block content (supports HTML)block.guidebook_block_type
- Block type ('identity', 'houseGuides', 'houseAccess', 'areaGuides', 'restaurantGuides', 'activityGuides', 'legalTerms')block.isVisible
- Boolean visibility flagblock.assets
- Array of media assets
Location Properties (for location-based blocks):
block.latitude
- Latitude coordinateblock.longitude
- Longitude coordinateblock.map_link
- Link to map/directions
Asset Properties:
block.assets[].id
- Asset unique identifierblock.assets[].name
- Asset filenameblock.assets[].url
- Asset URLblock.assets[].mime_type
- MIME type (image/jpeg, video/mp4, application/pdf, etc.)block.assets[].thumbnail_width
- Custom thumbnail width in pixels
Special Block Properties:
block.verification_assets
- Number of required identity documents (identity blocks)block.terms_text
- Terms and conditions text (legal terms blocks)
Additional Template Variables
Display Control Variables:
showAddress
- Boolean indicating if property address should be shownhideEmail
- Boolean to hide guest email addresshidePhoneNumber
- Boolean to hide guest phone numberhidePrice
- Boolean to hide reservation pricerevealDate
- Date when address information becomes visiblelogo
- Company logo URLuploadPath
- Path for identity document uploadsunitAssets
- Array of property image assetsverificationAssets
- Count of uploaded identity documentstermsAcceptances
- Array of accepted legal terms
Formatting Helper Variables:
locale
- Locale setting for date formattingcolors
- Computed color scheme objectbgImage
- Background image CSS properties
Variable Usage in Content
Variables are used in content blocks using double curly braces:
// In template
p Welcome #{contact.first_name} to #{reservation.units[0].name}
// In content blocks
p Welcome {{contact.first_name}} to {{company.name}}
Security Considerations
The interpolation system includes safety measures:
- Variables are evaluated in a controlled context
- Invalid variable paths return the original placeholder
- HTML content is sanitized when rendered
Theme Configuration
Color System
Templates support dynamic theming through CSS custom properties:
const colors = {
primary: guidebook?.primary_color || '#1E40AF',
secondary: guidebook?.secondary_color || '#6366F1',
background: guidebook?.background_color || '#F3F4F6'
}
Background Assets
Optional background images can be configured:
const bgImage = guidebook?.backgroundAsset ?
{ page: `url('${guidebook.backgroundAsset.url}')` } : {}
Block Types and Rendering
Supported Block Types
Templates handle several predefined block types:
identity
- Identity verification blockshouseGuides
- Property guide informationhouseAccess
- Access instructionsareaGuides
- Local area informationrestaurantGuides
- Dining recommendationsactivityGuides
- Activity suggestionslegalTerms
- Terms and conditions
Block Rendering Logic
each type in ['identity', 'houseGuides', 'houseAccess', 'areaGuides', 'restaurantGuides', 'activityGuides', 'legalTerms']
if guidebook.blocks.some(block => block.guidebook_block_type === type && (block.isVisible === undefined || block.isVisible === true))
.mb-8
h3.text-lg.font-bold.mb-4
// Block type specific icons and headers
.grid.grid-cols-1.gap-4
each block in guidebook.blocks.filter(b => b.guidebook_block_type === type && (b.isVisible === undefined || b.isVisible === true))
// Block content rendering
Dynamic Content Features
Collapsible Headers
Templates support collapsible content sections:
function toggleBlockContent(contentId) {
const content = document.getElementById(contentId);
// Toggle display logic
}
Media Galleries
Blocks can include image galleries with lightbox functionality:
if block.assets && block.assets.length > 0
.w-full.block-gallery.mt-4
// Swiper carousel for multiple images
// Single image display for single assets
Interactive Features
Map Integration
Templates include comprehensive Google Maps integration for displaying location-based blocks and property information:
let map;
let markers = [];
let infoWindows = [];
function initMap() {
const mapOptions = {
zoom: 15,
center: { lat: 0, lng: 0 },
mapTypeControl: true,
streetViewControl: true,
fullscreenControl: true,
styles: [
{
featureType: "poi",
elementType: "labels",
stylers: [{ visibility: "off" }]
}
]
};
map = new google.maps.Map(document.getElementById('map'), mapOptions);
// Set map center to property or company address
setMapCenter();
// Add markers for guidebook blocks
addBlockMarkers();
}
function setMapCenter() {
const geocoder = new google.maps.Geocoder();
// Try property address first if available
if (showAddress) {
const propertyAddress = buildAddressString(reservation.units[0]);
geocoder.geocode({ address: propertyAddress }, (results, status) => {
if (status === 'OK') {
const location = results[0].geometry.location;
map.setCenter(location);
// Add property marker
const propertyMarker = new google.maps.Marker({
map: map,
position: location,
title: reservation.units[0].name,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10,
fillColor: '#FF0000',
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});
// Add info window for property
const infoWindow = new google.maps.InfoWindow({
content: createPropertyInfoWindow(reservation.units[0])
});
propertyMarker.addListener('click', () => {
infoWindow.open(map, propertyMarker);
});
}
});
}
}
function addBlockMarkers() {
// Clear existing markers
markers.forEach(marker => marker.setMap(null));
infoWindows.forEach(window => window.close());
markers = [];
infoWindows = [];
// Add markers for each guidebook block with location data
blockLocations.forEach(block => {
if (block.latitude && block.longitude) {
const position = {
lat: parseFloat(block.latitude),
lng: parseFloat(block.longitude)
};
const marker = new google.maps.Marker({
position: position,
map: map,
title: block.name,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: getMarkerColor(block.type),
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});
const infoWindow = new google.maps.InfoWindow({
content: createBlockInfoWindow(block)
});
marker.addListener('click', () => {
// Close other info windows
infoWindows.forEach(window => window.close());
infoWindow.open(map, marker);
// Scroll to block in list view
scrollToBlock(block.id);
});
markers.push(marker);
infoWindows.push(infoWindow);
}
});
}
function getMarkerColor(blockType) {
const colorMap = {
'restaurantGuides': '#FF6B6B', // Red for restaurants
'activityGuides': '#4ECDC4', // Teal for activities
'areaGuides': '#45B7D1', // Blue for area info
'houseGuides': '#96CEB4', // Green for house info
'houseAccess': '#FFEEAD' // Yellow for access info
};
return colorMap[blockType] || '#1E40AF';
}
function createBlockInfoWindow(block) {
let content = `
<div class="p-4 max-w-sm">
<h3 class="font-bold text-lg mb-2">${block.name}</h3>
`;
// Add image if available
if (block.assets && block.assets.length > 0) {
const firstImage = block.assets.find(asset =>
!asset.mime_type.includes('video') && !asset.mime_type.includes('pdf')
);
if (firstImage) {
content += `
<img src="${firstImage.url}" alt="${block.name}"
class="w-full h-32 object-cover rounded mb-2">
`;
}
}
content += `
<p class="text-sm text-gray-600 mb-2">${block.description}</p>
<button onclick="toggleView('list'); scrollToBlock('${block.id}')"
class="text-primary hover:underline text-sm">
View Details
</button>
</div>
`;
return content;
}
function scrollToBlock(blockId) {
const blockElement = document.getElementById(`block-${blockId}`);
if (blockElement) {
blockElement.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
}
Adding Custom Markers
To add custom markers for specific locations:
function addCustomMarker(lat, lng, title, content, color = '#1E40AF') {
const marker = new google.maps.Marker({
position: { lat: lat, lng: lng },
map: map,
title: title,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
fillColor: color,
fillOpacity: 1,
strokeWeight: 2,
strokeColor: '#FFFFFF'
}
});
const infoWindow = new google.maps.InfoWindow({
content: content
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
return marker;
}
Identity Verification
For identity blocks, templates include file upload functionality. You can call the upload handler with the following signature:
handleUpload(file, documentType)
Parameters:
file
- The file object from the input elementdocumentType
- String indicating the document type (e.g., 'document', 'passport', 'license')
The system handles the complete upload process including signed URL generation, S3 upload, and confirmation.
Legal Terms Acceptance
Legal terms blocks include acceptance functionality. You can call the acceptance handler with the following signature:
acceptLegalTerms(event, blockId)
Parameters:
event
- The form submission event objectblockId
- The unique identifier for the legal terms block
The system handles IP tracking, backend submission, and UI updates automatically.
Template Development Best Practices
Security Guidelines
- Variable Sanitization: Always sanitize user-generated content
- Context Limitation: Only expose necessary data objects
- XSS Prevention: Avoid using
!=
for untrusted content - Input Validation: Validate all user inputs on the server side
Performance Optimization
- Asset Optimization: Compress images and minimize file sizes
- Lazy Loading: Implement lazy loading for media assets
- Caching: Use appropriate caching headers for static assets
- CDN Integration: Leverage CDNs for external libraries
Responsive Design
- Mobile-First: Design for mobile devices first
- Breakpoint Strategy: Use Tailwind's responsive utilities
- Touch Interactions: Ensure touch-friendly interface elements
- Viewport Configuration: Set appropriate viewport meta tags
Accessibility
- Semantic HTML: Use proper HTML5 semantic elements
- Alt Text: Provide descriptive alt text for images
- Keyboard Navigation: Ensure keyboard accessibility
- Screen Reader Support: Test with screen reading software
External Library Integration
Required Libraries
Templates include several external libraries:
- Tailwind CSS: Utility-first CSS framework
- Font Awesome: Icon library
- Google Fonts: Typography (Open Sans)
- PhotoSwipe: Image lightbox functionality
- Swiper: Touch slider/carousel functionality
- Google Maps API: Map integration
- html2pdf.js: PDF export functionality
Loading Strategy
// CDN-based loading for reliability
script(src="https://cdn.tailwindcss.com")
link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css")
Testing and Deployment
Template Validation
- Syntax Checking: Validate Pug template syntax
- Variable Testing: Test all variable interpolations
- Cross-Browser Testing: Verify compatibility across browsers
- Device Testing: Test on various screen sizes
Content Security
- Sanitization: Ensure all dynamic content is properly sanitized
- Variable Validation: Test with malicious variable inputs
- XSS Testing: Verify protection against cross-site scripting
- Content Injection: Test against content injection attacks
Customization Guidelines
Extending Block Types
When adding new block types:
- Update the block type enumeration
- Add appropriate rendering logic
- Include necessary styling classes
- Test variable interpolation
Adding Interactive Features
For new interactive elements:
- Use progressive enhancement principles
- Provide fallbacks for JavaScript-disabled users
- Ensure accessibility compliance
- Test across different devices
Styling Customization
When modifying styles:
- Use Tailwind utility classes when possible
- Create custom CSS only when necessary
- Maintain responsive design principles
- Test color contrast ratios
This documentation provides the foundation for building secure, functional guidebook templates while maintaining compatibility with the SendSquared platform's data structure and security requirements.