Automagical UI and sample react controls for Azure Search using AzSearchStore. Written in TypeScript.
- Sample TypeScript project: clone, customize, and go.
The following link contains a tool to help generate starting sample app that you can further customeize:
- AzSearch.js application generator. Generate an app to explore your data with just your service name, query key, and index definition JSON.
Don't forget to enable CORS on your index. Make sure to always use a query key.
Samples and documentation assume the real estate sample index available through the portal. A demo account is provided for the samples. To create your own service and load the real estate sample see this guide.
<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/AzSearch.css">
<!-- Dependencies -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/react/15.5.4/react.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/react/15.5.4/react-dom.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/redux/3.6.0/redux.min.js"></script>
<!-- Main -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/AzSearch.bundle.js"></script>
npm install azsearch.js --save
Automagic provides a set of simple APIs to generate a sample search UI. Automagic is a wrapper of AzSearchStore (for state management) and the sample react components in this repo.
// Initialize
var automagic = new AzSearch.Automagic({
index: "realestate-us-sample",
queryKey: "D1CD08C7AC6A1886024E0F23B1824417",
service: "azs-playground" });
// Add a search box on id #seachBox that uses suggester "sg", grabbing some additional
// fields to display during suggestions.
automagic.addSearchBox("searchBox",
{
highlightPreTag: "<b>",
highlightPostTag: "</b>",
suggesterName: "sg",
select: "number,street,city,region,countryCode"
});
// add a results view
automagic.addResults("results");
// Adds a pager control << 1 2 3 ... >>
automagic.addPager("pager");
// range facet for sqft
automagic.addRangeFacet("sqftFacet", "sqft", "number", 0, 17000);
// checkbox facet for numeric field beds
automagic.addCheckboxFacet("bedsFacet", "beds", "number");
// checkbox facet for numeric field baths
automagic.addCheckboxFacet("bathsFacet", "baths", "number");
// checkbox facet for string field type
automagic.addCheckboxFacet("typeFacet", "type", "string");
// checkbox facet for collection field tags
automagic.addCheckboxFacet("tagsFacet", "tags", "collection");
constructor(config)
Sets basic configuration to connect to service. Expects an object of type Config from AzSearchStore
// constructs and instance of Automagic
// will also create an instance of AzSearchStore that connects to your service
var automagic = new AzSearch.Automagic({
index: "realestate-us-sample",
queryKey: "D1CD08C7AC6A1886024E0F23B1824417",
service: "azs-playground" });
type Config = {
index: string;
queryKey: string;
service: string;
suggestCallback?: (state: Store.SearchState, postBody: {
[key: string]: any;
}) => Promise<any>;
searchCallback?: (state: Store.SearchState, postBody: {
[key: string]: any;
}) => Promise<any>;
};
addSearchBox(htmlId, suggestionsParametersUpdate?, suggestionValueKey?, suggestionTemplate?, css?)
Adds an input field capable of suggestions/autocomplete and executing search requests. Attaches on the specified htmlID. Optionally takes SuggestionUpdateParameters (from AzSearchStore), an optional key indicating which suggestion value should be set when a suggestion is clicked (defaults to "@search.text"), a mustache template to customize rendering, or css overrides. When template is not specified, a json representation of the suggestions is displayed. When specified, the mustache template is rendered against east suggestion. The content of each suggestion can be customized by adding fields via the select parameter as shown in the example below, or by setting a suggestions processor on the store.
// css class overrides
var css = {
searchBox__button: "searchBox__button btn btn-default",
};
// mustache template for custom suggestion rendering. Default displays formatted JSON
var suggestionsTemplate = "{{displayText}} <br/> {{{searchText}}}";
// Add a search box that uses suggester "sg", grabbing some additional fields to
// display during suggestions. Use the template defined above
automagic.addSearchBox("searchBox",
{
highlightPreTag: "<b>",
highlightPostTag: "</b>",
suggesterName: "sg",
select: "number,street,city,region,countryCode"
},
"displayText"
suggestionsTemplate,
css);
// Set a processor to format suggestions for display
var suggestionsProcessor = function(results) {
return results.map(function(result){
result.displayText = result.number + " " +
result.street+ " " +result.city+ ", " +result.region+ " " +
result.countryCode;
result.searchText = result["@search.text"];
return result;
});
};
automagic.store.setSuggestionsProcessor(suggestionsProcessor);
type SuggestionsParametersUpdate = {
top?: number;
filter?: string;
orderby?: string;
fuzzy?: boolean;
highlightPreTag?: string;
highlightPostTag?: string;
select?: string;
searchFields?: string;
minimumCoverage?: string;
apiVersion?: SearchApiVersion;
suggesterName?: string;
};
type SearchApiVersion = "2016-09-01" | "2015-02-28-Preview";
addResults(htmlId, searchParametersUpdate?, resultsTemplate?, css?)
Adds a view the search results on the specifed htmlId. Optionally takes searchParametersUpdate (from AzSearchStore), a mustache template used to format individual results, or css class overrides. When specified, the mustace template will be rendered against each individual search result. Otherwise formatted JSON will be displayed. The content of each search result can be customized by setting a results processor on the store.
var resultTemplate =
`<div class="col-xs-12 col-sm-5 col-md-3 result_img">
<img class="img-responsive result_img" src={{thumbnail}} alt="image not found" />
</div>
<div class="col-xs-12 col-sm-7 col-md-9">
<h4>{{displayText}}</h4>
<div class="resultDescription">
{{{summary}}}
</div>
<div>
sqft: <b>{{sqft}}</b>
</div>
<div>
beds: <b>{{beds}}</b>
</div>
<div>
baths: <b>{{baths}}</b>
</div>
</div>`;
// add a results view, updates parameters to include count, uses the above template
automagic.addResults("results", { count: true }, resultTemplate);
// add a resultsProcessor to more easily format the results display
var resultsProcessor = function(results) {
return results.map(function(result){
result.displayText = result.number + " " + result.street+ " " +result.city+ ", " +result.region+ " " +result.countryCode;
var summary = result.description;
result.summary = summary.length < 200 ? summary : summary.substring(0, 200) + "...";
return result;
});
};
automagic.store.setResultsProcessor(resultsProcessor);
type SearchParametersUpdate = {
count?: boolean;
top?: number;
skip?: number;
orderby?: string;
searchMode?: SearchMode;
scoringProfile?: string;
select?: string;
searchFields?: string;
minimumCoverage?: string;
apiVersion?: SearchApiVersion;
queryType?: QueryType;
};
type QueryType = "simple" | "full";
type SearchApiVersion = "2016-09-01" | "2015-02-28-Preview";
type SearchMode = "any" | "all";
addLoadingIndicator(htmlId)
Adds a component to show loading state there are inflight requests for searching/faceting on the specified id.
// Adds a loading indicator: . -> .. -> ... -> . -> .. -> ...
automagic.addLoadingIndicator("spinner");
addPager(htmlId, css?)
Adds a pagination control <<< 1 2 3 ... >>>
on the specified id after all of the results
// Adds a pager control << 1 2 3 ... >>
automagic.addPager("pager");
addRangeFacet(htmlId, fieldName, dataType, min, max, css?)
Adds a range facet control for on the specified htmlId for the given fieldName. dataType can be either "number" or "date". Range will go from min to max. Type of min and max should correspond to specified dataType.
// range facet numeric field sqft
automagic.addRangeFacet("sqftFacet", "sqft", "number", 0, 17000);
// adding a range facet on date field 'published'
// notice that this one is for example only and is not in the realestate index
// initialize the date range at the year 2007
let startDate = new Date();
startDate.setFullYear(2007);
// allow date range to go all the way up to present
let endDate = new Date();
automagic.addRangeFacet("publishedFacet", "published", "date", startDate, endDate);
addCheckboxFacet(htmlId, fieldName, dataType, css?)
Adds a checkbox style faceting control to the specified htmlId over the specified field. Supported dataTypes are "number" | "string" | "collection"
. Also accepts optional css overrides
automagic.addCheckboxFacet("bedsFacet", "beds", "number");
// checkbox facet for numeric field baths
automagic.addCheckboxFacet("bathsFacet", "baths", "number");
// checkbox facet for string field type
automagic.addCheckboxFacet("typeFacet", "type", "string");
// checkbox facet for collection field tags
automagic.addCheckboxFacet("tagsFacet", "tags", "collection");
addClearFiltersButton(htmlId, css?)
Adds a button (anchor) to the specified element (htmlId
) which when triggered clears all applied filters (facets) and updates the search results. Also accepts optional css overrides.
addSortBy(htmlId, fields, defaultSortFieldName?, css?)
Adds sorting control to the specified htmlId for the specified sortable fields. Accepts optional default sorting field name & css overrides.
// With optional displayName & default sort fieldName
var fields = [
{displayName: "Relevance", fieldName: ""},
{displayName: "Size", fieldName: "sqft"},
{displayName: "Beds", fieldName: "beds"},
{displayName: "Baths", fieldName: "baths"},
// set lat/long to do geo distance ordering
{displayName: "Distance", fieldName: "location", latitude: 47.673988099999995, longitude: -122.12151199999998}
];
automagic.addSortBy("sortBy", fields, "sqft");
addStaticFilter(htmlId, filterKey, filters, defaultFilter, title?, css?)
Adds a dropdown style filter control with static pre-defined filters. Can be useful for scenarios such as language or geo filtering. You may want to allow user to set a constant filter for language to be english, or for only results within 50 miles to be shown. filterKey
is a unique key that will be used to lookup and set the filter from state within the store state.facets.globalFilter[filterKey]
, as it is possible to have multiple global filters.
// static filter for home type
var filters = [
{ displayName: "Any", filter: "" },
{ displayName: "House", filter: "type eq 'House'"},
{ displayName: "Apartment", filter: "type eq 'Apartment'"}
];
var defaultFilter = "";
var title = "Home Type";
automagic.addStaticFilter("typeFilter", "type", filters, defaultFilter, title);
store
Instance of AzSearchStore. Methods can be called directly on the store, and actions can be dispatched to the store using APIs documented in the AzSearchStore repo.
// set api version to preview
automagic.store.setSearchApiVersion("2015-02-28-Preview");
// set a pre-defined input to search/suggest
automagic.store.setInput("bears beets battlestar galactica");
// log state changes to console
automagic.store.subscibe(function() {
var state = automagic.store.getState();
console.info(JSON.stringify(state, null, 4));
});
// clear all facets
automagic.store.clearFacetsSelections();
If you wish to use a custom theme. Please use the browser tools element inspector ctrl shift C
and compare against css constants used in the project. Css classes can be overridden in the following manner and passed in to a component:
//...
// css class overrides
var css = {
searchBox__buttonIcon: "searchBox__button-icon glyphicon glyphicon-search",
searchBox__button: "searchBox__button btn btn-default",
};
automagic.addSearchBox("searchBox",
{
highlightPreTag: "<b>",
highlightPostTag: "</b>",
suggesterName: "sg",
select: "number,street,city,region,countryCode"
},
"@search.text",
suggestionsTemplate,
css);
AzSearch.js is build with react components and react-redux containers. Both of these are exposed and available for direct consumption/extension with your own instance of AzSearchStore. More docs on this coming soon.
Set up should be as simple as running npm install
. Run npm run devbuild
for tslint, typescript compilation, and webpack dev bundling all in one step. Run npm run prodpack
for a minified bundle. Testing: npm test