/** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.browse.list.js - Gallery browse list controller * * Author: Rikki Tissier */ ;( function($, _, undefined){ "use strict"; ips.controller.register('gallery.front.browse.list', { initialize: function () { this.on( 'change', '[data-role="moderation"]', this.selectImage ); this.on( 'tableRowsUpdated', this.rowsUpdated ); }, /** * Refreshes the patchwork when table rows are updated * * @returns {void} */ rowsUpdated: function () { var patchwork = ips.ui.photoLayout.getObj( this.scope ); if( patchwork ){ patchwork.refresh(); } }, /** * Toggles classes when the moderation checkbox is checked * * @param {event} e Event object * @returns {void} */ selectImage: function (e) { // e.stopPropagation(); // Can't do that or the moderator floating menu never shows up var row = $( e.currentTarget ).closest('.cGalleryImageItem'); row.toggleClass( 'cGalleryImageItem_selected', $( e.currentTarget ).is(':checked') ); //return false; } }); }(jQuery, _)); li', this.clickAlbum ); this.setup(); }, setup: function () { this._checkSelected(); }, /** * Event handler for clicking an album entry * * @param {event} e Event object * @returns {void} */ clickAlbum: function (e) { $( e.currentTarget ).find('input[type="radio"]').prop( 'checked', true ); this._checkSelected(); }, /** * Checks whether any radios are selected, and enables/disables the submit button as needed * * @returns {void} */ _checkSelected: function () { if( this.scope.find('input[name="existing_album"]:checked').length ){ this.scope.find('button[type="submit"]').prop( 'disabled', false ); } else { this.scope.find('button[type="submit"]').prop( 'disabled', true ); } } }); }(jQuery, _));]]> ').html( response ).find('[data-role="editForm"]').html(); doneFunc({ form: html, index: self._switchToAfterLoad.index }); self._switchToAfterLoad = null; }) } } else { // If we're doing a simple next/prev then we can just run it now with the response doneFunc({ form: data.response.form, index: data.response.index, success: data.response.success }); // Scroll the slider to the new image self._makeThumbActive( data.response.done ? null : data.response.index ); self._switchToAfterLoad = null; } }, /** * An info controller has informed us the form is loading * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ formLoading: function (e, data) { this._editForm.hide(); this._infoPanel.addClass('ipsLoading'); }, /** * Finishes editing and submits the wizard to continue to the next step after * verifying there's no unsaved images * * @returns {void} */ _doFinish: function () { // How many unsaved images do we have? We can count them in the slider to figure it out var self = this; var form = this.scope.find('[data-role="allImagesForm"]'); var unsavedCount = this._slider.find('li:not( .cGallerySubmit_done )').length; if( unsavedCount ){ ips.ui.alert.show( { type: 'confirm', icon: 'warn', message: ips.getString('images_without_data', { count: ips.pluralize( ips.getString( 'count_image' ), unsavedCount ) }), subText: ips.getString('images_without_data_desc'), callbacks: { ok: function () { form.attr('data-bypassChecks', true).submit(); }, cancel: function () { self._switchToAfterLoad = null; } } }); } else { form.attr('data-bypassChecks', true).submit(); } }, /** * Marks all images done and submits * * @returns {void} */ markAllDoneAndSubmit: function() { var currentIndex = this._slider.find('.cGallerySubmit_current').attr('data-index'); this._slider.find('li:not( .cGallerySubmit_done )').addClass( 'cGallerySubmit_done' ); var form = this.scope.find('[data-role="allImagesForm"]'); form.prepend( "" ); this._switchToAfterLoad = { finish: true }; // Does the currently-selected image need to be saved first? this.triggerOn( 'gallery.front.submit.imageInfo', 'submitWithoutRedirect.gallerySubmit', { index: currentIndex }); }, /** * Marks an image in the slider as done (i.e. info is saved) * * @param {number} index Index of the ID to make active * @returns {void} */ _markImageDone: function (index) { if( index !== -1 ){ this._slider.find('[data-index="' + index + '"]').addClass('cGallerySubmit_done'); } }, /** * Highlights and scrolls to the item in the slider that matches the given index * * @param {number} index Index of the ID to make active * @returns {void} */ _makeThumbActive: function (index) { // Remove highlight on all items this._slider.find('[data-role="imageThumb"]').removeClass('cGallerySubmit_current'); if( _.isNull( index ) ){ // If next index is null, we won't select any thumb - indicaes we're finished return; } // Find the new item @todo account for movies here var nextItem = this._slider.find('[data-index="' + index + '"]'); // Add class and scroll to it nextItem.addClass('cGallerySubmit_current'); if( ips.utils.responsive.enabled() && ips.utils.responsive.currentIs('phone') ){ var scrollLeft = this._slider.scrollLeft(); var nextOffset = nextItem.position()['left'] + scrollLeft; var nextMargin = parseInt( nextItem.css('margin-right') ); this._slider.animate({ scrollLeft: ( nextOffset - nextMargin ) + 'px' }, 'slow' ); } else { var scrollTop = this._slider.scrollTop(); var nextOffset = nextItem.position()['top'] + scrollTop; var nextMargin = parseInt( nextItem.css('margin-bottom') ); this._slider.animate({ scrollTop: ( nextOffset - nextMargin ) + 'px' }, 'slow' ); } } }); }(jQuery, _));]]> form').height(); var submitHeight = this.scope.find('.cGallerySubmit_bottomBar').height(); var availableHeight = height - submitHeight - 30; this.scope.find('.ipsAttachment_dropZone').css({ height: availableHeight + 'px' }); } }, /** * Uploader has told us all uploads are complete * * @param {event} e Event object * @param {objct} data Data object from uploader * @returns {void} */ uploadComplete: function (e, data) { if( data.success > 0 ){ this.scope.find('[data-role="submitForm"]').prop( 'disabled', false ); } if( data.error > 0 ){ this.scope.find('[data-role="imageErrors"]').show(); } }, /** * Responds to event from the uploader * * @param {event} e Event object * @param {object} data Data object from uploader * @returns {void} */ filesAdded: function (e, data) { this._shrinkUploader( true ); this.scope.find('[data-role="submitForm"]').prop( 'disabled', true ); this.scope.find('[data-role="addFiles"]').removeClass( 'ipsHide' ); $( window ).trigger('resize'); }, /** * Shrinks the uploader size so that files are shown * * @param {boolean} animate Animate to the smaller size? * @returns {void} */ _shrinkUploader: function (animate) { var dropzone = this.scope.find('.ipsAttachment_dropZone'); var css = { paddingTop: '10px', height: '160px' }; if( animate ){ dropzone .find('.fa-cloud-upload') .remove() .end() .animate( css ); } else { dropzone .find('.fa-cloud-upload') .remove() .end() .css( css ); } this._shrunk = true; } }); }(jQuery, _));]]> ' + data.contents + ''); this._updateContents( content.find('[data-controller="gallery.front.submit.wizard"]').contents() ); }, /** * Updates the wizard contents from a URL response * * @param {string} url URL to call * @param {object} data Data to pass to ajax handler * @returns {void} */ _changeContents: function (url, data) { if( _.isUndefined( data ) ){ data = {}; } var wizardContent = this.scope.find('[data-role="wizardContent"]'); var self = this; this.cleanContents(); wizardContent.html('').addClass('ipsLoading'); ips.getAjax()( url, { data: data, type: 'post', bypassRedirect: true }) .done( function (response, status, jqXHR) { // Just find the internal content var content = $('
' + response + '
'); self._updateContents( content.find('[data-controller="gallery.front.submit.wizard"]').contents() ); }); }, /** * Updates the wizard contents * * @param {string} contents HTML to insert into wizard * @returns {void} */ _updateContents: function (contents) { this.scope.html( contents ); $( document ).trigger( 'contentChange', [ this.scope ] ); } }); }(jQuery, _));]]>
viewportHeight + docScrollTop ){ this._scrolling = true; $('html, body').animate( { scrollTop: elemPosition.absPos.top + 'px' }, function () { self._scrolling = false; } ); } }, /** * Handles the keyDown event for navigating photos * * @returns {void} */ keyDown: function (e) { // Ignore the keypress if we're in a form element if( $( e.target ).closest('input, textarea, .ipsComposeArea, .ipsComposeArea_editor').length ){ return; } switch( e.keyCode ){ case ips.ui.key.LEFT: this.scope.find('[data-action="prevImage"]').click(); break; case ips.ui.key.RIGHT: this.scope.find('[data-action="nextImage"]').click(); break; } }, /** * An image in the slider was clicked on * * @param {event} e Event object * @returns {void} */ clickImage: function (e) { e.preventDefault(); var link = $( e.currentTarget ).find('a'); var url = link.attr('href'); var id = $( e.currentTarget ).attr('data-imageID'); var title = link.attr('title'); History.pushState( { controller: 'gallery.front.view.image', imageID: id }, title, url ); //this._scrollToImage( imageID ); }, /** * Scrolls the slider to the specified image * * @param {number} imageID Image ID to scroll to * @returns {void} */ _scrollToImage: function (imageID) { var self = this; var collection = this.scope.find('[data-role="collection"]'); var collectionItems = collection.find('li'); var toLoad = 0; var newItems = []; // Find the image ID we're navigating to var item = collectionItems.filter('[data-imageID="' + imageID + '"]'); if( !item.length ){ Debug.error("Couldn't find the image in the slider"); return; } var currentlyActive = collection.find('.cGalleryAlbumSlider_active'); // Make sure there's at least 2 before and 2 after var index = collectionItems.index( item.get(0) ); var toLoadPrev = 0; var toLoadNext = 0; var newPrevItems = []; var newNextItems = []; var newPrevID = 0; var newNextID = 0; // Add any images we need to load at the START of the slider // If the index is less than 3, we need to add placeholders to fill up the extra space if( index < 3 ){ newPrevItems = this._addPlaceholders( 'start', 3 - index ); collectionItems = collection.find('li'); // We need to change the left of the slider to compensate for the new items collection.css({ left: ( ( collection.position().left + ( newPrevItems.length * 50 ) ) * -1 ) + 'px' }); // Work out the index for( var i = index; i >= 0; i-- ){ var id = $( collectionItems[ i ] ).attr('data-imageID'); if( !_.isUndefined( id ) ){ newPrevID = id; } else { break; } } if( newPrevID ){ this._loadAndReplace( newPrevID, 'prev', newPrevItems.length, newPrevItems ) .done( function (response) { self._updateNavButton( 'prev', response.moreResults ); }); } } // Add any images we need to load at the END of the slider if( collectionItems.length < ( index + 4 ) ){ newNextItems = this._addPlaceholders( 'end', ( index + 4 ) - collectionItems.length ); collectionItems = collection.find('li'); // Work out the index for( var i = index; i < collectionItems.length; i++ ){ var id = $( collectionItems[ i ] ).attr('data-imageID'); if( !_.isUndefined( id ) ){ newNextID = id; } else { break; } } if( newNextID ){ this._loadAndReplace( newNextID, 'next', 10, newNextItems ) .done( function (response) { self._updateNavButton( 'next', response.moreResults ); }); } } // Get the position of the middle image var newLeftPos = item.position().left; var leftPos = parseInt( collection.css('left') ) * -1; var leftIdx = ( ( leftPos ) / 50 ); var middleLI = collectionItems.slice( leftIdx + 3, leftIdx + 4 ); var middleLeftPos = middleLI.position().left; var difference = newLeftPos - middleLeftPos; collection.animate({ left: "-=" + difference + 'px' }); collectionItems.removeClass('cGalleryAlbumSlider_active'); item.addClass('cGalleryAlbumSlider_active'); }, /** * Paginates to the previous screen of thumbs in the slider * * @param {event} e Event object * @returns {void} */ prevCollection: function (e) { e.preventDefault(); var self = this; var collection = this.scope.find('[data-role="collection"]'); var collectionItems = collection.find('li'); var toLoad = 0; var newItems = []; // Get the first image shown var leftPos = parseInt( collection.css('left') ) * -1; var leftIdx = ( ( leftPos ) / 50 ); var leftMostLI = collectionItems.slice( leftIdx, leftIdx + 1 ); var leftMostID = leftMostLI.attr('data-imageID'); // Make sure we have enough elements before this one if( leftIdx < 6 ){ toLoad = 6 - leftIdx; } var newLeftPos = leftPos * -1; if( toLoad > 0 ){ // Add spacers for the incoming images newItems = this._addPlaceholders( 'start', toLoad ); // We need to change the left of the slider to compensate for the new items newLeftPos = ( leftPos + ( newItems.length * 50 ) ) * -1; collection.css({ left: newLeftPos + 'px' }); } collection.animate({ left: ( newLeftPos + 300 ) + 'px' }); // Now load any images we need if( toLoad > 0 ){ this._loadAndReplace( leftMostID, 'prev', 6, newItems ) .done( function (response) { self._updateNavButton( 'prev', response.moreResults ); self._updateNavButton( 'next', true ); }); } else { self._updateNavButton( 'next', true ); } }, /** * Paginates to the next screen of images in the slider * * @param {event} e Event object * @returns {void} */ nextCollection: function (e) { e.preventDefault(); var self = this; var collection = this.scope.find('[data-role="collection"]'); var collectionItems = collection.find('li'); var toLoad = 0; var newItems = []; // Figure out the right-most image var leftPos = parseInt( collection.css('left') ) * 1; var leftIdx = ( ( 350 - leftPos ) / 50 ) - 1; // Get the left position var rightMostLI = collectionItems.slice( leftIdx, leftIdx + 1 ); var rightMostID = rightMostLI.attr('data-imageID'); var rightPos = rightMostLI.position(); // Make sure we have enough elements after this one if( collectionItems.length < ( leftIdx + 6 ) ){ toLoad = ( collectionItems.length + 5 ) - leftIdx; } // If we are loading any new images, create a holder and append it to our list if( toLoad > 0 ){ // Add spacers for the incoming images newItems = this._addPlaceholders( 'end', toLoad ); } collection.animate({ left: ( rightPos.left * -1 ) + 'px' }); // Now load any images we need if( toLoad > 0 ){ this._loadAndReplace( rightMostID, 'next', 6, newItems ) .done( function (response) { self._updateNavButton( 'next', response.moreResults ); self._updateNavButton( 'prev', true ); }); } else { self._updateNavButton( 'prev', true ); } }, /** * Adds placeholder images to the slider in the location specified * * @param {string} location 'start' or 'end' * @param {number} count Number to add * @returns {array} Array of placeholder elements created */ _addPlaceholders: function (location, count) { var collection = this.scope.find('[data-role="collection"]'); var newItems = []; for( var i = 0; i < count; i++ ){ var item = $('
  • ').addClass('cGalleryTrimmedImage ipsAreaBackground ipsLoading_tiny'); newItems.push( item ); if( location == 'end' ){ collection.append( item ); } else { collection.prepend( item ); } } return newItems; }, /** * Loads more slider images * * @param {string} direction Which direction we're updating * @param {boolean} moreResults Are there more results to see? * @returns {void} */ _updateNavButton: function (direction, moreResults) { var elem = this.scope.find('[data-role="collectionNav"] [data-action="' + direction + '"]'); if( moreResults ){ elem.removeAttr('data-disabled'); } else { elem.attr( 'data-disabled', true ); } }, /** * Loads more slider images * * @param {number} fromID The ID to fetch from * @param {string} direction next or prev * @param {number} count Number of items to get * @param {array} itemsArray Array of placeholders that will be replaced with the loaded images * @returns {void} */ _loadAndReplace: function (fromID, direction, count, itemsArray) { var deferred = $.Deferred(); this.scope.find('[data-role="collectionNav"] [data-action]').attr( 'data-disabled', true ); ips.getAjax()( ips.getSetting('baseURL') + 'index.php?app=gallery&module=gallery&controller=browse&do=getImages&image=' + fromID + '&direction=' + direction + '&count=' + ( count + 1 ) ) .done( function (response) { // Now replace each of the placeholders for( var i = 0; i < itemsArray.length; i++ ){ if( response[ i ] ){ itemsArray[ i ].replaceWith( response[i] ); } } deferred.resolve({ moreResults: !( response.length <= count ) }); }); return deferred.promise(); }, /** * Navigates the page to the next image * * @param {event} e Event object * @returns {void} */ nextImage: function (e) { e.preventDefault(); var url = $( e.currentTarget ).attr('href'); var id = $( e.currentTarget ).attr('data-imageID'); var title = $( e.currentTarget ).attr('title'); History.pushState( { controller: 'gallery.front.view.image', imageID: id }, title, url ); }, /** * Navigates the page to the previous image * * @param {event} e Event object * @returns {void} */ prevImage: function (e) { e.preventDefault(); var url = $( e.currentTarget ).attr('href'); var id = $( e.currentTarget ).attr('data-imageID'); var title = $( e.currentTarget ).attr('title'); History.pushState( { controller: 'gallery.front.view.image', imageID: id }, title, url ); }, /** * Sets the current image as the user's profile picture * * @param {event} e Event object * @returns {void} */ setAsProfile: function (e) { e.preventDefault(); var url = $( e.currentTarget ).attr('href'); ips.ui.alert.show( { type: 'confirm', icon: 'question', message: ips.getString('set_as_photo_confirm'), callbacks: { ok: function () { ips.getAjax()( url, { showLoading: true } ) .done( function (response) { ips.ui.flashMsg.show( response.message ); }) .fail( function () { window.location = url; }); } } }); }, /** * Sets the image as a cover photo * * @param {event} e Event object * @returns {void} */ setAsCover: function (e) { e.preventDefault(); var url = $( e.currentTarget ).attr('href'); ips.getAjax()( url, { showLoading: true } ) .done( function (response) { ips.ui.flashMsg.show( response.message ); }) .fail( function () { window.location = url; }); }, /** * Event handler for window resizing * * @returns {void} */ windowResize: function () { this._setUpSizing(); }, /** * Loads the specified URL to fetch a new image * * @param {string} url URL of new image to fetch * @returns {void} */ _loadURL: function (url) { var self = this; this._setImageLoading( true ); // Cancel any request running if( this._ajaxObj && _.isFunction( this._ajaxObj.abort ) ){ this._ajaxObj.abort(); } this._ajaxObj = ips.getAjax(); this._ajaxObj( url, { dataType: 'json' } ) .done( function (response) { self._updateImage( response ); }) .fail( function( jqXHR, textStatus, errorThrown ) { if( Debug.isEnabled() ){ Debug.error( errorThrown ); } else { window.location = url; } }); }, /** * Handles a response from the server with new image info * * @param {object} response Server response json * @returns {void} */ _updateImage: function (response) { this.cleanContents(); this.scope.find('[data-role="imageInfo"]').html( response.info ); this.scope.find('[data-role="imageFrame"]').replaceWith( response.image ); if( response.slider ){ this.scope.find('#elGalleryNav > ul').html( response.slider ); } if( response.image.match( /