Есть ли обходной путь для IE 6/7 «Неопределенная ошибка» при доступе к offsetParent - PullRequest
7 голосов
/ 16 декабря 2008

Я использую перетаскиваемые и сбрасываемые библиотеки jQuery UI в простом приложении ASP.NET для проверки концепции. Эта страница использует ASP.NET AJAX UpdatePanel для частичного обновления страницы. Страница позволяет пользователю поместить элемент в корзину для мусора, которая вызовет обратную передачу, которая удалит запись из базы данных, а затем повторно свяжет список (и другие элементы управления), из которого этот элемент был наркотиком. Все эти элементы (перетаскиваемые элементы и элемент корзины) находятся внутри панели обновления ASP.NET.

Вот сценарий инициализации перетаскивания:

    function initDragging()
    {
        $(".person").draggable({helper:'clone'});
        $("#trashcan").droppable({
            accept: '.person',
            tolerance: 'pointer',
            hoverClass: 'trashcan-hover',
            activeClass: 'trashcan-active',
            drop: onTrashCanned
        });
    }

    $(document).ready(function(){
        initDragging();

        var prm = Sys.WebForms.PageRequestManager.getInstance();
        prm.add_endRequest(function()
        {
            initDragging();
        });
    });

    function onTrashCanned(e,ui)
    {
        var id = $('input[id$=hidID]', ui.draggable).val();
        if (id != undefined)
        {
            $('#hidTrashcanID').val(id);
            __doPostBack('btnTrashcan','');
        }

    }

Когда страница отправляется обратно, частично обновляя содержимое UpdatePanel, я перепривязываю draggables и droppables. Когда я затем перетаскиваю курсор, я получаю «htmlfile: Unspecified error». исключение. Я могу решить эту проблему в библиотеке jQuery, заменив elem.offsetParent вызовами этой функции, которые я написал:

function IESafeOffsetParent(elem)
{
    try
    {
        return elem.offsetParent;
    }
    catch(e)
    {        
        return document.body;
    }
}

Я также должен избегать вызовов elem.getBoundingClientRect (), поскольку он выдает ту же ошибку. Для тех, кто заинтересован, мне нужно было только внести эти изменения в функцию jQuery.fn.offset в плагине Dimensions .

Мои вопросы:

  • Несмотря на то, что это работает, существуют ли более эффективные способы (более чистые; повышенная производительность; без необходимости изменять библиотеку jQuery) для решения этой проблемы?
  • Если нет, как лучше синхронизировать изменения, когда я буду обновлять библиотеки jQuery в будущем? Например, можно ли расширить библиотеку где-то, кроме встроенных файлов, которые я загружаю с веб-сайта jQuery.

Обновление:

@ some Это не общедоступно, но я посмотрю, позволит ли SO разместить в этом ответе соответствующий код. Просто создайте веб-приложение ASP.NET (назовите его DragAndDrop ) и создайте эти файлы. Не забудьте установить Complex.aspx в качестве стартовой страницы. Вам также нужно будет загрузить jQuery UI drag & drop в , а также jQuery core

