diff --git a/CHANGELOG b/CHANGELOG index 43d200f..e5c087c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add deprecation extension. [Tobie Langel] + *1.6.0.2* (January 25, 2008) * Update the list of officially supported browsers in the README. [sam] diff --git a/ext/deprecation/deprecation.js b/ext/deprecation/deprecation.js new file mode 100644 index 0000000..3e625e3 --- /dev/null +++ b/ext/deprecation/deprecation.js @@ -0,0 +1,373 @@ +/* Prototype Deprecation Notifier for Prototype 1.6.0.2 + * (c) 2008 Tobie Langel + * + * Prototype Deprecation Notifier is freely distributable under the same + * terms as Prototype (MIT-style license). + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + * Include this file right below prototype.js. All warning message + * will be logged to the console. + * + * Note: You can turn off deprecation messages (and log only removal and + * modification messages) by specifying: + * DeprecationNotifier.logDeprecation = false; + * + * THIS SCRIPT WORKS IN FIREFOX ONLY + *--------------------------------------------------------------------------*/ + +var DeprecationNotifier = { + logDeprecation: true, + MessageTemplate: new Template('Prototype #{type} Warning: #{message}\n#{stack}'), + Regexp: new RegExp("@" + window.location.protocol + ".*?\\d+\\n", "g"), + + init: function(deprecatedMethods) { + if (!Prototype.Browser.Gecko) return; + deprecatedMethods.each(function(d) { + var condition = d.condition, + type = d.type || 'deprecation', + message = d.message, + namespace = d.namespace, + method = d.methodName; + + namespace[method] = (namespace[method] || function() {}).wrap(function(proceed) { + var args = $A(arguments).splice(1); + if (!condition || condition.apply(this, args)) + DeprecationNotifier.notify(message, type); + return proceed.apply(proceed, args); + }); + }); + Element.addMethods(); + }, + + notify: function(message, type) { + if (type == 'deprecation' && !DeprecationNotifier.logDeprecation) + return false; + this.log(this.MessageTemplate.evaluate({ + message: message, + stack: this.getStack(), + type: type.capitalize() + })); + return true; + }, + + getStack: function() { + try { + throw new Error("stack"); + } catch(e) { + return e.stack.match(this.Regexp).reject(function(path) { + return /(prototype|unittest|deprecation)\.js/.test(path) + }).join("\n"); + } + }, + + log: function(message) { + console.log(message) + } +}; + +DeprecationNotifier.init([ + { + methodName: 'display', + namespace: Toggle, + message: 'Toggle.display has been deprecated, please use Element.toggle instead.' + }, + + { + methodName: 'show', + namespace: Element.Methods, + message: 'Passing an arbitrary number of elements to Element.show is no longer supported.\n' + + 'Use [id_1, id_2, ...].each(Element.show) or $(id_1, id_2, ...).invoke("show") instead.', + type: 'removal', + condition: function() { return arguments.length > 1 && !Object.isNumber(arguments[1]) } + }, + + { + methodName: 'hide', + namespace: Element.Methods, + message: 'Passing an arbitrary number of elements to Element.hide is no longer supported.\n' + + 'Use [id_1, id_2, ...].each(Element.hide) or $(id_1, id_2, ...).invoke("hide") instead.', + type: 'removal', + condition: function() { return arguments.length > 1 && !Object.isNumber(arguments[1]) } + }, + + { + methodName: 'toggle', + namespace: Element.Methods, + message: 'Passing an arbitrary number of elements to Element.toggle is no longer supported.\n' + + 'Use [id_1, id_2, ...].each(Element.toggle) or $(id_1, id_2, ...).invoke("toggle") instead.', + type: 'removal', + condition: function() { return arguments.length > 1 && !Object.isNumber(arguments[1]) } + }, + + { + methodName: 'clear', + namespace: Form.Element.Methods, + message: 'Passing an arbitrary number of elements to Field.clear is no longer supported.\n' + + 'Use [id_1, id_2, ...].each(Form.Element.clear) or $(id_1, id_2, ...).invoke("clear") instead.', + type: 'removal', + condition: function() { return arguments.length > 1 && !Object.isNumber(arguments[1]) } + }, + + { + methodName: 'present', + namespace: Form.Element.Methods, + message: 'Passing an arbitrary number of elements to Field.present is no longer supported.\n' + + 'Use [id_1, id_2, ...].each(Form.Element.present) or $(id_1, id_2, ...).invoke("present") instead.', + type: 'removal', + condition: function() { return arguments.length > 1 && !Object.isNumber(arguments[1]) } + }, + + { + methodName: 'childOf', + namespace: Element.Methods, + message: 'Element#childOf has been deprecated, please use Element#descendantOf instead.' + }, + + { + methodName: 'Before', + namespace: Insertion, + message: 'Insertion.Before has been deprecated, please use Element#insert instead.' + }, + + { + methodName: 'Top', + namespace: Insertion, + message: 'Insertion.Top has been deprecated, please use Element#insert instead.' + }, + + { + methodName: 'Bottom', + namespace: Insertion, + message: 'Insertion.Bottom has been deprecated, please use Element#insert instead.' + }, + + { + methodName: 'After', + namespace: Insertion, + message: 'Insertion.After has been deprecated, please use Element#insert instead.' + }, + + { + methodName: 'prepare', + namespace: Position, + message: 'Position.prepare has been deprecated.' + }, + + { + methodName: 'within', + namespace: Position, + message: 'Position.within has been deprecated.' + }, + + { + methodName: 'withinIncludingScrolloffsets', + namespace: Position, + message: 'Position.withinIncludingScrolloffsets has been deprecated.' + }, + + { + methodName: 'overlap', + namespace: Position, + message: 'Position.overlap has been deprecated.' + }, + + { + methodName: 'cumulativeOffset', + namespace: Position, + message: 'Position.cumulativeOffset has been deprecated, please use Element#cumulativeOffset instead.' + }, + + { + methodName: 'positionedOffset', + namespace: Position, + message: 'Position.positionedOffset has been deprecated, please use Element#positionedOffset instead.' + }, + + { + methodName: 'absolutize', + namespace: Position, + message: 'Position.absolutize has been deprecated, please use Element#absolutize instead.' + }, + + { + methodName: 'relativize', + namespace: Position, + message: 'Position.relativize has been deprecated, please use Element#relativize instead.' + }, + + { + methodName: 'realOffset', + namespace: Position, + message: 'Position.realOffset has been deprecated, please use Element#cumulativeScrollOffset instead.' + }, + + { + methodName: 'offsetParent', + namespace: Position, + message: 'Position.offsetParent has been deprecated, please use Element#getOffsetParent instead.' + }, + + { + methodName: 'page', + namespace: Position, + message: 'Position.page has been deprecated, please use Element#viewportOffset instead.' + }, + + { + methodName: 'clone', + namespace: Position, + message: 'Position.clone has been deprecated, please use Element#clonePosition instead.' + }, + + { + methodName: 'initialize', + namespace: Element.ClassNames.prototype, + message: 'Element.ClassNames has been deprecated.' + }, + + { + methodName: 'classNames', + namespace: Element.Methods, + message: 'Element#classNames has been deprecated.\n' + + 'If you need to access CSS class names as an array, try: $w(element.classname).' + }, + + { + methodName: 'setStyle', + namespace: Element.Methods, + message: 'Use of uncamelized style-property names is no longer supported.\n' + + 'Use either camelized style-property names or a regular CSS string instead (see online documentation).', + type: 'removal', + condition: function(element, style) { + return !Object.isString(style) && Object.keys(style).join('').include('-'); + } + }, + + { + methodName: 'getElementsByClassName', + namespace: document, + message: 'document.getElementsByClassName has been deprecated, please use $$ instead.' + }, + + { + methodName: 'getElementsByClassName', + namespace: Element.Methods, + message: 'Element#getElementsByClassName has been deprecated, please use Element#select instead.' + }, + + { + methodName: 'immediateDescendants', + namespace: Element.Methods, + message: 'Element#immediateDescendants has been deprecated, please use Element#childElements instead.' + }, + + { + methodName: 'getElementsBySelector', + namespace: Element.Methods, + message: 'Element#getElementsBySelector has been deprecated, please use Element#select instead.' + }, + + { + methodName: 'toQueryString', + namespace: Hash, + message: 'Hash.toQueryString has been deprecated.\n' + + 'Use the instance method Hash#toQueryString or Object.toQueryString instead.' + }, + + { + methodName: 'remove', + namespace: Hash.prototype, + message: 'Hash#remove is no longer supported, use Hash#unset instead.\n' + + 'Please note that Hash#unset only accepts one argument.', + type: 'removal' + }, + + { + methodName: 'merge', + namespace: Hash.prototype, + message: 'Hash#merge is no longer destructive: it operates on a clone of the Hash instance.\n' + + 'If you need a destructive merge, use Hash#update instead.', + type: 'modification' + }, + + { + methodName: 'unloadCache', + namespace: Event, + message: 'Event.unloadCache has been deprecated.' + } +]); + +// Special casing for Hash. + +(function() { + if (!Prototype.Browser.Gecko) return; + + var __properties = Object.keys(Hash.prototype).concat(['_object', '__properties']); + + var messages = { + setting: new Template("Directly setting a property on an instance of Hash is no longer supported.\n" + + "Please use Hash#set('#{property}', #{value}) instead."), + getting: new Template("Directly accessing a property of an instance of Hash is no longer supported.\n" + + "Please use Hash#get('#{property}') instead.") + }; + + function notify(property, value) { + var message = messages[arguments.length == 1 ? 'getting' : 'setting'].evaluate({ + property: property, + value: Object.inspect(value) + }); + DeprecationNotifier.notify(message, 'removal'); + } + + function defineSetters(obj, prop) { + if (obj.__properties.include(prop)) return; + obj.__properties.push(prop); + obj.__defineGetter__(prop, function() { + checkProperties(this); + notify(prop); + }); + obj.__defineSetter__(prop, function(value) { + checkProperties(this); + notify(prop, value); + }); + } + + function checkProperties(hash) { + var current = Object.keys(hash); + if (current.length == hash.__properties.length) + return; + current.each(function(prop) { + if (hash.__properties.include(prop)) return; + notify(prop, hash[prop]); + defineSetters(hash, prop); + }); + } + + Hash.prototype.set = Hash.prototype.set.wrap(function(proceed, property, value) { + defineSetters(this, property); + return proceed(property, value); + }); + + $w('merge update').each(function(name) { + Hash.prototype[name] = Hash.prototype[name].wrap(function(proceed, object) { + for (var prop in object) defineSetters(this, prop); + return proceed(object); + }); + }); + + $H(Hash.prototype).each(function(method) { + var key = method.key; + if (!Object.isFunction(method.value) || key == 'initialize') return; + Hash.prototype[key] = Hash.prototype[key].wrap(function(proceed) { + checkProperties(this); + return proceed.apply(proceed, $A(arguments).splice(1)); + }); + }); + + Hash.prototype.initialize = Hash.prototype.initialize.wrap(function(proceed, object) { + this.__properties = __properties.clone(); + for (var prop in object) defineSetters(this, prop); + proceed(object); + }); +})(); + \ No newline at end of file diff --git a/ext/deprecation/deprecation_test.html b/ext/deprecation/deprecation_test.html new file mode 100644 index 0000000..aa7a0cd --- /dev/null +++ b/ext/deprecation/deprecation_test.html @@ -0,0 +1,281 @@ + + +
++ Test of deprecation.js +
+ +