Как отказаться от jquery в современном фронтенде: опыт команды github

Введение в jQuery

С базовым пониманием JavaScript и некоторых его основ, настало время взглянуть на jQuery. jQuery является библиотекой JavaScript с открытым исходным кодом, написанной Джоном Ресигом, которая упрощает взаимодействие между HTML, CSS и JavaScript. С 2006 года, когда был выпущен jQuery, он приобрёл внушительное число пользователей, будучи используемый сайтами и компаниями, большими и малыми.

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

Начало работы с jQuery

Первый шаг к использованию jQuery — это установить на него ссылку в HTML-документе. Как упоминалось ранее про JavaScript, это делается с помощью элемента <script>, расположенным непосредственно перед закрывающим тегом </body>. Поскольку jQuery является самостоятельной библиотекой, лучше держать её отдельно от остального JavaScript который будет написан.

Для ссылки на jQuery есть несколько вариантов, в частности, следует ли использовать минимизированную или несжатую версию, а также воспользоваться ли сетью доставки контента (content delivery network, CDN), такую как Google hosted libraries. Если код пишется для живого, производственного окружения, то рекомендуется использовать минимизированную версию для сокращения времени загрузки. Кроме того, применение CDN, вроде Google, также помогает со временем загрузки и потенциально приносит пользу с кэшированием.

В примере кода выше, обратите внимание на второй элемент

Кроме того, этот файл специально размещён после файла jQuery, так что он может ссылаться на уже определённые функции jQuery.

Где протокол http?

Вы, возможно, заметили, что в примере выше нет протокола http в ссылке на Google CDN. http опущен преднамеренно, чтобы позволить оба соединения — http и https. При работе локально, без использования веб-сервера, протокол http необходимо включить, чтобы предотвратить попытки найти файл на локальном системном диске.

Объект jQuery

jQuery поставляется с собственным объектом, знаком доллара ($), также известным как jQuery. Объект $ сделан специально для выбора элемента, а затем возвращает этот элемент для выполнения над ним действий. Эти выборки и действия должны быть написаны в новом файле, на который ведёт ссылка за пределами актуальной библиотеки jQuery.

Готовность документа

Перед запуском любого jQuery для обхода и манипуляцией страницы, лучше подождать пока DOM не закончит её загрузку. К счастью, в jQuery есть готовое событие .ready(), которое может быть вызвано, когда HTML-документ готов к изменению. Размещая весь наш код внутри этой функции jQuery мы можем гарантировать, что код не будет выполняться, пока страница загружается и DOM не готов.

Создание и настройка базы данных

Для того чтобы перейти к создании базы данных, запускаем сервер и открываем СУБД PHPMyAdmin.

Рис 5. Открываем PhpMyAdmin в Open Server

Нажимаем на ссылку ‘New’.

Рис 6. Создание новой базы данных

Указываем название базы ‘live_search’ и выбираем кодировку ‘utf8mb4_unicode_ci’.

Рис 7. Создание новой базы данных в PhpMyAdmin

Далее, создаем таблицу ‘users’, со столбцами ‘id’ и ‘name’ и наполняем ее данными.

Для этого, в PhpMyAdmin, переходим на вкладке SQL и запускам следующий запрос:

        CREATE TABLE users (

        id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,

        Name VARCHAR(30) NOT NULL

        );

        INSERT INTO `users` VALUES

        (1, 'Василий'),

        (2, 'Татьяна'),

        (3, 'Наталия'),

        (4, 'Иван'),

        (5, 'Андрей'); 

    

Рис 8. Запуск SQL кода для создания таблицы users

И видим, что данные добавились.

Зачем jQuery был нужен раньше?

jQuery 1.2.1 вошёл в число зависимостей GitHub в конце 2007 года. Это произошло за год до того, как Google выпустил первую версию браузера Chrome. На тот момент не было общепринятого способа обращаться к элементам DOM с помощью CSS-селектора, не было стандартного способа добавить анимацию к стилю элемента, а интерфейс XMLHttpRequest, предложенный Internet Explorer, как и многие API, был плохо совместим с браузерами.

