Skip to content

Instantly share code, notes, and snippets.

@jmccartie
Last active January 25, 2016 00:22
Show Gist options
  • Save jmccartie/2c76022b8cade2e0aa27 to your computer and use it in GitHub Desktop.
Save jmccartie/2c76022b8cade2e0aa27 to your computer and use it in GitHub Desktop.
First shot at some React
(function($){
$.fn.searchField = function(){
this.each(function(_index, entry){
var $entry = $(entry);
//1. Add the results dropdown page
// - add the pane
// - add the help message
// - add the results list
//2. Handle the dropdown events
// - show and hide the dropdown
//3. Handle the visibility of the contents of the dropdown
//4. Populate the results list upon text entry
//5. Handle the down and up arrows
//6. Handle the item navigation upon selection
var data = {
oldEntryText: "",
entryText: "",
results: [],
grouped: {campgrounds: [], places: [], cities: [], states: []},
hasFocus: false,
selectedIndex: -1
};
var dropdownTemplate = "<div class='search-field-dropdown'><div class='search-field-dropdown-help'></div><div class='search-field-dropdown-results'></div></div>";
var helpTemplate = "<div class='search-field-help'>Start typing...<br />&nbsp;- City, State or Zip Code<br />&nbsp;- National Park, National Forest, State Park<br />&nbsp;- Campground Name</div>";
var resultsTemplate = "<div class='search-field-results'></div>";
// DOM manipulation part
$entry.attr('autocomplete', 'off');
$entry.after(dropdownTemplate);
var $dropdown = $entry.next();
$dropdown.append(helpTemplate).append(resultsTemplate);
var $help = $dropdown.find('.search-field-help');
var $results = $dropdown.find('.search-field-results');
var groupResults = function(results){
var pregrouped = _.groupBy(results, 'type');
var grouped = {};
grouped.campgrounds = pregrouped['Campground'];
grouped.places = _.sortBy(pregrouped['Place'], "title");
grouped.cities = _.sortBy(pregrouped['City'], "title");
grouped.states = _.sortBy(pregrouped['State'], "title");
return grouped;
};
var updateResults = _.debounce(function(){
$.getJSON('/search.json', { phrase: data.entryText }, function(reply){
data.results = reply;
data.grouped = groupResults(data.results);
data.selectedIndex = -1;
synchronize(false);
});
}, 300);
var updateData = function(fromEvent){
if(fromEvent){
data.oldEntryText = data.entryText;
data.entryText = $entry.val();
if(data.oldEntryText !== data.entryText){
updateResults();
}
}
};
var handleItemSelect = function(e){
var $el = $(e.currentTarget);
window.location.href = $el.data('url');
return false;
};
var updateContentsVisibility = function(){
var funcs = data.results.length > 0 ? ['hide', 'show'] : ['show', 'hide'];
$help[funcs[0]]();
$results[funcs[1]]();
var dropdownFunc = data.hasFocus ? 'show' : 'hide';
$dropdown[dropdownFunc]();
};
var updateResultsList = function(){
var entities = [['campgrounds', 'Campgrounds'], ['places', 'Places'], ['cities', 'Cities'], ['states', 'States']].reverse();
$results.html(_.map(entities, function(o){
return "<div class='results-" + o[0] + "'><h3>" + o[1] + "</h3><ul></ul></div>";
}).join("\n"));
_.each(entities, function(e){
var $list = $results.find(".results-" + e[0] + " ul");
if(data.grouped[e[0]] !== undefined && data.grouped[e[0]].length > 0){
_.each(data.grouped[e[0]], function(result){
$list.append("<li data-url='" + result.url + "'>" + result.title + "</li>");
});
$('li', $list).on('click', handleItemSelect);
}
else {
$list.parent().hide();
}
});
};
var synchronize = function(fromEvent){
updateData(fromEvent);
updateContentsVisibility();
if(fromEvent == false){
updateResultsList();
}
};
var updateSelection = function(delta){
data.selectedIndex = data.selectedIndex + delta;
if(data.selectedIndex < 0) data.selectedIndex = 0;
if(data.selectedIndex >= data.results.length) data.selectedIndex = data.results.length - 1;
$results.find('li').removeClass('hovered');
$($results.find('li')[data.selectedIndex]).addClass('hovered');
};
var navigateSelected = function(){
if(data.selectedIndex !== -1){
var url = $($results.find('li')[data.selectedIndex]).data('url');
window.location.href = url;
}
else {
$entry.parents('form').first().submit();
}
return false;
};
// Basic spatial positioning part
var updateSpatialPositioning = function(){
var entryBottom = $entry.position().top + $entry.outerHeight(true);
$dropdown.css('width', $entry.outerWidth(true));
$dropdown.css('top', entryBottom + 2);
}
var initialize = function(){
$entry.on('keyup', function(){
synchronize(true);
});
$entry.on('focusin', function(){
data.hasFocus = true;
synchronize(true);
});
$entry.on('keydown', function(e){
switch(e.which){
case 38: // up
updateSelection(-1);
e.preventDefault();
break;
case 40: // down
updateSelection(1);
e.preventDefault();
break;
case 13: // enter
navigateSelected();
e.preventDefault();
break;
default: return;
}
});
$(window).on('click', function(e){
if(e.target !== $entry[0] && $(e.target).parents('.search-field-dropdown').length == 0 ){
data.hasFocus = false;
synchronize(true);
}
});
$(window).on('resize', updateSpatialPositioning);
updateSpatialPositioning();
synchronize(false);
return $entry;
};
return initialize();
});
return this;
};
$(document).ready(function(){
$('.search-field').searchField();
});
}(jQuery));
var SearchField = React.createClass({
getInitialState: function() {
return {dropdownVisibility: 'none', helpVisibility: 'block', results: []};
},
fetchResults: function(event) {
$.getJSON('/search.json', { q: event.target.value }, function(data){
this.setState({ results: data });
if (data.length > 0) {
this.hideHelp();
} else {
this.showHelp();
}
}.bind(this));
},
showHelp: function() {
this.setState({ helpVisibility: 'block'});
},
hideHelp: function() {
this.setState({ helpVisibility: 'none'});
},
showDropdown: function() {
this.setState({dropdownVisibility: 'block'});
},
hideDropdown: function () {
this.setState({dropdownVisibility: 'none'});
},
render: function() {
return <div>
<input id="q_ftx_search_cont" className="string required form-control search-field"
autoComplete="off" name="q[ftx_search_cont]" placeholder="Search" type="Text"
onKeyUp={this.fetchResults} onFocus={this.showDropdown} />
<div className='search-field-dropdown' style={{display: this.state.dropdownVisibility}}>
<div className='search-field-dropdown-help'></div>
<div className='search-field-dropdown-results'>
{this.state.results.map(function(result) {
return <SearchFieldGroup key={result.title} data={result}/>;
})}
</div>
<div className='search-field-help' style={{display: this.state.helpVisibility}}>Start typing...<br />&nbsp;- City, State or Zip Code<br />&nbsp;- National Park, National Forest, State Park<br />&nbsp;- Campground Name</div>
</div>
</div>
;
}
});
var SearchFieldGroup = React.createClass({
render: function() {
return <div>
<h3>{this.props.data.title}</h3>
<ul>
{this.props.data.results.map(function(result) {
return <SearchFieldResult key={result.id} data={result} />
})}
</ul>
</div>
;
}
});
var SearchFieldResult = React.createClass({
visitPath: function() {
window.location = this.props.data.url;
},
render: function() {
return <li><span onClick={this.visitPath}>{this.props.data.title}</span></li>;
}
});
@jmccartie
Copy link
Author

Thanks, @danott!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment