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

Как структурировать Javascript-программы в сложных веб-приложениях?

У меня есть проблема, о которой нелегко описать. Я пишу веб-приложение, которое сильно использует вызовы jQuery и AJAX. Теперь у меня нет большого опыта в архивировании Javascript, но я понимаю, что у моей программы нет хорошей структуры. Я думаю, что у меня слишком много идентификаторов, относящихся к одной и той же (по крайней мере, более или менее) вещи.

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

  • Обработчики событий используют элементы DOM в качестве параметров. Элемент DOM представляет виджет в браузере.
  • Много раз я использую объекты jQuery (в основном обертки вокруг элементов DOM), чтобы что-то делать с виджетами. Иногда они используются временно, иногда они сохраняются в переменной для последующих целей.
  • Функция AJAX вызывает использование строковых идентификаторов для этих виджетов. Они обработаны на стороне сервера.
  • Кроме того, у меня есть класс виджета, экземпляры которого представляют собой виджет. Он создается через нового оператора.

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

Любой совет?



EDIT:
@Will Morgan: Это дизайнер форм, который позволяет создавать веб-формы в браузере. Бэкэнд - это Zope, сервер веб-приложений python. Трудно получить более явное, поскольку это общая проблема, которую я наблюдаю все время при разработке Javasscript с трио jQuery, деревом DOM и моими собственными экземплярами класса прототипа.

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

makeLogger = function(){
     var rootEl = document.createElement('div');
     rootEl.innerHTML = 'Logged items:';
     rootEl.setAttribute('class', 'logger');

     var append = function(msg){
           // append msg as a child of root element.
           var msgEl = document.createElement('div');
           msgEl.innerHTML = msg;
           rootEl.appendChild(msgEl);
     };

     return {
          getRootEl: function() {return rootEl;},
          log      : function(msg) {append(msg);}
     };
};

// Usage 
var logger = makeLogger();
var foo = document.getElementById('foo');
foo.appendChild(logger.getRootEl());
logger.log('What\ up?');

В этот момент у меня есть обертка вокруг HTMLDivElement (размещенного объекта). Имея экземпляр регистратора (собственный объект) под рукой, я могу легко работать с ним через функцию logger.getRootEl().
Где я застрял, когда у меня есть только элемент DOM, и вам нужно что-то сделать с публичным API, возвращаемым функцией makeLogger (например, в обработчиках событий). И здесь начинается беспорядок. Мне нужно сохранить все собственные объекты в репозитории или что-то еще, чтобы я мог снова получить. Было бы гораздо приятнее иметь соединение (например, свойство объекта) с размещенным объектом с моим исходным объектом. Я знаю, что это можно сделать, но у него есть некоторые недостатки:

  • Эти типы (круговые) ссылки потенциально распространяются на IE7
  • Когда передать размещенный объект и когда передать собственный объект (в функциях)?

В настоящее время я делаю обратную ссылку с помощью метода jQuery data(). Но в целом мне не нравится, как мне приходится отслеживать связь между размещенным объектом и его родным коллегой.

Как вы справляетесь с этим сценарием?



<Б > EDIT3:
После некоторого понимания, которое я получил от примера Анурага.
@Anurag: Если я правильно понял ваш пример, критическая точка состоит в том, чтобы установить правильное (что правильно зависит от ваших потребностей, хотя) контекст выполнения для события обработчики. И это в вашем случае экземпляр объекта презентации, который выполняется с помощью функции Mootool bind(). Таким образом, вы гарантируете, что вы ВСЕГДА имеете дело с объектом-оболочкой (я назвал его родным объектом) вместо объекта DOM, правильно?

Примечание для читателя: Вы не должны использовать Mootools для достижения этого. В jQuery вы должны настроить обработчики событий с помощью функции $.proxy(), или если вы используете простой старый Javascript, вы должны использовать свойство apply, которое предоставляет каждая функция.

4b9b3361

Ответ 1

Для четырех объектов, которые необходимо синхронизировать, вы можете иметь один объект и передать ссылку в конструкторе или в качестве аргументов функции.

