Пользователь
0,0
рейтинг
18 июля 2011 в 17:11

Разработка → Стартап: Технология Ajax Portal — на пути к энтерпрайз порталам нового поколения из песочницы

Ajax*
Как это часто случается, технология Ajax Portal появилась случайно, как синтез двух технологий Enterprise Portal и Ajax при построении «движка» для корпоративного сайта. В результате появилось нечто новое, что может дать второе дыхание энтерпрайз порталам.

В течение приблизительно 5 лет я работал в Минском подразделении крупного европейского концерна. В рамках своей работы я занимался Intranet (разработкой новых приложений и поддержкой старых). Целью была разработка некого интегрирующего инструмента, который бы объединял разрозненные приложения в единое информационное пространство. Спецификой такой работы являлось то, что приложения были разработаны с применением различных Web-технологий и физически находились на разных серверах. С технической точки зрения существовало только одно решение для того, чтобы «подружить» эти приложения, — применить HTML-фреймы. HTML-фреймы – это набор HTML-тегов, позволяющих разделить окно браузера на несколько частей, в каждой из которых можно отображать любые Интернет-сайты, Web -приложения и Web -страницы. Изюминкой такого подхода было то, что один из фреймов (одна из независимых частей браузера) содержала набор ссылок в виде простого списка или дерева, в другом фрейме отображались Интернет-сайты, Web -приложения, Web -страницы, PDF-документы, MS Office-документы (Word, Excel, PowerPoint и т.д.), фотографии и т.д. (в дальнейшем будем называть это контентом или Интернет-ресурсами). При этом не было никакой разницы, где располагался отображаемый контент и тип контента.

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

На базе разработанного решения я предлагал выпустить продукт для создания корпоративных сайтов, что не нашло свой поддержки у руководства. Типовая реакция менеджера крупной компании на любое новшество отлично описана в книге Джека Траута «Чувство лошади». Тем не менее, я не оставлял попыток создать новый продукт работая в двух следующих компаниях. При этом каждый раз «движки» для корпоративного сайта становились все более совершенными.

Со временем использование HTML-фреймов стало считаться «плохой практикой» при создании Web-приложений. Исключение делалось только для HTML-тега IFRAME, который позволяет добавить независимое окно браузера в любом месте Web-страницы и отображать в нем любой контент. Как следствие, я начал экспериментировать со страницами со встроенным деревом ссылок и тегом IFRAME. При этом при нажатии ссылки в дереве контент загружался внутри тега IFRAME. Естественно, дерево стало вскоре динамическим на основе JavaScript, с подгружаемыми по необходимости уровнями. Существовала даже версия, в которой дерево заменялось набором «иконок».

С появлением технологии Ajax пришла идея загружать HTML/XHTML-контент при помощи технологии Ajax, а потом отображать его путем вставки контента через JavaScript-свойство innerHTML (в общем случае, тега DIV). Это идеально работало для родственных приложений – приложений использующих один и те же CSS и JavaScript файлы.

Следующим этапом стала идея извлечения всех необходимых CSS и JavaScript файлов, относящихся к загружаемому HTML/XHTML-документу (контенту) и вставку их в основной документ. Классная идея, простая на первый взгляд. Я потратил на массу времени, прежде чем покрыл большинство возможных ситуаций, различие браузеров и т.д. Результатом работы стала JavaScript-библиотека ajax4all, которая позволяет при помощи JavaScript вставить практически любой HTML/XHTML-документ (Web-приложение) в другой HTML/XHTML-документ (Web-приложение), а затем работать с одним и другим практически без проблем (ремарка: конечно, может возникнуть проблема с одинаковыми названиями CSS-классов и JavaScript-методов и классов, но даже эта проблема может быть решена программным способом путем подмены имен CSS-классов и JavaScript-классов и методов). Эта библиотека могла бы оказаться игрушкой, если бы у меня бы не было еще одной страсти – портальные решения (энтерпрайз порталы, основанные на стандартах JSR-168 и JSR-286).

