/**
* Search box component functionality
* @module searchBox
* @param {jQuery} $ Instance of jQuery
* @param {Document} document dom document object
* @return {Object} list of methods for working with search box
*/
XA.component.search.box = (function ($, document) {
var api = {},
searchBoxViews = [],
searchBoxModels = [],
queryModel,
urlHelperModel,
searchResultModels,
initialized = false;
/**
* @name module:searchBox.SearchBoxModel
* @constructor
* @augments Backbone.Model
*/
var SearchBoxModel = Backbone.Model.extend(
/** @lends module:searchBox.SearchBoxModel.prototype **/
{
/**
* Default model options
* @default
*/
defaults: {
searchEngine: "",
typeahead: "",
dataProperties: {},
searchQuery: "",
loadingInProgress: false,
sig: []
},
/**
* create instance of
* ["Bloodhound"]{@link https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md}
* width necessary options
* @memberOf module:searchBox.SearchBoxModel
* @method
* @alias module:searchBox.SearchBoxModel.initSearchEngine
*/
initSearchEngine: function () {
var inst = this,
siteName = XA.component.search.ajax.getPrameterByName("sc_site"),
searchEngine = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name"),
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: inst.get("dataProperties").p,
remote: {
url: urlHelperModel.createSiteUrl(inst.createSuggestionsUrl($.extend({ l: inst.getLanguage() }, inst.get("dataProperties")), inst.get("searchQuery")), siteName),
filter: function (list) {
var searchArr = [];
return _.map(list.Results, function (item) { return { html: item.Html }; });
},
replace: function () {
var valueProvider = inst.get("valueProvider"),
searchQuery = valueProvider(),
properties = $.extend({ l: inst.getLanguage() }, inst.get("dataProperties"));
return urlHelperModel.createSiteUrl(inst.createSuggestionsUrl(properties, searchQuery), siteName);
},
ajax: {
beforeSend: function () {
inst.set({ "loadingInProgress": true });
},
complete: function () {
inst.set({ "loadingInProgress": false });
}
}
}
});
searchEngine.initialize();
this.set({ searchEngine: searchEngine });
},
/**
* Creates url from which suggestion should be taken
* @memberOf module:searchBox.SearchBoxModel
* @method
* @param {Object} properties list of properties for suggestion functionality
* @param {String} searchQuery text from search box
* @alias module:searchBox.SearchBoxModel.createSuggestionsUrl
* @returns {string} search url based on setups
*/
createSuggestionsUrl: function (properties, searchQuery) {
var suggestionsMode = this.get("dataProperties").suggestionsMode,
resultsEndpoint = this.get("dataProperties").endpoint,
suggestionsEndpoint = this.get("dataProperties").suggestionEndpoint;
switch (suggestionsMode) {
case "ShowPredictions": {
return urlHelperModel.createPredictiveSearchUrl(suggestionsEndpoint, properties, searchQuery);
}
default: {
return urlHelperModel.createPredictiveSearchUrl(resultsEndpoint, properties, searchQuery);
}
}
},
/**
* get search box signature
* @memberOf module:searchBox.SearchBoxModel
* @method
* @alias module:searchBox.SearchBoxModel.getSignature
* @returns {string} search box signature
*/
getSignature: function () {
var rawSignature = this.get("dataProperties").searchResultsSignature,
signatures;
if (typeof rawSignature === "undefined" || rawSignature === null) {
return "";
}
signatures = rawSignature.split(',');
if (rawSignature === "") {
return "";
} else {
return signatures[0];
}
},
/**
* get search box language
* @memberOf module:searchBox.SearchBoxModel
* @method
* @alias module:searchBox.SearchBoxModel.getLanguage
* @returns {string} selected language
*/
getLanguage: function () {
var dataProperties = this.get("dataProperties"),
searchResultModels = XA.component.search.results.searchResultModels,
languageSource = dataProperties.languageSource,
signature = this.getSignature(),
model;
switch (languageSource) {
case "CurrentLanguage":
case "AllLanguages": {
return dataProperties.l;
}
default: {
model = searchResultModels.filter(function (element) {
return element.get("dataProperties").sig === signature;
})[0];
if (typeof model !== "undefined") {
return model.get("dataProperties").l;
}
break;
}
}
return "";
}
});
/**
* @name module:searchBox.SearchBoxView
* @constructor
* @augments Backbone.View
*/
var SearchBoxView = XA.component.search.baseView.extend(
/** @lends module:searchBox.SearchBoxView.prototype **/
{
/**
*Initializes typeahead for view, sets up watchers for change on model
*and call ["initSearchEngine"]{@link module:searchBox.SearchBoxModel}
* @memberof module:searchBox.SearchBoxView.prototype
*/
initialize: function () {
var inst = this,
dataProperties = this.$el.data(),
typeahead;
dataProperties.properties.targetSignature = dataProperties.properties.targetSignature !== null ? dataProperties.properties.targetSignature : "";
this.model.set({ dataProperties: dataProperties.properties });
this.model.set("sig", this.translateSignatures(dataProperties.properties.searchResultsSignature, "q"));
this.model.initSearchEngine();
this.model.on("change:loadingInProgress", this.loading, this);
typeahead = this.$el.find(".search-box-input").typeahead({
hint: true,
minLength: dataProperties.properties.minSuggestionsTriggerCharacterCount
},
{
source: inst.model.get("searchEngine").ttAdapter(),
displayKey: function () { return inst.$el.find(".search-box-input.tt-input").val(); },
templates: {
suggestion: function (data) {
var suggestionsMode = dataProperties.properties.suggestionsMode,
text = data.html.replace(/(<([^>]+)>)/ig, ""),
suggestionText = text !== "" ? text : data.html;
switch (suggestionsMode) {
case "ShowPredictions":
case "ShowSearchResultsAsPredictions": {
return '<div class="sugesstion-item">' + suggestionText + '</div>';
}
default: {
return '<div class="sugesstion-item">' + data.html + '</div>';
}
}
}
}
}).on('typeahead:selected', this.suggestionSelected.bind(inst));
//TODO: Seems like bellow line isn't needed as updateSearchBoxValue() function will be callend when hash will change - to be tested in non/multi signatures cases
//$searchBox.val(hashObj[this.model.get("sig")] !== undefined ? hashObj[this.model.get("sig")] : "");
this.model.set({ typeahead: typeahead });
this.model.set({
valueProvider: function () {
return inst.$el.find(".search-box-input.tt-input").val();
}
});
XA.component.search.vent.on("hashChanged", this.updateSearchBoxValue.bind(this));
},
/**
* list of events for Backbone View
* @memberof module:searchBox.SearchBoxView
* @alias module:searchBox.SearchBoxView#events
*/
events: {
"click .search-box-button": "updateQueryModelClick",
"click .search-box-button-with-redirect": "updateQueryWithRedirect",
"keypress .search-box-input.tt-input": "predictiveSearch",
"keydown .search-box-input.tt-input": "predictiveSearch"
},
/**
* Toggle css class "loading-in-progress"
* @memberof module:searchBox.SearchBoxView
* @alias module:searchBox.SearchBoxView#loading
*/
loading: function () {
this.$el.toggleClass("loading-in-progress");
},
/**
* Called when selected value from search box and call
* ["performSearch"]{@link module:performSearch} or re
* @memberof module:searchBox.SearchBoxView
* @param {jQuery<Event>} event jQuery event object
* @param {DomElement} data DOM element that was selected
* @alias module:searchBox.SearchBoxView#suggestionSelected
*/
suggestionSelected: function (event, data) {
event.preventDefault();
var suggestionsMode = this.model.get("dataProperties").suggestionsMode;
var text;
try {
text = $(data.html).text();
}
catch (error) {
text = "";
}
var suggestionText = text != "" ? text : data.html,
link;
switch (suggestionsMode) {
case "ShowPredictions":
case "ShowSearchResultsAsPredictions": {
this.performSearch(suggestionText);
break;
}
default: {
link = $(data.html).find("a");
if (link.length) {
window.location.href = $(link[0]).attr("href");
}
break;
}
}
},
/**
* Updates hash value and makes redirect based on value from search box
* @memberof module:searchBox.SearchBoxView
* @param {jQuery<Event>} event jQuery event object
* @alias module:searchBox.SearchBoxView#updateQueryWithRedirect
*/
updateQueryWithRedirect: function (event) {
event.preventDefault();
var resultPage = this.model.get("dataProperties").resultPage,
targetSignature = this.model.get("dataProperties").targetSignature,
searchResultsSignature = this.model.get("dataProperties").searchResultsSignature,
query = encodeURIComponent(this.$el.find(".search-box-input.tt-input").val()),
sig = this.model.get("sig"),
queryWithSignature = {};
if (targetSignature !== "") {
queryWithSignature = this.updateSignaturesHash([targetSignature + "_q"], query, this.createOffsetObject());
} else {
queryWithSignature = this.updateSignaturesHash(sig, query, this.createOffsetObject());
}
window.location.href = urlHelperModel.createRedirectSearchUrl(resultPage, queryWithSignature, searchResultsSignature, targetSignature);
},
/**
* Takes search box value as a query value and calls
* ["updateQueryModel"]{@link module:module:searchBox.SearchBoxView#updateQueryModel}
* @memberof module:searchBox.SearchBoxView
* @param {jQuery<Event>} event jQuery event object
* @alias module:searchBox.SearchBoxView#updateQueryModelClick
*/
updateQueryModelClick: function (event) {
event.preventDefault();
var query = this.$el.find(".search-box-input.tt-input").val();
this.closeDropdown();
this.updateQueryModel(query);
},
/**
* Updates hash based on searchValue
* @memberof module:searchBox.SearchBoxView
* @param {String} query jQuery event object
* @alias module:searchBox.SearchBoxView#updateQueryModel
*/
updateQueryModel: function (query) {
var searchValue = {},
offsetSignatures = this.translateSignatures(this.model.get("dataProperties").searchResultsSignature, "e"),
sig = this.model.get("sig"),
i;
for (i = 0; i < sig.length; i++) {
searchValue[sig[i]] = query;
searchValue[offsetSignatures[i]] = 0;
}
queryModel.updateHash(searchValue, this.model.get("dataProperties").targetUrl);
},
/**
* Calls ["performSearch"]{@link module:searchBox.SearchBoxView#performSearch}
* by pressing Enter key
* @memberof module:searchBox.SearchBoxView
* @param {Event} event Event object
* @alias module:searchBox.SearchBoxView#predictiveSearch
*/
predictiveSearch: function (event) {
if (event.keyCode === 13) {
event.preventDefault();
this.performSearch(this.$el.find(".search-box-input.tt-input").val());
}
},
/**
* Makes search
* @memberof module:searchBox.SearchBoxView
* @param {String} query value from search box
* @alias module:searchBox.SearchBoxView#performSearch
*/
performSearch: function (query) {
var properties = this.model.get("dataProperties"),
targetSignature = properties.targetSignature,
searchResultsSignature = properties.searchResultsSignature,
resultPage = properties.resultPage,
sig = this.model.get("sig"),
queryWithSignature = {};
this.closeDropdown();
if (resultPage === "") {
this.updateQueryModel(query);
this.$el.find(".search-box-input.tt-input").blur().val(query);
} else {
query = encodeURIComponent(query);
if (targetSignature !== "") {
queryWithSignature = this.updateSignaturesHash([targetSignature + "_q"], query, this.createOffsetObject())
} else {
queryWithSignature = this.updateSignaturesHash(sig, query, this.createOffsetObject())
}
window.location.href = urlHelperModel.createRedirectSearchUrl(resultPage, queryWithSignature, searchResultsSignature, targetSignature);
}
},
/**
* Creates object that contain signatures
* @memberof module:searchBox.SearchBoxView
* @alias module:searchBox.SearchBoxView#createOffsetObject
*/
createOffsetObject: function () {
var sig = this.model.get("sig"),
targetSignature = this.model.get("dataProperties").targetSignature,
signature = targetSignature !== "" ? targetSignature : this.model.get("dataProperties").searchResultsSignature,
offsetSignatures = this.translateSignatures(signature, "e"),
offsetObject = {},
i;
for (i = 0; i < sig.length; i++) {
offsetObject[offsetSignatures[i]] = 0;
}
return offsetObject;
},
/**
* Sets value into search box based on hash
* @memberof module:searchBox.SearchBoxView
* @alias module:searchBox.SearchBoxView#updateSearchBoxValue
*/
updateSearchBoxValue: function () {
var hashObj = queryModel.parseHashParameters(window.location.hash),
el = this.$el.find(".search-box-input.tt-input"),
sig = this.model.get("sig"),
i;
for (i = 0; i < sig.length; i++) {
if (hashObj.hasOwnProperty(sig[i])) {
el.val(decodeURIComponent(hashObj[sig[i]]));
} else {
el.val("");
}
}
},
/**
* Closes typeahead drop down
* @memberof module:searchBox.SearchBoxView
* @alias module:searchBox.SearchBoxView#closeDropdown
*/
closeDropdown: function () {
this.$el.find(".search-box-input").typeahead('close');
}
});
/**
* For each search box on a page creates instance of
* ["searchBoxModel"]{@link module:searchBox.SearchBoxModel} and
* ["SearchBoxView"]{@link module:searchBox.SearchBoxView}
* @memberOf module:searchBox
* @alias module:searchBox.init
*/
api.init = function () {
if ($("body").hasClass("on-page-editor") || initialized) {
return;
}
queryModel = XA.component.search.query;
searchResultModels = XA.component.search.results.searchResultModels;
urlHelperModel = XA.component.search.url;
var searchBox = $(".search-box:not(.initialized)");
_.each(searchBox, function (elem) {
var $el = $(elem);
var boxModel = new SearchBoxModel();
searchBoxModels.push(boxModel);
searchBoxViews.push(new SearchBoxView({ el: $el, model: boxModel }));
$el.addClass("initialized");
});
initialized = true;
};
/**
* Extends search box API with
* ["searchBoxViews"]{@link module:searchBox.searchBoxViews}
* @memberOf module:searchBox
* @alias module:searchBox.searchBoxViews
*/
api.searchBoxViews = searchBoxViews;
/**
* Extends search box API with
* ["searchBoxModels"]{@link module:searchBox.searchBoxModels}
* @memberOf module:searchBox
* @alias module:searchBox.searchBoxViews
*/
api.searchBoxModels = searchBoxModels;
return api;
}(jQuery, document));
XA.register('searchBox', XA.component.search.box);