').addClass('resource-edit-link').attr({
'href': data.editURL
}).text("Edit").appendTo(extraElem);
}
$(container).empty().append(this.resourceElem);
};
CCResource.prototype._preloadImage = function(url, container) {
var imgElem = $('');
imgElem.one('load error', function(e) {
container.removeClass('loading');
if (e.type == 'error') container.addClass('loading-error');
$(this).remove();
});
imgElem.attr('src', url);
};
function CCResourceFeed(requestURL, options) {
$.extend(this, this.DEFAULT_OPTIONS, options);
this.init(requestURL);
};
CCResourceFeed.prototype.DEFAULT_OPTIONS = {
'batchSize': 60,
/* Always limit the number of resources to load */
'defaultConnectionLimit': 250,
'meteredConnectionLimit': 30,
/* It is typically safe to assume that these connection types are metered */
'meteredConnectionTypes': ['bluetooth', 'cellular', 'wimax'],
'onResourcesLoaded': function() {}
};
CCResourceFeed.prototype.init = function(requestURL) {
this.requestURL = requestURL;
this.initialId = 0;
this.resourcesTotal = undefined;
this.resourcesRemaining = undefined;
this.incomingData = [];
this.nextRequestStart = 0;
this.loading = undefined;
};
CCResourceFeed.prototype.next = function() {
/* TODO: Instead, we should check hasNext and call loadMoreFromServer
* automatically if we aren't at the end */
var data = this.incomingData.shift(),
resource = new CCResource(data);
return resource;
};
CCResourceFeed.prototype.hasNext = function() {
return this.incomingData.length > 0;
};
CCResourceFeed.prototype.loadMoreFromServer = function() {
var _this = this;
if (this.loading && this.loading.status === undefined) return;
var requestEnd = this.getMaximum() - this.nextRequestStart,
requestCount = (requestEnd > 0) ? Math.min(this.batchSize, requestEnd) : 0;
if (requestCount > 0) {
this.loading = $.ajax({
'url': this.requestURL,
'type': 'get',
'dataType': 'json',
'data': {
'action': 'get_resources',
'first': this.initialId,
'start': this.nextRequestStart,
'count': requestCount
}
}).done(function(data, textStatus, jqXHR) {
_this.addResourcesFromData(data);
});
}
};
CCResourceFeed.prototype.hasMoreFromServer = function() {
return this.resourcesRemaining === undefined || this.resourcesRemaining > 0;
};
CCResourceFeed.prototype.getMaximum = function() {
var maximum = this.getConnectionLimit();
if (this.resourcesTotal !== undefined && maximum !== undefined) {
maximum = Math.min(maximum, this.resourcesTotal);
} else {
maximum = this.resourcesTotal;
}
return maximum;
};
CCResourceFeed.prototype.getConnectionLimit = function() {
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection,
connectionType = (connection) ? connection.type : undefined,
isMetered = this.meteredConnectionTypes.indexOf(connectionType) >= 0;
return (isMetered) ? this.meteredConnectionLimit : this.defaultConnectionLimit;
};
CCResourceFeed.prototype.addResourcesFromData = function(data) {
data = data || {};
this.resourcesTotal = data['total'];
this.resourcesRemaining = data['remaining'];
var newResources = data['resources'] || [];
if (newResources) {
if (this.initialId == 0) {
var firstResource = newResources[0] || {};
this.initialId = firstResource.id;
}
Array.prototype.push.apply(this.incomingData, newResources);
this.nextRequestStart += newResources.length;
this.onResourcesLoaded();
}
};
function CCResourceGrid(container, options) {
$.extend(this, this.DEFAULT_OPTIONS, options);
this.init(container);
};
CCResourceGrid.prototype.DEFAULT_OPTIONS = {
'maximum': undefined,
'maxExtraRows': 3
};
CCResourceGrid.prototype.DEFAULT_ADD_TILES_OPTIONS = {
'initial': false
};
CCResourceGrid.prototype.init = function(container) {
this.container = $(container);
this.tileWidth = undefined;
this.tileHeight = undefined;
this.rowSize = undefined;
// List of resource tiles with class "empty", duplicated here to avoid
// hammering the browser with DOM lookups
this.emptyTiles = [];
this.tilesCount = 0;
this.container.on('mouseenter click', '.resource', function(e) {
var isActive = $(this).hasClass('active');
if (!isActive) {
$('.resource.active', container).not(this).removeClass('active');
$(this).addClass('active');
e.preventDefault();
}
});
this.container.on('mouseleave', '.resource', function(e) {
$(this).removeClass('active');
});
// Disable mouse hover events for touch devices. This is an ugly hack
// to avoid the double tap problem.
this.container.one('touchstart', '.resource', function(e) {
$(container).off('mouseenter');
});
};
CCResourceGrid.prototype.updateDimensions = function() {
// Cache element dimensions that only change on resize
var aTile = this.container.children('.resource-tile').first();
if (aTile.length > 0) {
var listWidth = this.container.outerWidth();
this.tileWidth = Math.ceil(aTile.outerWidth()),
this.tileHeight = Math.ceil(aTile.outerHeight());
} else {
this.tileWidth = 0;
this.tileHeight = 0;
}
this.rowSize = this.tileWidth > 0 ? Math.ceil(listWidth / this.tileWidth) : undefined;
this.maxExtraRows = Math.ceil($(window).height() / this.tileHeight);
};
CCResourceGrid.prototype.setInitialDimensions = function(addTilesOptions) {
this.addTiles(1, addTilesOptions);
this.updateDimensions();
};
CCResourceGrid.prototype.updateOnScreen = function() {
// Loops through resource tiles and marks them if they are offscreen.
// This may be a good place to proactively unload images if we need to.
var scrollTop = $(window).scrollTop(),
scrollBottom = scrollTop + $(window).height();
var footerElem = $('.site-footer.sticky');
if (footerElem.hasClass('detached') && !footerElem.hasClass('offscreen')) {
scrollBottom -= footerElem.height();
}
var newTiles = $('.resource-tile.offscreen', this.container);
$(newTiles).each(function(index, resourceTile) {
var tileTop = $(resourceTile).offset().top;
if (scrollBottom > tileTop) {
$(resourceTile).removeClass('offscreen');
}
});
};
CCResourceGrid.prototype.getRemaining = function() {
if (this.maximum !== undefined) {
return this.maximum - this.tilesCount;
} else {
return undefined;
}
};
CCResourceGrid.prototype.addTiles = function(count, addTilesOptions) {
var remainingTiles = this.getRemaining(),
options = $.extend({}, this.DEFAULT_ADD_TILES_OPTIONS, addTilesOptions);
if (remainingTiles !== undefined) {
count = Math.min(count, remainingTiles);
}
for (var i = 0; i < count; i++) {
var resourceTile = $('').addClass('resource-tile empty');
if (!options.initial) resourceTile.addClass('offscreen');
this.emptyTiles.push(resourceTile);
resourceTile.appendTo(this.container);
}
this.tilesCount += count;
return count;
};
CCResourceGrid.prototype.addRows = function(rows, addTilesOptions) {
if (this.rowSize === undefined) this.setInitialDimensions(addTilesOptions);
// Add enough resource tiles to fill the given number of rows.
// We calculate a remainder to keep everything square.
var tilesNeeded = rows * this.rowSize,
remainder = (this.tilesCount + tilesNeeded) % this.rowSize;
return this.addTiles(tilesNeeded + remainder, addTilesOptions);
};
CCResourceGrid.prototype.addRowsForSpace = function(scrollBottom, velocity, addTilesOptions) {
if (this.tileHeight === undefined) this.setInitialDimensions(addTilesOptions);
var padding = this.tileHeight || 0,
listBottom = this.container.offset().top + this.container.outerHeight(),
triggerEdge = listBottom - padding,
distanceFromEdge = scrollBottom - triggerEdge,
rowsNeeded = (padding > 0) ? Math.ceil(distanceFromEdge / padding) : 0;
if (rowsNeeded > 0) {
var extraRows = (velocity && padding > 0) ? Math.ceil(velocity / padding) : 0;
// Load as many rows as we need, and a bit extra
var newRows = Math.min(this.maxExtraRows, rowsNeeded + extraRows);
return this.addRows(newRows, addTilesOptions);
} else {
return 0;
}
};
CCResourceGrid.prototype.next = function() {
var resourceTile = this.emptyTiles.shift();
return resourceTile;
};
CCResourceGrid.prototype.fillTiles = function(resources) {
// Loop through empty resource tiles and add loaded resources to them.
while (this.hasNext() && resources.hasNext()) {
var resourceTile = this.next(),
resource = resources.next();
resource.createElem(resourceTile);
resourceTile.removeClass('empty');
}
// Return true if all tiles were filled; false if we ran out of resources.
return resources.hasNext() || !this.hasNext();
}
CCResourceGrid.prototype.hasNext = function() {
return this.emptyTiles.length > 0;
};
$(document).ready(function() {
var resourceFeed = undefined,
resourceGrid = undefined;
var fillEmptyResourceTiles = function() {
var needsMoreResources = !resourceGrid.fillTiles(resourceFeed);
if (needsMoreResources && resourceFeed.hasMoreFromServer()) {
resourceFeed.loadMoreFromServer();
}
};
var onResourcesLoaded = function() {
resourceGrid.maximum = resourceFeed.getMaximum();
fillEmptyResourceTiles();
};
var onResizeCb = function(e) {
resourceGrid.updateDimensions();
resourceGrid.updateOnScreen();
};
var onScrollCb = function(e, params) {
var newTilesCount = 0,
velocity = params['velocity'],
bottom = params['bottom'];
if (velocity === undefined) {
newTilesCount = resourceGrid.addRowsForSpace(bottom, undefined);
} else if (velocity > 0) {
newTilesCount = resourceGrid.addRowsForSpace(bottom + velocity, velocity);
}
if (newTilesCount > 0) {
fillEmptyResourceTiles();
}
resourceGrid.updateOnScreen();
};
resourceFeed = new CCResourceFeed(CC_RESOURCE.ajaxurl, {
'onResourcesLoaded': onResourcesLoaded
});
resourceGrid = new CCResourceGrid('.resource-list');
resourceFeed.addResourcesFromData(CC_RESOURCE.initial);
var initialTilesCount = resourceGrid.addRows(2, {
'initial': true
});
if (initialTilesCount > 0) {
fillEmptyResourceTiles(initialTilesCount);
}
$(window).on('resize', onResizeCb);
$(document).on('cc-scroll', onScrollCb);
});
})(jQuery);