Page templates

Global layout

The global layout can be used for inner pages on the City of Winnipeg website. It uses the global header, global footer, breadcrumbs, a search bar and sidebar navigation. Inner page content would vary based on need.

Template structure

The global page layout uses the body class of global along with the following elements for the general page structure.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Page title</title>
  </head>
  <body class="global">
    <!-- Insert global header here -->
    <div class="container">
      <div class="mt-4 mb-4">
        <!-- Insert breadcrumbs here -->
      </div>
      <div class="row">
        <div class="col-12 col-md-12 col-lg-3 col-xl-3">
          <div class="sidebar">
            <!-- Optional search bar -->
            <div class="form-group sm search-group mb-4">
              <input type="search" class="form-control form-control-sm google-search" placeholder="Search 311 City Services" aria-label="Search 311 City Services">
              <button type="submit" aria-label="Search 311 City Services" class="btn btn-sm btn-search"><i aria-hidden="true" class="far fa-search"></i></button>
            </div>
            <!-- Insert side navigation here -->
          </div>
        </div>
        <main class="col-12 col-sm-12 col-lg-9 col-xl-9">
          <div class="cw-content">
            <!-- Page content -->
          </div>
        </main>
      </div>
    </div>
    <!-- Insert global footer here -->
  </body>
</html>

Sample pages

These sample pages have been pre-built including all default design system CSS and JavaScript.

Sample page
Basic page (contains component examples)Download
Basic French page (contains component examples)Download
Basic page 2 (blank template)Download
Basic French page 2 (blank template)Download
Basic page for winnipeg.caDownload
Basic page with no sidenav (blank template)Download
Basic French page with no sidenav (blank template)Download
Basic page with no sidenav - full width (blank template)Download
Basic French page with no sidenav - full width (blank template)Download
Global alert and typographyDownload

How it works

The following JavaScript for initializing Bootstrap components and for custom component functionality has also been included (assets/js/global.js):

