Алексей Качаев | Web-developer, фрилансер, менеджер

PHP, jQuery, AJAX, CodeIgniter, ZendFramework, Web2.0, блоггинг, Wordpress, бизнес, StartUp, Инветоры, web-проекты, бизнес-идеи, фриланс, интерфейсы

JQuery-навигация: каскад динамических списков произвольной длины

Опубликовано: Алексей Качаев |

Добрый день, читатели. Решил сегодня расслабиться от каждодневной работы и написать очередной tutorial. На повестке дня сегодня магия jQuery и небольшой рассказ о том, как динамически создавать на странице группы select`ов. Для того, чтобы вам была понятна задача, которую мы решаем, немного предыстории.

Сейчас я работаю над крупным проектом, который условно можно поделить на две части: 1) парсинг данных со сторонних ресурсов в наше базу данных, 2) расширенный поиск по этой базе с кучей условий и фильтров. Задача, которую мы решим сегодня, появилась именно при создании пользовательского интерфейса для задания условий поиска.

В дело в том, что в информация в базе данных разделена по категориям неограниченной вложенности. И этих самых категорий очень много. Для того, чтобы дать возможность пользователю выбрать категорию для поиска, было решено использовать динамические select`ы. Т.е. изначально на страничку загружается список с категориями верхнего уровня. Если пользователь выбирает для в этом списке какую-то категорию, то мы даем ajax-запрос на специальный скрипт, который возвращает нам список подкатегорий (т.е. тех, для которых выбранная категория является непосредственным родителем).

jQuery, Ajax и PHP - создание каскада динамических списков

О том, как организовать подобную иерархию списков, можно прочитать здесь. Но. Такое решение не снимает проблем, ведь оно создано специально под 3 списка, в которых динамически меняется содержимое. А при работе с деревом категорий неограниченной вложенности мы никогда не знаем, сколько списков нам понадобиться. Поэтому описанную в статье методику пришлось “расширить”.

Вооружимся jQuery, PHP и современными традициями AJAX, начнем (сразу скажу, что нам понадобиться jQuery 1.3)…

Для нетерпеливых: то, что у нас получиться в итоге, вы можете посмотреть здесь:

jQuery, Ajax, PHP - создание каскада динамических списков.

1. Задача

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

- Компьютерная техника
— Персональные компьютеры
—– Windows
——- Athlon
——- Celeron
——- Pentium
—– Macintosh
—– Прочее
— Ноутбуки
— КПК
— Периферийные устройства

- Бытовая техника
— Аудио-аппаратура
— Фотоаппараты
— Микроклиматическое оборудование
—– Вентиляторы
—– Кондиционеры
——- Оконные
——- Навесные
—– Обогреватели

- Офисное оборудование
— Копировальная техника
— Факсы

Необходимо при загрузке страницы формировать первый список с родительскими категориями (выделены жирным шрифтом). Далее при выборе элемента в списке, создавать следующий список с подкатегориями. К примеру, пользователь выбирает в списке “Персональные компьютеры”. Мы подгружаем еще один список с содержанием “Windows”, “Macintosh”, “Прочее”. И т.д.

2. Планирование

Заранее продумаем все “тонкости” данной задачи. Списки (select) будем именовать “category_N”, где N - номер “уровня” списка. Первый уровень 0. Следовательно, первый селект на страничке будет иметь атрибут name=”category_0″, следующий name=”category_1″ и т.д.

Список будет заполнен элементами (option), текст которых совпадает с текстом категории, а атрибут value содержит уникальный идентификатор доступа (например, id категории в таблице базы данных).

При выборе любого элемента в любом списке, мы должны:

1) отправить ajax-запрос на серверсайд (пусть это будет ajax/list.php) и передать параметры: value выбранного элемента и уровень (level) создаваемого списка.

2) ajax/list.php возвращет в JSON формате: количество найденных “подкатегорий” и уровень списка, массив “подкатегорий” (если их количество больше 0 :) ).

Далее у нас два варианта:

3) если количество найденных “подкатегорий” 0, то мы должны просто удалить все списки, уровни которых выше, чем уровень списка вызвавшего запрос.

