galaxy-commits
  Threads by month 
                
            - ----- 2025 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
August 2013
- 1 participants
- 149 discussions
 
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: fubar: Patch for missing options in FromDataTableOutputActionOption when these are used in an output filter
                        
                        
by commits-noreply@bitbucket.org 10 Aug '13
                    by commits-noreply@bitbucket.org 10 Aug '13
10 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/009088d5e76f/
Changeset:   009088d5e76f
User:        fubar
Date:        2013-08-10 03:28:28
Summary:     Patch for missing options in FromDataTableOutputActionOption when these are used in an output filter
Affected #:  1 file
diff -r 31d2d58ebf102f87c21f170b16452d06fd510a14 -r 009088d5e76fb00794da78dc0ee3cdaa8524b7d8 lib/galaxy/tools/parameters/output.py
--- a/lib/galaxy/tools/parameters/output.py
+++ b/lib/galaxy/tools/parameters/output.py
@@ -217,7 +217,10 @@
         else:
             self.missing_tool_data_table_name = self.name
     def get_value( self, other_values ):
-        options = self.options
+        try:
+            options = self.options
+        except:
+            options = []
         for filter in self.filters:
             options = filter.filter_options( options, other_values )
         try:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: jgoecks: Unify Trackster tool objects with generic tool objects. This unification enhances the generic tool framework and simplifies the trackster tool framework. Also, backbone-ify Trackster tool handling.
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/31d2d58ebf10/
Changeset:   31d2d58ebf10
User:        jgoecks
Date:        2013-08-08 23:05:51
Summary:     Unify Trackster tool objects with generic tool objects. This unification enhances the generic tool framework and simplifies the trackster tool framework. Also, backbone-ify Trackster tool handling.
Affected #:  3 files
diff -r 8c3b553871bbcce9170d235f5188b9803094ad3b -r 31d2d58ebf102f87c21f170b16452d06fd510a14 lib/galaxy/visualization/genome/visual_analytics.py
--- a/lib/galaxy/visualization/genome/visual_analytics.py
+++ b/lib/galaxy/visualization/genome/visual_analytics.py
@@ -38,6 +38,8 @@
 
 def get_tool_def( trans, hda ):
     """ Returns definition of an interactive tool for an HDA. """
+
+    #FIXME: use tools.to_dict rather than custom code here.
     
     job = get_dataset_job( hda )
     # TODO: could use this assertion to provide more information.
@@ -71,10 +73,10 @@
             tool_params.append( param_dict )
         elif type( input ) == SelectToolParameter and type( input.options ) != DynamicOptions:
             tool_params.append( { 'name' : name, 'label' : input.label, 'type' : 'select', \
-                                  'value' : tool_param_values.get( name, None ), \
+                                  'value' : tool_param_values.get( name, '' ), \
                                   'html' : urllib.quote( input.get_html() ) } )
         
     # If tool has parameters that can be interactively modified, return tool.
     if len( tool_params ) != 0:
-        return { 'id': tool.id, 'name' : tool.name, 'params' : tool_params } 
+        return { 'id': tool.id, 'name' : tool.name, 'inputs' : tool_params } 
     return None
\ No newline at end of file
diff -r 8c3b553871bbcce9170d235f5188b9803094ad3b -r 31d2d58ebf102f87c21f170b16452d06fd510a14 static/scripts/mvc/tools.js
--- a/static/scripts/mvc/tools.js
+++ b/static/scripts/mvc/tools.js
@@ -41,6 +41,7 @@
         label: null,
         type: null,
         value: null,
+        html: null,
         num_samples: 5
     },
     
@@ -70,7 +71,51 @@
         }
         
         return samples;