С jQuery стало гораздо проще управлять DOM, создавать анимации и делать AJAX-запросы. У веб-разработчиков появилась возможность создавать более современные, динамические сайты, которые выделялись среди остальных. Самое главное, все функции JavaScript, проверенные в одном браузере с помощью jQuery, как правило, работали и в других браузерах. На заре GitHub, когда большинство его функций только обретали форму, появление jQuery позволило небольшой команде разработчиков быстро создавать прототипы и представлять новые функции без необходимости подстраивать их отдельно под каждый браузер.

Простой интерфейс jQuery также послужил основой для создания библиотек, которые в будущем стали компонентами остальной части фронтенда GitHub.com: pjax и facebox.

The PHP

/* draws a calendar */
function draw_calendar($month,$year){

	/* draw table */
	$calendar = '<table cellpadding="0" cellspacing="0" class="calendar">';

	/* table headings */
	$headings = array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
	$calendar.= '<tr class="calendar-row"><td class="calendar-day-head">'.implode('</td><td class="calendar-day-head">',$headings).'</td></tr>';

	/* days and weeks vars now ... */
	$running_day = date('w',mktime(0,0,0,$month,1,$year));
	$days_in_month = date('t',mktime(0,0,0,$month,1,$year));
	$days_in_this_week = 1;
	$day_counter = 0;
	$dates_array = array();

	/* row for week one */
	$calendar.= '<tr class="calendar-row">';

	/* print "blank" days until the first of the current week */
	for($x = 0; $x < $running_day; $x++):
		$calendar.= '<td class="calendar-day-np"> </td>';
		$days_in_this_week++;
	endfor;

	/* keep going with days.... */
	for($list_day = 1; $list_day <= $days_in_month; $list_day++):
		$calendar.= '<td class="calendar-day">';
			/* add in the day number */
			$calendar.= '<div class="day-number">'.$list_day.'</div>';

			/** QUERY THE DATABASE FOR AN ENTRY FOR THIS DAY !!  IF MATCHES FOUND, PRINT THEM !! **/
			$calendar.= str_repeat('<p> </p>',2);
			
		$calendar.= '</td>';
		if($running_day == 6):
			$calendar.= '</tr>';
			if(($day_counter+1) != $days_in_month):
				$calendar.= '<tr class="calendar-row">';
			endif;
			$running_day = -1;
			$days_in_this_week = 0;
		endif;
		$days_in_this_week++; $running_day++; $day_counter++;
	endfor;

	/* finish the rest of the days in the week */
	if($days_in_this_week < 8):
		for($x = 1; $x <= (8 - $days_in_this_week); $x++):
			$calendar.= '<td class="calendar-day-np"> </td>';
		endfor;
	endif;

	/* final row */
	$calendar.= '</tr>';

	/* end the table */
	$calendar.= '</table>';
	
	/* all done, return result */
	return $calendar;
}

/* sample usages */
echo '<h2>July 2009</h2>';
echo draw_calendar(7,2009);

echo '<h2>August 2009</h2>';
echo draw_calendar(8,2009);

The PHP is largely based upon one function that only requires the month and year of the calendar you’d like. It’s a sizable function but obviously worth its weight in gold. Also note that I’ve identified a spot within the calendar where you should query the database to see if there are any events for that day. I use tables because they nicely stretch when one day in the week is longer than others. Working with absolute positioning and DIVs in the calendar is far too much hassle for a simple calendar.

I look forward to seeing what you can do with the calendar!

Макеты

Wookmark jQuery Plugin — jQuery плагин для создания динамичных мульти-колонок.

Это JQuery-плагин позволяющий быстро и просто организовать динамический макет блоков разного размера практически без потери места.