К примеру, у пользователя на страничке были списки: Бытовая техника > Микроклиматическое оборудование > Кондиционеры > Оконные. И он выбирает из второго списка (уровень = 1) Фотоаппараты. У категории Фотоаппараты нет вложенных подкатегорий, поэтому мы должны просто удалить списки с уровнем 2 и 3.

4) если количество найденных подкатегорий больше 0, то мы ищем список с нужным уровнем. Если таковой существует, очищаем его и заполняем нужными данными. Все старшие удаляем. Если списка такого уровня нет, то мы создаем его с нужным набором option`ов.

Также не забываем, что пользователи особи нетерпеливые, а ajax запрос требует некоторое время. Показывать картинки для сигнализации о том, что запрос обрабатывается, конечно, можно. Но это, мягко говоря, опасно. Если пользователь не дождется выполнения запроса и выберет еще один пункт меню, то потом может случиться непредвиденное :) Поэтому при отправке ajax-запрос заблокируем все списки из нашего каскада. При получении ответа, соответственно эту блокировку снимем.

5) Для повышения юзабильности наших списков, вызывать ajax - запрос будем только в том случае, если пользователь сменил текущую позицию списка. Т.е. если он кликнул по списку, прокрутил его…, посмотрел.., подумал… и снова кликнул на тот пункт, который уже был выбран - то делать запрос снова, переопределять следующий список нам не смысла. Добиться этого можно используя событие onChange для элемента select.

3. PHP serverside

В исходном проекте у меня была база данных, но сейчас мы не будем так все усложнять. Сделаем два массива - один с названиями категорий, другой с идентификатором родительской категории. Такой объем данных позволяет нам это сделать :) Напомню, что серверсайд принимает два параметра POST-запроса: pcategory и level. Из найденных данных формируем JSON и отдаем клиенту.

/**
 * @project:    SelectTutorial
 * @file:		ajax/list.php
 * @author		Alex Kachayev <kachayev@gmail.com>
 *              http://www.kachayev.ru
 * @version:	1.0.0000
 *
 * Returns categories list from array
 * This is only example for learning
 * jQuery and Ajax
 */
 
/* Если это не AJAX.. умираем :) */
if( $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest')
	die( 'Request Error!' );
 
/**
 * @var array Здесь содержаться названия категорий:
 *                 [идентификатор категории] => 'название'
 * @todo      Это только учебный пример, в реальной
 *                ситуации вы скорее всего воспользуетесь
 *                для этого базой данных или XML файлом
 */
$categories = array(
	'x0'  => 'Компьютерная техника', 'x1'  => 'Персональные компьютеры',
	'x2'  => 'Windows', 'x3'  => 'Athlon', 'x4'  => 'Celeron',
	'x5'  => 'Pentium', 'x6'  => 'Macintosh', 'x7'  => 'Прочее',
	'x8'  => 'Ноутбуки', 'x9'  => 'КПК', 'x10' => 'Периферийные устройства',
	's0'  => 'Бытовая техника', 's1'  => 'Аудио-аппаратура',
	's2'  => 'Фотоаппараты', 's3'  => 'Микроклиматическое оборудование',
	's4'  => 'Вентиляторы', 's5'  => 'Кондиционеры', 's6'  => 'Оконные',
	's7'  => 'Навесные', 's8'  => 'Обогреватели', 'k0'  => 'Офисное оборудование',
	'k1'  => 'Копировальная техника', 'k2'  => 'Факсы'
);
 
/**
 * @var array В этом массиве мы храним идентификаторы
 *            родительских категорий. '0' означает, что
 *            категория не имеет родительской
 * @see $categories
 */
$levels = array(
	'x0'  => '0',  'x1'  => 'x0',
	'x2'  => 'x1', 'x3'  => 'x2', 'x4'  => 'x2',
	'x5'  => 'x2', 'x6'  => 'x1', 'x7'  => 'x1',
	'x8'  => 'x0', 'x9'  => 'x0', 'x10' => 'x0',
	's0'  => '0',  's1'  => 's0',
	's2'  => 's0', 's3'  => 's0',
	's4'  => 's3', 's5'  => 's3', 's6'  => 's5',
	's7'  => 's5', 's8'  => 's0', 'k0'  => '0',
	'k1'  => 'k0', 'k2'  => 'k0'
);
 
/**
 * Получаем значение параметров из запроса
 * либо устанавливаем значения по умолчанию
 */
$pcategory = isset($_POST['pcategory']) ? $_POST['pcategory'] : 0;
$level        = isset($_POST['level']) ? $_POST['level'] : 0;
 
/**
 * @var array Результирующий список категорий
 *            [item]  => Массив категорий
 *            	  [id]   => Идентификатор
 *                [name] => Название
 *            [count] => Количество категорий
 *            [level] => Уровень каскада
 */
$list = array();
/**
 * @var array Промежуточный массив идентификаторов категорий
 */
$result = array();
 
/**
 * Получаем идентификаторы нужных нам категорий
 * @see http://www.php.net/array_keys
 */
foreach( array_keys($levels, $pcategory) as $value)
	$result[] = array('id'=>$value, 'name'=>$categories[$value]);
 
/* Заносим категории в результирующий массив */
if(count($result))
	$list['item'] = $result;
 
/* Указываем дополнительные параметры */
$list['count'] = count($result);
$list['level'] = $level;
 
/* Явно указываем формат данных */
header('Content-type: application/json');
 
/**
 * Преобразуем в массив в JSON
 * @see http://www.php.net/json_encode
 */
echo json_encode($list);

Обратите внимание, мы использовали функцию json_encode, которая преобразует переданные ей значения в JSON формат.

4. Расширяем возможности jQuery

Для своего же удобства создам три дополнительных jQuery-метода. Идею такого подхода, я вычитал у Геннадия, ссылку на статью которого я приводил выше. Спс ему большое :)

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

Код откомментирован, думаю это поможет разобраться:

(function($){
    /* Очищаем select */
    $.fn.clearSelect = function() {
        return this.each(function(){
            /* Проверяем является ли элемент select`ом */
            if(this.tagName=='SELECT') {
                this.options.length = 0;
                /* Блокируем на время заполнения */
                $(this).attr('disabled','disabled');
            }
        });
    }
 
    /* Удаляем старшие элементы */
    $.fn.clearField = function(selector) {
        /**
         * Ищем все элементы следующие за вызывавшим
         * и удовлеторяющие переданному селектору
         */
        this.nextAll(selector).remove();
        return this;
    }
 
    /* Заполняем select переданными данными */
    $.fn.fillSelect = function(dataArray) {
        return this.clearSelect().each(function(){
            /* Проверяем является ли элемент select`ом */
            if(this.tagName=='SELECT') {
                var currentSelect = this;
 
                /* Устанавливаем этот option первым в списке */
                if($.support.cssFloat) {
                    currentSelect.add(start,null);
                } else {
                    currentSelect.add(start);
                }
 
                $.each(dataArray,function(index,data){
                    /* Если определено 'name' */
                    if(data.name) {
                        /* Создаем новый option */
                        var option = new Option(data.name,data.id);
                        /* Добавляем новый option к select`у */
                        if($.support.cssFloat) {
                            currentSelect.add(option,null);
                        } else {
                            currentSelect.add(option);
                        }
                    }
                });
                /* Выделяем первый элемент списка */
                $(this).removeAttr('disabled').find('option:first').attr('selected', 'selected');
            }
        });
    }
})(jQuery);

