Last active
January 25, 2016 00:22
-
-
Save jmccartie/2c76022b8cade2e0aa27 to your computer and use it in GitHub Desktop.
First shot at some React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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 /> - City, State or Zip Code<br /> - National Park, National Forest, State Park<br /> - 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)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 /> - City, State or Zip Code<br /> - National Park, National Forest, State Park<br /> - Campground Name</div> | |
</div> | |
</div> | |
; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks, @danott!