Foundation Site Type Ahead Search - akumina/AkuminaTraining GitHub Wiki
Akumina Foundation 3.4 and later
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.
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.
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
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 */
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;
}
}
}
This control will provide the search results while allowing the easy changing of the display of the results.
- 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”.
- 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
- 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
- Copy the Widget Snippet of your Widget Instance.
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>
GenericSearchList
Akumina.AddIn.GenericSearchListWidget
###WidgetViews
Type Ahead
/Style Library/DigitalWorkplace/Content/Templates/Search/TypeAhead.html
TypeAhead
HitHighlightedSummary,Title,Path,ContentType,SiteTitle,ListID,PageDataTitle,PageDataSiteId
false
5
*{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
0
Checked
Selected
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 -->
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/');
}
}
};