Isotope – изысканный jQuery-плагин для анимированной сортировки данных в блоках. Можно скрывать и показывать данные по признакам, сортировать, располагать на странице.

Легкий, простой в использовании  JQuery плагин для работы с видео.

Gridster позволяет построить многоколоночный шаблон с возможностью перемещения ячеек.

Freetile позволяет создавать элементы любого размера, как для обычного, так и для адаптивного дизайна.

Положительные и негативные стороны локализации JavaScript

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

Однако, присутствуют и негативные стороны использования JavaScript как средства для локализации вашего сайта, такие как:

  1. По умолчанию, разработчики l20n предлагают оставлять теги для подстановки локализованных строк пустыми, что чревато понижением в поисковой выдаче, т.к. робот не видит текста на вашем сайте, т.к. поисковый робот парсит вашу страницу без JavaScript! Этот огромный недостаток можно с легкостью исправить, просто заполнив теги предпочтительным, преобладающим языком на вашем сайте, как это сделано в примере.
  2. Поисковая система не сможет проиндексировать разные версии сайта, и будет видеть только одну версию — версию, которую вы указали в предыдущем пункте. Если реализовать локализацию на php, то можно создать для поисковой системы отдельные каталоги с разными локализациями.
  3. На мой взгляд такая реализация мультиязычности не подойдет для больших порталов и крупных сайтов, т.к. она достаточно проста и не функциональна. Необходимо будет прописывать текст для каждой странички отдельно, т.е. такая конструкция позволительна исключительно для сайтов, состоящих из нескольких страниц.

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

Эффекты

Наряду с событиями, jQuery также предлагает несколько настраиваемых эффектов. Эти эффекты приходят через разные методы, включая методы событий для отображения и скрытия содержимого, постепенного исчезновения и появления содержимого или скольжения содержимого вверх и вниз. Все они готовы к использованию через методы и могут быть настроены лучше по своему усмотрению.

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

Анимация в CSS и jQuery

Пользовательские анимации различных свойств CSS могут быть выполнены в jQuery, хотя это несколько менее актуально, так как CSS теперь может обрабатывать анимацию самостоятельно. Анимация в CSS предлагает более высокую производительность с точки зрения обработки браузером и являются предпочтительной, насколько это возможно. Эффекты анимации jQuery с помощью Modernizr — это идеальное запасное решение для любого браузера, который не поддерживает анимацию через CSS.

Длительность эффекта

Используя метод .show() в качестве примера, первый параметр опционально доступный для передачи — это длительность, которая может быть задана с помощью ключевого слова или значений миллисекунд. Ключевое слово slow по умолчанию равно 600 миллисекундам, в то время как ключевое слово fast по умолчанию равно 200 миллисекундам. Использовать ключевое слово в качестве значения хорошо, но миллисекунды также могут быть переданы непосредственно. Ключевое слово должно указываться в кавычках, тогда как для миллисекунд этого не требуется.

Плавность эффекта

Кроме длительности, также может быть задана плавность или скорость, с которой анимация прогрессирует в разное время в течение эффекта. По умолчанию jQuery содержит два ключевых слова для плавности — значение по умолчанию swing с дополнительным значением linear. Значение по умолчанию swing запускает анимацию в медленном темпе, набирая скорость во время анимации, а затем снова замедляясь до завершения. Значение linear запускает анимацию в одном постоянном темпе в течение всей продолжительности анимации.

jQuery UI

Два значения плавности, которые поставляются с jQuery можно расширить за счёт разных плагинов, которые могут предложить дополнительные значения. Одним из наиболее популярных плагинов является набор jQuery UI.

Кроме новых значений плавности jQuery UI также предлагает горсть дополнительных взаимодействий, эффектов, виджетов и другие полезные ресурсы, на которые стоит взглянуть.

Функция обратного вызова

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

Синтаксис эффекта

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

Демонстрация эффектов

