2
\$\begingroup\$

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>&nbsp;&nbsp;' : x[p] + '&nbsp;&nbsp;';
 $('#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.

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jan 4, 2019 at 21:59
\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

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>&nbsp;&nbsp;' : x[p] + '&nbsp;&nbsp;';
 $('#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>&nbsp;&nbsp;' : x[p] + '&nbsp;&nbsp;';
 $('#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);

jsfiddle

answered Jan 7, 2019 at 5:08
\$\endgroup\$
2
  • \$\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\$ Commented Jan 7, 2019 at 12:22
  • \$\begingroup\$ @j4ffa By callback are you referring to the function set at cbOnImageSelect? eval() is not necessary. \$\endgroup\$ Commented Jan 7, 2019 at 16:27

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.