2013-02-11 2 views
0

Я борюсь с управлением динамически построенными обработчиками событий в javascript.Избегайте использования eval() для динамического создания обработчиков событий

В нескольких местах я создаю формы или элементы управления, в которых необходимо обрабатывать определенные события (в основном, мыши, выходы, щелчки).

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

Как таковой, я использовал «eval()» для создания событий и включения соответствующих данных, и это сработало несколько хорошо.

Проблема в том, что я продолжаю видеть/слышать такие вещи, как «Вы никогда не должны использовать eval()!» а также несколько все более уродливых реализаций, где мой динамически созданный обработчик событий должен динамически создавать другие обработчики событий, а вложенные оценки довольно тупые (мягко говоря).

Итак, я здесь, спрашиваю, может ли кто-нибудь показать мне лучший способ (только для родного javascript, я не внедряю сторонние библиотеки!).

Вот грубый пример, чтобы проиллюстрировать то, что я говорю о:

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked) 
{ 
    var inp = document.createElement('input'); 
    inp.id = controlName; 
    inp.type = type; 
    inp.style.cssText = dormantStyle; 
    eval("inp.onfocus = function() { this.style.cssText = '" + activeStyle + "'; }"); 
    eval("inp.onblur = function() { this.style.cssText = '" + dormantStyle + "'; }"); 
    eval("inp.onclick = function() { " + whenClicked + "; }"); 
    return inp; 
} 

Эта функция, очевидно, позволит мне легко создать множество различных тегов INPUT, и указать ряд уникальных атрибутов и действий, событий, только с один вызов функции для каждого. Опять же, это чрезвычайно упрощенный пример, просто чтобы продемонстрировать, о чем я говорю, в некоторых случаях с проектом, в котором я сейчас участвую, события могут включать в себя десятки строк, они могут даже делать динамические аякс-вызовы на основе пройденного параметром или другими динамически генерируемыми данными. В более крайних случаях я создаю таблицы, чьи отдельные строки/столбцы/ячейки могут нуждаться в обработке событий на основе динамически созданного содержимого обработчика или обработчика обработчика.

Первоначально я построил такие функции, как выше, как так:

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked) 
{ 
    var inp = document.createElement('input'); 
    inp.id = controlName; 
    inp.type = type; 
    inp.style.cssText = dormantStyle; 
    inp.onfocus = function() { this.style.cssText = activeStyle; }; 
    inp.onblur = function() { this.style.cssText = dormantStyle; }; 
    eval("inp.onclick = function() { " + whenClicked + "; }"); 
    return inp; 
} 

... но я обнаружил, что все последнее присвоенное значение было для «activeStyle» и «dormantStyle» стало значением, используемым всеми обработчиками, созданными таким образом (вместо того, чтобы каждый из них сохранил свой собственный уникальный набор стилей, например). Именно это привело меня к использованию eval() для «блокировки» значений переменных при создании функции, но это привело меня к кошмарам, таким как:

(Это образец одного динамически построенный событие обработчик, который я в настоящее время работаю, и которая использует вложенную Eval() функция):

eval("input.onkeyup = function() { " + 
      "InputParse(this,'ucwords'); " + 
      "var tId = '" + myName + This.nodeName + "SearchTable" + uidNo + "'; " + 
      "var table = document.getElementById(tId); " + 
      "if (this.value.length>2) { " + 
       "var val = (this.value.indexOf(',') >=0) ? this.value.substr(0,this.value.indexOf(',')) : this.value; " + 
       "var search = Global.LoadData('?fn=citySearch&limit=3&value=' + encodeURI(val)); " + 
       "if (table) { " + 
        "while (table.rows.length>0) { table.deleteRow(0); } " + 
        "table.style.display='block'; " + 
       "} else { " + 
        "table = document.createElement('table'); " + 
        "table.id = tId; " + 
        "ApplyStyleString('" + baseStyle + ";position=absolute;top=20px;left=0px;display=block;border=1px solid black;backgroundColor=rgba(224,224,224,0.90);zIndex=1000;',table); " + 
        "var div = document.getElementById('" + divName + "'); " + 
        "if (div) { div.appendChild(table); } " + 
       "} " + 
       "if (search.rowCount()>0) { " + 
        "for (var i=0; i<search.rowCount(); i++) { " + 
         "var tr = document.createElement('tr'); " + 
         "tr.id = 'SearchRow' + i + '" + uidNo + "'; " + 
         "tr.onmouseover = function() { ApplyStyleString('cursor=pointer;color=yellow;backgroundColor=rgba(40,40,40,0.90);',this); }; " + 
         "tr.onmouseout = function() { ApplyStyleString('cursor=default;color=black;backgroundColor=rgba(224,224,224,0.90);',this); }; " + 
         "eval(\"tr.onclick = function() { " + 
          "function set(id,value) { " + 
           "var o = document.getElementById(id); " + 
           "if (o && o.value) { o.value = value; } else { alert('Could not find ' + id); } " + 
          "} " + 
          "set('" + myName + This.nodeName + "CityId" + uidNo + "','\" + search.id(i)+ \"'); " + 
          "set('" + myName + This.nodeName + "ProvId" + uidNo + "','\" + search.provId(i)+ \"'); " + 
          "set('" + myName + This.nodeName + "CountryId" + uidNo + "','\" + search.countryId(i) + \"'); " + 
          "set('" + input.id + "','\" + search.name(i)+ \"'); " + 
          "}\"); " + 
         "var td = document.createElement('td'); " + 
         "var re = new RegExp('('+val+')', 'gi'); " + 
         "td.innerHTML = search.name(i).replace(re,'<span style=\"font-weight:bold;\">$1</span>') + ', ' + search.provinceName(i) + ', ' + search.countryName(i); " + 
         "tr.appendChild(td); " + 
         "table.appendChild(tr); " + 
        "} " + 
       "} else { " + 
        "var tr = document.createElement('tr'); " + 
        "var td = document.createElement('td'); " + 
        "td.innerHTML = 'No matches found...';" + 
        "tr.appendChild(td); " + 
        "table.appendChild(tr); " + 
       "} " + 
      "} else { " + 
       "if (table) table.style.display = 'none'; " + 
      "} " + 
     "} "); 

в настоящее время у меня возникают проблемы с получением вложенной Eval(), чтобы связать».onclick «событие в таблицу-строку, и, как вы можете видеть, выяснение кода становится довольно волосатым (отладка тоже по всем известным причинам) ... Итак, я был бы очень признателен, если бы кто-нибудь мог указать мне направление того, чтобы быть в состоянии выполнить эти же цели, избегая при этом ужасного использования заявление «eval()»!

Спасибо!

+0

Это кричит что-то вроде jQuery, но поскольку вы не хотите использовать сторонние библиотеки: ваше начальное решение (только с одним «eval») имеет гораздо больший смысл. Это 'activeStyle' и' dormantStyle' 'stick, вероятно, связано с контекстом, из которого вы вызываете 'createInput' (возможно, в' for' loop?), А не для самой функции. – robertklep

ответ

1

И это, среди многих других причин, почему вы должны не использовать eval. (Что делать, если те значения, которые вы «выпекаете» в кавычках, содержат кавычки?). В общем, попробуйте выяснить, почему правильный способ не работает, а не ошибаться в подаче. :)

Также, это не рекомендуется назначать on* атрибутам; они не очень хорошо масштабируются. Новая жара должна использовать element.addEventListener, что позволяет использовать несколько обработчиков для одного и того же события. (Для старшего IE, вам нужно attachEvent. Этот вид IE ерунду является основной причиной мы начали использовать библиотеки, как JQuery в первую очередь.)


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

переменные JavaScript являются функцией в области видимости, а не блок-Scoped, поэтому, когда вы делаете это:

var callbacks = []; 
for (var i = 0; i < 10; i++) { 
    callbacks.push(function() { alert(i) }); 
} 

for (var index in callbacks) { 
    callbacks[index](); 
} 

... вы получите 9 десять раз. Каждый цикл цикла создает функцию, которая закрывает ту же переменнуюi, а затем на следующей итерации изменяется значение i.

Что вы хотите - это заводская функция: встроенная или независимая.

for (var i = 0; i < 10; i++) { 
    (function(i) { 
     callbacks.push(function() { alert(i) }); 
    })(i); 
} 

Это создает отдельную функцию и выполняет ее немедленно. iвнутри функция является другой переменной каждый раз (поскольку она ограничена функцией), поэтому она эффективно фиксирует значение внешнегоi и игнорирует любые дальнейшие изменения.

Вы можете разбить это в явном виде:

function make_function(i) { 
    return function() { alert(i) }; 
} 

// ... 

for (var i = 0; i < 10; i++) { 
    callbacks.push(make_function(i)); 
} 

Точно то же самое, но с функцией определяется независимо, а не инлайн.

У этого come up before, но это немного сложно обнаружить, что вызывает удивление.


Даже ваш код «правильного пути» по-прежнему использует строки для содержимого функций или стилей. Я бы передал поведение этого клика как функцию, и я бы использовал классы вместо встраивания кусков CSS в свой JavaScript. (Я сомневаюсь, что я добавлю идентификатор для каждого входа.)

Так что я бы написать что-то вроде этого:

function create_input(id, type, active_class, onclick) { 
    var inp = document.createElement('input'); 
    inp.id = id; 
    inp.type = type; 
    inp.addEventListener('focus', function() { 
     this.className = active_class; 
    }); 
    inp.addEventListener('blur', function() { 
     this.className = ''; 
    }); 
    inp.addEventListener('click', onclick); 

    return inp; 
} 

// Called as: 
var textbox = create_input('unique-id', 'text', 'focused', function() { alert("hi!") }); 

Это имеет некоторые проблемы до сих пор: он не работает в старых IE, и это приведет к удалению всех имен классов вы пытаетесь добавить позже. Именно поэтому JQuery популярен:

function create_input(id, type, active_class, onclick) { 
    var inp = $('<input>', { id: id, type: type }); 
    inp.on('focus', function() { 
     $(this).addClass(active_class); 
    }); 
    inp.on('blur', function() { 
     $(this).removeClass(active_class); 
    }); 

    inp.on('click', onclick); 

    return inp; 
} 

Конечно, даже большинство из этого ненужно, вы можете просто использовать селектор :focus CSS, а не возиться с focus и blur событий на всех!

+0

Как я уже сказал, это был просто упрощенный пример. Последний код-образец гораздо больше похож на то, с чем я имею дело (хотя это всего лишь часть всей функции). –

+0

Я предпочитаю определять свои стили ГДЕ ИСПОЛЬЗОВАТЬ их, переходя к отдельному документу, который в конечном итоге может содержать тысячи записей. Когда элемент в моем приложении не выглядит корректным, я просто перехожу к коду, в котором этот элемент создан и настраивается. С CSS-файлом мне нужно будет пройти код, найти ссылку на класс, перейти к файлу css, а затем просмотреть класс, а затем внести изменения. Этот способ дает тот же результат, но сокращает количество шагов. Плюс у меня нет никаких проблем с появлением имен для тысяч классов (коллизий, опечаток и т. Д.). –

+0

Если вы проверяли свой документ в браузере с помощью отладчика, вы могли бы легко увидеть, какие классы у него есть, поэтому найти подходящий CSS легко. но нет простого способа выяснить, что изначально создало JS-элемент, если вы уже не помните. – Eevee

0

Вам не нужно eval, чтобы «зафиксировать» значение.

Непонятно из опубликованного кода, почему вы видите изменения значений после возврата CreateInput.Если CreateInput реализовал цикл, тогда я ожидал бы, что последние значения будут назначены activeStyle и dormantStyle для использования. Но даже вызов CreateInput из цикла не приведет к неправильному поведению, которое вы описываете, вопреки комментатору.

В любом случае, решение такого рода устаревших данных - использовать закрытие. Ложные переменные JavaScript привязаны к области вызова функции, независимо от того, объявлены они внутри функции или в цикле. Таким образом, вы добавляете вызов функции для создания новых переменных.

function CreateInput(controlName,type,activeStyle,dormantStyle,whenClicked) 
{ 
    while (something) { 
     activeStyle += "blah"; // modify local vars 
     function (activeStyle, dormantStyle) { // make copies of local vars 
      var inp = document.createElement('input'); 
      inp.id = controlName; 
      inp.type = type; 
      inp.style.cssText = dormantStyle; 
      inp.onfocus = function() { this.style.cssText = activeStyle; }; 
      inp.onblur = function() { this.style.cssText = dormantStyle; }; 
      inp.onclick = whenClicked; 
     }(activeStyle, dormantStyle); // specify values for copies 
    } 
    return inp; 
} 
+0

Хм, ну, я вижу новый код, который вы вложили, но вы также сохранили инструкции eval(), это правильно? и если да, то в чем преимущество добавления дополнительного кода? –

+0

@BrettLeuszler Извините, я хотел стереть их. исправлено. – Potatoswatter

Смежные вопросы