Merge branch 'master' of bitbucket.org:jamkazam/jam-web
This commit is contained in:
commit
2d856351d7
|
|
@ -126,9 +126,7 @@ Message from Seth on sequence for creating/joining sessions:
|
|||
self.location = '#/session/' + newSessionId;
|
||||
});
|
||||
}
|
||||
).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
).fail(app.ajaxError);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -147,9 +145,7 @@ Message from Seth on sequence for creating/joining sessions:
|
|||
data: invite
|
||||
}).done(function(response) {
|
||||
callCount--;
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
}).fail(app.ajaxError);
|
||||
});
|
||||
// TODO - this is the second time I've used this pattern.
|
||||
// refactor to make a common utility for this.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
context.JK.Header = function(app) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var searcher; // Will hold an instance to a JK.Searcher (search.js)
|
||||
var userMe = null;
|
||||
var instrumentAutoComplete;
|
||||
var instrumentIds = [];
|
||||
|
|
@ -97,9 +98,7 @@
|
|||
data: JSON.stringify(user)
|
||||
}).done(function(response) {
|
||||
userMe = response;
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
}).fail(app.ajaxError);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -134,9 +133,7 @@
|
|||
data: JSON.stringify(user)
|
||||
}).done(function(response) {
|
||||
userMe = response;
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
}).fail(app.ajaxError);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -149,9 +146,7 @@
|
|||
}).done(function(r) {
|
||||
userMe = r;
|
||||
updateAccountForms();
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
}).fail(app.ajaxError);
|
||||
}
|
||||
|
||||
function updateAccountForms() {
|
||||
|
|
@ -178,6 +173,9 @@
|
|||
events();
|
||||
loadInstruments();
|
||||
loadMe();
|
||||
|
||||
searcher = new JK.Searcher(app);
|
||||
searcher.initialize();
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@
|
|||
context.JK.MessageType.LOGIN_ACK, loggedIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic error handler for Ajax calls.
|
||||
*/
|
||||
function ajaxError(jqXHR, textStatus, errorMessage) {
|
||||
notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for all known types, logging events as they happen, and
|
||||
* notifying subscribers (see this.subscribe) as they occur.
|
||||
|
|
@ -107,6 +114,11 @@
|
|||
*/
|
||||
this.fireEvent = handleMessage;
|
||||
|
||||
/**
|
||||
* Expose ajaxError.
|
||||
*/
|
||||
this.ajaxError = ajaxError;
|
||||
|
||||
/**
|
||||
* Provide a handler object for events related to a particular screen
|
||||
* being shown or hidden.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
client.JoinSession({ sessionID: session_id });
|
||||
}
|
||||
context.JK.refreshMusicSession(session_id);
|
||||
});
|
||||
}).fail(app.ajaxError);
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
(function(context,$) {
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.Searcher = function(app) {
|
||||
var logger = context.JK.logger;
|
||||
|
||||
var searchSectionTemplate;
|
||||
var searchItemTemplate;
|
||||
var noResultsTemplate;
|
||||
|
||||
function events() {
|
||||
$('.searchtextinput').keyup(handleKeyup);
|
||||
$('.searchtextinput').focus(function(evt) {
|
||||
var searchVal = $(this).val();
|
||||
search(searchVal);
|
||||
});
|
||||
$('.searchtextinput').blur(hideSearchResults);
|
||||
}
|
||||
|
||||
function templates() {
|
||||
searchSectionTemplate = $('#template-search-section').html();
|
||||
searchItemTemplate = $('#template-search-item').html();
|
||||
noResultsTemplate = $('#template-search-noresults').html();
|
||||
}
|
||||
|
||||
function hideSearchResults() {
|
||||
$('.searchresults').hide();
|
||||
}
|
||||
|
||||
function showSearchResults() {
|
||||
$('.searchresults').show();
|
||||
}
|
||||
|
||||
function handleKeyup(evt) {
|
||||
if (evt.which === 27) {
|
||||
return hideSearchResults();
|
||||
}
|
||||
var searchVal = $(this).val();
|
||||
search(searchVal);
|
||||
}
|
||||
|
||||
function search(query) {
|
||||
if (query.length < 2) {
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/api/search?query=" + query,
|
||||
success: searchResponse,
|
||||
error: app.ajaxError
|
||||
});
|
||||
}
|
||||
|
||||
function searchResponse(response) {
|
||||
ensureResultsDiv();
|
||||
updateResultsDiv(response);
|
||||
positionResultsDiv();
|
||||
showSearchResults();
|
||||
}
|
||||
|
||||
function ensureResultsDiv() {
|
||||
if ($('.searchresults').length === 0) {
|
||||
$searchresults = $('<div/>');
|
||||
$searchresults.addClass('searchresults');
|
||||
$searchresults.css({position:'absolute'});
|
||||
$('body').append($searchresults);
|
||||
}
|
||||
}
|
||||
|
||||
function updateResultsDiv(searchResults) {
|
||||
var sections = ['musicians', 'bands', 'fans', 'recordings'];
|
||||
var fullHtml = '';
|
||||
$.each(sections, function() {
|
||||
fullHtml += getSectionHtml(this, searchResults);
|
||||
});
|
||||
if (fullHtml === '') {
|
||||
fullHtml += getNoResultsMessage();
|
||||
}
|
||||
$('.searchresults').html(fullHtml);
|
||||
}
|
||||
|
||||
function getNoResultsMessage() {
|
||||
// No replacement needed at the moment.
|
||||
return noResultsTemplate;
|
||||
}
|
||||
|
||||
function getSectionHtml(section, searchResults) {
|
||||
if (section in searchResults && searchResults[section].length === 0) {
|
||||
return '';
|
||||
}
|
||||
var items = '';
|
||||
$.each(searchResults[section], function() {
|
||||
items += getItemHtml(this);
|
||||
});
|
||||
var html = JK.fillTemplate(
|
||||
searchSectionTemplate,
|
||||
{ section: section, items: items });
|
||||
return html;
|
||||
}
|
||||
|
||||
function getItemHtml(item) {
|
||||
var replacements = {
|
||||
id: item.id,
|
||||
name: item.first_name + " " + item.last_name,
|
||||
image: item.photo_url,
|
||||
subtext: item.location
|
||||
};
|
||||
return JK.fillTemplate(
|
||||
searchItemTemplate, replacements);
|
||||
}
|
||||
|
||||
function positionResultsDiv() {
|
||||
var bodyOffset = $('body').offset();
|
||||
var inputOffset = $('.searchtextinput').offset();
|
||||
var inputHeight = $('.searchtextinput').outerHeight();
|
||||
var resultsTop = bodyOffset.top + inputOffset.top + inputHeight;
|
||||
var resultsLeft = bodyOffset.left + inputOffset.left;
|
||||
$('.searchresults').css({
|
||||
top: resultsTop + 'px',
|
||||
left: resultsLeft + 'px'});
|
||||
}
|
||||
|
||||
this.initialize = function() {
|
||||
events();
|
||||
templates();
|
||||
};
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/* Styles used by things related to search */
|
||||
@import "client/common.css.scss";
|
||||
|
||||
/* Container for the search input */
|
||||
.header .search {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -125px;
|
||||
top: 26px;
|
||||
}
|
||||
|
||||
.searchtextinput {
|
||||
border: 1px solid #fff;
|
||||
background:none;
|
||||
color:#fff;
|
||||
font-size: 120%;
|
||||
line-height: 120%;
|
||||
width: 250px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.searchresults {
|
||||
background-color:$color8;
|
||||
border:1px solid #000;
|
||||
color:#000;
|
||||
}
|
||||
|
||||
.searchresults h2 {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
background-color: shade($color8, 10%);
|
||||
}
|
||||
|
||||
.searchresults li {
|
||||
clear:both;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.searchresults img {
|
||||
float:left;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #333;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.searchresults .text {
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.searchresults .subtext {
|
||||
display:block;
|
||||
color: #000;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
|
|
@ -14,4 +14,4 @@ child(:fans => :fans) {
|
|||
|
||||
child(:recordings => :recordings) {
|
||||
attributes :id, :name
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,32 @@
|
|||
<div class="curtain"></div>
|
||||
<div layout="header" class="header">
|
||||
<h1>JamKazam</h1>
|
||||
<div class="search">
|
||||
<input type="text" class="searchtextinput" placeholder="Search for Bands, Musicians and Fans"/>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-search-section">
|
||||
<h2>{section}</h2>
|
||||
<ul>
|
||||
{items}
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-search-noresults">
|
||||
<h2 class="emptyresult">No Matches</h2>
|
||||
<p>No results returned</p>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-search-item">
|
||||
<li>
|
||||
<a>
|
||||
<img src="{image}"/>
|
||||
<span class="text">{name}</span>
|
||||
<span class="subtext">{subtext}</span>
|
||||
</a>
|
||||
</li>
|
||||
</script>
|
||||
|
||||
<div class="userinfo">
|
||||
<%= gravatar_for current_user, size: 52, hclass: "avatar medium" %>
|
||||
<div class="username">
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<![endif]-->
|
||||
<%= stylesheet_link_tag "client/ie", media: "all" %>
|
||||
<%= stylesheet_link_tag "client/jamkazam", media: "all" %>
|
||||
<%= stylesheet_link_tag "client/search", media: "all" %>
|
||||
<%= stylesheet_link_tag "client/lato", media: "all" %>
|
||||
<%= include_gon %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Fixtures for Jasmine Tests for searcher.js -->
|
||||
<input type="text" class="searchtextinput" placeholder="Search for Magical Things" />
|
||||
|
||||
<script type="text/template" id="template-search-section">
|
||||
<h2>{section}</h2>
|
||||
<ul>
|
||||
{items}
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-search-noresults">
|
||||
<h2 class="emptyresult">No Matches</h2>
|
||||
<p>No results returned</p>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="template-search-item">
|
||||
<li>
|
||||
<a>
|
||||
<img src="{image}"/>
|
||||
<span class="text">{name}</span>
|
||||
<span class="subtext">{subtext}</span>
|
||||
</a>
|
||||
</li>
|
||||
</script>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
window.TestResponses = {
|
||||
search : {
|
||||
bands: [ ],
|
||||
musicians: [ ],
|
||||
fans: [
|
||||
{
|
||||
id: "1",
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
location: "Austin, TX",
|
||||
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
|
||||
}
|
||||
],
|
||||
recordings: [ ]
|
||||
},
|
||||
|
||||
emptySearch: {
|
||||
bands: [],
|
||||
musicians: [],
|
||||
fans: [],
|
||||
recordings: []
|
||||
},
|
||||
|
||||
fullSearch: {
|
||||
bands: [
|
||||
{
|
||||
id: "1",
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
location: "Austin, TX",
|
||||
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
|
||||
}
|
||||
],
|
||||
musicians: [
|
||||
{
|
||||
id: "1",
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
location: "Austin, TX",
|
||||
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
|
||||
}
|
||||
],
|
||||
fans: [
|
||||
{
|
||||
id: "1",
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
location: "Austin, TX",
|
||||
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
|
||||
}
|
||||
],
|
||||
recordings: [
|
||||
{
|
||||
id: "1",
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
location: "Austin, TX",
|
||||
photo_url: "http://www.jamkazam.com/images/users/photos/1.gif"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
(function(context) {
|
||||
|
||||
describe("searcher.js tests", function() {
|
||||
|
||||
describe("Empty Search", function() {
|
||||
|
||||
// See the markup in fixtures/searcher.htm
|
||||
|
||||
var searcher;
|
||||
var ajaxSpy;
|
||||
|
||||
var fakeApp = {
|
||||
ajaxError: function() {
|
||||
console.debug("ajaxError");
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('searcher.htm');
|
||||
spyOn($, "ajax").andCallFake(function(opts) {
|
||||
opts.success(TestResponses.emptySearch);
|
||||
});
|
||||
searcher = new JK.Searcher(fakeApp);
|
||||
searcher.initialize();
|
||||
});
|
||||
|
||||
it("No Results message shown", function() {
|
||||
// Workaround for key events not being reflected in val() calls
|
||||
$('.searchtextinput').val('AA');
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').focus();
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
expect($('.searchresults .emptyresult').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Full Search", function() {
|
||||
|
||||
// See the markup in fixtures/searcher.htm
|
||||
|
||||
var searcher;
|
||||
var ajaxSpy;
|
||||
|
||||
var fakeApp = {
|
||||
ajaxError: function() {
|
||||
console.debug("ajaxError");
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('searcher.htm');
|
||||
spyOn($, "ajax").andCallFake(function(opts) {
|
||||
opts.success(TestResponses.fullSearch);
|
||||
});
|
||||
searcher = new JK.Searcher(fakeApp);
|
||||
searcher.initialize();
|
||||
});
|
||||
|
||||
it("No Results message shown", function() {
|
||||
// Workaround for key events not being reflected in val() calls
|
||||
$('.searchtextinput').val('AA');
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').focus();
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
expect($('.searchresults h2').length).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Search Tests", function() {
|
||||
|
||||
// See the markup in fixtures/searcher.htm
|
||||
|
||||
var searcher;
|
||||
var ajaxSpy;
|
||||
|
||||
var fakeApp = {
|
||||
ajaxError: function() {
|
||||
console.debug("ajaxError");
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('searcher.htm');
|
||||
spyOn($, "ajax").andCallFake(function(opts) {
|
||||
opts.success(TestResponses.search);
|
||||
});
|
||||
searcher = new JK.Searcher(fakeApp);
|
||||
searcher.initialize();
|
||||
});
|
||||
|
||||
it("first keypress should not search", function() {
|
||||
// Workaround for key events not being reflected in val() calls
|
||||
$('.searchtextinput').val('A');
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').focus();
|
||||
$('.searchtextinput').trigger(e);
|
||||
expect($.ajax.wasCalled).toBe(false);
|
||||
});
|
||||
|
||||
it("second keypress should search", function() {
|
||||
$('.searchtextinput').val('AA');
|
||||
$('.searchtextinput').focus();
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').trigger(e);
|
||||
// trigger again
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
expect($.ajax).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("response div is absolute position", function() {
|
||||
$('.searchtextinput').val('AA');
|
||||
$('.searchtextinput').focus();
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
expect($('.searchresults').css('position')).toEqual('absolute');
|
||||
});
|
||||
|
||||
it("response displayed in results", function() {
|
||||
$('.searchtextinput').val('AA');
|
||||
$('.searchtextinput').focus();
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
expect($('.searchresults').length).toEqual(1);
|
||||
expect($('.searchresults h2').length).toEqual(1);
|
||||
expect($('.searchresults li').length).toEqual(1);
|
||||
expect($('.searchresults li img').length).toEqual(1);
|
||||
expect($('.searchresults li span.text').length).toEqual(1);
|
||||
expect($('.searchresults li span.subtext').length).toEqual(1);
|
||||
});
|
||||
|
||||
|
||||
it("response positioned under input", function() {
|
||||
$('.searchtextinput').val('AA');
|
||||
$('.searchtextinput').focus();
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
expect($('.searchresults').length).toEqual(1);
|
||||
var bodyOffset = $('body').offset();
|
||||
var inputOffset = $('.searchtextinput').offset();
|
||||
var inputHeight = $('.searchtextinput').outerHeight();
|
||||
var expectedTop = bodyOffset.top + inputOffset.top + inputHeight;
|
||||
var expectedLeft = bodyOffset.left + inputOffset.left;
|
||||
|
||||
var searchResultOffset = $('.searchresults').offset();
|
||||
expect(searchResultOffset.top).toEqual(expectedTop);
|
||||
expect(searchResultOffset.left).toEqual(expectedLeft);
|
||||
});
|
||||
|
||||
it("search results are visible", function() {
|
||||
$('.searchtextinput').val('AA');
|
||||
$('.searchtextinput').focus();
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
var visible = $('.searchresults').is(':visible');
|
||||
expect(visible).toBe(true);
|
||||
});
|
||||
|
||||
it("escape key hides search results", function() {
|
||||
$('.searchtextinput').val('AA');
|
||||
$('.searchtextinput').focus();
|
||||
var e = jQuery.Event("keyup");
|
||||
e.which = 65; // "a"
|
||||
$('.searchtextinput').trigger(e);
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
e = jQuery.Event("keyup");
|
||||
e.which = 27; // ESCAPE
|
||||
$('.searchtextinput').trigger(e);
|
||||
|
||||
var visible = $('.searchresults').is(':visible');
|
||||
expect(visible).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
})(window);
|
||||
Loading…
Reference in New Issue