5. Получение списка категорий

Теперь напишем JavaScript-функцию getCategory(), которая будет инициализировать ajax-запрос. Основные моменты ее работы мы уже обговорили, для остального есть комментарии и в коде.

/* Функция отсылает ajax-запрос */
function getCategory(pcategory, level) {
    $.ajax({
        url: 'ajax/list.php',
        type: 'POST',
        data: 'pcategory='+ pcategory +'&level='+ level,
        dataType: 'JSON',
        timeout: 5000,
        beforeSend: function(){
            // Блокируем все необходимы select`ы
            $('select[name^=category_]').attr('disabled', 'disabled');
        },
        complete: function(){
            // Снимаем блокировку
            $('select[name^=category_]').removeAttr('disabled');
        },
        success: function(response){
            var data = eval('('+ response +')');
            // Если количество категорий в ответе 0 либо не определено
            if(data.count === 'undefined' || data.count == 0) {
                // просто удаляет старшие уровни каскада
                $('select[name=category_'+ (data.level - 1) +']')
                    .clearField('select[name^=category]')
                    .clearField('span');
                return false;
            }
            if( $('select[name=category_'+ data.level +']').length ) {
                // Если select этого уровня уже существует
                // мы должны удалить все старшие select`ы,
                // очистить старые данные и заполнить новым контентом
                $('select[name=category_'+ data.level +']')
                    .clearField('select[name^=category]')
                    .clearField('span')
                    .fillSelect(data.item);
            } else {
                // Если select этого уровня не существует,
                // мы должны его создать и заполнить данными
                $('#categories select:last').after('<span>&gt;</span> <select name="category_'+ data.level +'"></select>');
                $('select[name=category_'+ data.level +']').fillSelect(data.item);
            }
 
            /* Сбрасываем старый обработчик */
            $('select[name=category_'+ data.level +']').unbind('change');
            /* Вешаем новый */
            $('select[name=category_'+ data.level +']').change(function(){ return clickEvent($(this)); });
 
            return false;
        },
        error: function(){
            // Сообщаем пользователю, что произошла ошибка
            $('#msg').append('<p>Some error with categories. Please, try later ;)</p>');
            return false;
        }
    });
}

