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};