Complex.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Complex.aspx.cs" Inherits="DragAndDrop.Complex" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <script src="jquery-1.2.6.min.js" type="text/javascript"></script>
    <script src="jquery-ui-personalized-1.5.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        function initDragging()
        {
            $(".person").draggable({helper:'clone'});
            $("#trashcan").droppable({
                accept: '.person',
                tolerance: 'pointer',
                hoverClass: 'trashcan-hover',
                activeClass: 'trashcan-active',
                drop: onTrashCanned
            });
        }

        $(document).ready(function(){
            initDragging();

            var prm = Sys.WebForms.PageRequestManager.getInstance();
            prm.add_endRequest(function()
            {
                initDragging();
            });
        });

        function onTrashCanned(e,ui)
        {
            var id = $('input[id$=hidID]', ui.draggable).val();
            if (id != undefined)
            {
                $('#hidTrashcanID').val(id);
                __doPostBack('btnTrashcan','');
            }

        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <asp:UpdatePanel ID="updContent" runat="server" UpdateMode="Always">
    <ContentTemplate>
        <asp:LinkButton ID="btnTrashcan" Text="trashcan" runat="server" CommandName="trashcan" 
            onclick="btnTrashcan_Click" style="display:none;"></asp:LinkButton>
        <input type="hidden" id="hidTrashcanID" runat="server" />
        <asp:Button ID="Button1" runat="server" Text="Save" onclick="Button1_Click" />
        <table>
            <tr>
                <td style="width: 300px;">
                    <asp:DataList ID="lstAllPeople" runat="server" DataSourceID="odsAllPeople"
                        DataKeyField="ID">
                        <ItemTemplate>
                            <div class="person">
                                <asp:HiddenField ID="hidID" runat="server" Value='<%# Eval("ID") %>' />
                                Name:
                                <asp:Label ID="lblName" runat="server" Text='<%# Eval("Name") %>' />
                                <br />
                                <br />
                            </div>
                        </ItemTemplate>
                    </asp:DataList>
                    <asp:ObjectDataSource ID="odsAllPeople" runat="server" SelectMethod="SelectAllPeople" 
                        TypeName="DragAndDrop.Complex+DataAccess" 
                        onselecting="odsAllPeople_Selecting">
                        <SelectParameters>
                            <asp:Parameter Name="filter" Type="Object" />
                        </SelectParameters>
                    </asp:ObjectDataSource>
                </td>
                <td style="width: 300px;vertical-align:top;">
                    <div id="trashcan">
                        drop here to delete
                    </div>
                    <asp:DataList ID="lstPeopleToDelete" runat="server" 
                        DataSourceID="odsPeopleToDelete">
                        <ItemTemplate>
                            ID:
                            <asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' />
                            <br />
                            Name:
                            <asp:Label ID="NameLabel" runat="server" Text='<%# Eval("Name") %>' />
                            <br />
                            <br />
                        </ItemTemplate>
                    </asp:DataList>
                    <asp:ObjectDataSource ID="odsPeopleToDelete" runat="server" 
                        onselecting="odsPeopleToDelete_Selecting" SelectMethod="GetDeleteList" 
                        TypeName="DragAndDrop.Complex+DataAccess">
                        <SelectParameters>
                            <asp:Parameter Name="list" Type="Object" />
                        </SelectParameters>
                    </asp:ObjectDataSource>
                </td>
            </tr>
        </table>    
    </ContentTemplate>
    </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>

Complex.aspx.cs

namespace DragAndDrop
{
    public partial class Complex : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected List<int> DeleteList
        {
            get
            {
                if (ViewState["dl"] == null)
                {
                    List<int> dl = new List<int>();
                    ViewState["dl"] = dl;

                    return dl;
                }
                else
                {
                    return (List<int>)ViewState["dl"];
                }
            }
        }

        public class DataAccess
        {
            public IEnumerable<Person> SelectAllPeople(IEnumerable<int> filter)
            {
                return Database.SelectAll().Where(p => !filter.Contains(p.ID));
            }

            public IEnumerable<Person> GetDeleteList(IEnumerable<int> list)
            {
                return Database.SelectAll().Where(p => list.Contains(p.ID));
            }
        }

        protected void odsAllPeople_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
        {
            e.InputParameters["filter"] = this.DeleteList;
        }

        protected void odsPeopleToDelete_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
        {
            e.InputParameters["list"] = this.DeleteList;
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            foreach (int id in DeleteList)
            {
                Database.DeletePerson(id);
            }

            DeleteList.Clear();
            lstAllPeople.DataBind();
            lstPeopleToDelete.DataBind();
        }

        protected void btnTrashcan_Click(object sender, EventArgs e)
        {
            int id = int.Parse(hidTrashcanID.Value);
            DeleteList.Add(id);
            lstAllPeople.DataBind();
            lstPeopleToDelete.DataBind();
        }
    }
}