Так получилось, работая над проектом, который в дальнейшем должны были мигрировать на WebLogic Portal, я познакомился с портальными решениями. В итоге произошло главное – две идеи-страсти соединились, и в результате появился Ajax Portal.

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

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

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

Что Ajax Portal дает разработчику энтерпрайз порталов:
  1. Агрегация контента на стороне клиента (в браузере при помощи JavaScript) более эффективна, нежели классическая агрегация контента на стороне сервера, которая основана на стандартах JSR-168 и JSR-286.
  2. Снижение нагрузки на портальный сервер. Контент и данные пересылаются напрямую с портлетного сервера (сервера, на котором работает портлетное приложение) в браузер, минуя портальный сервер. Уменьшение трафика происходит приблизительно в десять раз – двадцать раз и зависит от количества портлетов на портальной странице.
  3. Любое изменение в портлете происходит без перезагрузки всей портальной страницы, в том числе добавление нового или удаление старого портлета. Как следствие, все работает быстрее.
  4. Портлеты могут открывать новые и закрывать старые портлеты (удалять с портальной страницы) без перезагрузки всей портальной страницы.
  5. Упростился обмен данными с другими портлетами (interportlet communication). Это может производиться как через ядро портальной страницы, так и через портальный или портлетный сервер.
  6. Отпала необходимость в processAction методе.
  7. Нет необходимости поддерживать громоздкие бриджи на основе стандартов JSR-301 и JSR-329 и как следствие – появился единый способ загрузки контента и ресурсов.
  8. Появилась возможность использования обычных Web-приложений в качестве портлетов (в частном случае mashup-ов, этому будет посвящена одна из следующих статей). Кроме того, частным случаем можно считать использование porjax (Web-приложений с портлет дескрипторами).
  9. В качестве портлетов можно использовать версии Web-приложения для мобильных устройств, которые предназначены для отображения на экранах с относительно маленьким разрешением. Это идеально подходит для отображения таких приложений в портлетах (пример — мобильные версии приложений социальной сети Одноклассники или геосервиса ATO.BY).
  10. Отсутствие специфического API. Как следствие — более дешевая доступная квалифицированная рабочая сила (это более интересно менеджеру или архитектору).


Все что я привел выше, в конечном итоге ведет к удешевлению создания программного обеспечения и снижении постоянных издержек при его обслуживании. Это все в основном достигается использованием библиотеки ajax4all. Она является сердцем Ajax Portal.

При создании библиотеки ajax4all, я столкнулся необходимостью преобразовать все относительные ссылки на изображения, стили и скрипты в абсолютные ссылки. Иначе попадая в портальную страницу, реальный адрес каждой относительной ссылки начинал рассчитываться браузером относительно адреса портальной страницы. Единственной серьезной проблемой являются относительные адреса «зашитые» в JavaScript-код. В этом случае решением является только адаптация Web-приложения (введение best practice для Web-приложений, которые предполагается использовать в качестве портлетов).

Второй задачей при создании библиотеки ajax4all было преобразовать обычные гипертекстовые ссылки в вызовы Ajax-запросов с последующей заменой контента в окне портлета.

Большинство Web-приложений по-прежнему используют HTML-формы, т.е. оказалось не достаточным просто «вычленить» контент Web-приложения, используемого в качестве портлета, и вставить его в портлетную страницу. Нужно было также поменять относительные ссылки в HTML-формах. В свою очередь, возник вопрос, что же делать с функциональностью самих форм? –Любой submit HTML-формы по Submit кнопке или при помощи JavaScript обычно приводил к перезагрузке все портальной страницы, вместо перезагрузки контента в окне портлета.

Выход был найден через применение Ajax Submit паттерна, который стал стандартом де-факто. Суть этого паттерна заключается в подмене обычного submit HTML-формы на запрос при помощи Ajax и затем получение стандартного ответа сервера, его преобразование и вставку в портальную страницу внутрь окна портлета. Звучит просто, но на поиск приемлемого архитектурного решения было потрачено несколько месяцев. Сложность задачи заключалась в существенных различиях DOM-модели браузеров. Причем, самым капризным браузером при написании JavaScript оказался Internet Explorer, а не Safari, как обычно считается.

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

Лишившись классического Portlet API на Java (100% совместимого со стандартами JSR-168 и JSR-286), я достиг главного: Ajax Portal построен на основе HTML, CSS и JavaScript. Это означает, что нет зависимости от платформы (в том числе и языка программирования) серверной части портала. Как следствие, Ajax Portal можно считаться кроссплатформенным решением. Разработка Web-приложения на Java поддерживалась путем библиотеки JSP-тегов. Как только библиотека стабилизируется, и перестанут появляться новые атрибуты тегов, можно будет создать библиотеку JSF-компонент. В июне этого года (2011) ко мне присоединился .NET-разработчик, который сделал библиотеку для .NET. С появлением, PHP-версии – можно будет с гордостью заявлять, что Ajax Portal абсолютно кроссплатформенное решение. Есть идея сделать библиотеку для Perl, но это далекая перспектива.

В качестве альтернативы Portlet API на Java проект может предложить Java MVC-фреймворк MicroServlet. Это был пример для моей книги по Java, который позволяет буквально на основе одного класса (на основе HttpServlet) построить полноценное Web-приложение. Фреймворк поддерживает форм бины и JSP-страницы. Широко используются Java-аннотации. Это идеальное решение для создания портлета, в котором могут быть всего один-два метода. Пользователи дадут свой ответ «быть ему или не быть». Мне нравится. Пока фреймворк MicroServlet все больше и больше приближается к Spring MVC. Пока я еще не понял, хорошо ли это или плохо. Последняя тенденция – это новое Portlet API основанное на фреймворке MicroServlet. Буде ли это удобно пользователям покажет время.

Часто портальные решения связывают с CMS. Создавать свою реализацию CMS считаю абсолютно не целесообразным, т.к. множество стартапов пытаются занять и так уже переполненную нишу. Существует достаточно решений, как коммерческих, так и бесплатных open source. С другой стороны, сама архитектура Ajax Portal позволяет использовать портал в качестве CMS с урезанным функционалом. Я думаю, этому надо будет посвятить отдельную статью.

Я столкнулся еще с одним интересным фактом – при создании реальных приложений потенциальные клиенты хотят использовать свои разработки для системы авторизации и аунтификации уже используемые на предприятии. Поэтому вопросы по реализации security модуля до сих пор являются открытыми.

Каждая версия Ajax Portal помимо библиотеки ajax4all и Portlet API на базе фреймворка MicroServlet несет в себе какие-то новшества. Для версий 1.0.* таким новшеством является динамическая трансформация регионов (контейнера портлетов на портальной странице). Применяется State паттерн. Трансформация осуществляется при помощи CSS. В качестве маркеров используются CSS-классы. При той же самой структуре HTML/XHTML-кода, в зависимости от маркера меняется представление региона и портлетов. Регион может преобразовываться из стандартного представления (каждый портлет отображается в маленьком окне) в аккордеон или систему закладок. Помимо этого, существует возможность весь регион представлять как одно окно без выделения порлетов. Существует так же представление, в котором отсутствует всяческое декорирование портлетов и региона.

За последние несколько лет множество компаний ушли с рынка портальных решений. Множество стартапов и open source решений были заброшены. На мой взгляд, главная причина – стандарт JSR-286 не смог реализовать потенциал технологии Ajax. Стандарт JSR-286, вышедший после появления Ajax, фактически лишь расширил возможности JSR-168, но не принес ничего нового. Именно эти проблемы призвана решить библиотека ajax4all.

Последнее веяние моды — плавный переход портальных решений в сторону mashup-ов. Я думаю, это тема для отдельной статьи. Ajax Portal находится в нише между энтерпрайз порталами и mashup-ми. В итоге получился кросбраузерный кросплатформенный Web 2.0 портал/mashup с поддержкой кастомизации и персонализации.

