From: Loic Baron Date: Wed, 1 Oct 2014 09:59:21 +0000 (+0200) Subject: Plugin UnivBristol by Frederic Francois X-Git-Tag: myslice-1.2~1^2~21^2~2 X-Git-Url: http://git.onelab.eu/?p=myslice.git;a=commitdiff_plain;h=d651e8f12d326efb5185ecc28b760e8972b1e889 Plugin UnivBristol by Frederic Francois --- diff --git a/plugins/univbris/__init__.py b/plugins/univbris/__init__.py index ca96b6f6..444fffe5 100644 --- a/plugins/univbris/__init__.py +++ b/plugins/univbris/__init__.py @@ -5,14 +5,21 @@ class Univbris(Plugin): def __init__ (self, query=None, **settings): Plugin.__init__ (self, **settings) self.query=query + self.query_uuid = query.query_uuid if query else None + print "called univbris plugin" def template_file (self): - return "univbris.html" + try: + return "univbris_welcome.html" + except: + print "error template" def requirements (self): reqs = { - 'js_files' : [ - 'js/univbris.js' + 'js_files' : [ "js/spin-presets.js", "js/spin.min.js", "js/jquery.spin.js", + "js/manifold.js", "js/manifold-query.js", + "js/unfold-helper.js", + 'js/univbris.js', ], 'css_files': [ 'css/univbris.css', @@ -24,7 +31,7 @@ class Univbris(Plugin): # query_uuid will pass self.query results to the javascript # and will be available as "record" in : # on_new_record: function(record) - return ['plugin_uuid', 'domid', 'query_uuid'] + return ['plugin_uuid', 'domid', 'query_uuid','init_key',] def export_json_settings (self): return True diff --git a/plugins/univbris/static/js/univbris.js b/plugins/univbris/static/js/univbris.js index 4c84a90c..a22201e9 100644 --- a/plugins/univbris/static/js/univbris.js +++ b/plugins/univbris/static/js/univbris.js @@ -1,6 +1,6 @@ /** - * univbris: test plugin for Bristol University - * Version: 0.1 + * univbris: test ofam plugin for Bristol University + * Version: 0.2 * Description: just testing plugin in myslice * Requires: js/plugin.js * URL: http://www.myslice.info @@ -13,94 +13,21 @@ var Univbris = Plugin.extend({ - /** XXX to check - * @brief Plugin constructor - * @param options : an associative array of setting values - * @param element : - * @return : a jQuery collection of objects on which the plugin is - * applied, which allows to maintain chainability of calls - */ init: function(options, element) { - // Call the parent constructor, see FAQ when forgotten this._super(options, element); - - /* Member variables */ - - /* Plugin events */ - - /* Setup query and record handlers */ - - // Explain this will allow query events to be handled - // What happens when we don't define some events ? - // Some can be less efficient this.listen_query(options.query_uuid); - this.listen_query(options.query_uuid, 'all'); - - /* GUI setup and event binding */ - // call function - - alert("univbris plugin 2"); }, - /* PLUGIN EVENTS */ - // on_show like in querytable - - - /* GUI EVENTS */ - - // a function to bind events here: click change - // how to raise manifold events - - - /* GUI MANIPULATION */ - - // We advise you to write function to change behaviour of the GUI - // Will use naming helpers to access content _inside_ the plugin - // always refer to these functions in the remaining of the code - - show_hide_button: function() - { - // this.id, this.el, this.cl, this.elts - // same output as a jquery selector with some guarantees - }, + on_query_done: function() + { + $("#univbris_welcome").hide(); + jQuery("#univbris_flowspace_selection").show(); + }, - /* TEMPLATES */ - - // see in the html template - // How to load a template, use of mustache - - /* QUERY HANDLERS */ - - // How to make sure the plugin is not desynchronized - // He should manifest its interest in filters, fields or records - // functions triggered only if the proper listen is done - - // no prefix - - on_filter_added: function(filter) - { - - }, - - // ... be sure to list all events here - - /* RECORD HANDLERS */ - on_all_new_record: function(record) - { - // - }, - - /* INTERNAL FUNCTIONS */ - _dummy: function() { - // only convention, not strictly enforced at the moment - }, }); - /* Plugin registration */ $.plugin('Univbris', Univbris); - // TODO Here use cases for instanciating plugins in different ways like in the pastie. - })(jQuery); diff --git a/plugins/univbris/templates/univbris.html b/plugins/univbris/templates/univbris.html index f8aec26c..64e50d11 100644 --- a/plugins/univbris/templates/univbris.html +++ b/plugins/univbris/templates/univbris.html @@ -1,4 +1,2 @@ -
-

Bristol University Island Infrastructure Monitoring

-

information to follow

-
+

gathering information from testbeds

+ diff --git a/plugins/univbris/templates/univbris_welcome.html b/plugins/univbris/templates/univbris_welcome.html new file mode 100644 index 00000000..09cc7b7a --- /dev/null +++ b/plugins/univbris/templates/univbris_welcome.html @@ -0,0 +1,2 @@ +

...gathering information from testbeds...

+ diff --git a/plugins/univbrisfoam/__init__.py b/plugins/univbrisfoam/__init__.py index b732618b..254fda21 100644 --- a/plugins/univbrisfoam/__init__.py +++ b/plugins/univbrisfoam/__init__.py @@ -40,7 +40,7 @@ Current implementation makes the following assumptions as we use 'aoColumnDefs' instead. """ - def __init__ (self, query=None, query_all=None, + def __init__ (self, query=None, query_all=None, sync_query=None, checkboxes=False, columns=None, init_key=None, datatables_options={}, **settings): @@ -49,6 +49,7 @@ Current implementation makes the following assumptions # Until we have a proper way to access queries in Python self.query_all = query_all self.query_all_uuid = query_all.query_uuid if query_all else None + self.sync_query_uuid = sync_query.query_uuid if sync_query else None self.checkboxes = checkboxes # XXX We need to have some hidden columns until we properly handle dynamic queries if columns is not None: @@ -115,6 +116,6 @@ Current implementation makes the following assumptions # the list of things passed to the js plugin def json_settings_list (self): return ['plugin_uuid', 'domid', - 'query_uuid', 'query_all_uuid', + 'query_uuid', 'query_all_uuid', 'sync_query_uuid', 'checkboxes', 'datatables_options', 'hidden_columns', 'init_key',] diff --git a/plugins/univbrisfoam/static/js/univbrisfoam.js b/plugins/univbrisfoam/static/js/univbrisfoam.js index 22ba431b..cbf72ae8 100644 --- a/plugins/univbrisfoam/static/js/univbrisfoam.js +++ b/plugins/univbrisfoam/static/js/univbrisfoam.js @@ -7,8 +7,12 @@ (function($){ var debug=false; + window.query_itr2=0; + table_links=[]; //debug=true + regex_filter="((packet)|(compute))"; + var UnivbrisFoam = Plugin.extend({ init: function(options, element) { @@ -62,9 +66,12 @@ /* Setup query and record handlers */ this.listen_query(options.query_uuid); this.listen_query(options.query_all_uuid, 'all'); + this.listen_query(options.sync_query_uuid,'sync'); /* GUI setup and event binding */ this.initialize_table(); + + jQuery( "#univbris_foam_ports_selection" ).hide(); }, @@ -95,7 +102,7 @@ // Customize the position of Datatables elements (length,filter,button,...) // we use a fluid row on top and another on the bottom, making sure we take 12 grid elt's each time //sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-7'p>>", - sDom: "<'row'<'col-xs-2'l><'col-xs-9'r><'col-xs-2'f>>t<'row'<'col-xs-5'i><'col-xs-5'p>><'next'>", + sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-5'p>>", // XXX as of sept. 2013, I cannot locate a bootstrap3-friendly mode for now // hopefully this would come with dataTables v1.10 ? // in any case, search for 'sPaginationType' all over the code for more comments @@ -158,7 +165,7 @@ //self.hide_column(field); }); - $('').appendTo('div.next'); + //$('').appendTo('div.next'); //type="submit" @@ -197,14 +204,14 @@ jQuery( "#univbris_flowspace_selection" ).show(); //$("#multi_flowpspace_ports_selected").append(''); - $("form#uob_form :input[type=checkbox]").each(function(){ - var input = $(this); // This is the jquery object of the input, do what you will - //alert("id: "+ input.attr('id') + " checked: "+ input.is(':checked')); - if(input.is(':checked')==true){ - //alert("got true"); - $("#multi_flowpspace_ports_selected").append(''); - } - }); + //$("form#uob_form :input[type=checkbox]").each(function(){ + // var input = $(this); // This is the jquery object of the input, do what you will + // //alert("id: "+ input.attr('id') + " checked: "+ input.is(':checked')); + // if(input.is(':checked')==true){ + // //alert("got true"); + // $("#multi_flowpspace_ports_selected").append(''); + // } + //}); jQuery( "#univbris_foam_ports_selection" ).hide(); //jQuery( "#univbris_flowspace_selection" ).hide(); @@ -256,6 +263,32 @@ return result; }, + fnLinkClick:function(link){ + //console.log("link has been clicked: "); + //console.log(link.target.id); + + var svg_links = svg.selectAll(".link"); + for(var i=0;i - + diff --git a/plugins/univbrisfoam/templates/univbrisfoam.html b/plugins/univbrisfoam/templates/univbrisfoam.html index 77493974..89a65db6 100644 --- a/plugins/univbrisfoam/templates/univbrisfoam.html +++ b/plugins/univbrisfoam/templates/univbrisfoam.html @@ -19,4 +19,5 @@ +
diff --git a/plugins/univbrisfv/__init__.py b/plugins/univbrisfv/__init__.py index a44a0ab7..93653b9f 100644 --- a/plugins/univbrisfv/__init__.py +++ b/plugins/univbrisfv/__init__.py @@ -40,7 +40,7 @@ Current implementation makes the following assumptions as we use 'aoColumnDefs' instead. """ - def __init__ (self, query=None, query_all=None, + def __init__ (self, query=None, query_all=None, sync_query=None, checkboxes=False, columns=None, init_key=None, datatables_options={}, **settings): @@ -49,6 +49,7 @@ Current implementation makes the following assumptions # Until we have a proper way to access queries in Python self.query_all = query_all self.query_all_uuid = query_all.query_uuid if query_all else None + self.sync_query_uuid = sync_query.query_uuid if sync_query else None self.checkboxes = checkboxes # XXX We need to have some hidden columns until we properly handle dynamic queries if columns is not None: @@ -118,6 +119,6 @@ Current implementation makes the following assumptions # the list of things passed to the js plugin def json_settings_list (self): return ['plugin_uuid', 'domid', - 'query_uuid', 'query_all_uuid', + 'query_uuid', 'query_all_uuid', 'sync_query_uuid', 'checkboxes', 'datatables_options', 'hidden_columns', 'init_key',] diff --git a/plugins/univbrisfv/__init__1.py b/plugins/univbrisfv/__init__1.py index 73cd5520..853bc1ee 100644 --- a/plugins/univbrisfv/__init__1.py +++ b/plugins/univbrisfv/__init__1.py @@ -1,15 +1,14 @@ from unfold.plugin import Plugin class UnivbrisFv (Plugin): - """ - //////////////////////////////////////// - modified querytable for univbris foam - /////////////////////////////////////// - """ +""" +//////////////////////////////////////// +modified querytable for univbris foam +///////////////////////////////////////""" def __init__ (self, init_key=None,datatables_options={}, **settings): - Plugin.__init__ (self, **settings) - self.columns = list (['switch dpid','port no<->peer dpid/port no.','selected']) + Plugin.__init__ (self, **settings) + self.columns = list (['switch dpid','port no<->peer dpid/port no.','selected']) self.init_key=init_key self.datatables_options=datatables_options diff --git a/plugins/univbrisfv/static/js/univbrisfv.js b/plugins/univbrisfv/static/js/univbrisfv.js index 82581ab2..7cefef60 100644 --- a/plugins/univbrisfv/static/js/univbrisfv.js +++ b/plugins/univbrisfv/static/js/univbrisfv.js @@ -11,8 +11,10 @@ pk_flowspace_index=0; opt_flowspace_index=0; + pk_mode=0; fvf_add=1; fvf_nrow=0; + var UnivbrisFv = Plugin.extend({ @@ -95,8 +97,11 @@ initialize_table: function() { + + /* Transforms the table into DataTable, and keep a pointer to it */ var self = this; + //alert(self.options); var actual_options = { // Customize the position of Datatables elements (length,filter,button,...) // we use a fluid row on top and another on the bottom, making sure we take 12 grid elt's each time @@ -179,7 +184,7 @@ $('').appendTo('div.buttons');**/ - jQuery( "#univbris_flowspace_selection" ).hide(); + //jQuery( "#univbris_flowspace_selection" ).hide(); //$('next link').appendTo('div.submit'); @@ -189,121 +194,354 @@ //this.new_record("t"); //this.new_record("t"); this._querytable_draw_callback(); - + jQuery("#univbris_flowspace_selection").hide(); }, // initialize_table fnButsubmit:function(e){ - alert("submitting"); - var rows = $("#univbris_flowspace_selection__table").dataTable().fnGetNodes(); - var cells=[]; - for(var i=0;i=7){ + //alert(controller.substring(0,4)) + if(controller.substring(0,4)=="tcp:" | controller.substring(0,4)=="ssl:"){ + var controller_ip=controller.substring(4,controller.length); + //alert(controller_ip) + var index=controller_ip.indexOf(":") + if (index!=-1){ + var controller_ip1=controller_ip.substring(0,index); + //alert(controller_ip1); + var ip_validator= /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + if (!controller_ip1.match(ip_validator)){ + throw "Incorrect IP format"; + } + //else{ + // throw "incorrect ip"; + //} + + var controller_port=controller_ip.substring(index+1,controller_ip.length); + if(!isNaN(controller_port)){ + controller_port=parseInt(controller_port,10); + if (!((controller_port >0) & (controller_port <65536))){ + //if(controller_port >0){ + throw "Incorrect controller port"; + } + //else{ + // throw "correct port"; + //} + } + else{ + throw "Incorrect controller port"; + } + } + else{ + throw "Incorrect controller specified"; + } + } + else{ + throw "Controller must start with tcp: or ssl:"; + } + + } + else{ + throw "Incorrect controller specified"; + } + //end of validation of controller field + + var rows = $("#univbris_flowspace_selection__table").dataTable().fnGetNodes(); + var cells=[]; + + var json_rspec={}; + json_rspec["controller"]=controller; + var groups_rspec=[]; + var matches_rspec=[]; + + if (rows.length <=0) { + throw "No Flowspace defined" + } + + + var queryStringToJSON = function (url) { + if (url === '') return ''; + var pairs = (url || location.search).slice(1).split('&'); + var result = {}; + for (var idx in pairs) { + if ($.isNumeric(idx)) { + var pair = pairs[idx].split('='); + if (!!pair[0]){ + result[pair[0].toLowerCase()] = decodeURIComponent(pair[1].replace(/\+/g, " ") || ''); + } + } + } + return result; } - } - var controller= $('#controller_loc').val(); + for(var i=0;i "+$("#flowspace_name").val()+"

"; - this.table.fnDeleteRow(fvf_nrow); - this.table.fnAddData([string, 'Edit', 'Delete']); - jQuery( "#univbris_flowspace_selection" ).show(); - //onclick=\'fnEdit("test");\' - //alert("myserialise: "+form); - //alert("added flowspace:" + sData); - }, @@ -641,6 +879,8 @@ on_all_query_done: function() { if (debug) messages.debug("1-shot initializing dataTables content with " + this.buffered_lines.length + " lines"); + + this.table.fnAddData (this.buffered_lines); this.buffered_lines=[]; diff --git a/plugins/univbrisfv/templates/univbrisfv.html b/plugins/univbrisfv/templates/univbrisfv.html index 44d51af8..8ddb249d 100644 --- a/plugins/univbrisfv/templates/univbrisfv.html +++ b/plugins/univbrisfv/templates/univbrisfv.html @@ -21,7 +21,7 @@ - ' + ' diff --git a/plugins/univbrisfvf/static/js/univbrisfvf.js b/plugins/univbrisfvf/static/js/univbrisfvf.js index a21e5246..0422bd7c 100644 --- a/plugins/univbrisfvf/static/js/univbrisfvf.js +++ b/plugins/univbrisfvf/static/js/univbrisfvf.js @@ -7,7 +7,9 @@ (function($){ var debug=false; - debug=true + debug=true; + sync_query_uuid=""; + var UnivbrisFvf = Plugin.extend({ @@ -171,55 +173,122 @@ fnCancel:function(e){ //var sData=$("#uob_fv_table_form").find("input").serialize(); //alert("add flowspace:" + sData); + //alert("cancel"); + jQuery("#uob_ofv_table_form").hide(); jQuery("#uob_fv_table_form").hide(); + jQuery( "#univbris_foam_ports_selection" ).hide(); jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); + /*var port_table=$("#univbris_foam_ports_selection__table").dataTable(); + var nodes = $('input',port_table.fnGetNodes()); + for(var i=0;i "+$("#flowspace_name").val()+"

"; + this.table = $("#univbris_flowspace_selection__table").dataTable(); + var val_status=validateFvfForm(); + if (val_status == true){ + pk_flowspace_index=1+pk_flowspace_index; + flowspace=sData; + var m_form=form+","+form2; + var string = "

"+$("#flowspace_name").val()+"

"; + if(fvf_add==1){ + this.table.fnAddData([string, 'Edit', 'Delete']); + } + else{ + this.table.fnDeleteRow(fvf_nrow); this.table.fnAddData([string, 'Edit', 'Delete']); - jQuery( "#univbris_flowspace_selection" ).show(); - } + } + jQuery( "#univbris_foam_ports_selection" ).hide(); + jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); + } + else{ + alert("validation failed"); + jQuery("#uob_fv_table_form").show(); + jQuery("#uob_ofv_table_form").hide(); + jQuery( "#univbris_foam_ports_selection" ).show(); + } + } + else{ + + var sData=$("#uob_ofv_table_form").find("input").serialize(); + var form =serializeAnything("#uob_ofv_table_form"); + var port_table=$("#univbris_foam_ports_selection__table").dataTable(); + var form2=$('input',port_table.fnGetNodes()).serialize(); + var nodes = $('input',port_table.fnGetNodes()); + + this.table = $("#univbris_flowspace_selection__table").dataTable(); + + var val_status=validateoFvfForm(); + if (val_status == true){ + opt_flowspace_index=1+opt_flowspace_index; + flowspace=sData; + var m_form=form+","+form2; + var string = "

"+$("#oflowspace_name").val()+"

"; + if(fvf_add==1){ + this.table.fnAddData([string, 'Edit', 'Delete']); + } + else{ + this.table.fnDeleteRow(fvf_nrow); + this.table.fnAddData([string, 'Edit', 'Delete']); + } + jQuery( "#univbris_foam_ports_selection" ).hide(); + jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); + } + else{ + alert("validation failed"); + jQuery("#uob_ofv_table_form").show(); + jQuery("#uob_fv_table_form").hide(); + jQuery( "#univbris_foam_ports_selection" ).show(); + } + + } + /*} else{ jQuery("#uob_fv_table_form").hide(); + jQuery("#uob_ofv_table_form").hide(); var sData=$("#uob_fv_table_form").find("input").serialize(); var form =serializeAnything("#uob_fv_table_form"); + //var form2=serializeAnything("#uob_form"); + + var port_table=$("#univbris_foam_ports_selection__table").dataTable(); + var form2=$('input',port_table.fnGetNodes()).serialize(); this.table = $("#univbris_flowspace_selection__table").dataTable(); flowspace=sData; - - var string = "

"+$("#flowspace_name").val()+"

"; + var m_form=form+","+form2; + var string = "

"+$("#flowspace_name").val()+"

"; this.table.fnDeleteRow(fvf_nrow); this.table.fnAddData([string, 'Edit', 'Delete']); + + jQuery( "#univbris_foam_ports_selection" ).hide(); jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); - } + }*/ }, - fnModflowspace:function(e){ - //alert("modify"); - - jQuery("#uob_fv_table_form").hide(); - var sData=$("#uob_fv_table_form").find("input").serialize(); - var form =serializeAnything("#uob_fv_table_form"); - this.table = $("#univbris_flowspace_selection__table").dataTable(); - flowspace=sData; - alert(form+"\n"+sData); - - var string = "

"+$("#flowspace_name").val()+"

"; - this.table.fnDeleteRow(fvf_nrow); - this.table.fnAddData([string, 'Edit', 'Delete']); - jQuery( "#univbris_flowspace_selection" ).show(); - }, - /** * @brief Determine index of key in the table columns * @param key @@ -311,24 +380,6 @@ line.push("first"); } - - /*if (typeof colnames[j] == 'undefined') { - line.push('...'); - } else if (colnames[j] == 'hostname') { - if (record['type'] == 'resource,link') - //TODO: we need to add source/destination for links - line.push(''); - else - line.push(record['hostname']); - - } else if (colnames[j] == 'hrn' && typeof(record) != 'undefined') { - line.push(' '+record['hrn']); - } else { - if (record[colnames[j]]) - line.push(record[colnames[j]]); - else - line.push(''); - }*/ } // catch up with the last column if checkboxes were requested @@ -635,9 +686,9 @@ $("#addflowspaceform").unbind('click').click(this, this.fnAddflowspace); } else{ - $("[id='addflowspaceform'").unbind('click').click(this, this.fnModflowspace); + $("[id='addflowspaceform']").unbind('click').click(this, this.fnModflowspace); } - $("#cancel_addflowspaceform").unbind('click').click(this,this.fnCancel); + $("#cancel_addflowspaceform").unbind('click').click(this,this.fnCancel); if (!this.table) return; @@ -717,38 +768,521 @@ })(jQuery); +function deserializeDT(d){ -function fnPopTable(e){ - //alert("e: "+e); - //this.table = $("#univbris_flowspace_selection__table").dataTable(); - //alert(this.table); - /**var rows = $("#univbris_flowspace_selection__table").dataTable().fnGetNodes(); - for(var i=0;i= 19) { + status=true; + } + + if (port_selected==false & checked == 18){ + alert("you need to select at least one port"); + } + else if (port_selected==false & checked <= 18){ + alert("you need to select at least one port and correct other flowspace parameter errors"); + } + else if (port_selected==true & checked <= 18){ + alert("you need to correct other flowspace parameter errors"); + } + + //alert("validator status:"+status+" checked:"+checked); + return status; +} + +function validateoFvfForm(){ + var status = false; + var checked =0; + + //row 1 validation + if (wavelengthValidator($('#uob_ofv_table_wavelength').val())==false){ + $("#uob_ofv_table_wavelength").addClass('error'); + $("#uob_ofv_table_wavelength_error").show(); + } + else { + checked++; + } + + + + //validate that at least one port is selected + var port_table=$("#univbris_foam_ports_selection__table").dataTable(); + var nodes = $('input',port_table.fnGetNodes()); + + var port_selected=false; + for(var i=0;i= 2) { + status=true; + } + + if (port_selected==false & checked == 1){ + alert("you need to select at least one port"); + } + else if (port_selected==false & checked <= 1){ + alert("you need to select at least one port and correct other flowspace parameter errors"); + } + else if (port_selected==true & checked <= 1){ + alert("you need to correct other flowspace parameter errors"); + } + + //alert("validator status:"+status+" checked:"+checked); + return status; +} + + function fnGetSelected( oTableLocal ) { @@ -777,7 +1311,28 @@ function serializeAnything (form){ }); return toReturn.join("&").replace(/%20/g, "+"); +} + +function hideFvfError(){ + $("[id*=_error]").hide(); + console + $("#uob_fv_table_form :input").each(function(){ + try{ + $(this).removeClass('error'); + } + catch (err){ + } + + }); + + $("#uob_ofv_table_form :input").each(function(){ + try{ + $(this).removeClass('error'); + } + catch (err){ + } + }); } diff --git a/plugins/univbrisfvf/templates/univbrisfv.html b/plugins/univbrisfvf/templates/univbrisfv.html index 44d51af8..d6900f63 100644 --- a/plugins/univbrisfvf/templates/univbrisfv.html +++ b/plugins/univbrisfvf/templates/univbrisfv.html @@ -21,7 +21,7 @@ - ' + ' diff --git a/plugins/univbrisfvf/templates/univbrisfvf.html b/plugins/univbrisfvf/templates/univbrisfvf.html index 63973cb3..bb531c5f 100644 --- a/plugins/univbrisfvf/templates/univbrisfvf.html +++ b/plugins/univbrisfvf/templates/univbrisfvf.html @@ -9,22 +9,37 @@ .multiselect label { display:block; } - l + .multiselect-on { color:#ffffff; background-color:#000099; } + +.error{ +border:2px solid red; +} +
- +

+ + + + + + + @@ -46,10 +65,12 @@ + @@ -63,6 +84,9 @@ + @@ -76,6 +100,9 @@ + @@ -89,6 +116,9 @@ + @@ -102,6 +132,9 @@ + @@ -115,6 +148,9 @@ + @@ -128,6 +164,9 @@ + @@ -141,19 +180,13 @@ + - - - - +
Flowspace Name - + +
+
from
+
+
to
+
MAC Source @@ -37,6 +52,10 @@ +

mac format should be ff:ff:ff:ff:ff:ff

+
MAC Destination - +

mac format should be ff:ff:ff:ff:ff:ff

+
Ethernet Type +

dl type format should be hex format: 0xffff

+
VLAN ID +

vlan value should be integer between 1 and 4095

+
IP Source +

ip format should be xxx.xxx.xxx.xxx

+
IP Destination +

ip format should be xxx.xxx.xxx.xxx

+
IP Protocol +

ip protocol should be integer between 0 and 255

+
TCP/UDP Source +

tcp value should be integer between 0 and 65535

+
TCP/UDP Destination +

tcp value should be integer between 0 and 65535

+
Ports - - -
-
-
-
diff --git a/plugins/univbrisfvfo/__init__.py b/plugins/univbrisfvfo/__init__.py new file mode 100644 index 00000000..b774faee --- /dev/null +++ b/plugins/univbrisfvfo/__init__.py @@ -0,0 +1,87 @@ +from unfold.plugin import Plugin + +class UnivbrisFvfo (Plugin): + + """ + +//////////////////////////////////////// + +modified querytable for univbris foam +/////////////////////////////////////// + +A plugin for displaying a query as a list + +More accurately, we consider a subject entity (say, a slice) +that can be linked to any number of related entities (say, resources, or users) +The 'query' argument will correspond to the subject, while +'query_all' will fetch the complete list of +possible candidates for the relationship. + +Current implementation makes the following assumptions +* query will only retrieve for the related items a list of fields + that corresponds to the initial set of fields displayed in the table +* query_all on the contrary is expected to return the complete set of + available attributes that may be of interest, so that using a QueryEditor + one can easily extend this table without having to query the backend +* checkboxes is a boolean flag, set to true if a rightmost column + with checkboxes is desired +* optionally pass columns as the initial set of columns + if None then this is taken from the query's fields +* init_key is the name of a column that should appear in both queries + and used internally in the plugin for checkboxes initialization. + If not specified, metadata will be used to find out a primary key. + However in the case of nodes & slice for example, the default key + as returned by the metadata would be 'urn', but 'urn' could only + be used for this purpose if it gets displayed initially, which is + not necessarily a good idea. + This is why a slice view would use 'hrn' here instead. +* datatables_options are passed to dataTables as-is; + however please refrain from passing an 'aoColumns' + as we use 'aoColumnDefs' instead. +""" + + def __init__ (self, query=None, query_all=None, + checkboxes=False, columns=None, + init_key=None, + datatables_options={}, **settings): + Plugin.__init__ (self, **settings) + self.query = query + # Until we have a proper way to access queries in Python + self.query_all = query_all + self.query_all_uuid = query_all.query_uuid if query_all else None + + + def template_file (self): + return "univbrisfvfo.html" + + def template_env (self, request): + env={} + env.update(self.__dict__) + #env['columns']=self.columns + return env + + def requirements (self): + reqs = { + 'js_files' : [ "js/spin-presets.js", "js/spin.min.js", "js/jquery.spin.js", + "js/dataTables.js", "js/dataTables.bootstrap.js", "js/with-datatables.js", + "js/manifold.js", "js/manifold-query.js", + "js/unfold-helper.js", + # querytable.js needs to be loaded after dataTables.js as it extends + # dataTableExt.afnSortData + "js/univbrisfvfo.js", + ] , + 'css_files': [ "css/dataTables.bootstrap.css", + # hopefully temporary, when/if datatables supports sPaginationType=bootstrap3 + # for now we use full_numbers, with our own ad hoc css + "css/dataTables.full_numbers.css", + "css/univbrisfvfo.css", + ], + } + return reqs + + # the list of things passed to the js plugin + def json_settings_list (self): + return ['plugin_uuid', 'domid', + 'query_uuid', 'query_all_uuid', + 'checkboxes', 'datatables_options', + 'hidden_columns', 'init_key',] diff --git a/plugins/univbrisfvfo/static/css/univbrisfvfo.css b/plugins/univbrisfvfo/static/css/univbrisfvfo.css new file mode 100644 index 00000000..4b518d3c --- /dev/null +++ b/plugins/univbrisfvfo/static/css/univbrisfvfo.css @@ -0,0 +1,65 @@ + +/* the bottom of the datatable needs more space */ +div.univbrisfvf-spacer { padding: 8px 4px 15px 4px; } + +div.UnivbrisFv table.dataTable th { + font: bold 12px/22px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; + color: #4f6b72; + border-right: 1px solid #C1DAD7; + border-bottom: 1px solid #C1DAD7; + border-top: 1px solid #C1DAD7; + letter-spacing: 1px; + text-transform: uppercase; + text-align: left; + padding: 8px 12px 4px 20px; + vertical-align:middle; +/* background: #CAE8EA url(../img/tablesort-header.jpg) no-repeat; */ +} + +div.UnivbrisFv table.dataTable th.checkbox { + padding-left: 14px; +} + +div.UnivbrisFv table.dataTable td, div.UnivbrisFv table.dataTable textarea, div.UnivbrisFv table.dataTable input [type="text"] { + font: bold 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; + color: #660099; + border-right: 1px solid #C1DAD7; + border-bottom: 1px solid #C1DAD7; +} +div.UnivbrisFv table.dataTable td { + padding: 4px 8px 4px 8px; + /* this applies on even rows only, odd ones have a setting in bootstrap of rbg 249.249.249 */ + background-color: #f4f4f4; + color: #660099; +} +div.UnivbrisFv table.dataTable td a { + font-weight:normal; +} +/* these come from bootstrap */ +div.UnivbrisFv div.dataTables_info { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +/* one could think or using repeat-x here but that's not working because of the arrows + * we might need to make these wider some day + * and/or to add background-color: #caebea + * which would look less conspicuous in case of overflow +*/ + +div.UnivbrisFv table.dataTable thead .sorting { background: url('../img/tablesort-header-sortable.png') no-repeat; } +div.UnivbrisFv table.dataTable thead .sorting_asc { background: url('../img/tablesort-header-up.png') no-repeat; } +div.UnivbrisFv table.dataTable thead .sorting_desc { background: url('../img/tablesort-header-down.png') no-repeat; } +/* this icons set does not have that exact equivalent - using an approximation for now */ +div.UnivbrisFv table.dataTable thead .sorting_asc_disabled { background: url('../img/tablesort-header.png') repeat-x; } +div.UnivbrisFv table.dataTable thead .sorting_desc_disabled { background: url('../img/tablesort-header.png') repeat-x; } + +/* the footers are not active */ +div.UnivbrisFv table.dataTable tfoot { + background: url('../img/tablesort-header.png') repeat-x; + background-color: #caebea; +} +/* and when sorting is turned off it's useful to set this on header too */ +div.UnivbrisFv table.dataTable thead { + background: url('../img/tablesort-header.png') repeat-x; + background-color: #caebea; +} diff --git a/plugins/univbrisfvfo/static/img/README b/plugins/univbrisfvfo/static/img/README new file mode 100644 index 00000000..5df2d69b --- /dev/null +++ b/plugins/univbrisfvfo/static/img/README @@ -0,0 +1,2 @@ +these styling elements come from plekit with a simple transition to png +they're currently not all used in myslice diff --git a/plugins/univbrisfvfo/static/img/tablesort-bullet1.png b/plugins/univbrisfvfo/static/img/tablesort-bullet1.png new file mode 100644 index 00000000..4304f360 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-bullet1.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-bullet2.png b/plugins/univbrisfvfo/static/img/tablesort-bullet2.png new file mode 100644 index 00000000..4f181e19 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-bullet2.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-col-alt.png b/plugins/univbrisfvfo/static/img/tablesort-col-alt.png new file mode 100644 index 00000000..8179f830 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-col-alt.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-gradient.png b/plugins/univbrisfvfo/static/img/tablesort-gradient.png new file mode 100644 index 00000000..26558a49 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-gradient.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-header-down.png b/plugins/univbrisfvfo/static/img/tablesort-header-down.png new file mode 100644 index 00000000..c8ed6576 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-header-down.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-header-sortable.png b/plugins/univbrisfvfo/static/img/tablesort-header-sortable.png new file mode 100644 index 00000000..0c169040 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-header-sortable.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-header-up.png b/plugins/univbrisfvfo/static/img/tablesort-header-up.png new file mode 100644 index 00000000..d12fe2a5 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-header-up.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-header.png b/plugins/univbrisfvfo/static/img/tablesort-header.png new file mode 100644 index 00000000..cff526f9 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-header.png differ diff --git a/plugins/univbrisfvfo/static/img/tablesort-td-alt.png b/plugins/univbrisfvfo/static/img/tablesort-td-alt.png new file mode 100644 index 00000000..ef5ab358 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/tablesort-td-alt.png differ diff --git a/plugins/univbrisfvfo/static/img/toggle-hidden.png b/plugins/univbrisfvfo/static/img/toggle-hidden.png new file mode 100755 index 00000000..023f22a8 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/toggle-hidden.png differ diff --git a/plugins/univbrisfvfo/static/img/toggle-visible.png b/plugins/univbrisfvfo/static/img/toggle-visible.png new file mode 100755 index 00000000..baf6c286 Binary files /dev/null and b/plugins/univbrisfvfo/static/img/toggle-visible.png differ diff --git a/plugins/univbrisfvfo/static/js/univbrisfvfo.js b/plugins/univbrisfvfo/static/js/univbrisfvfo.js new file mode 100644 index 00000000..7d502a85 --- /dev/null +++ b/plugins/univbrisfvfo/static/js/univbrisfvfo.js @@ -0,0 +1,1266 @@ +/** + * Description: display a query result in a datatables-powered + * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA + * License: GPLv3 + */ + +(function($){ + + var debug=false; + debug=true; + + + var UnivbrisFvfo = Plugin.extend({ + + init: function(options, element) { + //alert("foam init called"); + this.classname="univbrisfvfo"; + this._super(options, element); + + //alert(this.options.hidden_columns); + /* Member variables */ + // in general we expect 2 queries here + // query_uuid refers to a single object (typically a slice) + // query_all_uuid refers to a list (typically resources or users) + // these can return in any order so we keep track of which has been received yet + //this.received_all_query = false; + //this.received_query = false; + + // We need to remember the active filter for datatables filtering + this.filters = Array(); + + // an internal buffer for records that are 'in' and thus need to be checked + this.buffered_records_to_check = []; + // an internal buffer for keeping lines and display them in one call to fnAddData + this.buffered_lines = []; + + /* Events */ + // xx somehow non of these triggers at all for now + //this.elmt().on('show', this, this.on_show); + //this.elmt().on('shown.bs.tab', this, this.on_show); + //this.elmt().on('resize', this, this.on_resize); + + //var query = manifold.query_store.find_analyzed_query(this.options.query_uuid); + //this.object = query.object; + + //// we need 2 different keys + // * canonical_key is the primary key as derived from metadata (typically: urn) + // and is used to communicate about a given record with the other plugins + // * init_key is a key that both kinds of records + // (i.e. records returned by both queries) must have (typically: hrn or hostname) + // in general query_all will return well populated records, but query + // returns records with only the fields displayed on startup + var keys = manifold.metadata.get_key(this.object); + this.canonical_key = (keys && keys.length == 1) ? keys[0] : undefined; + // + this.init_key = this.options.init_key; + // have init_key default to canonical_key + this.init_key = this.init_key || this.canonical_key; + // sanity check + if ( ! this.init_key ) messages.warning ("UnivbrisFvfo : cannot find init_key"); + if ( ! this.canonical_key ) messages.warning ("UnivbrisFvfo : cannot find canonical_key"); + if (debug) messages.debug("UnivbrisFvfo: canonical_key="+this.canonical_key+" init_key="+this.init_key); + + /* Setup query and record handlers */ + //this.listen_query(options.query_uuid); + //this.listen_query(options.query_all_uuid, 'all'); + + /* GUI setup and event binding */ + //this.initialize_table(); + //alert("init fvf"); + jQuery("#uob_ofv_table_form").hide(); + + //$('').appendTo('#fvf_table_button'); + + //$('').appendTo('#fvf_table_button'); + + this._querytable_draw_callback(); + }, + + /* PLUGIN EVENTS */ + + on_show: function(e) { + if (debug) messages.debug("univbrisfvfo.on_show"); + var self = e.data; + self.table.fnAdjustColumnSizing(); + }, + + on_resize: function(e) { + if (debug) messages.debug("univbrisfvfo.on_resize"); + var self = e.data; + self.table.fnAdjustColumnSizing(); + }, + + /* GUI EVENTS */ + + /* GUI MANIPULATION */ + + initialize_table: function() + { + /* Transforms the table into DataTable, and keep a pointer to it */ + var self = this; + var actual_options = { + // Customize the position of Datatables elements (length,filter,button,...) + // we use a fluid row on top and another on the bottom, making sure we take 12 grid elt's each time + //sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-7'p>>", + //sDom: "<'row'<'col-xs-2'l><'col-xs-9'r><'col-xs-2'f>>t<'row'<'col-xs-5'i><'col-xs-5'p>><'next'>", + sDom: "<'row'<'col-xs-9'r>t<'buttons'>", + // XXX as of sept. 2013, I cannot locate a bootstrap3-friendly mode for now + // hopefully this would come with dataTables v1.10 ? + // in any case, search for 'sPaginationType' all over the code for more comments + sPaginationType: 'bootstrap', + // Handle the null values & the error : Datatables warning Requested unknown parameter + // http://datatables.net/forums/discussion/5331/datatables-warning-...-requested-unknown-parameter/p2 + aoColumnDefs: [{sDefaultContent: '',aTargets: [ '_all' ]}], + // WARNING: this one causes tables in a 'tabs' that are not exposed at the time this is run to show up empty + // sScrollX: '100%', /* Horizontal scrolling */ + bProcessing: false, /* Loading */ + fnDrawCallback: function() { self._querytable_draw_callback.call(self);} + //fnFooterCallback: function() {self._univbrisfvf_footer_callback.call(self,nFoot, aData, iStart, iEnd, aiDisplay)};} + // XXX use $.proxy here ! + }; + // the intention here is that options.datatables_options as coming from the python object take precedence + // xxx DISABLED by jordan: was causing errors in datatables.js + // xxx turned back on by Thierry - this is the code that takes python-provided options into account + // check your datatables_options tag instead + // however, we have to accumulate in aoColumnDefs from here (above) + // and from the python wrapper (checkboxes management, plus any user-provided aoColumnDefs) + if ( 'aoColumnDefs' in this.options.datatables_options) { + actual_options['aoColumnDefs']=this.options.datatables_options['aoColumnDefs'].concat(actual_options['aoColumnDefs']); + delete this.options.datatables_options['aoColumnDefs']; + } + $.extend(actual_options, this.options.datatables_options ); + this.table = $("#univbris_flowspace_form__table").dataTable(actual_options); + + //alert(this.table); + + /* Setup the SelectAll button in the dataTable header */ + /* xxx not sure this is still working */ + var oSelectAll = $('#datatableSelectAll-'+ this.options.plugin_uuid); + oSelectAll.html("Select All"); + oSelectAll.button(); + oSelectAll.css('font-size','11px'); + oSelectAll.css('float','right'); + oSelectAll.css('margin-right','15px'); + oSelectAll.css('margin-bottom','5px'); + oSelectAll.unbind('click'); + oSelectAll.click(this._selectAll); + + /* Add a filtering function to the current table + * Note: we use closure to get access to the 'options' + */ + $.fn.dataTableExt.afnFiltering.push(function( oSettings, aData, iDataIndex ) { + /* No filtering if the table does not match */ + if (oSettings.nTable.id != self.options.plugin_uuid + '__table') + return true; + return self._querytable_filter.call(self, oSettings, aData, iDataIndex); + }); + + //alert(this.options.hidden_columns); + + /* Processing hidden_columns */ + $.each(this.options.hidden_columns, function(i, field) { + //manifold.raise_event(self.options.query_all_uuid, FIELD_REMOVED, field); + //alert (field); + self.hide_column(field); + //self.hide_column(field); + }); + + }, // initialize_table + + + fnCancel:function(e){ + //var sData=$("#uob_fv_table_form").find("input").serialize(); + //alert("add flowspace:" + sData); + //alert("cancel"); + + jQuery("#uob_ofv_table_form").hide(); + jQuery( "#univbris_foam_ports_selection" ).hide(); + jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); + /*var port_table=$("#univbris_foam_ports_selection__table").dataTable(); + var nodes = $('input',port_table.fnGetNodes()); + for(var i=0;i "+$("#flowspace_name").val()+"

"; + this.table.fnAddData([string, 'Edit', 'Delete']); + jQuery( "#univbris_foam_ports_selection" ).hide(); + jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); + } + else{ + alert("validation failed"); + jQuery("#uob_ofv_table_form").show(); + jQuery( "#univbris_foam_ports_selection" ).show(); + } + } + else{ + jQuery("#uob_fv_table_form").hide(); + var sData=$("#uob_fv_table_form").find("input").serialize(); + var form =serializeAnything("#uob_fv_table_form"); + //var form2=serializeAnything("#uob_form"); + + var port_table=$("#univbris_foam_ports_selection__table").dataTable(); + var form2=$('input',port_table.fnGetNodes()).serialize(); + this.table = $("#univbris_flowspace_selection__table").dataTable(); + flowspace=sData; + var m_form=form+","+form2; + var string = "

"+$("#flowspace_name").val()+"

"; + this.table.fnDeleteRow(fvf_nrow); + this.table.fnAddData([string, 'Edit', 'Delete']); + + jQuery( "#univbris_foam_ports_selection" ).hide(); + jQuery( "#univbris_flowspace_selection" ).show(); + jQuery('#topo_plugin').hide(); + + } + }, + + + /** + * @brief Determine index of key in the table columns + * @param key + * @param cols + */ + getColIndex: function(key, cols) { + var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; }); + return (tabIndex.length > 0) ? tabIndex[0] : -1; + }, // getColIndex + + // create a checkbox tag + // computes 'id' attribute from canonical_key + // computes 'init_id' from init_key for initialization phase + // no need to used convoluted ids with plugin-uuid or others, since + // we search using table.$ which looks only in this table + checkbox_html : function (record) { + var result=""; + // Prefix id with plugin_uuid + result += "
a"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = + document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>"; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + fragment = div = input = null; +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && ( + // Support: IE < 9 + src.returnValue === false || + // Support: Android < 4.0 + src.getPreventDefault && src.getPreventDefault() ) ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!support.noCloneEvent || !support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + deletedIds.push( id ); + } + } + } + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "