This is my first attempt at a jQuery plugin and really doing anything with jQuery which isn't simple DOM manipulation or initialising another plugin!
I'm really keen to learn where I have gone wrong and improve on the code.
The code for the plugin is:
(function($) {
$.fn.unsplash = function(options, cbOnImageSelect) {
var defaults = {
apiRootURL: "https://api.unsplash.com", // root for API, should not need to be overridden
searchTerm: '', // the search term the results will be relating to
imagesToReturn: 6, // number of rows to output per page of results
page: 1, // page of search results to display, will be 1 to start
randomImagesToShow: 3, // number of images to display at the outset, 0 for none
apiClientId: '6066a5f8e2e83faef343bdca81bd128aebccec5d601e8e27501b0f6375423477', // can be overridden for a client id per account
orientation: 'landscape', // orientation of images to query for [landscape | portrait | squarish]
source: '', // where the plugin is being called from
cbOnImageSelect: $.noop
};
images = [];
totalImageCount = 0;
selectedImage = {};
var settings = $.extend({}, defaults, options);
//return to avoid breaking chaining
return this.each(function() {
//clear any content in the unsplash search
$('#unsplashSearch').empty();
//add a search form
$('#unsplashSearch').append('<div class="in_searchBox"><input type="text" name="us_searchTerm" id="us_searchTerm" value="" maxlength="50"><button type="submit" name="us_SearchBtn" id="us_SearchBtn"><span>Search</span></button></div>');
//bind the search handler event to the button in the form
$("#us_SearchBtn").bind("click", function(e) {
e.preventDefault();
//reset page back to 1 to avoid a new search starting on the wrong page.
settings.page = 1;
//set the settings search term to the new value
settings.searchTerm = $('#us_searchTerm').val();
searchAndPopulate(getSearchURL(settings.searchTerm, 1), settings.apiClientId);
});
$('#unsplashSearch').append('<ul id="imageList" class="horizontalList"></ul>');
$('#unsplashSearch').append('<div id="us_Paging" style="text-align: center;"></div>');
if (settings.randomImagesToShow > 0) {
//build up the request url based on whether the search term is null or not...
searchAndPopulate((!settings.searchTerm) ? getRandomURL(settings.randomImagesToShow) : getSearchURL(settings.searchTerm, 1), settings.apiClientId);
} else {
$('#us_Paging').hide();
}
//private functions
function callUnsplash(url) {
return $.ajax({
url: url,
dataType: 'json',
method: 'GET',
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Client-ID ' + settings.apiClientId);
xhr.setRequestHeader('Accept-Version', 'v1');
},
success: function(json) {
totalImageCount = (json.hasOwnProperty('total')) ? json.total : settings.imagesToReturn;
images = (json.hasOwnProperty('results')) ? json.results : json;
},
error: function(xhr, status, error) {
alert("Sorry, something went wrong (" + error + " " + xhr.status + " " + xhr.statusText + ")");
}
});
}
function searchAndPopulate(url, key) {
var callUnsplashPromise = callUnsplash(url, key);
callUnsplashPromise.done(populateImages);
}
function populateImages() {
if (images.total == 0) {
} else {
//clear any existing images in the list
$('#imageList').empty();
$.each(images, function(index, image) {
var liContent = `<li><a href="#" class="splashImage" data-photoid="${image.id}"><div class="in_max200"><img src='${image.urls.thumb}' class='in_singlepic in_max200'></a></li>`;
$('#imageList').append(liContent);
});
//bind the callback function to the splashImage so it is called when clicked. if no callback is provided, then the default will be performed
$(".splashImage").bind("click", function(event) {
event.preventDefault();
if (typeof settings.cbOnImageSelect == 'function') {
var photoId = $(this).data('photoid');
var url = getImageURL(photoId);
var getImagePromise = callUnsplash(url);
getImagePromise.done(function(i) {
settings.cbOnImageSelect.call(this, i);
});
}
});
if (totalImageCount <= settings.imagesToReturn) {
$('#us_Paging').hide();
} else {
$('#us_Paging').empty().show();
if (totalImageCount > settings.imagesToReturn) {
var noOfPages = Math.ceil(totalImageCount / settings.imagesToReturn);
var x = pagination(settings.page, noOfPages);
for (var p = 0; p < x.length; p++) {
var linkText = ((x[p] !== '...') && (x[p] !== settings.page)) ? '<a href="##" class="us_changePage" data-page="' + x[p] + '">' + x[p] + '</a> ' : x[p] + ' ';
$('#us_Paging').append(linkText);
}
//add the click event to the buttons with the class us_changePage
$(".us_changePage").bind("click", function(event) {
event.preventDefault();
var nextPage = $(this).data('page');
settings.page = nextPage;
var nextURL = getSearchURL(settings.searchTerm, nextPage);
searchAndPopulate(nextURL, settings.apiClientId);
});
}
}
}
}
function getSearchURL(searchTerm, pageToShow) {
return settings.apiRootURL + '/search/photos?page=' + pageToShow + '&per_page=' + settings.imagesToReturn + '&orientation=' + settings.orientation + '&query=' + searchTerm;
}
function getRandomURL(numberOfImages) {
return settings.apiRootURL + '/photos/random?count=' + numberOfImages + '&orientation=' + settings.orientation;
}
function getImageURL(photo_id) {
return settings.apiRootURL + '/photos/' + photo_id;
}
function pagination(c, m) {
var current = c,
last = m,
delta = 2,
left = current - delta,
right = current + delta + 1,
range = [],
rangeWithDots = [],
l;
for (var i = 1; i <= last; i++) {
if (i == 1 || i == last || i >= left && i < right) {
range.push(i);
}
}
for (var i of range) {
if (l) {
if (i - l === 2) {
rangeWithDots.push(l + 1);
} else if (i - l !== 1) {
rangeWithDots.push('...');
}
}
rangeWithDots.push(i);
l = i;
}
return rangeWithDots;
}
});
};
})(jQuery);
A link to a demo on JSFiddle: click here
Questions I have:
Is there a better way of maintaining the returned images JSON between the Ajax request and populating the page i.e.
images = [];
Have I bound the click event handlers correctly in the plugin; i.e.
$("#us_SearchBtn").bind("click", function(e) {
Also if you have any suggestions on how to run a different callback depending on which "Choose background" link is clicked, I'd be really grateful.
1 Answer 1
Questions I have:
Is there a better way of maintaining the returned images JSON between the Ajax request and populating the page i.e.
images = [];
images is a value that can be removed from the global scope. The value is defined when the jQuery promise object returns a value
images variable is not necessary
Define a function to handle errors
function handleError(error) {
alert(error.message)
}
return a value from callUnsplash
function callUnsplash(url) {
return $.ajax({
url: url,
dataType: 'json',
method: 'GET',
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Client-ID ' + settings.apiClientId);
xhr.setRequestHeader('Accept-Version', 'v1');
},
success: function(json) {
totalImageCount = (json.hasOwnProperty('total')) ? json.total : settings.imagesToReturn;
// `return` value here, which will be `images` parameter at `populateImages`
return json.hasOwnProperty('results') ? json.results : json;
},
error: function(xhr, status, error) {
// throw error
throw new Error("Sorry, something went wrong (" + error + " " + xhr.status + " " + xhr.statusText + ")");
}
});
}
images is value returned from callUnsplash(). $.map() can be substituted for $.each() for ability to return an HTML string or jQuery object when passed as parameter to .append(). Concatenate HTML strings where possible instead of calling .append() more than once chained to more than one jQuery() call. Include missing closing </div> where HTML is appended to #imageList unexpected results.
function populateImages(images) {
if (images.total == 0) {} else {
//clear any existing images in the list
$('#imageList').empty()
.append($.map(images, function(image, index) {
return `<li><a href="#" class="splashImage" data-photoid="${image.id}"><div class="in_max200"><img src='${image.urls.thumb}' class='in_singlepic in_max200'></div></a></li>`
}));
//bind the callback function to the splashImage so it is called when clicked. if no callback is provided, then the default will be performed
$(".splashImage").on("click", function(event) {
event.preventDefault();
if (typeof settings.cbOnImageSelect == 'function') {
var photoId = $(this).data('photoid');
var url = getImageURL(photoId);
// use `.then()`, handle errors
callUnsplash(url).then(function(i) {
settings.cbOnImageSelect.call(this, i);
}, handleError);
}
});
if (totalImageCount <= settings.imagesToReturn) {
$('#us_Paging').hide();
} else {
$('#us_Paging').empty().show();
if (totalImageCount > settings.imagesToReturn) {
var noOfPages = Math.ceil(totalImageCount / settings.imagesToReturn);
var x = pagination(settings.page, noOfPages);
for (var p = 0; p < x.length; p++) {
var linkText = ((x[p] !== '...') && (x[p] !== settings.page)) ? '<a href="##" class="us_changePage" data-page="' + x[p] + '">' + x[p] + '</a> ' : x[p] + ' ';
$('#us_Paging').append(linkText);
}
//add the click event to the buttons with the class us_changePage
$(".us_changePage").on("click", function(event) {
event.preventDefault();
var nextPage = $(this).data('page');
settings.page = nextPage;
var nextURL = getSearchURL(settings.searchTerm, nextPage);
searchAndPopulate(nextURL, settings.apiClientId);
});
}
}
}
}
Use .then() instead of .done(). Handle errors
function searchAndPopulate(url, key) {
callUnsplash(url, key)
.then(populateImages, handleError);
}
Have I bound the click event handlers correctly in the plugin; i.e.
$("#us_SearchBtn").bind("click", function(e) {
Substitute .on() for .bind() which is deprecated
Also if you have any suggestions on how to run a different callback depending on which "Choose background" link is clicked, I'd be really grateful.
Not certain what is meant by "a different callback". If a different handle for the click event is meant logic can be included in the function passed to .on() where the data-photoid="${image.id}" of the clicked element can be evaluated to perform different tasks based on the image.id value. For example
if ($(this).data().photoid === "abc") {
// do stuff
}
(function($) {
/*
* unsplash
*
* jQuery plugin to manage interaction with unsplash API
*
*
*/
$.fn.unsplash = function(options, cbOnImageSelect) {
// defaults for the plugin, client ID can be provided on a init by init basis so that each client can have their own CLIENTID and therefore won't count towards
// a common limit
var defaults = {
apiRootURL: "https://api.unsplash.com", // root for API, should not need to be overridden
searchTerm: '', // the search term the results will be relating to
imagesToReturn: 6, // number of rows to output per page of results
page: 1, // page of search results to display, will be 1 to start
randomImagesToShow: 3, // number of images to display at the outset, 0 for none
apiClientId: '9371fe014143706ce089532bc0149f75e5672f1a07acdd1599d42461c73e0707', // can be overridden for a client id per account
orientation: 'landscape', // orientation of images to query for [landscape | portrait | squarish]
source: '', // where the plugin is being called from
cbOnImageSelect: $.noop
};
// images = [];
var totalImageCount = 0;
var selectedImage = {};
function handleError(error) {
alert(error.message)
}
var settings = $.extend({}, defaults, options);
//return to avoid breaking chaining
return this.each(function() {
//clear any content in the unsplash search
$('#unsplashSearch').empty()
//add a search form
.append('<div class="in_searchBox"><input type="text" name="us_searchTerm" id="us_searchTerm" value="" maxlength="50"><button type="submit" name="us_SearchBtn" id="us_SearchBtn"><span>Search</span></button></div>');
//bind the search handler event to the button in the form
$("#us_SearchBtn").on("click", function(e) {
e.preventDefault();
//reset page back to 1 to avoid a new search starting on the wrong page.
settings.page = 1;
//set the settings search term to the new value
settings.searchTerm = $('#us_searchTerm').val();
searchAndPopulate(getSearchURL(settings.searchTerm, 1), settings.apiClientId);
});
$('#unsplashSearch').append('<ul id="imageList" class="horizontalList"></ul>' +
'<div id="us_Paging" style="text-align: center;"></div>');
if (settings.randomImagesToShow > 0) {
//build up the request url based on whether the search term is null or not...
searchAndPopulate((!settings.searchTerm) ? getRandomURL(settings.randomImagesToShow) : getSearchURL(settings.searchTerm, 1), settings.apiClientId);
} else {
$('#us_Paging').hide();
}
//private functions
function callUnsplash(url) {
return $.ajax({
url: url,
dataType: 'json',
method: 'GET',
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Client-ID ' + settings.apiClientId);
xhr.setRequestHeader('Accept-Version', 'v1');
},
success: function(json) {
totalImageCount = (json.hasOwnProperty('total')) ? json.total : settings.imagesToReturn;
return json.hasOwnProperty('results') ? json.results : json;
},
error: function(xhr, status, error) {
throw new Error("Sorry, something went wrong (" + error + " " + xhr.status + " " + xhr.statusText + ")");
}
});
}
function searchAndPopulate(url, key) {
callUnsplash(url, key)
.then(populateImages, handleError);
}
function populateImages(images) {
if (images.total == 0) {} else {
//clear any existing images in the list
$('#imageList').empty()
.append($.map(images, function(image, index) {
return `<li><a href="#" class="splashImage" data-photoid="${image.id}"><div class="in_max200"><img src='${image.urls.thumb}' class='in_singlepic in_max200'></div></a></li>`;
}));
//bind the callback function to the splashImage so it is called when clicked. if no callback is provided, then the default will be performed
$(".splashImage").on("click", function(event) {
event.preventDefault();
if (typeof settings.cbOnImageSelect == 'function') {
var photoId = $(this).data('photoid');
var url = getImageURL(photoId);
callUnsplash(url).then(function(i) {
settings.cbOnImageSelect.call(this, i);
}, handleError);
}
});
if (totalImageCount <= settings.imagesToReturn) {
$('#us_Paging').hide();
} else {
$('#us_Paging').empty().show();
if (totalImageCount > settings.imagesToReturn) {
var noOfPages = Math.ceil(totalImageCount / settings.imagesToReturn);
var x = pagination(settings.page, noOfPages);
for (var p = 0; p < x.length; p++) {
var linkText = ((x[p] !== '...') && (x[p] !== settings.page)) ? '<a href="##" class="us_changePage" data-page="' + x[p] + '">' + x[p] + '</a> ' : x[p] + ' ';
$('#us_Paging').append(linkText);
}
//add the click event to the buttons with the class us_changePage
$(".us_changePage").on("click", function(event) {
event.preventDefault();
var nextPage = $(this).data('page');
settings.page = nextPage;
var nextURL = getSearchURL(settings.searchTerm, nextPage);
searchAndPopulate(nextURL, settings.apiClientId);
});
}
}
}
}
function getSearchURL(searchTerm, pageToShow) {
return settings.apiRootURL + '/search/photos?page=' + pageToShow + '&per_page=' + settings.imagesToReturn + '&orientation=' + settings.orientation + '&query=' + searchTerm;
}
function getRandomURL(numberOfImages) {
return settings.apiRootURL + '/photos/random?count=' + numberOfImages + '&orientation=' + settings.orientation;
}
function getImageURL(photo_id) {
return settings.apiRootURL + '/photos/' + photo_id;
}
function pagination(c, m) {
var current = c,
last = m,
delta = 2,
left = current - delta,
right = current + delta + 1,
range = [],
rangeWithDots = [],
l;
for (var i = 1; i <= last; i++) {
if (i == 1 || i == last || i >= left && i < right) {
range.push(i);
}
}
for (var i of range) {
if (l) {
if (i - l === 2) {
rangeWithDots.push(l + 1);
} else if (i - l !== 1) {
rangeWithDots.push('...');
}
}
rangeWithDots.push(i);
l = i;
}
return rangeWithDots;
}
});
};
})(jQuery);
-
\$\begingroup\$ Thanks for the comments; when I say a different callback, I mean if you clicked on choose image on widget 1 and selecting an image it would run a different callback from clicking on choose image on widget 3 and selecting an image - even if all the chooseImage links were initialised with a single unsplash() call. So I guess putting the callback as a data attribute on the choose image link and using eval(). Is that bad practice and if so, is there a better way? thanks \$\endgroup\$j4ffa– j4ffa2019年01月07日 12:22:14 +00:00Commented Jan 7, 2019 at 12:22
-
\$\begingroup\$ @j4ffa By callback are you referring to the function set at
cbOnImageSelect?eval()is not necessary. \$\endgroup\$guest271314– guest2713142019年01月07日 16:27:50 +00:00Commented Jan 7, 2019 at 16:27