From 4b4ecef4a32319932f31413feaf60e0dbfa96973 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Wed, 29 Sep 2010 00:52:56 -0500 Subject: Added error handler for ipa_cmd(). The ipa_cmd() has been modified such that when an error occurs a dialog box will appear showing the error message with 2 buttons: Retry and Cancel. If Retry is clicked, it will attempt to execute the same operation again. If Cancel is clicked, the operation will be canceled and the control is returned to the caller. New unit tests have been added to test ipa_cmd() on successfull and unsuccessfull cases. The associate.js, details.js, entity.js, search.js, and webui.js have been modified to display the error message inside the page. This behavior can be changed in the future (e.g. redirect to error page). The navigation.js and webui.js have been modified to render only the visible tabs. This improves the performance and reduce hidden errors. The navigation unit test has been modified to reflect this behavior. Some variables/functions also have been renamed for consistency. --- install/static/associate.js | 42 +++---- install/static/details.js | 29 +++-- install/static/entity.js | 5 +- install/static/ipa.js | 38 ++++-- install/static/navigation.js | 64 +++++++++- install/static/search.js | 38 +++--- install/static/test/ipa_tests.html | 3 + install/static/test/ipa_tests.js | 201 ++++++++++++++++++++++++++++++++ install/static/test/navigation_tests.js | 12 +- install/static/webui.js | 67 ++--------- 10 files changed, 380 insertions(+), 119 deletions(-) (limited to 'install') diff --git a/install/static/associate.js b/install/static/associate.js index 207c745c3..c3453e965 100644 --- a/install/static/associate.js +++ b/install/static/associate.js @@ -222,30 +222,30 @@ function AssociationList(obj, pkey, manyObj, associationColumns, jobj) this.manyObj = manyObj; this.parentTab = jobj; - this.populate = function(userData) { - var tbody = this.parentTab.find('.search-table tbody'); - tbody.empty(); - var associationList = userData.result.result[this.associationColumns[0].column]; - for (var j = 0; j < associationList.length; j++){ - var row = $("").appendTo(tbody); - for (var k = 0; k < associationColumns.length ;k++){ - var column = this.associationColumns[k].column; - $("",{ - html: userData.result.result[column][j] - }).appendTo(row); + this.refresh = function() { + + function refresh_on_success(userData) { + var tbody = this.parentTab.find('.search-table tbody'); + tbody.empty(); + var associationList = userData.result.result[this.associationColumns[0].column]; + for (var j = 0; j < associationList.length; j++){ + var row = $("").appendTo(tbody); + for (var k = 0; k < associationColumns.length ;k++){ + var column = this.associationColumns[k].column; + $("",{ + html: userData.result.result[column][j] + }).appendTo(row); + } } } - } - this.refresh = function() { - ipa_cmd( 'show', [this.pkey], {}, - function(result){ - form.populate(result); - }, - function(){ - alert("associationListFailure"); - }, - form.obj); + function refresh_on_error(xhr, text_status, error_thrown) { + var search_results = $('.search-results', jobj).empty(); + search_results.append('

Error: '+error_thrown.name+'

'); + search_results.append('

'+error_thrown.message+'

'); + } + + ipa_cmd('show', [this.pkey], {}, refresh_on_success, refresh_on_error, form.obj); } this.setup = function() { diff --git a/install/static/details.js b/install/static/details.js index f68c77324..4e37ed696 100644 --- a/install/static/details.js +++ b/install/static/details.js @@ -37,20 +37,24 @@ function ipa_details_create(obj_name, dls, container) container.attr('title', obj_name); container.addClass('details-container'); - container.append('
'); - var jobj = container.children().last(); + var details = $('
', { + class: 'details' + }).appendTo(container); + + details.append('
'); + var jobj = details.children().last(); jobj.append('Reset'); jobj.append('Update'); - container.append('
'); + details.append('
'); for (var i = 0; i < dls.length; ++i) { var d = dls[i]; - ipa_generate_dl(container.children().last(), d[0], d[1], d[2]); + ipa_generate_dl(details.children().last(), d[0], d[1], d[2]); } - container.append('
'); - var jobj = container.children().last(); + details.append('
'); + var jobj = details.children().last(); jobj.append('Back to Top'); } @@ -87,8 +91,10 @@ function ipa_generate_dl(jobj, id, name, dts) jobj.after('
'); } -function ipa_details_load(obj_name, pkey, on_win, on_fail, sampleData) +function ipa_details_load(jobj, pkey, on_win, on_fail) { + var obj_name = jobj.attr('id'); + function load_on_win(data, text_status, xhr) { if (on_win) on_win(data, text_status, xhr); @@ -103,14 +109,17 @@ function ipa_details_load(obj_name, pkey, on_win, on_fail, sampleData) function load_on_fail(xhr, text_status, error_thrown) { if (on_fail) on_fail(xhr, text_status, error_thrown); + + var details = $('.details', jobj).empty(); + details.append('

Error: '+error_thrown.name+'

'); + details.append('

'+error_thrown.message+'

'); }; if (!pkey) return; ipa_cmd( - 'show', [pkey], {all: true}, load_on_win, load_on_fail, - obj_name, sampleData + 'show', [pkey], {all: true}, load_on_win, load_on_fail, obj_name ); } @@ -301,7 +310,7 @@ var _ipa_param_type_2_handler_map = { 'Str': _ipa_create_text_input, 'Int': _ipa_create_text_input, 'Bool': _ipa_create_text_input, - 'List': _ipa_create_text_input, + 'List': _ipa_create_text_input }; /* create an HTML element for displaying/editing an attribute diff --git a/install/static/entity.js b/install/static/entity.js index 7fb19fc79..7b82c06e3 100644 --- a/install/static/entity.js +++ b/install/static/entity.js @@ -83,7 +83,7 @@ function ipa_entity_setup(jobj) }).appendTo($( "div#" + obj_name + " > div.search-controls")); if (typeof filter != 'undefined') - search_load(obj_name, filter, null, null); + search_load(jobj, filter, null, null); }; function setup_details_facet() { @@ -92,8 +92,9 @@ function ipa_entity_setup(jobj) ipa_details_create(obj_name, ipa_entity_details_list[obj_name], jobj); jobj.find('.details-reset').click(reset_on_click); jobj.find('.details-update').click(update_on_click); + if (pkey) - ipa_details_load(obj_name, pkey, null, null); + ipa_details_load(jobj, pkey, null, null); }; function setup_associate_facet() { diff --git a/install/static/ipa.js b/install/static/ipa.js index 6000fb73e..25a3f1bb7 100644 --- a/install/static/ipa.js +++ b/install/static/ipa.js @@ -30,7 +30,7 @@ var ipa_ajax_options = { contentType: 'application/json', dataType: 'json', async: true, - processData: false, + processData: false }; /* JSON-RPC ID counter */ @@ -40,6 +40,7 @@ var ipa_jsonrpc_id = 0; var ipa_messages = {}; var ipa_objs = {}; +var ipa_dialog = $('
', {id: 'ipa_dialog'}); /* initialize the IPA JSON-RPC helper * arguments: @@ -74,9 +75,33 @@ function ipa_init(url, use_static_files, on_win, on_error) * objname - name of an IPA object (optional) */ function ipa_cmd(name, args, options, win_callback, fail_callback, objname) { + function ipa_error_handler(xhr, text_status, error_thrown) { + ipa_dialog.empty(); + ipa_dialog.attr('title', 'Error: '+error_thrown.name); + ipa_dialog.append('

'+error_thrown.message+'

'); + + ipa_dialog.dialog({ + modal: true, + width: 400, + buttons: { + 'Retry': function() { + ipa_dialog.dialog('close'); + ipa_cmd(name, args, options, win_callback, fail_callback, objname); + }, + 'Cancel': function() { + ipa_dialog.dialog('close'); + fail_callback(xhr, text_status, error_thrown); + } + } + }); + }; + id = ipa_jsonrpc_id++; + + var method_name = name; + if (objname) - name = objname + '_' + name; + method_name = objname + '_' + name; var url = ipa_json_url; @@ -84,19 +109,19 @@ function ipa_cmd(name, args, options, win_callback, fail_callback, objname) url = IPA_DEFAULT_JSON_URL; if (ipa_use_static_files) - url += '/' + name + '.json'; + url += '/' + method_name + '.json'; var data = { - method: name, + method: method_name, params: [args, options], - id: id, + id: id }; var request = { url: url, data: JSON.stringify(data), success: win_callback, - error: fail_callback, + error: ipa_error_handler }; $.ajax(request); @@ -163,4 +188,3 @@ function ipa_get_member_attribute(obj_name, member) return null; } - diff --git a/install/static/navigation.js b/install/static/navigation.js index 169729ba0..3aa49fe2b 100644 --- a/install/static/navigation.js +++ b/install/static/navigation.js @@ -18,6 +18,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* use this to track individual changes between two hashchange events */ +var window_hash_cache = {}; function nav_create(nls, container, tabclass) { @@ -38,6 +40,8 @@ function nav_create(nls, container, tabclass) return true; } }); + + nav_update_tabs(nls, container); } function nav_generate_tabs(nls, container, tabclass, depth) @@ -62,9 +66,7 @@ function nav_generate_tabs(nls, container, tabclass, depth) var div = nav_create_tab_div(n.name); container.append(div); - if (n.setup) { - n.setup(div); - } else if (n.children) { + if (n.children) { nav_generate_tabs(n.children, div, tabclass, depth +1 ); } } @@ -103,3 +105,59 @@ function nav_select_tabs(nls, container) } } } + +function nav_update_tabs(nls, container) +{ + nav_select_tabs(nls, container); + + var index1 = container.tabs('option', 'selected'); + if (index1 >= nls.length) return; + + var tab1 = nls[index1]; + if (!tab1.children) return; + + var div1 = $('#' + tab1.name); + var index2 = div1.tabs('option', 'selected'); + if (index2 >= tab1.children.length) return; + + var tab2 = tab1.children[index2]; + var obj_name = tab2.name; + var entity_setup = tab2.setup; + var div2 = $('#' + tab2.name); + + var state = obj_name + '-facet'; + var facet = $.bbq.getState(state, true) || 'search'; + var last_facet = window_hash_cache[state]; + + if (facet != last_facet) { + entity_setup(div2); + window_hash_cache[state] = facet; + + } else if (facet == 'search') { + state = obj_name + '-filter'; + var filter = $.bbq.getState(state, true); + var last_filter = window_hash_cache[state]; + if (filter == last_filter) return; + + entity_setup(div2); + window_hash_cache[state] = filter; + + } else if (facet == 'details') { + state = obj_name + '-pkey'; + var pkey = $.bbq.getState(state, true); + var last_pkey = window_hash_cache[state]; + if (pkey == last_pkey) return; + + entity_setup(div2); + window_hash_cache[state] = pkey; + + } else if (facet == 'associate') { + state = obj_name + '-enroll'; + var enroll = $.bbq.getState(state, true); + var last_enroll = window_hash_cache[state]; + if (enroll == last_enroll) return; + + entity_setup(div2); + window_hash_cache[state] = enroll; + } +} diff --git a/install/static/search.js b/install/static/search.js index 5e5be8fa0..59caf71f0 100644 --- a/install/static/search.js +++ b/install/static/search.js @@ -52,13 +52,19 @@ function search_create(obj_name, scl, container) jobj.children().last().click(find_on_click); div.append(''); - container.append('
'); - jobj = container.children().last(); - jobj.append(''); - jobj.append(''); - jobj.append(''); + var search_results = $('
', { + class: 'search-results' + }).appendTo(container); - var tr = jobj.find('tr'); + var search_table = $('', { + class: 'search-table' + }).appendTo(search_results); + + search_table.append(''); + search_table.append(''); + search_table.append(''); + + var tr = search_table.find('tr'); for (var i = 0; i < scl.length; ++i) { var c = scl[i]; search_insert_th(tr, obj_name, c[0], c[1], c[2]); @@ -85,23 +91,29 @@ function search_insert_th(jobj, obj_name, attr, name, render_call) jobj.append(th); } -function search_load(obj_name, criteria, on_win, on_fail) +function search_load(jobj, criteria, on_win, on_fail) { - function load_on_win(data, text_status, xhr) { + var obj_name = jobj.attr('id'); + + function search_on_success(data, text_status, xhr) { + if (on_win) + on_win(data, text_status, xhr); if (data.error) return; search_display(obj_name, data); - if (on_win) - on_win(data, text_status, xhr); }; - function load_on_fail(xhr, text_status, error_thrown) { + function search_on_error(xhr, text_status, error_thrown) { if (on_fail) on_fail(xhr, text_status, error_thrown); - }; + + var search_results = $('.search-results', jobj); + search_results.append('

Error: '+error_thrown.name+'

'); + search_results.append('

'+error_thrown.message+'

'); + } ipa_cmd( - 'find', [criteria], {all: true}, load_on_win, load_on_fail, obj_name + 'find', [criteria], {all: true}, search_on_success, search_on_error, obj_name ); } diff --git a/install/static/test/ipa_tests.html b/install/static/test/ipa_tests.html index 3031f2310..dfe2720a5 100644 --- a/install/static/test/ipa_tests.html +++ b/install/static/test/ipa_tests.html @@ -3,8 +3,11 @@ Core Test Suite + + + diff --git a/install/static/test/ipa_tests.js b/install/static/test/ipa_tests.js index 8dbdd62d5..8617a8439 100644 --- a/install/static/test/ipa_tests.js +++ b/install/static/test/ipa_tests.js @@ -92,3 +92,204 @@ test("Testing ipa_get_member_attribute().", function() { "ipa_get_member_attribute(null, \"group\")" ); }); + +test("Testing successful ipa_cmd().", function() { + + var method = 'method'; + var args = ['arg1', 'arg2', 'arg3']; + var options = { + opt1: 'val1', + opt2: 'val2', + opt3: 'val3' + }; + var object = 'object'; + + var success_handler_counter = 0; + var error_handler_counter = 0; + + function success_handler(data, status, xhr) { + success_handler_counter++; + } + + function error_handler(xhr, text_status, error_thrown) { + error_handler_counter++; + } + + var orig = $.ajax; + + var xhr = {}; + var text_status = null; + var error_thrown = {name:'ERROR', message:'An error has occured'}; + + var ajax_counter = 0; + + $.ajax = function(request) { + ajax_counter++; + + equals( + request.url, "data/"+object+"_"+method+".json", + "Checking request.url" + ); + + var data = JSON.parse(request.data); + + equals( + data.method, object+'_'+method, + "Checking method" + ); + + same( + data.params, [args, options], + "Checking parameters" + ); + + request.success(xhr, text_status, error_thrown); + }; + + ipa_cmd(method, args, options, success_handler, error_handler, object); + + equals( + ajax_counter, 1, + "Checking ajax invocation counter" + ); + + var dialog = ipa_dialog.parent('.ui-dialog'); + + ok( + !dialog.length, + "The dialog box is not created." + ); + + ok( + success_handler_counter == 1 && error_handler_counter == 0, + "Only the success handler is called." + ); + + $.ajax = orig; +}); + +test("Testing unsuccessful ipa_cmd().", function() { + + var method = 'method'; + var args = ['arg1', 'arg2', 'arg3']; + var options = { + opt1: 'val1', + opt2: 'val2', + opt3: 'val3' + }; + var object = 'object'; + + var success_handler_counter = 0; + var error_handler_counter = 0; + + function success_handler(data, status, xhr) { + success_handler_counter++; + } + + function error_handler(xhr, text_status, error_thrown) { + error_handler_counter++; + } + + var orig = $.ajax; + + var xhr = {}; + var text_status = null; + var error_thrown = {name:'ERROR', message:'An error has occured'}; + + var ajax_counter = 0; + + $.ajax = function(request) { + ajax_counter++; + + equals( + request.url, "data/"+object+"_"+method+".json", + "Checking request.url" + ); + + var data = JSON.parse(request.data); + + equals( + data.method, object+'_'+method, + "Checking method" + ); + + same( + data.params, [args, options], + "Checking parameters" + ); + + request.error(xhr, text_status, error_thrown); + }; + + ipa_cmd(method, args, options, success_handler, error_handler, object); + + var dialog = ipa_dialog.parent('.ui-dialog'); + + equals( + ajax_counter, 1, + "Checking ajax invocation counter" + ); + + ok( + dialog.length == 1 && ipa_dialog.dialog('isOpen'), + "The dialog box is created and open." + ); + + ok( + success_handler_counter == 0 && error_handler_counter == 0, + "Initially none of the handlers are called." + ); + + // search the retry button from the beginning + var retry = $('button', dialog).first(); + retry.trigger('click'); + + equals( + ajax_counter, 2, + "Checking ajax invocation counter" + ); + + ok( + success_handler_counter == 0 && error_handler_counter == 0, + "After 1st retry, none of the handlers are called." + ); + + // search the retry button from the beginning again because the dialog + // has been recreated + dialog = ipa_dialog.parent('.ui-dialog'); + retry = $('button', dialog).first(); + retry.trigger('click'); + + equals( + ajax_counter, 3, + "Checking ajax invocation counter" + ); + + ok( + success_handler_counter == 0 && error_handler_counter == 0, + "After 2nd retry, none of the handlers are called." + ); + + // search the cancel button from the beginning because the dialog has + // been recreated + dialog = ipa_dialog.parent('.ui-dialog'); + var cancel = $('button', dialog).first().next(); + cancel.trigger('click'); + + equals( + ajax_counter, 3, + "Checking ajax invocation counter" + ); + + ok( + !ipa_dialog.dialog('isOpen'), + "After cancel, the dialog box is closed." + ); + + ok( + success_handler_counter == 0 && error_handler_counter == 1, + "Only the error handler is called." + ); + + $.ajax = orig; +}); diff --git a/install/static/test/navigation_tests.js b/install/static/test/navigation_tests.js index 16b3ae925..4144e81a4 100644 --- a/install/static/test/navigation_tests.js +++ b/install/static/test/navigation_tests.js @@ -41,17 +41,17 @@ test("Testing nav_create().", function() { } ipa_objs= {}; - var navigation = $('