Главный вопрос для любого стартапа — это аналоги. Я долго искал и хорошо искал, но пока не нашел. Если Вы, читатели этой статьи, их знаете, я буду очень благодарен, если Вы укажите их мне. Обратите внимание, что речь идет:
  1. об альтернативе HTML-тегу IFrame и замене его на Ajax-запросы,
  2. реализации Ajax Submit паттерна для стандартных Web-приложений без их специальной подготовки (в offline).

И второй вопрос, который обычно возникает, а почему это не было сделано раньше? -Раньше не было стандарта «Cross-Origin Resource Sharing», который специфицирует возможность обращения Ajax-запросов за пределы текущего домена. Ajax Portal для remote портлетов так же поддерживает proxy.

Я «препарировал» множество популярных решений, включая iGoogle, и выяснил, что все они в своей основе используют HTML-тег IFrame. Поэтому библиотека ajax4all является единственной известной мне альтернативой HTML-тегу IFrame для remote-портлетов. При использовании ajax4all в Ajax Portal появляется возможность естественного заполнения контентом выделенной области и пропорциональной растяжке ее по вертикали, именно то, что не может быть достигнуто при использовании HTML-тега IFrame.

Продолжение:
http://habrahabr.ru/blogs/AJAX/125568/
@SVTeam
карма
3,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (37)

  • 0
    Сразу же к самому началу статьи комментарий:
    С технической точки зрения существовало только одно решение для того, чтобы «подружить» эти приложения, — применить HTML-фреймы
    А что, SOAP или XML-RPC нынче не в моде?
    Или, например, я когда-то писал свою XML-RPC-подобную систему интеграции веб-сервисов, которая не требовала почти ничего (а при должной заточке под конкретные ресурсы — вообще ничего) от сервисов-«доноров».
    И реализация крайне простая.
    Уж о чем-чем, а про iFrame я бы в последнюю очередь думал
    • 0
      Это был 12 лет назад. О существовании SOAP и XML-RPC я тогда еще даже не слышал. Система писалась на Perl… Тег IFrame так же позволял показывать MS Word и MS Excel документы, а так же изображения.
      • 0
        С каких это пор браузеры научились показывать ворды-эксели?
        • 0
          Боюсь ошибиться, по моему с IE4 или IE5. У Microsoft тогда появилась концепция, что их браузер должен иметь возможность отображения офисных документов, если сам офис или соответствующие просмотрщики установлены на компьютере.
          • +1
            Это не браузеры, это расширения, которые ставились с некоторыми версиями офиса. Но потом, если мне не изменяет память, из офиса эти расширения убрали и с офисом 2003+ это уже не катит.
          • +1
            И да, «браузеры» != IE
            • 0
              Если браузер не может показать контент офисного файла, он предлагает открыть файл внешним редактором. Всех устраивало, кто не пользовался IE. А потом наступила эра PDF, а он был и есть везде.
  • 0
    а не подскажете, в каких броузерах CORS работает?
    я релиза таких пока не встречал
    • +1
      Начиная с MF 3.5 (https://developer.mozilla.org/en/HTTP_access_control), в IE8 есть свой объект (http://magmainteractive.net/weblogs/post/Internet-Explorer-8-and-Cross-domain-Requests.aspx).
  • 0
    Не, какое-то это костыльное решение.
    Крос-доменность — сплошные костыли. Стандарт-то есть, но его поддержка весьма туманна. А учитывая то что в большинстве случаев корп. сети сидят в экосистеме Microsoft и браузер там — IE (хорошо если свежий), то вообще беда.
    А контроль доступа? Роли? Интеграция с Active Directory (раз уж это Enterprise сегмент)? И еще сотни вопросов.
    Нельзя такие вещи переносить на яваскрипт, нельзя.
    SOAP, XML-RPC или схожая собственная реализация на curl-ах или сокетах для получения данных из сервисов и вся бизнес-логика на сервере портала.
    Вариантов масса.
    • 0
      Terion, крос-доменность нужна для построения машапов и технологиям SOAP и XML-RPC там нет места. Машапы это Web 2.0 решения основанные на Ajax. Я понимаю вашу озабоченность вопросами безопасности. Именно поэтому в статье я указал, что секурити модуль еще не готов именно потому, что я тоже вижу некоторые проблемы. Лично мне нравится пока использовать обычный прокси. Я внимательно наблюдаю за развитием ситуации с CORS и как только все будет хорошо, я сделаю CORS в качестве основного решения. Я просто буду готов. Это то как в рекламе, вы не должны, но вы в принципе можете…
      • 0
        «крос-доменность нужна для...»
        Я не говорю что она не нужна, я говорю, что в яваскрипте это реализовывается через задницу, из-за проблем со стандартами и их поддержкой.

        ничто не мешает пропускать все через сервер и делать эти ваши мЭшапы с использованием серверных технологий, которые будут контролировать данные и доступы
        • 0
          Terion, я же и в статье и в ответах написал, что можно и мне нравится использование proxy в Ajax Portal. Но у этого подхода повышается нагрузка на портальный сервер. При использовании крос-доменных запросов: 1) снижается нагрузка на портальный сервер 2) ускоряется загрузка контента 3) нет необходимости тотального использования кешей для блокировки лавинного возрастания ретранслируемого контента через портальный сервер. Это всеравно будет сделано — это следующий шаг эволюции и переход к распределенным системам. Путь текущая версия CORS неидеальна. Следующая версия будет лучше.
  • 0
    а почему просто не использовать, к примеру, extJS? в коем можно написать свои store под нужный api? да и концепция «портлетов» на него тоже легко ложится
    • 0
      ну и кросс доменность там тоже легко реализуется через Ext.data.ScriptTagProxy
    • 0
      На сколько я знаю, extJS не является бесплатной. Вы готовы платить 500 баксов или на 500 больше за одну девелоперскую лицензию? С другой стороны, если вы готовы платить и у вас есть покупатели — можем договориться (я вполне серьезно).
      • 0
        екста (а ныне сенча) платная, да, но там это все уже реализовано, по сути.
        Взяли бы джейквери и сократили бы себе объем работы в десятки раз.
      • 0
        Осваивать можно и бесплатно. А при запуске Корпоративного портала со всякими наворотами заплатить 500 баксов.
        Статья как бы намекает на сложные разработки, а что такое 500 баксов относительно при нескольких месяцах разработки энтерпрайз софта?
        • 0
          Продукт только выходит из альфы в бету. До тех пор пока проект не выйдет на самоокупаймость я буду воздерживаться от использования платных решений. Исключение может быть сделано, только при наличии спонсора или если инициатором является владелец(ы) JS-фреймворка.
          • 0
            Причём тут альфа, бета, гамма?
            Вам за продукт не платят(вы на нём не зарабатываете)? Тогда и за использование extJS платить не надо
            Вам за продукт платят? Вот тогда можно и нужно заплатить
            • 0
              PyKaB, так я и пытаюсь его продавать… Неужели Вы подумали, что я стал писать цикл статей просто так?!!!
          • 0
            Пока проект не зарабатывает денег ничего платить не надо. Или цена продукта сравнима с 500$ что не позволяет с одной продажи его купить?

            Интересно во сколько вы оцениваете час своей работы и сколько часов Вы планируете потратить на свой продукт + будущая стоимость продукта. Ну чтоб прикинуть процент от 500 баксов
            • 0
              Стоимость первых продаж не связана со стоимостью моего рабочего времени.
              • 0
                Дело даже не в моих затратах, а в затратах потенциальных потребителей моего продукта. Я пытался продать первые версии по 300$ за лицензию. Просто не знал тогда, что высокая цена являться конкурентным преимуществом.

                Короче, вопрос ребром: Вы готовы купить пакет из 20 лицензий за 10000$?
                • 0
                  Я имел в виду лицензии Ajax Portal. Вы хотите иметь ajax4all основанный на extJS, я готов Вам предоставить такую библиотеку. Цена вопроса 10000$.
  • 0
    Вдогонку:
    При использовании ajax4all в Ajax Portal появляется возможность естественного заполнения контентом выделенной области и пропорциональной растяжке ее по вертикали, именно то, что не может быть достигнуто при использовании HTML-тега IFrame.

    Если родительское и дочернее окно находятся в пределах одного домена (включая его поддомены) то сайзятся фреймы на «ура». Ну это так, на заметку.
    • 0
      так весь же интерес для remote.
  • 0
    Ах, и еще:
    «зааяксивание» форм (любых форм и без потери функциональности) на любом аякс-фреймворке (jquery например) реализуется в три десятка строчек кода.
    А писать такие решения без фреймворков вообще — имхо глупость.
    • +1
      Terion, главной идей не было просто «зааяксить форму». Идея была в том, чтобы в runtime режиме переделать форму так, чтобы обычное Web-приложение ходило по Ajax, т.е. вы берете независимое Web-приложение, вставляете его в портальную страницу так, чтобы оно работало как и прежде (полностью сохранялась прежняя функциональность). Это нужно, чтобы из обычного Web-приложения получился порлет. Зачем тут нужен Ajax? -Чтобы submit этого ставленого в портальную страницу Web-приложения не вызвал перезагрузку всей портальной страницы. Это можно только сделать через Ajax и фреймы.
      • 0
        Это делается путем блокировки сабмита (возвратом false в конце обработчика onSubmit), onSubmit упаковки данных формы в строку и отправки ее аяксом по атрибуту action формы методом, указанным в атрибуте method формы.
        А дальше слушаем ответ и обрабатываем его как угодно.
        Всё. И не нужны никакие фреймы.
        Одна функция (или класс) на несколько десятков строк, провешенный на $(form).live() (для jQuery). и не нужно городить никаких огородов.
        • 0
          Terion, Вы правы, реализация Ajax Submit это только часть функционала ajax4all. Наверное 10%. Я из принципа только что посчитал все необходимые строчки: ajax4all_core.js (89 строчек или 4178 с форматированием без комментариев) и ajax4all_submit.js (65 строчек или 2643 с форматированием без комментариев). Думая, соизмеримо, если бы я писал это на jQuery. В самом начале, я сделал реализацию по верх Bindows и чуть не начал ваять версию под Dojo. Мода такая переменчивая… Главное я не хотел зависеть от какого-то фреймворка и как у любого решения тут свои плюсы и минусы. Представляете если бы Вы были фанатом Dojo?!!! Стали бы вы пользоваться библиотекой написанной под jQuery… Тоже самое касается ExtJS. Может быть я сделаю позже или при поддержке (например, при помощи гранда или спонсора) или если найду инвестора.
          • 0
            Да я jQuery привожу лишь в качестве примера.
            Какой фреймворк — не важно.
            Просто писанина велосипедов — занятие неблагодарное и ненужное.
            Есть редкие случаи, когда действительно нужно писать голым кодом (будь то яваскрипт или пхп), но подобного рода приложение — явно не тот случай.
          • 0
            «Представляете если бы Вы были фанатом Dojo?!!! Стали бы вы пользоваться библиотекой написанной под jQuery»
            — ничто не мешает их совмещать, кстати. Джейквери легко изолируется и они не будут друг другу мешать.
            А работать с голым яваскриптом фанату доджо — не думаю, что легче и лучше)
            • 0
              Terion, это обязательно будет сделано, только чуть позже.
      • 0
        Да, единственный случай, когда действительно не обойтись без фреймов — это когда аплоадятся файлы.
        Но опять же ничто не мешает добавить несколько строк для обработки этого случая.

        И да, у вас там написан ровно этот же метод, я «прицепился» к тому, что вы расписали это как что-то страшно сложное, на что у вас ушло несколько месяцев поисков решений, хотя искать тут абсолютно нечего, совершенно тривиальная задача.
        А раз вам она далась с таким трудом — то говорить о качестве конечного продукта не приходится. Да и сама по себе концепция, как я уже выше писал, мягко говоря, «не идеальная».
        • 0
          Terion, есть проблема — есть решение. Любое решение неидеально. Только пользователи могут ответить нравится им предлагаемое решение или нет.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.