Взяв те же события из демонстрации сверху, метод .remove() теперь используется как часть функции обратного вызова в методе .fadeOut(). Использование метода .fadeOut() позволяет сделать так, чтобы аварийное сообщение затухало постепенно, а не исчезало быстро, а затем удалить его из DOM после завершения анимации.

JavaScript

Эффекты скольжения

  • .slideDown()
  • .slideToggle()
  • .slideUp()

JavaScript

JavaScript

jQuery AJAX Call to Get Previous/Next Month Calendar

On clicking the forward and the backward arrow on the calendar header, an AJAX request is sent to a PHP file calendar-ajax.php by sending the previous or next month/year data.

The PHP file returns calendar HTML based on the month and year passed as an AJAX response. In this example, we can input the year by changing the content of the editable year element to get the calendar HTML via AJAX.

<script>
$(document).ready(function(){
	$(document).on("click", '.prev', function(event) { 
		var month =  $(this).data("prev-month");
		var year =  $(this).data("prev-year");
		getCalendar(month,year);
	});
	$(document).on("click", '.next', function(event) { 
		var month =  $(this).data("next-month");
		var year =  $(this).data("next-year");
		getCalendar(month,year);
	});
	$(document).on("blur", '#currentYear', function(event) { 
		var month =  $('#currentMonth').text();
		var year = $('#currentYear').text();
		getCalendar(month,year);
	});
});
function getCalendar(month,year){
	var url = "calendar-ajax.php";
	$.ajax({
		url: "calendar-ajax.php",
		type: "POST",
		data:'month='+month+'&year='+year,
		success: function(response){
			$("#calendar-html-output").html(response);	
		},
		error: function(){} 
	});
	
}
</script>

View DemoDownload

Другие

Быстрый, легкий плагин, который следит за временными отметками и автоматически обновляет их,

jQuery Validation Plugin умеет проверять форму на корректность заполенных данных.

Довольно надежная загрузка файлов.

Плагин позволит отображать результаты с фотографиями с Instagram по заданному тегу или географическим координатам.

Поможет легко добавить все виды различных теней  к HTML-элементам.

MotionCAPTCHA представляет собой jQuery-плагин, который предлагает вам различные типы картинок-кэпчей, включая даже систему, когда человек должен нарисовать увиденную фигуру в специальном поле.

CSS3 преобразования и переходы для JQuery. Поворот, перспектива, наклон, масштабирование и многое другое.

Перевод — Дежурка

Смотрите также:

Как прочитать данные в формате JSON с помощью jQuery

4.1 Показать товары на странице при загрузке

В папке products откройте файл read-products.js

Следующий код вызовет метод showProducts() при первой загрузке веб-страницы.

Функция showProducts() покажет список продуктов в виде HTML-таблицы. Поместите следующий код в файл read-products.js.

4.2 Показать товары при клике на кнопку

Следующий код вызовет метод showProducts() при нажатии кнопки с классом кнопки read-products-button.

Кнопку можно найти в HTML-шаблонах «Создать товар и «Обновить товар. Мы увидим это в следующих разделах.

Поместите следующий код под showProducts(); предыдущего раздела.

4.3 Создание функции showProducts()

Теперь мы создадим метод showProducts(). Замените комментарий // здесь будет метод showProducts() в файле read-products.js следующим кодом.

4.4 Получение списка товаров

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

4.5 Создание кнопки «Добавить товар»

Мы должны добавить кнопку «Создать продукт» в списке товаров. Мы заставим эту кнопку работать позже в этом руководстве.

Поместите следующий код после открывающей фигурной скобки предыдущего раздела.

4.6 Создание HTML-таблицы

Мы должны начать строить HTML-таблицу, в которой появится список продуктов.

Следующий код создаст HTML-таблицу с ее заголовком. Разместите его после кода предыдущего раздела.

4.7 Построение строки таблицы для каждой записи

