Подтвердить что ты не робот

Обнаруживать, когда загружается iframe

Я использую <iframe> (я знаю, я знаю,...) в своем приложении (одностраничное приложение с ExtJS 4.2) для загрузки файлов, потому что они содержат много данных и могут занять некоторое время сгенерируйте файл Excel (мы говорим что угодно от 20 секунд до 20 минут в зависимости от параметров).

Текущее состояние вещей: когда пользователь нажимает кнопку загрузки, он "перенаправляется" на Javascript (window.location.href = xxx) на страницу, выполняющую экспорт, но так как это делается в PHP, и никакие заголовки не отправляются, браузер постоянно загружает страницу, пока файл не будет загружен. Но он не очень удобен для пользователя, потому что ничто не показывает ему, все ли он загружается, выполняется (за исключением загрузки файла) или проваливается (что приводит к тому, что страница фактически перенаправляется, что может заставить его потерять работу, которую он выполнял).

Итак, я создал небольшое немодальное окно, закрепленное в нижнем правом углу, которое содержит iframe, а также небольшое сообщение, чтобы успокоить пользователя. Мне нужно, чтобы иметь возможность обнаруживать, когда он загружен, и иметь возможность различать два случая:

  • Нет данных: OK = > Закрыть окно
  • Текстовые данные: Сообщение об ошибке = > Отобразить сообщение пользователю + Закрыть окно

Но я пробовал все 4 события (W3Schools doc), и ни один из них не был когда-либо. Я мог бы хотя бы понять, что если это не вернет HTML-данные, возможно, он не сможет запустить событие, но даже если я заставляю ошибку возвращать текстовые данные, он не запускается.

Если кто-нибудь знает о решении для этого или альтернативной системе, которая может поместиться здесь, я все уши! Спасибо!

EDIT: Добавлен код iframe. Идея состоит в том, чтобы получить лучший способ ее закрыть, чем setTimeout.

var url = 'http://mywebsite.com/my_export_route';

var ifr = $('<iframe class="dl-frame" src="'+url+'" width="0" height="0" frameborder="0"></iframe>');
ifr.appendTo($('body'));

setTimeout(function() {
    $('.dl-frame').remove();
}, 3000);
4b9b3361

Ответ 1

Интересно, потребуются ли некоторые существенные изменения как в интерфейсе, так и в backend-коде, но рассмотрели ли вы использование AJAX? Рабочий процесс будет примерно таким: пользователь отправляет запрос AJAX для запуска генерации файлов, и интерфейс постоянно проверяет его статус с сервера, когда это будет сделано - покажет ссылку для загрузки пользователю. Я считаю, что рабочий процесс будет более простым.

Хорошо, вы также можете попробовать этот трюк. В родительском окне создайте функцию обратного вызова для полной загрузки iframe myOnLoadCallback, а затем вызовите ее из iframe с помощью parent.myOnLoadCallback(). Но вам все равно придется использовать setTimeout для обработки ошибок сервера/тайм-аутов подключения.

И последнее: как вы пытались поймать события iframe? Возможно, это что-то связанное с браузером. Вы пытались настроить обратные вызовы событий в атрибутах HTML напрямую? Как

<iframe onload="done()" onerror="fail()"></iframe>

Это плохая практика, я знаю, но иногда работу нужно выполнять быстро, а?

UPDATE Ну, боюсь, вам придется провести долгий и болезненный день с отладчиком JS. load событие должно работать. У меня все еще есть предложения:

1) Попробуйте установить прослушиватель событий перед установкой элемента src. Возможно, событие onload срабатывает так быстро, что оно проскальзывает между создающим элементом и настройкой обратного вызова события

2) В то же время попробуйте проверить, хорошо ли ваш код сервера работает с iframe. Я сделал простой тест, который пытается загрузить PDF из Dropbox, попробуйте заменить мой URL с помощью вашего поддерживаемого маршрута.

<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<iframe id="book"></iframe>
<button id="go">Request downloads!</button>

<script>
    var bookUrl = 'https://www.dropbox.com/s/j4o7tw09lwncqa6/thinkpython.pdf';

    $('#book').on('load', function(){
        console.log('WOOT!', arguments);
    });

    $('#go').on('click', function(){
        $('#book').attr('src', bookUrl);
    });
</script>

ОБНОВЛЕНИЕ 2

3) Также просмотрите вкладку "Сеть" отладчика вашего браузера, что произойдет, когда вы установите src в iframe, он должен отображать запрос и ответ сервера с заголовками.

Ответ 2

Я пробовал с jQuery, и он работал отлично, как вы можете видеть в этом сообщении.

Я сделал рабочий пример здесь.

В основном это:

<iframe src="http://www.example.com" id="myFrame"></iframe>

И код:

function test() {
    alert('iframe loaded');
}

$('#myFrame').load(test);

Протестировано в IE11.

Ответ 3

Возможно, вы должны использовать

$($('.dl-frame')[0].contentWindow.document).ready(function () {...})

Ответ 4