$(document).ready(function() {

  /* ==================== Global header =================== */

  // Search bar
  $('#globalSearchInput').keyup(function(){
    if($(this).val() === ''){
      $('#globalSearchBtn').attr("disabled", true);
    }else{
      $('#globalSearchBtn').attr("disabled", false);
    }
  });

  // Mobile menu
  function openMenu(){
    $('html').scrollTop( 0 );
    $('.headerGradient').fadeIn(200);
    $('.headerGradient').addClass('menuOpen');
    $('nav.main-nav').addClass('in');
    $('body').css({'position':'fixed','left':0,'right':0, 'top':0});
  }

  function closeMenu(){
    $('.headerGradient').fadeOut(200);
    $('.headerGradient').removeClass('menuOpen');
    $('nav.main-nav').removeClass('in');
    $('body').css({'position':'initial','left':'auto','right':'auto','top':'auto'});
  }

  $('.menuBtn').click(function() {
    openMenu();
  })

  // Mobile search
  function openSearch(){
    $('html').scrollTop( 0 );
    $('.headerGradient').fadeIn(200);
    $('.global-header .search-group-container').addClass('in');
    $('.global-header .search-group-container input').focus();
    $('body').css({'position':'fixed','left':0,'right':0, 'top':0});
  }

  function closeSearch(){
    $('.headerGradient').fadeOut(200);
    $('.global-header .search-group-container').removeClass('in');
    $('body').css({'position':'initial','left':'auto','right':'auto','top':'auto'});
  }

  $('.searchBtn').click(function() {
    openSearch();
  })

  // Close search and menu on gradient click
  $('.headerGradient, .headerCloseBtn').click(function() {
    closeMenu();
    closeSearch();
  })

  /* ==================== Enable tooltips =================== */

  $('[data-toggle="tooltip"]').tooltip();

  // Dark styled
  $('.dark-tooltip').tooltip({
    'template': '<div class="tooltip dark" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>'
  });


  /* ==================== Datepicker - https://bootstrap-datepicker.readthedocs.io/en/stable/ =================== */

  //----- Enable single date picker
  $('.datePicker').datepicker({
    format: 'mm/dd/yyyy',
    startDate: '-3d',
    todayHighlight: true,
    daysOfWeekDisabled: '0,6'
  });

  // Single date picker trigger
  $('.datePickerTrigger').click(function() {
    $(this).closest('.input-group').find('.datePicker').datepicker('show');
  })

  //----- Enable date range 
  $('.dateRange').datepicker({
    inputs: $('.dateRangeInput')
  });

  // Date range trigger
  $('.dateRangeTrigger').click(function() {
    $(this).closest('.input-group').find('.dateRangeInput').datepicker('show');
  })

  /* ==================== Accordions =================== */

  $('.accordion .heading').click(function() {
    // Toggle the icon
    $(this).find('i').toggleClass('fa-chevron-down fa-chevron-up');
  })

  $('.collapse').on('hidden.bs.collapse', function() {
    // Toggle the icon
    $(this).find('i.fa-chevron-up').toggleClass('fa-chevron-down fa-chevron-up');

    // Disable the expand all link
    var id = $(this).closest('.accordion').attr('id');
    $('.expandAll[data-target="#' + id + '"]').removeClass('disabled');

    // If all items are hidden then disable the collapse link
    if ($(this).closest('.accordion').find('.collapse.show').length == 0) {
      $('.collapseAll[data-target="#' + id + '"]').addClass('disabled');
    }

  })

  $('.collapse').on('shown.bs.collapse', function() {
    // Toggle the icon
    $(this).find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');

    // Disable the collapse all link
    var id = $(this).closest('.accordion').attr('id');
    $('.collapseAll[data-target="#' + id + '"]').removeClass('disabled');

    // If all items are shown then disable the expand link
    if ($(this).closest('.accordion').find('.collapse:not(.show)').length == 0) {
      $('.expandAll[data-target="#' + id + '"]').addClass('disabled');
    }
  })

  $('.expandAll').click(function() {
    // Disable the expand all link
    var id = $(this).attr('data-target');
    $(this).addClass('disabled');

    // Enable the collapse all link
    $('.collapseAll[data-target="' + id + '"]').removeClass('disabled');

    // Show all items
    $(id + ' .collapse').collapse('show');

    // Toggle the icons
    $(id).find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');
  })

  $('.collapseAll').click(function() {
    // Disable the collapse all link
    var id = $(this).attr('data-target');
    $(this).addClass('disabled');

    // Enable the expand all link
    $('.expandAll[data-target="' + id + '"]').removeClass('disabled');

    // Hide all items
    $(id + ' .collapse').collapse('hide');

    // Toggle the icons
    $(id).find('i.fa-chevron-up').toggleClass('fa-chevron-down fa-chevron-up');
  })

  /* ==================== Dropdown toggles - Sidebar navigation =================== */
  
  $('.sidebarBtn').click(function() {
    $(this).find('i').toggleClass('fa-chevron-up fa-chevron-down');
    if($(this).attr('aria-expanded') == true){
      $(this).attr({'aria-expanded': false});
    }else{
      $(this).attr({'aria-expanded': true});
    }
    $(this).next('ul').slideToggle();
  });

})

Vertical header layout

The vertical header layout uses the vertical header and includes a secondary navigation (‘Jump to’ menu on the right) as well as a footer.

Template structure

This template uses a body class of vertical-layout along with the following elements for the general page layout.

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Page title</title>
   </head>
   <body class="vertical-layout">
      <!-- Insert vertical header here -->
      <div class="container-fluid">
         <main class="clearfix">
            <div class="cw-content">
               <!-- Page content here -->
            </div>
            <div class="secondary-nav-container">
               <div class="secondary-nav" id="secondaryNav">
                  <!-- Secondary navigation dynamically generated here -->
               </div>
            </div>
         </main>
      </div>
      <!-- Footer -->
      <footer class="vertical-layout-footer">
         <div class="container-fluid">
            <div class="guide-footer-inner small">
               <p>Copyright or any other important information can be added inside the footer, maybe a link to <a href="#">contact us</a>.</p>
            </div>
         </div>
      </footer>
   </body>