Мы пройдемся по каждой записи, возвращаемой API. Для каждой записи мы создадим строку таблицы.

Помимо данных о товаре, строка таблицы также будет иметь кнопки «Действие». К ним относятся кнопки «Просмотр», «Редактировать» и «Удалить».

Замените комментарий // здесь будут строки следующим кодом.

4.8 Вставка контента на страницу

Мы должны сделать так, чтобы HTML-таблица появилась на нашей веб-странице. Мы сделаем это, выводя таблицу в div page-content.

Поместите следующий код после закрывающего тега table

4.9 Изменение заголовка страницы

Следующий код изменит «заголовок» на веб-странице и «заголовок» на вкладке браузера.

Поместите следующий код после кода предыдущего раздела.

PHP Events Calendar Control

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

Достоинства:

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

PHP Events Calendar Control существует уже давно и хорошо себя показал.

Преимущества Ajax jQuery

Несмотря на то, что AJAX — самостоятельная технология, jQuery значительно упрощает работу над кодом для асинхронного взаимодействия с сервером.

С помощью jQuery очень легко писать код для отправки AJAX-запросов. На «чистом» JavaScript приходилось писать большие фрагменты кода.

Ajax jQuery совместим с различными браузерами. Ранее нужно было писать кастомный код, учитывая разную реализацию поддержки AJAX в различных браузерах. Большинство браузеров поддерживали объект , а в Internet Explorer запросы отправлялись через , причем в зависимости от версии IE это мог быть объект Msxml2.XMLHTTP.6.0, Msxml2.XMLHTTP.5.0, Msxml2.XMLHTTP.4.0, Msxml2.XMLHTTP.3.0, Msxml2.XMLHTTP или Microsoft.XMLHTTP.

В Ajax jQuery реализованы удобные методы и события. Они позволяют контролировать взаимодействие с сервером, загружать данные в различных форматах (текст, JSON, XML, HTML и сценарии). Создавать такой код вручную — сложная и утомительная работа даже для опытных программистов.

Как сделать пагинацию данных с помощью jQuery AJAX?

Изменение URL-адреса JSON

Чтобы сделать нумерацию страниц, нам нужно изменить URL-адрес JSON. Содержимое этих новых данных JSON будет включать узел «пагинации». Похоже на следующее.

Поэтому мы изменим URL JSON с:

На:

Это означает, что мы должны что-то изменить в нашем коде. Смотрите изменения в следующем разделе.

10.3 Добавим пагинацию HTML

Откройте app/products/products.js, найдите закрывающий тег table и добавьте после него следующий код.

Вам так же необходимо изменить URL домашней страницы из предыдущего руководства в соответствии с вашим URL.

Файл находится ваша_корневая_папка/api/config/core.php.

В моём случае я изменяю

На:

PHP Class for Getting Calendar HTML

The following code shows the class.calendar.php file which contains functions to compute calendar data and return HTML. On loading page, I create an instance for this class to get the current month calendar HTML by using this class function.

Further, I used jQuery script to send AJAX request to get the calendar data while navigating between previous and next months.

<?php
class PHPCalendar {
	private $weekDayName = array ("MON","TUE","WED","THU","FRI","SAT","SUN");
	private $currentDay = 0;
	private $currentMonth = 0;
	private $currentYear = 0;
	private $currentMonthStart = null;
	private $currentMonthDaysLength = null;	
	
	function __construct() {
		$this->currentYear = date ( "Y", time () );
		$this->currentMonth = date ( "m", time () );
		
		if (! empty ( $_POST  )) {
			$this->currentYear = $_POST ;
		}
		if (! empty ( $_POST  )) {
			$this->currentMonth = $_POST ;
		}
		$this->currentMonthStart = $this->currentYear . '-' . $this->currentMonth . '-01';
		$this->currentMonthDaysLength = date ( 't', strtotime ( $this->currentMonthStart ) );
	}
	
