Documentation

JQuery 1.2 Roadmap

From jQuery JavaScript Library

Jump to: navigation, search

The following are all proposed features for jQuery 1.2 (it's very likely that many of the features here won't make the final cut - but this is what's currently desired to arrive in 1.2).

Contents

Selectors

Move from [@attr] to [attr]

Supporting the XPath-like [@attr] syntax is rather silly at this point, considering the number of other JavaScript libraries that support CSS selectors. We should be supporting the standard syntax for this selection.

However, we could get away with this, without much pain, by simply ignoring the @ character if it exists (allowing for some degree of backwards compatibility).

An implementation would allow for results that look like:

$("img[src]")
$("a[href^=google]")

 :has()

Currently, we support the XPath-like [selector] syntax. This should be removed in favor of better CSS selector support ([attr], [attr=value], etc.). Instead, this functionality (while still useful) should be delegated to a new :has(selector) selector.

An implementation would work something like this (finding all the divs that have a paragraph inside of them):

$("div:has(p)")

Here's a possible implementation:

jQuery.expr[":"].has = "jQuery.find(m[3],a).length";

DOM

Form/Field Serialization

Our current means of form/field serialization are very poor. We currently provide two methods:

  • .val() (returns the DOM-specified value of the element)
  • .serialize() (returns a data structure for use by Ajax methods)

These two methods are lacking for real-world use. Two methods from the Forms plugin should be considered for import:

  • formSerialize/fieldSerialize - These should be imported and be made to overwrite the existing .serialize() method (since they are far superior in every respect). .serialize() would serialize each form element that it encounters in the result set, and if a form is encountered, retrieve all form elements and serialize those as well.
  • fieldValue - User's expect .val() to return a more-realistic representation of the value within a form element. Overwriting, or replacing, .val() isn't really an option, since it's still useful unto itself. fieldValue would be capable of handing complex elements like selects, multiple checkboxes, etc.

There are a number of open bugs that relate to .val() and .serialize() that would be solved with the introduction of these new methods.

.wrapAll()

An important alternative to .wrap() is the ability to wrap a single element around all elements matched by a selector. This could be achieved with a wrapAll method, like the following:

$.fn.wrapAll = function() {
   // There needs to be at least one matched element for this to work
   if ( !this.length ) return this;

   // Find the element that we're wrapping with
   var b = jQuery.clean(arguments)[0];

   // Make sure that its in the right position in the DOM
   this[0].parentNode.insertBefore( b, this[0] );

   // Find its lowest point
   while ( b.firstChild ) b = b.firstChild;

   // And add all the elements there
   return this.appendTo(b);
};

Additionally, the existing .wrap() method could be made to use .wrapAll() (allowing us to save some bytes in its addition).

A demo can be found here.

.nextUntil() / .prevUntil()

Currently jQuery has .next() and .prev(), which are limited to only retrieving immediately-adjacent elements. Being able to retrieve all previous siblings and all next siblings (and being able to filter them) is highly desirable.

Here's a sample implementaton of .nextUntil():

$.fn.nextUntil = function(expr) {
   var match = [];

   // We need to figure out which elements to push onto the array
   this.each(function(){
       // Traverse through the sibling nodes
       for( var i = this.nextSibling; i; i = i.nextSibling ) {
           // Make sure that we're only dealing with elements
           if ( i.nodeType != 1 ) continue;

           // If we find a match then we need to stop
           if ( jQuery.filter( expr, [i] ).r.length ) break;

           // Otherwise, add it on to the stack
           match.push( i );
       }
   });

   return this.pushStack( match, arguments );
};

A demo of this in action can be found here.

It's important to note the dual nature of this method:

Find all elements up until the next h3:

 $("h3").nextUntil("h3");
 => [ div, p, p ]

Find all elements after this one:

 $("h3").nextUntil();
 => [ div, p, p, h3, div, p ]

.andSelf()

Currently, all the methods that allow users to select elements along a specific axis (parents, find, children, next, prev, siblings) don't allow you to include the starting element itself.

It's most obvious with siblings:

 $("p").siblings();
 => [ div, div, div ]

With .andSelf() you would get:

 $("p").siblings().andSelf();
 => [ div, div, p, div ]

Combining this functionality with .wrapAll() and .nextUntil you could wrap a collection of related elements in a single line of code:

 $("h3#head").nextUntil("h3").andSelf().wrapAll("<div></div>");

An implementation of andSelf would look like:

$.fn.andSelf = function(){
  return this.add( this.prevObject );
};

.contents()

There has been some strong demand for the ability to access the full contents (including text nodes) within an element. There are two specific use cases that this would solve:

  • Moving the contents of an element to another element:
 $("#foo").contents().appendTo("#bar");
  • Wrapping the contents of an element in another element:
 $("a").contents().wrap("<b></b>");

It's important to note that this would be the first jQuery method capable of returning non-element nodes. Thusly, most jQuery methods should be extra-sure not to throw exceptions when dealing with incorrect nodes. (Additionally, the documentation for this method should be explicit in seeding concern in this matter.)

One possible solution:

jQuery.fn.contents = function() {
    return this.pushStack( jQuery.map( this, "jQuery.makeArray(a.childNodes)" ) );
};

Additionally, this may be a good time to consider extending .contents() to handle the contents of obtuse elements. For example:

It could return the contentDocument of the specified iframe:

 $("iframe").contents().find("body").append("hello!");
 => [ body ]

It could return all form elements in a form:

 $("form").contents();
 => [ input, input, select, input ]

It could return all options in a select:

 $("select").contents();
 => [ option, option ]

I think the iframe use case is especially important, and one that we may want to consider.

Please see the related bug for more information.

.hasClass()

This would be a simple alias for .is(".className"). It's an oft-requested feature, and the current API doesn't make it obvious that this is what needs to occur.

A possible implementation:

jQuery.fn.hasClass = function(c){
  return this.is("." + c);
};

Ajax

Append .load()

The second most popular use case of loading HTML into a document (via an Ajax call) is to load the HTML and append it to another element. (Of course, prepend may also be rather popular in this instance.) We should seriously consider a way to make .load() viable for other types of insertion.

The most obvious solution is to adapt .load() to handle multiple types of insertion, such as appending the returned HTML instead of using .html().

In order to achieve this, two things would need to occur:

  1. .evalScripts() would need to be moved to domManip (and related methods) instead of existing solely in the Ajax realm.
  2. .loadIfModified() would need to be removed.

The new .load() signature would look something like the following:

 .load( url, params, insertion, callback )

Allowing you to do:

 $("#foo").load("test.html", "append");

This argument would be completely optional (having its default value be equivalent to 'html').

Make .getScript() Work Cross-Domain

Currently .getScript() uses $.ajax() to request scripts. However, this use case is highly limited. The ability to load remote scripts would be highly useful.

Two things are required in order to make this happen:

  1. Using dynamic script element generation instead of $.ajax().
  2. Figuring out some means of determining when a script is loaded in all browsers.

The second one is especially critical, since getScript() provides the user with a callback, to let them know once the script has loaded. There are easy solutions for all browsers but Safari. Some of the most recent techniques revolve around the synchronous nature of script loading - but a reusable implementation needs to be well defined.

The final solution would allow you to do something like this:

 $.getScript("http://foo.com/bar.js");

Events

.ready() waits for .getScript()

This addition would allow users to dynamically load remote scripts and have them be executed before the main flow of the application commences.

This is very useful functionality, and similar to the features provided by Dojo, Mochikit, and OpenJSAN.

A solution would allow users to do the following:

 $.getScript("http://foo.com/bar.js");
 $.getScript("/my/test.js");

 $(document).ready(function(){
   bar(); // use bar from bar.js
   test(); // use test from test.js
 });

This proposal currently doesn't call for the scripts in getScript to be loaded synchronously - meaning that the above test.js could be loaded, and executed, before bar.js.

Multi-type Events

The use case of binding a single function to multiple, different, events is a common one - especially when dealing with mouse, or keyboard, events. This addition would allow users to perform that action with a single call to .bind(), behaving similarly to .addClass() and .removeClass().

The final call would allow you to do:

 .bind("mouseover focus", myfn);

The space-separated style would mimic that of addClass/removeClass (helping to keep us consistent with the rest of the API).

Please see the related bug and patch for more information.

.clone() Events

Currently you can clone a set of elements, but when that occurs, their associated event handlers are lost. It would be good to provide a way to clone elements while keeping their handlers intact.

Brandon has already done considerable work on this topic - so importing his solution would probably be desired.

The final result allows you to do something like:

 $("#foo").cloneWithEvents().appendTo("#bar");

However, having an extra method for this functionality seems redundant. Currently the only argument to .clone() is a boolean (representing if the clone should be "deep" or "shallow"). A shallow clone is rarely, if ever, used. (And can be easily duplicated by doing: .clone().empty().) I propose that we change the boolean argument of .clone() to mean "clone elements and events" (with the default being "don't clone events").

The result would then become:

 $("div").click(myFn);
 $("div:last").clone(true).appendTo("body");
 $("div:last").click(); // triggers myFn

Re-work triggered events

Triggering events was changed in jQuery 1.1 to trigger the default browser event after calling handlers. This causes as many issues as it solves, #873 proposes a good way to enhance this.

It should be considered to swap trigger and triggerAction, with trigger being the default for click() and alike. An API change seems to be necessary anyway.

Effects & Animations

Stop Animations

A critical aspect of impressive animations is the ability to stop an animation mid-stream. Support for this will be broken down into a couple factors:

  • A new .stop() method that stops all animations currently running against each of the matched elements.
  • A new .stop() method on each new jQuery.fx() object.
  • A way to determine if an element is currently being animated. Currently, I think the best way would be: .is("[@animated]").

Combined, the final result would look something like this:

 $("div[@animated]").stop().animate({ top: 0, left: 0 }, "slow");

Queue Control

Currently, all elements being animated are auto-queued based upon the element that they're animating against. While this is useful for most use cases, it is rather unacceptable for most serious forms of animation.

Support for this feature will be broken down into a couple features:

  • .queue() - accesses the animation queue (returns an array of functions attached to the first matched element). If an array is provided as an argument, it will replace the current queue, if a single function is provided, it's appended to the end of the queue.
  • { queue: false } - A new queue setting that allows you to override the queuing functionality of an animation.

An implementation would work like so:

 $("#foo")
   .stop() // Stop any running animations
   .queue([]) // Empty the queue
   .animate({ top: 100, left: 100 }, "fast")
   // For the second animation to skip the queue
   .animate({ width: 200, height: 200 }, { duration: "slow", queue: false });

Animating scrollLeft/scrollTop

Currently, these are the only two DOM properties that users want to animate - but making it such that any generic DOM property could be animated using .animate(), in favor of a CSS property, would generally be a good idea.

An example implementation can be found here. With the user's final code looking something like this:

$("input").click(function(){
  $("body")
    .animate({ scrollTop: 200 }, "slow")
    .animate({ scrollTop: 0 }, 2000, "bounceout");
});

Relative Animations

Animating an element, relatively, from a current position is, currently, a major hassle. There's some code in place that could make it easier, with just a little bit of work. This is a proposal for adding a new set of values as arguments to .animate().

It is best illustrated with an example:

$("#foo").animate({
  top: 100,      // Animate from the current top to 100 pixels
  left: "+100",  // Animate from the current left to "left + 100 pixels"
  height: "-50", // Animate the height to "height - 50 pixels"
  width: "hide"  // Animate the width to 0 pixels, then set its display to none
});

The relative animations would always be signified by a string beginning with either a "+" or a "-" followed by an integer. This would be parsed and a correct animation value would be determined from it.

Relative Animations Combine

When the combination of "{queue: false}" and relative animation is used, a different style of animation should take place. Instead of animating from A to B, the animation should animate (relatively) from 0 to (B - A), adding incremental amounts of pixels every time it's called. Thus if you were to do:

$("#foo")
  .css( "top", 100 )
  .animate({ top: "+100" }, { queue: false })
  .animate({ top: "+100" }, { queue: false });

The final result would be an animation going from 100px to 300px.

The calculation would work something like this (for each animation):

max_val = 100 // User input
duration = 600 // User input
last_val = 0
start_time = current_time
For every step
  prct = (current_time - start_time) / duration
  if ( prct >= 1 )
    cur_val = max_val
  else
    cur_val = max_val * prct
  val = val + (cur_val - last_val)
  last_val = cur_val

Attributes/CSS

.height()/.width() work for document/window

Currently running the .height() or .width() methods against document or window return unusable results. I propose that we import the functionality from the Dimensions plugin.

The code required to do so would look something like this:

jQuery.each( [ "Height", "Width" ], function(i,name){
	var n = name.toLowerCase();
	
	jQuery.fn[ n ] = function(h) {
		if ( this[0] == window )
			return self["inner" + name] ||
				$.boxModel && document.documentElement["client" + name] ||
				document.body["client" + name];
		
		if ( this[0] == document )
			return Math.max(
				document.body["scroll" + name],
				document.body["offset" + name]
			);
        
		return h == undefined ?
			( this.length ? jQuery.css( this[0], n ) : null ) :
			this.css( n, h.constructor == String ? h : h + "px" );
	};
});

Misc

Wrap jQuery in a Closure

Wrapping jQuery in a closure will have some very interesting results and things that will need to be considered:

  • jQuery and $ will need to be forcefully introduced into the global scope (currently they're only introduced in the current scope - whatever it may be). Fixing this will allow for some interesting compression/dynamic loading of jQuery.
  • While jQuery will be referred to as 'jQuery' within it's local scope, user's will be able to dynamically rename the jQuery located in the global scope AND be able to run multiple versions of jQuery side-by-side.
  • A number of variables and data structures that, previously, only existed as properties of the main jQuery object (due to the fact that they needed to be referenced in multiple locations) can now be referred to as locally-scoped variables. This will allow for shorter, tighter, code - while making it incredibly compressible (using Dojo's Shrinksafe).

In short, it will make jQuery smaller, more compressible, more reusable, and more malleable; all around a great option for inclusion.

I did exactly this with an early version of jQuery (rev 27) back in the spring of last year. It worked very well, with one exception: a huge memory leak in the event handlers. The only way I found to fix this was to move the addEvent/removeEvent/triggerEvent/handleEvent functions (which are now in the jQuery.event namespace object) out of the closure and back into the global scope. I don't recall exactly which of these functions was the one that caused the leak - I think I moved them all to be consistent. I suspect that the same issue may still occur with the current code. --Michael Geary 19:59, 14 July 2007 (PDT)

Move Docs to External File

Currently, jQuery documentation is included inline, in the form of ScriptDoc comments. In order to be more maintainable, the documentation should be moved to an external location and managed from there.

To Be Removed

The following are pieces of functionality that may be removed in jQuery 1.2.

  • Selectors
    • .. selector support (in favor of using .parent())
    • / selector support (in favor of using >)
    • // selector support (in favor of using ' ')
    • [selector] selector support (in favor of using :has(selector))
    • [@attr] selector support (in favor of using [attr])
  • DOM
    • .clone(Boolean) (in favor of using .clone().empty() instead)
  • Ajax
    • .loadIfModified() (in favor of using $.ajax())
    • .getIfModified() (in favor of using $.ajax()
    • .ajaxTimeout() (in favor of using $.ajaxSetup())
    • .evalScripts() (in favor of native support in append, et. al.)
  • Misc
    • .lt(), .gt(), and .contains() (in favor of using .filter(':lt(0)'), etc.)

Past Considerations

These are other items that were being considered, but have since been re-thought.

Use XPath for Selectors

Using XPath to select elements in an HTML document is the most obvious way to improve the speed of certain jQuery selectors in Firefox, Opera, and Webkit.

There are two schools of thought on this. Prototype rewrites all CSS selectors to XPath in Firefox, et. al., whereas Dojo selectively re-writes some queries.

Example code from Prototype:

 document._getElementsByXPath = function(expression, parentElement) {
   var results = [];
   var query = document.evaluate(expression, $(parentElement) || document,
     null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
   for (var i = 0, length = query.snapshotLength; i < length; i++)
     results.push(query.snapshotItem(i));
   return results;
 };

Personally, I feel that Dojo's solution is more inline with how we should tackle the problem. Implementing a full CSS Selector -> XPath Selector routine is byte costly. Whereas, we get the most benefit from three types of queries:

  • Class selectors (div.class)
  • Attribute selectors ([@foo=bar])
  • "Deep" selectors (div div div)

These less-popular queries would also benefit, if we were to consider them:

  • Child selectors (:first-child, :last-child, :only-child, :nth-child)
  • Axis selectors (div > span, div ~ div, div + div)

Detecting those three query types and selectively using XPath would seem to be the right solution for us.

Although, if we were to implement that much CSS -> XPath conversion, it may simply result that a full conversion would be ideal, but that remains to be seen.

Behaviors / Live Query

Based upon the plugin written by Brandon - providing native support for behaviors.

However, before this becomes a serious option for inclusion in 1.2 I would like to see more wide-scale adoption of its use. If that were to occur, I would be more inclined to vouch for its inclusion (even though it is very, very, cool).

In short, Live Query will allow you to do two things very easily:

  • Execute a function within the context of an element, any time a new element appears that matches a specific selector.
  • Bind a function to an element, any time a new element appears that matches a specific selector.

The result of which would look something like this:

 $("ul > li").livequery("click", myFn);

making it such that anytime a new li is inserted into the document (that matches "ul > li"), it will have the myFn function bound to its click event.

or:

 $("div").livequery(function(){
   $(this).addClass("test");
 });

and then any time a new div is inserted into the document, a class will be added to it.

Element Offset

This has a compelling use case, but its file size is daunting. Further clarification is needed here.

Variables in .attr()/.css()

This was shelved after a failed implementation in 1.1, I'm still not convinced that this is particularly useful (or could be made useful in a reasonable number of bytes).