Сага о том, как подружить Ajax(JQuery)+MVC+BreadCrumbs

Тема в разделе "Программирование", создана пользователем omfgpanda, 26.02.2010.

  1. omfgpanda
    Offline

    omfgpanda специалист

    Регистрация:
    22.01.2008
    Сообщения:
    673
    Симпатии:
    53
    Пол:
    Мужской
    Добрый день.

    Сегодня мы рассмотрим довольна сложный пример - я постараюсь Вам рассказать как правильно использовать аякс в ваших компонентах.

    MVC:
    Не вижу смысла перепечатывать стопяцот букв про то, что такое МВЦ и с чем его едят, вот несколько источников:
    - вебфлешер.ру
    - Записки начинающего веб-мастера

    Рекомендую у ОБЯЗАТЕЛЬНОМУ прочтению этих статей по MVC, иначе, без понимания MVC, весь оставшийся материал будет для вас безполезен.

    1. Структура компонента

    В нашем примере мы рассмотрим компонент с одним контроллером и несколькими моделями. Каждая модель будет иметь свое представление и будет отвечать
    за свой "уровень вложенности", т.е. мы реализуем многоуровневый компонент. Я думаю этот пример идеально подойдет для реализации телефонного справочника.
    Ниже приведена схема передачи данных внутри нашей МВЦ-модели
    [​IMG]

    1.1 Контроллер (controller.php)

    Как мы видим из схемы, после того как пользователь попадает на страницу компонента управление передается в контроллер, который в свою очередь, в зависимости от полученных параметров решает какой модели передать управление и, соответственно, какие данные вернутся к нам обратно.
    Код (PHP):
    1. function display()
    2.     {
    3.         $this->_checkParams();
    4.         parent::display();
    5.     }

    Основной метод контроллера - display(), в листинге мы видим, что вызывается еще один метод checkParams(), который и определяет - какие параметры были переданы и что нам дальше делать )
    Код (PHP):
    1. private function _checkParams()
    2.     {
    3.         $cur_param = JRequest::getInt('c_option');
    4.        
    5.         switch ($cur_param)
    6.         {
    7.             case 1:
    8.                 sleep(3);
    9.                 JRequest::setVar('view', 'model1' );
    10.                 $this->getView('model1','html');   
    11.             break;
    12.            
    13.             case 2:
    14.                 sleep(3);
    15.                 JRequest::setVar('view', 'model2' );
    16.                 $this->getView('model2','html');
    17.             break;
    18.             case 3:
    19.                 sleep(3);
    20.                 JRequest::setVar('view', 'model3' );
    21.                 $this->getView('model3','html');
    22.             break;
    23.             default:
    24.                 JRequest::setVar('view', 'textcomponent' );
    25.                 $this->getView('textcomponent','html');
    26.         }      
    27.     }

    Из листинга видно, что мы получаем из адресной параметр "c_option" и в зависимости от его значения передаем управление в соответствующую модель. Если "c_option" пустой или не попадает не в одно из условий - показываем стартовую страницу компонента.

    1.2. Стартовая страница

    Для отображения главной страницы компонента мы не используем ни какой модели. Из контроллера управление сразу же переходит в представление
    Код (CODE):
    1. views/textcomponent/view.html.php:

    Код (PHP):
    1. defined( '_JEXEC' ) or die( 'Restricted access' );
    2.  
    3. jimport( 'joomla.application.component.view');
    4.  
    5.  
    6. class textcomponentViewtextcomponent extends JView
    7. {
    8.     function display($tpl = null)
    9.     {
    10.         $greeting = "Стартовый шаблон!";
    11.         $iID = JRequest::getInt('Itemid');
    12.         $cName = JRequest::getString('option');
    13.         $this->assignRef( 'greeting',   $greeting );
    14.         $this->assignRef( 'itemID', $iID );
    15.         $this->assignRef( 'cName',   $cName );
    16.        
    17.         parent::display($tpl);
    18.     }
    19. }

    Тут мы получаем параметры из адресной строки - Itemid и option. (Это сделано для того, что бы после переименования компонента не нужно было править код.) И выводим соответствующий шаблон
    Код (CODE):
    1. view/textcomponent/tmpl/default.php

    Код (PHP):
    1. <?php
    2. defined('_JEXEC') or die('Restricted access');
    3. $com_path = 'components/com_textcomponent/js/';
    4.  
    5. JHTML::script('jquery.js' , $com_path );
    6. JHTML::script('ajax_history.js' , $com_path );
    7. JHTML::script('main.js' , $com_path );
    8. ?>
    9. <link rel="stylesheet" href="/components/com_textcomponent/css/main.css" type="text/css" />
    10.  
    11. <h1><?php echo $this->greeting; ?></h1>
    12. <div id="main-component-div">
    13.     <a href="index.php?option=<? echo $this->cName ?>&Itemid=<? echo $this->itemID ?>
    14.                                         &c_option=1" name="firstlvl" id="start-link">
    15.         Переход на первый уровень
    16.     </a>
    17.     <div id="ajax-data"></div>
    18.     <div id="ajax-loader"></div>
    19. </div>

    Тут мы подключаем 3 JS библиотеки:
    Код (CODE):
    1. jquery.js - ядро фреймворка JQuery
    2. ajax_history.js - плагин для сохранения истории в браузере при использовании аякса (работает не во всех браузерах)
    3. main.js - библиотека в которой будут описаны все функции для работы с аяксом.

    Также мы имеем 3 слоя:
    Код (CODE):
    1. - main-component-div - основной слой, содержит в себе ссылку для перехода на следующий
    2. уровень компонента, а также еще два слоя для реализации аякса.
    3.  - ajax-data - слой, куда мы будет загружать полученные данные
    4.  - ajax-loader - слой, в котором будет отображаться индикатор работы приложения

    Визуальная схема прилагается
    [​IMG]

    1.3. Передача управления модели

    На главной странице компонента у нас имеется сслыка и она содержит параметр "c_option=1". Это значит, что при нажатии на ссылку управление будет передано с начала контроллеру, т.к. значение = 1 будет выполнен следующий кусок кода:
    Код (PHP):
    1. case 1:
    2.                 sleep(3);
    3.                 JRequest::setVar('view', 'model1' );
    4.                 $this->getView('model1','html');   
    5.             break;

    и управление будет передано первой модели:
    Код (CODE):
    1. models/model1.php

    Код (PHP):
    1. defined( '_JEXEC' ) or die( 'Restricted access' );
    2. jimport('joomla.application.component.model');
    3.  
    4. class TextcomponentModelModel1 extends JModel
    5. {
    6.     public $_content;
    7.     public $pathway;
    8.     public $iID;
    9.     public $cName;
    10.  
    11.     function __construct()
    12.     {
    13.         parent::__construct();
    14.        
    15.         $this->_getParams();
    16.         $this->getData();
    17.     }
    18.    
    19.     private function _getParams()
    20.     {
    21.         $this->iID = JRequest::getInt('Itemid');
    22.         $this->cName = JRequest::getString('option');  
    23.     }
    24.  
    25.  
    26.     function addBCItem()
    27.     {
    28.         $pathway = TextcomponentController::getBCObject();
    29.         $pathway->addItem('Model1');  
    30.         $this->pathway = TextcomponentController::getBCData(); 
    31.     }
    32.  
    33.     function getData()
    34.     {
    35.         $this->addBCItem();
    36.  
    37.         $this->_content = "Model1";
    38.        
    39.         return $this->_content;
    40.     }
    41.    
    42.  
    43. }

    В классе TextcomponentModelModel1 мы видим 4 метода:
    Код (CODE):
    1. __construct() - конструктор класса
    2. _getParams() - метод для получения параметров option и Itemid
    3. addBCItem() - метод для работы со строкой навигации
    4. getData() - метод в котором мы получаем "нужные" нам данные )))


    1.3.2 Строка навигации

    Не так давно я написал статью "Модифицируем строку навигации (не безызвестный BreadCrumb)", где описал основные принципы работы со строкой навигации. Сейчас мы применим это дело на практике:
    Код (PHP):
    1. $pathway = TextcomponentController::getBCObject(); // получаем объект СН для модификации
    2. $pathway->addItem('Model1');  //добавляем новый элемент
    3. $this->pathway = TextcomponentController::getBCData(); //получаем хтмл-код модуля "mod_breadcrumbs"

    Функции getBCObject() и getBCData() находятся в контроллере и выглядят так:
    Код (PHP):
    1. function getBCObject()
    2.     {
    3.         $app =& JFactory::getApplication();
    4.         $pathway =& $app->getPathway();
    5.        
    6.         return $pathway;
    7.     }
    8.    
    9.     function getBCData()
    10.     {
    11.         jimport( 'joomla.application.module.helper' );
    12.         $gBC = JModuleHelper::getModule( 'mod_breadcrumbs');
    13.         $cBC = JModuleHelper::renderModule( $gBC );
    14.        
    15.         return $cBC;
    16.     }

    думаю комментарии излишни )

    1.3.3. Представление

    После того, как модель заканчивает свою работу вызывается представление
    Код (CODE):
    1. views/model1/view.html.php

    Код (PHP):
    1. defined( '_JEXEC' ) or die( 'Restricted access' );
    2.  
    3. jimport( 'joomla.application.component.view');
    4.  
    5.  
    6. class TextcomponentViewModel1 extends JView
    7. {
    8.     function display($tpl = null)
    9.     {
    10.          //отображаем вид
    11.          
    12.          parent::display();
    13.  
    14.     }
    15. }

    с помощью которого мы отображаем шаблон
    Код (CODE):
    1. views/model1/tmpl/default.php

    Код (PHP):
    1. <?php
    2. defined('_JEXEC') or die('Restricted access');
    3. $myModel = $this->_models["model1"];
    4.  
    5. ?>
    6. <div id="ajax-first-lvl" class="child">
    7.     <?php echo $myModel->pathway; ?>
    8.     <a href="index.php?option=com_textcomponent&Itemid=120&c_option=2" name="firstlvl">
    9.         <?php echo $myModel->_content; ?>
    10.     </a>
    11. </div>

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

    2. Ajax

    И так, аякс. Работа с аяксом будет реализована с помощью фреймворка JQuery. В шаблоне по-умолчанию
    Код (CODE):
    1. views/textcomponent/tmpl/default.php

    мы подключали библиотеку main.js, листинг ниже
    Код (CODE):
    1. $(document).ready(function()
    2. {
    3.      $("#ajax-loader").bind("ajaxSend", function()
    4.      {
    5.          $("#ajax-data").hide();
    6.          $(this).show(); // показываем элемент
    7.        
    8.      }).bind("ajaxComplete", function()
    9.      {
    10.         $(this).hide(); // скрываем элемент
    11.         $("#ajax-data").fadeIn(1000);
    12.      });
    13.      
    14.      ajaxEvent();
    15.  
    16. });
    17.  
    18. function ajaxEvent()
    19. {
    20.     var mDiv = "main-component-div";
    21.     var BCDiv = "pathway";
    22.    
    23.     $("#" + BCDiv + " a[name='bc-ajax-mode']").click(function()
    24.     {
    25.         linkAction($(this));
    26.        
    27.         return false;
    28.     });
    29.    
    30.     $("#" + mDiv + " a[name='firstlvl']").click(function()
    31.     {
    32.         linkAction($(this));
    33.        
    34.         return false;
    35.     });
    36. }
    37.  
    38. function linkAction(item)
    39. {
    40.     var ajaxMode = "&tmpl=component&format=raw";
    41.     var cURL = item.attr("href") + ajaxMode;     
    42.    
    43.     $("#start-link").hide();
    44.     $.post(cURL, getData); 
    45. }
    46.  
    47. function replaceBC()
    48. {
    49.     var pItems = $("#ajax-data a.pathway");
    50.     var iLength = pItems.length;
    51.     if(iLength > 0)
    52.     {
    53.         for(i = 2; i < iLength; i++)
    54.             $(pItems[i]).attr("name", "bc-ajax-mode");
    55.         var PathwayContainer = $("#pathway");
    56.         var newPathway = $("#ajax-data span.breadcrumbs");
    57.         PathwayContainer.html(newPathway);
    58.         console.log(pItems.length);  
    59.     }
    60. }
    61.  
    62. function getData(msg)
    63. {
    64.     $("#ajax-data").html(msg);
    65.     replaceBC();
    66.     ajaxEvent();
    67. }

    Рассмотрим подробнее:
    - блок $(document).ready(function() {...});
    Код (CODE):
    1. $(document).ready(function()
    2. {
    3.      $("#ajax-loader").bind("ajaxSend", function()
    4.      {
    5.          $("#ajax-data").hide();
    6.          $(this).show(); // показываем элемент
    7.        
    8.      }).bind("ajaxComplete", function()
    9.      {
    10.         $(this).hide(); // скрываем элемент
    11.         $("#ajax-data").fadeIn(1000);
    12.      });
    13.      
    14.      ajaxEvent();
    15.  
    16. });

    Как и в Joomla, в JQuery при использовании аякса возникают события, которые мы можем перехватить и навесить на них, нужные нам функции.
    Метод ready() объекта $(document) вызывается при полной закгрузке хтмл-страницы, т.е. DOM-дерево полностью готово и все элементы доступны.
    В этом методе мы навешиваем обработчики событий
    Код (CODE):
    1. ajaxStart - начало выполнения аякс-запроса
    2. ajaxComplite - окончание выполнения запроса (не обязательно удачное)

    Т.е. при старте запроса мы прячим слой с данными "#ajax-data" и показываем слой с индикатором работы аякса "#ajax-loader".

    Также мы вызываем функцию ajaxEvent() которая навешивает обработчики событий для известных DOM-элементов.
    Код (CODE):
    1. function ajaxEvent()
    2. {
    3.     //id родительного слоя (см. схему)
    4.     var mDiv = "main-component-div";
    5.     //id слоя, содержащего строку навигации
    6.     var BCDiv = "pathway";
    7.     //перехватываем нажатие на ссылку с атрибутом
    8.     //name='bc-ajax-mode' (строка навигации)
    9.     $("#" + BCDiv + " a[name='bc-ajax-mode']").click(function()
    10.     {
    11.         //передаем в функцию объект ссылки на которую нажали
    12.         linkAction($(this));
    13.        
    14.          //выходим из обработчика, тем самым не даем браузеру перейти по действительному адресу
    15.         return false;
    16.     });
    17.     //перехватываем нажатие на ссылку с атрибутом
    18.     //name='firstlvl' (строка навигации)
    19.     $("#" + mDiv + " a[name='firstlvl']").click(function()
    20.     {
    21.         linkAction($(this));
    22.        
    23.         return false;
    24.     });
    25. }

    Суть данной функции в том , что бы перехватить нажатия на ссылки с заданным именем и выполнить нужные нам действия, а точнее функцию linkAction(), которая в качестве параметра принимает объект элемента на которого было совершено нажатие:
    Код (CODE):
    1. function linkAction(item)
    2. {
    3.     //необходимые POST-параметры для получения
    4.     //только данных из компонента
    5.     var ajaxMode = "&tmpl=component&format=raw";
    6.     //получаем адрес, который указан в атрибуте Href
    7.     var cURL = item.attr("href") + ajaxMode;     
    8.    
    9.     //прячем ссылку, которая находится
    10.     //на стртовой странице
    11.     $("#start-link").hide();
    12.    
    13.    //делаем аякс запрос
    14.     // и вызываем колбэк функцию
    15.     $.post(cURL, getData); 
    16. }

    Тут мы получаем урл из переданного, в качестве аргумента, элемента, добавляем параметры, с помощью которых мы сообщаем ядру джумлы, что хотим получить только данные, которые вернет нам наш компонент и не более того ) Выполняем аякс-запрос с помощью функции $.pos() , которая в качестве параметров принимает адрес скрипта, который должен обработать запрос , и имя колбэк функции, с помощью который мы обработаем результаты запроса.
    Код (CODE):
    1. function getData(msg)
    2. {
    3.     //@msg - данные которые пришли в результате ответа сервера
    4.     //запихиваем данные в <div id="ajax-data">
    5.     $("#ajax-data").html(msg);
    6.     //подсовываем новую строкунавигации
    7.     replaceBC();
    8.     //вешаем на созданные объекты обработчики
    9.     ajaxEvent();
    10. }

    Данные, которые вернет нам сервер, хранятся в переменной "msg". Помещаем их в нужный нам контейнер и навешиваем на вновь созданные DOM-элементы обработчики событий.
    На этом моменте я бы хотел заострить Ваше внимание - дело в том, что когда мы в $(document).ready(function() {...}); первый раз вызывали функцию ajaxEvent(), которая навешивала события на элементы, находящиеся на странице. Но после аякс-запроса мы получили новые элементы, на которые действие функции ajaxEvent() не расространяется, т.о. нужно вызвать эту функцию еще раз и обновить, нужные нам, обработчики.

    В getData() присутствует еще одна очень интересная функция replaceBC(), суть которой заключается в изменении строки навигации. Рассмотрим ее подробнее:
    Код (CODE):
    1. function replaceBC()
    2. {
    3.     //получаем массив объектов <a> с классом pathway
    4.     var pItems = $("#ajax-data a.pathway");
    5.     var iLength = pItems.length;
    6.     if(iLength > 0)
    7.     {
    8.         //дописываем в них атрибут  name = bc-ajax-mode
    9.         for(i = 2; i < iLength; i++)
    10.             $(pItems[i]).attr("name", "bc-ajax-mode");
    11.         //получаем контейнер, в котором находится настоящая СН
    12.         var PathwayContainer = $("#pathway");
    13.         //получаем контейнер, в котором находится новая СН
    14.         var newPathway = $("#ajax-data span.breadcrumbs");
    15.         //подставляем новую СН вместо старой (:
    16.         PathwayContainer.html(newPathway);
    17.     }
    18. }

    В обще, идея с модификацией и подменой строки навигации пришла ко мне не сразу, а в результате долго хождения по квартире, даже ушиб себе ногу ((( Но я очень рад что это ко мне пришло :)
    Итак, что было понятнее приведи структуру СН:
    Код (html):
    1. <div id="pathway">
    2.     <span class="breadcrumbs pathway">
    3.         <a class="pathway" href="http://avangard-sk.ru/">
    4.             Главная
    5.         </a>
    6.         <img alt="" src="/templates/rhuk_milkyway/images/arrow.png">
    7.         <a class="pathway" href="http://avangard-sk.ru/">
    8.             Наш компонент
    9.         </a>
    10.         <img alt="" src="/templates/rhuk_milkyway/images/arrow.png">
    11.             Модель1
    12.     </span>
    13. </div>

    Теперь, после выполнения аякс-запроса мы имеем две строки навигации:
    - Первая - настоящая, ее все видят но изменений в ней ни кто не наблюдает
    - Вторая - вновь полученная, с изменениями, но ее никто не видит, так же как и полученный контент, но это временно ;) Итак, что мы делаем с помощью replaceBC() - получаем массив элементов СН, нас интересуют 3,4 и т.д. элементы, т.е. строка может выглядеть следующим образом:
    Код (CODE):
    1. [B][COLOR="Blue"][U]Главная [/U][/COLOR][/B]- [B][COLOR="Blue"][U]Наш компонент[/U][/COLOR][/B] - [B][COLOR="Blue"][U]Раздел1 [/U][/COLOR][/B]- [B][COLOR="Blue"][U]Подраздел1 [/U][/COLOR][/B]- [B][COLOR="Blue"][U]Подподраздел1 [/U][/COLOR][/B]- Предмет1

    И таким образом первые два элемента СН - "Главная" и "Наш компонент" нас не интересуют, нам не нужно навешивать на атрибуты name="bc-ajax-mode", с помощью которых мы сможем перехватить переход по этим ссылкам.
    После того, как все элементы были изменены - берем "новую" СН и меняем ее на "старую".
    Код (CODE):
    1. PathwayContainer.html(newPathway);


    Принцип аякс-запроса в мвц модели:
    [​IMG]

    Заключение

    Вот в принципе и все. Считаю что для российского джумла-комъюнти статья будет очень полезна, т.к. в рунете нет нихрена подобного ))))

    Приветствую любую адекватную критику, также буду рад ответить на все ваши вопросы (по сабжу )) )

    Пример работы.
    Сам компонент в аттаче.
     

    Вложения:

    Последнее редактирование модератором: 31.03.2014
    Dead Krolik, OlegM и Asylum нравится это.
  2.  
  3. Offline

    AnastaJoo Недавно здесь

    Регистрация:
    15.12.2009
    Сообщения:
    39
    Симпатии:
    0
    Пол:
    Женский
    омг ) (в хорошем смысле этого слова)
     
  4. omfgpanda
    Offline

    omfgpanda специалист

    Регистрация:
    22.01.2008
    Сообщения:
    673
    Симпатии:
    53
    Пол:
    Мужской
    ммм...женщина ;)
     
  5. Offline

    nagi Недавно здесь

    Регистрация:
    03.10.2007
    Сообщения:
    48
    Симпатии:
    0
    Пол:
    Мужской
    Хорошая статья, но вот у меня вообщем такой вопрос, про плагин и jquery

    Если мы используем jquery в плагине и хотим чтобы данные передаваемые из скрипта леттели не в отдельный файл от плагина, а в тот же плагин в определённый обработчик, как правильно задвать УРЛ при посыле Json?
     

Поделиться этой страницей

Загрузка...