1/** 2 * @package Tagregator 3 */ 4 5 6/** 7 * Wrapper function to safely use $ 8 */ 9function tggrWrapper( $ ) { 10 var tggr = { 11 12 /** 13 * Initialization 14 */ 15 init : function() { 16 if ( typeof tggrData === 'undefined' ) { 17 return; 18 } 19 20 tggr.prefix = 'tggr_'; 21 tggr.cssPrefix = 'tggr-'; 22 tggr.loadingNewPosts = '#' + tggr.cssPrefix + 'loading-new-posts'; 23 tggr.mediaItemContainer = '#' + tggr.cssPrefix + 'media-item-container'; 24 tggr.mediaItem = '.' + tggr.cssPrefix + 'media-item'; 25 tggr.existingItemIDs = tggr.getExistingItemIDs(); 26 tggr.retrievalInterval = false; 27 tggr.retrievingNewItems = false; 28 tggr.loadingNewPostsVisible = tggr.isScrolledIntoView( tggr.loadingNewPosts ); 29 30 /* 31 * Enable Masonry for multi-column layouts 32 * 33 * The initial HTML/CSS layout that exists before masonry is enabled is designed to match the Masonry 34 * layout as closely as possible, so that when Masonry is enabled there won't be a dramatic visual 35 * shift that would disorient the user. 36 * 37 * There are still differences between the two layouts, though, so we initialize Masonry as soon as 38 * the DOM is ready, in order to hopefully switch to Masonry before the user has scrolled down far 39 * enough to notice the differences. 40 * 41 * That happens before images have fully loaded, though, which creates a problem. As the images load, 42 * they change the height of each item container, which causes the containers to overlap. So, we refresh 43 * the layout once more after all the images have loaded. 44 */ 45 if ( ! $( tggr.mediaItemContainer ).hasClass( 'one-column' ) ) { 46 $( tggr.mediaItemContainer ).masonry( { 47 itemSelector: tggr.mediaItem 48 } ); 49 50 $( tggr.mediaItemContainer ).imagesLoaded( function() { 51 $( tggr.mediaItemContainer ).masonry( 'reloadItems' ); 52 $( tggr.mediaItemContainer ).masonry( 'layout' ); 53 } ); 54 55 $( tggr.mediaItemContainer ).on( 'tggr-rendered', tggr.refreshLayout ); 56 $( window ).on( 'scroll', tggr.toggleRetrieval ); 57 } 58 59 tggr.enableRetrieval(); 60 }, 61 62 /** 63 * Determines if an element is visible in the viewport 64 * 65 * Based on http://stackoverflow.com/a/488073/450127 66 * Modified to detect if the entire element is visible (rather than just part) 67 * 68 * @param {string} element 69 * @returns {boolean} 70 */ 71 isScrolledIntoView : function( element ) { 72 var docViewTop = $( window ).scrollTop(), 73 docViewBottom = docViewTop + $( window ).height(), 74 elementTop = $( element ).offset().top, 75 elementBottom = elementTop + $( element ).height(); 76 77 return ( ( docViewTop < elementTop ) && ( docViewBottom > elementBottom ) ); 78 }, 79 80 /** 81 * Retrieve new posts and schedule automatic retrieves in the future 82 * 83 * It's possible for this to get called multiple times, so we won't set an interval if we already have one. 84 * Otherwise only one of them would get cleared and the page would continue to load new pots when we don't 85 * want it to. 86 */ 87 enableRetrieval : function() { 88 if ( ! tggr.retrievalInterval ) { 89 tggr.retrieveNewItems(); 90 tggr.retrievalInterval = setInterval( tggr.retrieveNewItems, tggrData.refreshInterval * 1000 ); // convert to milliseconds 91 } 92 }, 93 94 /** 95 * Only retrieve new posts when the user is viewing the top of Tagregator output 96 * 97 * If the user has scrolled down to the point where the masonry layout fills the screen, and we then add 98 * new items and redraw the layout, we will potentially disrupt and disorient them, and probably make them 99 * lose their place. 100 * 101 * So, when they scroll past the "Loading new posts" spinner, we stop retrieving new items. When they scroll 102 * back above it, we immediately retrieve new items and re-establish the interval to automatically load them 103 * in the future. 104 * 105 * @param {object} event 106 */ 107 toggleRetrieval : function( event ) { 108 var newState = tggr.isScrolledIntoView( tggr.loadingNewPosts ); 109 110 if ( tggr.loadingNewPostsVisible && ! newState ) { 111 clearInterval( tggr.retrievalInterval ); 112 tggr.retrievalInterval = false;
113 } else if ( ! tggr.loadingNewPostsVisible && newState ) { 114 tggr.enableRetrieval(); 115 } 116 117 tggr.loadingNewPostsVisible = newState; 118 }, 119 120 /** 121 * Builds an array of which item IDs are already present in the DOM 122 * 123 * @return {array} 124 */ 125 getExistingItemIDs : function() { 126 var itemIDs = []; 127 128 $( tggr.mediaItemContainer ).children( tggr.mediaItem ).each( function() { 129 itemIDs.push( parseInt( $( this ).attr( 'id' ).replace( tggr.cssPrefix, '' ) ) ); 130 } ); 131 132 return itemIDs; 133 }, 134 135 /** 136 * Makes an AJAX call to the server to get any new items that have been imported since the last check 137 */ 138 retrieveNewItems : function() { 139 if ( tggr.retrievingNewItems ) { 140 return; 141 } 142 143 tggr.retrievingNewItems = true; 144 $( tggr.loadingNewPosts ).removeClass( tggr.cssPrefix + 'transparent' ); 145 146 $.post( 147 tggrData.ajaxPostURL, { 148 'action' : tggr.prefix + 'render_latest_media_items', 149 'hashtag' : tggrData.hashtag, 150 'existingItemIDs' : tggr.existingItemIDs 151 }, 152 153 function( response ) { 154 if ( response.hasOwnProperty( 'success' ) && true === response.success && 0 != response.data ) { 155 tggr.refreshContent( response.data ); 156 } 157 158 $( tggr.loadingNewPosts ).addClass( tggr.cssPrefix + 'transparent' ); 159 tggr.retrievingNewItems = false; 160 } 161 ); 162 }, 163 164 /** 165 * Updates the DOM with new items that were retrieved during the last check 166 * 167 * We only update it when a refresh interval is set, though, for the same reasons described in 168 * toggleRetrieval(). Normally this won't be called if the refresh interval is cleared, but it's 169 * possible that it will be if the user scrolls to the top of the page and then scrolls back down 170 * before the AJAX request returns. 171 * 172 * @param {string} new_items_markup 173 */ 174 refreshContent : function( new_items_markup ) { 175 var $newItems; 176 177 if ( ! tggr.retrievalInterval ) { 178 return; 179 } 180 181 $newItems = $( new_items_markup ).prependTo( tggr.mediaItemContainer ); 182 $( tggr.mediaItemContainer ).trigger( 'tggr-rendered', { items: $newItems } ); 183 184 $( '#' + tggr.cssPrefix + 'no-posts-available' ).hide(); 185 tggr.existingItemIDs = tggr.getExistingItemIDs(); 186 }, 187 188 /** 189 * Refresh Masonry's layout after we add new elements to the DOM. 190 * 191 * @param {object} event 192 * @param {object} data 193 */ 194 refreshLayout: function( event, data ) { 195 $( tggr.mediaItemContainer ).masonry( 'prepended', data.items.get() ); 196 197 $( tggr.mediaItemContainer ).imagesLoaded( function() { 198 $( tggr.mediaItemContainer ).masonry( 'reloadItems' ); 199 $( tggr.mediaItemContainer ).masonry( 'layout' ); 200 } ); 201 } 202 }; // end tggr 203 204 $( document ).ready( tggr.init ); 205 206} // end tggr_wrapper() 207 208tggrWrapper( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.