	function getCalendarHTML() {
		$calendarHTML = '<div id="calendar-outer">'; 
		$calendarHTML .= '<div class="calendar-nav">' . $this->getCalendarNavigation() . '</div>'; 
		$calendarHTML .= '<ul class="week-name-title">' . $this->getWeekDayName () . '</ul>';
		$calendarHTML .= '<ul class="week-day-cell">' . $this->getWeekDays () . '</ul>';		
		$calendarHTML .= '</div>';
		return $calendarHTML;
	}
	
	function getCalendarNavigation() {
		$prevMonthYear = date ( 'm,Y', strtotime ( $this->currentMonthStart. ' -1 Month'  ) );
		$prevMonthYearArray = explode(",",$prevMonthYear);
		
		$nextMonthYear = date ( 'm,Y', strtotime ( $this->currentMonthStart . ' +1 Month'  ) );
		$nextMonthYearArray = explode(",",$nextMonthYear);
		
		$navigationHTML = '<div class="prev" data-prev-month="' . $prevMonthYearArray . '" data-prev-year = "' . $prevMonthYearArray. '"><</div>'; 
		$navigationHTML .= '<span id="currentMonth">' . date ( 'M ', strtotime ( $this->currentMonthStart ) ) . '</span>';
		$navigationHTML .= '<span contenteditable="true" id="currentYear">'.	date ( 'Y', strtotime ( $this->currentMonthStart ) ) . '</span>';
		$navigationHTML .= '<div class="next" data-next-month="' . $nextMonthYearArray . '" data-next-year = "' . $nextMonthYearArray. '">></div>';
		return $navigationHTML;
	}
	
	function getWeekDayName() {
		$WeekDayName= '';		
		foreach ( $this->weekDayName as $dayname ) {			
			$WeekDayName.= '<li>' . $dayname . '</li>';
		}		
		return $WeekDayName;
	}
	
	function getWeekDays() {
		$weekLength = $this->getWeekLengthByMonth ();
		$firstDayOfTheWeek = date ( 'N', strtotime ( $this->currentMonthStart ) );
		$weekDays = "";
		for($i = 0; $i < $weekLength; $i ++) {
			for($j = 1; $j <= 7; $j ++) {
				$cellIndex = $i * 7 + $j;
				$cellValue = null;
				if ($cellIndex == $firstDayOfTheWeek) {
					$this->currentDay = 1;
				}
				if (! empty ( $this->currentDay ) && $this->currentDay <= $this->currentMonthDaysLength) {
					$cellValue = $this->currentDay;
					$this->currentDay ++;
				}
				$weekDays .= '<li>' . $cellValue . '</li>';
			}
		}
		return $weekDays;
	}
	
	function getWeekLengthByMonth() {
		$weekLength =  intval ( $this->currentMonthDaysLength / 7 );	
		if($this->currentMonthDaysLength % 7 > 0) {
			$weekLength++;
		}
		$monthStartDay= date ( 'N', strtotime ( $this->currentMonthStart) );		
		$monthEndingDay= date ( 'N', strtotime ( $this->currentYear . '-' . $this->currentMonth . '-' . $this->currentMonthDaysLength) );
		if ($monthEndingDay < $monthStartDay) {			
			$weekLength++;
		}
		
		return $weekLength;
	}
}
?>

Плагины только для отображения данных

Часть плагинов занимается лишь тем, что как-то по-особенному отображает уже существующие данные. В этом случае логика использования jQuery-плагина в связке с Vue достаточно проста.

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

// <template><div id="plugin-container"></div>// <script>import $ from 'jquery';  // подключаем jQueryimport 'whateverplugin'; // подключаем сам плагин...export default {  data () {    return {      options:     };  },  ...  mounted () {    $('#plugin-container').whateverplugin({       ...       // передаем в плагин данные из свойства Vue       options: this.options       ...    });  }}

Готово! Но что если нужна обратная связь от плагина?

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: