Reputation: 12707
I'm building a App that need to list the categories and subcategories of a Product.
When a user selects a category, the subcategories related to this category are loaded from the server with ajax, but only if they have not been loaded later (in this case they are loaded from the DOM).
Code: http://jsbin.com/abijad/edit#javascript,html
var $form = $('#new-product-form');
$form.on( 'change', 'select.product-categories', function( e ) { //I'm using event delegation for a future feature...
getSubCategories( $(this).val() ).done( function( $subCategoriesEl ){
$form.find( 'select.product-subcategories' ).not( $subCategoriesEl ).hide();
$subCategoriesEl.show();
});
});
var getSubCategories = function( categoryId ) {
var dfd = $.Deferred(),
$alreadyisLoaded = $form.find( 'select.product-subcategories' ).map( function( idx, el ){
if( parseInt( $( this ).data( 'category' ), 10 ) === parseInt( categoryId, 10 ) ){
return el;
}
});
if( $alreadyisLoaded.length > 0 ){ //don't request subCategories that already are loaded
console.log( 'SubCategory loaded from DOM' );
dfd.resolve( $alreadyisLoaded );
} else {
var subCategoriesHtml = '<select data-category="' + categoryId + '" class="product-subcategories">';
$.get( '/', { //The ajax request will only be simulated
categoryId : categoryId
}, function ( result ) {
//simulate success :X
result = {"status":1,"data":[{"name":"Sub-Category I","id":1},{"name":"Sub-Category II","id":2},{"name":"Sub-Category III","id":3}]};
console.log( 'SubCategory loaded with Ajax' );
if( result.status === 1 ) { //Success
for( var subCategoryInfo in result.data) {
if( result.data.hasOwnProperty( subCategoryInfo ) ){
subCategoriesHtml += '<option value="' + result.data[subCategoryInfo].id + '">';
subCategoriesHtml += result.data[subCategoryInfo].name + '</option>';
}
}
subCategoriesHtml += '</select>';
var $subCategories = $( subCategoriesHtml ).hide().appendTo( $form );
dfd.resolve( $subCategories );
} else {
dfd.reject();
}
});
}
return dfd.promise();
};
<form id="new-product-form">
<select class="product-categories">
<option value="1">Category I</option>
<option value="2">Category II</option>
<option value="3">Category III</option>
<option value="4">Category IV</option>
<option value="5">Category V</option>
</select>
<select data-category="1" class="product-subcategories">
<option value="1">SubCategory I</option>
<option value="2">SubCategory II</option>
<option value="3">SubCategory III</option>
<option value="4">SubCategory IV</option>
<option value="5">SubCategory V</option>
</select>
</form>
Because the code was getting full of callback here and there, I decided to use the jQuery Deferred object, but I do not know if this is the correct implementation. Could someone tell me I did the right thing, or should I do differently?
Upvotes: 1
Views: 235
Reputation: 17825
I don't see anything glaringly incorrect. All in all, you are using the deferred in the correct fashion: to abstract away the possibly dual-synchronistic nature of your method. Now, that being said, if this were code appearing in my codebase, this is how I would write it. The main points being: don't use for in
on arrays, build strings with arrays, consistent naming and spacing, and just some other terse JS preferences. These are a matter of taste, so otherwise, good job:
(function() {
var getSubCategories = function ( categoryId ) {
categoryId = +categoryId;
return $.Deferred( function ( dfd ) {
var isLoaded = form.find( 'select.product-subcategories' )
.map( function ( index, el ) {
if ( +$( this ).data( 'category' ) === categoryId ) {
return el;
}
}),
markup = [ ];
if ( isLoaded.length ) {
console.log( 'SubCategory loaded from DOM' );
dfd.resolve( isLoaded );
} else {
markup.push( '<select data-category="' + categoryId + '" class="product-subcategories">' );
var request = $.ajax({
url: '/',
data: { categoryId: categoryId }
});
request.done( function ( result ) {
//simulate success :X
result = {"status":1,"data":[{"name":"Sub-Category I","id":1},{"name":"Sub-Category II","id":2},{"name":"Sub-Category III","id":3}]};
var status = result.status,
data = result.data,
i = 0,
il = data.length,
current;
console.log( 'SubCategory loaded with Ajax' );
if ( status !== 1 || !data ) {
dfd.reject();
return;
}
for ( current = data[ i ]; i < il; i++ ) {
markup.push( '<option value="' + current.id + '">' );
markup.push( current.name + '</option>' );
}
markup.push( '</select>' );
dfd.resolve( $( markup.join( '' ) ).hide().appendTo( form ) );
});
}
}).promise();
};
var form = $( '#new-product-form' )
.on( 'change', 'select.product-categories', function ( e ) {
getSubCategories( $( this ).val() )
.done( function( el ) {
form.find( 'select.product-subcategories' )
.not( el )
.hide()
el.show();
});
});
});
As a side note, just wanted to bring up that you don't have any handling of problems if the ajax request fails. You would need to reject
in that case, and make sure you write fail
methods. Just a heads up.
Upvotes: 1