Developing A Realtor Theme For Hugo: Step 1 Building the pages
Chris Connelly
Posted on September 28, 2020
Introduction
I decided to develop a theme for realtors in my city as A LOT of realtors in my city are still using wordpress and I wanted to create an alternative for them.
The Plan
Here is the game plan:
- Find an SSG that will work
- Build out the pages first in HTML, SASS and JS
- Build out the templates
- Find a CMS that will work with the themes and SSG
For this article, I am going to cover the SSG and Pages
The SSG
For the static site generator, I decided to go with Hugo. It is simple to use and the templating engine is awesome! After reading the docs I think it will work with what I need.
Building out the pages
So, the main pages I require are:
- Home
- Listings
- A listing
- Staff Page
- Contact Us
Home Page
Listings
For the listing page I will need to set up a filterable gallery.
The Gallery
I decided to go witht thise style of gallery I think it looks nice. Next to add some filtering
The filters
// File#: _3_advanced-filter
// Usage: codyhouse.co/license
(function() {
// the AdvFilter object is used to handle:
// - number of results
// - form reset
// - filtering sections label (to show a preview of the option selected by the users)
var AdvFilter = function(element) {
this.element = element;
this.form = this.element.getElementsByClassName('js-adv-filter__form');
this.resultsList = this.element.getElementsByClassName('js-adv-filter__gallery')[0];
this.resultsCount = this.element.getElementsByClassName('js-adv-filter__results-count');
initAdvFilter(this);
};
function initAdvFilter(filter) {
if(filter.form.length > 0) {
// reset form
filter.form[0].addEventListener('reset', function(event){
setTimeout(function(){
resetFilters(filter);
resetGallery(filter);
});
});
// update section labels on form change
filter.form[0].addEventListener('change', function(event){
var section = event.target.closest('.js-adv-filter__item');
if(section) resetSelection(filter, section);
else if( Util.is(event.target, '.js-adv-filter__form') ) {
// reset the entire form lables
var sections = filter.form[0].getElementsByClassName('js-adv-filter__item');
for(var i = 0; i < sections.length; i++) resetSelection(filter, sections[i]);
}
});
}
// reset results count
if(filter.resultsCount.length > 0) {
filter.resultsList.addEventListener('filter-selection-updated', function(event){
updateResultsCount(filter);
});
}
};
function resetFilters(filter) {
// check if there are custom form elemets - reset appearance
// custom select
var customSelect = filter.element.getElementsByClassName('js-select');
if(customSelect.length > 0) {
for(var i = 0; i < customSelect.length; i++) customSelect[i].dispatchEvent(new CustomEvent('select-updated'));
}
// custom slider
var customSlider = filter.element.getElementsByClassName('js-slider');
if(customSlider.length > 0) {
for(var i = 0; i < customSlider.length; i++) customSlider[i].dispatchEvent(new CustomEvent('slider-updated'));
}
};
function resetSelection(filter, section) {
// change label value based on input types
var labelSelection = section.getElementsByClassName('js-adv-filter__selection');
if(labelSelection.length == 0) return;
// select
var select = section.getElementsByTagName('select');
if(select.length > 0) {
labelSelection[0].textContent = getSelectLabel(section, select[0]);
return;
}
// input number
var number = section.querySelectorAll('input[type="number"]');
if(number.length > 0) {
labelSelection[0].textContent = getNumberLabel(section, number);
return;
}
// input range
var slider = section.querySelectorAll('input[type="range"]');
if(slider.length > 0) {
labelSelection[0].textContent = getSliderLabel(section, slider);
return;
}
// radio/checkboxes
var radio = section.querySelectorAll('input[type="radio"]'),
checkbox = section.querySelectorAll('input[type="checkbox"]');
if(radio.length > 0) {
labelSelection[0].textContent = getInputListLabel(section, radio);
return;
} else if(checkbox.length > 0) {
labelSelection[0].textContent = getInputListLabel(section, checkbox);
return;
}
};
function getSelectLabel(section, select) {
if(select.multiple) {
var label = '',
counter = 0;
for (var i = 0; i < select.options.length; i++) {
if(select.options[i].selected) {
label = label + '' + select.options[i].text;
counter = counter + 1;
}
if(counter > 1) label = section.getAttribute('data-multi-select-text').replace('{n}', counter);
}
return label;
} else {
return select.options[select.selectedIndex].text;
}
};
function getNumberLabel(section, number) {
var counter = 0;
for(var i = 0; i < number.length; i++) {
if(number[i].value != number[i].min) counter = counter + 1;
}
if(number.length > 1) { // multiple input number in this section
if(counter > 0) {
return section.getAttribute('data-multi-select-text').replace('{n}', counter);
} else {
return section.getAttribute('data-default-text');
}
} else {
if(number[0].value == number[0].min) return section.getAttribute('data-default-text');
else return section.getAttribute('data-number-format').replace('{n}', number[0].value);
}
};
function getSliderLabel(section, slider) {
var label = '',
labelFormat = section.getAttribute('data-number-format');
for(var i = 0; i < slider.length; i++) {
if(i != 0 ) label = label+' - ';
label = label + labelFormat.replace('{n}', slider[i].value);
}
return label;
};
function getInputListLabel(section, inputs) {
var counter = 0;
label = '';
for(var i = 0; i < inputs.length; i++) {
if(inputs[i].checked) {
var labelElement = inputs[i].parentNode.getElementsByTagName('label');
if(labelElement.length > 0) label = labelElement[0].textContent;
counter = counter + 1;
}
}
if(counter > 1) return section.getAttribute('data-multi-select-text').replace('{n}', counter);
else if(counter == 0 ) return section.getAttribute('data-default-text');
else return label;
};
function resetGallery(filter) {
// emit change event + reset filtering
filter.form[0].dispatchEvent(new CustomEvent('change'));
filter.resultsList.dispatchEvent(new CustomEvent('update-filter-results'));
};
function updateResultsCount(filter) {
var resultItems = filter.resultsList.children,
counter = 0;
for(var i = 0; i < resultItems.length; i++) {
if(isVisible(resultItems[i])) counter = counter + 1;
}
filter.resultsCount[0].textContent = counter;
};
function isVisible(element) {
return (element.offsetWidth || element.offsetHeight || element.getClientRects().length);
};
//initialize the AdvFilter objects
var advFilter = document.getElementsByClassName('js-adv-filter');
if( advFilter.length > 0 ) {
for( var i = 0; i < advFilter.length; i++) {
(function(i){new AdvFilter(advFilter[i]);})(i);
}
}
// Remove the code below if you want to use a custom filtering function (e.g., you need to fetch your results from a database)
// The code below is used for filtering of page content (animation of DOM elements, no fetching results from database).
// It uses the Filter component (https://codyhouse.co/ds/components/app/filter) - you can modify the custom filtering functions based on your needs
// Check the info page of the component for info on how to use it: https://codyhouse.co/ds/components/info/filter
var gallery = document.getElementById('adv-filter-gallery');
if(gallery) {
new Filter({
element: gallery, // this is your gallery element
priceRange: function(items){ // this is the price custom function
var filteredArray = [],
minVal = document.getElementById('slider-min-value').value,
maxVal = document.getElementById('slider-max-value').value;
for(var i = 0; i < items.length; i++) {
var price = parseInt(items[i].getAttribute('data-price'));
filteredArray[i] = (price >= minVal) && (price <= maxVal);
}
return filteredArray;
},
indexValue: function(items){ // this is the index custom function
var filteredArray = [],
value = document.getElementById('index-value').value;
for(var i = 0; i < items.length; i++) {
var index = parseInt(items[i].getAttribute('data-sort-index'));
filteredArray[i] = index >= value;
}
return filteredArray;
}
});
}
}());
A Listing
Images
Tabs
I set up some tabs to host all of the main content for the listing. I think this will help separate everything and make it easier for the user to navigate.
Information
There is a lot of information that goes into the listing. So I used a template for a FAQ section and separated all the information within it.
Posted on September 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.