Вы можете использовать следующий script. Это происходит из моего проекта.

$("#reportContent").html("<iframe id='reportFrame' sandbox='allow-same-origin allow-scripts' width='100%' height='300' scrolling='yes' onload='onReportFrameLoad();'\></iframe>");

Ответ 5

Думаю, я дам более хакерскую альтернативу более правильным способам сделать это, чтобы другие опубликовали. Если у вас есть контроль над загрузкой PHP script, возможно, вы просто сможете просто вывести javascript при завершении загрузки. Или, возможно, перенаправить на страницу html, которая запускает javascript. Запуск javascript может затем попытаться вызвать что-то в родительском фрейме. Что будет работать, зависит от того, работает ли ваше приложение в том же домене или нет.

 

Тот же домен

Такой же фрейм домена может использовать объекты фрейма javascript для ссылки друг на друга. так что это может быть что-то вроде, в вашем приложении с одной страницей вы можете иметь что-то вроде

window.downloadHasFinished=function(str){ //Global pollution. More unique name?
    //code to be run when download has finished
}

А для загрузки php script вы можете получить этот html + javascript при его выполнении

<script>
if(parent && parent.downloadHasFinished)
    parent.downloadHasFinished("if you want to pass a data. maybe export url?")
</script>

 

Различные домены

Для разных доменов мы можем использовать postMessage. Таким образом, в вашем одностраничном приложении это будет что-то вроде

$(window).on("message",function(e){
    var e=e.originalEvent
    if(e.origin=="http://downloadphp.anotherdomain.com"){ //for security
      var message=e.data //data passed if any
      //code to be run when download has finished
    }
});

и в вашем php download script вы можете получить этот html + javascript

<script>
parent.postMessage("if you want to pass data",
   "http://downloadphp.anotherdomain.com");
</script>

 

Заключение

Честно говоря, если другие ответы работают, вы должны, вероятно, использовать их. Я просто подумал, что это интересная альтернатива, поэтому я опубликовал ее.

Ответ 6

Попробуйте это (рисунок)

    $(function () {
        var session = function (url, filename) {
           // `url` : URL of resource
           // `filename` : `filename` for resource (optional)
            var iframe = $("<iframe>", {
                "class": "dl-frame",
                    "width": "150px",
                    "height": "150px",
                    "target": "_top"
            })
            // `iframe` `load` `event`
            .one("load", function (e) {
                $(e.target)
                    .contents()
                    .find("html")
                    .html("<html><body><div>" 
                          + $(e.target)[0].nodeName 
                          + " loaded" + "</div><br /></body></html>");
                alert($(e.target)[0].nodeName 
                        + " loaded" + "\nClick link to download file");
                return false
            });

            var _session = $.when($(iframe).appendTo("body"));
            _session.then(function (data) {
                var link = $("<a>", {
                        "id": "file",
                        "target": "_top",
                        "tabindex": "1",
                        "href": url,
                        "download": url,
                        "html": "Click to start {filename} download"
                });
                $(data)
                    .contents()
                    .find("body")
                    .append($(link))
                    .addBack()
                    .find("#file")
                    .attr("download", function (_, o) {
                      return (filename || o)
                    })
                    .html(function (_, o) {
                      return o.replace(/{filename}/, 
                      (filename || $(this).attr("download")))
                })

            });
            _session.always(function (data) {
                $(data)
                    .contents()
                    .find("a#file")
                    .focus()
                    // start 6 second `download` `session`,
                    // on `link` `click`
                    .one("click", function (e) {
                    var timer = 6;
                    var t = setInterval(function () {
                        $(data)
                            .contents()
                            .find("div")
                             // `session` notifications
                            .html("Download session started at " 
                                  + new Date() + "\n" + --timer);
                    }, 1000);
                    setTimeout(function () {
                        clearInterval(t);
                        $(data).replaceWith("<span class=session-notification>"    
                          + "Download session complete at\n" 
                          + new Date() 
                          + "</span><br class=session-notification />"
                          + "<a class=session-restart href=#>"
                          + "Restart download session</a>");
                        if ($("body *").is(".session-restart")) {
                            // start new `session`,
                            // on `.session-restart` `click`
                            $(".session-restart")
                            .on("click", function () {
                                $(".session-restart, .session-notification")
                                .remove() 
                                // restart `session` (optional),
                                // or, other `session` `complete` `callback` 
                                && session(url, filename ? filename : null)
                            })
                        };
                    }, 6000);
                });
            });
        };
        // usage
        session("http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf", "ECMA_JS.pdf")
    });

jsfiddle http://jsfiddle.net/guest271314/frc82/

Ответ 7

Вы можете использовать эту библиотеку. Фрагмент кода для вас будет выглядеть примерно так:

window.onload = function () {
  rajax_obj = new Rajax('',
    {
        action : 'http://mywebsite.com/my_export_route', 
        onComplete : function(response) {
                //This will only called if you have returned any response 
               // instead of file from your export script
               // In your case 2
               // Text data : Error message => Display message to user
        }
    });
}

