PageRenderTime 86ms CodeModel.GetById 2ms RepoModel.GetById 0ms app.codeStats 0ms

/Apps/MergedContacts/static/js/client.js

https://github.com/robknight/Locker
JavaScript | 520 lines | 380 code | 60 blank | 80 comment | 130 complexity | ee62c0c005077b58de36e9e86f3ba427 MD5 | raw file
  1. var log = function(msg) { if (console && console.log) console.debug(msg); };
  2. var displayedContact = '';
  3. $(function() {
  4. $(document).keydown(function(e) {
  5. // disable enter
  6. if (e.keyCode === 13) return false;
  7. // esc key
  8. if (e.keyCode === 27) {
  9. $("input#search").val('');
  10. return true;
  11. }
  12. if ($('.clicked').length != 0) {
  13. // down arrow
  14. if (e.keyCode === 40) {
  15. if ($('.clicked').next().length != 0) {
  16. $('.clicked').next().click();
  17. window.scrollBy(0, 71);
  18. return false;
  19. }
  20. // up arrow
  21. } else if (e.keyCode === 38) {
  22. if ($('.clicked').prev().length != 0) {
  23. $('.clicked').prev().click();
  24. window.scrollBy(0, -71);
  25. return false;
  26. }
  27. }
  28. }
  29. });
  30. // Contact Model
  31. var Contact = Backbone.Model.extend({
  32. defaults: {}
  33. });
  34. // Contact Collection
  35. var AddressBook = Backbone.Collection.extend({
  36. model: Contact
  37. });
  38. // View used for details, overview
  39. var SideView = Backbone.View.extend({
  40. el: $('aside div.detail'),
  41. events: {},
  42. initialize: function() {
  43. _.bindAll(this, 'render'); // fixes loss of context for 'this' within methods
  44. that = this;
  45. this.render();
  46. },
  47. render: function() {
  48. // this.el.html("Hello!");
  49. }
  50. });
  51. // List View for Contacts
  52. var ListView = Backbone.View.extend({
  53. el: $('body'), // attaches `this.el` to an existing element.
  54. _s: {
  55. searchIndicator: "Search..."
  56. },
  57. sortType: "firstname",
  58. events: {
  59. 'keyup input#search' : 'searchChangeHandler',
  60. 'change #sort' : 'sortChangeHandler',
  61. 'hover #contacts li' : 'hoverContactHandler',
  62. 'click #contacts li' : 'clickContactHandler',
  63. 'focus #search' : 'focusSearchHandler',
  64. 'blur #search' : 'blurSearchHandler'
  65. },
  66. searchChangeHandler: function() {
  67. var q = $("input#search").val();
  68. if (q.length > 0 && q != this._s.searchIndicator) {
  69. this.render({q: q})
  70. } else {
  71. this.render();
  72. }
  73. },
  74. sortChangeHandler: function() {
  75. var sortVal = $("#sort").val();
  76. log("change sort to " + sortVal);
  77. this.sortType = sortVal;
  78. this.searchChangeHandler();
  79. },
  80. hoverContactHandler: function() {
  81. },
  82. clickContactHandler: function(ev) {
  83. var cid = $(ev.currentTarget).data('cid');
  84. if (cid === displayedContact) {
  85. return this.hideDetailsPane();
  86. }
  87. $('.clicked').removeClass('clicked');
  88. $(ev.currentTarget).addClass('clicked');
  89. this.drawDetailsPane(cid);
  90. },
  91. drawDetailsPane: function(cid) {
  92. displayedContact = cid;
  93. var self = this;
  94. var model = this.collection.get(cid);
  95. if(!(model.get('detailedData'))) {
  96. $.getJSON('/Me/contacts/' + cid, function(contact) {
  97. model.set({detailedData : contact});
  98. self.updateDetails(contact);
  99. });
  100. } else {
  101. self.updateDetails(model.get('detailedData'));
  102. }
  103. },
  104. hideDetailsPane: function() {
  105. displayedContact = '';
  106. $('aside').css('z-index', -1);
  107. $('#main').stop().animate({
  108. marginRight: '0px'}, 750, function() {
  109. $('.detail').hide();
  110. })
  111. return $('.clicked').removeClass('clicked');
  112. },
  113. focusSearchHandler: function() {
  114. var searchEl = $("#search");
  115. if (searchEl.val() == this._s.searchIndicator) {
  116. searchEl.val("");
  117. searchEl.removeClass("inactive");
  118. }
  119. },
  120. blurSearchHandler: function() {
  121. var searchEl = $("#search");
  122. if (searchEl.val() == "") {
  123. searchEl.val(this._s.searchIndicator);
  124. searchEl.addClass("inactive");
  125. }
  126. },
  127. addContact: function(contact) {
  128. var newContact = new Contact();
  129. if (contact.name) {
  130. var names = contact.name.split(' ');
  131. newContact.set({
  132. firstname: names[0],
  133. lastname: names[names.length - 1],
  134. name: contact.name
  135. })
  136. }
  137. newContact.set({
  138. name: contact.name,
  139. id: contact._id,
  140. });
  141. // copy email
  142. if (contact.emails) newContact.set({email: contact.emails[0].value});
  143. // copy photos
  144. if (contact.photos) newContact.set({photos: contact.photos});
  145. // copy accounts (twitter, github, facebook, foursquare)
  146. if(contact.accounts) {
  147. if(contact.accounts.twitter)
  148. newContact.set({twitterHandle: contact.accounts.twitter[0]});
  149. if(contact.accounts.github)
  150. newContact.set({github: contact.accounts.github[0]});
  151. if(contact.accounts.facebook)
  152. newContact.set({facebook: contact.accounts.facebook[0].data.link});
  153. if(contact.accounts.googleContacts) {
  154. // nothing for now, no unique data
  155. }
  156. if(contact.accounts.foursquare) {
  157. newContact.set({foursquare: contact.accounts.foursquare[0]});
  158. }
  159. if(contact.accounts.flickr) {
  160. newContact.set({flickr: contact.accounts.flickr[0]});
  161. }
  162. }
  163. this.collection.add(newContact); // add item to collection; view is updated via event 'add'
  164. },
  165. initialize: function(){
  166. _.bindAll(this, 'sortChangeHandler', 'focusSearchHandler', 'blurSearchHandler', 'searchChangeHandler', 'load', 'render', 'addContact'); // fixes loss of context for 'this' within methods
  167. that = this;
  168. this.collection = new AddressBook();
  169. // TODO: clean up so the search is a proper view.
  170. that.blurSearchHandler();
  171. this.load(function() {
  172. $("#searchBox").slideDown();
  173. });
  174. },
  175. /**
  176. * Load the contacts data (get contacts)
  177. * @param callback
  178. */
  179. load: function load(callback) {
  180. $('#loader').show();
  181. var that = this;
  182. var baseURL = '/query/getContact';
  183. var fields = "['_id','addresses','emails','name','phoneNumbers','photos','accounts.facebook.data.link'," +
  184. "'accounts.foursquare.data.id','accounts.github.data.login','accounts.twitter.data.screen_name']";
  185. var offset = 0;
  186. (function getContactsCB() {
  187. $.getJSON(baseURL, {offset:offset, limit: 250, fields: fields}, function(contacts) {
  188. if (contacts.length === 0) {
  189. $('#loader').hide();
  190. return callback();
  191. }
  192. for(var i in contacts) {
  193. // only add contacts if they have a name or email. might change this.
  194. if (typeof(contacts.account) != "undefined" && typeof(contacts.account.facebook) != "undefined") log(contacts[i]);
  195. if (contacts[i].emails || contacts[i].name) {
  196. that.addContact(contacts[i]);
  197. }
  198. }
  199. that.render();
  200. offset += 250;
  201. getContactsCB();
  202. });
  203. })();
  204. },
  205. /**
  206. * Update the details panel with a given contact
  207. * @param the object containing all of the information about the contact
  208. */
  209. updateDetails: function(contact) {
  210. $('.name').text(contact.name);
  211. $('.photo').attr('src', this.getPhoto(contact, true));
  212. // twitter
  213. if (contact.accounts.twitter && contact.accounts.twitter[0].data) {
  214. var twitter = contact.accounts.twitter[0].data;
  215. $('.twitterhandle').attr('href', 'http://www.twitter.com/' + twitter.screen_name);
  216. $('.twitterhandle').text('@' + twitter.screen_name);
  217. $('.lasttweet').text(twitter.status.text);
  218. $('.twitterSection').show();
  219. } else {
  220. $('.twitterSection').hide();
  221. }
  222. // email
  223. $('.emailaddress').text(this.getEmail(contact));
  224. $('.emailaddress').attr('href', 'mailto:' + this.getEmail(contact));
  225. // phone
  226. var phone = this.getPhone(contact);
  227. $('.phonenumber').text(phone);
  228. if (phone) {
  229. $('.phoneSection').show();
  230. } else {
  231. $('.phoneSection').hide();
  232. }
  233. // 4sq
  234. if (contact.accounts.foursquare && contact.accounts.foursquare[0].data) {
  235. var fsq = contact.accounts.foursquare[0].data;
  236. $('.4sqlastseen').attr('href', 'http://www.foursquare.com/user/' + fsq.id + '/checkin/' + fsq.checkins.items[0].id);
  237. $('.4sqlastseen').text(fsq.checkins.items[0].venue.name);
  238. $('.foursquarehandle').attr('href', 'http://www.foursquare.com/user/' + fsq.id);
  239. $('.foursquareSection').show();
  240. } else {
  241. $('.foursquareSection').hide();
  242. }
  243. // gh
  244. if (contact.accounts.github && contact.accounts.github[0].data) {
  245. var gh = contact.accounts.github[0].data;
  246. $('.githubHandle').text(gh.login);
  247. $('.githubHandle').attr('href', 'http://www.github.com/' + gh.login);
  248. $('.githubSection').show();
  249. } else {
  250. $('.githubSection').hide();
  251. }
  252. // fb
  253. if (contact.accounts.facebook && contact.accounts.facebook[0].data) {
  254. var fb = contact.accounts.facebook[0].data;
  255. $('.facebookHandle').attr('href', fb.link);
  256. $('.facebookHandle').text(fb.name);
  257. $('.facebookSection').show();
  258. } else {
  259. $('.facebookSection').hide();
  260. }
  261. // location
  262. var loc = this.getLocation(contact);
  263. $('.address').text(loc);
  264. if (loc) {
  265. $('.addressSection').show();
  266. $('.address').attr('href', 'http://maps.google.com/maps?q=' + encodeURI(loc));
  267. } else {
  268. $('.addressSection').hide();
  269. }
  270. // animation
  271. if (!$('.detail').is(':visible')) {
  272. $('.detail').show();
  273. $('#main').stop().animate({
  274. marginRight: '374px'}, 750, function() {
  275. $('aside').css('z-index', 1);
  276. });
  277. }
  278. },
  279. /**
  280. * Add the person's twitter username to a div
  281. * @param div - $(HTMLElement)
  282. * @param contact - contact obj
  283. */
  284. getTwitter: function(contact) {
  285. var twitterUsername;
  286. if(contact.accounts.twitter && contact.accounts.twitter[0].data
  287. && contact.accounts.twitter[0].data.screen_name) {
  288. twitterUsername = contact.accounts.twitter[0].data.screen_name;
  289. }
  290. if(twitterUsername) {
  291. return twitterUsername;
  292. }
  293. return false;
  294. },
  295. /**
  296. * Add the person's Facebook details to a div
  297. * @param contact {Object} Contact Object
  298. */
  299. getFacebook: function(contact) {
  300. var facebookUsername;
  301. return false;
  302. },
  303. /**
  304. * get the location of a contact
  305. * @param contact - contact obj
  306. */
  307. getLocation: function(contact) {
  308. if(contact.addresses && contact.addresses.length) {
  309. for(var i in contact.addresses) {
  310. if(contact.addresses[i].type === 'location') {
  311. return contact.addresses[i].value;
  312. }
  313. }
  314. }
  315. return '';
  316. },
  317. /**
  318. * get the phone number of a contact
  319. * @param contact - contact obj
  320. */
  321. getPhone: function(contact) {
  322. if (contact.phoneNumbers && contact.phoneNumbers.length) {
  323. return contact.phoneNumbers[0].value;
  324. }
  325. return '';
  326. },
  327. /**
  328. * Get the person's email address
  329. * @param contact - contact obj
  330. */
  331. getEmail: function(contact) {
  332. if (contact.emails && contact.emails.length) {
  333. return contact.emails[0].value;
  334. }
  335. return '';
  336. },
  337. /**
  338. * Get the person's GitHub details
  339. * @param contact {Object} contact obj
  340. */
  341. getGithub: function(contact) {
  342. },
  343. /**
  344. * Convience function to get the url from an object
  345. * @param contact {Object} Contact object (json)
  346. * @param fullsize {Boolean} Optional, default false. Use a large photo instead of small
  347. */
  348. getPhoto: function(contact, fullsize) {
  349. var url = 'img/silhouette.png';
  350. if(contact.photos && contact.photos.length) {
  351. url = contact.photos[0];
  352. //twitter
  353. if(fullsize && url.match(/_normal\.(jpg||png)/) && !url.match(/.*default_profile_([0-9])_normal\.png/)) {
  354. url = url.replace(/_normal\.(jpg||png)/, '.$1');
  355. }
  356. else if(url.indexOf('https://graph.facebook.com/') === 0) {
  357. if(fullsize)
  358. url += "?return_ssl_resources=true&type=large";
  359. else
  360. url += "?return_ssl_resources=true&type=square";
  361. }
  362. }
  363. return url;
  364. },
  365. render: function(config){
  366. // default to empty
  367. config = config || {};
  368. var filteredCollection,
  369. contactsEl, contactTemplate, contactsHTML,
  370. searchFilter, addContactToHTML;
  371. var that = this;
  372. filteredCollection = this.collection;
  373. contactsEl = $("#contacts");
  374. countEl = $("#count");
  375. contactsEl.html('');
  376. contactsHTML = "";
  377. /**
  378. * Truthy function for filtering down our collection based on config
  379. * @param c {Object} Contact object
  380. * @returns {Boolean} Pass or fail
  381. */
  382. searchFilter = function(c) {
  383. // test to see if we have a query, otherwise everything passes
  384. if (typeof(config.q) == "undefined") return true;
  385. else config.q = (config.q+'').toLowerCase();
  386. // make everything lowercase so search isn't case sensititive
  387. var name = c.get('name');
  388. if (name) name = name.toLowerCase();
  389. var email = c.get('email');
  390. if (email) email = email.toLowerCase();
  391. //search by name
  392. if (typeof(name) != "undefined" && name.indexOf(config.q) != -1) return true;
  393. // search by email
  394. if(typeof(email) != "undefined" && email.indexOf(config.q) != -1) return true;
  395. // search by twitter handle
  396. if(typeof(twitterHandle) != "undefined" && twitterHandle.indexOf(config.q) != -1) return true;
  397. // search by facebook handle
  398. if(typeof(facebookHandle) != "undefined" && facebookHandle.indexOf(config.q) != -1) return true;
  399. return false;
  400. };
  401. // I could put this in a script tag on the page,
  402. // but i kind of like being able to comment lines
  403. contactTemplate = '<li class="contact" data-cid="<%= id %>">';
  404. contactTemplate += '<div class="contactSummary"><img src="<% if (typeof(smPhoto) != "undefined" ) { %><%= smPhoto %><% } else { %>/static/img/lock.png<% } %>"/>';
  405. contactTemplate += '<strong><% if (typeof(name) != "undefined") { %><%= name %><% } %></strong>';
  406. contactTemplate += '</div>';
  407. contactTemplate += '<div class="contactActions">';
  408. contactTemplate += '<% if (typeof(email) != "undefined") { %><a href="mailto:<%= email %>" target="_b" class="social_link email">Email</a><% } %> ';
  409. contactTemplate += '<% if (typeof(facebook) != "undefined") { %><a href="<%= facebook %>" class="social_link facebook" target="_b">Facebook Profile</a><% } %>';
  410. contactTemplate += '<% if (typeof(twitterHandle) != "undefined" && typeof(twitterHandle.data.screen_name) != "undefined") { %><a href="http://twitter.com/<%= twitterHandle.data.screen_name %>" class="social_link twitter" target="_b">Twitter Profile</a><% } %>';
  411. contactTemplate += '<% if (typeof(flickr) != "undefined" && typeof(flickr.data.username) != "undefined") { %><a href="http://flickr.com/people/<%= flickr.data.nsid %>" class="social_link flickr" target="_b">Flickr Profile</a><% } %>';
  412. contactTemplate += '<% if (typeof(github) != "undefined" && typeof(github.data.login) != "undefined") { %><a href="http://github.com/<%= github.data.login %>" class="social_link github" target="_b">GitHub Profile</a><% } %>';
  413. contactTemplate += '</div>';
  414. contactTemplate += '<div class="clear"></div></li>';
  415. addContactToHTML = function(c) {
  416. // create a simple json obj to use for creating the template (if necessary)
  417. if (typeof(c.get('html')) == "undefined") {
  418. var tmpJSON = c.toJSON();
  419. if (typeof(tmpJSON.name) == "undefined") {
  420. tmpJSON.name = tmpJSON.email;
  421. }
  422. tmpJSON.smPhoto = that.getPhoto(tmpJSON, false);
  423. tmpJSON.photo = that.getPhoto(tmpJSON, true);
  424. tmpJSON.json = JSON.stringify(tmpJSON, null, 2);
  425. // cache compiled template to the model
  426. var compiledTemplate = _.template(contactTemplate, tmpJSON);
  427. c.set({'html': compiledTemplate});
  428. contactsEl.append(compiledTemplate);
  429. // contactsHTML += compiledTemplate;
  430. } else {
  431. // just get the rendered html from our model
  432. contactsEl.append(c.get('html'));
  433. }
  434. };
  435. var tmp = filteredCollection.filter(searchFilter);
  436. var sortFn = function(c) {
  437. if (c.get(that.sortType)) {
  438. return c.get(that.sortType);
  439. }
  440. return "zzz"; // force to the end of the sort
  441. };
  442. tmp = _.sortBy(tmp, sortFn);
  443. _.each(tmp, addContactToHTML);
  444. countEl.html(tmp.length);
  445. if ($('.contact').length === 1) {
  446. this.drawDetailsPane($('.contact').data('cid'));
  447. $('.contact').addClass('clicked');
  448. } else if ($('.detail').is(':visible')) {
  449. var selectedContact = $(".contact[data-cid='" + displayedContact + "']");
  450. if (selectedContact.length > 0) {
  451. selectedContact.addClass('clicked');
  452. } else {
  453. this.hideDetailsPane();
  454. }
  455. }
  456. }
  457. });
  458. var listView = new ListView();
  459. var sideView = new SideView();
  460. });