/**
* IPS Community Suite 4
* (c) 2013 Invision Power Services - http://www.invisionpower.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 continu 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 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 );
$( 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').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( /,,,
*/
_getPosition: function () {
var position = [];
var parent = this.scope.closest('.cGalleryViewImage');
var notePos = this.scope.position();
// Left
position[0] = ( notePos['left'] / parent.width() ) * 100;
// Top
position[1] = ( notePos['top'] / parent.height() ) * 100;
// Width
position[2] = ( this.scope.width() / parent.width() ) * 100;
// Height
position[3] = ( this.scope.height() / parent.height() ) * 100;
return position.join(',');
},
/**
* Takes note out of editing state
*
* @returns {void}
*/
_stopEditing: function () {
this._editing = false;
this._draggingNotEditing = false;
this.scope
.removeClass('cGalleryNote_editing')
.find('.cGalleryNote_note')
.text( this._note )
.end()
.find('.cGalleryNote_delete')
.remove();
},
/**
* Adds the note text to the note
*
* @returns {void}
*/
_buildNote: function () {
this.scope.find('.cGalleryNote_note').text( this._note );
},
/**
* When the note is editable, loads jQuery UI and sets up resizable/draggable
*
* @returns {void}
*/
_setUpEditable: function () {
if( !this._editable ){
return;
}
var self = this;
ips.loader.get( ['core/interface/jquery/jquery-ui.js'] ).then( function () {
self.scope.resizable({
containment: self.scope.closest('.cGalleryViewImage'),
handles: 'se',
stop: self._updatePosition.bind( self )
});
self.scope.draggable({
containment: self.scope.closest('.cGalleryViewImage'),
start: self._startDragging.bind( self ),
stop: self._updatePosition.bind( self )
});
// A workaround for an issue in resizable, where the container will jump because it uses percentage
// sizing, but resizable uses absolute sizing.
self.scope.find('.ui-resizable-handle').on('mouseover', function () {
self.scope.closest('.cGalleryViewImage').css( {
height: self.scope.closest('.cGalleryViewImage').height() + 'px'
});
});
});
},
/**
* Event handler for start event on Draggable. If we aren't already editing, set a flag so that
* when we stop dragging, the click doens't incorrectly put note into editing mode
*
* @returns {void}
*/
_startDragging: function () {
if( !this._editing ){
this._draggingNotEditing = true;
}
},
/**
* Saves the current position of the note. Called when resizable or draggable stop
*
* @returns {void}
*/
_updatePosition: function () {
var self = this;
// If this is a new note, we don't want to update the position remotely yet.
// We'll only do that once the note text is saved for the first time.
if( this.scope.attr('data-noteID') == 'new' ){
return;
}
this._saveNote( false, true )
.done( function () {
// If we were editing before updating pos/dims, we don't want to run the stop method
// otherwise changes to the note text will be lost.
if( !self._editing ){
self._stopEditing();
}
});
},
/**
* Positions the note based on the attributes on the scope element
*
* @returns {void}
*/
_initialPosition: function () {
var left = this.scope.attr('data-posLeft');
var top = this.scope.attr('data-posTop');
var width = this.scope.attr('data-dimWidth');
var height = this.scope.attr('data-dimHeight');
// Position the note
this.scope.css({
left: left + '%',
top: top + '%',
width: width + '%',
height: height + '%'
});
}
});
}(jQuery, _));]]>\
{{/showThumb}}\
{{^showThumb}}\
\
{{/showThumb}}\
\
{{#showThumb}}{{/showThumb}}\