Database.cs

namespace DragAndDrop
{
    public static class Database
    {
        private static Dictionary<int, Person> _people = new Dictionary<int,Person>();
        static Database()
        {
            Person[] people = new Person[]
            {
                new Person("Chad")
                , new Person("Carrie")
                , new Person("Richard")
                , new Person("Ron")
            };
            foreach (Person p in people)
            {
                _people.Add(p.ID, p);
            }
        }

        public static IEnumerable<Person> SelectAll()
        {
            return _people.Values;
        }

        public static void DeletePerson(int id)
        {
            if (_people.ContainsKey(id))
            {
                _people.Remove(id);
            }
        }

        public static Person CreatePerson(string name)
        {
            Person p = new Person(name);
            _people.Add(p.ID, p);

            return p;
        }
    }

    public class Person
    {
        private static int _curID = 1;
        public int ID { get; set; }
        public string Name { get; set; }
        public Person()
        {
            ID = _curID++;
        }

        public Person(string name)
            : this()
        {
            Name = name;
        }
    }
}

Ответы [ 6 ]

6 голосов
/ 05 февраля 2009

@ arilanto - я включаю этот скрипт после моих скриптов jquery. С точки зрения производительности, это не лучшее решение, но это быстро и легко обойти.

function IESafeOffsetParent(elem)
{
    try
    {
       return elem.offsetParent;
    }
    catch(e)
    {        
      return document.body;
    }
}

// The Offset Method
// Originally By Brandon Aaron, part of the Dimension Plugin
// http://jquery.com/plugins/project/dimensions
jQuery.fn.offset = function() {
/// <summary>
///     Gets the current offset of the first matched element relative to the viewport.
/// </summary>
/// <returns type="Object">An object with two Integer properties, 'top' and 'left'.</returns>

var left = 0, top = 0, elem = this[0], results;

if ( elem ) with ( jQuery.browser ) {
    var parent     = elem.parentNode,
        offsetChild  = elem,
        offsetParent = IESafeOffsetParent(elem),
        doc       = elem.ownerDocument,
        safari2   = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
        css       = jQuery.curCSS,
        fixed       = css(elem, "position") == "fixed";

    // Use getBoundingClientRect if available
    if (false && elem.getBoundingClientRect) {
        var box = elem.getBoundingClientRect();

        // Add the document scroll offsets
        add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
            box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));

        // IE adds the HTML element's border, by default it is medium which is 2px
        // IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
        // IE 7 standards mode, the border is always 2px
        // This border/offset is typically represented by the clientLeft and clientTop properties
        // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
        // Therefore this method will be off by 2px in IE while in quirksmode
        add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );

    // Otherwise loop through the offsetParents and parentNodes
    } else {

        // Initial element offsets
        add( elem.offsetLeft, elem.offsetTop );

        // Get parent offsets
        while ( offsetParent ) {
            // Add offsetParent offsets
            add( offsetParent.offsetLeft, offsetParent.offsetTop );

            // Mozilla and Safari > 2 does not include the border on offset parents
            // However Mozilla adds the border for table or table cells
            if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
                border( offsetParent );

            // Add the document scroll offsets if position is fixed on any offsetParent
            if ( !fixed && css(offsetParent, "position") == "fixed" )
                fixed = true;

            // Set offsetChild to previous offsetParent unless it is the body element
            offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
            // Get next offsetParent
            offsetParent = offsetParent.offsetParent;
        }

        // Get parent scroll offsets
        while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
            // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
            if ( !/^inline|table.*$/i.test(css(parent, "display")) )
                // Subtract parent scroll offsets
                add( -parent.scrollLeft, -parent.scrollTop );

            // Mozilla does not add the border for a parent that has overflow != visible
            if ( mozilla && css(parent, "overflow") != "visible" )
                border( parent );

            // Get next parent
            parent = parent.parentNode;
        }

        // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
        // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
        if ( (safari2 && (fixed || css(offsetChild, "position") == "absolute")) ||
            (mozilla && css(offsetChild, "position") != "absolute") )
                add( -doc.body.offsetLeft, -doc.body.offsetTop );

        // Add the document scroll offsets if position is fixed
        if ( fixed )
            add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
                Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
    }

    // Return an object with top and left properties
    results = { top: top, left: left };
}

