Skip to content

Foundation Site Type Ahead Search

scottkearney edited this page Aug 3, 2017 · 17 revisions

Applies to

Akumina Foundation 3.4 and later

Overview

The Akumina Foundation Site uses SharePoint for search within the typeahead in the upper right of the site. The default steps in the framework assume certain fields and elements to be present on the site. It is possible to override these functions to allow for customization; this article details the steps needed to do so.

Default functionality

The type ahead search function is enabled by the following step within the Akumina framework:

  • ShippedSteps.InitializeSearchFields - this binds the type ahead to the search box on the site. It handles the mechanics of getting and rendering search results from the API.

The default step assumes the following elements:

  • A button with the class "ia-search-site-btn" - This element when pressed will initiate the site search.
  • A button with the class "ia-search-directory-btn" - This element when pressed will initiate the directory search.
  • A text field with the class "ia-search-combo-box" that sits inside an element with the class "ak-search-sites-fields" - This text box is what the type ahead is bound to.
  • A text field with the class "ia-search-combo-site-name" - This controls whether the type ahead searches all sites or just the current. When the value of this element is "current" then the search will look to the current site only.

Overriding the default functionality: Overview

In order to override this functionality, it is necessary to do the following:

  • Add in required functions
  • Disable the default steps from running
  • Add a new GenericSearchList control instance for the typeahead
  • Add the GenericSearchList instance to the master page
  • Use your own steps to replace the functionality

Add in required functions

We need to add in the function RenderChildWidgetsInContainer in order for the type ahead to function.

/*    ak:Widget in widget  */
function RenderChildWidgetsInContainer(selector){
  if (!Array.prototype.includes) {
    Object.defineProperty(Array.prototype, "includes", {
      enumerable: false,
      value: function(obj) {
        var newArr = this.filter(function(el) {
        return el == obj;
        });
        return newArr.length > 0;
      }
    });
  }


  var widgetManager = new Akumina.Digispace.Data.WidgetManager();
  var innerWidgets = $(selector + " .ak-widget");
  var pageWidgets = [];
  var finalWidgets = [];
  
  try{
    Akumina.AddIn.Logger.WriteInfoLog('RenderChildWidgetsSample: Found (' + innerWidgets.length + ') widgets to load');
    //loop through all potential widgets based on selector
    for (var i = 0; i < innerWidgets.length; i++) {
      var widget = innerWidgets[i];
      var widgetId = $(widget).attr('id');
      Akumina.AddIn.Logger.WriteInfoLog('RenderChildWidgetsSample: Found Widget Id: ' + widgetId);
      pageWidgets.push(widgetId);
    }
    //get all isntances based on array of widgetids (from cache)
    widgetManager.GetWidgetInstances().then(function (items) {
      for(var j = 0; j < items.length; j++){
      
            var w = items[j];
            if(pageWidgets.includes(w.widgetId)){
                  finalWidgets.push(w);
            }
      
      }
      //initialize the widgets - .Init()
      Akumina.AddIn.Logger.WriteInfoLog('RenderChildWidgetsSample: Calling Init() on widgets');
      widgetManager.InitializeWidgets(finalWidgets);
      //loop through them all and call "update" -- this will be made eassier in future versions
      for(var k = 0; k < finalWidgets.length; k++){
          var widgetProps = JSON.parse(finalWidgets[k].widgetprops);
          widgetProps.id = finalWidgets[k].widgetId;
           Akumina.AddIn.Logger.WriteInfoLog('RenderChildWidgetsSample: Publishing Update Event on Widget Id: ' + widgetProps.id);
          Akumina.Digispace.AppPart.Eventing.Publish('/widget/updated/', widgetProps);
      }
    });
  } catch (ex){
       Akumina.AddIn.Logger.WriteErrorLog('ERROR IN RenderChildWidgetsSample:' + ex);
  }
}
/*    ak:Widget in widget  */

Disable the default steps

For more information, please see Page Life Cycle framework: execution steps

Turning off the type ahead required suppressing the INITSEARCHFIELDS step, which is done via the following property:

Akumina.Digispace.ConfigurationContext.CONSTANTS.LOADER_STEPS_ENABLE_INITSEARCHFIELDS = false;

In the code below, we prevent the INITSEARCHFIELDS step from running, with the intention of replacing the type ahead with our own (in the next section).

if ((typeof LoaderConfiguration.Custom) === 'undefined') {

  //Add shipped steps to loader

  LoaderConfiguration.Custom = {
    Init: function (config) {

      //turn off typeahead
      Akumina.Digispace.ConfigurationContext.CONSTANTS.LOADER_STEPS_ENABLE_INITSEARCHFIELDS = false;

      }
    }
}

Add a new GenericSearchList control instance for the typeahead