</html>

Sample pages

These sample pages have been pre-built including all default design system CSS and JavaScript.

Sample page
Vertical layout example (contains component examples)Download
Vertical layout example 2 (blank template)Download

How it works

The following JavaScript for initializing Bootstrap components and for custom component functionality has also been included (assets/js/vertical-layout.js):

Secondary navigation

The secondary sidebar navigation (‘Jump to’ menu) is dynamically inserted into #secondaryNav using JavaScript based on the h2 and h3 headings within .cw-content. You must add an id attribute to each h2 and h3 for the scroll functionality. Add the class no-scroll to any h2 or h3 headings that you don’t want to appear in the menu.

/* ==========================================================================
   Vertical layout JS
   ========================================================================== */

function makeArray(list) {
  return [].slice.call(list)
}

function getUrlParameter(name) {
    name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
    var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
    var results = regex.exec(location.search);
    return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

$(document).ready(function() {

  /* ==================== Vertical header =================== */

  // Search form
  // $('#searchInput').keyup(function(){
  //   if($(this).val() === ''){
  //     $('#searchBtn').attr("disabled", true);
  //   }else{
  //     $('#searchBtn').attr("disabled", false);
  //   }
  // });

  // Dropdown toggles
  $('.sidebarBtn').click(function() {
    $(this).find('i').toggleClass('fa-chevron-up fa-chevron-down');
    if($(this).attr('aria-expanded') == true){
      $(this).attr({'aria-expanded': false});
    }else{
      $(this).attr({'aria-expanded': true});
    }
    $(this).next('ul').slideToggle();
  });

  // Mobile menu
  function openVerticalHeader(){
    $('html').scrollTop( 0 );
    $('.verticalHeaderGradient').fadeIn(200);
    $('header.vertical-header').addClass('in');
    $('body').css({'position':'fixed','left':0,'right':0,'top':0});
  }

  function closeVerticalHeader(){
    $('.verticalHeaderGradient').fadeOut(200);
    $('header.vertical-header').removeClass('in');
    $('body').css({'position':'initial','left':'auto','right':'auto','top':'auto'});
  }

  $('.verticalHeaderBtn').click(function() {
    openVerticalHeader();
  })

  $('.verticalHeaderGradient, .verticalHeaderCloseBtn').click(function() {
    closeVerticalHeader();
  })

  /* ==================== Dynamic secondary side nav ("Jump to" menu) =================== */

  var navContainer = $('#secondaryNav');

  function createHashNav() {
    var navContainer = $('#secondaryNav');
    var navHTML = '';
    var count = 0;
    $('.cw-content > h2:not(.no-scroll), .cw-content > h3:not(.no-scroll)').each(function(index) {
      if ($(this).is("h2")) {
        navHTML += '<li><a class="scroll" href="#' + $(this).attr('id') + '">' + $(this).text() + '</a></li>';
      } else {
        navHTML += '<li class="inner"><a class="scroll" href="#' + $(this).attr('id') + '">' + $(this).text() + '</a></li>';
      }
      count++;
    })
    if (count > 1) {
      navContainer.html('<div class="title lead">Jump to</div><ul>' + navHTML + '</ul>');
    } else {
      navContainer.html('');
    }
  }

  if (navContainer.length > 0) {
    createHashNav();
  }

  // Smooth scroll
  $('a.scroll').click(function() {
    if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) {
      var target = $(this.hash);
      target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
      if (target.length) {

        var stateObj = {
          hash: this.hash,
        };
        history.pushState(stateObj, '', location.pathname + this.hash);

        $('html,body').animate({
          scrollTop: target.offset().top
        }, 500);
        return false;
      }
    }
  });

  // On scroll find the first h1 or h2 in the viewport to set as active
  $(window).scroll(function() {
    var scrollTop = $(window).scrollTop();
    var windowHeight = $(window).height();
    var first = false;
    $("h3, h2, h1").each(function() {
      var offset = $(this).offset();
      if (scrollTop <= offset.top && ($(this).height() + offset.top) < (scrollTop + windowHeight)) {
        $('a.scroll[href="#' + $(this).attr('id') + '"]').parent().siblings().find('a').removeClass('active');
        $('a.scroll[href="#' + $(this).attr('id') + '"]').parent().siblings().find('a').blur();
        $('a.scroll[href="#' + $(this).attr('id') + '"]').addClass('active');
        first = true;
      }
      if (first == true) {
        return false;
      }
    });
  });

  /* ==================== Enable tooltips =================== */

  $('[data-toggle="tooltip"]').tooltip();

  // Dark styled
  $('.dark-tooltip').tooltip({
    'template': '<div class="tooltip dark" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>'
  });


  /* ==================== Datepicker - https://bootstrap-datepicker.readthedocs.io/en/stable/ =================== */

  //----- Enable single date picker
  $('.datePicker').datepicker({
    format: 'mm/dd/yyyy',
    startDate: '-3d',
    todayHighlight: true,
    daysOfWeekDisabled: '0,6'
  });

  // Single date picker trigger
  $('.datePickerTrigger').click(function() {
    $(this).closest('.input-group').find('.datePicker').datepicker('show');
  })

  //----- Enable date range 
  $('.dateRange').datepicker({
    inputs: $('.dateRangeInput')
  });

  // Date range trigger
  $('.dateRangeTrigger').click(function() {
    $(this).closest('.input-group').find('.dateRangeInput').datepicker('show');
  })


  /* ==================== Accordions =================== */

  $('.accordion .heading').click(function() {
    // Toggle the icon
    $(this).find('i').toggleClass('fa-chevron-down fa-chevron-up');
  })

  $('.collapse').on('hidden.bs.collapse', function() {
    // Toggle the icon
    $(this).find('i.fa-chevron-up').toggleClass('fa-chevron-down fa-chevron-up');

    // Disable the expand all link
    var id = $(this).closest('.accordion').attr('id');
    $('.expandAll[data-target="#' + id + '"]').removeClass('disabled');

    // If all items are hidden then disable the collapse link
    if ($(this).closest('.accordion').find('.collapse.show').length == 0) {
      $('.collapseAll[data-target="#' + id + '"]').addClass('disabled');
    }

  })

  $('.collapse').on('shown.bs.collapse', function() {
    // Toggle the icon
    $(this).find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');

    // Disable the collapse all link
    var id = $(this).closest('.accordion').attr('id');
    $('.collapseAll[data-target="#' + id + '"]').removeClass('disabled');

    // If all items are shown then disable the expand link
    if ($(this).closest('.accordion').find('.collapse:not(.show)').length == 0) {
      $('.expandAll[data-target="#' + id + '"]').addClass('disabled');
    }
  })

  $('.expandAll').click(function() {
    // Disable the expand all link
    var id = $(this).attr('data-target');
    $(this).addClass('disabled');

    // Enable the collapse all link
    $('.collapseAll[data-target="' + id + '"]').removeClass('disabled');

    // Show all items
    $(id + ' .collapse').collapse('show');

    // Toggle the icons
    $(id).find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');
  })

  $('.collapseAll').click(function() {
    // Disable the collapse all link
    var id = $(this).attr('data-target');
    $(this).addClass('disabled');

    // Enable the expand all link
    $('.expandAll[data-target="' + id + '"]').removeClass('disabled');

    // Hide all items
    $(id + ' .collapse').collapse('hide');

    // Toggle the icons
    $(id).find('i.fa-chevron-up').toggleClass('fa-chevron-down fa-chevron-up');
  })

})