diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js
index 22261f3e4..16f9a7068 100644
--- a/IPython/html/static/base/js/utils.js
+++ b/IPython/html/static/base/js/utils.js
@@ -563,6 +563,22 @@ define([
);
};
+ /** Error type for wrapped XHR errors. */
+ var XHR_ERROR = 'XhrError';
+
+ /**
+ * Wraps an AJAX error as an Error object.
+ */
+ var wrap_ajax_error = function (jqXHR, status, error) {
+ var wrapped_error = new Error(ajax_error_msg(jqXHR));
+ wrapped_error.name = XHR_ERROR;
+ // provide xhr response
+ wrapped_error.xhr = jqXHR;
+ wrapped_error.xhr_status = status;
+ wrapped_error.xhr_error = error;
+ return wrapped_error;
+ }
+
var utils = {
regex_split : regex_split,
uuid : uuid,
@@ -588,6 +604,8 @@ define([
ajax_error_msg : ajax_error_msg,
log_ajax_error : log_ajax_error,
requireCodeMirrorMode : requireCodeMirrorMode,
+ XHR_ERROR : XHR_ERROR,
+ wrap_ajax_error : wrap_ajax_error
};
// Backwards compatability.
diff --git a/IPython/html/static/notebook/js/menubar.js b/IPython/html/static/notebook/js/menubar.js
index 8e14bc2dc..01b09ae0e 100644
--- a/IPython/html/static/notebook/js/menubar.js
+++ b/IPython/html/static/notebook/js/menubar.js
@@ -91,23 +91,17 @@ define([
// notebook's path.
that.contents.new_notebook(that.notebook.notebook_path,
{
- success: function (data, status, xhr) {
+ success: function (data) {
window.open(
utils.url_join_encode(
that.base_url, 'notebooks',
data.path, data.name
), '_blank');
},
- error: function(xhr, status, error) {
- var msg;
- if (xhr.responseJSON && xhr.responseJSON.message) {
- msg = xhr.responseJSON.message;
- } else {
- msg = xhr.statusText;
- }
+ error: function(error) {
dialog.modal({
title : 'Creating Notebook Failed',
- body : "The error was: " + msg,
+ body : "The error was: " + error.message,
buttons : {'OK' : {'class' : 'btn-primary'}}
});
}
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index fdc1c615b..05d1c06aa 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -1923,7 +1923,7 @@ define([
this.contents.save_file(this.notebook_path, this.notebook_name, model, {
extra_settings: extra_settings,
success: $.proxy(this.save_notebook_success, this, start),
- error: function (xhr, status, error) {
+ error: function (error) {
that.events.trigger('notebook_save_failed.Notebook');
}
});
@@ -1935,10 +1935,8 @@ define([
* @method save_notebook_success
* @param {Integer} start Time when the save request start
* @param {Object} data JSON representation of a notebook
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
+ Notebook.prototype.save_notebook_success = function (start, data) {
this.set_dirty(false);
if (data.message) {
// save succeeded, but validation failed.
@@ -2078,7 +2076,7 @@ define([
var that = this;
this.contents.rename_file(this.notebook_path, this.notebook_name,
this.notebook_path, new_name, {
- success: function (json, status, xhr) {
+ success: function (json) {
var name = that.notebook_name = json.name;
that.session.rename_notebook(name, json.path);
that.events.trigger('notebook_renamed.Notebook', json);
@@ -2091,12 +2089,12 @@ define([
this.contents.delete_file(this.notebook_name, this.notebook_path);
};
- Notebook.prototype.rename_error = function (xhr, status, error) {
+ Notebook.prototype.rename_error = function (error) {
var that = this;
var dialog_body = $('
').append(
$("").text('This notebook name already exists.')
);
- this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
+ this.events.trigger('notebook_rename_failed.Notebook', error);
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
@@ -2146,10 +2144,8 @@ define([
*
* @method load_notebook_success
* @param {Object} data JSON representation of a notebook
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.load_notebook_success = function (data, status, xhr) {
+ Notebook.prototype.load_notebook_success = function (data) {
var failed;
try {
this.fromJSON(data);
@@ -2281,20 +2277,18 @@ define([
* Failure callback for loading a notebook from the server.
*
* @method load_notebook_error
- * @param {jqXHR} xhr jQuery Ajax object
- * @param {String} status Description of response status
- * @param {String} error HTTP error message
- */
- Notebook.prototype.load_notebook_error = function (xhr, status, error) {
- this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
- utils.log_ajax_error(xhr, status, error);
- var msg = $("");
- if (xhr.status === 400) {
- msg.text(utils.ajax_error_msg(xhr));
- } else if (xhr.status === 500) {
- msg.text("An unknown error occurred while loading this notebook. " +
+ * @param {Error} error
+ */
+ Notebook.prototype.load_notebook_error = function (error) {
+ this.events.trigger('notebook_load_failed.Notebook', error);
+ var msg;
+ if (error.name = utils.XHR_ERROR && error.xhr.status === 500) {
+ utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
+ msg = "An unknown error occurred while loading this notebook. " +
"This version can load notebook formats " +
- "v" + this.nbformat + " or earlier. See the server log for details.");
+ "v" + this.nbformat + " or earlier. See the server log for details.";
+ } else {
+ msg = error.message;
}
dialog.modal({
notebook: this,
@@ -2350,7 +2344,7 @@ define([
var that = this;
this.contents.list_checkpoints(this.notebook_path, this.notebook_name, {
success: $.proxy(this.list_checkpoints_success, this),
- error: function(xhr, status, error_msg) {
+ error: function(error) {
that.events.trigger('list_checkpoints_failed.Notebook');
}
});
@@ -2361,10 +2355,8 @@ define([
*
* @method list_checkpoint_success
* @param {Object} data JSON representation of a checkpoint
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
+ Notebook.prototype.list_checkpoints_success = function (data) {
data = $.parseJSON(data);
this.checkpoints = data;
if (data.length) {
@@ -2384,7 +2376,7 @@ define([
var that = this;
this.contents.create_checkpoint(this.notebook_path, this.notebook_name, {
success: $.proxy(this.create_checkpoint_success, this),
- error: function (xhr, status, error_msg) {
+ error: function (error) {
that.events.trigger('checkpoint_failed.Notebook');
}
});
@@ -2395,10 +2387,8 @@ define([
*
* @method create_checkpoint_success
* @param {Object} data JSON representation of a checkpoint
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
+ Notebook.prototype.create_checkpoint_success = function (data) {
data = $.parseJSON(data);
this.add_checkpoint(data);
this.events.trigger('checkpoint_created.Notebook', data);
@@ -2457,7 +2447,7 @@ define([
this.contents.restore_checkpoint(this.notebook_path, this.notebook_name,
checkpoint, {
success: $.proxy(this.create_checkpoint_success, this),
- error: function (xhr, status, error_msg) {
+ error: function (error) {
that.events.trigger('checkpoint_restore_failed.Notebook');
}
});
@@ -2467,11 +2457,8 @@ define([
* Success callback for restoring a notebook to a checkpoint.
*
* @method restore_checkpoint_success
- * @param {Object} data (ignored, should be empty)
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
+ Notebook.prototype.restore_checkpoint_success = function () {
this.events.trigger('checkpoint_restored.Notebook');
this.load_notebook(this.notebook_name, this.notebook_path);
};
@@ -2488,8 +2475,8 @@ define([
this.contents.delete_checkpoint(this.notebook_path, this.notebook_name,
checkpoint, {
success: $.proxy(this.create_checkpoint_success, this),
- error: function (xhr, status, error_msg) {
- that.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
+ error: function (error) {
+ that.events.trigger('checkpoint_delete_failed.Notebook', error);
}
});
};
@@ -2498,12 +2485,9 @@ define([
* Success callback for deleting a notebook checkpoint
*
* @method delete_checkpoint_success
- * @param {Object} data (ignored, should be empty)
- * @param {String} status Description of response status
- * @param {jqXHR} xhr jQuery Ajax object
*/
- Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
- this.events.trigger('checkpoint_deleted.Notebook', data);
+ Notebook.prototype.delete_checkpoint_success = function () {
+ this.events.trigger('checkpoint_deleted.Notebook');
this.load_notebook(this.notebook_name, this.notebook_path);
};
diff --git a/IPython/html/static/services/contents.js b/IPython/html/static/services/contents.js
index 7dda2a3f0..69190316a 100644
--- a/IPython/html/static/services/contents.js
+++ b/IPython/html/static/services/contents.js
@@ -21,12 +21,47 @@ define([
this.base_url = options.base_url;
};
+ /** Error type */
+ Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
+
+ Contents.DirectoryNotEmptyError = function() {
+ // Constructor
+ //
+ // An error representing the result of attempting to delete a non-empty
+ // directory.
+ this.message = 'A directory must be empty before being deleted.';
+ }
+ Contents.DirectoryNotEmptyError.prototype = new Error;
+ Contents.DirectoryNotEmptyError.prototype.name =
+ Contents.DIRECTORY_NOTE_EMPTY_ERROR;
+
+
Contents.prototype.api_url = function() {
var url_parts = [this.base_url, 'api/contents'].concat(
Array.prototype.slice.apply(arguments));
return utils.url_join_encode.apply(null, url_parts);
};
+ /**
+ * Creates a basic error handler that wraps a jqXHR error as an Error.
+ *
+ * Takes a callback that accepts an Error, and returns a callback that can
+ * be passed directly to $.ajax, which will wrap the error from jQuery
+ * as an Error, and pass that to the original callback.
+ *
+ * @method create_basic_error_handler
+ * @param{Function} callback
+ * @return{Function}
+ */
+ Contents.prototype.create_basic_error_handler = function(callback) {
+ if (!callback) {
+ return function(xhr, status, error) { };
+ }
+ return function(xhr, status, error) {
+ callback(utils.wrap_ajax_error(xhr, status, error));
+ };
+ }
+
/**
* File Functions (including notebook operations)
*/
@@ -50,7 +85,7 @@ define([
type : "GET",
dataType : "json",
success : options.success,
- error : options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
var url = this.api_url(path, name);
$.ajax(url, settings);
@@ -71,7 +106,7 @@ define([
type : "POST",
dataType : "json",
success : options.success || function() {},
- error : options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
$.ajax(this.api_url(path), settings);
};
@@ -86,8 +121,12 @@ define([
dataType : "json",
success : options.success || function() {},
error : function(xhr, status, error) {
- utils.log_ajax_error(xhr, status, error);
- error(xhr, status, error);
+ // TODO: update IPEP27 to specify errors more precisely, so
+ // that error types can be detected here with certainty.
+ if (xhr.status === 400) {
+ error(new Contents.DirectoryNotEmptyError());
+ }
+ error(utils.wrap_ajax_error(xhr, status, error));
}
};
var url = this.api_url(path, name);
@@ -103,8 +142,8 @@ define([
data : JSON.stringify(data),
dataType: "json",
contentType: 'application/json',
- success : options.success || function() {},
- error : options.error || function() {}
+ success : options.success || function() {},
+ error : this.create_basic_error_handler(options.error)
};
var url = this.api_url(path, name);
$.ajax(url, settings);
@@ -119,7 +158,7 @@ define([
data : JSON.stringify(model),
contentType: 'application/json',
success : options.success || function() {},
- error : options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
if (options.extra_settings) {
$.extend(settings, options.extra_settings);
@@ -137,7 +176,7 @@ define([
var settings = {
type : "POST",
success: options.success || function() {},
- error: options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
@@ -147,7 +186,7 @@ define([
var settings = {
type : "GET",
success: options.success,
- error: options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
@@ -157,17 +196,17 @@ define([
var settings = {
type : "POST",
success: options.success || function() {},
- error: options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
- Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
+ Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
var settings = {
type : "DELETE",
success: options.success || function() {},
- error: options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
@@ -199,7 +238,7 @@ define([
type : "GET",
dataType : "json",
success : options.success,
- error : options.error || function() {}
+ error : this.create_basic_error_handler(options.error)
};
$.ajax(this.api_url(path), settings);
@@ -209,4 +248,4 @@ define([
IPython.Contents = Contents;
return {'Contents': Contents};
-});
+});
diff --git a/IPython/html/static/tree/js/main.js b/IPython/html/static/tree/js/main.js
index 1f53a7f77..73ed8b18e 100644
--- a/IPython/html/static/tree/js/main.js
+++ b/IPython/html/static/tree/js/main.js
@@ -62,23 +62,17 @@ require([
$('#new_notebook').click(function (e) {
contents.new_notebook(common_options.notebook_path,
{
- success: function (data, status, xhr) {
+ success: function (data) {
window.open(
utils.url_join_encode(
common_options.base_url, 'notebooks',
data.path, data.name
), '_blank');
},
- error: function(xhr, status, error) {
- var msg;
- if (xhr.responseJSON && xhr.responseJSON.message) {
- msg = xhr.responseJSON.message;
- } else {
- msg = xhr.statusText;
- }
+ error: function(error) {
dialog.modal({
title : 'Creating Notebook Failed',
- body : "The error was: " + msg,
+ body : "The error was: " + error.message,
buttons : {'OK' : {'class' : 'btn-primary'}}
});
}
diff --git a/IPython/html/static/tree/js/notebooklist.js b/IPython/html/static/tree/js/notebooklist.js
index 2e62f40b5..a044025e2 100644
--- a/IPython/html/static/tree/js/notebooklist.js
+++ b/IPython/html/static/tree/js/notebooklist.js
@@ -144,9 +144,8 @@ define([
var that = this
this.contents.list_contents(that.notebook_path, {
success: $.proxy(this.draw_notebook_list, this),
- error: function(xhr, status, error) {
- utils.log_ajax_error(xhr, status, error);
- that.draw_notebook_list([], "Error connecting to server.");
+ error: function(error) {
+ that.draw_notebook_list([], "Server error: " + error.message);
}
});
};