Good chunk of user profile (VRFS-74)
This commit is contained in:
parent
3fc3c57e20
commit
1239cad08f
|
|
@ -0,0 +1,184 @@
|
|||
(function(context,$) {
|
||||
|
||||
/**
|
||||
* Javascript for managing the header (account dropdown) as well
|
||||
* as any dialogs reachable from there. Account settings dialog.
|
||||
*/
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.Header = function(app) {
|
||||
|
||||
var logger = context.JK.logger;
|
||||
var userMe = null;
|
||||
var instrumentAutoComplete;
|
||||
var instrumentIds = [];
|
||||
var instrumentNames = [];
|
||||
var instrumentPopularities = {}; // id -> popularity
|
||||
|
||||
function loadInstruments() {
|
||||
// TODO: This won't work in the long-term. We'll need to provide
|
||||
// a handlers which accepts some characters and only returns users
|
||||
// who are musicians who match that input string. Once we get there,
|
||||
// we could just use the ajax functionality of the autocomplete plugin.
|
||||
//
|
||||
// But for now:
|
||||
// Load the users list into our local array for autocomplete.
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/api/instruments"
|
||||
}).done(function(response) {
|
||||
$.each(response, function() {
|
||||
instrumentNames.push(this.description);
|
||||
instrumentIds.push(this.id);
|
||||
// TODO - unused at the moment.
|
||||
instrumentPopularities[this.id] = this.popularity;
|
||||
});
|
||||
// Hook up the autocomplete.
|
||||
var options = {
|
||||
lookup: {suggestions:instrumentNames, data: instrumentIds},
|
||||
onSelect: addInstrument,
|
||||
width: 120
|
||||
};
|
||||
if (!(instrumentAutoComplete)) { // Shouldn't happen here. Header only drawn once.
|
||||
instrumentAutoComplete = $('#profile-instruments').autocomplete(options);
|
||||
} else {
|
||||
instrumentAutoComplete.setOptions(options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addInstrument(value, data) {
|
||||
var instrumentName = value;
|
||||
var instrumentId = data;
|
||||
var template = $('#template-profile-instrument').html(); // TODO: cache this
|
||||
var instrumentHtml = JK.fillTemplate(
|
||||
template, {instrumentId: instrumentId, instrumentName: instrumentName});
|
||||
$('#added-profile-instruments').append(instrumentHtml);
|
||||
$('#profile-instruments').select();
|
||||
}
|
||||
|
||||
function setProficiency(id, proficiency) {
|
||||
logger.debug("setProficiency: " + id + ',' + proficiency);
|
||||
var selector = '#added-profile-instruments div.profile-instrument[instrument-id="' +
|
||||
id + '"] select';
|
||||
logger.debug("Finding select to set proficiency. Length? " + $(selector).length);
|
||||
$(selector).val(proficiency);
|
||||
}
|
||||
|
||||
function removeInvitation(evt) {
|
||||
$(this).closest('.profile-instrument').remove();
|
||||
}
|
||||
|
||||
function events() {
|
||||
$('.username').on('click', function() {
|
||||
$('ul', this).toggle();
|
||||
});
|
||||
|
||||
$('#account-identity-form').submit(handleIdentitySubmit);
|
||||
$('#account-profile-form').submit(handleProfileSubmit);
|
||||
// Remove added instruments when 'X' is clicked
|
||||
$('#added-profile-instruments').on("click", ".instrument span", removeInvitation);
|
||||
}
|
||||
|
||||
function handleIdentitySubmit(evt) {
|
||||
evt.preventDefault();
|
||||
user = $(this).formToObject();
|
||||
if (!user.password) {
|
||||
delete user.password;
|
||||
delete user.password_confirmation;
|
||||
}
|
||||
logger.debug("submitting identity form with:");
|
||||
logger.debug(user);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/users/" + userMe.id,
|
||||
contentType: "application/json",
|
||||
processData:false,
|
||||
data: JSON.stringify(user)
|
||||
}).done(function(response) {
|
||||
userMe = response;
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleProfileSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
var user = {
|
||||
name: $('#account-profile-form input[name="name"]').val(),
|
||||
instruments: []
|
||||
};
|
||||
$added_instruments = $('.profile-instrument', '#added-profile-instruments');
|
||||
var count = 1;
|
||||
$.each($added_instruments, function() {
|
||||
var instrumentId = $(this).attr('instrument-id');
|
||||
var proficiency = $('select', this).val();
|
||||
logger.debug("Instrument ID:" + instrumentId + ", proficiency: " + proficiency);
|
||||
var instrument = {
|
||||
id: instrumentId,
|
||||
proficiency_level: proficiency,
|
||||
priority: count++
|
||||
};
|
||||
user.instruments.push(instrument);
|
||||
});
|
||||
|
||||
logger.debug("About to submit profile. User:");
|
||||
logger.debug(user);
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/users/" + userMe.id,
|
||||
contentType: "application/json",
|
||||
processData:false,
|
||||
data: JSON.stringify(user)
|
||||
}).done(function(response) {
|
||||
userMe = response;
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function loadMe() {
|
||||
logger.debug("loadMe");
|
||||
logger.debug("current user: " + JK.currentUserId);
|
||||
$.ajax({
|
||||
url: '/api/users/' + JK.currentUserId
|
||||
}).done(function(r) {
|
||||
userMe = r;
|
||||
updateAccountForms();
|
||||
}).fail(function(jqXHR, textStatus, errorMessage) {
|
||||
app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
|
||||
});
|
||||
}
|
||||
|
||||
function updateAccountForms() {
|
||||
var idTemplate = $('#template-identity-summary').html();
|
||||
var idHtml = JK.fillTemplate(idTemplate, userMe);
|
||||
$('#identity-summary').html(idHtml);
|
||||
// TODO:
|
||||
// Make a thing that puts a JSON object into a form
|
||||
// and fill the profile part of the form from the JSON.
|
||||
// Short-term thing for now:
|
||||
$('#account-identity-form input[name="email"]').val(userMe.email);
|
||||
|
||||
// Profile form
|
||||
$('#account-profile-form input[name="name"]').val(userMe.name);
|
||||
if ("instruments" in userMe) {
|
||||
$.each(userMe.instruments, function() {
|
||||
addInstrument(this.description, this.id);
|
||||
setProficiency(this.id, this.proficiency_level);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.initialize = function() {
|
||||
events();
|
||||
loadInstruments();
|
||||
loadMe();
|
||||
};
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
(function(context,$) {
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.HomeScreen = function(app) {
|
||||
|
||||
function events() {
|
||||
$('.homecard').on('mouseenter', function() {
|
||||
$(this).addClass('hover');
|
||||
});
|
||||
$('.homecard').on('mouseleave', function() {
|
||||
$(this).removeClass('hover');
|
||||
});
|
||||
}
|
||||
|
||||
this.initialize = function() {
|
||||
events();
|
||||
};
|
||||
};
|
||||
|
||||
})(window,jQuery);
|
||||
|
|
@ -37,18 +37,6 @@
|
|||
$(routes.handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some simple events for hovering on the home page. May want to move to a separate file.
|
||||
*/
|
||||
function events() {
|
||||
$('.homecard').on('mouseenter', function() {
|
||||
$(this).addClass('hover');
|
||||
});
|
||||
$('.homecard').on('mouseleave', function() {
|
||||
$(this).removeClass('hover');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a websocket message
|
||||
*/
|
||||
|
|
@ -72,13 +60,13 @@
|
|||
}
|
||||
|
||||
function _heartbeat() {
|
||||
//logger.debug("...sending heartbeat");
|
||||
message = context.JK.MessageFactory.heartbeat();
|
||||
context.JK.JamServer.send(message);
|
||||
}
|
||||
|
||||
function loggedIn(header, payload) {
|
||||
logger.debug('loggedIn');
|
||||
logger.debug(payload);
|
||||
app.clientId = payload.client_id;
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +85,7 @@
|
|||
logger.debug("registering " + message);
|
||||
context.JK.JamServer.registerMessageCallback(message, handleMessage);
|
||||
}
|
||||
// Specifically register a handler for LOGIN_ACK to setup the heartbeat calls.
|
||||
// Setup the heartbeat calls:
|
||||
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, _handleLoginAck);
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +139,6 @@
|
|||
routing();
|
||||
registerMessages();
|
||||
registerLoginAck();
|
||||
events();
|
||||
|
||||
hash = location.hash;
|
||||
url = '#/home';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* hoverIntent is similar to jQuery's built-in "hover" function except that
|
||||
* instead of firing the onMouseOver event immediately, hoverIntent checks
|
||||
* to see if the user's mouse has slowed down (beneath the sensitivity
|
||||
* threshold) before firing the onMouseOver event.
|
||||
*
|
||||
* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
|
||||
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
|
||||
*
|
||||
* hoverIntent is currently available for use in all personal or commercial
|
||||
* projects under both MIT and GPL licenses. This means that you can choose
|
||||
* the license that best suits your project, and use it accordingly.
|
||||
*
|
||||
* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
|
||||
* $("ul li").hoverIntent( showNav , hideNav );
|
||||
*
|
||||
* // advanced usage receives configuration object only
|
||||
* $("ul li").hoverIntent({
|
||||
* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
|
||||
* interval: 100, // number = milliseconds of polling interval
|
||||
* over: showNav, // function = onMouseOver callback (required)
|
||||
* timeout: 0, // number = milliseconds delay before onMouseOut function call
|
||||
* out: hideNav // function = onMouseOut callback (required)
|
||||
* });
|
||||
*
|
||||
* @param f onMouseOver function || An object with configuration options
|
||||
* @param g onMouseOut function || Nothing (use configuration options object)
|
||||
* @author Brian Cherne brian(at)cherne(dot)net
|
||||
*/
|
||||
(function($) {
|
||||
$.fn.hoverIntent = function(f,g) {
|
||||
// default configuration options
|
||||
var cfg = {
|
||||
sensitivity: 7,
|
||||
interval: 100,
|
||||
timeout: 0
|
||||
};
|
||||
// override configuration options with user supplied object
|
||||
cfg = $.extend(cfg, g ? { over: f, out: g } : f );
|
||||
|
||||
// instantiate variables
|
||||
// cX, cY = current X and Y position of mouse, updated by mousemove event
|
||||
// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
|
||||
var cX, cY, pX, pY;
|
||||
|
||||
// A private function for getting mouse position
|
||||
var track = function(ev) {
|
||||
cX = ev.pageX;
|
||||
cY = ev.pageY;
|
||||
};
|
||||
|
||||
// A private function for comparing current and previous mouse position
|
||||
var compare = function(ev,ob) {
|
||||
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
|
||||
// compare mouse positions to see if they've crossed the threshold
|
||||
if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
|
||||
$(ob).unbind("mousemove",track);
|
||||
// set hoverIntent state to true (so mouseOut can be called)
|
||||
ob.hoverIntent_s = 1;
|
||||
return cfg.over.apply(ob,[ev]);
|
||||
} else {
|
||||
// set previous coordinates for next time
|
||||
pX = cX; pY = cY;
|
||||
// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
|
||||
ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
|
||||
}
|
||||
};
|
||||
|
||||
// A private function for delaying the mouseOut function
|
||||
var delay = function(ev,ob) {
|
||||
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
|
||||
ob.hoverIntent_s = 0;
|
||||
return cfg.out.apply(ob,[ev]);
|
||||
};
|
||||
|
||||
// A private function for handling mouse 'hovering'
|
||||
var handleHover = function(e) {
|
||||
// copy objects to be passed into t (required for event object to be passed in IE)
|
||||
var ev = jQuery.extend({},e);
|
||||
var ob = this;
|
||||
|
||||
// cancel hoverIntent timer if it exists
|
||||
if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
|
||||
|
||||
// if e.type == "mouseenter"
|
||||
if (e.type == "mouseenter") {
|
||||
// set "previous" X and Y position based on initial entry point
|
||||
pX = ev.pageX; pY = ev.pageY;
|
||||
// update "current" X and Y position based on mousemove
|
||||
$(ob).bind("mousemove",track);
|
||||
// start polling interval (self-calling timeout) to compare mouse coordinates over time
|
||||
if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
|
||||
|
||||
// else e.type == "mouseleave"
|
||||
} else {
|
||||
// unbind expensive mousemove event
|
||||
$(ob).unbind("mousemove",track);
|
||||
// if hoverIntent state is true, then call the mouseOut function after the specified delay
|
||||
if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
|
||||
}
|
||||
};
|
||||
|
||||
// bind the function to the two event listeners
|
||||
return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover);
|
||||
};
|
||||
})(jQuery);
|
||||
|
|
@ -64,6 +64,15 @@ label {
|
|||
display:block;
|
||||
}
|
||||
|
||||
.button1 {
|
||||
background: $color1;
|
||||
color: #fff !important;
|
||||
font-weight:bold;
|
||||
font-size: 120%;
|
||||
margin: 2px;
|
||||
padding:8px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
|
@ -82,6 +91,31 @@ label {
|
|||
font-weight:bold;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color:$color8;
|
||||
border: #666;
|
||||
color:#000;
|
||||
min-width: 400px;
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
.dialog a {
|
||||
color: $color1;
|
||||
}
|
||||
|
||||
.dialog h1 {
|
||||
font-weight: bold;
|
||||
font-size: 150%;
|
||||
margin: 12px;
|
||||
}
|
||||
.dialog .panel {
|
||||
background-color: shade($color8, 6%);
|
||||
margin:12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.header h1 {
|
||||
margin:22px;
|
||||
font-size:300%;
|
||||
|
|
@ -126,7 +160,7 @@ label {
|
|||
|
||||
.screen.secondary {
|
||||
}
|
||||
.screen.secondary .footer {
|
||||
.buttonrow, .screen.secondary .footer {
|
||||
position: absolute;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
|
|
@ -279,7 +313,8 @@ label {
|
|||
clear:both;
|
||||
}
|
||||
|
||||
.invitation {
|
||||
/* TODO - generalize */
|
||||
.instrument, .invitation {
|
||||
margin: 1px;
|
||||
background: $color8;
|
||||
color:#000;
|
||||
|
|
@ -288,12 +323,16 @@ label {
|
|||
float:left;
|
||||
}
|
||||
|
||||
.invitation span {
|
||||
.instrument span, .invitation span {
|
||||
font-size:85%;
|
||||
font-weight:bold;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.profiency {
|
||||
float:left;
|
||||
}
|
||||
|
||||
/* Autocomplete */
|
||||
.autocomplete {
|
||||
border:1px solid #999;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<h2><%= current_user.name %></h2>
|
||||
<%= image_tag "down_arrow.png", :class=> "profile-toggle" %>
|
||||
<ul>
|
||||
<li>Profile</li>
|
||||
<li><a layout-link="account">Profile</a></li>
|
||||
<li><%= link_to "Sign out", signout_path, method: "delete" %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout="sidebar" class="sidebar">
|
||||
<div layout-sidebar-expander="visible" class="expander visible">
|
||||
<p>>> Hide</p>
|
||||
|
|
@ -427,10 +428,104 @@
|
|||
<p layout-link="home">Home</p>
|
||||
</div>
|
||||
|
||||
<div layout="dialog" layout-id="dialog1" class="dialog">
|
||||
<h1>Dialog 1</h1>
|
||||
<a layout-action="close">Close</a>
|
||||
<!-- Account Summary Dialog -->
|
||||
<div layout="dialog" layout-id="account" class="dialog">
|
||||
<h1>Account Settings</h1>
|
||||
<div class="panel">
|
||||
<h2>Identity</h2>
|
||||
<div id="identity-summary"></div>
|
||||
<a layout-link="account-identity">Update</a>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>Profile</h2>
|
||||
<div id="profile-summary"></div>
|
||||
<a layout-link="account-profile">Update</a>
|
||||
</div>
|
||||
<div class="buttonrow">
|
||||
<a layout-action="close" class="button1">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-identity-summary">
|
||||
<p>Username: {email}</p>
|
||||
<p>Change Password Here</p>
|
||||
</script>
|
||||
|
||||
<!-- Account Identity Update Form -->
|
||||
<div layout="dialog" layout-id="account-identity" class="dialog">
|
||||
<form id="account-identity-form">
|
||||
<fieldset>
|
||||
<legend>Identity</legend>
|
||||
<div class="formrow">
|
||||
<label>Email
|
||||
<input name="email" type="text"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<label>Password
|
||||
<input name="password" type="password"/>
|
||||
</label>
|
||||
<p class="tip">Leave blank to leave unchanged</p>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<label>Confirm Password
|
||||
<input name="password_confirmation" type="password"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="buttonrow">
|
||||
<button layout-action="close">Cancel</button>
|
||||
<input type="submit" value"Update"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Account Profile Update Form -->
|
||||
<div layout="dialog" layout-id="account-profile" class="dialog">
|
||||
<form id="account-profile-form">
|
||||
<fieldset>
|
||||
<legend>Profile</legend>
|
||||
<div class="formrow">
|
||||
<label>Name
|
||||
<input type="text" name="name"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<label>Profile Picture
|
||||
<p>TO DO</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div id="added-profile-instruments"></div>
|
||||
<div id="profile-instruments-controls">
|
||||
<label>Instruments
|
||||
<input id="profile-instruments" type="text"/>
|
||||
</label>
|
||||
<p class="tip">Limit of 5</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttonrow">
|
||||
<button layout-action="close">Cancel</button>
|
||||
<input type="submit" value"Update"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="template-profile-instrument">
|
||||
<div class="profile-instrument" instrument-id="{instrumentId}">
|
||||
<div class="instrument">{instrumentName} <span>X</span></div>
|
||||
<div class="proficiency">
|
||||
<select>
|
||||
<option value="1">Beginner</option>
|
||||
<option selected="selected" value="2">Intermediate</option>
|
||||
<option value="3">Advanced</option>
|
||||
<option value="4">Expert</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<div layout="notify" class="notify">
|
||||
<h1>Notification Popup</h1>
|
||||
<a layout-action="close">Close</a>
|
||||
|
|
@ -440,9 +535,19 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
|
||||
|
||||
var jk = JK.JamKazam();
|
||||
jk.initialize();
|
||||
|
||||
JK.currentUserId = '<%= current_user.id %>';
|
||||
|
||||
var header = new JK.Header(jk);
|
||||
header.initialize();
|
||||
|
||||
var homeScreen = new JK.HomeScreen(jk);
|
||||
homeScreen.initialize();
|
||||
|
||||
var createSessionScreen = new JK.CreateSessionScreen(jk);
|
||||
createSessionScreen.initialize();
|
||||
|
||||
|
|
@ -454,6 +559,7 @@
|
|||
|
||||
var jam_server = JK.JamServer;
|
||||
jam_server.connect();
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue