Changeset 1126 in main


Ignore:
Timestamp:
05/25/12 15:28:29 (7 years ago)
Author:
zali
Message:

Document file: select multiple files per input file (Chrome,Firefox). Input files and progress bar should work for IE and opera.

Location:
trunk/openPLM
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/openPLM/media/css/files.css

    r1124 r1126  
    1818.file_p{ 
    1919    padding : 10px;  
     20} 
     21 
     22span.del_link{ 
     23    cursor:pointer; 
     24    float:left; 
     25    position:relative; 
     26    bottom:5px; 
    2027} 
    2128 
  • trunk/openPLM/media/js/file.js

    r1124 r1126  
    1414    var input = $("<input type='file' name='filename"+num+"' id='id_filename"+num+"'/ >"); 
    1515    input.change(function(){ 
    16         var f_name = this.files[0].name; 
     16        /*var f_name = this.files[0].name; 
    1717        if(can_add_file(f_name)){ 
    1818            $(".up_fail").remove(); 
    1919            $("span.warning").remove(); 
    2020            add_file(this); 
    21         } 
    22     }); 
     21        }*/ 
     22        if(at_least_one(this)){ 
     23            $(".up_fail").remove(); 
     24            add_file(this); 
     25        }else{ 
     26             $.each(this.files, function(id,file){ 
     27                var can_add=can_add_file(file.name,true); 
     28                if (can_add==false){ 
     29                    can_add_native(file.name,true); 
     30                } 
     31            }); 
     32        } 
     33        $("span.warning").remove(); 
     34    }); 
     35    if(($.browser.msie!=true)&&($.browser.opera!=true)){ 
     36        input.attr("multiple","multiple"); 
     37    } 
    2338    var td_input = $("<td></td>"); 
    2439    td_input.append(input); 
     
    3853function render_size(f_size){ 
    3954    if (f_size<1000){ 
    40         return f_size+" bytes"; 
     55            return f_size+" bytes"; 
    4156    } 
    4257    if (f_size<=1024){ 
    43         return "1 KB"; 
     58            return "1 KB"; 
    4459    } 
    4560    var ret=""; 
     
    7590} 
    7691 
    77 function can_add_native(file_name){ 
    78     var ret; 
    79     var xhr_nat= getXHR(); 
    80     var url=location.href; 
    81     url=url.replace("/files/","/files/add/?file_name="+file_name); 
    82     xhr_nat.open("GET",url,false); 
    83     xhr_nat.onreadystatechange=function(){ 
    84         if(xhr_nat.readyState == 4){ 
    85             ret=xhr_nat.responseText.split(":"); 
    86         } 
    87     } 
    88     xhr_nat.send(null); 
     92/*test if at least one file from the input can be added*/ 
     93function at_least_one(input){ 
     94    var ret=false; 
     95    files = input.files; 
     96    var nbr=0; 
     97    $.each(files,function(ind,file){ 
     98        if((can_add_file(file.name,false))&&(can_add_native(file.name,false))){ 
     99            ret=true; 
     100        } 
     101    }); 
     102    return ret; 
     103} 
     104 
     105function can_add_native(file_name,display){ 
     106    var response; 
     107    var ret=true; 
     108    if((doc_type.toLowerCase()=="document3d")&&(xhr!=null)){ 
     109        var xhr_nat= getXHR(); 
     110        var url=location.href; 
     111        url=url.replace("/files/","/files/add/?file_name="+file_name); 
     112        xhr_nat.open("GET",url,false); 
     113        xhr_nat.onreadystatechange=function(){ 
     114            if(xhr_nat.readyState == 4){ 
     115                response=xhr_nat.responseText.split(":"); 
     116                if(response[0]=="true"){ 
     117                    ret=false; 
     118                } 
     119                if((display==true)&&(response[0]=="true")){ 
     120                    //$(".up_fail").remove(); 
     121                    var warning_msg= $("<div class='up_fail'></div>"); 
     122                    $(warning_msg).text(response[1]); 
     123                    $("#fileupload").after(warning_msg); 
     124                } 
     125            } 
     126        } 
     127        xhr_nat.send(null); 
     128    } 
    89129    return ret; 
    90130} 
     
    97137} 
    98138 
    99 function can_add_file(file_name){ 
     139function can_add_file(file_name,display){ 
    100140    var msg_error=""; 
    101141    if((file_linked(file_name)==false)&&(file_selected(file_name)==false)){ 
     
    112152        } 
    113153    } 
    114     if($(".up_fail").length == 0){ 
     154    if(display==true){ 
     155        /*if($(".up_fail").length == 0){ 
     156            var div_error=$("<div class='up_fail'></div>"); 
     157            $("#fileupload").after(div_error); 
     158        }else{ 
     159            msg_error="<p>"+msg_error+"</p>"; 
     160        }*/ 
    115161        var div_error=$("<div class='up_fail'></div>"); 
     162        div_error.text(msg_error); 
    116163        $("#fileupload").after(div_error); 
    117164    } 
    118     $(".up_fail").text(msg_error); 
    119165    return false; 
    120166} 
    121167 
     168function add_f_file(input,f){ 
     169    //test if the file f can be added 
     170    if(can_add_file(f.name,true)){ 
     171        //test if the file can be added even if it is a native file 
     172        if((can_add_native(f.name,true))||(doc_type.toLowerCase()!="document3d")){ 
     173 
     174            var key = f.name.replace(".","_"); 
     175            var size=f.size; 
     176 
     177            //create line for file progress 
     178            var file_line = $("<div id='"+key+"' class='file_p'>"+f.name+"("+render_size(size)+") "+"</div>"); 
     179            var progress = $("<span class='progress'><span class='text'></span></span>"); 
     180            file_line.append(progress); 
     181 
     182            //create link to delete this file from the queue for upload 
     183            var link = $("<span class='del_link'> </span>"); 
     184            link.click(function(){ 
     185                del_file(file_line,input); 
     186            }); 
     187            var del_img = "<img src='/media/img/trash_can1.png' alt='delete' title='"+trans["remove the file from the queue"]+"'>"; 
     188            link.append(del_img); 
     189            file_line.append(link); 
     190 
     191            //add the file in the list of files to upload 
     192            $("#div_files").append(file_line); 
     193 
     194            var f_id = gen_uuid(); 
     195             
     196            if(($.browser.opera!=true)&&($.browser.msie!=true)){ 
     197                /*var data_key = "filename"; 
     198                if(nbr_files!=0){ 
     199                    data_key+=nbr_files; 
     200                } 
     201                data.append(data_key,f);*/ 
     202                //create an object related to the file in the files_info array-like 
     203                files_info[key]={"field_name":" ", "f_name":f.name, "p_id":f_id, "size":size, "uploaded":0, "status":"waiting"}; 
     204            }else{ 
     205                files_info[key]={"f_name":f.name, "p_id":f_id, "size":size, "uploaded":0, "status":"waiting"}; 
     206            } 
     207             
     208             
     209            nbr_files+=1; 
     210        } 
     211    } 
     212} 
    122213//add a file in the queue for uploading 
    123214function add_file(input){ 
     
    126217    } 
    127218 
    128     //create line where the progress of the upload will be display for this input 
     219   /* //create line where the progress of the upload will be display for this input 
    129220    var f_name = input.value; 
    130221    if(f_name.substr(0, 12) == "C:\\fakepath\\"){ 
     
    134225    var size = input.files[0].size; 
    135226    var mod_2 = nbr_files%2; 
    136     var file_line = $("<div id='"+key+"' class='file_p'>"+input.files[0].name+"("+render_size(size)+") "+"</div>"); 
     227    var file_line = $("<div id='"+key+"' class='file_p'><span>"+input.files[0].name+"("+render_size(size)+")</span></div>"); 
    137228    var progress = $("<span class='progress'><span class='text'></span></span>"); 
    138229    file_line.append(progress); 
    139230 
    140231    //create link to delete this file from the queue for upload 
    141     var link = $("<a class='del_link'></a>"); 
     232    var link = $("<span class='del_link'> </span>"); 
    142233    link.click(function(){ 
    143         del_file(file_line,input); 
    144     }); 
    145     var del_img = "<img src='/media/img/delete.png' alt='delete' title='"+trans["remove the file from the queue"]+"'>"; 
     234            del_file(file_line,input); 
     235    }); 
     236    var del_img = "<img src='/media/img/trash_can1.png' alt='delete' title='"+trans["remove the file from the queue"]+"'>"; 
    146237    link.append(del_img); 
    147     var span_link =$("<span></span>"); 
    148     span_link.append(link); 
    149     file_line.append(span_link); 
     238    file_line.append(link); 
    150239    //add the file in the list of files to upload 
    151     $("#div_files").append(file_line); 
     240    $("#div_files").append(file_line);*/ 
     241    $.each(input.files,function(ind,f){ 
     242        add_f_file(input,f); 
     243    }) 
    152244    if($("#fileupload").attr("action")=="."){ 
    153         add_in_queue_form(input); 
     245        //add_in_queue_form(input); 
     246        //$(input).parent().parent().hide(); 
     247        //if($.browser.opera){ 
     248            add_in_queue_form(input); 
     249        /*}else{ 
     250            hide(input); 
     251        }*/ 
    154252        new_input_file(); 
    155253    } 
    156     nbr_files+=1; 
     254    /*nbr_files+=1; 
    157255    var f_id = gen_uuid(); 
    158256    //create an object related to the file in the files_info array-like 
    159     files_info[key]={"f_name":f_name, "p_id":f_id, "size":size, "uploaded":0, "status":"waiting"}; 
     257    files_info[key]={"f_name":f_name, "p_id":f_id, "size":size, "uploaded":0, "status":"waiting"};*/ 
    160258} 
    161259 
     
    166264 
    167265//delete a file from list and form which contains files to upload 
    168 function del_file(item,input){ 
     266/*function del_file(item,input){ 
    169267    var key = item.attr("id"); 
    170268    files_info = files_info.remove(key); 
     
    173271    nbr_files--; 
    174272    $(".up_fail").remove(); 
     273}*/ 
     274function del_file(item,input){ 
     275    var key = item.attr("id"); 
     276    files_info=files_info.remove(key); 
     277    item.remove(); 
     278    nbr_files--; 
     279    $(".up_fail").remove(); 
     280    if(($.browser.opera)||($.browser.msie)){ 
     281        $(input).remove(); 
     282    } 
    175283} 
    176284 
     
    310418    var keys = files_info.key(); 
    311419    $.each(keys,function(index,val){ 
    312         ret += val+"="+files_info[val].p_id+"&"; 
     420        ret += files_info[val].field_name+"="+files_info[val].p_id+"&"; 
    313421    }); 
    314422    ret = ret.substr(0,ret.length-1); 
     
    335443    xhr2.onreadystatechange = function() { 
    336444        if((xhr2.readyState == 4)&&(xhr2.status==200)) { 
    337             var response=xhr2.responseText; 
    338             var uploaded = parseInt(response.split(":")[0]); 
    339             var totalsize = files_info[key].size; 
    340             var f_status = response.split(":")[1]; 
    341             files_info[key].status= f_status; 
    342             var percent = progress_bar(uploaded, totalsize); 
    343             if(isNaN(percent)){ 
    344                 $("#"+key+" .progress .text").text("0% ("+files_info[key].status+")"); 
    345             }else{ 
    346                 var aux_per=""+percent; 
    347                 if (aux_per.split(".").length>1){ 
    348                     aux_per=aux_per.split(".")[0]+"."+aux_per.split(".")[1].substr(0,2); 
    349                 } 
    350                 $("#"+key+" .progress .text").text(aux_per+"%"); 
    351             } 
    352             if(f_status=="linking"){ 
    353                 files_info[key].uploaded=files_info[key].size; 
    354             }else{ 
    355                 if (f_status!="waiting"){ 
    356                     files_info[key].uploaded=parseInt(response.split(":")[0]); 
    357                     $("#"+key).find("progress").attr("value",percent); 
    358                 } 
    359             } 
    360         } 
     445                var response=xhr2.responseText; 
     446                var uploaded = parseInt(response.split(":")[0]); 
     447                var totalsize = files_info[key].size; 
     448            var f_status = response.split(":")[1]; 
     449            files_info[key].status= f_status; 
     450            var percent = progress_bar(uploaded, totalsize); 
     451            if(isNaN(percent)){ 
     452                        $("#"+key+" .progress .text").text("0% ("+files_info[key].status+")"); 
     453            }else{ 
     454                        var aux_per=""+percent; 
     455                        if (aux_per.split(".").length>1){ 
     456                            aux_per=aux_per.split(".")[0]+"."+aux_per.split(".")[1].substr(0,2); 
     457                        } 
     458                        $("#"+key+" .progress .text").text(aux_per+"%"); 
     459            } 
     460            if(f_status=="linking"){ 
     461                        files_info[key].uploaded=files_info[key].size; 
     462            }else{ 
     463                        if (f_status!="waiting"){ 
     464                            files_info[key].uploaded=parseInt(response.split(":")[0]); 
     465                            $("#"+key).find("progress").attr("value",percent); 
     466                        } 
     467            } 
     468        } 
    361469    } 
    362470    xhr2.send(null); 
    363471} 
    364472 
     473function init_files_data(f_form){ 
     474    //initialize field_name for each file selected 
     475    var keys = files_info.key(); 
     476    $.each(keys,function(id,key){ 
     477        files_info[key].field_name="filename"; 
     478        if(id>0){ 
     479            files_info[key].field_name+=id; 
     480        } 
     481    }); 
     482    var inputs_file = $(f_form).find("input[type='file']"); 
     483    if(($.browser.opera!=true)&&($.browser.msie!=true)){ 
     484        $.each(inputs_file, function(id, input){ 
     485            files = input.files; 
     486            $.each(files,function(id, file){ 
     487                var f_key = file.name; 
     488                f_key = f_key.replace(".","_"); 
     489                if(files_info[f_key]!=undefined){ 
     490                    data.append(files_info[f_key].field_name,file); 
     491                } 
     492            }); 
     493        }); 
     494    }else{ 
     495        opera_prepare_form(inputs_file); 
     496    } 
     497} 
     498 
     499function upload_failed(){ 
     500    var fail_div = $("<div class='up_fail'></div>"); 
     501    var span_text=trans["Your upload(s) failed"]+"!<br>"+trans["Try again"]; 
     502    if(nbr_files>1){ 
     503        span_text+=" "+trans["maybe with less files. "]; 
     504    }else{ 
     505        span_text+=". "; 
     506    } 
     507    span_text+="<br/>"+xhr.responseText; 
     508    fail_div.append(span_text); 
     509    $("#fileupload").after(fail_div); 
     510    $(".progress .text").text(" "); 
     511    $("progress").remove(); 
     512    $(".del_link").show(); 
     513    $("#global").remove(); 
     514    clearTimeout(t); 
     515} 
    365516//launch and track progress of upload file in the form f_form 
    366517function up_file(f_form){ 
     
    377528        new_action = new_action.split("/files")[0]+"/files/up/"; 
    378529    } 
     530    init_files_data(f_form); 
    379531    var ids_list = ids_to_data(); 
    380532    new_action +="?"+ids_list; 
     
    396548        } 
    397549        $("#global .progress .text").text(textTot+"%"); 
    398         t=window.setTimeout(update_progress_info, 1000); 
     550        t=window.setTimeout(update_progress_info, 1000) 
     551        if(($.browser.opera)||($.browser.msie)){ 
     552            var x=document.getElementById("hidden_frame"); 
     553            var y=(x.contentWindow || x.contentDocument); 
     554            if(y.document) 
     555                y=y.document; 
     556             
     557            if($(y).find("body").text()!=""){ 
     558                if($(y).find("body").text()=="."){ 
     559                    go_to=location.href; 
     560                }else{ 
     561                    upload_failed(); 
     562                } 
     563            } 
     564        } 
    399565        if(go_to!=""){ 
    400566            location.href=go_to; 
    401567        } 
    402568    } 
    403         t=window.setTimeout(update_progress_info, 1000); 
    404     xhr.onreadystatechange = function() { 
    405         if(xhr.readyState == 4){ 
    406             if(xhr.status==200) { 
    407                 go_to = location.href; 
    408                 //go_to = go_to.replace("/files*","/files"); 
    409                 go_to = go_to.split("/files")[0]+"/files/"; 
    410             }else{ 
    411                 var fail_div = $("<div class='up_fail'></div>"); 
    412                 var span_text=trans["Your upload(s) failed"]+"!<br>"+trans["Try again"]; 
    413                 if(nbr_files>1){ 
    414                     span_text+=" "+trans["maybe with less files. "]; 
     569    t=window.setTimeout(update_progress_info, 1000); 
     570    if(($.browser.opera!=true)&&($.browser.msie!=true)){ 
     571        xhr.onreadystatechange = function() { 
     572            if(xhr.readyState == 4){ 
     573                if(xhr.status==200) { 
     574                    go_to = location.href; 
     575                    //go_to = go_to.replace("/files*","/files"); 
     576                    go_to = go_to.split("/files")[0]+"/files/"; 
    415577                }else{ 
    416                     span_text+=". "; 
    417                 } 
    418                 fail_div.append(span_text); 
    419                 $("#fileupload").after(fail_div); 
    420                 $(".progress .text").text(" "); 
    421                 $("progress").remove(); 
    422                 $(".del_link").show(); 
    423                 clearTimeout(t); 
    424             } 
    425         } 
    426     } 
    427     xhr.open("POST", new_action,true); 
    428     var data = new FormData(f_form); 
    429     xhr.send(data); 
    430     /*var data = new FormData(f_form); 
    431     $.ajax({ 
    432         type: "POST", 
    433         url: new_action, 
    434         data: data, 
    435         success: function(msg){ 
    436             go_to = location.href; 
    437             //go_to = go_to.replace("/files*","/files"); 
    438             go_to = go_to.split("/files")[0]+"/files/"; 
    439         } 
    440     });*/ 
     578                    upload_failed(); 
     579                } 
     580            } 
     581        } 
     582        xhr.open("POST", new_action,true); 
     583        //var data = new FormData(f_form); 
     584        xhr.send(data); 
     585    }else{ 
     586        opera_up_file(f_form,new_action,go_to); 
     587    } 
    441588} 
    442589 
     
    480627         
    481628    if (window.XMLHttpRequest || window.ActiveXObject) { 
    482         if (window.ActiveXObject) { 
    483             try { 
    484                 xhr = new ActiveXObject("Msxml2.XMLHTTP"); 
    485             } catch(e) { 
    486                 xhr = new ActiveXObject("Microsoft.XMLHTTP"); 
     629            if (window.ActiveXObject) { 
     630                try { 
     631                        xhr = new ActiveXObject("Msxml2.XMLHTTP"); 
     632                } catch(e) { 
     633                        xhr = new ActiveXObject("Microsoft.XMLHTTP"); 
     634                } 
     635            } else { 
     636                    xhr = new XMLHttpRequest();  
    487637            } 
    488         } else { 
    489                 xhr = new XMLHttpRequest();  
    490         } 
    491638    } else { 
    492         return null; 
    493     } 
    494  
     639            return null; 
     640    } 
     641     
    495642    return xhr; 
    496643} 
    497644 
    498645var trans=[]; 
     646var data; 
     647if(($.browser.opera!=true)&&($.browser.msie!=true)){ 
     648    data = new FormData($('form.hidden')[0]); 
     649} 
    499650 
    500651$(function(){ 
     652    if(($.browser.msie!=true)&&($.browser.opera!=true)){ 
     653        $("input[type='file']").attr('multiple','multiple'); 
     654    } 
    501655    $("#add_file_container").toggleClass("hidden"); 
    502656    $("#add_form_file").toggleClass("hidden"); 
     
    529683 
    530684    $("input[type='file']").change(function (){ 
    531         var f_name = this.files[0].name; 
    532         if(xhr!=null){ 
    533             var can_add = can_add_native(f_name); 
    534             if (can_add[0]=="true"){ 
    535                 $(".up_fail").remove(); 
    536                 var warning_msg= $("<div class='up_fail'></div>"); 
    537                 $(warning_msg).text(can_add[1]); 
    538                 $("#fileupload").after(warning_msg); 
    539                 return; 
    540             } 
    541         } 
    542         if(can_add_file(f_name)){ 
     685        if(at_least_one(this)){ 
    543686            $(".up_fail").remove(); 
    544687            add_file(this); 
     688        }else{ 
     689            $.each(this.files, function(id,file){ 
     690                var can_add=can_add_file(file.name,true); 
     691                if (can_add==false){ 
     692                    can_add_native(file.name,true); 
     693                } 
     694            }); 
    545695        } 
    546696        $("span.warning").remove(); 
     
    548698 
    549699    $("#_delete").click(function(){ 
    550         //var file_lines = $("#div_files > div"); 
    551700        $("a.del_link").click(); 
    552701         
     
    595744            $('form.hidden')[0].method="post"; 
    596745            $('form.hidden')[0].submit(); 
    597         }); 
     746            }); 
    598747    } 
    599748}); 
  • trunk/openPLM/plmapp/filehandlers/progressbarhandler.py

    r1103 r1126  
    1313    def __init__(self, *args, **kwargs): 
    1414        super(ProgressBarUploadHandler, self).__init__(*args, **kwargs) 
    15         self.progress_id = {} 
     15        self.progress_id = {} 
    1616 
    1717    def new_file(self,file_name, *args, **kwargs): 
     
    1919        Create the file object to append to as data is coming in. 
    2020        """ 
    21         #self.progress_id = self.request.GET['X-Progress-ID'] 
    22         self.progress_id["%s" % args[0]]=self.request.GET["%s" % args[0]] 
     21        self.progress_id["%s" % file_name]=self.request.GET["%s" % file_name] 
     22        print "key : %s" % self.request.GET["%s" % file_name] 
    2323        super(ProgressBarUploadHandler, self).new_file(file_name, *args, **kwargs) 
    24         self.file = ProgressUploadedFile(self.progress_id["%s" % args[0]],self.file_name, self.content_type, 0, self.charset) 
    25         #self.file = ProgressUploadedFile(self.progress_id,self.file_name, self.content_type, 0, self.charset) 
     24        self.file = ProgressUploadedFile(self.progress_id["%s" % file_name],self.file_name, self.content_type, 0, self.charset) 
    2625 
    2726    def receive_data_chunk(self, raw_data, start): 
  • trunk/openPLM/plmapp/views/main.py

    r1125 r1126  
    10261026            f_name = f_name.encode("utf-8") 
    10271027            ret = obj.has_standard_related_locked(f_name) 
    1028             print "%s : %s" % (f_name,ret) 
    10291028            if ret==True: 
    10301029                return HttpResponse("true:Native file has a standard related locked file.") 
     
    10521051        add_file_form = forms.AddFileForm(request.POST, request.FILES) 
    10531052        if add_file_form.is_valid(): 
    1054             for fkey, f in request.FILES.iteritems(): 
    1055                 obj.add_file(request.FILES[fkey]) 
     1053            added_file="" 
     1054            for key, f_id in request.GET.iteritems(): 
     1055                obj.add_file(request.FILES[key]) 
    10561056            return HttpResponse(".") 
     1057        else: 
     1058            return HttpResponse("failed") 
    10571059 
    10581060@handle_errors 
  • trunk/openPLM/templates/documents/files_add.html

    r1124 r1126  
    1616        </div> 
    1717    </noscript> 
    18     <form class="hidden" action="" target="hidden_frame">{% csrf_token %}</form> 
     18    <form class="hidden" action="" target="hidden_frame" method="post" enctype="multipart/form-data"></form> 
    1919    <div id="div_files"></div> 
    2020</div> 
    21 <script type="text/javascript" src="/media/js/file.js"> 
    22 </script> 
     21<script type="text/javascript" src="/media/js/file.js"></script> 
    2322<script type="text/javascript"> 
    2423    {% with file_formset.forms as forms %} 
     
    4645    trans[""]="{% trans '' %}";*/ 
    4746</script> 
     47<script type="text/javascript" src="/media/js/opera_upload.js"></script> 
    4848 
     49 
  • trunk/openPLM/templates/snippets/undo_form.html

    r1103 r1126  
    1616                    <input type="submit" class="{{"Button"|button}}" value="{% trans "Validate" %}" name="_validate"/> 
    1717                </noscript> 
    18                 <input type="button" class="{{"Button"|button}}" value="{% trans "Upload" %}" id="_up"/> 
    19                 <!--<input type="button" class="{{"Button"|button}}" value="{% trans "Delete" %}" id="_delete"/>--> 
     18                <input type="button" class="{{"Button"|button}} hidden" value="{% trans "Upload" %}" id="_up"/> 
     19                <input type="button" class="{{"Button"|button}} hidden" value="{% trans "Delete" %}" id="_delete" title="{%trans "Delete the files selected for upload" %}"/> 
    2020                {% else %} 
    2121                <input type="submit" class="{{"Button"|button}}" value="{% trans "Validate" %}" name="_validate"/> 
Note: See TracChangeset for help on using the changeset viewer.