Как я могу исправить эту проблему, никогда не теряйте ссылку на объект-оболочку. Всякий раз, когда необходим объект DOM (например, вставка на страницу), этот объект-оболочка предоставляет его. Но приклеивая этот виджет на экран, объект-оболочка настраивает всю обработку событий и код обработки AJAX, специфичный для виджета, поэтому ссылка на оболочку поддерживается всегда в этих обработчиках событий и обратных вызовах AJAX.

Я создал простой пример на jsfiddle, используя MooTools, который может иметь смысл для вас.

Ответ 2

Вы можете использовать глобальный реестр:

window.WidgetRegistry = {};
window.WidgetRegistry['foowidget'] = new Widget('#myID');

и когда AJAX вызывает возврат, они могут получить виджетов следующим образом:

var widgetID = data.widgetID;
if (widgetID in window.WidgetRegistry) {
    var widget = window.WidgetRegistry[widgetID];
}

Для ваших вызовов jQuery: я предполагаю, что они относительно недороги, поскольку jQuery кэширует объекты для последующего использования. Но вы можете расширить предлагаемый выше WidgetRegistry с помощью .data():

var $widget = $('#myWidget');
var widgetID = 'foo';
$widget.data('widget', widgetID);

Таким образом, вы можете сохранить идентификатор виджетов, прикрепленный к каждому объекту jQuery, и повторно получить доступ к нему из глобального реестра.

Тестирование, если объект jQuery имеет существующий виджет:

return $('#test').data('widget') &&
       ($('#test').data('widget') in window.WidgetRegistry);

Обратите внимание, что это только предложения. На самом деле существует множество способов достижения такой консолидации. Если вы хотите совместить свой код с jQuery, вы можете расширить объект jQuery, чтобы вы могли написать что-то вроде:

$('#widget').widget({'foo':'bar'});
// and/or
var allWidgets = $('*:widget');
// ...

Ответ 3

Я не уверен, что полностью понял ваш вопрос, но я попытаюсь указать некоторые идеи.

По-моему, вы должны сделать базовый класс виджетов, который содержит общую функциональность для виджетов.

Можно использовать, например, AppName.Widgets.base(). Одной из переменных экземпляра является _events, который является объектом, который хранит события как ключи и функционирует как значения. Таким образом, каждый класс определяет события для этого виджета, и вы можете легко связать их в конструкторе. Что касается строковых идентификаторов, самым простым способом является использование toString().

Пример:

namespace('AppName.Widgets'); // you can find implementations easy

AppName.Widgets.base = function() {
    if (!this._type) return;

    this._dom = $('div.widget.'+this._type);
    for (var e in this._events) {
        this._dom.bind(e, this._events[e]);
    }

    this.toString = function() { return this._type; };
}

AppName.Widgets.example = function() { // extends AppName.Widgets.base
    this._type   = 'example';
    this._events = { 'click' : function(e) { alert('click'); }  };

    AppName.Widgets.base.call(this);
}

Ответ 4

Многие из того, что вы можете или не можете сделать, будут зависеть от того, сколько у вас контроля над javascript. Лично мне часто приходится использовать библиотеки, созданные другими, поэтому я могу получить только DOM node, но мне действительно нужен мой объект. В этих случаях я считаю, что использование функции данных в jQuery очень удобно. Используя функцию данных, вы можете "сохранить" свой объект внутри DOM node, чтобы получить его позже.

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

makeLogger = function(){
     var rootEl = document.createElement('div');
     rootEl.innerHTML = 'Logged items:';
     rootEl.setAttribute('class', 'logger');

     var append = function(msg){
           // append msg as a child of root element.
           var msgEl = document.createElement('div');
           msgEl.innerHTML = msg;
           rootEl.appendChild(msgEl);
     };

     var self = {
          getRootEl: function() {return rootEl;},
          log      : function(msg) {append(msg);}
     };

    // Save a copy to the domNode
    $(rootEl).data("logger", self);
    return self;
};

// Example of only getting the dom node
function whatsUp (domNode){
   // Get the logger from the domNode
   $(domNode).data('logger').log('What\ up?');
}

// Usage
var logger = makeLogger();
var loggerNode = logger.getRootEl();
var foo = document.getElementById('foo');
foo.appendChild(loggerNode);
whatsUp(loggerNode);