-    }   
+    },
+
+    set_value: function(value) {
+        this.set('value', value || '');
+    }
+},
+{
+    /**
+     * Dictionary mapping parameter type strings to parameter classes.
+     */
+    TYPE_DICT: {
+        'number': IntegerToolParameter
+    },
+
+    /**
+     * Create new parameter from a dictionary.
+     */
+    create: function(options) {
+        var param_class = ToolParameter.TYPE_DICT[options.type] || ToolParameter;
+        return new param_class(options);
+    }
+});
+
+/**
+ * A number tool parameter.
+ */
+var IntegerToolParameter = ToolParameter.extend({
+    defaults: _.extend({}, ToolParameter.prototype.defaults, {
+        min: null,
+        max: null
+    }),
+
+    initialize: function() {
+        ToolParameter.prototype.initialize.call(this);
+        if (this.attributes.min) {
+            this.attributes.min = parseInt(this.attributes.min, 10);    
+        }
+        if (this.attributes.max) {
+            this.attributes.max = parseInt(this.attributes.max, 10);
+        }
+    },
+
+    set_value: function(value) {
+        this.set('value', parseInt(value, 10))
+    }
 });
 
 /**
@@ -79,22 +124,22 @@
 var Tool = BaseModel.extend({
     // Default attributes.
     defaults: {
+        id: null,
+        name: null,
         description: null,
         target: null,
         inputs: []
     },
-    
-    relations: [
-        {
-            type: Backbone.HasMany,
-            key: 'inputs',
-            relatedModel: ToolParameter,
-            reverseRelation: {
-                key: 'tool',
-                includeInJSON: false
-            }
-        }
-    ],
+
+    initialize: function(options) {
+        // Unpack parameters manually so that different parameter types can be created.
+        this.attributes.inputs = new Backbone.Collection( _.map(options.inputs, function(param_dict) {
+            // FIXME: it is still useful to be able to save/restore tool state?
+            // Update parameter value from tool state dict.
+            //param_dict.value = tool_state_dict[ param_dict[name] ] || param_dict.value;
+            return ToolParameter.create(param_dict);
+        }));
+    },
     
     urlRoot: galaxy_paths.get('tool_url'),
 
@@ -209,6 +254,13 @@
 });
 
 /**
+ * Tool view.
+ */
+var ToolView = Backbone.View.extend({
+
+});
+
+/**
  * Wrap collection of tools for fast access/manipulation.
  */
 var ToolCollection = Backbone.Collection.extend({
@@ -697,6 +749,8 @@
 
 // Exports
 return {
+    ToolParameter: ToolParameter,
+    IntegerToolParameter: IntegerToolParameter,
     Tool: Tool,
     ToolSearch: ToolSearch,
     ToolPanel: ToolPanel,
diff -r 8c3b553871bbcce9170d235f5188b9803094ad3b -r 31d2d58ebf102f87c21f170b16452d06fd510a14 static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -1,7 +1,7 @@
 define( ["libs/underscore", "viz/visualization", "viz/trackster/util", 
-         "viz/trackster/slotting", "viz/trackster/painters", "mvc/data",
-         "viz/trackster/filters" ], 
-         function( _, visualization, util, slotting, painters, data, filters_mod ) {
+         "viz/trackster/slotting", "viz/trackster/painters", "viz/trackster/filters",
+         "mvc/data", "mvc/tools" ], 
+         function(_, visualization, util, slotting, painters, filters_mod, data, tools_mod) {
 
 var extend = _.extend;
 
@@ -1622,89 +1622,103 @@
 /**
  * Encapsulation of a tool that users can apply to tracks/datasets.
  */
-var Tool = function(track, tool_dict, tool_state_dict) {    
-    //
-    // Unpack tool information from dictionary.
-    //
-    this.track = track;
-    this.id = tool_dict.id;
-    this.name = tool_dict.name;
-    this.params = [];
-    var params_dict = tool_dict.params;
-    for (var i = 0; i < params_dict.length; i++) {
-        // FIXME: use dict for creating parameters.
-        var param_dict = params_dict[i],
-            name = param_dict.name,
-            label = param_dict.label,
-            html = unescape(param_dict.html),
-            value = param_dict.value,
-            type = param_dict.type;
-        if (type === "number") {
-            this.params.push(
-                new NumberParameter(name, label, html, 
-                                    (name in tool_state_dict ? tool_state_dict[name] : value),
-                                    param_dict.min, param_dict.max)
-                            );
+var TracksterTool = Backbone.RelationalModel.extend({
+    defaults: {
+        track: null,
+        tool: null,
+    },
+
+    relations: [
+        {
+            type: Backbone.HasOne,
+            key: 'tool',
+            relatedModel: tools_mod.Tool
         }
-        else if (type === "select") {
-            this.params.push(
-                new ToolParameter(name, label, html, 
-                                  (name in tool_state_dict ? tool_state_dict[name] : value))
-                            );
-        }
-        else {
-            console.log("WARNING: unrecognized tool parameter type:", name, type);
-        }        
-    }
-    
-    //
-    // Create div elt for tool UI.
-    //
-    this.parent_div = $("<div/>").addClass("dynamic-tool").hide();
-    // Disable dragging, clicking, double clicking on div so that actions on slider do not impact viz.
-    this.parent_div.bind("drag", function(e) {
-        e.stopPropagation();
-    }).click(function(e) {
-        e.stopPropagation();
-    }).bind("dblclick", function(e) {
-        e.stopPropagation();
-    });
-    var name_div = $("<div class='tool-name'>").appendTo(this.parent_div).text(this.name);
-    var tool_params = this.params;
-    var tool = this;
-    $.each(this.params, function(index, param) {
-        var param_div = $("<div>").addClass("param-row").appendTo(tool.parent_div);
+    ]
+
+});
+
+/**
+ * View renders tool parameter HTML and updates parameter value as it is changed in the HTML.
+ */
+ var ToolParameterView = Backbone.View.extend({
+
+    events: {
+        'change input': 'update_value'
+    },
+
+    render: function() {
+        var param_div = this.$el.addClass("param-row"),
+            param = this.model;
+
         // Param label.
-        var label_div = $("<div>").addClass("param-label").text(param.label).appendTo(param_div);
+        var label_div = $("<div>").addClass("param-label").text(param.get('label')).appendTo(param_div);
         // Param HTML.
-        var html_div = $("<div/>").addClass("param-input").html(param.html).appendTo(param_div);
+        var html_div = $("<div/>").addClass("param-input").html(param.get('html')).appendTo(param_div);
         // Set initial value.
-        html_div.find(":input").val(param.value);
+        html_div.find(":input").val(param.get('value'));
         
         // Add to clear floating layout.
         $("<div style='clear: both;'/>").appendTo(param_div);
-    });
-    
-    // Highlight value for inputs for easy replacement.
-    this.parent_div.find("input").click(function() { $(this).select(); });
-    
-    // Add buttons for running on dataset, region.
-    var run_tool_row = $("<div>").addClass("param-row").appendTo(this.parent_div);
-    var run_on_dataset_button = $("<input type='submit'>").attr("value", "Run on complete dataset").appendTo(run_tool_row);
-    var run_on_region_button = $("<input type='submit'>").attr("value", "Run on visible region").css("margin-left", "3em").appendTo(run_tool_row);
-    run_on_region_button.click( function() {
-        // Run tool to create new track.
-        tool.run_on_region();
-    });
-    run_on_dataset_button.click( function() {
-        tool.run_on_dataset();
-    });
-    
-    if ('visible' in tool_state_dict && tool_state_dict.visible) {
-        this.parent_div.show();
+    },
+
+    update_value: function(update_event) {
+        this.model.set_value($(update_event.target).val());
     }
-};
-extend(Tool.prototype, {
+ });
+
+/**
+ * View for TracksterTool.
+ */
+var TracksterToolView = Backbone.View.extend({
+
+    /**
+     * Render tool UI.
+     */
+    render: function() {
+        var self = this;
+            tool = this.model.get('tool'),
+            parent_div = this.$el.addClass("dynamic-tool").hide();
+
+        // Prevent div events from propogating to other elements.
+        parent_div.bind("drag", function(e) {
+            e.stopPropagation();
+        }).click(function(e) {
+            e.stopPropagation();
+        }).bind("dblclick", function(e) {
+            e.stopPropagation();
+        }).keydown(function(e) { e.stopPropagation(); });
+
+        // Add name, inputs.
+        var name_div = $("<div class='tool-name'>").appendTo(parent_div).text(tool.get('name'));
+        tool.get('inputs').each(function(param) {
+            var param_view = new ToolParameterView({ model: param });
+            param_view.render();
+            parent_div.append(param_view.$el);
+        });
+
+        // Highlight value for inputs for easy replacement.
+        parent_div.find("input").click(function() { $(this).select(); });
+        
+        // Add buttons for running on dataset, region.
+        var run_tool_row = $("<div>").addClass("param-row").appendTo(parent_div);
+        var run_on_dataset_button = $("<input type='submit'>").attr("value", "Run on complete dataset").appendTo(run_tool_row);
+        var run_on_region_button = $("<input type='submit'>").attr("value", "Run on visible region").css("margin-left", "3em").appendTo(run_tool_row);
+        run_on_region_button.click( function() {
+            // Run tool to create new track.
+            self.run_on_region();
+        });
+        run_on_dataset_button.click( function() {
+            self.run_on_dataset();
+        });
+        
+        /*
+        if ('visible' in tool_state_dict && tool_state_dict.visible) {
+            this.parent_div.show();
+        }
+        */
+    },
+
     /**
      * Update tool parameters.
      */
@@ -1713,68 +1727,42 @@
             this.params[i].update_value();
         }
     },
+
     /**
      * Returns a dict with tool state information.
      */
     state_dict: function() {
         // Save parameter values.
-        var tool_state = {};
-        for (var i = 0; i < this.params.length; i++) {
-            tool_state[this.params[i].name] = this.params[i].value;
-        }
+        var tool_state = this.model.get('tool').get_param_values_dict();
         
         // Save visibility.
         tool_state.visible = this.parent_div.is(":visible");
         
         return tool_state;
     },
-    /** 
-     * Returns dictionary of parameter name-values.
-     */
-    get_param_values_dict: function() {
-        var param_dict = {};
-        this.parent_div.find(":input").each(function() {
-            var name = $(this).attr("name"), value = $(this).val();
-            param_dict[name] = value;
-        });
-        return param_dict;
-    },
-    /**
-     * Returns array of parameter values.
-     */
-    get_param_values: function() {
-        var param_values = [];
-        this.parent_div.find(":input").each(function() {
-            // Only include inputs with names; this excludes Run button.
-            var name = $(this).attr("name"), value = $(this).val();
-            if (name) {
-                param_values[param_values.length] = value;
-            }
-        });
-        return param_values;
-    },
+
     /**
      * Run tool on dataset. Output is placed in dataset's history and no changes to viz are made.
      */
     run_on_dataset: function() {
-        var tool = this;
-        tool.run(
-                 // URL params.
-                 { 
-                     target_dataset_id: this.track.dataset.id,
-                     action: 'rerun',
-                     tool_id: tool.id
-                 },
-                 null,
-                 // Success callback.
-                 function(track_data) {
-                     show_modal(tool.name + " is Running", 
-                                tool.name + " is running on the complete dataset. Tool outputs are in dataset's history.", 
-                                { "Close" : hide_modal } );
-                 }
-                );
-        
+        var tool = this.model.get('tool');
+        this.run(
+            // URL params.
+            { 
+                target_dataset_id: this.model.get('track').dataset.id,
+                action: 'rerun',
+                tool_id: tool.id
+            },
+            null,
+            // Success callback.
+            function(track_data) {
+                show_modal(tool.get('name') + " is Running", 
+                            tool.get('name') + " is running on the complete dataset. Tool outputs are in dataset's history.", 
+                            { "Close" : hide_modal } );
+            }
+        );
     },
+
     /**
      * Run dataset on visible region. This creates a new track and sets the track's contents
      * to the tool's output.
@@ -1783,21 +1771,23 @@
         //
         // Create track for tool's output immediately to provide user feedback.
         //
-        var region = new visualization.GenomeRegion({
-                chrom: this.track.view.chrom,
-                start: this.track.view.low,
-                end: this.track.view.high
+        var track = this.model.get('track'),
+            tool = this.model.get('tool'),
+            region = new visualization.GenomeRegion({
+                chrom: track.view.chrom,
+                start: track.view.low,
+                end: track.view.high
             }),
             url_params = 
             { 
-                target_dataset_id: this.track.dataset.id,
+                target_dataset_id: track.dataset.id,
                 action: 'rerun',
-                tool_id: this.id,
+                tool_id: tool.id,
                 regions: [
                     region.toJSON()
                 ]
             },
-            current_track = this.track,
+            current_track = track,
             // Set name of track to include tool name, parameters, and region used.
             track_name = url_params.tool_id +
                          current_track.tool_region_and_parameters_str(region),
@@ -1838,25 +1828,25 @@
         new_track.tiles_div.text("Starting job.");
         
         // Run tool.
-        this.update_params();
         this.run(url_params, new_track,
-                 // Success callback.
-                 function(track_data) {
-                     new_track.set_dataset(new data.Dataset(track_data));
-                     new_track.tiles_div.text("Running job.");
-                     new_track.init();
-                 }
-                );
+                // Success callback.
+                function(track_data) {
+                    new_track.set_dataset(new data.Dataset(track_data));
+                    new_track.tiles_div.text("Running job.");
+                    new_track.init();
+                }
+        );
     },
+
     /**
      * Run tool using a set of URL params and a success callback.
      */
     run: function(url_params, new_track, success_callback) {
         // Run tool.
-        url_params.inputs = this.get_param_values_dict();
+        url_params.inputs = this.model.get('tool').get_inputs_dict();
         var ss_deferred = new util.ServerStateDeferred({
             ajax_settings: {
-                url: config.root + "/api/tools",
+                url: config.root + "api/tools",
                 data: JSON.stringify(url_params),
                 dataType: "json",
                 contentType: 'application/json',
@@ -1889,36 +1879,7 @@
             }            
         });
     }
-});
-
-/**
- * Tool parameters.
- */
-var ToolParameter = function(name, label, html, value) {
-    this.name = name;
-    this.label = label;
-    // Need to use jQuery for HTML so that value can be queried and updated dynamically.
-    this.html = $(html);
-    this.value = value;
-};
-
-extend(ToolParameter.prototype, {
-    update_value: function() {
-        this.value = $(this.html).val();
-    } 
-});
-
-var NumberParameter = function(name, label, html, value, min, max) {
-    ToolParameter.call(this, name, label, html, value);
-    this.min = min;
-    this.max = max;
-};
-
-extend(NumberParameter.prototype, ToolParameter.prototype, {
-    update_value: function() {
-        ToolParameter.prototype.update_value.call(this);
-        this.value = parseFloat(this.value);
-    }
+
 });
 
 /**
@@ -2819,7 +2780,12 @@
     // FIXME: prolly need function to set filters and update data_manager reference.
     this.data_manager.set('filters_manager', this.filters_manager);
     this.filters_available = false;
-    this.tool = ('tool' in obj_dict && obj_dict.tool ? new Tool(this, obj_dict.tool, obj_dict.tool_state) : null);
+    this.tool = (obj_dict.tool ? new TracksterTool({
+        'track': this, 
+        'tool': obj_dict.tool,
+        'tool_state': obj_dict.tool_state
+    }) 
+    : null);
     this.tile_cache = new visualization.Cache(TILE_CACHE_SIZE);
     this.left_offset = 0;
     
@@ -2830,10 +2796,12 @@
         this.set_filters_manager(this.filters_manager);
         
         //
-        // Create dynamic tool div.
+        // Create dynamic tool view.
         //
-        if (this.tool) {  
-            this.dynamic_tool_div = this.tool.parent_div;
+        if (this.tool) {
+            var tool_view = new TracksterToolView({ model: this.tool });
+            tool_view.render();
+            this.dynamic_tool_div = tool_view.$el;
             this.header_div.after(this.dynamic_tool_div);
         }
     }
@@ -3427,8 +3395,9 @@
      */
     tool_region_and_parameters_str: function(region) {
         var track = this,
-            region_str = (region !== undefined ? region.toString() : "all");
-        return " - region=[" + region_str + "], parameters=[" + track.tool.get_param_values().join(", ") + "]";
+            region_str = (region !== undefined ? region.toString() : "all"),
+            param_str = _.values( track.tool.get('tool').get_inputs_dict()).join(', ');
+        return " - region=[" + region_str + "], parameters=[" + param_str + "]";
     },
 
     /**
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: carlfeberhard: pull request #168 from Kyle Ellrott: add graphview visualization as visualization plugin
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8c3b553871bb/
Changeset:   8c3b553871bb
User:        carlfeberhard
Date:        2013-08-08 22:48:56
Summary:     pull request #168 from Kyle Ellrott: add graphview visualization as visualization plugin
Affected #:  5 files
diff -r 6d3d5e571a43dc2defa94c0baef8c96461818a53 -r 8c3b553871bbcce9170d235f5188b9803094ad3b config/plugins/visualizations/graphview/config/graphview.xml
--- /dev/null
+++ b/config/plugins/visualizations/graphview/config/graphview.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE visualization SYSTEM "../../visualization.dtd">
+<visualization name="Graphview">
+    <data_sources>
+        <data_source>
+            <model_class>HistoryDatasetAssociation</model_class>
+            <test type="has_dataprovider" test_attr="datatype">node-edge</test>
+            <to_param param_attr="id">dataset_id</to_param>
+        </data_source>
+        <data_source>
+            <model_class>HistoryDatasetAssociation</model_class>
+            <test type="isinstance" test_attr="datatype" result_type="datatype">graph.Rdf</test>
+            <to_param param_attr="id">dataset_id</to_param>
+        </data_source>
+    </data_sources>
+    <params>
+        <param type="dataset" var_name_in_template="hda" required="true">dataset_id</param>
+    </params>
+    <template>graphview/templates/graphview.mako</template>
+</visualization>
diff -r 6d3d5e571a43dc2defa94c0baef8c96461818a53 -r 8c3b553871bbcce9170d235f5188b9803094ad3b config/plugins/visualizations/graphview/static/graphview.css
--- /dev/null
+++ b/config/plugins/visualizations/graphview/static/graphview.css
@@ -0,0 +1,70 @@
+svg {
+  border: 1px solid lightgrey;
+  background-color: #FFF;
+  cursor: default;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -o-user-select: none;
+  user-select: none;
+}
+
+svg:not(.active):not(.ctrl) {
+  cursor: crosshair;
+}
+
+path.link {
+  fill: none;
+  stroke: #000;
+  stroke-width: 4px;
+  cursor: default;
+}
+
+svg:not(.active):not(.ctrl) path.link {
+  cursor: pointer;
+}
+
+path.link.selected {
+  stroke-dasharray: 10,2;
+}
+
+path.link.dragline {
+  pointer-events: none;
+}
+
+path.link.hidden {
+  stroke-width: 0;
+}
+
+circle.node {
+  stroke-width: 1.0px;
+  cursor: pointer;
+}
+
+circle.node.reflexive {
+  stroke: #000 !important;
+  stroke-width: 2.5px;
+}
+
+text {
+  font: 12px sans-serif;
+  pointer-events: none;
+}
+
+text.id {
+  text-anchor: middle;
+  font-weight: bold;
+}
+
+div.tooltip {
+  position: absolute;
+  text-align: center;
+  width: 60px;
+  height: 28px;
+  padding: 2px;
+  font: 12px sans-serif;
+  background: lightsteelblue;
+  border: 0px;
+  border-radius: 8px;
+  pointer-events: none;
+}
diff -r 6d3d5e571a43dc2defa94c0baef8c96461818a53 -r 8c3b553871bbcce9170d235f5188b9803094ad3b config/plugins/visualizations/graphview/static/graphview.js
--- /dev/null
+++ b/config/plugins/visualizations/graphview/static/graphview.js
@@ -0,0 +1,175 @@
+function get_firstChild(n) {
+	y=n.firstChild;
+	while (y && y.nodeType!=1) {
+		y=y.nextSibling;
+ 	}
+	return y;
+}
+
+function get_nextSibling(n) {
+	y=n.nextSibling;
+	while (y) {
+		if (y.nodeType==1) {
+			return y;
+		}
+		y=y.nextSibling;
+	}
+	return y;
+}
+
+function parse_xgmml_attr(elm) {
+	out = {};
+	var c = get_firstChild(elm);
+	while (c) {
+		if (c.nodeName == "att") {
+			if (c.attributes['type'].value == "string") {
+				out[c.attributes['name'].value] = c.attributes['value'].value;
+			}
+		}
+		c = get_nextSibling(c);
+	}
+	return out
+}
+
+function parse_xgmml(root, add_node, add_edge) {
+	graph=root.getElementsByTagName("graph")[0];
+	elm = get_firstChild(graph);
+	while (elm) {
+		if (elm.nodeName == "node") {
+			var attr = parse_xgmml_attr(elm);
+			add_node( elm.attributes['id'].value, elm.attributes['label'].value, attr );
+		}
+		if (elm.nodeName == "edge") {
+			var attr = parse_xgmml_attr(elm);
+			add_edge( elm.attributes['source'].value, elm.attributes['target'].value, attr );
+		}
+		
+		elm = get_nextSibling(elm);
+	}
+}
+
+function parse_sif(data, add_node, add_edge) {
+	var lines = data.split("\n")
+	for (var i in lines) {
+		var tmp = lines[i].split("\t");
+		if (tmp.length == 3) {
+			add_edge(tmp[0], tmp[2], {type:tmp[1]});
+		}
+	}
+
+}
+
+jQuery.fn.graphViewer = function(config) {
+
+	var svg, colors;
+	var nodes = [],
+	  	links = [];
+
+	var height = config.height;
+	var width = config.width;
+
+
+	colors = d3.scale.category10();
+
+	this.each(function() {
+		svg = d3.select(this)
+			.append('svg')
+			.attr('width', width)
+			.attr('height', height);
+		}
+	);
+
+	var tooltip_div = d3.select("#tooltip").attr("class", "tooltip").style("opacity", 0);;
+
+	this.add_node = function(node_id, node_label, attr) {	
+		nodes.push( {id: node_id, label:node_label, attr:attr} );
+	}
+
+	this.add_edge = function(src_id, dst_id, attr) {
+		var src, target;
+		for (var i in nodes) {
+			if (nodes[i].id == src_id) {
+				src = nodes[i];
+			}
+			if (nodes[i].id == dst_id) {
+				target = nodes[i];
+			}
+		}
+		if (typeof src==="undefined") {
+			i = nodes.length
+			nodes.push( {id:src_id, label:src_id, attr:{}} )
+			src = nodes[i]
+		}
+		if (typeof target==="undefined") {
+			i = nodes.length
+			nodes.push( {id:dst_id, label:dst_id, attr:{}} )
+			target = nodes[i]
+		}
+		if (src && target) {
+			links.push( {source: src, target: target, left: false, right: true } );
+		}
+	}
+
+	this.render = function() {
+		var path = svg.append('svg:g').selectAll('path'),
+	    circle = svg.append('svg:g').selectAll('g');
+
+
+		circle = circle.data(nodes, function(d) { return d.id; });
+		var g = circle.enter().append('svg:g');
+
+		circle.on('mouseover', function(d) {
+			tooltip_div.transition()        
+            .duration(200)      
+            .style("opacity", .9);
+			tooltip_div.html( "<div>" + d.label + "</div><div>" + d.attr.type + "</div>" ).style("left", (d3.event.pageX + 40) + "px")
+            .style("top", (d3.event.pageY - 35) + "px");   
+		})
+		.on("mouseout", function(d) {       
+        	tooltip_div.transition()        
+            .duration(500)      
+            .style("opacity", 0);
+        });
+
+		path = path.data(links);
+
+
+		function tick() {
+			path.attr('d', function(d) {
+    			return 'M' +  d.source.x + ',' +  d.source.y + 'L' + d.target.x + ',' + d.target.y;
+    		});
+			circle.attr('transform', function(d) {
+				return 'translate(' + d.x + ',' + d.y + ')';
+			});
+		}
+
+
+		g.append('svg:circle')
+			.attr('class', 'node')
+			.attr('r', 8)
+			.style('stroke', 'black')
+			.attr('fill', function(d) { 
+				return colors(d['attr']['type']) 
+			});
+
+		path.enter().append('svg:path')
+			.attr('class', 'link')
+
+
+	  	// init D3 force layout
+		var force = d3.layout.force()
+		    .nodes(nodes)
+		    .links(links)
+		    .size([width, height])
+		    .linkDistance(25)
+		    .charge(-50)
+		    .on('tick', tick)
+		circle.call(force.drag)
+		force.start()
+
+	}
+
+	return this
+
+}
+
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: jgoecks: Update jquery.mousewheel plugin from version 3.0.4 to 3.1.3, introducing a necessary (for now) hack. This makes the plugin compatable with the most recent version of jquery.
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6d3d5e571a43/
Changeset:   6d3d5e571a43
User:        jgoecks
Date:        2013-08-08 22:41:18
Summary:     Update jquery.mousewheel plugin from version 3.0.4 to 3.1.3, introducing a necessary (for now) hack. This makes the plugin compatable with the most recent version of jquery.
Affected #:  1 file
diff -r 61ddd0d07950fafc18fed95354459e7715e6b1d7 -r 6d3d5e571a43dc2defa94c0baef8c96461818a53 static/scripts/libs/jquery/jquery.mousewheel.js
--- a/static/scripts/libs/jquery/jquery.mousewheel.js
+++ b/static/scripts/libs/jquery/jquery.mousewheel.js
@@ -1,78 +1,125 @@
-/* Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
+/*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
  * Licensed under the MIT License (LICENSE.txt).
  *
  * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
  * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
  * Thanks to: Seamus Leahy for adding deltaX and deltaY
  *
- * Version: 3.0.4
- * 
+ * Version: 3.1.3
+ *
  * Requires: 1.2.2+
  */
 
-(function($) {
+(function (factory) {
+    // GALAXY HACK 
+    // (JG): Galaxy's mixing of a global jQuery and require modules doesn't work with 
+    // the logic below. Instead, do the right thing for this configuration without any checks.
+    factory(jQuery);
+    // END HACK
 
-var types = ['DOMMouseScroll', 'mousewheel'];
+    /*
+    if ( typeof define === 'function' && define.amd ) {
+        // AMD. Register as an anonymous module.
+        define(['jquery'], factory);
+    } else if (typeof exports === 'object') {
+        // Node/CommonJS style for Browserify
+        module.exports = factory;
+    } else {
+        // Browser globals
+        factory(jQuery);
+    }
+    */
+}(function ($) {
 
-$.event.special.mousewheel = {
-    setup: function() {
-        if ( this.addEventListener ) {
-            for ( var i=types.length; i; ) {
-                this.addEventListener( types[--i], handler, false );
-            }
-        } else {
-            this.onmousewheel = handler;
-        }
-    },
-    
-    teardown: function() {
-        if ( this.removeEventListener ) {
-            for ( var i=types.length; i; ) {
-                this.removeEventListener( types[--i], handler, false );
-            }
-        } else {
-            this.onmousewheel = null;
+    var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
+    var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
+    var lowestDelta, lowestDeltaXY;
+
+    if ( $.event.fixHooks ) {
+        for ( var i = toFix.length; i; ) {
+            $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
         }
     }
-};
 
-$.fn.extend({
-    mousewheel: function(fn) {
-        return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
-    },
-    
-    unmousewheel: function(fn) {
-        return this.unbind("mousewheel", fn);
+    $.event.special.mousewheel = {
+        setup: function() {
+            if ( this.addEventListener ) {
+                for ( var i = toBind.length; i; ) {
+                    this.addEventListener( toBind[--i], handler, false );
+                }
+            } else {
+                this.onmousewheel = handler;
+            }
+        },
+
+        teardown: function() {
+            if ( this.removeEventListener ) {
+                for ( var i = toBind.length; i; ) {
+                    this.removeEventListener( toBind[--i], handler, false );
+                }
+            } else {
+                this.onmousewheel = null;
+            }
+        }
+    };
+
+    $.fn.extend({
+        mousewheel: function(fn) {
+            return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
+        },
+
+        unmousewheel: function(fn) {
+            return this.unbind("mousewheel", fn);
+        }
+    });
+
+
+    function handler(event) {
+        var orgEvent = event || window.event,
+            args = [].slice.call(arguments, 1),
+            delta = 0,
+            deltaX = 0,
+            deltaY = 0,
+            absDelta = 0,
+            absDeltaXY = 0,
+            fn;
+        event = $.event.fix(orgEvent);
+        event.type = "mousewheel";
+
+        // Old school scrollwheel delta
+        if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
+        if ( orgEvent.detail )     { delta = orgEvent.detail * -1; }
+
+        // New school wheel delta (wheel event)
+        if ( orgEvent.deltaY ) {
+            deltaY = orgEvent.deltaY * -1;
+            delta  = deltaY;
+        }
+        if ( orgEvent.deltaX ) {
+            deltaX = orgEvent.deltaX;
+            delta  = deltaX * -1;
+        }
+
+        // Webkit
+        if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
+        if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
+
+        // Look for lowest delta to normalize the delta values
+        absDelta = Math.abs(delta);
+        if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
+        absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
+        if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
+
+        // Get a whole value for the deltas
+        fn = delta > 0 ? 'floor' : 'ceil';
+        delta  = Math[fn](delta / lowestDelta);
+        deltaX = Math[fn](deltaX / lowestDeltaXY);
+        deltaY = Math[fn](deltaY / lowestDeltaXY);
+
+        // Add event and delta to the front of the arguments
+        args.unshift(event, delta, deltaX, deltaY);
+
+        return ($.event.dispatch || $.event.handle).apply(this, args);
     }
-});
 
-
-function handler(event) {
-    var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
-    event = $.event.fix(orgEvent);
-    event.type = "mousewheel";
-
-    // Old school scrollwheel delta
-    if ( event.wheelDelta ) { delta = event.wheelDelta/120; }
-    if ( event.detail     ) { delta = -event.detail/3; }
-    
-    // New school multidimensional scroll (touchpads) deltas
-    deltaY = delta;
-    
-    // Gecko
-    if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
-        deltaY = 0;
-        deltaX = -1*delta;
-    }
-    
-    // Webkit
-    if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
-    if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
-    
-    // Add event and delta to the front of the arguments
-    args.unshift(event, delta, deltaX, deltaY);
-    
-    return $.event.handle.apply(this, args);
-}
-
-})(jQuery);
+}));
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                    
                    
                        2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e7e82003f235/
Changeset:   e7e82003f235
Branch:      next-stable
User:        natefoo
Date:        2013-08-08 21:40:00
Summary:     Define the ActionInputError exception that was being caught in the quotas API controller.  Thanks Jim Johnson.
Affected #:  3 files
diff -r 8d8429484ac3ffef21fedd1077acf050c8aed459 -r e7e82003f235c4cca84939e04aa432d128093866 lib/galaxy/actions/admin.py
--- a/lib/galaxy/actions/admin.py
+++ b/lib/galaxy/actions/admin.py
@@ -4,7 +4,7 @@
 
 import logging
 from galaxy import util
-from galaxy.exceptions import MessageException
+from galaxy.exceptions import ActionInputError
 
 log = logging.getLogger( __name__ )
 
@@ -21,21 +21,21 @@
             except AssertionError:
                 create_amount = False
         if not params.name or not params.description:
-            raise MessageException( "Enter a valid name and a description.", type='error' )
+            raise ActionInputError( "Enter a valid name and a description." )
         elif self.sa_session.query( self.app.model.Quota ).filter( self.app.model.Quota.table.c.name==params.name ).first():
-            raise MessageException( "Quota names must be unique and a quota with that name already exists, so choose another name.", type='error' )
+            raise ActionInputError( "Quota names must be unique and a quota with that name already exists, so choose another name." )
         elif not params.get( 'amount', None ):
-            raise MessageException( "Enter a valid quota amount.", type='error' )
+            raise ActionInputError( "Enter a valid quota amount." )
         elif create_amount is False:
-            raise MessageException( "Unable to parse the provided amount.", type='error' )
+            raise ActionInputError( "Unable to parse the provided amount." )
         elif params.operation not in self.app.model.Quota.valid_operations:
-            raise MessageException( "Enter a valid operation.", type='error' )
+            raise ActionInputError( "Enter a valid operation." )
         elif params.default != 'no' and params.default not in self.app.model.DefaultQuotaAssociation.types.__dict__.values():
-            raise MessageException( "Enter a valid default type.", type='error' )
+            raise ActionInputError( "Enter a valid default type." )
         elif params.default != 'no' and params.operation != '=':
-            raise MessageException( "Operation for a default quota must be '='.", type='error' )
+            raise ActionInputError( "Operation for a default quota must be '='." )
         elif create_amount is None and params.operation != '=':
-            raise MessageException( "Operation for an unlimited quota must be '='.", type='error' )
+            raise ActionInputError( "Operation for an unlimited quota must be '='." )
         else:
             # Create the quota
             quota = self.app.model.Quota( name=params.name, description=params.description, amount=create_amount, operation=params.operation )
@@ -59,9 +59,9 @@
 
     def _rename_quota( self, quota, params ):
         if not params.name:
-            raise MessageException( 'Enter a valid name', type='error' )
+            raise ActionInputError( 'Enter a valid name' )
         elif params.name != quota.name and self.sa_session.query( self.app.model.Quota ).filter( self.app.model.Quota.table.c.name==params.name ).first():
-            raise MessageException( 'A quota with that name already exists', type='error' )
+            raise ActionInputError( 'A quota with that name already exists' )
         else:
             old_name = quota.name
             quota.name = params.name
@@ -73,7 +73,7 @@
 
     def _manage_users_and_groups_for_quota( self, quota, params ):
         if quota.default:
-            raise MessageException( 'Default quotas cannot be associated with specific users and groups', type='error' )
+            raise ActionInputError( 'Default quotas cannot be associated with specific users and groups' )
         else:
             in_users = [ self.sa_session.query( self.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
             in_groups = [ self.sa_session.query( self.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
@@ -91,11 +91,11 @@
             except AssertionError:
                 new_amount = False
         if not params.amount:
-            raise MessageException( 'Enter a valid amount', type='error' )
+            raise ActionInputError( 'Enter a valid amount' )
         elif new_amount is False:
-            raise MessageException( 'Unable to parse the provided amount', type='error' )
+            raise ActionInputError( 'Unable to parse the provided amount' )
         elif params.operation not in self.app.model.Quota.valid_operations:
-            raise MessageException( 'Enter a valid operation', type='error' )
+            raise ActionInputError( 'Enter a valid operation' )
         else:
             quota.amount = new_amount
             quota.operation = params.operation
@@ -106,7 +106,7 @@
 
     def _set_quota_default( self, quota, params ):
         if params.default != 'no' and params.default not in self.app.model.DefaultQuotaAssociation.types.__dict__.values():
-            raise MessageException( 'Enter a valid default type.', type='error' )
+            raise ActionInputError( 'Enter a valid default type.' )
         else:
             if params.default != 'no':
                 self.app.quota_agent.set_default_quota( params.default, quota )
@@ -123,7 +123,7 @@
 
     def _unset_quota_default( self, quota, params ):
         if not quota.default:
-            raise MessageException( "Quota '%s' is not a default." % quota.name, type='error' )
+            raise ActionInputError( "Quota '%s' is not a default." % quota.name )
         else:
             message = "Quota '%s' is no longer the default for %s users." % ( quota.name, quota.default[0].type )
             for dqa in quota.default:
@@ -138,9 +138,9 @@
             if q.default:
                 names.append( q.name )
         if len( names ) == 1:
-            raise MessageException( "Quota '%s' is a default, please unset it as a default before deleting it" % ( names[0] ), type='error' )
+            raise ActionInputError( "Quota '%s' is a default, please unset it as a default before deleting it" % ( names[0] ) )
         elif len( names ) > 1:
-            raise MessageException( "Quotas are defaults, please unset them as defaults before deleting them: " + ', '.join( names ), type='error' )
+            raise ActionInputError( "Quotas are defaults, please unset them as defaults before deleting them: " + ', '.join( names ) )
         message = "Deleted %d quotas: " % len( quotas )
         for q in quotas:
             q.deleted = True
@@ -157,9 +157,9 @@
             if not q.deleted:
                 names.append( q.name )
         if len( names ) == 1:
-            raise MessageException( "Quota '%s' has not been deleted, so it cannot be undeleted." % ( names[0] ), type='error' )
+            raise ActionInputError( "Quota '%s' has not been deleted, so it cannot be undeleted." % ( names[0] ) )
         elif len( names ) > 1:
-            raise MessageException( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ), type='error' )
+            raise ActionInputError( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ) )
         message = "Undeleted %d quotas: " % len( quotas )
         for q in quotas:
             q.deleted = False
@@ -182,9 +182,9 @@
             if not q.deleted:
                 names.append( q.name )
         if len( names ) == 1:
-            raise MessageException( "Quota '%s' has not been deleted, so it cannot be purged." % ( names[0] ), type='error' )
+            raise ActionInputError( "Quota '%s' has not been deleted, so it cannot be purged." % ( names[0] ) )
         elif len( names ) > 1:
-            raise MessageException( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ), type='error' )
+            raise ActionInputError( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ) )
         message = "Purged %d quotas: " % len( quotas )
         for q in quotas:
             # Delete UserQuotaAssociations
diff -r 8d8429484ac3ffef21fedd1077acf050c8aed459 -r e7e82003f235c4cca84939e04aa432d128093866 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -21,6 +21,10 @@
 class ItemOwnershipException( MessageException ):
     pass
 
+class ActionInputError( MessageException ):
+    def __init__( self, err_msg, type="error" ):
+        super( ActionInputError, self ).__init__( err_msg, type )
+
 class ObjectNotFound( Exception ):
     """ Accessed object was not found """
     pass
diff -r 8d8429484ac3ffef21fedd1077acf050c8aed459 -r e7e82003f235c4cca84939e04aa432d128093866 lib/galaxy/webapps/galaxy/api/quotas.py
--- a/lib/galaxy/webapps/galaxy/api/quotas.py
+++ b/lib/galaxy/webapps/galaxy/api/quotas.py
@@ -11,7 +11,7 @@
 from galaxy.actions.admin import AdminActions
 
 from paste.httpexceptions import HTTPBadRequest
-from galaxy.exceptions import *
+from galaxy.exceptions import ActionInputError
 
 log = logging.getLogger( __name__ )
 
https://bitbucket.org/galaxy/galaxy-central/commits/61ddd0d07950/
Changeset:   61ddd0d07950
User:        natefoo
Date:        2013-08-08 21:50:05
Summary:     Merge next-stable.
Affected #:  3 files
diff -r 3778811f053a5f71c68408a9e8beb627f6ccdede -r 61ddd0d07950fafc18fed95354459e7715e6b1d7 lib/galaxy/actions/admin.py
--- a/lib/galaxy/actions/admin.py
+++ b/lib/galaxy/actions/admin.py
@@ -4,7 +4,7 @@
 
 import logging
 from galaxy import util
-from galaxy.exceptions import MessageException
+from galaxy.exceptions import ActionInputError
 
 log = logging.getLogger( __name__ )
 
@@ -21,21 +21,21 @@
             except AssertionError:
                 create_amount = False
         if not params.name or not params.description:
-            raise MessageException( "Enter a valid name and a description.", type='error' )
+            raise ActionInputError( "Enter a valid name and a description." )
         elif self.sa_session.query( self.app.model.Quota ).filter( self.app.model.Quota.table.c.name==params.name ).first():
-            raise MessageException( "Quota names must be unique and a quota with that name already exists, so choose another name.", type='error' )
+            raise ActionInputError( "Quota names must be unique and a quota with that name already exists, so choose another name." )
         elif not params.get( 'amount', None ):
-            raise MessageException( "Enter a valid quota amount.", type='error' )
+            raise ActionInputError( "Enter a valid quota amount." )
         elif create_amount is False:
-            raise MessageException( "Unable to parse the provided amount.", type='error' )
+            raise ActionInputError( "Unable to parse the provided amount." )
         elif params.operation not in self.app.model.Quota.valid_operations:
-            raise MessageException( "Enter a valid operation.", type='error' )
+            raise ActionInputError( "Enter a valid operation." )
         elif params.default != 'no' and params.default not in self.app.model.DefaultQuotaAssociation.types.__dict__.values():
-            raise MessageException( "Enter a valid default type.", type='error' )
+            raise ActionInputError( "Enter a valid default type." )
         elif params.default != 'no' and params.operation != '=':
-            raise MessageException( "Operation for a default quota must be '='.", type='error' )
+            raise ActionInputError( "Operation for a default quota must be '='." )
         elif create_amount is None and params.operation != '=':
-            raise MessageException( "Operation for an unlimited quota must be '='.", type='error' )
+            raise ActionInputError( "Operation for an unlimited quota must be '='." )
         else:
             # Create the quota
             quota = self.app.model.Quota( name=params.name, description=params.description, amount=create_amount, operation=params.operation )
@@ -59,9 +59,9 @@
 
     def _rename_quota( self, quota, params ):
         if not params.name:
-            raise MessageException( 'Enter a valid name', type='error' )
+            raise ActionInputError( 'Enter a valid name' )
         elif params.name != quota.name and self.sa_session.query( self.app.model.Quota ).filter( self.app.model.Quota.table.c.name==params.name ).first():
-            raise MessageException( 'A quota with that name already exists', type='error' )
+            raise ActionInputError( 'A quota with that name already exists' )
         else:
             old_name = quota.name
             quota.name = params.name
@@ -73,7 +73,7 @@
 
     def _manage_users_and_groups_for_quota( self, quota, params ):
         if quota.default:
-            raise MessageException( 'Default quotas cannot be associated with specific users and groups', type='error' )
+            raise ActionInputError( 'Default quotas cannot be associated with specific users and groups' )
         else:
             in_users = [ self.sa_session.query( self.app.model.User ).get( x ) for x in util.listify( params.in_users ) ]
             in_groups = [ self.sa_session.query( self.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ]
@@ -91,11 +91,11 @@
             except AssertionError:
                 new_amount = False
         if not params.amount:
-            raise MessageException( 'Enter a valid amount', type='error' )
+            raise ActionInputError( 'Enter a valid amount' )
         elif new_amount is False:
-            raise MessageException( 'Unable to parse the provided amount', type='error' )
+            raise ActionInputError( 'Unable to parse the provided amount' )
         elif params.operation not in self.app.model.Quota.valid_operations:
-            raise MessageException( 'Enter a valid operation', type='error' )
+            raise ActionInputError( 'Enter a valid operation' )
         else:
             quota.amount = new_amount
             quota.operation = params.operation
@@ -106,7 +106,7 @@
 
     def _set_quota_default( self, quota, params ):
         if params.default != 'no' and params.default not in self.app.model.DefaultQuotaAssociation.types.__dict__.values():
-            raise MessageException( 'Enter a valid default type.', type='error' )
+            raise ActionInputError( 'Enter a valid default type.' )
         else:
             if params.default != 'no':
                 self.app.quota_agent.set_default_quota( params.default, quota )
@@ -123,7 +123,7 @@
 
     def _unset_quota_default( self, quota, params ):
         if not quota.default:
-            raise MessageException( "Quota '%s' is not a default." % quota.name, type='error' )
+            raise ActionInputError( "Quota '%s' is not a default." % quota.name )
         else:
             message = "Quota '%s' is no longer the default for %s users." % ( quota.name, quota.default[0].type )
             for dqa in quota.default:
@@ -138,9 +138,9 @@
             if q.default:
                 names.append( q.name )
         if len( names ) == 1:
-            raise MessageException( "Quota '%s' is a default, please unset it as a default before deleting it" % ( names[0] ), type='error' )
+            raise ActionInputError( "Quota '%s' is a default, please unset it as a default before deleting it" % ( names[0] ) )
         elif len( names ) > 1:
-            raise MessageException( "Quotas are defaults, please unset them as defaults before deleting them: " + ', '.join( names ), type='error' )
+            raise ActionInputError( "Quotas are defaults, please unset them as defaults before deleting them: " + ', '.join( names ) )
         message = "Deleted %d quotas: " % len( quotas )
         for q in quotas:
             q.deleted = True
@@ -157,9 +157,9 @@
             if not q.deleted:
                 names.append( q.name )
         if len( names ) == 1:
-            raise MessageException( "Quota '%s' has not been deleted, so it cannot be undeleted." % ( names[0] ), type='error' )
+            raise ActionInputError( "Quota '%s' has not been deleted, so it cannot be undeleted." % ( names[0] ) )
         elif len( names ) > 1:
-            raise MessageException( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ), type='error' )
+            raise ActionInputError( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ) )
         message = "Undeleted %d quotas: " % len( quotas )
         for q in quotas:
             q.deleted = False
@@ -182,9 +182,9 @@
             if not q.deleted:
                 names.append( q.name )
         if len( names ) == 1:
-            raise MessageException( "Quota '%s' has not been deleted, so it cannot be purged." % ( names[0] ), type='error' )
+            raise ActionInputError( "Quota '%s' has not been deleted, so it cannot be purged." % ( names[0] ) )
         elif len( names ) > 1:
-            raise MessageException( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ), type='error' )
+            raise ActionInputError( "Quotas have not been deleted so they cannot be undeleted: " + ', '.join( names ) )
         message = "Purged %d quotas: " % len( quotas )
         for q in quotas:
             # Delete UserQuotaAssociations
diff -r 3778811f053a5f71c68408a9e8beb627f6ccdede -r 61ddd0d07950fafc18fed95354459e7715e6b1d7 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -21,6 +21,10 @@
 class ItemOwnershipException( MessageException ):
     pass
 
+class ActionInputError( MessageException ):
+    def __init__( self, err_msg, type="error" ):
+        super( ActionInputError, self ).__init__( err_msg, type )
+
 class ObjectNotFound( Exception ):
     """ Accessed object was not found """
     pass
diff -r 3778811f053a5f71c68408a9e8beb627f6ccdede -r 61ddd0d07950fafc18fed95354459e7715e6b1d7 lib/galaxy/webapps/galaxy/api/quotas.py
--- a/lib/galaxy/webapps/galaxy/api/quotas.py
+++ b/lib/galaxy/webapps/galaxy/api/quotas.py
@@ -11,7 +11,7 @@
 from galaxy.actions.admin import AdminActions
 
 from paste.httpexceptions import HTTPBadRequest
-from galaxy.exceptions import *
+from galaxy.exceptions import ActionInputError
 
 log = logging.getLogger( __name__ )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: carlfeberhard: Graph datatype: add data providers for SIF & XGMML; simple graph data structure to util
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3778811f053a/
Changeset:   3778811f053a
User:        carlfeberhard
Date:        2013-08-08 21:04:53
Summary:     Graph datatype: add data providers for SIF & XGMML; simple graph data structure to util
Affected #:  2 files
diff -r 2e0abb7f9b04540e616458a90a87191ed98a3ba4 -r 3778811f053a5f71c68408a9e8beb627f6ccdede lib/galaxy/datatypes/graph.py
--- a/lib/galaxy/datatypes/graph.py
+++ b/lib/galaxy/datatypes/graph.py
@@ -2,12 +2,18 @@
 Graph content classes.
 """
 
-import data, tabular, xml
+import data
+import tabular
+import xml
+
+import dataproviders
+from galaxy.util import simplegraph
 
 import logging
 log = logging.getLogger( __name__ )
 
 
+(a)dataproviders.decorators.has_dataproviders
 class Xgmml( xml.GenericXml ):
     """
     XGMML graph format
@@ -48,7 +54,13 @@
         #For one file only, use base class method (move/copy)
         data.Text.merge( split_files, output_file )
 
+    @dataproviders.decorators.dataprovider_factory( 'node-edge', dataproviders.hierarchy.XMLDataProvider.settings )
+    def node_edge_dataprovider( self, dataset, **settings ):
+        dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+        return XGMMLGraphDataProvider( dataset_source, **settings )
 
+
+(a)dataproviders.decorators.has_dataproviders
 class Sif( tabular.Tabular ):
     """
     SIF graph format
@@ -75,7 +87,6 @@
         """
         Determines whether the file is SIF
         """
-        print '---------------------------------------- sniffing Siffing'
         line = ''
         with open( filename ) as infile:
             correct = True
@@ -92,6 +103,11 @@
     def merge( split_files, output_file ):
         data.Text.merge( split_files, output_file )
 
+    @dataproviders.decorators.dataprovider_factory( 'node-edge', dataproviders.column.ColumnarDataProvider.settings )
+    def node_edge_dataprovider( self, dataset, **settings ):
+        dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+        return SIFGraphDataProvider( dataset_source, **settings )
+
 
 #TODO: we might want to look at rdflib or a similar, larger lib/egg
 class Rdf( xml.GenericXml ):
@@ -108,3 +124,75 @@
         else:
             dataset.peek = 'file does not exist'
             dataset.blurb = 'file purged from disk'
+
+    #TODO: won't be as simple
+    #(a)dataproviders.decorators.dataprovider_factory( 'node-edge', dataproviders.column.ColumnarDataProvider.settings )
+    #def node_edge_dataprovider( self, dataset, **settings ):
+    #    dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+    #    return None
+
+
+# ----------------------------------------------------------------------------- graph specific data providers
+class XGMMLGraphDataProvider( dataproviders.hierarchy.XMLDataProvider ):
+    """
+    Provide two lists: nodes, edges::
+
+        'nodes': contains objects of the form:
+            { 'id' : <some string id>, 'data': <any extra data> }
+        'edges': contains objects of the form:
+            { 'source' : <an index into nodes>, 'target': <an index into nodes>, 'data': <any extra data> }
+    """
+    def __iter__( self ):
+        # use simple graph to store nodes and links, later providing them as a dict
+        #   essentially this is a form of aggregation
+        graph = simplegraph.SimpleGraph()
+
+        parent_gen = super( XGMMLGraphDataProvider, self ).__iter__()
+        for graph_elem in parent_gen:
+            if 'children' not in graph_elem:
+                continue
+            for elem in graph_elem[ 'children' ]:
+                # use endswith to work around Elementtree namespaces
+                if elem[ 'tag' ].endswith( 'node' ):
+                    node_id = elem[ 'attrib' ][ 'id' ]
+                    # pass the entire, parsed xml element as the data
+                    graph.add_node( node_id, **elem )
+
+                elif elem[ 'tag' ].endswith( 'edge' ):
+                    source_id = elem[ 'attrib' ][ 'source' ]
+                    target_id = elem[ 'attrib' ][ 'target' ]
+                    graph.add_edge( source_id, target_id, **elem )
+
+        yield graph.as_dict()
+
+
+class SIFGraphDataProvider( dataproviders.column.ColumnarDataProvider ):
+    """
+    Provide two lists: nodes, edges::
+
+        'nodes': contains objects of the form:
+            { 'id' : <some string id>, 'data': <any extra data> }
+        'edges': contains objects of the form:
+            { 'source' : <an index into nodes>, 'target': <an index into nodes>, 'data': <any extra data> }
+    """
+    def __iter__( self ):
+        # use simple graph to store nodes and links, later providing them as a dict
+        #   essentially this is a form of aggregation
+        graph = simplegraph.SimpleGraph()
+        # SIF is tabular with the source, link-type, and all targets in the columns
+        parent_gen = super( SIFGraphDataProvider, self ).__iter__()
+        for columns in parent_gen:
+            if columns:
+                source_id = columns[0]
+                # there's no extra data for nodes (or links) in the examples I've seen
+                graph.add_node( source_id )
+
+                # targets are the (variadic) remaining columns
+                if len( columns ) >= 3:
+                    relation = columns[1]
+                    targets = columns[2:]
+                    for target_id in targets:
+                        graph.add_node( target_id )
+                        graph.add_edge( source_id, target_id, type=relation )
+
+        yield graph.as_dict()
diff -r 2e0abb7f9b04540e616458a90a87191ed98a3ba4 -r 3778811f053a5f71c68408a9e8beb627f6ccdede lib/galaxy/util/simplegraph.py
--- /dev/null
+++ b/lib/galaxy/util/simplegraph.py
@@ -0,0 +1,127 @@
+"""
+Fencepost-simple graph structure implementation.
+"""
+# Currently (2013.7.12) only used in easing the parsing of graph datatype data.
+
+from galaxy.util.odict import odict
+
+
+class SimpleGraphNode( object ):
+    """
+    Node representation.
+    """
+    def __init__( self, index, **data ):
+        """
+        :param index: index of this node in some parent list
+        :type index: int
+        :param data: any extra data that needs to be saved
+        :type data: (variadic dictionary)
+        """
+        # a bit application specific (could be 'id')
+        self.index = index
+        self.data = data
+
+
+class SimpleGraphEdge( object ):
+    """
+    Edge representation.
+    """
+    def __init__( self, source_index, target_index, **data ):
+        """
+        :param source_index: index of the edge's source node in some parent list
+        :type source_index: int
+        :param target_index: index of the edge's target node in some parent list
+        :type target_index: int
+        :param data: any extra data that needs to be saved
+        :type data: (variadic dictionary)
+        """
+        self.source_index = source_index
+        self.target_index = target_index
+        self.data = data
+
+
+class SimpleGraph( object ):
+    """
+    Each node is unique (by id) and stores it's own index in the node list/odict.
+    Each edge is represented as two indeces into the node list/odict.
+    Both nodes and edges allow storing extra information if needed.
+
+    Allows:
+        multiple edges between two nodes
+        self referential edges (an edge from a node to itself)
+
+    These graphs are not specifically directed but since source and targets on the
+    edges are listed - it could easily be used that way.
+    """
+    def __init__( self, nodes=None, edges=None ):
+        # use an odict so that edge indeces actually match the final node list indeces
+        self.nodes = nodes or odict()
+        self.edges = edges or []
+
+    def add_node( self, node_id, **data ):
+        """
+        Adds a new node only if it doesn't already exist.
+        :param node_id: some unique identifier
+        :type node_id: (hashable)
+        :param data: any extra data that needs to be saved
+        :type data: (variadic dictionary)
+        :returns: the new node
+        """
+        if node_id in self.nodes:
+            return self.nodes[ node_id ]
+        node_index = len( self.nodes )
+        new_node = SimpleGraphNode( node_index, **data )
+        self.nodes[ node_id ] = new_node
+        return new_node
+
+    def add_edge( self, source_id, target_id, **data ):
+        """
+        Adds a new node only if it doesn't already exist.
+        :param source_id: the id of the source node
+        :type source_id: (hashable)
+        :param target_id: the id of the target node
+        :type target_id: (hashable)
+        :param data: any extra data that needs to be saved for the edge
+        :type data: (variadic dictionary)
+        :returns: the new node
+
+        ..note: that, although this will create new nodes if necessary, there's
+        no way to pass `data` to them - so if you need to assoc. more data with
+        the nodes, use `add_node` first.
+        """
+        # adds target_id to source_id's edge list
+        #   adding source_id and/or target_id to nodes if not there already
+        if source_id not in self.nodes:
+            self.add_node( source_id )
+        if target_id not in self.nodes:
+            self.add_node( target_id )
+        new_edge = SimpleGraphEdge( self.nodes[ source_id ].index, self.nodes[ target_id ].index, **data )
+        self.edges.append( new_edge )
+        return new_edge
+
+    def gen_node_dicts( self ):
+        """
+        Returns a generator that yields node dictionaries in the form:
+            { 'id': <the nodes unique id>, 'data': <any additional node data> }
+        """
+        for node_id, node in self.nodes.items():
+            yield { 'id': node_id, 'data': node.data }
+
+    def gen_edge_dicts( self ):
+        """
+        Returns a generator that yields node dictionaries in the form:
+            {
+                'source': <the index of the source node in the graph's node list>,
+                'target': <the index of the target node in the graph's node list>,
+                'data'  : <any additional edge data>
+            }
+        """
+        for edge in self.edges:
+            yield { 'source': edge.source_index, 'target': edge.target_index, 'data': edge.data }
+
+    def as_dict( self ):
+        """
+        Returns a dictionary of the form
+            { 'nodes': <a list of node dictionaries>, 'edges': <a list of node dictionaries> }
+        """
+        return { 'nodes': list( self.gen_node_dicts() ), 'edges': list( self.gen_edge_dicts() ) }
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: guerler: Update	jquery.autocomplete.js to 2.4.4
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/2e0abb7f9b04/
Changeset:   2e0abb7f9b04
User:        guerler
Date:        2013-08-08 20:22:42
Summary:     Update jquery.autocomplete.js to 2.4.4
Affected #:  2 files
diff -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 -r 2e0abb7f9b04540e616458a90a87191ed98a3ba4 static/scripts/galaxy.autocom_tagging.js
--- a/static/scripts/galaxy.autocom_tagging.js
+++ b/static/scripts/galaxy.autocom_tagging.js
@@ -100,12 +100,6 @@
 
             var new_value = this.value;
 
-            // Do nothing if return key was used to autocomplete.
-            if (return_key_pressed_for_autocomplete === true) {
-                return_key_pressed_for_autocomplete = false;
-                return false;
-            }
-
             // Suppress space after a ":"
             if ( new_value.indexOf(": ", new_value.length - 2) !== -1) {
                 this.value = new_value.substring(0, new_value.length-1);
@@ -166,7 +160,7 @@
                     // Flush autocomplete cache because it's not out of date.
                     // TODO: in the future, we could remove the particular item
                     // that was chosen from the cache rather than flush it.
-                    zz.flushCache();
+                    zz.data('autocompleter').cacheFlush();
                 }
             });
 
diff -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 -r 2e0abb7f9b04540e616458a90a87191ed98a3ba4 static/scripts/libs/jquery/jquery.autocomplete.js
--- a/static/scripts/libs/jquery/jquery.autocomplete.js
+++ b/static/scripts/libs/jquery/jquery.autocomplete.js
@@ -1,832 +1,1152 @@
-/*
- * Autocomplete - jQuery plugin 1.0.2
- *
- * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
- *
- * Dual licensed under the MIT and GPL licenses:
- *   http://www.opensource.org/licenses/mit-license.php
- *   http://www.gnu.org/licenses/gpl.html
- *
- * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
- *
+/**
+ * @fileOverview jquery-autocomplete, the jQuery Autocompleter
+ * @author <a href="mailto:dylan@dyve.net">Dylan Verheul</a>
+ * @version 2.4.4
+ * @requires jQuery 1.6+
+ * @license MIT | GPL | Apache 2.0, see LICENSE.txt
+ * @see https://github.com/dyve/jquery-autocomplete
  */
+(function($) {
+    "use strict";
 
-String.prototype.endsWith = function(str) {return (this.match(str+"$")==str)}
-
-// JG HACK: each autocomplete object should have its own return_key flag.
-var return_key_pressed_for_autocomplete = false;
-
-;(function($) {
-    
-$.fn.extend({
-    autocomplete: function(urlOrData, options) {
-        var isUrl = typeof urlOrData == "string";
-        options = $.extend({}, $.Autocompleter.defaults, {
-            url: isUrl ? urlOrData : null,
-            data: isUrl ? null : urlOrData,
-            delay: isUrl ? $.Autocompleter.defaults.delay : 10,
-            max: options && !options.scroll ? 10 : 150
-        }, options);
-        
-        // if highlight is set to false, replace it with a do-nothing function
-        options.highlight = options.highlight || function(value) { return value; };
-        
-        // if the formatMatch option is not specified, then use formatItem for backwards compatibility
-        options.formatMatch = options.formatMatch || options.formatItem;
-        
+    /**
+     * jQuery autocomplete plugin
+     * @param {object|string} options
+     * @returns (object} jQuery object
+     */
+    $.fn.autocomplete = function(options) {
+        var url;
+        if (arguments.length > 1) {
+            url = options;
+            options = arguments[1];
+            options.url = url;
+        } else if (typeof options === 'string') {
+            url = options;
+            options = { url: url };
+        }
+        var opts = $.extend({}, $.fn.autocomplete.defaults, options);
         return this.each(function() {
-            new $.Autocompleter(this, options);
+            var $this = $(this);
+            $this.data('autocompleter', new $.Autocompleter(
+                $this,
+                $.meta ? $.extend({}, opts, $this.data()) : opts
+            ));
         });
-    },
-    result: function(handler) {
-        return this.bind("result", handler);
-    },
-    search: function(handler) {
-        return this.trigger("search", [handler]);
-    },
-    flushCache: function() {
-      return this.trigger("flushCache");
-    },
-    setOptions: function(options){
-        return this.trigger("setOptions", [options]);
-    },
-    unautocomplete: function() {
-        return this.trigger("unautocomplete");
-    },
-    // JG: add method to show all data in cache.
-    showAllInCache: function() {
-        return this.trigger("showAllInCache");
-    }
-});
-
-$.Autocompleter = function(input, options) {
-
-    var KEY = {
-        UP: 38,
-        DOWN: 40,
-        DEL: 46,
-        TAB: 9,
-        RETURN: 13,
-        ESC: 27,
-        COMMA: 188,
-        PAGEUP: 33,
-        PAGEDOWN: 34,
-        BACKSPACE: 8,
-        COLON: 16
     };
 
-    // Create $ object for input element
-    var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+    /**
+     * Store default options
+     * @type {object}
+     */
+    $.fn.autocomplete.defaults = {
+        inputClass: 'acInput',
+        loadingClass: 'acLoading',
+        resultsClass: 'acResults',
+        selectClass: 'acSelect',
+        queryParamName: 'q',
+        extraParams: {},
+        remoteDataType: false,
+        lineSeparator: '\n',
+        cellSeparator: '|',
+        minChars: 2,
+        maxItemsToShow: 10,
+        delay: 400,
+        useCache: true,
+        maxCacheLength: 10,
+        matchSubset: true,
+        matchCase: false,
+        matchInside: true,
+        mustMatch: false,
+        selectFirst: false,
+        selectOnly: false,
+        showResult: null,
+        preventDefaultReturn: 1,
+        preventDefaultTab: 0,
+        autoFill: false,
+        filterResults: true,
+        filter: true,
+        sortResults: true,
+        sortFunction: null,
+        onItemSelect: null,
+        onNoMatch: null,
+        onFinish: null,
+        matchStringConverter: null,
+        beforeUseConverter: null,
+        autoWidth: 'min-width',
+        useDelimiter: false,
+        delimiterChar: ',',
+        delimiterKeyCode: 188,
+        processData: null,
+        onError: null,
+        enabled: true
+    };
 
-    var timeout;
-    var previousValue = "";
-    var cache = $.Autocompleter.Cache(options);
-    var hasFocus = 0;
-    var lastKeyPressCode;
-    var config = {
-        mouseDownOnSelect: false
+    /**
+     * Sanitize result
+     * @param {Object} result
+     * @returns {Object} object with members value (String) and data (Object)
+     * @private
+     */
+    var sanitizeResult = function(result) {
+        var value, data;
+        var type = typeof result;
+        if (type === 'string') {
+            value = result;
+            data = {};
+        } else if ($.isArray(result)) {
+            value = result[0];
+            data = result.slice(1);
+        } else if (type === 'object') {
+            value = result.value;
+            data = result.data;
+        }
+        value = String(value);
+        if (typeof data !== 'object') {
+            data = {};
+        }
+        return {
+            value: value,
+            data: data
+        };
     };
-    var select = $.Autocompleter.Select(options, input, selectCurrent, config);
-    
-    var blockSubmit;
-    
-    // prevent form submit in opera when selecting with return key
-    $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
-        if (blockSubmit) {
-            blockSubmit = false;
-            return false;
+
+    /**
+     * Sanitize integer
+     * @param {mixed} value
+     * @param {Object} options
+     * @returns {Number} integer
+     * @private
+     */
+    var sanitizeInteger = function(value, stdValue, options) {
+        var num = parseInt(value, 10);
+        options = options || {};
+        if (isNaN(num) || (options.min && num < options.min)) {
+            num = stdValue;
         }
-    });
-    
-    // Firefox only triggers holding down a key with keypress
-    $input.bind(($.browser.mozilla ? "keypress" : "keydown") + ".autocomplete", function(event) {
-        // track last key pressed
-        lastKeyPressCode = event.keyCode;       
-        switch(event.keyCode) {
-        
-            case KEY.UP:
-                event.preventDefault();
-                if ( select.visible() ) {
-                    select.prev();
-                } else {
-                    onChange(0, true);
-                }
+        return num;
+    };
+
+    /**
+     * Create partial url for a name/value pair
+     */
+    var makeUrlParam = function(name, value) {
+        return [name, encodeURIComponent(value)].join('=');
+    };
+
+    /**
+     * Build an url
+     * @param {string} url Base url
+     * @param {object} [params] Dictionary of parameters
+     */
+    var makeUrl = function(url, params) {
+        var urlAppend = [];
+        $.each(params, function(index, value) {
+            urlAppend.push(makeUrlParam(index, value));
+        });
+        if (urlAppend.length) {
+            url += url.indexOf('?') === -1 ? '?' : '&';
+            url += urlAppend.join('&');
+        }
+        return url;
+    };
+
+    /**
+     * Default sort filter
+     * @param {object} a
+     * @param {object} b
+     * @param {boolean} matchCase
+     * @returns {number}
+     */
+    var sortValueAlpha = function(a, b, matchCase) {
+        a = String(a.value);
+        b = String(b.value);
+        if (!matchCase) {
+            a = a.toLowerCase();
+            b = b.toLowerCase();
+        }
+        if (a > b) {
+            return 1;
+        }
+        if (a < b) {
+            return -1;
+        }
+        return 0;
+    };
+
+    /**
+     * Parse data received in text format
+     * @param {string} text Plain text input
+     * @param {string} lineSeparator String that separates lines
+     * @param {string} cellSeparator String that separates cells
+     * @returns {array} Array of autocomplete data objects
+     */
+    var plainTextParser = function(text, lineSeparator, cellSeparator) {
+        var results = [];
+        var i, j, data, line, value, lines;
+        // Be nice, fix linebreaks before splitting on lineSeparator
+        lines = String(text).replace('\r\n', '\n').split(lineSeparator);
+        for (i = 0; i < lines.length; i++) {
+            line = lines[i].split(cellSeparator);
+            data = [];
+            for (j = 0; j < line.length; j++) {
+                data.push(decodeURIComponent(line[j]));
+            }
+            value = data.shift();
+            results.push({ value: value, data: data });
+        }
+        return results;
+    };
+
+    /**
+     * Autocompleter class
+     * @param {object} $elem jQuery object with one input tag
+     * @param {object} options Settings
+     * @constructor
+     */
+    $.Autocompleter = function($elem, options) {
+
+        /**
+         * Assert parameters
+         */
+        if (!$elem || !($elem instanceof $) || $elem.length !== 1 || ($elem.get(0).tagName.toUpperCase() !== 'INPUT' && $elem.get(0).tagName.toUpperCase() !== 'TEXTAREA')) {
+            throw new Error('Invalid parameter for jquery.Autocompleter, jQuery object with one element with INPUT or TEXTAREA tag expected.');
+        }
+
+        /**
+         * @constant Link to this instance
+         * @type object
+         * @private
+         */
+        var self = this;
+
+        /**
+         * @property {object} Options for this instance
+         * @public
+         */
+        this.options = options;
+
+        /**
+         * @property object Cached data for this instance
+         * @private
+         */
+        this.cacheData_ = {};
+
+        /**
+         * @property {number} Number of cached data items
+         * @private
+         */
+        this.cacheLength_ = 0;
+
+        /**
+         * @property {string} Class name to mark selected item
+         * @private
+         */
+        this.selectClass_ = 'jquery-autocomplete-selected-item';
+
+        /**
+         * @property {number} Handler to activation timeout
+         * @private
+         */
+        this.keyTimeout_ = null;
+
+        /**
+         * @property {number} Handler to finish timeout
+         * @private
+         */
+        this.finishTimeout_ = null;
+
+        /**
+         * @property {number} Last key pressed in the input field (store for behavior)
+         * @private
+         */
+        this.lastKeyPressed_ = null;
+
+        /**
+         * @property {string} Last value processed by the autocompleter
+         * @private
+         */
+        this.lastProcessedValue_ = null;
+
+        /**
+         * @property {string} Last value selected by the user
+         * @private
+         */
+        this.lastSelectedValue_ = null;
+
+        /**
+         * @property {boolean} Is this autocompleter active (showing results)?
+         * @see showResults
+         * @private
+         */
+        this.active_ = false;
+
+        /**
+         * @property {boolean} Is this autocompleter allowed to finish on blur?
+         * @private
+         */
+        this.finishOnBlur_ = true;
+
+        /**
+         * Sanitize options
+         */
+        this.options.minChars = sanitizeInteger(this.options.minChars, $.fn.autocomplete.defaults.minChars, { min: 0 });
+        this.options.maxItemsToShow = sanitizeInteger(this.options.maxItemsToShow, $.fn.autocomplete.defaults.maxItemsToShow, { min: 0 });
+        this.options.maxCacheLength = sanitizeInteger(this.options.maxCacheLength, $.fn.autocomplete.defaults.maxCacheLength, { min: 1 });
+        this.options.delay = sanitizeInteger(this.options.delay, $.fn.autocomplete.defaults.delay, { min: 0 });
+        if (this.options.preventDefaultReturn != 2) {
+            this.options.preventDefaultReturn = this.options.preventDefaultReturn ? 1 : 0;
+        }
+        if (this.options.preventDefaultTab != 2) {
+            this.options.preventDefaultTab = this.options.preventDefaultTab ? 1 : 0;
+        }
+
+        /**
+         * Init DOM elements repository
+         */
+        this.dom = {};
+
+        /**
+         * Store the input element we're attached to in the repository
+         */
+        this.dom.$elem = $elem;
+
+        /**
+         * Switch off the native autocomplete and add the input class
+         */
+        this.dom.$elem.attr('autocomplete', 'off').addClass(this.options.inputClass);
+
+        /**
+         * Create DOM element to hold results, and force absolute position
+         */
+        this.dom.$results = $('<div></div>').hide().addClass(this.options.resultsClass).css({
+            position: 'absolute'
+        });
+        $('body').append(this.dom.$results);
+
+        /**
+         * Attach keyboard monitoring to $elem
+         */
+        $elem.keydown(function(e) {
+            self.lastKeyPressed_ = e.keyCode;
+            switch(self.lastKeyPressed_) {
+
+                case self.options.delimiterKeyCode: // comma = 188
+                    if (self.options.useDelimiter && self.active_) {
+                        self.selectCurrent();
+                    }
+                    break;
+
+                // ignore navigational & special keys
+                case 35: // end
+                case 36: // home
+                case 16: // shift
+                case 17: // ctrl
+                case 18: // alt
+                case 37: // left
+                case 39: // right
+                    break;
+
+                case 38: // up
+                    e.preventDefault();
+                    if (self.active_) {
+                        self.focusPrev();
+                    } else {
+                        self.activate();
+                    }
+                    return false;
+
+                case 40: // down
+                    e.preventDefault();
+                    if (self.active_) {
+                        self.focusNext();
+                    } else {
+                        self.activate();
+                    }
+                    return false;
+
+                case 9: // tab
+                    if (self.active_) {
+                        self.selectCurrent();
+                        if (self.options.preventDefaultTab) {
+                            e.preventDefault();
+                            return false;
+                        }
+                    }
+                    if (self.options.preventDefaultTab === 2) {
+                        e.preventDefault();
+                        return false;
+                    }
                 break;
-                
-            case KEY.DOWN:
-                event.preventDefault();
-                if ( select.visible() ) {
-                    select.next();
-                } else {
-                    onChange(0, true);
-                }
+
+                case 13: // return
+                    if (self.active_) {
+                        self.selectCurrent();
+                        if (self.options.preventDefaultReturn) {
+                            e.preventDefault();
+                            return false;
+                        }
+                    }
+                    if (self.options.preventDefaultReturn === 2) {
+                        e.preventDefault();
+                        return false;
+                    }
                 break;
-                
-            case KEY.PAGEUP:
-                event.preventDefault();
-                if ( select.visible() ) {
-                    select.pageUp();
-                } else {
-                    onChange(0, true);
-                }
+
+                case 27: // escape
+                    if (self.active_) {
+                        e.preventDefault();
+                        self.deactivate(true);
+                        return false;
+                    }
                 break;
-                
-            case KEY.PAGEDOWN:
-                event.preventDefault();
-                if ( select.visible() ) {
-                    select.pageDown();
-                } else {
-                    onChange(0, true);
-                }
-                break;
-            
-            // matches also semicolon
-            case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
-            case KEY.TAB:
-            case KEY.RETURN:
-                if (event.keyCode == KEY.RETURN)
-                    return_key_pressed_for_autocomplete = false;
-                if( selectCurrent() ) {
-                    // stop default to prevent a form submit, Opera needs special handling
-                    event.preventDefault();
-                    blockSubmit = true;
-                    
-                    // JG: set flag to indicate that a selection just occurred using the return key. FYI:
-                    // event.stopPropagation() does not work.
-                    if (event.keyCode == KEY.RETURN)
-                        return_key_pressed_for_autocomplete = true;
-                    
-                    return false;
-                }
-                
-            case KEY.ESC:
-                select.hide();
-                break;
-            case KEY.COLON:
-                break;
-                
-            default:
-                clearTimeout(timeout);
-                timeout = setTimeout(onChange, options.delay);
-                break;
-        }
-    }).focus(function(){
-        // track whether the field has focus, we shouldn't process any
-        // results if the field no longer has focus
-        hasFocus++;
-    }).blur(function() {
-        hasFocus = 0;
-        if (!config.mouseDownOnSelect) {
-            // JG: if blur and user is not selecting with mouse, hide
-            // object.
-          select.hide();
-        }
-        return this;
-    }).click(function() {
-        // show select when clicking in a focused field
-        if ( hasFocus++ > 1 && !select.visible() ) {
-            onChange(0, true);
-        }
-        return this;
-      }).bind("search", function() {
-        // TODO why not just specifying both arguments?
-        var fn = (arguments.length > 1) ? arguments[1] : null;
-        function findValueCallback(q, data) {
-            var result;
-            if( data && data.length ) {
-                for (var i=0; i < data.length; i++) {
-                    if( data[i].result.toLowerCase() == q.toLowerCase() ) {
-                        result = data[i];
-                        break;
-                    }
-                }
+
+                default:
+                    self.activate();
+
             }
-            if( typeof fn == "function" ) fn(result);
-            else $input.trigger("result", result && [result.data, result.value]);
-        }
-        $.each(trimWords($input.val()), function(i, value) {
-            request(value, findValueCallback, findValueCallback);
         });
 
-        return this;
-    }).bind("flushCache", function() {
-        cache.flush();
-    }).bind("setOptions", function() {
-        $.extend(options, arguments[1]);
-        // if we've updated the data, repopulate
-        if ( "data" in arguments[1] )
-            cache.populate();
-    }).bind("unautocomplete", function() {
-        select.unbind();
-        $input.unbind();
-        $(input.form).unbind(".autocomplete");
-    })
-    // JG: Show all data in cache.
-    .bind("showAllInCache", function() {
-        receiveData('', cache.load(''));
-    });
-    
-    function selectCurrent() {
-        var selected = select.selected();
-        if( !selected )
-            return false;
-        
-        var v = selected.result;
-        previousValue = v;
-        
-        if ( options.multiple ) {
-            var words = trimWords($input.val());
-            if ( words.length > 1 ) {
-                v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
+        /**
+         * Attach paste event listener because paste may occur much later then keydown or even without a keydown at all
+         */
+        $elem.on('paste', function() {
+            self.activate();
+        });
+
+        /**
+         * Finish on blur event
+         * Use a timeout because instant blur gives race conditions
+         */
+        var onBlurFunction = function() {
+            self.deactivate(true);
+        }
+        $elem.blur(function() {
+            if (self.finishOnBlur_) {
+                self.finishTimeout_ = setTimeout(onBlurFunction, 200);
             }
-            v += options.multipleSeparator;
+        });
+        /**
+         * Catch a race condition on form submit
+         */
+        $elem.parents('form').on('submit', onBlurFunction);
+
+    };
+
+    /**
+     * Position output DOM elements
+     * @private
+     */
+    $.Autocompleter.prototype.position = function() {
+        var offset = this.dom.$elem.offset();
+        var height = this.dom.$results.outerHeight();
+        var totalHeight = $(window).outerHeight();
+        var inputBottom = offset.top + this.dom.$elem.outerHeight();
+        var bottomIfDown = inputBottom + height;
+        // Set autocomplete results at the bottom of input
+        var position = {top: inputBottom, left: offset.left};
+        if (bottomIfDown > totalHeight) {
+            // Try to set autocomplete results at the top of input
+            var topIfUp = offset.top - height;
+            if (topIfUp >= 0) {
+                position.top = topIfUp;
+            }
         }
-        
-        $input.val(v);
-        hideResultsNow();
-        $input.trigger("result", [selected.data, selected.value]);
-        return true;
-    }
-    
-    function onChange(crap, skipPrevCheck) {
-        if( lastKeyPressCode == KEY.DEL ) {
-            select.hide();
-            return;
+        this.dom.$results.css(position);
+    };
+
+    /**
+     * Read from cache
+     * @private
+     */
+    $.Autocompleter.prototype.cacheRead = function(filter) {
+        var filterLength, searchLength, search, maxPos, pos;
+        if (this.options.useCache) {
+            filter = String(filter);
+            filterLength = filter.length;
+            if (this.options.matchSubset) {
+                searchLength = 1;
+            } else {
+                searchLength = filterLength;
+            }
+            while (searchLength <= filterLength) {
+                if (this.options.matchInside) {
+                    maxPos = filterLength - searchLength;
+                } else {
+                    maxPos = 0;
+                }
+                pos = 0;
+                while (pos <= maxPos) {
+                    search = filter.substr(0, searchLength);
+                    if (this.cacheData_[search] !== undefined) {
+                        return this.cacheData_[search];
+                    }
+                    pos++;
+                }
+                searchLength++;
+            }
         }
+        return false;
+    };
 
-        var currentValue = $input.val();
-        
-        if ( !skipPrevCheck && currentValue == previousValue )
-            return;
-        
-        previousValue = currentValue;
-        
-        currentValue = lastWord(currentValue);
-        if ( currentValue.length >= options.minChars) {
-            $input.addClass(options.loadingClass);
-            if (!options.matchCase)
-                currentValue = currentValue.toLowerCase();
-            request(currentValue, receiveData, hideResultsNow);
-        } else {
-            stopLoading();
-            select.hide();
+    /**
+     * Write to cache
+     * @private
+     */
+    $.Autocompleter.prototype.cacheWrite = function(filter, data) {
+        if (this.options.useCache) {
+            if (this.cacheLength_ >= this.options.maxCacheLength) {
+                this.cacheFlush();
+            }
+            filter = String(filter);
+            if (this.cacheData_[filter] !== undefined) {
+                this.cacheLength_++;
+            }
+            this.cacheData_[filter] = data;
+            return this.cacheData_[filter];
         }
+        return false;
     };
-    
-    function trimWords(value) {
-        if ( !value ) {
-            return [""];
+
+    /**
+     * Flush cache
+     * @public
+     */
+    $.Autocompleter.prototype.cacheFlush = function() {
+        this.cacheData_ = {};
+        this.cacheLength_ = 0;
+    };
+
+    /**
+     * Call hook
+     * Note that all called hooks are passed the autocompleter object
+     * @param {string} hook
+     * @param data
+     * @returns Result of called hook, false if hook is undefined
+     */
+    $.Autocompleter.prototype.callHook = function(hook, data) {
+        var f = this.options[hook];
+        if (f && $.isFunction(f)) {
+            return f(data, this);
         }
-        var words = value.split( options.multipleSeparator );
-        var result = [];
-        $.each(words, function(i, value) {
-            if ( $.trim(value) )
-                result[i] = $.trim(value);
-        });
-        return result;
-    }
-    
-    function lastWord(value) {
-        if ( !options.multiple )
-            return value;
-        var words = trimWords(value);
-        return words[words.length - 1];
-    }
-    
-    // fills in the input box w/the first match (assumed to be the best match)
-    // q: the term entered
-    // sValue: the first matching result
-    function autoFill(q, sValue){
-        // autofill in the complete box w/the first match as long as the user hasn't entered in more data
-        // if the last user key pressed was backspace, don't autofill
-        if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
-            // fill in the value (keep the case the user has typed)
-            $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
-            // select the portion of the value not typed by the user (so the next character will erase)
-            $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
+        return false;
+    };
+
+    /**
+     * Set timeout to activate autocompleter
+     */
+    $.Autocompleter.prototype.activate = function() {
+        if (!this.options.enabled) return;
+        var self = this;
+        if (this.keyTimeout_) {
+            clearTimeout(this.keyTimeout_);
+        }
+        this.keyTimeout_ = setTimeout(function() {
+            self.activateNow();
+        }, this.options.delay);
+    };
+
+    /**
+     * Activate autocompleter immediately
+     */
+    $.Autocompleter.prototype.activateNow = function() {
+        var value = this.beforeUseConverter(this.dom.$elem.val());
+        if (value !== this.lastProcessedValue_ && value !== this.lastSelectedValue_) {
+            this.fetchData(value);
         }
     };
 
-    function hideResults() {
-        clearTimeout(timeout);
-        timeout = setTimeout(hideResultsNow, 200);
-    };
-
-    function hideResultsNow() {
-        var wasVisible = select.visible();
-        select.hide();
-        clearTimeout(timeout);
-        stopLoading();
-        if (options.mustMatch) {
-            // call search and run callback
-            $input.search(
-                function (result){
-                    // if no value found, clear the input box
-                    if( !result ) {
-                        if (options.multiple) {
-                            var words = trimWords($input.val()).slice(0, -1);
-                            $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
-                        }
-                        else
-                            $input.val( "" );
-                    }
-                }
-            );
-        }
-        if (wasVisible)
-            // position cursor at end of input field
-            $.Autocompleter.Selection(input, input.value.length, input.value.length);
-    };
-
-    function receiveData(q, data) {
-        if ( data && data.length && hasFocus ) {
-            stopLoading();
-            select.display(data, q);
-            autoFill(q, data[0].value);
-            select.show();
+    /**
+     * Get autocomplete data for a given value
+     * @param {string} value Value to base autocompletion on
+     * @private
+     */
+    $.Autocompleter.prototype.fetchData = function(value) {
+        var self = this;
+        var processResults = function(results, filter) {
+            if (self.options.processData) {
+                results = self.options.processData(results);
+            }
+            self.showResults(self.filterResults(results, filter), filter);
+        };
+        this.lastProcessedValue_ = value;
+        if (value.length < this.options.minChars) {
+            processResults([], value);
+        } else if (this.options.data) {
+            processResults(this.options.data, value);
         } else {
-            hideResultsNow();
+            this.fetchRemoteData(value, function(remoteData) {
+                processResults(remoteData, value);
+            });
         }
     };
 
-    function request(term, success, failure) {
-        if (!options.matchCase)
-            term = term.toLowerCase();
-        var data = cache.load(term);
-
-        // JG: hack: if term ends with ':', kill data to force an ajax request.
-        if (term.endsWith(":"))
-          data = null;
-
-        // recieve the cached data
-        if (data && data.length) {
-            success(term, data);
-        // if an AJAX url has been supplied, try loading the data now
-        } else if( (typeof options.url == "string") && (options.url.length > 0) ){
-            var extraParams = {
-                timestamp: +new Date()
+    /**
+     * Get remote autocomplete data for a given value
+     * @param {string} filter The filter to base remote data on
+     * @param {function} callback The function to call after data retrieval
+     * @private
+     */
+    $.Autocompleter.prototype.fetchRemoteData = function(filter, callback) {
+        var data = this.cacheRead(filter);
+        if (data) {
+            callback(data);
+        } else {
+            var self = this;
+            var dataType = self.options.remoteDataType === 'json' ? 'json' : 'text';
+            var ajaxCallback = function(data) {
+                var parsed = false;
+                if (data !== false) {
+                    parsed = self.parseRemoteData(data);
+                    self.cacheWrite(filter, parsed);
+                }
+                self.dom.$elem.removeClass(self.options.loadingClass);
+                callback(parsed);
             };
-            $.each(options.extraParams, function(key, param) {
-                extraParams[key] = typeof param == "function" ? param() : param;
+            this.dom.$elem.addClass(this.options.loadingClass);
+            $.ajax({
+                url: this.makeUrl(filter),
+                success: ajaxCallback,
+                error: function(jqXHR, textStatus, errorThrown) {
+                    if($.isFunction(self.options.onError)) {
+                        self.options.onError(jqXHR, textStatus, errorThrown);
+                    } else {
+                      ajaxCallback(false);
+                    }
+                },
+                dataType: dataType
             });
-            
-            $.ajax({
-                // try to leverage ajaxQueue plugin to abort previous requests
-                mode: "abort",
-                // limit abortion to this input
-                port: "autocomplete" + input.name,
-                dataType: options.dataType,
-                url: options.url,
-                data: $.extend({
-                    q: lastWord(term),
-                    limit: options.max
-                }, extraParams),
-                success: function(data) {
-                    var parsed = options.parse && options.parse(data) || parse(data);
-                    cache.add(term, parsed);
-                    success(term, parsed);
-                }
-            });
-        } else {
-            // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
-            select.emptyList();
-            failure(term);
         }
     };
-    
-    function parse(data) {
-        var parsed = [];
-        var rows = data.split("\n");
-        for (var i=0; i < rows.length; i++) {
-            var row = $.trim(rows[i]);
-            if (row) {
-                row = row.split("|");
-                parsed[parsed.length] = {
-                    data: row,
-                    value: row[0],
-                    result: options.formatResult && options.formatResult(row, row[0]) || row[0]
-                };
+
+    /**
+     * Create or update an extra parameter for the remote request
+     * @param {string} name Parameter name
+     * @param {string} value Parameter value
+     * @public
+     */
+    $.Autocompleter.prototype.setExtraParam = function(name, value) {
+        var index = $.trim(String(name));
+        if (index) {
+            if (!this.options.extraParams) {
+                this.options.extraParams = {};
+            }
+            if (this.options.extraParams[index] !== value) {
+                this.options.extraParams[index] = value;
+                this.cacheFlush();
             }
         }
-        return parsed;
+
+        return this;
     };
 
-    function stopLoading() {
-        $input.removeClass(options.loadingClass);
+    /**
+     * Build the url for a remote request
+     * If options.queryParamName === false, append query to url instead of using a GET parameter
+     * @param {string} param The value parameter to pass to the backend
+     * @returns {string} The finished url with parameters
+     */
+    $.Autocompleter.prototype.makeUrl = function(param) {
+        var self = this;
+        var url = this.options.url;
+        var params = $.extend({}, this.options.extraParams);
+
+        if (this.options.queryParamName === false) {
+            url += encodeURIComponent(param);
+        } else {
+            params[this.options.queryParamName] = param;
+        }
+
+        return makeUrl(url, params);
     };
 
-};
+    /**
+     * Parse data received from server
+     * @param remoteData Data received from remote server
+     * @returns {array} Parsed data
+     */
+    $.Autocompleter.prototype.parseRemoteData = function(remoteData) {
+        var remoteDataType;
+        var data = remoteData;
+        if (this.options.remoteDataType === 'json') {
+            remoteDataType = typeof(remoteData);
+            switch (remoteDataType) {
+                case 'object':
+                    data = remoteData;
+                    break;
+                case 'string':
+                    data = $.parseJSON(remoteData);
+                    break;
+                default:
+                    throw new Error("Unexpected remote data type: " + remoteDataType);
+            }
+            return data;
+        }
+        return plainTextParser(data, this.options.lineSeparator, this.options.cellSeparator);
+    };
 
-$.Autocompleter.defaults = {
-    inputClass: "ac_input",
-    resultsClass: "ac_results",
-    loadingClass: "ac_loading",
-    minChars: 1,
-    delay: 400,
-    matchCase: false,
-    matchSubset: true,
-    matchContains: false,
-    cacheLength: 10,
-    max: 100,
-    mustMatch: false,
-    extraParams: {},
-    selectFirst: true,
-    formatItem: function(row) { return row[0]; },
-    formatMatch: null,
-    autoFill: false,
-    width: 0,
-    multiple: false,
-    multipleSeparator: ", ",
-    highlight: function(value, term) {
-        // JG: short-circuit highlighting if term is empty string.
-        if (term == "")
-            return value;
-        return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
-    },
-    scroll: true,
-    scrollHeight: 180
-};
+    /**
+     * Default filter for results
+     * @param {Object} result
+     * @param {String} filter
+     * @returns {boolean} Include this result
+     * @private
+     */
+    $.Autocompleter.prototype.defaultFilter = function(result, filter) {
+        if (!result.value) {
+            return false;
+        }
+        if (this.options.filterResults) {
+            var pattern = this.matchStringConverter(filter);
+            var testValue = this.matchStringConverter(result.value);
+            if (!this.options.matchCase) {
+                pattern = pattern.toLowerCase();
+                testValue = testValue.toLowerCase();
+            }
+            var patternIndex = testValue.indexOf(pattern);
+            if (this.options.matchInside) {
+                return patternIndex > -1;
+            } else {
+                return patternIndex === 0;
+            }
+        }
+        return true;
+    };
 
-$.Autocompleter.Cache = function(options) {
+    /**
+     * Filter result
+     * @param {Object} result
+     * @param {String} filter
+     * @returns {boolean} Include this result
+     * @private
+     */
+    $.Autocompleter.prototype.filterResult = function(result, filter) {
+        // No filter
+        if (this.options.filter === false) {
+            return true;
+        }
+        // Custom filter
+        if ($.isFunction(this.options.filter)) {
+            return this.options.filter(result, filter);
+        }
+        // Default filter
+        return this.defaultFilter(result, filter);
+    };
 
-    var data = {};
-    var length = 0;
+    /**
+     * Filter results
+     * @param results
+     * @param filter
+     */
+    $.Autocompleter.prototype.filterResults = function(results, filter) {
+        var filtered = [];
+        var i, result;
 
-    function matchSubset(s, sub) {
-        if (!options.matchCase) 
-            s = s.toLowerCase();
-        var i = s.indexOf(sub);
-        if (i == -1) return false;
-        return i == 0 || options.matchContains;
+        for (i = 0; i < results.length; i++) {
+            result = sanitizeResult(results[i]);
+            if (this.filterResult(result, filter)) {
+                filtered.push(result);
+            }
+        }
+        if (this.options.sortResults) {
+            filtered = this.sortResults(filtered, filter);
+        }
+        if (this.options.maxItemsToShow > 0 && this.options.maxItemsToShow < filtered.length) {
+            filtered.length = this.options.maxItemsToShow;
+        }
+        return filtered;
     };
-    
-    function add(q, value) {
-        if (length > options.cacheLength){
-            flush();
+
+    /**
+     * Sort results
+     * @param results
+     * @param filter
+     */
+    $.Autocompleter.prototype.sortResults = function(results, filter) {
+        var self = this;
+        var sortFunction = this.options.sortFunction;
+        if (!$.isFunction(sortFunction)) {
+            sortFunction = function(a, b, f) {
+                return sortValueAlpha(a, b, self.options.matchCase);
+            };
         }
-        if (!data[q]){ 
-            length++;
+        results.sort(function(a, b) {
+            return sortFunction(a, b, filter, self.options);
+        });
+        return results;
+    };
+
+    /**
+     * Convert string before matching
+     * @param s
+     * @param a
+     * @param b
+     */
+    $.Autocompleter.prototype.matchStringConverter = function(s, a, b) {
+        var converter = this.options.matchStringConverter;
+        if ($.isFunction(converter)) {
+            s = converter(s, a, b);
         }
-        data[q] = value;
-    }
-    
-    function populate(){
-        if( !options.data ) return false;
-        // track the matches
-        var stMatchSets = {},
-            nullData = 0;
+        return s;
+    };
 
-        // no url was specified, we need to adjust the cache length to make sure it fits the local data store
-        if( !options.url ) options.cacheLength = 1;
-        
-        // track all options for minChars = 0
-        stMatchSets[""] = [];
-        
-        // loop through the array and create a lookup structure
-        for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
-            var rawValue = options.data[i];
-            // if rawValue is a string, make an array otherwise just reference the array
-            rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
-            
-            var value = options.formatMatch(rawValue, i+1, options.data.length);
-            if ( value === false )
-                continue;
-            
-            if( !stMatchSets[value] ) {
-                stMatchSets[value] = [];
+    /**
+     * Convert string before use
+     * @param {String} s
+     */
+    $.Autocompleter.prototype.beforeUseConverter = function(s) {
+        s = this.getValue(s);
+        var converter = this.options.beforeUseConverter;
+        if ($.isFunction(converter)) {
+            s = converter(s);
+        }
+        return s;
+    };
+
+    /**
+     * Enable finish on blur event
+     */
+    $.Autocompleter.prototype.enableFinishOnBlur = function() {
+        this.finishOnBlur_ = true;
+    };
+
+    /**
+     * Disable finish on blur event
+     */
+    $.Autocompleter.prototype.disableFinishOnBlur = function() {
+        this.finishOnBlur_ = false;
+    };
+
+    /**
+     * Create a results item (LI element) from a result
+     * @param result
+     */
+    $.Autocompleter.prototype.createItemFromResult = function(result) {
+        var self = this;
+        var $li = $('<li/>');
+        $li.html(this.showResult(result.value, result.data));
+        $li.data({value: result.value, data: result.data})
+            .click(function() {
+                self.selectItem($li);
+            })
+            .mousedown(self.disableFinishOnBlur)
+            .mouseup(self.enableFinishOnBlur)
+        ;
+        return $li;
+    };
+
+    /**
+     * Get all items from the results list
+     * @param result
+     */
+    $.Autocompleter.prototype.getItems = function() {
+        return $('>ul>li', this.dom.$results);
+    };
+
+    /**
+     * Show all results
+     * @param results
+     * @param filter
+     */
+    $.Autocompleter.prototype.showResults = function(results, filter) {
+        var numResults = results.length;
+        var self = this;
+        var $ul = $('<ul></ul>');
+        var i, result, $li, autoWidth, first = false, $first = false;
+
+        if (numResults) {
+            for (i = 0; i < numResults; i++) {
+                result = results[i];
+                $li = this.createItemFromResult(result);
+                $ul.append($li);
+                if (first === false) {
+                    first = String(result.value);
+                    $first = $li;
+                    $li.addClass(this.options.firstItemClass);
+                }
+                if (i === numResults - 1) {
+                    $li.addClass(this.options.lastItemClass);
+                }
             }
 
-            // if the match is a string
-            var row = {
-                value: value,
-                data: rawValue,
-                result: options.formatResult && options.formatResult(rawValue) || value
-            };
-            
-            // push the current match into the set list
-            stMatchSets[value].push(row);
+            this.dom.$results.html($ul).show();
 
-            // keep track of minChars zero items
-            if ( nullData++ < options.max ) {
-                stMatchSets[""].push(row);
+            // Always recalculate position since window size or
+            // input element location may have changed.
+            this.position();
+            if (this.options.autoWidth) {
+                autoWidth = this.dom.$elem.outerWidth() - this.dom.$results.outerWidth() + this.dom.$results.width();
+                this.dom.$results.css(this.options.autoWidth, autoWidth);
             }
-        };
-
-        // add the data items to the cache
-        $.each(stMatchSets, function(i, value) {
-            // increase the cache size
-            options.cacheLength++;
-            // add to the cache
-            add(i, value);
-        });
-    }
-    
-    // populate any existing data
-    setTimeout(populate, 25);
-    
-    function flush(){
-        data = {};
-        length = 0;
-    }
-    
-    return {
-        flush: flush,
-        add: add,
-        populate: populate,
-        load: function(q) {
-            if (!options.cacheLength || !length)
-                return null;
-
-            /* 
-             * if dealing w/local data and matchContains than we must make sure
-             * to loop through all the data collections looking for matches
-             */
-            if( !options.url && options.matchContains ) {
-                // track all matches
-                var csub = [];
-                // loop through all the data grids for matches
-                for( var k in data ) {
-                    // don't search through the stMatchSets[""] (minChars: 0) cache
-                    // this prevents duplicates
-                    if( k.length > 0 ){
-                        var c = data[k];
-                        $.each(c, function(i, x) {
-                            // if we've got a match, add it to the array
-                            if (matchSubset(x.value, q)) {
-                                csub.push(x);
-                            }
-                        });
-                    }
-                }               
-                return csub;
-            } else 
-            // if the exact item exists, use it
-            if (data[q]) {
-                return data[q];
-            } else
-            if (options.matchSubset) {
-                for (var i = q.length - 1; i >= options.minChars; i--) {
-                    var c = data[q.substr(0, i)];
-                    if (c) {
-                        var csub = [];
-                        $.each(c, function(i, x) {
-                            if ( (x.data.indexOf("#Header") == 0) || 
-                             (matchSubset(x.value, q)) ) {
-                                csub[csub.length] = x;
-                            }
-                        });
-                        return csub;
-                    }
-                }
-
+            this.getItems().hover(
+                function() { self.focusItem(this); },
+                function() { /* void */ }
+            );
+            if (this.autoFill(first, filter) || this.options.selectFirst || (this.options.selectOnly && numResults === 1)) {
+                this.focusItem($first);
             }
-            return null;
+            this.active_ = true;
+        } else {
+            this.hideResults();
+            this.active_ = false;
         }
     };
-};
 
-$.Autocompleter.Select = function (options, input, select, config) {
-    var CLASSES = {
-        ACTIVE: "ac_over"
+    $.Autocompleter.prototype.showResult = function(value, data) {
+        if ($.isFunction(this.options.showResult)) {
+            return this.options.showResult(value, data);
+        } else {
+            return $('<p></p>').text(value).html();
+        }
     };
-    
-    var listItems,
-        active = -1,
-        data,
-        term = "",
-        needsInit = true,
-        element,
-        list;
-    
-    // Create results
-    function init() {
-        if (!needsInit)
-            return;
-        
-        element = $("<div/>")
-        .hide()
-        .addClass(options.resultsClass)
-        .css("position", "absolute")
-        .bind("mouseleave", function() {
-            element.hide();
-        })
-        .appendTo(document.body);
 
-        list = $("<ul/>").appendTo(element).mouseover( function(event) {
-            if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
-                active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
-                // JG: Only add active class if target is not a header.
-                if (!headerAtPosition(active))
-                    $(target(event)).addClass(CLASSES.ACTIVE);            
+    $.Autocompleter.prototype.autoFill = function(value, filter) {
+        var lcValue, lcFilter, valueLength, filterLength;
+        if (this.options.autoFill && this.lastKeyPressed_ !== 8) {
+            lcValue = String(value).toLowerCase();
+            lcFilter = String(filter).toLowerCase();
+            valueLength = value.length;
+            filterLength = filter.length;
+            if (lcValue.substr(0, filterLength) === lcFilter) {
+                var d = this.getDelimiterOffsets();
+                var pad = d.start ? ' ' : ''; // if there is a preceding delimiter
+                this.setValue( pad + value );
+                var start = filterLength + d.start + pad.length;
+                var end = valueLength + d.start + pad.length;
+                this.selectRange(start, end);
+                return true;
             }
-        }).click(function(event) {
-            // JG: Ignore click on header.
-            active = $("li", list).index(target(event));
-            if (headerAtPosition(active))
-                return;
+        }
+        return false;
+    };
 
-            // Handle click on autocomplete options.
-            $(target(event)).addClass(CLASSES.ACTIVE);
-            select();
-            // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
-            input.focus();
-            return false;
-        }).mousedown(function() {
-            config.mouseDownOnSelect = true;
-        }).mouseup(function() {
-            config.mouseDownOnSelect = false;
-        });
+    $.Autocompleter.prototype.focusNext = function() {
+        this.focusMove(+1);
+    };
 
-        if( options.width > 0 )
-            element.css("width", options.width);
+    $.Autocompleter.prototype.focusPrev = function() {
+        this.focusMove(-1);
+    };
 
-        needsInit = false;
-    } 
-    
-    function target(event) {
-        var element = event.target;
-        while(element && element.tagName != "LI")
-            element = element.parentNode;
-        // more fun with IE, sometimes event.target is empty, just ignore it then
-        if(!element)
-            return [];
-        return element;
-    }
+    $.Autocompleter.prototype.focusMove = function(modifier) {
+        var $items = this.getItems();
+        modifier = sanitizeInteger(modifier, 0);
+        if (modifier) {
+            for (var i = 0; i < $items.length; i++) {
+                if ($($items[i]).hasClass(this.selectClass_)) {
+                    this.focusItem(i + modifier);
+                    return;
+                }
+            }
+        }
+        this.focusItem(0);
+    };
 
-    // JG: Returns true iff there is a header element at the given position.
-    function headerAtPosition(position) {
-        dataAtPosition = data[position].data;
-        return (dataAtPosition[0].indexOf("#Header") == 0);
-    }
-    
-    function moveSelect(step) {
-        listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
-        // JG: while active item is a header, continue stepping.
-        var isHeader = false;
-        do {
-            movePosition(step);
-            isHeader = headerAtPosition(active);
-        } 
-        while (isHeader);
-        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
-
-        if(options.scroll) {
-            var offset = 0;
-            listItems.slice(0, active).each(function() {
-                offset += this.offsetHeight;
-            });
-            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
-                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
-            } else if(offset < list.scrollTop()) {
-                list.scrollTop(offset);
+    $.Autocompleter.prototype.focusItem = function(item) {
+        var $item, $items = this.getItems();
+        if ($items.length) {
+            $items.removeClass(this.selectClass_).removeClass(this.options.selectClass);
+            if (typeof item === 'number') {
+                if (item < 0) {
+                    item = 0;
+                } else if (item >= $items.length) {
+                    item = $items.length - 1;
+                }
+                $item = $($items[item]);
+            } else {
+                $item = $(item);
+            }
+            if ($item) {
+                $item.addClass(this.selectClass_).addClass(this.options.selectClass);
             }
         }
     };
-    
-    function movePosition(step) {
-        active += step;
-        if (active < 0) {
-            active = listItems.size() - 1;
-        } else if (active >= listItems.size()) {
-            active = 0;
-        }
-    }
-    
-    function limitNumberOfItems(available) {
-        return options.max && options.max < available
-            ? options.max
-            : available;
-    }
-    
-    function fillList() {
-        list.empty();
-        var max = limitNumberOfItems(data.length);
-        for (var i=0; i < max; i++) {
-            if (!data[i])
-                continue;
-            
-            var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
-            if ( formatted === false )
-                continue;
 
-            // JG: Build list item by formatting the item and choosing a CSS class.
-            if (headerAtPosition(i)) {
-                // Found header element; only add header if there are subsequent elements.
-                if (i != max-1)
-                var li = $("<li/>").html(data[i].data[1]).addClass("ac_header").appendTo(list)[0];
-            } else {
-                // Found completion element.
-                var li = $("<li/>").html(options.highlight(formatted, term)).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
-            }
-
-            $.data(li, "ac_data", data[i]);
-        }
-        listItems = list.find("li");
-        if ( options.selectFirst ) {
-            listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
-            active = 0;
-        }
-        // apply bgiframe if available
-        if ( $.fn.bgiframe )
-            list.bgiframe();
-    }
-    
-    return {
-        display: function(d, q) {
-            init();
-            data = d;
-            term = q;
-            fillList();
-        },
-        next: function() {
-            moveSelect(1);
-        },
-        prev: function() {
-            moveSelect(-1);
-        },
-        pageUp: function() {
-            if (active != 0 && active - 8 < 0) {
-                moveSelect( -active );
-            } else {
-                moveSelect(-8);
-            }
-        },
-        pageDown: function() {
-            if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
-                moveSelect( listItems.size() - 1 - active );
-            } else {
-                moveSelect(8);
-            }
-        },
-        hide: function() {
-            element && element.hide();
-            listItems && listItems.removeClass(CLASSES.ACTIVE);
-            active = -1;
-        },
-        visible : function() {
-            return element && element.is(":visible");
-        },
-        current: function() {
-            return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
-        },
-        show: function() {
-            var offset = $(input).offset();
-            element.css({
-                width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
-                top: offset.top + input.offsetHeight,
-                left: offset.left
-            }).show();
-            if(options.scroll) {
-                list.scrollTop(0);
-                list.css({
-                    maxHeight: options.scrollHeight,
-                    overflow: 'auto'
-                });
-                
-                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
-                    var listHeight = 0;
-                    listItems.each(function() {
-                        listHeight += this.offsetHeight;
-                    });
-                    var scrollbarsVisible = listHeight > options.scrollHeight;
-                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
-                    if (!scrollbarsVisible) {
-                        // IE doesn't recalculate width when scrollbar disappears
-                        listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
-                    }
-                }
-                
-            }
-        },
-        selected: function() {
-            var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
-            return selected && selected.length && $.data(selected[0], "ac_data");
-        },
-        emptyList: function (){
-            list && list.empty();
-        },
-        unbind: function() {
-            element && element.remove();
+    $.Autocompleter.prototype.selectCurrent = function() {
+        var $item = $('li.' + this.selectClass_, this.dom.$results);
+        if ($item.length === 1) {
+            this.selectItem($item);
+        } else {
+            this.deactivate(false);
         }
     };
-};
 
-$.Autocompleter.Selection = function(field, start, end) {
-    if( field.createTextRange ){
-        var selRange = field.createTextRange();
-        selRange.collapse(true);
-        selRange.moveStart("character", start);
-        selRange.moveEnd("character", end);
-        selRange.select();
-    } else if( field.setSelectionRange ){
-        field.setSelectionRange(start, end);
-    } else {
-        if( field.selectionStart ){
-            field.selectionStart = start;
-            field.selectionEnd = end;
+    $.Autocompleter.prototype.selectItem = function($li) {
+        var value = $li.data('value');
+        var data = $li.data('data');
+        var displayValue = this.displayValue(value, data);
+        var processedDisplayValue = this.beforeUseConverter(displayValue);
+        this.lastProcessedValue_ = processedDisplayValue;
+        this.lastSelectedValue_ = processedDisplayValue;
+        var d = this.getDelimiterOffsets();
+        var delimiter = this.options.delimiterChar;
+        var elem = this.dom.$elem;
+        var extraCaretPos = 0;
+        if ( this.options.useDelimiter ) {
+            // if there is a preceding delimiter, add a space after the delimiter
+            if ( elem.val().substring(d.start-1, d.start) == delimiter && delimiter != ' ' ) {
+                displayValue = ' ' + displayValue;
+            }
+            // if there is not already a delimiter trailing this value, add it
+            if ( elem.val().substring(d.end, d.end+1) != delimiter && this.lastKeyPressed_ != this.options.delimiterKeyCode ) {
+                displayValue = displayValue + delimiter;
+            } else {
+                // move the cursor after the existing trailing delimiter
+                extraCaretPos = 1;
+            }
         }
-    }
-    field.focus();
-};
+        this.setValue(displayValue);
+        this.setCaret(d.start + displayValue.length + extraCaretPos);
+        this.callHook('onItemSelect', { value: value, data: data });
+        this.deactivate(true);
+        elem.focus();
+    };
 
-})(jQuery);
+    $.Autocompleter.prototype.displayValue = function(value, data) {
+        if ($.isFunction(this.options.displayValue)) {
+            return this.options.displayValue(value, data);
+        }
+        return value;
+    };
+
+    $.Autocompleter.prototype.hideResults = function() {
+        this.dom.$results.hide();
+    };
+
+    $.Autocompleter.prototype.deactivate = function(finish) {
+        if (this.finishTimeout_) {
+            clearTimeout(this.finishTimeout_);
+        }
+        if (this.keyTimeout_) {
+            clearTimeout(this.keyTimeout_);
+        }
+        if (finish) {
+            if (this.lastProcessedValue_ !== this.lastSelectedValue_) {
+                if (this.options.mustMatch) {
+                    this.setValue('');
+                }
+                this.callHook('onNoMatch');
+            }
+            if (this.active_) {
+                this.callHook('onFinish');
+            }
+            this.lastKeyPressed_ = null;
+            this.lastProcessedValue_ = null;
+            this.lastSelectedValue_ = null;
+            this.active_ = false;
+        }
+        this.hideResults();
+    };
+
+    $.Autocompleter.prototype.selectRange = function(start, end) {
+        var input = this.dom.$elem.get(0);
+        if (input.setSelectionRange) {
+            input.focus();
+            input.setSelectionRange(start, end);
+        } else if (input.createTextRange) {
+            var range = input.createTextRange();
+            range.collapse(true);
+            range.moveEnd('character', end);
+            range.moveStart('character', start);
+            range.select();
+        }
+    };
+
+    /**
+     * Move caret to position
+     * @param {Number} pos
+     */
+    $.Autocompleter.prototype.setCaret = function(pos) {
+        this.selectRange(pos, pos);
+    };
+
+    /**
+     * Get caret position
+     */
+    $.Autocompleter.prototype.getCaret = function() {
+        var $elem = this.dom.$elem;
+        var elem = $elem[0];
+        var val, selection, range, start, end, stored_range;
+        if (elem.createTextRange) { // IE
+            selection = document.selection;
+            if (elem.tagName.toLowerCase() != 'textarea') {
+                val = $elem.val();
+                range = selection.createRange().duplicate();
+                range.moveEnd('character', val.length);
+                if (range.text === '') {
+                    start = val.length;
+                } else {
+                    start = val.lastIndexOf(range.text);
+                }
+                range = selection.createRange().duplicate();
+                range.moveStart('character', -val.length);
+                end = range.text.length;
+            } else {
+                range = selection.createRange();
+                stored_range = range.duplicate();
+                stored_range.moveToElementText(elem);
+                stored_range.setEndPoint('EndToEnd', range);
+                start = stored_range.text.length - range.text.length;
+                end = start + range.text.length;
+            }
+        } else {
+            start = $elem[0].selectionStart;
+            end = $elem[0].selectionEnd;
+        }
+        return {
+            start: start,
+            end: end
+        };
+    };
+
+    /**
+     * Set the value that is currently being autocompleted
+     * @param {String} value
+     */
+    $.Autocompleter.prototype.setValue = function(value) {
+        if ( this.options.useDelimiter ) {
+            // set the substring between the current delimiters
+            var val = this.dom.$elem.val();
+            var d = this.getDelimiterOffsets();
+            var preVal = val.substring(0, d.start);
+            var postVal = val.substring(d.end);
+            value = preVal + value + postVal;
+        }
+        this.dom.$elem.val(value);
+    };
+
+    /**
+     * Get the value currently being autocompleted
+     * @param {String} value
+     */
+    $.Autocompleter.prototype.getValue = function(value) {
+        if ( this.options.useDelimiter ) {
+            var d = this.getDelimiterOffsets();
+            return value.substring(d.start, d.end).trim();
+        } else {
+            return value;
+        }
+    };
+
+    /**
+     * Get the offsets of the value currently being autocompleted
+     */
+    $.Autocompleter.prototype.getDelimiterOffsets = function() {
+        var val = this.dom.$elem.val();
+        if ( this.options.useDelimiter ) {
+            var preCaretVal = val.substring(0, this.getCaret().start);
+            var start = preCaretVal.lastIndexOf(this.options.delimiterChar) + 1;
+            var postCaretVal = val.substring(this.getCaret().start);
+            var end = postCaretVal.indexOf(this.options.delimiterChar);
+            if ( end == -1 ) end = val.length;
+            end += this.getCaret().start;
+        } else {
+            start = 0;
+            end = val.length;
+        }
+        return {
+            start: start,
+            end: end
+        };
+    };
+
+})(jQuery);
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: carlfeberhard: DataProviders: add hierarchy module and xml provider, add provider to datatype
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/bf6bf3b5edfd/
Changeset:   bf6bf3b5edfd
User:        carlfeberhard
Date:        2013-08-08 18:59:34
Summary:     DataProviders: add hierarchy module and xml provider, add provider to datatype
Affected #:  5 files
diff -r f46505803e8f0efd26be16f9e18353fbdc969205 -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 lib/galaxy/datatypes/dataproviders/__init__.py
--- a/lib/galaxy/datatypes/dataproviders/__init__.py
+++ b/lib/galaxy/datatypes/dataproviders/__init__.py
@@ -23,6 +23,7 @@
 import base
 import chunk
 import line
+import hierarchy
 import column
 import external
 import dataset
diff -r f46505803e8f0efd26be16f9e18353fbdc969205 -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 lib/galaxy/datatypes/dataproviders/decorators.py
--- a/lib/galaxy/datatypes/dataproviders/decorators.py
+++ b/lib/galaxy/datatypes/dataproviders/decorators.py
@@ -134,6 +134,7 @@
     Parse the values in `query_kwargs` from strings to the proper types
     listed in the same key in `settings`.
     """
+    #TODO: this was a relatively late addition: review and re-think
     def list_from_query_string( s ):
         # assume csv
         return s.split( ',' )
@@ -155,10 +156,11 @@
             #TODO: this would be the place to sanitize any strings
             query_value = query_kwargs[ key ]
             needed_type = settings[ key ]
-            try:
-                query_kwargs[ key ] = parsers[ needed_type ]( query_value )
-            except ( KeyError, ValueError ):
-                del query_kwargs[ key ]
+            if needed_type != 'str':
+                try:
+                    query_kwargs[ key ] = parsers[ needed_type ]( query_value )
+                except ( KeyError, ValueError ):
+                    del query_kwargs[ key ]
 
         #TODO:?? do we want to remove query_kwarg entries NOT in settings?
     return query_kwargs
diff -r f46505803e8f0efd26be16f9e18353fbdc969205 -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 lib/galaxy/datatypes/dataproviders/hierarchy.py
--- /dev/null
+++ b/lib/galaxy/datatypes/dataproviders/hierarchy.py
@@ -0,0 +1,142 @@
+"""
+Dataproviders that iterate over lines from their sources.
+"""
+
+import line
+import xml.etree.ElementTree as elementtree
+
+_TODO = """
+"""
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+# ----------------------------------------------------------------------------- hierarchal/tree data providers
+class HierarchalDataProvider( line.BlockDataProvider ):
+    """
+    Class that uses formats where a datum may have a parent or children
+    data.
+
+    e.g. XML, HTML, GFF3, Phylogenetic
+    """
+    def __init__( self, source, **kwargs ):
+        #TODO: (and defer to better (than I can write) parsers for each subtype)
+        super( HierarchalDataProvider, self ).__init__( source, **kwargs )
+
+
+# ----------------------------------------------------------------------------- xml
+class XMLDataProvider( HierarchalDataProvider ):
+    """
+    Data provider that converts selected XML elements to dictionaries.
+    """
+    # using elementtree's iterparse method to keep mem down
+    #TODO:   this, however (AFAIK), prevents the use of xpath
+    settings = {
+        'selector'  : 'str', #urlencoded
+        'max_depth' : 'int',
+    }
+    ITERPARSE_ALL_EVENTS = ( 'start', 'end', 'start-ns', 'end-ns' )
+    #TODO: move appropo into super
+
+    def __init__( self, source, selector=None, max_depth=None, **kwargs ):
+        """
+        :param  selector:   some partial string in the desired tags to return
+        :param  max_depth:  the number of generations of descendents to return
+        """
+        self.selector = selector
+        self.max_depth = max_depth
+        self.namespaces = {}
+
+        super( XMLDataProvider, self ).__init__( source, **kwargs )
+
+    def matches_selector( self, element, selector=None ):
+        """
+        Returns true if the ``element`` matches the ``selector``.
+
+        :param  element:    an XML ``ElementTree.Element``
+        :param  selector:   some partial string in the desired tags to return
+
+        Change point for more sophisticated selectors.
+        """
+        # search for partial match of selector to the element tag
+        #TODO: add more flexibility here w/o re-implementing xpath
+        #TODO: fails with '#' - browser thinks it's anchor - use urlencode
+        #TODO: need removal/replacement of etree namespacing here - then move to string match
+        return bool( ( selector == None )
+                  or ( isinstance( element, elementtree.Element ) and selector in element.tag ) )
+
+    def element_as_dict( self, element ):
+        """
+        Converts an XML element (its text, tag, and attributes) to dictionary form.
+
+        :param  element:    an XML ``ElementTree.Element``
+        """
+        #TODO: Key collision is unlikely here, but still should be better handled
+        return {
+            'tag'      : element.tag,
+            'text'     : element.text.strip() if element.text else None,
+            # needs shallow copy to protect v. element.clear()
+            'attrib'   : dict( element.attrib )
+        }
+
+    def get_children( self, element, max_depth=None ):
+        """
+        Yield all children of element (and their children - recursively)
+        in dictionary form.
+        :param  element:    an XML ``ElementTree.Element``
+        :param  max_depth:  the number of generations of descendents to return
+        """
+        if not isinstance( max_depth, int ) or max_depth >= 1:
+            for child in element.getchildren():
+                child_data = self.element_as_dict( child )
+
+                next_depth = max_depth - 1 if isinstance( max_depth, int ) else None
+                grand_children = list( self.get_children( child, next_depth ) )
+                if grand_children:
+                    child_data[ 'children' ] = grand_children
+
+                yield child_data
+
+    def __iter__( self ):
+        context = elementtree.iterparse( self.source, events=self.ITERPARSE_ALL_EVENTS )
+        context = iter( context )
+
+        selected_element = None
+        for event, element in context:
+            #print 'iterparse, event:', event
+            #print 'iterparse, element:', element, ( element.tag if hasattr( element, 'tag' ) else '' )
+
+            if   event == 'start-ns':
+                ns, uri = element
+                self.namespaces[ ns ] = uri
+
+            elif event == 'start':
+                if( ( selected_element == None )
+                and ( self.matches_selector( element, self.selector ) ) ):
+                    # start tag of selected element - wait for 'end' to emit/yield
+                    selected_element = element
+
+            elif event == 'end':
+                if( ( selected_element != None )
+                and ( element == selected_element ) ):
+                    self.num_valid_data_read += 1
+
+                    # offset
+                    if self.num_valid_data_read > self.offset:
+                        # convert to dict and yield
+                        selected_element_dict = self.element_as_dict( selected_element )
+                        children = list( self.get_children( selected_element, self.max_depth ) )
+                        if children:
+                            selected_element_dict[ 'children' ] = children
+                        yield selected_element_dict
+
+                        # limit
+                        self.num_data_returned += 1
+                        if self.limit is not None and self.num_data_returned >= self.limit:
+                            break
+
+                    selected_element.clear()
+                    selected_element = None
+
+                self.num_data_read += 1
diff -r f46505803e8f0efd26be16f9e18353fbdc969205 -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 lib/galaxy/datatypes/dataproviders/line.py
--- a/lib/galaxy/datatypes/dataproviders/line.py
+++ b/lib/galaxy/datatypes/dataproviders/line.py
@@ -132,7 +132,7 @@
     e.g. Fasta, GenBank, MAF, hg log
     Note: mem intensive (gathers list of lines before output)
     """
-    def __init__( self, source, new_block_delim_fn, block_filter_fn=None, **kwargs ):
+    def __init__( self, source, new_block_delim_fn=None, block_filter_fn=None, **kwargs ):
         """
         :param new_block_delim_fn: T/F function to determine whether a given line
             is the start of a new block.
@@ -214,7 +214,7 @@
         """
         if self.new_block_delim_fn:
             return self.new_block_delim_fn( line )
-        return False
+        return True
 
     # NOTE:
     #   some formats have one block attr per line
@@ -251,17 +251,3 @@
         if self.block_filter_fn:
             return self.block_filter_fn( block )
         return block
-
-
-# ----------------------------------------------------------------------------- hierarchal/tree data providers
-class HierarchalDataProvider( BlockDataProvider ):
-    """
-    Class that uses formats where a datum may have a parent or children
-    data.
-
-    e.g. XML, HTML, GFF3, Phylogenetic
-    """
-    def __init__( self, source, **kwargs ):
-        #TODO: (and defer to better (than I can write) parsers for each subtype)
-        raise NotImplementedError( 'Abstract class' )
-        super( HierarchalDataProvider, self ).__init__( source, **kwargs )
diff -r f46505803e8f0efd26be16f9e18353fbdc969205 -r bf6bf3b5edfdc409f235cb91edcc9d91246df816 lib/galaxy/datatypes/xml.py
--- a/lib/galaxy/datatypes/xml.py
+++ b/lib/galaxy/datatypes/xml.py
@@ -4,9 +4,11 @@
 import data
 import logging
 from galaxy.datatypes.sniff import *
+import dataproviders
 
 log = logging.getLogger(__name__)
 
+(a)dataproviders.decorators.has_dataproviders
 class GenericXml( data.Text ):
     """Base format class for any XML file."""
     file_ext = "xml"
@@ -47,6 +49,12 @@
         data.Text.merge(split_files, output_file)
     merge = staticmethod(merge)
 
+    @dataproviders.decorators.dataprovider_factory( 'xml', dataproviders.hierarchy.XMLDataProvider.settings )
+    def xml_dataprovider( self, dataset, **settings ):
+        dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+        return dataproviders.hierarchy.XMLDataProvider( dataset_source, **settings )
+
+
 class MEMEXml( GenericXml ):
     """MEME XML Output data"""
     file_ext = "memexml"
@@ -62,6 +70,7 @@
     def sniff( self, filename ):
         return False
 
+
 class CisML( GenericXml ):
     """CisML XML data""" #see: http://www.ncbi.nlm.nih.gov/pubmed/15001475
     file_ext = "cisml"
@@ -77,6 +86,7 @@
     def sniff( self, filename ):
         return False
 
+
 class Phyloxml( GenericXml ):
     """Format for defining phyloxml data http://www.phyloxml.org/"""
     file_ext = "phyloxml"
@@ -105,4 +115,4 @@
         Returns a list of visualizations for datatype.
         """
 
-        return [ 'phyloviz' ]
\ No newline at end of file
+        return [ 'phyloviz' ]
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                    
                    
                        2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8d8429484ac3/
Changeset:   8d8429484ac3
Branch:      next-stable
User:        carlfeberhard
Date:        2013-08-08 16:40:27
Summary:     History view: fix broken scrollbar
Affected #:  1 file
diff -r 8ee4176d44bad2c1029356e869d601752ae54cd0 -r 8d8429484ac3ffef21fedd1077acf050c8aed459 templates/webapps/galaxy/history/view.mako
--- a/templates/webapps/galaxy/history/view.mako
+++ b/templates/webapps/galaxy/history/view.mako
@@ -27,6 +27,7 @@
 
     <style type="text/css">
 
+        /* these don't appear to be used? */
         .page-body
         {
             padding: 10px;
@@ -44,20 +45,38 @@
             border-top: 4px solid #DDDDDD;
         }
 
+
+        body {
+            padding: 0px;
+            margin: 0px;
+        }
+
         div.unified-panel-body {
-            position: relative;
+            position: absolute;
             top: 0px;
-            width: auto;
+            width: 100%;
+        }
+
+        #history-name-area {
+            margin: 12px 0px 0px 16px;
+            font-size: 120%;
+        }
+        #top-links {
+            margin: 4px 0px 8px 16px;
         }
 
         .historyItemContainer {
-            padding-right: 3px;
-            border-right-style: solid;
-            border-right-color: #66AA66;
+            /*padding-right: 3px;*/
         }
         .historyItemBody {
             display: none;
         }
+        div.historyItemWrapper {
+            margin: 0px 4px 0px 4px ;
+            border-left: 1px solid #999999;
+            border-right: 1px solid #999999;
+        }
+        /* TODO: unify with other history css and into .less */
     </style><noscript>
@@ -92,14 +111,13 @@
             href_to_user_histories = h.url_for( controller='/history', action='list_published' )##should this instead be be None or empty string?
     %>
     
-    %if context.get( 'use_panels' ):
-    <div class="unified-panel-header" unselectable="on">
-    </div>
-    %endif
-    
     <div class="unified-panel-body">
-        <div class="wrapping" style="overflow: auto; height: 100%;">
+        <div style="overflow: auto; height: 100%;">
             ## Render view of history.
+            <div id="history-name-area" class="historyLinks" style="color: gray; font-weight: bold; padding: 0px 0px 5px 0px">
+                <div id="history-name">${history.get_display_name()}</div>
+            </div>
+
             <div id="top-links" class="historyLinks" style="padding: 0px 0px 5px 0px">
                 %if not history.purged:
                     <a href="${h.url_for(controller='history', action='imp', id=trans.security.encode_id(history.id) )}">import and start using history</a> |
@@ -113,10 +131,6 @@
                 <a href="#" class="toggle">collapse all</a></div>
 
-            <div id="history-name-area" class="historyLinks" style="color: gray; font-weight: bold; padding: 0px 0px 5px 0px">
-                <div id="history-name">${history.get_display_name()}</div>
-            </div>
-
             %if history.deleted:
                 <div class="warningmessagesmall">
                     ${_('You are currently viewing a deleted history!')}
https://bitbucket.org/galaxy/galaxy-central/commits/f46505803e8f/
Changeset:   f46505803e8f
User:        carlfeberhard
Date:        2013-08-08 16:41:18
Summary:     merge next-stable
Affected #:  2 files
diff -r 3f8f52fa01f807b624ce05ef2186284b947c2e34 -r f46505803e8f0efd26be16f9e18353fbdc969205 templates/webapps/galaxy/history/view.mako
--- a/templates/webapps/galaxy/history/view.mako
+++ b/templates/webapps/galaxy/history/view.mako
@@ -27,6 +27,7 @@
 
     <style type="text/css">
 
+        /* these don't appear to be used? */
         .page-body
         {
             padding: 10px;
@@ -44,20 +45,38 @@
             border-top: 4px solid #DDDDDD;
         }
 
+
+        body {
+            padding: 0px;
+            margin: 0px;
+        }
+
         div.unified-panel-body {
-            position: relative;
+            position: absolute;
             top: 0px;
-            width: auto;
+            width: 100%;
+        }
+
+        #history-name-area {
+            margin: 12px 0px 0px 16px;
+            font-size: 120%;
+        }
+        #top-links {
+            margin: 4px 0px 8px 16px;
         }
 
         .historyItemContainer {
-            padding-right: 3px;
-            border-right-style: solid;
-            border-right-color: #66AA66;
+            /*padding-right: 3px;*/
         }
         .historyItemBody {
             display: none;
         }
+        div.historyItemWrapper {
+            margin: 0px 4px 0px 4px ;
+            border-left: 1px solid #999999;
+            border-right: 1px solid #999999;
+        }
+        /* TODO: unify with other history css and into .less */
     </style><noscript>
@@ -92,14 +111,13 @@
             href_to_user_histories = h.url_for( controller='/history', action='list_published' )##should this instead be be None or empty string?
     %>
     
-    %if context.get( 'use_panels' ):
-    <div class="unified-panel-header" unselectable="on">
-    </div>
-    %endif
-    
     <div class="unified-panel-body">
-        <div class="wrapping" style="overflow: auto; height: 100%;">
+        <div style="overflow: auto; height: 100%;">
             ## Render view of history.
+            <div id="history-name-area" class="historyLinks" style="color: gray; font-weight: bold; padding: 0px 0px 5px 0px">
+                <div id="history-name">${history.get_display_name()}</div>
+            </div>
+
             <div id="top-links" class="historyLinks" style="padding: 0px 0px 5px 0px">
                 %if not history.purged:
                     <a href="${h.url_for(controller='history', action='imp', id=trans.security.encode_id(history.id) )}">import and start using history</a> |
@@ -113,10 +131,6 @@
                 <a href="#" class="toggle">collapse all</a></div>
 
-            <div id="history-name-area" class="historyLinks" style="color: gray; font-weight: bold; padding: 0px 0px 5px 0px">
-                <div id="history-name">${history.get_display_name()}</div>
-            </div>
-
             %if history.deleted:
                 <div class="warningmessagesmall">
                     ${_('You are currently viewing a deleted history!')}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0
                            
                          
                          
                            
    
                          
                        
                     
                        
                    
                        
                            
                                
                            
                            commit/galaxy-central: guerler: Fix precision	formatting in Circster
                        
                        
by commits-noreply@bitbucket.org 08 Aug '13
                    by commits-noreply@bitbucket.org 08 Aug '13
08 Aug '13
                    
                        1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3f8f52fa01f8/
Changeset:   3f8f52fa01f8
User:        guerler
Date:        2013-08-08 10:10:36
Summary:     Fix precision formatting in Circster
Affected #:  1 file
diff -r ba8330a9fdefe0431ee1869e3a60c55caebc937c -r 3f8f52fa01f807b624ce05ef2186284b947c2e34 static/scripts/viz/circster.js
--- a/static/scripts/viz/circster.js
+++ b/static/scripts/viz/circster.js
@@ -85,10 +85,14 @@
      */
     formatNum: function(num, sigDigits) {
         // Use default of 2 sig. digits.
-        if (sigDigits === undefined) {
+        if (sigDigits === undefined)
             sigDigits = 2;
-        }
-
+       
+        // Verify input number
+        if (num === null)
+            return null;
+       
+        // Calculate return value
         var rval = null;
         if (num < 1) {
             rval = num.toPrecision(sigDigits);
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
                    
                  
                  
                          
                            
                            1
                            
                          
                          
                            
                            0