This control will provide the search results while allowing the easy changing of the display of the results.

How to Deploy

  1. In the Management Apps tab of AppManager, click on the View Manager. Click “Add New”. In the left pane navigate to “/DigitalWorkplace/Content/Templates/Search” for the folder path. Click “Choose File”, navigate to your custom template (Typeahead.html). Click “Save”.
  2. In the Management Apps tab of AppManager, click on the Widget Manager app. In the Manage Widgets window, find GenericSearchList and click the ‘Edit’ button. Add the view in the previous step to the widget definition. Click Save & Exit
  3. In the Manage Widgets window, find GenericSearchList and click its ‘View Widget Definitions’ button. Then click on ‘Add New’. Create your widget instance with the values in the Widget Manager – Widget Instance section. Click Save & Exit
  4. Copy the Widget Snippet of your Widget Instance.

Template

We will leverage the markup for the important dates to achieve the effect of a circle around the result icon.

Create a file called TypeAhead.html and paste the following markup within:

<div class="large-12 medium-11 small-12 columns columns-no-padding ak-main-col">
  <div class="ak-interAction">
    <div class="ak-event-list ak-module">
      <h4 class="ak-module-header">{{WebPartTitle}}</h4>
      <ul>
        {{#Items}}
        <li>
          <span class="ak-events-date">
            <span class="ak-events-day">
              <img src="{{{Image}}}" alt="{{{Title}}}" style="padding-top: 10px;">
            </span>
          </span>
          <h5 class="ak-events-title"><a href="{{Url}}" style="color: #338200;">{{Title}}</a></h5>
          <p class="ak-events-description">{{{Summary}}}</p>
        </li>
        {{/Items}}
      </ul>
    </div>
  </div>
</div>

Widget Manager - Widget Definition

WidgetName

GenericSearchList

WidgetClass

Akumina.AddIn.GenericSearchListWidget

###WidgetViews

Type Ahead

View Name

Type Ahead

Relative Template Url

/Style Library/DigitalWorkplace/Content/Templates/Search/TypeAhead.html

Widget Manager – Widget Instance

Title

TypeAhead

WidgetProperties

selectfields – Property Value

HitHighlightedSummary,Title,Path,ContentType,SiteTitle,ListID,PageDataTitle,PageDataSiteId

Enable Paging – Property Value

false

Items/Page – Property Value

5

Query – Property Value

*{searchboxquery}* (SPSiteURL:{SiteCollection} (((FileExtension:zip OR FileExtension:txt OR FileExtension:doc OR FileExtension:docx OR FileExtension:xls OR FileExtension:xlsx OR FileExtension:ppt OR FileExtension:pptx OR FileExtension:pdf)(IsDocument:"True")) OR (contentclass:"STS_ListItem" Path:"{SiteCollection}/Lists/PageData_AK/*" {AkLanguageId:{Site.LanguageId}}))) XRANK(cb=100) ContentType=Item

Cache Interval – Property Value

0

WidgetOptions

Type Ahead

Display View

Checked

Selected View

Selected

Add the GenericSearchList instance to the master page

In the default master page, add in the following (where bb73a843-0970-4ce0-82f2-7d0c7e6cf41f is the instance id):

<div class="ak-search-typeahead-container" style="display:none;">
  <div rel="TypeAhead" class="ak-widget" id="bb73a843-0970-4ce0-82f2-7d0c7e6cf41f"></div>
</div>

In the default master page, this would be placed in the div with class "ak-search-sites-fields", after the line:

<!-- end .interaction -->

Use your own steps to replace the functionality

We must add in steps to replace the functionality of the type ahead. We must add in 2 steps:

  • SetSearchVariablesInContext - this will set the properties for the search including which element to use, the search page and others.
  • BindSearchBox- this binds the search box to use the genericsearchlist control

To add our custom steps, we create objects in the AdditionalSteps namespace.

if ((typeof AdditionalSteps.MoreSteps) === 'undefined') {

  AdditionalSteps.MoreSteps = {

    Init: function () {

      var steps = [];

      steps.push({
        stepName: "Event Subscription",
        additionalSteps: [
          {
              name: "SetSearchVariablesInContext",
              callback: DevTrainingSteps.SetSearchVariablesInContext.Init
          },
          {
              name: "BindSearchBox",
              callback: DevTrainingSteps.BindSearchBox.Init
          }
        ]
      });

      return steps;

    }        
  }
}

Next, we add in our steps as shown below.

// We first declare our object to house our steps.
var DevTrainingSteps = DevTrainingSteps || {};

if ((typeof DevTrainingSteps.BindSearchBox) === 'undefined') {
	DevTrainingSteps.BindSearchBox = {
		Init: function () {
			var searchBox = $(Akumina.Digispace.ConfigurationContext.SearchBox);
			var searchButton = $(Akumina.Digispace.ConfigurationContext.SearchButton);

			// orig/dir
			$('.ia-search-directory-btn').click(function (event) {
				event.preventDefault();
				ShippedSteps.InitializeSearchFields.NavigateToPeopleDirectory();
			});
			$('.ak-search-directory-icon ').click(function () {
				$('.ak-search-sites-fields').removeClass('ak-active');
				$('.ak-search-directory-fields').toggleClass('ak-active');
				$('.ia-languagepicker-active').removeClass('active');
				$('.ia-languagepicker-languages').hide();

			});
			$('.ak-search-sites-icon ').click(function () {
				$('.ak-search-directory-fields').removeClass('ak-active');
				$('.ak-search-sites-fields').toggleClass('ak-active');
				$('.ia-languagepicker-active').removeClass('active');
				$('.ia-languagepicker-languages').hide();

			});
			$(document).on('click', function (event) {
				if (!$(event.target).closest('.ak-header').length) {
					$('.ak-search-directory-fields').removeClass('ak-active');
					$('.ak-search-sites-fields').removeClass('ak-active');
				}
			});
			$('.interAction .ia-search-combo-site-list').click(function (e) {
				e.preventDefault();
				$(this).children('.ia-search-combo-site-list-dropdown').slideToggle();
				$('.ia-search-combo-site-list-icon').toggleClass('fa-caret-down');
				$('.ia-search-combo-site-list-icon').toggleClass('fa-caret-up');
			});
			$('.interAction .ia-search-combo-site-list-dropdown a').click(function () {
				var title = $(this).html();
				$('.ia-search-combo-site-name').html(title);
			});
			// orig/dir


			$(searchButton).click(function (event) {
				event.preventDefault();
				DevTrainingSteps.BindSearchBox.SiteSearch();
			});

			$(searchBox).keypress(function (e) {
				if (e.which == 13) {
					e.preventDefault();
					DevTrainingSteps.BindSearchBox.SiteSearch();
				}
			});

			$(searchBox).on("input", function () {
				var term = this.value;
				DevTrainingSteps.BindSearchBox.SetHashValue(term);
				var typeahead = Akumina.Digispace.ConfigurationContext.SearchTypeAheadContainer;
				if (term.length > 2) {
					$(typeahead).show();
					RenderChildWidgetsInContainer(typeahead);
				} else {
					$(typeahead).hide();
				}
			});

			Akumina.Digispace.AppPart.Eventing.Publish('/loader/onexecuted/');
		},
		SetHashValue: function (searchTerm) {
			if (searchTerm == '') {
				window.location.hash = '';
			} else {
				window.location.hash = '#term=' + searchTerm;
			}
		},
		SiteSearch: function () {
			var searchBox = $(Akumina.Digispace.ConfigurationContext.SearchBox);
			var searchText = $(searchBox).val();

			var searchUrl = '';
			var searchCurrentSite = true;
			var scopeCondition = '';
			var searchPage = window.location.pathname.toLowerCase().endsWith(Akumina.Digispace.ConfigurationContext.SearchPage.toLowerCase());

			searchUrl = Akumina.Digispace.ConfigurationContext.SearchPage;

			var searchObj = {
				"k": searchText,
				"l": 1033
			};
			var searchObjJSON = JSON.stringify(searchObj);
			var searchUrlParam = '#term=' + encodeURIComponent(searchText);
			location.href = searchUrl + searchUrlParam;

			if (searchPage) {
				location.reload();
			}
		},
	}
};

if ((typeof DevTrainingSteps.SetSearchVariablesInContext) === 'undefined') {
    DevTrainingSteps.SetSearchVariablesInContext= {
        Init: function () {   
            Akumina.Digispace.ConfigurationContext.SearchPage = __getTemplatePrefix() + "/pages/search.aspx"
            Akumina.Digispace.ConfigurationContext.SearchBox = ".ia-search-combo #siteSearch";
            Akumina.Digispace.ConfigurationContext.SearchButton = ".ia-search-combo .ia-search-site-btn";
            Akumina.Digispace.ConfigurationContext.SearchTypeAheadContainer = ".ak-search-typeahead-container";

            Akumina.Digispace.AppPart.Eventing.Publish('/loader/onexecuted/');
        }
    }
};

QuickLinks

Latest Release Notes

Akumina Developer Training

Akumina Learning Center

Akumina and SPFx

Framework Integration Points

4.0.0.0 Overview

Custom Widgets

Implementing Akumina

Digital Workplace Core Site Quick Start

Widget Development

Development Concepts

Deployment

Administration

Developing Solutions

CAML

Clone this wiki locally
You can’t perform that action at this time.