Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / core / src / lang / Error.js
1 /**
2  * @class Ext.Error
3  * @private
4  * @extends Error
5
6 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
7 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
9 the error was raised. It also includes logic to automatically log the eroor to the console, if available,
10 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
11 execution will halt.
12
13 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
14 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
15 although in a real application it's usually a better idea to override the handling function and perform
16 logging or some other method of reporting the errors in a way that is meaningful to the application.
17
18 At its simplest you can simply raise an error as a simple string from within any code:
19
20 #Example usage:#
21
22     Ext.Error.raise('Something bad happened!');
23
24 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
25 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
26 additional metadata about the error being raised.  The {@link #raise} method can also take a config object.
27 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
28 added to the error object and, if the console is available, logged to the console for inspection.
29
30 #Example usage:#
31
32     Ext.define('Ext.Foo', {
33         doSomething: function(option){
34             if (someCondition === false) {
35                 Ext.Error.raise({
36                     msg: 'You cannot do that!',
37                     option: option,   // whatever was passed into the method
38                     'error code': 100 // other arbitrary info
39                 });
40             }
41         }
42     });
43
44 If a console is available (that supports the `console.dir` function) you'll see console output like:
45
46     An error was raised with the following data:
47     option:         Object { foo: "bar"}
48         foo:        "bar"
49     error code:     100
50     msg:            "You cannot do that!"
51     sourceClass:   "Ext.Foo"
52     sourceMethod:  "doSomething"
53
54     uncaught exception: You cannot do that!
55
56 As you can see, the error will report exactly where it was raised and will include as much information as the
57 raising code can usefully provide.
58
59 If you want to handle all application errors globally you can simply override the static {@link handle} method
60 and provide whatever handling logic you need. If the method returns true then the error is considered handled
61 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
62
63 #Example usage:#
64
65     Ext.Error.handle = function(err) {
66         if (err.someProperty == 'NotReallyAnError') {
67             // maybe log something to the application here if applicable
68             return true;
69         }
70         // any non-true return value (including none) will cause the error to be thrown
71     }
72
73  * Create a new Error object
74  * @param {Object} config The config object
75  * @markdown
76  * @author Brian Moeskau <brian@sencha.com>
77  * @docauthor Brian Moeskau <brian@sencha.com>
78  */
79 Ext.Error = Ext.extend(Error, {
80     statics: {
81         /**
82          * @property ignore
83 Static flag that can be used to globally disable error reporting to the browser if set to true
84 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
85 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
86 be preferable to supply a custom error {@link #handle handling} function instead.
87
88 #Example usage:#
89
90     Ext.Error.ignore = true;
91
92          * @markdown
93          * @static
94          */
95         ignore: false,
96
97         /**
98          * @property notify
99 Static flag that can be used to globally control error notification to the user. Unlike
100 Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
101 set to false to disable the alert notification (default is true for IE6 and IE7).
102
103 Only the first error will generate an alert. Internally this flag is set to false when the
104 first error occurs prior to displaying the alert.
105
106 This flag is not used in a release build.
107
108 #Example usage:#
109
110     Ext.Error.notify = false;
111
112          * @markdown
113          * @static
114          */
115         //notify: Ext.isIE6 || Ext.isIE7,
116
117         /**
118 Raise an error that can include additional data and supports automatic console logging if available.
119 You can pass a string error message or an object with the `msg` attribute which will be used as the
120 error message. The object can contain any other name-value attributes (or objects) to be logged
121 along with the error.
122
123 Note that after displaying the error message a JavaScript error will ultimately be thrown so that
124 execution will halt.
125
126 #Example usage:#
127
128     Ext.Error.raise('A simple string error message');
129
130     // or...
131
132     Ext.define('Ext.Foo', {
133         doSomething: function(option){
134             if (someCondition === false) {
135                 Ext.Error.raise({
136                     msg: 'You cannot do that!',
137                     option: option,   // whatever was passed into the method
138                     'error code': 100 // other arbitrary info
139                 });
140             }
141         }
142     });
143          * @param {String/Object} err The error message string, or an object containing the
144          * attribute "msg" that will be used as the error message. Any other data included in
145          * the object will also be logged to the browser console, if available.
146          * @static
147          * @markdown
148          */
149         raise: function(err){
150             err = err || {};
151             if (Ext.isString(err)) {
152                 err = { msg: err };
153             }
154
155             var method = this.raise.caller;
156
157             if (method) {
158                 if (method.$name) {
159                     err.sourceMethod = method.$name;
160                 }
161                 if (method.$owner) {
162                     err.sourceClass = method.$owner.$className;
163                 }
164             }
165
166             if (Ext.Error.handle(err) !== true) {
167                 var msg = Ext.Error.prototype.toString.call(err);
168
169                 Ext.log({
170                     msg: msg,
171                     level: 'error',
172                     dump: err,
173                     stack: true
174                 });
175
176                 throw new Ext.Error(err);
177             }
178         },
179
180         /**
181 Globally handle any Ext errors that may be raised, optionally providing custom logic to
182 handle different errors individually. Return true from the function to bypass throwing the
183 error to the browser, otherwise the error will be thrown and execution will halt.
184
185 #Example usage:#
186
187     Ext.Error.handle = function(err) {
188         if (err.someProperty == 'NotReallyAnError') {
189             // maybe log something to the application here if applicable
190             return true;
191         }
192         // any non-true return value (including none) will cause the error to be thrown
193     }
194
195          * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
196          * that were originally raised with it, plus properties about the method and class from which
197          * the error originated (if raised from a class that uses the Ext 4 class system).
198          * @static
199          * @markdown
200          */
201         handle: function(){
202             return Ext.Error.ignore;
203         }
204     },
205
206     // This is the standard property that is the name of the constructor.
207     name: 'Ext.Error',
208
209     /**
210      * @constructor
211      * @param {String/Object} config The error message string, or an object containing the
212      * attribute "msg" that will be used as the error message. Any other data included in
213      * the object will be applied to the error instance and logged to the browser console, if available.
214      */
215     constructor: function(config){
216         if (Ext.isString(config)) {
217             config = { msg: config };
218         }
219
220         var me = this;
221
222         Ext.apply(me, config);
223
224         me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
225         // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
226     },
227
228     /**
229 Provides a custom string representation of the error object. This is an override of the base JavaScript
230 `Object.toString` method, which is useful so that when logged to the browser console, an error object will
231 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
232
233 The default implementation will include the error message along with the raising class and method, if available,
234 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
235 a particular error instance, if you want to provide a custom description that will show up in the console.
236      * @markdown
237      * @return {String} The error message. If raised from within the Ext 4 class system, the error message
238      * will also include the raising class and method names, if available.
239      */
240     toString: function(){
241         var me = this,
242             className = me.className ? me.className  : '',
243             methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
244             msg = me.msg || '(No description provided)';
245
246         return className + methodName + msg;
247     }
248 });
249
250 /*
251  * This mechanism is used to notify the user of the first error encountered on the page. This
252  * was previously internal to Ext.Error.raise and is a desirable feature since errors often
253  * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
254  * where exceptions are handled in a try/catch.
255  */
256 //<debug>
257 (function () {
258     var prevOnError, timer, errors = 0,
259         extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
260         win = Ext.global;
261
262     if (typeof window === 'undefined') {
263         return; // build system or some such environment...
264     }
265
266     // This method is called to notify the user of the current error status.
267     function notify () {
268         var counters = Ext.log.counters,
269             supports = Ext.supports,
270             hasOnError = supports && supports.WindowOnError; // TODO - timing
271
272         // Put log counters to the status bar (for most browsers):
273         if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
274             var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
275                         'Info:',counters.info, 'Log:',counters.log].join(' ');
276             if (errors) {
277                 msg = '*** Errors: ' + errors + ' - ' + msg;
278             } else if (counters.error) {
279                 msg = '*** ' + msg;
280             }
281             win.status = msg;
282         }
283
284         // Display an alert on the first error:
285         if (!Ext.isDefined(Ext.Error.notify)) {
286             Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
287         }
288         if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
289             Ext.Error.notify = false;
290
291             if (timer) {
292                 win.clearInterval(timer); // ticks can queue up so stop...
293                 timer = null;
294             }
295
296             alert('Unhandled error on page: See console or log');
297             poll();
298         }
299     }
300
301     // Sets up polling loop. This is the only way to know about errors in some browsers
302     // (Opera/Safari) and is the only way to update the status bar for warnings and other
303     // non-errors.
304     function poll () {
305         timer = win.setInterval(notify, 1000);
306     }
307
308     // window.onerror is ideal (esp in IE) because you get full context. This is harmless
309     // otherwise (never called) which is good because you cannot feature detect it.
310     prevOnError = win.onerror || Ext.emptyFn;
311     win.onerror = function (message) {
312         ++errors;
313
314         if (!extraordinarilyBad.test(message)) {
315             // too much recursion + our alert right now = crash IE
316             // our polling loop will pick it up even if we don't alert now
317             notify();
318         }
319
320         return prevOnError.apply(this, arguments);
321     };
322     poll();
323 })();
324 //</debug>