function border(elem) {
    /// <summary>
    ///     This method is internal.
    /// </summary>
    /// <private />
    add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
}

function add(l, t) {
    /// <summary>
    ///     This method is internal.
    /// </summary>
    /// <private />
    left += parseInt(l, 10) || 0;
    top += parseInt(t, 10) || 0;
}

return results;
};
2 голосов
/ 13 сентября 2010

Если вы хотите исправить сжатый / сжатый файл .js для jQuery версии 1.4.2, замените:

var d=b.getBoundingClientRect(),

с

var d = null; 
try { d = b.getBoundingClientRect(); }
catch(e) { d = { top : b.offsetTop, left : b.offsetLeft } ; }

(обратите внимание, что после закрывающей скобки теперь нет запятой)

1 голос
/ 07 мая 2012

Это не просто ошибка jQuery. Я столкнулся с этим, используя ExtJS 4.0.2a на IE8. Кажется, что IE почти всегда натыкается на element.getBoundingClientRect(), если элемент был заменен в DOM. Ваш взлом попробуйте / поймать - почти единственный способ обойти это. Я думаю, что реальным решением было бы в конечном итоге отказаться от поддержки IE <9. </p>

Восстановить исходный код ExtJS v4.0.2a (строки 11861-11869):

...
if(el != bd){
    hasAbsolute = fly(el).isStyle("position", "absolute");

    if (el.getBoundingClientRect) {
        b = el.getBoundingClientRect();
        scroll = fly(document).getScroll();
        ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
    } else {
...

С исправлением попытки / улова:

...
if(el != bd){
    hasAbsolute = fly(el).isStyle("position", "absolute");

    if (el.getBoundingClientRect) {
        try {
            b = el.getBoundingClientRect();
            scroll = fly(document).getScroll();
            ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
        } catch(e) {
            ret = [0,0];
        }
    } else {
...
1 голос
/ 14 сентября 2010

Моя версия:

  1. Добавить функцию:

    function getOffsetSum(elem) {
        var top = 0, left = 0
        while (elem) {
            top = top + parseInt(elem.offsetTop)
            left = left + parseInt(elem.offsetLeft)
            try {
                elem = elem.offsetParent
            }
            catch (e) {
                return { top: top, left: left }
            }
        }
        return { top: top, left: left }
    };
    
  2. замена

    var box  = this[0].getBoundingClientRect()
    

    с

    var box = getOffsetSum(this[0])
    

PS: jquery-1.3.2.

1 голос
/ 16 июня 2010

@ Raghu

Спасибо за этот бит кода! У меня была та же проблема в IE, и это исправило ее.

var box = null; 
try { 
    box = elem.getBoundingClientRect(); 
} catch(e) { 
    box = { 
        top : elem.offsetTop, 
        left : elem.offsetLeft 
    }; 
}
1 голос
/ 11 июня 2010

Я попробовал следующий обходной путь для getBoundingClientRect () неопределенной ошибки при перетаскивании n, и она отлично работает.

в jquery.1.4.2.js ( т.е. базовый файл jquery, в котором ошибка выдается точно )

заменить elem.getBoundingClientRect () вызов функции в js-файле

// строка, которая выбрасывает неопределенную ошибку

var box = elem.getBoundingClientRect(),

с этим ..

var box = null; try { box = elem.getBoundingClientRect(); } catch(e) { box = { top : elem.offsetTop, left : elem.offsetLeft } ; }

Это решает проблему, и перетаскивание n будет работать без проблем даже после отправки сообщения через панель обновления

Привет

Raghu

...