2 * The javascript for friendicas hovercard. Bootstraps popover is needed.
4 * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im>
5 * qvitter code (https://github.com/hannesmannerheim/qvitter)
7 * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
10 $(document).ready(function(){
11 // Elements with the class "userinfo" will get a hover-card.
12 // Note that this elements does need a href attribute which links to
13 // a valid profile url
14 $("body").on("mouseover", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) {
15 var timeNow = new Date().getTime();
16 removeAllhoverCards(e,timeNow);
17 var hoverCardData = false;
19 var targetElement = $(this);
22 if(targetElement.is('[href]')) {
23 hrefAttr = targetElement.attr('href');
28 // no hover card if the element has the no-hover-card class
29 if(targetElement.hasClass('no-hover-card')) {
33 // no hovercard for anchor links
34 if(hrefAttr.substring(0,1) == '#') {
38 targetElement.attr('data-awaiting-hover-card',timeNow);
40 // Take link href attribute as link to the profile
41 var profileurl = hrefAttr;
42 // the url to get the contact and template data
43 var url = baseurl + "/hovercard";
45 // store the title in an other data attribute beause bootstrap
46 // popover destroys the title.attribute. We can restore it later
47 var title = targetElement.attr("title");
48 targetElement.attr({"data-orig-title": title, title: ""});
50 // if the device is a mobile open the hover card by click and not by hover
51 if(typeof is_mobile != "undefined") {
52 targetElement[0].removeAttribute("href");
53 var hctrigger = 'click';
55 var hctrigger = 'manual';
58 // Timeoute until the hover-card does appear
59 setTimeout(function(){
60 if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
61 if($('.hovercard').length == 0) { // no card if there already is one open
62 // get an additional data atribute if the card is active
63 targetElement.attr('data-hover-card-active',timeNow);
64 // get the whole html content of the hover card and
65 // push it to the bootstrap popover
66 getHoverCardContent(profileurl, url, function(data){
68 targetElement.popover({
70 placement: function () {
71 // Calculate the placement of the the hovercard (if top or bottom)
72 // The placement depence on the distance between window top and the element
73 // which triggers the hover-card
74 var get_position = $(targetElement).offset().top - $(window).scrollTop();
75 if (get_position < 270 ){
81 template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
90 }).on("mouseleave", ".userinfo, .wall-item-responses a, .wall-item-bottom .mention a", function(e) { // action when mouse leaves the hover-card
91 var timeNow = new Date().getTime();
92 // copy the original title to the title atribute
93 var title = $(this).attr("data-orig-title");
94 $(this).attr({"data-orig-title": "", title: title});
95 removeAllhoverCards(e,timeNow);
100 // hover cards should be removed very easily, e.g. when any of these events happen
101 $('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
102 // remove hover card only for desktiop user, since on mobile we openen the hovercards
103 // by click event insteadof hover
104 if(typeof is_mobile == "undefined") {
105 var timeNow = new Date().getTime();
106 removeAllhoverCards(e,timeNow);
110 // if we're hovering a hover card, give it a class, so we don't remove it
111 $('body').on('mouseover','.hovercard', function(e) {
112 $(this).addClass('dont-remove-card');
114 $('body').on('mouseleave','.hovercard', function(e) {
115 $(this).removeClass('dont-remove-card');
116 $(this).popover("hide");
119 }); // End of $(document).ready
121 // removes all hover cards
122 function removeAllhoverCards(event,priorTo) {
123 // don't remove hovercards until after 100ms, so user have time to move the cursor to it (which gives it the dont-remove-card class)
124 setTimeout(function(){
125 $.each($('.hovercard'),function(){
126 var title = $(this).attr("data-orig-title");
127 // don't remove card if it was created after removeAllhoverCards() was called
128 if($(this).data('card-created') < priorTo) {
129 // don't remove it if we're hovering it right now!
130 if(!$(this).hasClass('dont-remove-card')) {
131 $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
132 $(this).popover("hide");
139 // Ajax request to get json contact data
140 function getContactData(purl, url, actionOnSuccess) {
147 // Normalize and clean the profile so we can use a standardized url
148 // as key for the cache
149 var nurl = cleanContactUrl(purl).normalizeLink();
151 // If the contact is allready in the cache use the cached result instead
152 // of doing a new ajax request
153 if(nurl in getContactData.cache) {
154 setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1);
162 success: function(data, textStatus, request){
163 // Check if the nurl (normalized profile url) is present and store it to the cache
164 // The nurl will be the identifier in the object
165 if(data.nurl.length > 0) {
166 // Test if the contact is allready connected with the user (if url containing
167 // the expression ("redir/") We will store different cache keys
168 if((data.url.search("redir/")) >= 0 ) {
173 getContactData.cache[key] = data;
175 actionOnSuccess(data, url, request);
177 error: function(data) {
178 actionOnSuccess(false, data, url);
182 getContactData.cache = {};
184 // Get hover-card template data and the contact-data and transform it with
185 // the help of jSmart. At the end we have full html content of the hovercard
186 function getHoverCardContent(purl, url, callback) {
187 // fetch the raw content of the template
188 getHoverCardTemplate(url, function(stpl) {
189 var template = unescape(stpl);
191 // get the contact data
192 getContactData (purl, url, function(data) {
193 if(typeof template != 'undefined') {
194 // get the hover-card variables
195 var variables = getHoverCardVariables(data);
198 // use friendicas template delimiters instead of
200 jSmart.prototype.left_delimiter = '{{';
201 jSmart.prototype.right_delimiter = '}}';
203 // create a new jSmart instant with the raw content
205 var tpl = new jSmart (template);
206 // insert the variables content into the template content
207 var HoverCardContent = tpl.fetch(variables);
209 callback(HoverCardContent);
214 // This is interisting. this pice of code ajax request are done asynchron.
215 // To make it work getHOverCardTemplate() and getHOverCardData have to return it's
216 // data (no succes handler for each of this). I leave it here, because it could be useful.
217 // https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/
219 // getHoverCardTemplate(url),
220 // getContactData (term, url )
222 // ).done(function(template, profile){
223 // if(typeof template != 'undefined') {
224 // var variables = getHoverCardVariables(profile);
226 // jSmart.prototype.left_delimiter = '{{';
227 // jSmart.prototype.right_delimiter = '}}';
228 // var tpl = new jSmart (template);
229 // var html = tpl.fetch(variables);
237 // Ajax request to get the raw template content
238 function getHoverCardTemplate (url, callback) {
244 // Look if we have the template already in the cace, so we don't have
246 if('hovercard' in getHoverCardTemplate.cache) {
247 setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1);
254 success: function(data, textStatus) {
255 // write the data in the cache
256 getHoverCardTemplate.cache['hovercard'] = data;
259 }).fail(function () {callback([]); });
261 getHoverCardTemplate.cache = {};
263 // The Variables used for the template
264 function getHoverCardVariables(object) {
272 location: object.location,
273 gender: object.gender,
275 network: object.network,
278 account_type: object.account_type,
279 actions: object.actions
282 var variables = { profile: profile};
287 // This is the html template for the hover-card
288 // Since we grab the original hovercard.tpl we don't
290 function hovercard_template() {
292 <div class="basic-content" >\
293 <div class="hover-card-details">\
294 <div class="hover-card-header left-align">\
295 <div class="hover-card-pic left-align">\
296 <span class="image-wrapper medium">\
297 <a href="{{$profile.url}}" title="{{$profile.name}}"><img href="" class="left-align thumbnail" src="{{$profile.thumb}}"></a>\
300 <div class="hover-card-content">\
301 <div class="profile-entry-name">\
302 <h4 class="left-align1"><a href="{{$profile.url}}">{{$profile.name}}</a></h4>{{if $profile.account_type}}<span>{{$profile.account_type}}</span>{{/if}}\
304 <div class="profile-details">\
305 <span class="profile-addr">{{$profile.addr}}</span>\
306 {{if $profile.network}}<span class="profile-network"> ({{$profile.network}})</span>{{/if}}\
308 {{*{{if $profile.about}}<div class="profile-details profile-about">{{$profile.about}}</div>{{/if}}*}}\
311 <div class="hover-card-actions right-aligned">\
312 {{* here are the differnt actions like privat message, poke, delete and so on *}}\
313 {{* @todo we have two different photo menus one for contacts and one for items at the network stream. We currently use the contact photo menu, so the items options are missing We need to move them *}}\
314 <div class="hover-card-actions-social">\
315 {{if $profile.actions.pm}}<a class="btn btn-labeled btn-primary btn-sm" onclick="addToModal("{{$profile.actions.pm.1}}")" title="{{$profile.actions.pm.0}}"><i class="fa fa-envelope" aria-hidden="true"></i></a>{{/if}}\
316 {{if $profile.actions.poke}}<a class="btn btn-labeled btn-primary btn-sm" onclick="addToModal("{{$profile.actions.poke.1}}")" title="{{$profile.actions.poke.0}}"><i class="fa fa-heartbeat" aria-hidden="true"></i></a>{{/if}}\
318 <div class="hover-card-actions-connection">\
319 {{if $profile.actions.edit}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.edit.1}}" title="{{$profile.actions.edit.0}}"><i class="fa fa-pencil" aria-hidden="true"></i></a>{{/if}}\
320 {{if $profile.actions.drop}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.drop.1}}" title="{{$profile.actions.drop.0}}"><i class="fa fa-user-times" aria-hidden="true"></i></a>{{/if}}\
321 {{if $profile.actions.follow}}<a class="btn btn-labeled btn-primary btn-sm" href="{{$profile.actions.follow.1}}" title="{{$profile.actions.follow.0}}"><i class="fa fa-user-plus" aria-hidden="true"></i></a>{{/if}}\
326 <div class="clearfix"></div>\
330 {{if $profile.tags}}<div class="hover-card-footer">{{$profile.tags}}</div>{{/if}}';