Работа с медиабиблиотекой WordPress
WordPress уже давно вырос из штанов «блогового движка», и теперь осваивает просторы тру-кул-продакшн системы. Скажем прямо, справляется с переменным успехом, все-таки CMS была создана в те времена, когда только рождался Php 5, и до сих пор пытается сохранять обратную совместимость.
Если «правильно готовить», то на выходе можно получить годный продукт, в плане пользовательского интерфейса в админке все достаточно интуитивно, и за пару дней можно понять принципы работы.
Для одного коммерческого проекта, потребовалось создать плагин с выбором файлов из медиабиблиотеки. Подобная задача решается несколькими вариантами: можно сделать обычное текстовое поле, куда писать ссылку на файл, можно в это текстовое поле писать id файла из медиабиблиотеки — но мне захотелось сделать красиво. В WP (WordPress) есть уже готовый функционал работы с файлами, добавление их в библиотеку и выбор. Нужно только подцепиться к этому АПИ.
Чтобы было проще понять, поставлю конкретную проблему, которую героически преодолею: нужно в каждую публикацию типа post добавить кнопку прикрепления файла из библиотеки. Границы четкие и ясные. Поехали!
Технически, ничто не мешает все нижеописанные куски php писать напрямую в файл functions.php в тему, но мне кажется, что то, что решаю, несколько больше чем функция темы, а скорее даже наоборот, вообще к функциям темы не имеет отношения, поэтому, я рекомендую создать отдельный плагин.
Допустим, входной файл плагина создан, и пока он чистый, кроме служебных строк, которые расскажут движку, что это плагин и его можно включать и выключать. Для начала, нужно подключить АПИ
function my_plugin_enqueue_media() { wp_enqueue_media(); } add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_media' );
Эти строки подключают все стили и скрипты связанные с работой медиабиблиотеки.
А теперь начинается магия. Следи за руками и постарайся не отставать за ходом мысли.
В папку с новый плагином (конечно, папку можно не создавать, но тогда магии не произойдет) нужно положить некоторый js-файлик, о содержимом которого поговорим чуть позже. Будет неплохо, если он будет правильно подключен. Для этого немного модифицируем вышеописанный код.
function my_plugin_enqueue_media() { // Подключение АПИ для работы с медиабиблиотекой wp_enqueue_media(); // Скрипт для выбора файла wp_enqueue_script('add-one-media.js', plugins_url('/js/add-one-media.js', __FILE__), array('jquery')); } add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_media' );
В функции wp_enqueue_script третьим параметром передается массив зависимостей. Да, я до сих пор использую уже не мэйнстримовый jquery, не нравится, ничто не мешает использовать что-то другое. Сам движок уже содержит jquery, поэтому незачем тянуть свою версию. Обожаю логически структурировать расположение файлов, поэтому js положу в отдельную папку для js, а вдруг потом это все обрастет чем-то большим, пусть уж сразу лежит там где положено.
Добавим саму кнопку для прикрепления файла к записи. Реализуем это через механизм метаполей. Надеюсь что такое хук в WP не надо объяснять? В противном случае, тебе еще рано так глубоко погружаться в кишки WP. Или просто копируй, и пусть оно работает как истинная магия.
/** * Создание блока метаполей для постов */ function add_one_meta() { add_meta_box('add_one_meta', 'Прикрепленный файл', 'add_one_meta_view', 'post'); } add_action('add_meta_boxes', 'add_one_meta'); /** * HTML код блока */ function add_one_meta_view() { global $post; // Если это пост отличный от необходимого, уйдем отсюда, и ничего не отобразим if ($post->post_type != 'post') { return; } // Используем nonce для верификации wp_nonce_field(plugin_basename(__FILE__), 'add_one_nonce'); // Заберем значение прикрепленного файла $add_file_id = get_post_meta($post->ID, 'add_file_id', true); // Ссылка на добавление файлов, если js отколючен $upload_link = esc_url(get_upload_iframe_src('null', $post->ID)); // Поле для выбора файла echo ' <div class="custom_field_itm"> <div class="js-add-wrap">'; if ($add_file_id) : $file_info = get_post($add_file_id); $file_icon = wp_get_attachment_image($add_file_id, 'thumbnail', true); echo '<div class="add_file js-add_file_itm"> <input type="hidden" name="add_file_id" value="' . $add_file_id . '" /> <div class="add_file_icon">' . $file_icon . '</div> <p class="add_file_name">' . $file_info->post_title . '</p> <a href="#" class="button button-primary button-large js-add-file-remove">Открепить файл</a> </div>'; endif; echo '</div><br/> <a href="' . $upload_link . '" class="button button-primary button-large js-add-file">Добавить файл</a> </div>'; }
На выходе получается такое
Как не трудно догадаться, в метаполе планируется хранить только id вложения, а зачем что-то большее? Имея id можно получить хоть черта лысого, было бы желание. Сохраняется все это вот так:
/** * Сохраняем данные, когда пост сохраняется * * @param $post_id * @param $post * * @return mixed */ function omsu_doc_save_postdata($post_id, $post) { // Если это пост отличный от документа, уйдем отсюда if ($post->post_type != 'post') { return $post_id; } // проверяем nonce нашей страницы, потому что save_post может быть вызван с другого места. if ( ! isset($_POST['add_one_nonce']) || ! wp_verify_nonce($_POST['add_one_nonce'], plugin_basename(__FILE__)) ) { return $post_id; } // проверяем, если это автосохранение ничего не делаем с данными нашей формы. if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return $post_id; } // проверяем разрешено ли пользователю указывать эти данные if ('page' == $_POST['post_type'] && ! current_user_can('edit_page', $post_id)) { return $post_id; } elseif ( ! current_user_can('edit_post', $post_id)) { return $post_id; } // Сохраним прикрепленный файл $add_file_id = (int)$_POST['add_file_id']; update_post_meta($post_id, 'add_file_id', $add_file_id); return $post_id; } add_action('save_post', 'omsu_doc_save_postdata', 10, 2);
Куча проверок и защиты и 2 строки непосредственно сохранения. Кажется, что уже много написано, но к сути пока не подошел. А вся сила, брат, в JavaScript. Так-то! Вот мы и пришли к нему. Блин! Когда ты уже знаешь как это делать, все кажется таким очевидным, но на момент изучения потратил полдня на формулирование запроса и еще полдня на поиск ответа. А сейчас вот так просто я раскрыл все таинства этого действа, которые по сути уместились в 27 строчек кода. И отчего знания не приходят в голову сразу как только становятся необходимы?
jQuery(function ( $ ) { var frame; // Прикрепить файл к редакции $(document).on('click', '.js-add-file', function ( event ) { event.preventDefault(); // Если окно загрузки уже доступно, просто откроем его if( frame ) { frame.open(); return; } // В противном случае, создадим новое frame = wp.media({ title : 'Выберите файл', button : { text: 'Использовать этот файл' }, multiple: false // Если нужна возможность крепить одним махом несколько файлов }); // показать инфу о прикрепленном файле frame.on('select', function () { // Получим объект со всей информацией о выбранном файле var attachment = frame.state().get('selection').first().toJSON(); $('.js-add-wrap').html('<div class="add_file js-add_file_itm">' + '<input type="hidden" name="add_file_id" value="' + attachment.id + '" />' + '<div class="add_file_icon"><img src="' + attachment.icon + '" alt="" /></div>' + '<p class="add_file_name">' + attachment.title + '</p>' + '<a href="#" class="button button-primary button-large js-add-file-remove">Открепить файл</a>' + '</div>'); }); // Откроем файл frame.open(); }); });
Вот как бы и всё. Теперь нет проблем, чтобы крепить файлы к посту. Можно запуливать в продакшен и радоваться ровно до того момента, пока не потребуется открепить файл =) К счастью это совсем не проблема.
Внутрь замыкания добавляем 4 строчки кода, и теперь плагин умеет откреплять файлы.
// отцепить приложенный файл от редакции $(document).on('click', '.js-add-file-remove', function ( event ) { event.preventDefault(); $(this).closest('.js-add_file_itm').remove(); });
Чтобы добавить немного красивостей (хотя я бы не рекомендовал вам в области «красивостей» консультироваться с человеком взращенным ComicSans’ом) добавим немного css. В самую первую функцию, где подключаются скрипты, допишем еще строку с подключением стилей.
wp_enqueue_style('add-one-media', plugins_url('/css/add-one-media.css', __FILE__));
А в css, например, такие стили:
.add_file { position: relative; overflow: hidden; } .add_file_icon { position: relative; float: left; margin: 0 10px 10px 0; } .add_file_name { position: relative; margin: 0 0 10px; }
Теперь точно всё!
Показал как работать с медиабиблиотекой, попутно расписал как создавать свои метаполя, как правильно подключать свои скрипты и стили в админку. Кстати, рабочий плагин, созданный по мотивам этого поста, можно забрать на гитхабе. Туда же свои предложения по улучшению и исправлению.
Всем рок!
Работа с графиками и диаграммами google chart
У гугла есть сотни крутых сервисов, начиная от почты gmail и заканчивая уведомлениями google alert. Но самое прикольное, что есть гугловские сервисы, которые упрощают жизнь разработчикам, например, google font для нестандартных шрифтов на сайте или google chart для построения графиков.
Вот о последнем я и хочу поговорить. Если кто пытался постичь этот неимоверный сервис, то видел какого объема документация для разработчиков. Естественно, при выполнении прикладных задач, никто всю документацию читать не будет, я тоже не стал заниматься фигней, и полез сразу в примеры. Чтобы вывести 2 таких графика, я потратил очень много времени.
Поэтому хочу помочь таким же бедолагам и сэкономить их время. А заодно будет и мне небольшая шпаргалка.
Начнем с круговой диаграммы, я посчитал, что эта задача сложнее и поэтому начал с нее. Вариант из примеров выглядит так:
See the Pen vgWwWW by Ildar Saribzhanov (@ildar_r_saribzhanov) on CodePen.
От него и будем плясать. Что меня не устроило? Во-первых, цвета, не то чтобы они мне не понравились, но в макете были другие. Поэтому начнем с изменения цветов сегмента. Благо поиск по странице документации слова «color» быстро меня навел на нужный кусок. В опции надо запихать необходимые параметры:
var options = { title: 'Название', pieHole: 0.6, slices: { 0: { color: '#56b900' }, 1: { color: '#cccccc' } } };
Шестнадцатиричное значение цвета или можно написать то, что понимает css: ‘yellow’, ‘red’ и т.д. Количество предустановленных цветов не обязательно должно равняться количеству реально заданных сегментов, но лучше установить количество цветов с запасом, т.к. если ваших цветов не будет хватать, то диаграмма возьмет их из значений по умолчанию, что может сильно поломать ваш дизайн. Сразу изменим толщину рамки у диаграммы, за это отвечает параметр pieHole (размер дырки от бублика =) ), значения от нуля до единицы, где ноль это сплошные сегменты, а при n→1 рамка становится тоньше.
Еще мне нужно, чтобы размер самой диаграммы был максимальным на холсте, а вот легенда мне вообще не нужна, и фон тоже надо бы убрать. Опять же это меняется через объект опций:
var options = { title: '', pieHole: 0.6, slices: { 0: { color: '#56b900' }, 1: { color: '#cccccc' } }, backgroundColor: 'transparent', legend : 'none', chartArea : {left: 10, top: 10, width: '95%', height: '95%'} };
Значения говорят сами за себя. С chartArea есть особенность, если точку начала установить в нуле, а размеры сделать по 100%, то будет небольшой баг, при наведении на сегмент он подсвечивается, и вот эта подсветка обрежется границами обертки, поэтому я отступил от края. Теперь завернем все это в более осмысленную функцию (мне хочется верить, что она более осмысленная) и посмотрим что получается.
See the Pen oBoRGZ by Ildar Saribzhanov (@ildar_r_saribzhanov) on CodePen.
Продублирую функцию отдельно (вдруг онлайн интерпретатор удалит мое творение?), чтобы дать комментарии
function drawChartRound( arr_data, id ) { var data = google.visualization.arrayToDataTable(arr_data); var options = { title: '', pieHole: 0.6, slices: { 0: { color: '#56b900' }, 1: { color: '#cccccc' } }, legend : 'none', backgroundColor: 'transparent', chartArea : {left: 10, top: 10, width: '95%', height: '95%'} }; var chart = new google.visualization.PieChart(document.getElementById(id)); chart.draw(data, options); } // Пример вызова google.charts.load("current", {packages:["corechart"]}); var data = [['Actives', ''], ['var 1', 4], ['var 2', 6]]; drawChartRound( data, 'id_block' );
Принимает 2 параметра: массив с данными (первым элементом в массиве должно идти описание графика) и id блока куда встраивать диаграмму. Таким образом не обязательно отрисовывать график в момент готовности АПИ, можно вызвать в любой другой удобный момент, например, кода пользователь введет данные, которые нужно визуализировать. Мне кажется, это хорошо.
Несмотря на то, что я считал эту задачу простой, но поковыряться пришлось с ней больше, чем с круговой диаграмой. Конечно, проблема, в примерах, они были сильно перегружены.
Сразу дам итоговый вариант, и опишу что здесь за что отвечает
See the Pen dNZBJg by Ildar Saribzhanov (@ildar_r_saribzhanov) on CodePen.
И сама функция выглядит так
function drawHistogramChart( info, id ) { var data = google.visualization.arrayToDataTable(info); var view = new google.visualization.DataView(data), max = 0; for(itm in info ) { if( max < info[itm][1] ) { max = info[itm][1]; } } var options = { title : "", chartArea : {left: 0, top: 0, width: '100%', height: '100%'}, backgroundColor: 'transparent', bar : {groupWidth: "95%"}, width : 280, vAxis : { minValue: 0, maxValue: max } }; var chart = new google.visualization.ColumnChart(document.getElementById(id)); chart.draw(view, options); } // вызывается так google.charts.load("current", {packages:["corechart"]}); var data = [ ["", "", {role: "style"}], ["Описание 1", 10, "#cccccc"], ["Описание 2", 8, "#56b900"] ]; drawHistogramChart(data, 'capital');
По умолчанию включено слишком много всего. Что хочется отметить, если в круговой диаграмме настройки цвета секторов шли отдельной настройкой, то тут цвета передаются в массиве с данными.
Параметр chartArea в действительности очень сильно влияет на отображение. Если точку начала рисования отодвинуть от края, то появится градация шкал. Если высоту задать меньше 100%, то снизу появятся подписи к колонкам, если ширину установить меньше 100%, то справа появится кнопка выделения группы. Или что-то типа такого, по примерам из документации можно понять, что происходит.
Параметр bar — устанавливает ширину колонок.
vAxis — устанавливает максимально и минимальное значение шкалы, может быть важно, если необходимо, чтобы была видна нулевая точка. Так как я старался сделать функцию универсальной, то максимальное значение вычисляется, а не устанавливается жестко.
Однозначно Google Chart очень мощный сервис от корпорации добра. Если внимательно изучить примеры, то видно, что я использую крайне малую долю возможностей, там еще есть анимации для диаграмм, можно было заморочиться. Хочется верить что гугл не прикроет его, а иначе все наши красивости превратятся в тыкву. В этом и основной минус, я сам себя подписал на зависимость от третьих лиц. Когда только анализировал «рынок решений» для своей задачи, то посматривал в сторону d3.js, но мне он показался еще более мудреным. Когда-нибудь доберусь и до него, но как говорится в одном оскароносном фильме: «Не сейчас. Не сейчас…»
Всем рок!
JQuery плагин для валидации и форматирования денежных значений
Пожеланием заказчика было, чтобы при вводе денежного значения, вводимые символы разбивались по группам разрядов и выравнивались по правому краю.
Выравнивание это меньшая из проблем. Думаю, все понимают, что выравнивание текста в поле ввода ничем не отличается от выравнивания текста в любом другом html элементе. А вот разбиение по разрядам задача интересная.
Первым делом идем в великий и могучий поисковик. Недолгие поиски привели меня к этому плагину. У него даже есть возможность задавать маску для ввода денежных значений. Немного поигравшись я понял, что это не совсем то, что мне нужно: во-первых, жестко задано количество дробных разрядов, ни больше ни меньше; во-вторых, дробная часть сразу отделяется и это вызывает диссонанс при вводе. Если решение не нравится, нужно придумать своё.
Я уже однажды сталкивался с проблемой валидации поля input. Если кратко — это не самая очевидно решаемая задача. В моем случае сам алгоритм решения задачи прост:
Собственно сама логика реализуется вот так:
function format( val ) { var has_separator = false, res, int, fraction; val = val.split(','); // форматирование целой части while ( val[0].length > 3 ) { int = val[0].substr(val[0].length - 3) + ' ' + int; val[0] = val[0].substr(0, val[0].length - 3); } int = val[0] + ' ' + int; // Последний пробел справа убираем res = int.replace(/\s$/g, ''); res = res.replace(/^0/g, ''); // форматирование дробной части, при наличии if( val.length > 1 ) { has_separator = true; while ( val[1].length > 3 ) { fraction += ' ' + val[1].substr(0, 3); val[1] = val[1].substr(3); } fraction = fraction + ' ' + val[1]; // сначала удалим пробел fraction = fraction.replace(/^\s/g, ''); } if( has_separator ) { res += ','; } if( fraction !== '' ) { res += fraction; } return res; }
Вот как бы и чики-бамбони, что в переводе значит PROFIT. И тут вы спросите: «Эй, уважаемый, что за омлет, а где же яйца?!» — как этот код будет работать в рамках html+js. Вот мы и пришли к основной проблеме. Вопрос вот в чем: как использовать вышеописанную функцию? При вводе символа в input происходит ряд событий, которые можно перехватить, это keydown, keypress и keyup. Логичным предположением было, повесить обработчик на keypress и, когда это событие произойдет, передать значение input’а в функцию на обработку. Именно так я и сделал. Осознание ошибки пришло мгновенно: в момент вызова обработчика keypress, в поле ввода еще нет того символа, который мы только что ввели. Сюрприз-сюрприз! Конечно, если вы очень хорошо понимаете событийную модель, для вас это не новость, и для меня это была не новость, но когда идет поток сознания некоторые детали реализации могут ускользать. «И что делать?» — спросите вы, а я вам отвечу! (Обожаю задавать вопросы от вымышленного оппонента). «Терпение, спокойствие, сейчас они появятся». Забегая вперед, скажу, что гений меня в этот момент покинул и решение я придумал не столь изящное как хотелось бы. К черту объяснения, как только происходит событие keypress, запихиваем в обработчик тйамаут с минимальным значением паузы, по истечении которого будет вызвана функция форматирования. Та-да!
$(selector).on('keypress', function ( e ) { setTimeout(function () { $(self).val(format($(self).val())); }, 1); });
Добавим пару регэкспов в функцию форматирования. Разрешим пользователям вводить любой разделитель, хоть запятую, хоть точку, но в input будем выводить только правильный разделитель, в моем случае это запятая. При форматировании функция добавляет пробелы, их нужно исключить из обработки при форматировании, более того, вообще нужны только цифры и разделить. С функцией форматирования разобрались.
Теперь к обработчику нажатия клавиши. Мы можем узнать какие символы пришли от клавиши и какие коды клавиш, поэтому часть ответственности переложим на основной обработчик. О чем идет речь: если пользователь будет вводить что угодно кроме цифр и точки, то вообще не будем запускать форматирование. Это решается не сложно, но есть одна проблема, хочется чтобы стандартное поведение работало. Мне удобнее работать с клавиатуры, навигация по стрелочкам, клавиши home и end, выделение, копирование и вставка, все это должно работать.
И конечно, же не обошлось без проблем на мобильных устройствах. Они, видимо, плохо понимают все это keypress и иже, но есть еще одно замечательное событие для полей ввода, зовется оно «input». Поэтому для мобильников мы еще добавим обработчик на oninput, и к тому же это событие работает и для вставки (ctrl+V), только поддерживается не везде, поэтому оставим обработку и на keypres.
Заканчиваем мусолить. Вот результат работы, обернутый в jquery плагин.
(function ( $ ) { $.fn.validMoney = function () { var make = function () { var self = this; // перепишем объект $(document).ready(function () { var def = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ','], // клавиши, которые не влияют [ctrl, end, home, left, right, tab] access = [17, 35, 36, 37, 39, 9]; $(self).on('keypress', function ( e ) { // is accessed keys if( access.indexOf(e.keyCode) >= 0 ) { return true; } // ctrl+A if( e.keyCode === 65 && e.ctrlKey ) { return true; } // backspace and del if( e.keyCode === 8 || e.keyCode === 46 ) { setTimeout(function () { $(self).val(format($(self).val())); }, 1); return true; } if( e.ctrlKey ) { setTimeout(function () { $(self).val(format($(self).val())); }, 1); return true; } if( def.indexOf(e.key) >= 0 ) { setTimeout(function () { $(self).val(format($(self).val())); }, 1); return true; } return false; }); }); self.oninput = function () { self.value = format(self.value); if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) { setTimeout(function () { $(self).val(self.value); }, 1); } }; function format( val ) { var res, int = '', fraction = '', has_separator = false; // точку меняем на запятую val = val.replace(/\./g, ','); // оставляем только цифры и запятую val = val.replace(/[^0-9,]/g, ''); // разделим на целую и дробную части val = val.split(','); // форматирование целой части while ( val[0].length > 3 ) { int = val[0].substr(val[0].length - 3) + ' ' + int; val[0] = val[0].substr(0, val[0].length - 3); } int = val[0] + ' ' + int; // Убираем последний пробел справа res = int.replace(/\s$/g, ''); // и ноль слева res = res.replace(/^0/g, ''); // форматирование дробной части, при наличии if( val.length > 1 ) { has_separator = true; while ( val[1].length > 3 ) { fraction += ' ' + val[1].substr(0, 3); val[1] = val[1].substr(3); } fraction = fraction + ' ' + val[1]; // сначала удалим пробел fraction = fraction.replace(/^\s/g, ''); } if( has_separator ) { res += ','; } if( fraction !== '' ) { res += fraction; } return res; } }; return this.each(make); }; })(jQuery);
Вызывается как и любой плагин вот так:
$('input').validMoney();
Пока писал этот пост нашел еще ряд багов, поэтому не уверен, что это финальный вариант. Для страждущих на гитхабе создал репозиторий, который может обновляться. Там код с примером, если надумаете его использовать, то лучше брать оттуда.
Есть очень логичный вопрос. Нафига? Зачем мне это было нужно, если хорошо поискать, то можно было бы найти готовый вариант, который бы устроил. Полностью согласен с такой точкой зрения. С маленькой поправкой. Используя готовые решения сторонних разработчиков, есть риск нарваться на какой-то баг, который будет не просто исправить. JQuery плагины сейчас пишутся новичками для новичков, многие разработчики стараются запихать в плагин как можно больше функций, чтобы на выходе получить комбайн. В итоге получается монстр на 5 ног, он может все, но отлаживать его удовольствие ниже среднего. Поэтому некоторые несложные вещи, мне нравится писать самому, плагин, который решает одну задачу. Мне будет потом проще понять, что тут происходит, когда я открою код через N-ное время, а если появится новая фича, то её легко добавить.
Стилизация скролл-бара средствами JavaScript
Тема дизайнерских изысков и пожеланий иногда находится за гранью добра и зла. Но от этого никуда не деться, бывает, что они хотят невозможного. Но я считаю, что можно сделать все (если это не противоречит законам физики), весь вопрос в сложности реализации, а соответственно в сроках решения и как итог — стоимость.
В web’е есть ряд вещей, которые не поддаются стилизации в классическом понимании, т.е. нельзя, используя только CSS, изменить их внешний вид. К таким элементам относятся многие теги формы: селекты, чекбоксы, радио-кнопки. Ранее я уже рассказывал, как стилизовать select. Теперь поговорим, как сделать красивый скролл-бар. Откровенно говоря, я ненавижу такие задачи, которые решаются не очевидно или не стандартно, потому что от браузера к браузеру, или даже от версии к версии браузера или ОС, могут вести себя по разному. Хватит лирики, работаем.
Полная эмуляция на js мне не нравится, т.к. это сложно и если js падает, то возможность пролистывания блока пропадает совсем. Где-то давно я почерпнул следующую идею:
Черный прямоугольник — это область просмотра, например, окно браузера.
Зеленый прямоугольник — это то, что должно прокручиваться. Таким образом, ширина прокручиваемого блока шире области просмотра на ширину скролла, примерно, лучше больше, т.к. в разных браузерах, ширина полосы прокрутки разная. Теперь строим из div’ов визуальное представление скролл-бара и вешаем на него нужные обработчики. Теперь можем задать любой мыслимый стиль для лемента, и если js у пользователя отключен, либо в результате ошибки, он упадет, то пролистать блок все равно будет можно.
Технически я представляю как это все делать, и когда-то даже писал полностью всю обработку сам, но в данном случае мне не хотелось изобретать велосипед. Поискав варианты эмуляции скролла, нашел библиотеку baron. Она меня устраивала более чем, за исключением того, что все обертки и блоки эмуляции нужно вставлять в html. Такой вариант мне казался несколько странным, раз есть плагин, то мне хотелось бы совершать минимум действий, в идеале просто натравить плагин на блок, который надо стилизовать и все, пусть он сам добавляет нужную разметку. Исходя из этого душевного посыла было принято решение написать небольшую обертку, которая подготовит блоки и запустит эмуляцию скролла.
Собственно, код обертки такой:
(function ($) { $.fn.wrapFoBaron = function (options) { options = $.extend({ root : '.scroller_wrap', barOnCls: 'baron' }, options); var make = function () { $(document).ready(function () { $(options.root).each(function () { $(this).wrapInner('<div class="scroller__content"></div>'); $(this).wrapInner('<div class="scroller"></div>'); $(this).append('<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>'); }); var params = { root : options.root, scroller: '.scroller', bar : '.scroller__bar', barOnCls: options.barOnCls }; var scroll = baron(params); check_size(); check_size(); function check_size() { $(options.root).each(function () { if ($(this).find('.scroller__content').height() <= $(this).height()) { $(this).find('.scroller__bar-wrapper').hide(); $(this).find('.scroller').addClass('.with-scroll'); } else { $(this).find('.scroller').removeClass('.with-scroll'); } }); } }) }; return make(); }; })(jQuery);
И вызываем вот так
$(document).wrapFoBaron({ root: '.class-name', barOnCls: 'baron' });
Я даже немного попытался соответствовать идеологии БЭМ. Глядя на код сразу бросается в глаза, что функция check_size вызывается 2 раза подряд, зачем так делается и вообще зачем она? А затем, что кастомный скролл-бар добавляется только тогда, когда это реально необходимо, это ответ на второй вопрос, а на первый вопрос ответ такой: После того как было принято решение, что надо добавить скролл-бар, запускается процесс добавления разметки и в этот момент размеры наружных блоков могут (с большой вероятностью они это сделают) изменится, и чтобы все выглядело адекватно, запускаем процесс инициализации дважды.
Чтобы было проще втянутся в процесс понимания, приведу тут пример стилей одного из наших проектов
.scroller { height: 100%; overflow-y: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .scroller::-webkit-scrollbar { width: 0; } .scroller__content { position: relative; padding-right: 30px; overflow: hidden; } .scroller__bar-wrapper { position: absolute; z-index: 2; top: 10px; bottom: 10px; right: 5px; width: 8px; border-radius: 5px; background-color: rgba(255, 255, 255, 0.5); pointer-events: none; -webkit-transition: all 0.2s linear 0s; -moz-transition: all 0.2s linear 0s; -o-transition: all 0.2s linear 0s; transition: all 0.2s linear 0s; opacity: 0; } .scroller__bar { position: absolute; z-index: 1; width: 8px; border-radius: 3px; background-color: rgba(0, 0, 0, 0.5); -webkit-transition: opacity 0.2s linear 0s; -moz-transition: opacity 0.2s linear 0s; -o-transition: opacity 0.2s linear 0s; transition: opacity 0.2s linear 0s; pointer-events: auto; opacity: 0; } .baron:hover .scroller__bar-wrapper { opacity: 1; } .baron:hover .scroller__bar { opacity: 1; }
Реализация, конечно, костыльная, и для мобильных устройств её стоит отключать, но вроде бы везде работает, так что можете пользоваться на здоровье. Кстати, есть небольшой пример вот тут. Пользуйтесь на здоровье =)
С началом нового рабочего года ;)
Не отображаются лайки при использовании ajax
Тем кто понимает проблему и хочет по-быстрому ее решить, советую перейти к части как решить?. Остальным немного лирики.
Практически на всех проектах мы используем стандартный набор социальных кнопок: vk-like, facebook-like, g+, tweet. И код для всего это тоже используется стандартный.
Добавляется в head вот такой код
<!-- G+ --> <script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script> <!-- FB --> <script type="text/javascript" src="http://userapi.com/js/api/openapi.js?34"></script> <!-- VK --> <script type="text/javascript"> VK.init({apiId: your_API_ID, onlyWidgets: true}); </script>
А в место? где надо отобразить социальные кнопки, вставляется примерно такой код:
<div class="b_likes"> <div class="like_itm"> <?php $pageURL = get_permalink(); echo '<iframe src="http://www.facebook.com/plugins/like.php?href='.$pageURL. '&send=false&layout=button_count&width=110&show_faces=true&action=like &colorscheme=light&font&height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:140px; height:21px;" allowTransparency="true"></iframe>'; ?> </div> <div class="like_itm"><a href="http://twitter.com/share" class="twitter-share-button" data-count="horizontal">Tweet</a> <script type="text/javascript" src="http://platform.twitter.com/widgets.js"> </script> </div> <divclass="like_itm"> <g:plusone size="medium"></g:plusone></div> <divclass="like_itm"> <div id="vk_like-<?php the_ID(); ?>"></div> <script type="text/javascript"> VK.Widgets.Like( "vk_like-<?php the_ID(); ?>" , {type: "button"} , <?php the_ID(); ?> ); </script> </div> </div>
Последние веяния интернет технологий говорят нам, что ajax очень круто и для пользователей крайне необходимо. С этим можно согласить или не согласиться, но необратить на эту штуку внимания нельзя. Поскольку sawtech прогрессивная контора, то и нас не миновала чаша сия. И вот мы сделали ajax-подгрузку постов в одном из проектов. Чтобы конечный продукт хорошо продвигался добавили сразу же для каждой записи социальные кнопки и обнаружили, что по какой-то причине не все лайки, загружаемые с помощью axaj хотят отображаться корректно.
Проблема в том, что инициация кнопок происходит скриптом, который отрабатывает в момент создания документа, а подгружаемые элементы вставляются уже после.
А решается не сложнее начальной инициации. Для всех кнопок существуют методы повтороной инициации. Для этого надо добавить небольшое кусочки кода рядом с кодом самой кнопки.
<!-- Для твиттера --> <script type="text/javascript"> !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0], p=/^http:/.test(d.location)?'http':'https'; if(!d.getElementById(id)){js=d.createElement(s); js.id=id;js.src=p+'://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js,fjs);}} (document, 'script', 'twitter-wjs'); twttr.widgets.load(); </script> <!-- Для G+ --> <script type="text/javascript">gapi.plusone.go();</script> <!-- Для FB, если используете XFBML вариант --> <script type="text/javascript">FB.XFBML.parse();</script>
Я использую iframe загрузку fk-like, поэтому у меня все было нормально с fb изначально, а контакт сам шарит и все обрабатывает без дополнительных тычков.
В итоге, лично я получил вот такой код:
<div class="b_likes"> <?php $like_url = urlencode( get_permalink() ); ?> <div class="like_itm"> <?php $pageURL = get_permalink(); echo '<iframe src="http://www.facebook.com/plugins/like.php?href='.$pageURL. '&send=false&layout=button_count&width=110&show_faces=true&action=like &colorscheme=light&font&height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:140px; height:21px;" allowTransparency="true"></iframe>'; ?> </div> <div class="like_itm"> <a href="https://twitter.com/share" class="twitter-share-button" data-url="<?php echo $like_url; ?>" data-text="<?php the_title(); ?> <?php the_permalink(); ?> ">Tweet</a> <script type="text/javascript"> !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0], p=/^http:/.test(d.location)?'http':'https'; if(!d.getElementById(id)){js=d.createElement(s); js.id=id;js.src=p+'://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js,fjs);}} (document, 'script', 'twitter-wjs'); twttr.widgets.load(); </script> </div> <div class="like_itm"> <div class="g-plusone" data-size="medium" data-href="<?php echo $like_url; ?>"></div> <script type="text/javascript">gapi.plusone.go();</script> </div> <div class="like_itm"> <div id="vk_like-<?php the_ID(); ?>"></div> <script type="text/javascript"> VK.Widgets.Like("vk_like-<?php the_ID(); ?>", { type: "button" , pageUrl: '<?php echo $like_url; ?>' }, <?php the_ID(); ?> ); </script> </div> </div>
Теперь все работает корректно. Как я и обещал, ничего сложного.
Полезные куски кода для JavaScript
Поскольку моя основная «специализация» все же верстальщик, то я в основном имею дело с html и javasript. Как было кем-то сказано: «На javascript сначала начинают писать, и только потом начинают его изучать». С этим нельзя не согласиться, по крайней мере на своем примере я это проверил.
Я достаточно ленив и не люблю сложные решения. Поэтому во всех наших проектах используется jquery, да и в стандартном комплекте wordpress, который используется в наших проектах этот фрэймворк идет в комплекте, поэтому я вообще не беспокоюсь о лишних килобайтах.
В прошлый раз я выкладывал коды для WP, теперьм Мне хочется поделиться некоторыми полезными кусочками кода, которые я периодически использую при написании скриптов. И так поехали:
1. Обращение к элементу по его порядковому номеру, отсчет начинается с единицы, нумерация идет слева-направо, сверху- вниз:
$('.carusel_itm:nth-child(' +number_itm + ')').css('opacity','1');
2. Вырезать цифры из строки «stroka»:
$('.carusel_itm:nth-child(' +number_itm + ')').css('opacity','1');
Ну вроде бы тут все понятно, особенно для тех, кто шарит в регулярных выраениях.
3. Получение элемента в наборе, нумерация начинается с нулевого, поэтому добавляем единицу
var index = $(this).index()+1;
4. Поиск подстроки в строке:
str.indexOf('подстрока') + 1
Я использую данное выражение для проверки наличия подстроки в строке. Поясню немного зачем добавляю единицу. Данное выражение по идее предназначено для поиска первого вхождения подстроки в строку, поэтому оно возвращает не булево значение, а номер символа с которого начинается искомая подстрока. Самое интересное, что нумерация символов начинается с нуля, что вполне логично, но не в этом соль, а прикол в том если строка не найдена возвращается -1. В javasript, значение 0 = false, а любое отличное от нуля это true, отсюда и родилась +1. Во-первых подстока может быть в начале и тогда сама функция вернет 0, и.. Ну в общем, все понятно))
Едем далее.
5. Подсчет количества элементов по классу
$('.class').size();
Этот код я достаточно часто использую в галереях, когда необходимо просчитать ширину контейнера, в котором лежат все слайды. Конечно, в большинстве реализация, которые я видел тупо задают ширину 10000em и не парятся, но мне это кажется не особо эстетичным.
6. Проход по всем элементам набора:
$('.class').each(function (i) { });
Если нужно у что-то сделать не со всеми элементами набора, а лишь с несколькими, выборочно, то я пользуюсь именно этим набором.
7. Данный код выведет текущий url страницы. Можно запихать в переменную и обрабатывать.
alert(document.location.href);
8. Достать первый из дочерних элементов и дальше можно делать все что душе угодно:
$(this).children(':first').any_method()
На этом я хочу пока закончить. Переодически буду добавлять еще что-нибудь, память у меня с прошлого раза лучше не стала)) Поэтому пусть здесь все это лежит. Может и еще кому пригодится. В комментах можете добавлять свои часто используемые коды.