Prev: FAQ Topic - What is JScript? (2010-07-16)
Next: How to find if statusbar is hidden or visible in firefox?
From: David Mark on 15 Jul 2010 23:38 Sencha Touch is a bizarre mish-mash of ExtJS and a jQuery plug-in called JQTouch. It is advertised as the first "HTML5 framework" based on "standards" like HTML5 and CSS3. Of course, HTML5 is neither a standard nor widely implemented and the script eschews CSS3 for proprietary WebKit extensions. And the most bizarre thing is that very little of the script relates to HTML5. The script is touted as "cross-browser", despite the fact that it is admittedly dual-browser at best. It weighs 228K (minified) and several of its key features rely on UA-based browser sniffing. The rationalization for these unfortunate facts is that Android and iPhone/iPod/iPad devices account for 90% of the mobile market. It is unclear who conducted that study; but regardless, unlike in school, 90% is not an A-, particularly when the remaining 10% are left with non-functioning applications. The weight problem is dismissed with the usual marketing tactic of quoting sizes after GZIP compression (only 80K!) And feature detection is deemed "impossible" due to the fact that common features implemented in the two "supported" browsers vary in their behavior (apparently feature testing is outside of the authors' realm of understanding). Not much new here. It's the same "tired old arguments" that readers of this group have heard over and over. This shouldn't be surprising as library developers keep making the same tired old mistakes. And, of course, most newcomers to this group have failed to read each and every previous related post, so repetition is required. /* Copyright(c) 2010 Sencha Inc. licensing(a)sencha.com http://www.sencha.com/touchlicense */ Yes, they plan to charge money for this thing. // for old browsers window.undefined = window.undefined; First line and it is the usual rookie mistake. Note that this line runs in the global execution context, so - this - points to the global object. Why not use that instead of attempting to augment a host object? Likely because this line has been copied and pasted from a similarly ludicrous script that preceded it. There is no standard for the window object and, as a host object, it is explicitly allowed to throw an exception (or fail silently) in this case. This is not a minor quibble. IE can be configured to disallow host object expandos and nobody knows how many other browsers behave in similar fashion (perhaps even by default). The sum effect of this first line of code is to indicate that the authors don't really know what they are doing. Not only is "this" shorter than "window" (neither of which will be shortened on minification), but you have to wonder what "old browsers" this dual- browser framework is attempting to placate. As will become evident, no such browser stands a chance in hell of running this script, so the only explanation is cargo cult programming (i.e. they saw this line in another script and copied it without understanding the ramifications). /** * @class Ext There are no classes in ECMAScript implementations. * Ext core utilities and functions. Everything old is new again. :) * @singleton It goes without saying that there are no singletons either. */ Ext.setup = function(config) { if (Ext.isObject(config)) { Oh dear. We'll get to that one shortly. Suffice to say that there is no call for this (no pun intended). if (config.addMetaTags !== false) { var viewport = Ext.get(document.createElement('meta')), app = Ext.get(document.createElement('meta')), statusBar = Ext.get(document.createElement('meta')), startupScreen = Ext.get(document.createElement('link')), appIcon = Ext.get(document.createElement('link')); Okay. Five elements created. viewport.set({ name: 'viewport', content: 'width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0;' }); No call for this either. As we'll see, the "set" function is another botched attempt at setting attributes and/or properties. if (config.fullscreen !== false) { app.set({ name: 'apple-mobile-web-app-capable', content: 'yes' }); Ditto. if (Ext.isString(config.statusBarStyle)) { The isString function is also dubious. statusBar.set({ name: 'apple-mobile-web-app-status-bar-style', content: config.statusBarStyle }); } } if (Ext.isString(config.tabletStartupScreen) && Ext.platform.isTablet) { Ext.platform is populated largely by UA-based browser sniffing. startupScreen.set({ rel: 'apple-touch-startup-image', href: config.tabletStartupScreen }); } else if (Ext.isString(config.phoneStartupScreen) && Ext.platform.isPhone) { startupScreen.set({ rel: 'apple-touch-startup-image', href: config.phoneStartupScreen }); } if (config.icon) { config.phoneIcon = config.tabletIcon = config.icon; } This is a very odd design. Why have three properties? If they were going to allow specifying separate icons for what they "detect" as phones and tablets, then surely they shouldn't step on them without looking. var precomposed = (config.glossOnIcon == false) ? '- precomposed' : ''; if (Ext.isString(config.tabletIcon) && Ext.platform.isTablet) { Why didn't they just check config.icon? appIcon.set({ rel: 'apple-touch-icon' + precomposed, href: config.tabletIcon }); } else if (Ext.isString(config.phoneIcon) && Ext.platform.isPhone) { appIcon.set({ el: 'apple-touch-icon' + precomposed, href: config.phoneIcon }); } var head = Ext.get(document.getElementsByTagName('head') [0]); Why pass the result to Ext.get? Is there some ill-advised host object augmentation going on here? head.appendChild(viewport); if (app.getAttribute('name')) head.appendChild(app); if (statusBar.getAttribute('name')) head.appendChild(statusBar); if (appIcon.getAttribute('href')) head.appendChild(appIcon); if (startupScreen.getAttribute('href')) head.appendChild(startupScreen); } Nope. They used only standard DOM methods. Of course, there was no need to call getAttribute at all. They could have just checked the corresponding DOM properties; but more importantly, as seen above, they are the ones who set (or didn't set) these attributes in the first place. In other words, the logic takes the long way around, unnecessarily involving one of the least trustworthy methods in the history of browser scripting (getAttribute). And why did they create all of those elements in advance when some or all of them may not be appended at all? In short, the whole preceding mess could be re-factored in five minutes to save several function and host method calls, not to mention the creation of up to five elements. When choosing a browser script, one of the first questions should be who: wrote it and what is their relative level of proficiency? It's not a "personal smear" to make a judgment call at this point. The authors are obviously yet another batch of clueless neophytes (see also jQuery, Prototype, Dojo, YUI, ExtJS, MooTools, etc.) Suffice to say that leaning on a script written by such authors is going to lead to problems. Readers with even the slightest experience in cross- browser scripting can stamp this one "Avoid at all Costs" and move on at this point (if they haven't already). if (Ext.isArray(config.preloadImages)) { Oops. There is no way to write a reliable "isArray" function in JS. And as above, there is no reason to do anything more than a boolean type conversion here (as long as the documentation indicates that this property must be an array). Anything "truthy" that is not an array will result in a silent failure as written, which is the least helpful result (often described as "robustness" by library authors). for (var i = config.preloadImages.length - 1; i >= 0; i--) { How about:- for (var i = config.preloadImages.length; i--;) { Sure that's a minor quibble, but it is yet another glimpse into the authors' proficiency (or lack thereof). We are only a few dozen lines in and virtually every line needs to be rewritten (which should take *one* proficient JS developer about ten minutes). (new Image()).src = config.preloadImages[i]; }; } if (Ext.isFunction(config.onReady)) { Ext.onReady(config.onReady, config.scope || window); } As we'll see, the isFunction function is yet another dubious entry. And scope has *nothing* to do with the - this - despite the insistence of seemingly every "major" library author. In this case, it's not just a naming snafu as the ExtJS developers constantly refer to - this - as "scope" in their documentation and support forums. And if they don't understand why that is wrong, they haven't gotten past the first page of the manual. I wonder what they call scope. Bananas? Seriously, you have to wonder if these developers understand the language they are using at all. Ext.apply = function(object, config, defaults) { // no "this" reference for friendly out of scope calls There they go again. if (defaults) { What? No isObject call? :) Ext.apply(object, defaults); } if (object && config && typeof config == 'object') { for (var key in config) { Oops. Unfiltered for-in loops are a very bad idea (at least for scripts deployed on the Web). If this script is mashed up with something that augments Object.prototype (e.g. older versions of the unfortunately named Prototype library), all hell will break loose here (in the deepest part of their core). object[key] = config[key]; } } return object; }; Calling this function "apply" was a pretty silly idea as well (because of Function.prototype.apply). You've got to wonder if the authors are that out of touch (no pun intended) or simply trying to confuse beginners to keep them dependent on their dubious offerings. Both are unsavory prospects. Ext.apply(Ext, { userAgent: navigator.userAgent.toLowerCase(), Nellie bar the door. As has been known for a decade, referencing this deception device cannot lead to anything good. applyIf : function(object, config) { var property, undefined; There's no need to declare a local "undefined" identifier. if (object) { for (property in config) { Another unfiltered for-in. if (object[property] === undefined) { Just use the typeof operator. object[property] = config[property]; } } } return object; }, /** * Repaints the whole page. This fixes frequently encountered painting issues in mobile Safari. */ Their "fix" is nothing but a mystical incantation. Clearly their script has had problems with the one platform they seek to support, but rather than attempting to understand the cause of these problems, they've resorted to nonsense code that appears to "fix" the problem in whatever mobile devices they had handy to test with at the time of writing. repaint : function() { var mask = Ext.getBody().createChild({ cls: 'x-mask x-mask-transparent' }); Here they create and immediately discard a wrapper object for document.body. setTimeout(function() { mask.remove(); }, 0); ISTM that using 0 for the second argument to setTimeout is ill-advised (as is the implied global). }, /** * Generates unique ids. If the element already has an id, it is unchanged * @param {Mixed} el (optional) The element to generate an id for * @param {String} prefix (optional) Id prefix (defaults "ext- gen") * @return {String} The generated Id. */ id : function(el, prefix) { return (el = Ext.getDom(el) || {}).id = el.id || (prefix || "ext-gen") + (++Ext.idSeed); }, That's just plain ridiculous and falls under the category of "concise" code that is all the rage these days. How about something like this:- var id; if (typeof el == 'string') { // ID passed, find it el = document.getElementById(el); } if (el) { // Element exists if (el.id) { // Has an ID id = el.id; } else { // Does not have an ID, assign id = el.id = (prefix || "ext-gen") + (++Ext.idSeed); } } return id; // undefined if element not found It's not as if that will be any longer once white space and comments are removed. Will be a hell of a lot easier to maintain and debug as well (no phantom ID's generated). [skipped tired old Ext "class" related functions] urlEncode : function(o, pre){ var empty, buf = [], e = encodeURIComponent; Ext.iterate(o, function(key, item){ Why not just use a for-in loop? If the (omitted) comments are to be believed, then o is only allowed to be an Object object. The iterate function makes calls to isObject, isEmpty and isIterable in a futile attempt to support Object, Array and host objects with one function. empty = Ext.isEmpty(item); As we shall see, isEmpty is another unnecessary function and itself calls isArray (of all things). Ext.each(empty ? key : item, function(val){ buf.push('&', e(key), '=', (!Ext.isEmpty(val) && (val ! = key || !empty)) ? (Ext.isDate(val) ? Ext.encode(val).replace(/"/g, '') : e(val)) : ''); }); }); Hmmm. Skipping ahead, the Ext.encode function is defined as:- /** * Shorthand for {@link Ext.util.JSON#encode} * @param {Mixed} o The variable to encode * @return {String} The JSON string * @member Ext * @method encode */ Ext.encode = Ext.util.JSON.encode; And the "longhand" version is defined as:- /** * @class Ext.util.JSON How is an object literal a class? * Modified version of Douglas Crockford"s json.js that doesn"t * mess with the Object prototype * http://www.json.org/js.html * @singleton :) */ Ext.util.JSON = { encode : function(o) { return JSON.stringify(o); }, Now what does JSON have to do with urlencoding dates? So far none of this is anything close to professional code and much of it is positively senseless. Still no sign of HTML5 either. :) On to decoding:- /** * Takes an encoded URL and and converts it to an object. Example: * <pre><code> Ext.urlDecode("foo=1&bar=2"); // returns {foo: "1", bar: "2"} Ext.urlDecode("foo=1&bar=2&bar=3&bar=4", false); // returns {foo: "1", bar: ["2", "3", "4"]} </code></pre> Of course, none of those are URL's. * @param {String} string * @param {Boolean} overwrite (optional) Items of the same name will overwrite previous values instead of creating an an array (Defaults to false). * @return {Object} A literal with members A what?! */ urlDecode : function(string, overwrite){ if(Ext.isEmpty(string)){ return {}; } var obj = {}, pairs = string.split('&'), d = decodeURIComponent, name, value; Ext.each(pairs, function(pair) { pair = pair.split('='); name = d(pair[0]); value = d(pair[1]); obj[name] = overwrite || !obj[name] ? value : [].concat(obj[name]).concat(value); }); return obj; }, Interesting. No call to Ext.decode. I guess JSON is only related to encoding URL's. :) /** * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages. * @param {String} value The string to encode * @return {String} The encoded text */ htmlEncode : function(value){ return Ext.util.Format.htmlEncode(value); }, Unnecessary bloat and another unneeded function call. Odd choices for a mobile framework. /** * Appends content to the query string of a URL, handling logic for whether to place * a question mark or ampersand. * @param {String} url The URL to append to. * @param {String} s The content to append to the URL. * @return (String) The resulting URL */ urlAppend : function(url, s){ if(!Ext.isEmpty(s)){ Another half-dozen unneeded function calls. This would be bad enough in and of itself, but the functions in question are highly dubious as well. Try this:- if (s) { return url + (url.indexOf('?') === -1 ? '?' : '&') + s; No need for a strict comparison there. It's ham-fisted and makes future maintainers (and reviewers) have to pause and wonder why they did it. } return url; }, /** * Converts any iterable (numeric indices and a length property) into a true array There's no such thing as an "iterable" or a "true array". * Don't use this on strings. IE doesn't support "abc"[0] which this implementation depends on. Oh, I they needn't worry about IE. ;) * For strings, use this instead: "abc".match(/./g) => [a,b,c]; That's a syntax error (so don't use it). * @param {Iterable} the iterable object to be turned into a true Array. * @return (Array) array */ toArray : function(array, start, end){ return Array.prototype.slice.call(array, start || 0, end || array.length); That will blow up in IE < 9. }, /** * Iterates an array calling the supplied function. Not according to the next line. * @param {Array/NodeList/Mixed} array The array to be iterated. If this There is no way to reliably differentiate between Array and host objects (so don't design systems that hinge on making that work). And I don't know what a "Mixed" is supposed to be. each : function(array, fn, scope) { That's not scope! :) if (Ext.isEmpty(array, true)) { return 0; } Whatever. They'd have been much better off copying Array.prototype.forEach (then they could use that when available). if (!Ext.isIterable(array) || Ext.isPrimitive(array)) { array = [array]; } Now there's a bizarre disjunction. If it is not "iterable" or it is a primitive? You have to wonder what sort of "iterable" their code would identify as a primitive. It's positively Dojo-esque (meaning full of incomprehensible intertwined type-checking calls where none are needed). for (var i = 0, len = array.length; i < len; i++) { if (fn.call(scope || array[i], array[i], i, array) === false) { Why would they set - this - to array[i]? That's going to lead to some odd results. return i; }; } return true; }, iterate : function(obj, fn, scope){ if(Ext.isEmpty(obj)){ return; } if (Ext.isIterable(obj)) { Ext.each(obj, fn, scope); return; } else if(Ext.isObject(obj)) { for (var prop in obj) { if (obj.hasOwnProperty(prop)) { Finally, a filter! :) Inconsistent with the rest though. You've got to wonder if they simply forgot to filter the others. More likely this is simply a slap-dash patchwork of previous scripts (e.g. ExtJS, JQTouch). if (fn.call(scope || obj, prop, obj[prop], obj) === false) { return; }; } } } }, The indisputedly unreliable (and wholly unneeded) type-checking permeates everything. All hopes of cross-browser compatibility are lost (for no other reason than the authors were/are too green to design a system this complicated). * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc) * when this method is called to be successful. * @param {Mixed} el Apparently "Mixed" means "botched". In this case, the function is expected to discriminate between native and host objects. Such "overloading" strategies are highly ill-advised in JS, yet seemingly everything in this script relies on them. * @return HTMLElement */ getDom : function(el) { if (!el || !document) { return null; } Now when is document going to type-convert to false? return el.dom ? el.dom : (typeof el == 'string' ? document.getElementById(el) : el); }, /** * Returns the current document body as an {@link Ext.Element}. * @return Ext.Element The document body */ getBody : function(){ return Ext.get(document.body || false); }, More gobbledygook. It's as if every line is designed with madness in mind. The first line of Ext.get is:- if(!el){ return null; } ....and it's not as if document.body will be missing in either of the two browsers they aspire to support. Regardless, why not something like this:- var body = document.body; return body ? Ext.get(body) : null; Function calls are not free and a "mobile framework" needs to be as efficient as possible due to the limited resources available to mobile browsers. By the same token, property access costs and this thing uses them constantly when they could easily be avoided. /** * Returns the current HTML document object as an {@link Ext.Element}. * @return Ext.Element The document */ getDoc : function(){ return Ext.get(document); }, This is jQuery-itis. In other words, an API abstraction that makes no sense. Why would they wrap a document object in an Ext.Element object. After all, elements and documents are very different types of DOM nodes. What do the element-specific methods do for documents? And assuming there are any document-specific methods, what do they do for elements? It's mind-boggling. The only rationalization I've heard for such a bizarre design is that Web developers could be confused by more than one wrapper type. :) /** * This is shorthand reference to {@link Ext.ComponentMgr#get}. * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id} * @param {String} id The component {@link Ext.Component#id id} * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a * Class was found. */ getCmp : function(id){ return Ext.ComponentMgr.get(id); }, Another ridiculous waste of time and space. So far, everything that can go wrong has gone wrong. Literally. No doubt, this is somehow my fault. In other words, if I hadn't pointed out all of these problems then they wouldn't exist. :) /** * Returns the current orientation of the mobile device * @return {String} Either 'portrait' or 'landscape' */ getOrientation: function() { return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape'; }, Highly dubious and virtually never needed. In the same way that older libraries attempt to replace rock-solid mechanisms like IE's conditional comments with browser sniffing, this attempts to can CSS3 media queries. http://www.w3.org/TR/css3-mediaqueries/#orientation Of course, you can't sell what you can't can. :) /** * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference. * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval} is * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node * will be ignored if passed in.</p> * @param {HTMLElement} node The node to remove */ removeNode : function(n){ if (n && n.parentNode && n.tagName != 'BODY') { Checking for the body is ludicrous. If the calling application fouls up and passes the body, a silent failure will only obscure the mistake, making it harder for the developers to track it down. Same for checking the parentNode property. Ext.EventManager.removeAll(n); Almost certainly unneeded. If their EventManager thing actually creates circular references then they should fix that, not dance around the problem with this outdated Crockfordian strategy. And regardless, this script won't work with IE (the browser with the circular reference problem) anyway. n.parentNode.removeChild(n); delete Ext.cache[n.id]; } }, /** * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the * DOM (if applicable) and calling their destroy functions (if available). This method is primarily * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}, but any subclass of There are no classes in JS, so there can be no subclasses. * {@link Ext.util.Observable} can be passed in. Any number of elements and/or components can be * passed into this function in a single call as separate arguments. * @param {Mixed} arg1 An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy Again, you cannot reliably tell an Object from an Array object. Designing such systems in JS is like deliberately walking into a wall (over and over in the case of this script). * @param {Mixed} arg2 (optional) * @param {Mixed} etc... (optional) */ destroy : function() { var ln = arguments.length, i, arg; for (i = 0; i < ln; i++) { arg = arguments[i]; if (arg) { if (Ext.isArray(arg)) { this.destroy.apply(this, arg); } else if (Ext.isFunction(arg.destroy)) { arg.destroy(); } else if (arg.dom) { arg.remove(); } } } }, isIterable : function(v){ //check for array or arguments if(Ext.isArray(v) || v.callee){ return true; } The isArray function is unreliable and the presence of a "callee" property does not indicate anything close to what they are trying to determine (see their previous definition of an "iterable"). And you've got to wonder what sort of strange design would require passing the arguments object to a function like this. I mean, the only way a variable could reference an arguments objects is by explicitly setting it to reference an arguments object. //check for node list type if(/NodeList| HTMLCollection/.test(Object.prototype.toString.call(v))){ return true; } Not only is the RegExp botched, but host objects are allowed to return *anything* in response to a toString call. For example, this will fail in many versions of IE (and presumably at least some of its mobile derivations). Of course, this script will fall flat on its face in IE anyway, for no reason other than dubious design decisions. //NodeList has an item and length property //IXMLDOMNodeList has nextNode method, needs to be checked first. Suffice to say that an application that needs to pass an XML nodelist to this function is doomed from the start. Likely that includes the framework itself. return ((typeof v.nextNode != 'undefined' || v.item) && Ext.isNumber(v.length)); }, Ext.isNumber is another one of the botched (and unneeded) type- checking functions. So obviously, the "isIterable" function is an untenable (and unneeded) design. And each time the authors hit upon a host object that returned the "wrong" result they added another set of checks to make it "right", instead of realizing they were pissing in the wind from the start. Again, very Dojo-esque. /** * Utility method for validating that a value is numeric, returning the specified default value if it is not. * @param {Mixed} value Should be a number, but any type will be handled appropriately * @param {Number} defaultValue The value to return if the original value is non-numeric * @return {Number} Value, if numeric, else defaultValue */ num : function(v, defaultValue){ v = Number(Ext.isEmpty(v) || Ext.isArray(v) || typeof v == 'boolean' || (typeof v == 'string' && v.trim().length == 0) ? NaN : v); return isNaN(v) ? defaultValue : v; }, Another faulty design, rendered in typically bizarre and unreliable fashion (nothing that calls isArray can be considered reliable). And you really must wonder what sort of application would need such a function. /** * <p>Returns true if the passed value is empty.</p> * <p>The value is deemed to be empty if it is<div class="mdetail- params"><ul> * <li>null</li> * <li>undefined</li> * <li>an empty array</li> * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li> * </ul></div> * @param {Mixed} value The value to test * @param {Boolean} allowBlank (optional) true to allow empty strings (defaults to false) * @return {Boolean} */ isEmpty : function(v, allowBlank) { return v == null || ((Ext.isArray(v) && !v.length)) || (! allowBlank ? v === '' : false); }, Another call to isArray, which means the previous functions results in two calls to that unreliable function. There appears to be no real design work here, just a bunch of ill-advised functions tangled up in haphazard fashion. I can hear the chorus of "show me where it fails" now. :) And again, read the description and wonder what sort of strange application would need such a function. /** * Returns true if the passed value is a JavaScript array, otherwise false. * @param {Mixed} value The value to test * @return {Boolean} */ isArray : function(v) { return Object.prototype.toString.apply(v) === '[object Array]'; }, There it is. The "Miller Device" has been proven unreliable. /** * Returns true if the passed object is a JavaScript date object, otherwise false. * @param {Object} object The object to test * @return {Boolean} */ isDate : function(v) { return Object.prototype.toString.apply(v) === '[object Date]'; }, Same. /** * Returns true if the passed value is a JavaScript Object, otherwise false. * @param {Mixed} value The value to test * @return {Boolean} */ isObject : function(v) { return !!v && Object.prototype.toString.call(v) === '[object Object]'; }, The left part of the conjunction is clearly unneeded. And if they would just stop to think about their design, they'd realize that a typeof test would do just as well if they disallowed host objects as arguments to this function (which they should do anyway). /** * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean. * @param {Mixed} value The value to test * @return {Boolean} */ isPrimitive : function(v) { return Ext.isString(v) || Ext.isNumber(v) || Ext.isBoolean(v); }, This review could really be compressed to a string of "unneeded and botched" comments (a good ways through the core of this thing nothing is close to competently designed or implemented). Trying to learn anything about browser scripting from the code and/or documentation of scripts like this is hopeless as the authors mangle virtually every aspect of the language (e.g. scope, primitives, literals, etc.) Yet somehow many developers have come to the conclusion that they *must* abdicate all responsibility for browser scripting to "expert" efforts like this one. /** * Returns true if the passed value is a JavaScript Function, otherwise false. * @param {Mixed} value The value to test * @return {Boolean} */ isFunction : function(v) { return Object.prototype.toString.apply(v) === '[object Function]'; }, Unneeded and botched. The ambiguities that led to the advent of the (unreliable) "Miller Device" do not apply to Function objects (i.e. just use typeof). But I suppose the "designers" wanted to allow for discrimination between RegExp, Function and host objects, despite the fact that there is no practical application for such testing. Ironically, defenders of such scripts often pepper their retorts with references to the "real world". :) /** * Returns true if the passed value is a number. Returns false for non-finite numbers. * @param {Mixed} value The value to test * @return {Boolean} */ isNumber : function(v) { return Object.prototype.toString.apply(v) === '[object Number]' && isFinite(v); }, Unneeded and botched. Recall that isPrimitive calls this, yet it is clearly "designed" to allow Number objects to slip through. /** * Returns true if the passed value is a string. * @param {Mixed} value The value to test * @return {Boolean} */ isString : function(v) { return Object.prototype.toString.apply(v) === '[object String]'; }, Unneeded and botched (see previous). /**util * Returns true if the passed value is a boolean. * @param {Mixed} value The value to test * @return {Boolean} */ isBoolean : function(v) { return Object.prototype.toString.apply(v) === '[object Boolean]'; }, Clearly we are dealing with a cargo cult here, copying and pasting patterns of code without the slightest understanding of what it does. This one reduces to:- isBoolean : function(v) { return typeof v == 'boolean'; }, ....which is clearly a waste of a function call (why wouldn't you just use the typeof operator in the first place?) /** * Returns true if the passed value is an HTMLElement * @param {Mixed} value The value to test * @return {Boolean} */ isElement : function(v) { return !!v && v.tagName; }, Unneeded and botched. /** * Returns true if the passed value is not undefined. * @param {Mixed} value The value to test * @return {Boolean} */ isDefined : function(v){ return typeof v !== 'undefined'; }, Unneeded function with an unneeded strict comparison. /** * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>). * @type String */ Ext.SSL_SECURE_URL = Ext.isSecure && 'about:blank'; Huh? This is isSecure:- isSecure : /^https/i.test(window.location.protocol), ....so if the document is not served with the https protocol, the above will be false (which is obviously not a string). Ext.ns = Ext.namespace; Why? To save themselves keystrokes? Or perhaps this is a misguided attempt to shorten the download. Either way it makes no sense. I'm still looking for the first bit that indicates some semblance of sense. Though I know there are those who will dismiss this review in light of the really "cool" Solitaire demo. In other words, regardless of how bad the code is, if any sort of application can be demonstrated to "work" in at least two browsers, then arguments about the quality of the code can be dismissed out of hand (commonly phrased as "we write code for users!"). Ext.ns( 'Ext.util', 'Ext.data', 'Ext.list', 'Ext.form', 'Ext.menu', 'Ext.state', 'Ext.layout', 'Ext.app', 'Ext.ux', 'Ext.plugins', 'Ext.direct' ); Another waste of time and space, though I am sure some misguided programmers would consider this more "advanced" than simple assigning properties to the Ext object. I skipped the "namespace" function; here it is:- /** * Creates namespaces to be used for scoping variables and classes so that they are not global. Scoping?! This (repeated) criticism is not being petty. I run into programmers all the time who use the term "scope" to refer to all sorts of things that have nothing to do with scope. I have to wonder what word they would use if they actually wanted to discuss scope (it's not as if scope is an arcane subject in the context of JS). Scripts and comments like this are to blame. * Specifying the last node of a namespace implicitly creates all other nodes. Usage: * <pre><code> Ext.namespace('Company', 'Company.data'); Ext.namespace('Company.data'); // equivalent and preferable to above syntax Company.Widget = function() { ... } Company.data.CustomStore = function(config) { ... } </code></pre> * @param {String} namespace1 * @param {String} namespace2 * @param {String} etc * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created) * @method namespace */ namespace : function() { var ln = arguments.length, i, value, split, x, xln; for (i = 0; i < ln; i++) { value = arguments[i]; parts = value.split("."); object = window[parts[0]] = Object(window[parts[0]]); As mentioned, using the window object in lieu of a reference to the global object is a common rookie mistake. And why are they calling Object? I suppose it is in the name of "robustness" again, obscuring rather than exposing problems and making the debugging of applications more difficult. For example, assume that the global "Ext" variable unexpectedly has been set to the primitive value of 5. Passing that to Object will result in a Number object, not an Object object (clearly not what was desired). The application developer should be aware that something had gone horribly wrong prior to the conversion, so forcing the issue is counter-productive. for (x = 1, xln = parts.length; x < xln; x++) { object = object[parts[x]] = Object(object[parts[x]]); More of the same. } } return object; }, [Skip the usual ExtJS function wrapper suspects] /** * @class String * These functions are available on every String object. */ Ext.applyIf(String.prototype, { Good night. Never did get to the HTML5 bits. Having reviewed the script in its entirety for a client, I know they are few and far between. There's a dozen line function to create AUDIO elements (complete with browser sniffing and lacking any sort of feature detection), and another that does the same for VIDEO elements. I don't know if localStorage is part of the "official" HTML5 effort, but they've got a wrapper for that. Oh and another dozen lines dealing with geolocation (also fuzzy on whether that is part of HTML5). IIRC, that's about it. As for the "touch" part. They've attempted to smooth out the differences in the ontouch* event handling (between the two browsers they aspire to support) using more browser sniffing. Also IIRC, they attempt to synthesize the ongesture* events based on touches. Like the rest of it, it's a tangled up house of cards. Pretty lousy name too. Sencha doesn't exactly roll off the tongue, does it? :)
From: Garrett Smith on 16 Jul 2010 01:28 On 2010-07-15 08:38 PM, David Mark wrote: > Sencha Touch is a bizarre mish-mash of ExtJS and a jQuery plug-in The code Sencha, or "Ext Touch" is of such poor quality that it does not seem worth reviewing and the flaws start from line 1. [...] > Yes, they plan to charge money for this thing. > > // for old browsers > window.undefined = window.undefined; > Which of the two browsers is that for? > First line and it is the usual rookie mistake. Note that this line > runs in the global execution context, so - this - points to the global > object. Why not use that instead of attempting to augment a host > object? Likely because this line has been copied and pasted from a > similarly ludicrous script that preceded it. There is no standard for > the window object and, as a host object, it is explicitly allowed to > throw an exception (or fail silently) in this case. > > This is not a minor quibble. IE can be configured to disallow host > object expandos Can it? How so? Sencha is apparently not intended to be used on any version of Internet Explorer. By including sencha js on a page, errors are thrown in Internet Explorer due to calling document.addEventListener. // for browsers that support DOMContentLoaded document.addEventListener('DOMContentLoaded', fireReady, false); The comment is very misleading. The addEventListener method is not feature tested and despite what the comment says, it is not known if the browser supports DOMContentLoaded or not. Other parts of Sencha have code for Internet Explorer, so why did they design it to fail in IE on this line? It would be trivial to provide a fallback for IE on that. [...] You're pulling weeds out of a patch of poison ivy. The design is the problem and fixing those issues isn't gonna make much difference. > Pretty lousy name too. Sencha doesn't exactly roll off the tongue, > does it? :) No, if they want to sell, then they got the name right, and they sure did get investment money from it, so it is working. Trends say that a name should be Japanese or have an "x" or an "i" in it. The web page should use buzzwords. If intended for sale, then it can make outlandish claims (because the fools with money will not know or care), just so long as it has good-looking demos and graphics. The website of sencha.com, I read: | Web Standards | Sencha Touch is built with web standard HTML5, CSS3, and Javascript, | making it a very flexible mobile app framework HTML5 and CSS3 are not standards, they are what is known as W3C Working Drafts <URL: http://www.w3.org/2005/10/Process-20051014/tr#first-wd>. "Javascript" is not a web standard either. By using "built with," they further avoid making claims of adherence to either of those drafts allowing the reader to make a "very flexible" interpretation. I'll cover some of the more significant flaws in Sencha later. -- Garrett
From: RobG on 16 Jul 2010 02:16 On Jul 16, 1:38 pm, David Mark <dmark.cins...(a)gmail.com> wrote: > Sencha Touch is a bizarre mish-mash of ExtJS and a jQuery plug-in > called JQTouch. It is advertised as the first "HTML5 framework" based > on "standards" like HTML5 and CSS3. Of course, HTML5 is neither a > standard nor widely implemented Ah, but it's the next best buzz-word after "Web 2-point-oh" and "AJAX". > and the script eschews CSS3 for > proprietary WebKit extensions. And the most bizarre thing is that > very little of the script relates to HTML5. > > The script is touted as "cross-browser", despite the fact that it is > admittedly dual-browser at best. It weighs 228K (minified) The other excuse is that it can be stored on the client using the HTML5 cache. I think that cache will be abused by every library author or web developer who finds it easier to dump a huge resource on the client than write efficient code. Usually that means users of inefficient frameworks. > and > several of its key features rely on UA-based browser sniffing. There was a post in the iPhone GG recently from someone selling platform statistics lamenting that you can't tell which model of iPhone the browser is running in from the UA string. [...] > As for the "touch" part. They've attempted to smooth out the > differences in the ontouch* event handling (between the two browsers > they aspire to support) using more browser sniffing. Also IIRC, they > attempt to synthesize the ongesture* events based on touches. Like > the rest of it, it's a tangled up house of cards. I visited their site with an iPhone. Of the 3 demos listed, two didn't work at all. The one that did was designed for iPad and was almost useless on an iPhone. -- Rob
From: David Mark on 16 Jul 2010 02:31 On Jul 16, 1:28 am, Garrett Smith <dhtmlkitc...(a)gmail.com> wrote: > On 2010-07-15 08:38 PM, David Mark wrote: > > > Sencha Touch is a bizarre mish-mash of ExtJS and a jQuery plug-in > > The code Sencha, or "Ext Touch" is of such poor quality that it does not > seem worth reviewing and the flaws start from line 1. No question there, yet I predict that hordes of prospective mobile Web developers will flock to it based on outrageous lies (also known as marketing) and pretty graphics. > > [...] > > > Yes, they plan to charge money for this thing. > > > // for old browsers > > window.undefined = window.undefined; > > Which of the two browsers is that for? Well, neither of course. :) I already reported this (and several other issues) to the author (or the main author) of Sencha Touch. Predictably, just like the Dojo hacks, he referred to my suggestions as "optimizations" (rather than wholesale replacements of code so dubious it never should have been written in the first place). > > > First line and it is the usual rookie mistake. Note that this line > > runs in the global execution context, so - this - points to the global > > object. Why not use that instead of attempting to augment a host > > object? Likely because this line has been copied and pasted from a > > similarly ludicrous script that preceded it. There is no standard for > > the window object and, as a host object, it is explicitly allowed to > > throw an exception (or fail silently) in this case. > > > This is not a minor quibble. IE can be configured to disallow host > > object expandos > > Can it? How so? I think you know full well how so. document.expando = false; Perhaps you are quibbling with the use of the term "configure"? > > Sencha is apparently not intended to be used on any version of Internet > Explorer. Yes, apparently they don't "care" about IE, yet they mention IE in their comments. And if not for highly dubious design decisions, the script could easily run in any version of IE. Their justification is likely that they didn't want to bloat their "svelte" 80K of (compressed) code. Of course, My Library which runs in IE5-9 (and virtually everything else) and features fifty times the functionality is a little more than half that size (when compressed in the same way). > By including sencha js on a page, errors are thrown in > Internet Explorer due to calling document.addEventListener. Which makes it fairly useless for the Web (if you only consider mobile devices). > > // for browsers that support DOMContentLoaded > document.addEventListener('DOMContentLoaded', fireReady, false); > > The comment is very misleading. The addEventListener method is not > feature tested and despite what the comment says, it is not known if the > browser supports DOMContentLoaded or not. No question there. > Other parts of Sencha have > code for Internet Explorer, so why did they design it to fail in IE on > this line? It would be trivial to provide a fallback for IE on that. Because it is a hastily thrown together mish-mash dressed up as a breakthrough. > > [...] > > You're pulling weeds out of a patch of poison ivy. The design is the > problem and fixing those issues isn't gonna make much difference. I agree that the entire thing should be scrapped and the authors are clearly nowhere near proficient enough to write cross-browser scripts. They've got little more than some pretty graphics and bluster. But that never stopped jQuery (and many similar efforts) and it didn't seem to register on the Ajaxian editors (of course, nothing ever does). > > > Pretty lousy name too. Sencha doesn't exactly roll off the tongue, > > does it? :) > > No, if they want to sell, then they got the name right, How do you consider Sencha to be "right"? > and they sure > did get investment money from it, so it is working. I know all about the millions they got recently, but that's not exclusively because of this product. They have a whole host of bad scripts and a history of selling them. It will be interesting to see if they use any of that money to hire competent programmers. > > Trends say that a name should be Japanese or have an "x" or an "i" in > it. Who conducted that study? And I don't see an "x" or an "i" in Sencha. > The web page should use buzzwords. If intended for sale, then it can > make outlandish claims (because the fools with money will not know or > care), just so long as it has good-looking demos and graphics. Exactly. > > The website of sencha.com, I read: > > | Web Standards > | Sencha Touch is built with web standard HTML5, CSS3, and Javascript, > | making it a very flexible mobile app framework Lies, lies, lies. > > HTML5 and CSS3 are not standards, they are what is known as W3C Working > Drafts <URL:http://www.w3.org/2005/10/Process-20051014/tr#first-wd>. That's why I referred to them as "standards". > "Javascript" is not a web standard either. Sure as hell not the way they write it. :) > By using "built with," they > further avoid making claims of adherence to either of those drafts > allowing the reader to make a "very flexible" interpretation. If their brains are malleable enough. > > I'll cover some of the more significant flaws in Sencha later. It certainly doesn't get any better from where I left off. If anything, it gets much worse. I didn't even get to the most egregious browser sniffs. IIRC, there was a comment that indicated they would remove a sniff as soon as Android supported something or other. And where will that leave users of the older devices? We've gone from "aw, just get a new browser" to "aw, just get a new phone". I suppose if you are "lucky" you can just upgrade the device's OS and download another 228K of Sencha (for each site that "upgrades" the script of course). And at least for now, users of IE mobile, Blackberry and Opera Mini browsers are left with a pile of error messages. But the marketers spin that as covering a full 90% of the mobile Web (A-!).
From: David Mark on 16 Jul 2010 02:50
On Jul 16, 2:16 am, RobG <rg...(a)iinet.net.au> wrote: > On Jul 16, 1:38 pm, David Mark <dmark.cins...(a)gmail.com> wrote: > > > Sencha Touch is a bizarre mish-mash of ExtJS and a jQuery plug-in > > called JQTouch. It is advertised as the first "HTML5 framework" based > > on "standards" like HTML5 and CSS3. Of course, HTML5 is neither a > > standard nor widely implemented > > Ah, but it's the next best buzz-word after "Web 2-point-oh" and > "AJAX". When did investment bankers turn into easily misdirected morons? That's what I want to know. > > > and the script eschews CSS3 for > > proprietary WebKit extensions. And the most bizarre thing is that > > very little of the script relates to HTML5. > > > The script is touted as "cross-browser", despite the fact that it is > > admittedly dual-browser at best. It weighs 228K (minified) > > The other excuse is that it can be stored on the client using the > HTML5 cache. I'm sure they will try, even though HTML5 is still a pipe dream. From what I've heard the manifest stuff is a nightmare, particularly if your scripts are ever-changing (as this one certainly will be). It's another layer of complexity aimed at people who are overwhelmed to begin with. > I think that cache will be abused by every library author > or web developer who finds it easier to dump a huge resource on the > client than write efficient code. Usually that means users of > inefficient frameworks. Of course. They'll pass the "savings" on to the end-user. > > > and > > several of its key features rely on UA-based browser sniffing. > > There was a post in the iPhone GG recently from someone selling > platform statistics lamenting that you can't tell which model of > iPhone the browser is running in from the UA string. Right. The ExtJS (er Sencha) developers are already getting irritable about people reporting that their stuff doesn't work. Lots of replies in their support forums start along the lines of "We told you we only care about version XYZ!". Like many JS library developers before them, they create problems (usually through monumental incompetence) and then get pissed off when people report them. If only people would stop messing with their fantasies. :) > > [...] > > > As for the "touch" part. They've attempted to smooth out the > > differences in the ontouch* event handling (between the two browsers > > they aspire to support) using more browser sniffing. Also IIRC, they > > attempt to synthesize the ongesture* events based on touches. Like > > the rest of it, it's a tangled up house of cards. > > I visited their site with an iPhone. Of the 3 demos listed, two didn't > work at all. Well, as the song goes, one out of three ain't bad. Oh wait... :) > The one that did was designed for iPad and was almost > useless on an iPhone. > Yes, their "Universal UI" turned out to be nonsense. They've since changed their tune on it, describing it as an "experiment". I wonder what would their bankers would think about investing millions in such experiments? At this point, they've tried to support two (nearly identical) browsers and failed miserably, even with the crutch of browser sniffing. But if history (e.g. jQuery) is any indicator, their starry- eyed fans will dismiss any attempts to disrupt their delusions by rationalizing "aw, they'll get it right eventually" or "nobody's perfect". Save time, save money, write less, do more, etc. Sound familiar? :) |