6. Обработчик события

Давайте теперь определим ту самую функцию-обработчик, которую уже подцепили на наши списки в предыдущем пункте.

function clickEvent(select)
{
    var id = select.find('option:selected').attr('value');
    /**
     * Если id=-1, значит выбран пункт "Выбор.."
     * значит мы должны просто очистить старшие списки
     */
    if (id == '-1') {
    	select.clearField('select[name^=category]').clearField('span');
    	return false;
    }
 
    var level = parseInt(select.attr('name').replace('category_', '')) + 1;
    return getCategory(id, level);
}

Как только DOM готов к обработке, вызываем getCategory() для получения первого списка.

$(document).ready(function(){
    /* Получаем список категорий */
    getCategory(0, 0);
});

7. Что дальше?

Естественно, все вышеописанное - только “каркас”, на который можно еще очень много чего по прикручивать. Так что, дерзайте!

P.S. Код будет работать только под jQuery 1.3 Вообще, разницы мелочь… но все же. В предыдущих версиях jQuery мы бы писали select[@name=], а не select[name=], как делаем это сейчас.

Понравился пост? Будь в курсе последних событий: подпишись на RSS-ленту.!

Также читайте по теме:

39 комментариев на “JQuery-навигация: каскад динамических списков произвольной длины”

  1. Афигеть, вот это супер, я мечтал такое простое реализовать, но моих познаний JS не хватало. Огромное спасибо вам за это. Я очень вам благодарен:)

  2. упс, смотрел на Mozilla Firefox 3.0.6 работает на отлично а вот попробовал на IE 6.0.2900.2180 при выборе первого списка другие не подгружаются

  3. 2Андрей:

    Сейчас гляну. Когда проверял, не обратил внимание.

  4. Исправил :) Все дело в том, что я использовал событие “change”. Но при создании списка мы автоматически выделяет первый его элемент. Вот в этой строке:

    $(this).removeAttr(’disabled’).find(’option:first’).attr(’selected’, ’selected’);

    Соответственно, при клике по первому элементу, браузер не генерирует события “change”. Я решил эту проблемку путем использования события “click” для option`ов. Правда теперь можно подумать о какой-нибудь функции кеширования… но это уже позже.

    Спасибо за замечание :)

  5. спасибо за оперативность, сейчас проверил, странно , но список по прежнему не срабатывает((
    При выборе какого либо элемента, ничего не происходит, ни ошибок ничего не выдаёт, страницу обновлял кнопкой F5. А в ФФ всё отлично.

    версия WinXP и IE у меня
    6.0.2900.2180.xpsp_sp2_rtm.040803-2158

  6. Довольно интересный подход к проблеме. Спасибо за советы.

  7. Можно попробовать вместо live() использовать непосредственно click(), IE6 очень строго к подобным функциям относится.
    Динамические селект списки вообще довольно распростроненная задача, поэтому статья полезная. Мне как-то довелось реализовывать связанные автокомплит поля на манер селет списков - дикое количество скрытых обработчиков на странице. Жаль в то время не попался туториал на тему, очень пригодился бы.

  8. У меня, кстати, тоже не срабатывает, Алексей, Вы ответите?

  9. 2Павел:

    Да, Павел. Отвечу :) Сегодня попробую заменить live() либо на функцию, которая будет реализовать bind() для каждого новосозданного списка. Либо с помощью плагина к jQuery. Как только получиться позитивный результат, обязательно напишу.

    P.S. Я вообще IE обажаю :)

  10. Спасибо Алексей, буду надеяться что всё у вас получится.

  11. Только что попробовал, и ни чего не вышло, жду Алексей решения проблемы.

  12. “Я решил эту проблемку путем использования события “click” для option`ов”
    OMG, это совсем плохо.
    1) Я совсем не сразу понял что нужно еще раз выбрать уже выбраный пункт в select-е. А я всего-лиш user
    2) Если я буду использовать клавиатуру то ничего не работает.

    Как по мне то лучше сделать фиктивный пунк меню “Выбрать” и вернуть “change” обратно. Для пользователя намного проще.

    Второе:
    А как же принцип unobtrusive JavaScript? Без JavaScript совсем работать не будет, это плохо.

    А так спасибо за статью. Интерсная.

  13. Да ладно, во многих приложениях используют click даже поверх onchange. Люди не жалуются ;)

    на счет js-unobtrusive, то знаете ли, этот принцип сложно реализовать в js-коде))))))))) Это реализуется в совокупности php+css+html уже кому как надо - под конкретный проект, автор же, как я понял, предлагает разработку касающуюся исключительно js

    кстати спасибо, автор, здорово и просто :):):)

  14. @riddi
    “Да ладно, во многих приложениях используют click даже поверх onchange. Люди не жалуются”

    Я жалуюсь :). И вообще это очень профессионально говорить “да ладно”, “ничье, и так пойдет”, “да какая разница” :).

    Относительно “предлагает разработку касающуюся исключительно js” согласен. Но статья не полная, если не упомянуть, что для того, чтобы приложение работало, как говорится, bullet-proof нужно сделать non-javascript версию.

    @Алексей Качаев
    Относительно “live() либо на функцию, которая будет реализовать bind() для каждого новосозданного списка”

    Есть такая техника - event delegation, для jQuery есть пример на http://www.danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery.

    live() делает то же, но на сайте написано, что пока не работает с change. http://docs.jquery.com/Events/live

    Не уверен, что сработает delegation, думаю, если в 1.3 не работает live(), то сомнительно, чтобы что-то другое, старше, заработало, но можно попробовать :)

  15. Новая версия каскада готова.

    Я лично проверил на Firefox 3.0.6, IE 7, Google Chorme 1.0.154, Opera 9.5. Кто протестирует на более старых версиях браузеров - буду признателен :)

    Сделал с помощью динамического “навешивания” change(). Для удобства, по совету Eater добавил фиктивный пункт “Выбрать”. Который, кстати, удобен, если пользователь сначала раскрыл старшие селекты, а потом вдруг передумал - выбором этого пункта он удаляет старший каскад.

    По поводу unobtrusive JavaScript, то к сожалению, разрабатывать js код на этих принципах пока не умею. Если у вас получиться реализовать что-то подобное в стиле unobtrusive JS - с удовольствием почитаю :)

  16. класно, теперь всё работает, спасибо огромное:)
    проверял через
    Firefox/3.0.6 и IE 6.0 работает отлично

  17. а у меня все равно ничего не работает, правда проверял через лису исключительно

  18. 2Яков:

    Укажите, пжл, точную версию вашего браузера. В лисе то, с самого начало все работало…

  19. [...] вроде никогда о нем не писал). …И даже о том, что мой каскад динамических списков не работает в Opera 12 (!!!). Неужели я один так отстал от [...]

  20. [...] JQuery-навигация: каскад динамических списков произвольн… [...]

  21. Не работает в IE 5.5 и соответственно ниже

  22. :) Ого вы добрались… Я даже не помню как этот агрегат выглядел.

    Смотрите официальные доки jQuery. Там ясно сказано - корректная работа с IE 6.0+, FF 2+, Opera 9.0+, Safari 3.0 +.

  23. Для начинающих выложили бы работающий архив как на примере, который выше сами указали http://www.linkexchanger.su/2009/82.html , а то не понятно какой фрагмент программы в какой вайл записывать. На уже работающем примере легче разобраться и поня что к чему.

  24. 2selex:

    Имхо, разбираясь с этим вопросом сами - куда быстрее получите нужные навыки.

  25. Алексей, скажи пожалуйста как сделать так, чтоб вместо последнего select`а появлялась форма типа checkbox, для случая возможности выбора нескольких категорий?

  26. Автору огромное спасибо. Хотелось бы спросить про функцию eval(), для чего она необходима? У меня без нее ничего не получилось. Точнее мой вариант несколько отличается от всего выше представленного.

  27. Здравсвуйте!
    Хочу попробовать запустить этот же пример, но кроме пункта “Выбрать…” в первый селект ничего больше не подгружается((
    Вроде все сделала правильно.. и jQuery есть, и php файл, и сами скрипты…
    Подскажите, в чем может быть причина?

  28. событие change не генерируется

  29. Функция очистки списка работать правильно только в том случае если список не содержит группировки пунктов .
    Иначе строку
    this.options.length = 0;
    надо менять на что-то вроде:
    while (this.childNodes.length) {
    if (this.firstChild.tagName == ‘OPTGROUP’) {
    while (this.firstChild.childNodes.length) {
    this.firstChild.removeChild(this.firstChild.firstChild);
    }
    }
    this.removeChild(this.firstChild);

  30. Ребятки, автор упустил маленькую деталь… Это проблема с кодировкой в функции php “json_encode”. У вас просто ничего не будет показываться , кроме первого селекта. Решение есть в сети. Вижу, что многие с этим мучаются, а простые и важные вопросы не были опубликованы. Приятно, что автор уделил время поделиться, но прошу прощения, полуфабрикаты мало кто делает, это плохой тон в программировании. Не вижу еще и кода html в открытом виде, пришлось слизывать с примера на сайте, чтобы проверить, все ли работает… Так что даже опытный пользователь не сразу поймет, почему код не работает. Если бы я не загрузил Firebug и не отследил переменные, я бы еще долго мучался, хотя не первый месяц “за рулем”.

  31. А как отобразить выбранные данные в селектах после пост запроса?

  32. Народ подскажите что из себя представляет форма:
    я ее такой нарисовал(или списал)

    Категория:

  33. У мене теж не працює. я провіряв в усіх бровзерах. Викладіть архів!

  34. В IE синтаксическая ошибка ругается на эту строку
    var data = eval(’('+ response +’)');

    форму написал такую это правильно:

    Категория:

    В список ничего не загружается помогите плиз???

  35. форма такая:
    [form name="search" action="" method="post"]
    [table width="973" bgcolor="#f2f2f2" border="0" cellpadding="5" cellspacing="5"]
    [tbody]
    [tr]
    [td]
    [div align="right">Категория:[/div]
    [/td]
    [td id="categories"]
    [select name=""]

    [/select]
    [/td]
    [/tr]
    [/tbody]
    [/table]
    [/form]

  36. Спасибо Алексею, пример работает. но с кодировкой проблема.
    база данных mysql кодировка utf8 general ci

  37. Извеняюсь за флуд не знаю как убрать верхние надписи но проблема у меня в том что пришлось закоментировать //header(’Content-type: application/json;’); и тогда стало работать но опять с ошибкой :
    - поддерживается только английский шрифт, надписи на русском не отображаются;
    - первый селект у меня пустой а все начинает работать со второго, то есть во втором селекте уменя данные которые должны быть в первом
    спасибо помогите!

  38. вот решение проблемы для русского языка
    function json_safe_encode($var)
    {
    return json_encode(json_fix_cyr($var));
    }

    function json_fix_cyr($var)
    {
    if (is_array($var)) {
    $new = array();
    foreach ($var as $k => $v) {
    $new[json_fix_cyr($k)] = json_fix_cyr($v);
    }
    $var = $new;
    } elseif (is_object($var)) {
    $vars = get_object_vars($var);
    foreach ($vars as $m => $v) {
    $var->$m = json_fix_cyr($v);
    }
    } elseif (is_string($var)) {
    $var = iconv(’cp1251′, ‘utf-8′, $var);
    }
    return $var;
    }

    echo json_safe_encode($list);

    Остается вторая проблема:
    первый селект у меня пустой а все начинает работать со второго, то есть во втором селекте уменя данные которые должны быть в первом
    спасибо помогите!

  39. Справа в самому файлі php - якщо всі російські букви на латиницю, то воно запрацює!!!

Оставьте комментарий