]> git.mxchange.org Git - friendica.git/blob - js/hovercard.js
README: Add link to commit history
[friendica.git] / js / hovercard.js
1 /* 
2  * The javascript for friendicas hovercard. Bootstraps popover is needed.
3  * 
4  * Much parts of the code are from Hannes Mannerheims <h@nnesmannerhe.im> 
5  * qvitter code (https://github.com/hannesmannerheim/qvitter)
6  * 
7  * It is licensed under the GNU Affero General Public License <http://www.gnu.org/licenses/>
8  * 
9  */
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", function(e) {
15                         var timeNow = new Date().getTime();
16                         removeAllhoverCards(e,timeNow);
17                         var hoverCardData = false;
18                         var hrefAttr = false;
19                         var targetElement = $(this);
20
21                         // get href-attribute
22                         if(targetElement.is('[href]')) {
23                                 hrefAttr = targetElement.attr('href');
24                         } else {
25                                 return true;
26                         }
27
28                         // no hover card if the element has the no-hover-card class
29                         if(targetElement.hasClass('no-hover-card')) {
30                                 return true;
31                         }
32
33                         // no hovercard for anchor links
34                         if(hrefAttr.substring(0,1) == '#') {
35                                 return true;
36                         }
37
38                         targetElement.attr('data-awaiting-hover-card',timeNow);
39
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 + "/frio_hovercard";
44
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: ""});
49
50                         // Timeoute until the hover-card does appear
51                         setTimeout(function(){
52                                 if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
53                                         if($('.hovercard').length == 0) {       // no card if there already is one open
54                                                 // get an additional data atribute if the card is active
55                                                 targetElement.attr('data-hover-card-active',timeNow);
56                                                 // get the whole html content of the hover card and
57                                                 // push it to the bootstrap popover
58                                                 getHoverCardContent(profileurl, url, function(data){
59                                                         if(data) {
60                                                                 targetElement.popover({
61                                                                         html: true,
62                                                                         placement: function () {
63                                                                                 // Calculate the placement of the the hovercard (if top or bottom)
64                                                                                 // The placement depence on the distance between window top and the element
65                                                                                 // which triggers the hover-card
66                                                                                 var get_position = $(targetElement).offset().top - $(window).scrollTop();
67                                                                                 if (get_position < 270 ){
68                                                                                         return "bottom";
69                                                                                 }
70                                                                                 return "top";
71                                                                         },
72                                                                         trigger: 'manual',
73                                                                         template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
74                                                                         content: data
75                                                                 }).popover('show');
76                                                         }
77                                                 });
78                                         }
79                                 }
80                         }, 500);
81         }).on("mouseleave", ".userinfo", function(e) { // action when mouse leaves the hover-card
82                 var timeNow = new Date().getTime();
83                 // copy the original title to the title atribute
84                 var title = $(this).attr("data-orig-title");
85                 $(this).attr({"data-orig-title": "", title: title});
86                 removeAllhoverCards(e,timeNow);
87         });
88
89
90
91         // hover cards should be removed very easily, e.g. when any of these events happen
92         $('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
93                 var timeNow = new Date().getTime();
94                 removeAllhoverCards(e,timeNow);
95         });
96
97         // if we're hovering a hover card, give it a class, so we don't remove it
98         $('body').on('mouseover','.hovercard', function(e) {
99                 $(this).addClass('dont-remove-card');
100         });
101         $('body').on('mouseleave','.hovercard', function(e) {
102                 $(this).removeClass('dont-remove-card');
103                 $(this).popover("hide");
104         });
105
106 }); // End of $(document).ready
107
108 // removes all hover cards
109 function removeAllhoverCards(event,priorTo) {
110         // 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)
111         setTimeout(function(){
112                 $.each($('.hovercard'),function(){
113                         var title = $(this).attr("data-orig-title");
114                         // don't remove card if it was created after removeAllhoverCards() was called
115                         if($(this).data('card-created') < priorTo) {
116                                 // don't remove it if we're hovering it right now!
117                                 if(!$(this).hasClass('dont-remove-card')) {
118                                         $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
119                                         $(this).popover("hide");
120                                 }
121                         }
122                 });
123         },100);
124 }
125
126 // Ajax request to get json contact data
127 function getContactData(purl, url, actionOnSuccess) {
128         var postdata = {
129                 mode            : 'none',
130                 profileurl      : purl,
131                 datatype        : 'json',
132         };
133
134         // Normalize and clean the profile so we can use a standardized url
135         // as key for the cache
136         var nurl = cleanContactUrl(purl).normalizeLink();
137
138         // If the contact is allready in the cache use the cached result instead
139         // of doing a new ajax request
140         if(nurl in getContactData.cache) {
141                 setTimeout(function() { actionOnSuccess(getContactData.cache[nurl]); } , 1);
142                 return;
143         }
144
145         $.ajax({
146                 url: url,
147                 data: postdata,
148                 dataType: "json",
149                 success: function(data, textStatus, request){
150                         // Check if the nurl (normalized profile url) is present and store it to the cache
151                         // The nurl will be the identifier in the object
152                         if(data.nurl.length > 0) {
153                                 // Test if the contact is allready connected with the user (if url containing
154                                 // the expression ("redir/") We will store different cache keys 
155                                 if((data.url.search("redir/")) >= 0 ) {
156                                         var key = data.url;
157                                 } else {
158                                         var key = data.nurl;
159                                 }
160                                 getContactData.cache[key] = data;
161                         }
162                         actionOnSuccess(data, url, request);
163                 },
164                 error: function(data) {
165                         actionOnSuccess(false, data, url);
166                 }
167         });
168 }
169 getContactData.cache = {};
170
171 // Get hover-card template data and the contact-data and transform it with
172 // the help of jSmart. At the end we have full html content of the hovercard
173 function getHoverCardContent(purl, url, callback) {
174         // fetch the raw content of the template
175         getHoverCardTemplate(url, function(stpl) {
176                 var template = unescape(stpl);
177
178                 // get the contact data
179                 getContactData (purl, url, function(data) {
180                         if(typeof template != 'undefined') {
181                                 // get the hover-card variables
182                                 var variables = getHoverCardVariables(data);
183                                 var tpl;
184
185                                 // use friendicas template delimiters instead of
186                                 // the original one
187                                 jSmart.prototype.left_delimiter = '{{';
188                                 jSmart.prototype.right_delimiter = '}}';
189
190                                 // create a new jSmart instant with the raw content
191                                 // of the template
192                                 var tpl = new jSmart (template);
193                                 // insert the variables content into the template content
194                                 var HoverCardContent = tpl.fetch(variables);
195
196                                 callback(HoverCardContent);
197                         }
198                 });
199         });
200
201 // This is interisting. this pice of code ajax request are done asynchron.
202 // To make it work getHOverCardTemplate() and getHOverCardData have to return it's 
203 // data (no succes handler for each of this). I leave it here, because it could be useful.
204 // https://lostechies.com/joshuaflanagan/2011/10/20/coordinating-multiple-ajax-requests-with-jquery-when/
205 //      $.when(
206 //              getHoverCardTemplate(url),
207 //              getContactData (term, url )
208 //
209 //      ).done(function(template, profile){
210 //              if(typeof template != 'undefined') {
211 //                      var variables = getHoverCardVariables(profile);
212 //
213 //                      jSmart.prototype.left_delimiter = '{{';
214 //                      jSmart.prototype.right_delimiter = '}}';
215 //                      var tpl = new jSmart (template);
216 //                      var html = tpl.fetch(variables);
217 //
218 //                      return html;
219 //              }
220 //      });
221 }
222
223
224 // Ajax request to get the raw template content
225 function getHoverCardTemplate (url, callback) {
226         var postdata = {
227                 mode: 'none',
228                 datatype: 'tpl'
229         };
230
231         // Look if we have the template already in the cace, so we don't have
232         // request it again
233         if('hovercard' in getHoverCardTemplate.cache) {
234                 setTimeout(function() { callback(getHoverCardTemplate.cache['hovercard']); } , 1);
235                 return;
236         }
237
238         $.ajax({
239                 url: url,
240                 data: postdata,
241                 success: function(data, textStatus) {
242                         // write the data in the cache
243                         getHoverCardTemplate.cache['hovercard'] = data;
244                         callback(data);
245                 }
246         }).fail(function () {callback([]); });
247 }
248 getHoverCardTemplate.cache = {};
249
250 // The Variables used for the template
251 function getHoverCardVariables(object) {
252         var profile = {
253                         name:           object.name,
254                         nick:           object.nick,
255                         addr:           object.addr,
256                         thumb:          object.thumb,
257                         url:            object.url,
258                         nurl:           object.nurl,
259                         location:       object.location,
260                         gender:         object.gender,
261                         about:          object.about,
262                         network:        object.network,
263                         tags:           object.tags,
264                         bd:             object.bd,
265                         account_type:   object.account_type,
266                         actions:        object.actions
267         };
268
269         var variables = { profile:  profile};
270
271         return variables;
272 }
273
274 // This is the html template for the hover-card
275 // Since we grab the original hovercard.tpl we don't
276 // need it anymore
277 function hovercard_template() {
278         var tempate = '\
279         <div class="basic-content" >\
280                 <div class="hover-card-details">\
281                         <div class="hover-card-header left-align">\
282                                 <div class="hover-card-pic left-align">\
283                                         <span class="image-wrapper medium">\
284                                                 <a href="{{$profile.url}}" title="{{$profile.name}}"><img href="" class="left-align thumbnail" src="{{$profile.thumb}}"></a>\
285                                         </span>\
286                                 </div>\
287                                 <div class="hover-card-content">\
288                                         <div class="profile-entry-name">\
289                                                 <h4 class="left-align1"><a href="{{$profile.url}}">{{$profile.name}}</a></h4>{{if $profile.account_type}}<span>{{$profile.account_type}}</span>{{/if}}\
290                                         </div>\
291                                         <div class="profile-details">\
292                                                 <span class="profile-addr">{{$profile.addr}}</span>\
293                                                 {{if $profile.network}}<span class="profile-network"> ({{$profile.network}})</span>{{/if}}\
294                                         </div>\
295                                         {{*{{if $profile.about}}<div class="profile-details profile-about">{{$profile.about}}</div>{{/if}}*}}\
296 \
297                                 </div>\
298                                 <div class="hover-card-actions  right-aligned">\
299                                         {{* here are the differnt actions like privat message, poke, delete and so on *}}\
300                                         {{* @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 *}}\
301                                         <div class="hover-card-actions-social">\
302                                                 {{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}}\
303                                                 {{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}}\
304                                         </div>\
305                                         <div class="hover-card-actions-connection">\
306                                                 {{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}}\
307                                                 {{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}}\
308                                                 {{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}}\
309                                         </div>\
310                                 </div>\
311                         </div>\
312 \
313                         <div class="clearfix"></div>\
314 \
315                 </div>\
316         </div>\
317         {{if $profile.tags}}<div class="hover-card-footer">{{$profile.tags}}</div>{{/if}}';
318 }