Затем вы можете вызвать rajax_obj.post() на ссылку загрузки.

<a href="javascript:rajax_obj.post()">Download</a>

NB: вы должны добавить некоторый заголовок в свой PHP script, чтобы принудительно загрузить файл

header('Content-Disposition: attachment; filename="'.$file.'"');
header('Content-Transfer-Encoding: binary');

Ответ 8

В отношении вашего комментария о том, чтобы лучше всего закрыть его вместо setTimeout. Вы можете использовать опцию jQuery fadeOut или любой из переходов, а в "полном" обратном вызове удалить элемент. Ниже приведен пример, который вы можете сбросить прямо в скрипку, и нужно только ссылаться на jQuery.

Я также обернул внутри прослушивателя событие "load", чтобы не исчезать до тех пор, пока iFrame не будет загружен, как задавал вопрос.

// plugin your URL here 
var url = 'http://jquery.com';

// create the iFrame, set attrs, and append to body 
var ifr = $("<iframe>") 
    .attr({
        "src": url, 
        "width": 300,
        "height": 100,
        "frameborder": 0 
    }) 
    .addClass("dl-frame")
    .appendTo($('body'))
;

// log to show its part of DOM 
console.log($(".dl-frame").length + " items found"); 

// create listener for load 
ifr.one('load', function() {
    console.log('iframe is loaded'); 

    // call $ fadeOut to fade the iframe 
    ifr.fadeOut(3000, function() {
        // remove iframe when fadeout is complete
        ifr.remove();  
        // log after, should no longer exist in DOM
        console.log($(".dl-frame").length + " items found");
    });  
}); 

Ответ 9

Попробуйте следующее:
Примечание. Вы должны находиться в том же домене.

var url = 'http://mywebsite.com/my_export_route',
    iFrameElem = $('body')
        .append('<iframe class="dl-frame" src="' + url + '" width="0" height="0" frameborder="0"></iframe>')
        .find('.dl-frame').get(0),
    iDoc = iFrameElem.contentDocument || iFrameElem.contentWindow.document;

$(iDoc).ready(function (event) {
    console.log('iframe ready!');
    // do stuff here
});

Ответ 10

Если вы делаете загрузку файла из iframe, событие загрузки не запускается:) Я делал это неделю назад. Единственное решение этой проблемы - вызвать прокси-сервер загрузки script с тегом, а затем вернуть этот тег через файл cookie, после чего файл будет загружен. мин, в то время как вам нужно иметь setInterval на странице, которая будет следить за этим конкретным файлом cookie.

//Jst to clearyfy

var token = new Date().getTime(); // ticks
$('<iframe>',{src:"yourproxy?file=somefile.file&token="+token}).appendTo('body');

var timers = [];
timers[timers.length+1] = setInterval(function(){
var _index = timers.length+1;
 var cookie = $.cooke(token);
 if(typeof cookie != "undefined"){
  // File has been downloaded
   $.removeCookie(token);
   clearInterval(_index);
 }
},400);

в вашем прокси-сервере script добавьте файл cookie с именем, установленным в строку, посланную в буфер, с параметром url-маркера.

Ответ 11

Если вы управляете script на сервере, который генерирует excel или все, что вы отправляете в iframe, почему бы вам не поместить флаг UID и сохранить его в сеансе со значением 0, поэтому... когда iframe создается и сервер script вызывается только как установленный флаг UID в 1, а когда script завершен (iframe будет загружен), просто поставьте его в 2.

Затем вам нужен только таймер и периодический вызов AJAX на сервер, чтобы проверить флаг UID... если он установлен в 0, процесс не запускается, если он 1 создает файл и, наконец, если он 2 процесс завершен.

Как вы думаете? Если вам нужна дополнительная информация об этом подходе, просто спросите.

Ответ 12

То, что вы говорите, может быть сделано для изображений и других форматов мультимедиа, используя $(iframe).load(function() {...});

Для файлов PDF или других мультимедийных материалов вы можете использовать следующую библиотеку: http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/

Примечание. Вам потребуется пользовательский интерфейс JQuery

Ответ 13

Есть два решения, о которых я могу думать. Либо у вас есть PHP-пост, он продвигается к таблице MySQL, где из frontend будет извлекать информацию из-за использования вызовов AJAX, чтобы проверить ход генерации. Использование somekind уникального ключа, который генерируется при доступе к странице, будет идеальным для нескольких людей, которые генерируют файлы excel одновременно.

Другим решением было бы использовать nodejs, а затем в PHP отправить ход файла excel с помощью cURL или сокета в службу nodejs. Затем, получая обновления от PHP в nodejs, вы просто пишете прогресс файла excel для правильного сокета. Тем не менее, это остановит поддержку браузера. Если вы не воспользуетесь им, используя внешние библиотеки, чтобы обеспечить поддержку веб-обозревателя для почти всех браузеров и версий.

Надеюсь, что этот ответ помог. У меня была такая же проблема в прошлом году. Закончилось выполнение опроса AJAX с ходом работы с PHP-пост.