Использование exports/require в CommonJS и export/import в EcmaScript 2015

Начиная с ECMAScript 2015 (ES6), JavaScript имеет концепцию модулей. Модули выполняются в своей собственной области, а не в глобальной области.

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

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

Модули декларативные, а отношения между модулями определяются в терминах импорта и экспорта на уровне файлов с кодом, которые именуются модулями.

Как это используют в CommonJS? Модули импортируют друг друга, используя загрузчик модулей. Во время выполнения загрузчик модуля отвечает за поиск и выполнение всех зависимостей модуля перед его выполнением. Хорошо известные загрузчики модулей, используемые в JavaScript — это загрузчик Node.js для модулей CommonJS и загрузчик RequireJS для модулей AMD в веб-приложениях.

Как это используют в ECMAScript 2015? Любой файл, содержащий импорт или экспорт верхнего уровня, считается модулем. И наоборот, файл без каких-либо объявлений импорта или экспорта верхнего уровня обрабатывается как сценарий, содержимое которого доступно в глобальной области.

Различие в использовании модулей CommonJS и ES6

Что предпочтительнее использовать?

Вопрос, относительно использовании модулей ES6 против CommonJS не однозначен. Первые универсальны, последние в основном используются в Node.js, но также поддерживаются сборщиками Webpack и другими компоновщиками, поэтому в приложениях на подобии React, Angular или Vue могут потенциально использоваться как import, так и require.

Начало работы с модулями Node.js: require, exports, imports и не только

Модули являются важной концепцией для понимания проектов Node.js. В этом посте мы рассмотрим модули Node: requireexports и будущее import .

Модули Node.js позволяют писать повторно используемый код. Вы можете вкладывать их друг в друга. Используя Node Package Manager (NPM) вы можете опубликовать свои модули и сделать их доступными для сообщества. Кроме того, NPM позволяет повторно использовать модули, созданные другими разработчиками.

Мы используем Node 12.x для примеров и синтаксис ES6 +. Тем не менее, концепции действительны для любой версии.

В этом разделе мы рассмотрим, как создавать модули Node и каждый из его компонентов:

  • Require
  • Exports
  • Module (module.exports vs. export)
  • Import

require — подключение модулей в Node.js

require используются для подключения модулей. Это позволяет вам включать модули в ваши программы. Вы можете добавить встроенные базовые модули Node.js, модули, написанные сообществом разработчиков из папки node_modules и локальные модули.

Допустим, мы хотим прочитать файл из файловой системы. Node.js имеет для этого встроенный модуль под названием «fs»:

const fs = require('fs');

fs.readFile('./file.txt', 'utf-8', (err, data) => {
  if(err) { throw err; }
  console.log('data: ', data);
});

Как видите, мы импортировали модуль «fs» в наш код. Это позволяет нам использовать любую прикрепленную к нему функцию, например «readFile» и многие другие.

Функция require будет искать файлы в следующем порядке:

  1. Built-in. Встроенные в Node.js модули (такие как fs)
  2. NPM Modules. Устанавливаемые модули из репозитория npm, их можно посмотреть в папке node_modules.
  3. Local Modules. Локальные модули, которых надо подгружать из вашего локального места через обращение к пути .// or ../. Модули соответствуют расширениям: *.js*.json*.mjs*.cjs*.wasm и *.node.

Давайте теперь объясним каждый чуть подробнее

1. Встроенные модули Node.js

Когда вы устанавливаете Node.js, он поставляется со многими встроенными модулями. Node.js поставляется с батарейками в комплекте :).

Некоторые из наиболее используемых основных модулей:

  • fs: позволяет манипулировать (создавать / читать / писать) файлами и каталогами .
  • path: утилиты для работы с файлами и каталогами путей.
  • http: создавать HTTP-серверы и клиенты для веб-разработки .
  • url: утилиты для разбора URL и извлечения из него элементов .

Эти модули вам не нужно устанавливать, вы можете импортировать их и использовать в своих программах из коробки.

2. Модули из репозитория NPM

Модули NPM — это сторонние модули, которые вы можете использовать после их установки. Назвать несколько:

  • lodash: коллекция служебных функций для манипулирования массивами, объектами и строками.
  • request: HTTP-клиент проще в использовании, чем встроенный модуль  http.
  • express: HTTP сервер для создания сайтов и API. Опять же, проще в использовании, чем встроенный модуль http.

Это вы должны сначала установить их, вот так:

$ npm install express

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

const express = require('express');
3. Собственные модули

Если вы не можете найти встроенную или стороннюю библиотеку, которая делает то, что вы хотите, вам придется разрабатывать ее самостоятельно. В следующих разделах вы узнаете, как это сделать с помощью экспорта exports.

exports — экспорт данных из модуля Node.js

Ключевое слово export дает вам возможность «экспортировать» ваши объекты и методы. Давайте сделаем пример:

const PI = 3.14159265359;

exports.area = radius => (radius ** 2) * PI;
exports.circumference = radius => 2 * radius * PI;

В приведенном ниже коде мы экспортируем функции area и circumference. Мы определили константу PI, но она доступна только внутри модуля. Только элементы, связанные с exports, доступны за пределами модуля.

Итак, мы можем использовать его, используя require в другом файле, как показано ниже:

const circle = require('./circle');

const r = 3;
console.log(`Circle with radius ${r} has
  area: ${circle.area(r)};
  circumference: ${circle.circumference(r)}`);

Заметил, что на этот раз мы добавляем имя модуля к ./. Это означает, что модуль является локальным файлом.

Обертка модуля в Node.js

Вы можете рассматривать каждый модуль Node.js как отдельную функцию, подобную следующей:

(function (exports, require, module, __filename, __dirname) {
  module.exports = exports = {};

  // Your module code ...

});

Мы уже описывали exports и require. Обратите внимание на связь между module.exports и exports. Они указывают на одну и ту же ссылку. Но если вы назначите что-то напрямую для exports, вы прервете его ссылку на module.exports — подробнее об этом в следующем разделе.

Для нашего удобства определены __filename и __dirname. Они предоставляют полный путь к текущему файлу и каталогу. Последний исключает имя файла и печатает путь к каталогу.

Например, для нашего модуля ./circle.js это будет примерно так:

  • __filename : /User/websofter/code/circle.js
  • __dirname : /User/websofter/code

Хорошо, мы рассмотрели exports, require, __filename и __dirname. Единственное, что мы не рассмотрели, это module. Давай сделаем это!

Что использовать module.exports vs. exports ?

Модуль module не является глобальным и является локальным для каждого модуля. Он содержит метаданные о модуле, такие как id, exports, parent, children и так далее.

exports — это псевдоним module.exports. Следовательно, все, что вы назначаете для exports, также доступно в module.exports. Однако, если вы назначите что-то напрямую для экспорта, вы потеряете ярлык для module.exports. Например.

class Cat {
  makeSound() {
    return `${this.constructor.name}: Meowww`;
  }
}

// exports = Cat; // It will not work with `new Cat();`
// exports.Cat = Cat; // It will require `new Cat.Cat();` to work (yuck!)
module.exports = Cat;

Попробуйте следующий случай с exports, а затем с module.exports.

const Cat = require('./cat');

const cat = new Cat();
console.log(cat.makeSound());

Подводя итог, когда использовать module.exports против exports:

Используйте exports для:

  • Экспорт именованной функции. например exports.area, exports.circumference.

Используйте module.exports для:

  • если вы хотите экспортировать объект, класс, функцию на корневом уровне (например, module.exports = Cat)
  • Если вы предпочитаете возвращать один объект, который предоставляет несколько назначений. например module.exports = {area, circumference};

imports — новые возможности импорта модулей

Начиная с версии 8.5.0+, Node.js нативно поддерживает модули ES с флагом функции и новым расширением файла * .mjs.

Например, наш предыдущий circle.js можно переписать как circle.mjs следующим образом:

const PI = 3.14159265359;

export function area(radius) {
  return (radius ** 2) * PI;
}

export function circumference(radius) {
  return 2 * radius * PI;
}

Затем мы можем использовать импорт:

import { area, circumference } from './circle.mjs';

const r = 3;

console.log(`Circle with radius ${r} has
  area: ${area(r)};
  circunference: ${circumference(r)}`);

И, наконец, вы можете запустить его, используя флаг функции экспериментального модуля:

$ node --experimental-modules main.mjs

Если вам не нравятся экспериментальные модули, другой альтернативой является использование транспилятора. Это преобразует современный JavaScript в более старые версии для вас. Хорошими вариантами являются TypeScriptBabel и Rollup.

Устранение неполадок при использовании imports и require

Экспериментальный Флаг

Если вы не используете экспериментальный флаговый узел --experimental-modules и пытаетесь использовать import, вы получите ошибку, подобную этой:

internal/modules/cjs/loader.js:819
  throw new ERR_REQUIRE_ESM(filename);
  ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: bla bla blah
Расширения файлов .mjs vs .js (или .cjs)

Если у вас есть код модуля написан в файле с расширением *.mjs, то вы не можете использовать require, он выдаст ошибку (ReferenceError: require не определен). .mjs предназначен для импорта через imports в соответствии ECMAScript, а .js — для регулярного использования require.

Однако с помощью *.mjs вы можете загружать оба вида модулей!

import { area, circumference } from './circle.mjs';
import Cat from './cat.js';

const r = 3;
console.log(`Circle with radius ${r} has
  area: ${area(r)};
  circumference: ${circumference(r)}`);

const cat = new Cat();
console.log(cat.makeSound());

Обратите внимание, что cat.js использует модули commonJS.

Резюме

Мы узнали о том, как создавать модули Node.js, и использовали этот опыт в нашем коде. Модули позволяют легко повторно использовать код. Они обеспечивают функциональность, которая изолирована от других модулей. Функция require используется для загрузки модулей. exports и module.exports позволяют нам определить, какие части нашего кода мы хотим представить. Мы также исследовали разницу между module.exports и exports. Наконец, мы быстро выбрали, что будет с модулями, использующими imports.

Модули Node.js и ключевое слово require в JavaScript

Многие новички, иногда, приходят в конфуз, связанный с тем, что на серверной стороне в Node.js используются включение отдельных скриптов и модулей через ключевое слово require, хотя, если глянуть в стандарт клиентского языка JavaScript, то там он не используется.

Модульная система Node.js

Node.js обрабатывает каждый файл JavaScript как отдельный модуль. Но этот способ подключения не используется на клиентской стороне в целях безопасности

Например, если у вас есть файл, содержащий некоторый код, и этот файл называется xyz.js, то этот файл рассматривается как модуль в Node, и вы можете сказать, что создали модуль с именем xyz.

Node.js абстрагируется от конкретного расширения, потому что есть и другие языки, к примеру TypeScript, который бы потребовал другого именования

Давайте возьмем пример, чтобы понять это лучше.

У вас есть файл с именем circle.js, который состоит из логики для вычисления площади и окружности круга заданного радиуса, как показано ниже:

// constant
const PI = 3.14; 

/**
 * Function to calculate area of a circle with given radius r
 * @param {number} r
 * @returns {number} area of circle 
 */
const calculateArea = r => PI * r * r;

/**
 * Function to calculate circumference of a circle with given radius r
 * @param {number} r
 * @returns {number} circumference of circle 
 */
const calculateCircumference = r => 2 * PI * r;

Вы можете назвать файл circle.js модулем с именем circle.

Вам может быть интересно, почему нужно иметь несколько модулей? Вы могли бы просто написать весь код в одном модуле. Что ж, очень важно написать модульный код. Под модульным я хочу сказать, что ваш код должен быть независимым и должен быть слабо связанным. Представьте, что есть большое приложение, и весь ваш код написан в одном месте, в одном файле. Слишком грязно, верно?

Как работает код, написанный внутри модуля?

Перед выполнением кода, написанного внутри модуля, Node берет весь код и заключает его в оболочку функции. Синтаксис этой функции-обёртки:

Весь код, который вы пишете в модуле, находится в оболочке функции!

Функциональная оболочка для модуля circle будет выглядеть так, как показано ниже:

(function(exports, require, module, __filename, __dirname) {
  // all the code written inside the 'circle' module will breathe inside this function wrapper
  
  // constant
  const PI = 3.14; 

  /**
   * Function to calculate area of a circle with given radius r
   * @param {number} r
   * @returns {number} area of circle 
   */
  const calculateArea = r => PI * r * r;

  /**
   * Function to calculate circumference of a circle with given radius r
   * @param {number} r
   * @returns {number} circumference of circle 
   */
  const calculateCircumference = r => 2 * PI * r;
});

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

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

Это самое значительное преимущество наличия модулей в Node.js. Даже если вы определяете глобальную переменную в модуле, используя ключевые слова var, let или const, переменные ограничиваются локальной областью модуля, а не глобально. Это происходит потому, что каждый модуль имеет свою собственную оболочку функции, а код, написанный внутри одной функции, является локальным для этой функции и недоступен вне этой функции.

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

Представьте, что есть два модуля — A и B. Код, написанный внутри модуля A, заключен в оболочку функции, соответствующую модулю A. Подобное происходит с кодом, написанным внутри модуля B. Потому что код, относящийся к обоим модулям заключен в различные функции, эти функции не смогут получить доступ к коду друг друга. (Помните, что каждая функция в JavaScript имеет свою локальную область видимости?) По этой причине модуль A не может получить доступ к коду, написанному внутри модуля B, и наоборот.

Пять параметров — export, require, module, __filename, __dirname доступны внутри каждого модуля в Node.

Хотя эти параметры являются глобальными для кода в модуле, они являются локальными для модуля (из-за функции-оболочки, как описано выше). Эти параметры предоставляют ценную информацию, связанную с модулем.

Давайте вернемся к модулю circle, который вы рассматривали ранее. В этом модуле определены три конструкции: константа-переменная PI, функция с именем CalculaArea и другая функция с именем CalculateCircumference. Важно помнить, что все эти конструкции по умолчанию являются частными для модуля circle. Это означает, что вы не можете использовать эти конструкции в любом другом модуле, если это не указано явно.

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

Параметр module

Параметр module (скорее ключевое слово в модуле в Node) относится к объекту, представляющему текущий модуль. exports— это ключевое слово объекта модуля, соответствующее значение которого является объектом.

Значением по умолчанию для модуля module.exports является {} (пустой объект). Вы можете проверить это, зарегистрировав значение ключевого слова module внутри любого модуля. Давайте проверим, каково значение параметра module внутри модуля circle


// constant
const PI = 3.14; 

/**
 * Function to calculate area of a circle with given radius r
 * @param {number} r
 * @returns {number} area of circle 
 */
const calculateArea = r => PI * r * r;

/**
 * Function to calculate circumference of a circle with given radius r
 * @param {number} r
 * @returns {number} circumference of circle 
 */
const calculateCircumference = r => 2 * PI * r;

// logging the contents of module object
console.log(module);

Обратите внимание, в коде есть строчка вывода в консоль console.log (module). Переде тем, как вывести модуль регистрирует объект module, у которого есть ключ с именем exports, и значение, соответствующее этому ключу, равно {} (пустой объект).

Теперь, что делает объект module.exports? Ну, это используется для определения вещей, которые могут быть экспортированы модулем.

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

Экспортировать что-то довольно легко. Вам просто нужно добавить его в объект module.exports. Есть три способа добавить что-то к объекту module.exports для экспорта. Давайте обсудим эти методы один за другим.

Экспорт свойств из модуля

Для экспорта вы сначала определяете конструкции, а затем используете несколько операторов module.exports, где каждая инструкция используется для экспорта чего-либо из модуля.

Давайте посмотрим на этот способ в действии и посмотрим, как можно экспортировать две функции, определенные в модуле circle.

// constant
const PI = 3.14; 

/**
 * Function to calculate area of a circle with given radius r
 * @param {number} r
 * @returns {number} area of circle 
 */
const calculateArea = r => PI * r * r;

/**
 * Function to calculate circumference of a circle with given radius r
 * @param {number} r
 * @returns {number} circumference of circle 
 */
const calculateCircumference = r => 2 * PI * r;

// exporting stuff by adding to module.exports object
module.exports.calculateArea = calculateArea; // exporting function named calculateArea
module.exports.calculateCircumference = calculateCircumference; // exporting function named calculateCircumference

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

Первое свойство имеет ключ calculateArea (определен в строке 19), а значение, записанное в правой части оператора присваивания, является функцией, определенной с именем calculateArea (в строке 9).

Второе свойство (определено в строке 20) имеет ключ calculateCircumference, а значением является функция, определенная с именем calculateCircumference (в строке 16).

Таким образом, вы присвоили два свойства (пары ключ-значение) объекту module.exports.

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

...
// exporting stuff by adding to module.exports object using the bracket notation
module.exports['calculateArea'] = calculateArea;
module.exports['calculateCircumference'] = calculateCircumference; 
...

Давайте теперь попробуем записать значение объекта module.exports после добавления свойств. Обратите внимание, что следующий оператор вывода значения объекта module добавляется в конец кода в приведенном ниже файле:

// constant
const PI = 3.14; 

/**
 * Function to calculate area of a circle with given radius r
 * @param {number} r
 * @returns {number} area of circle 
 */
const calculateArea = r => PI * r * r;

/**
 * Function to calculate circumference of a circle with given radius r
 * @param {number} r
 * @returns {number} circumference of circle 
 */
const calculateCircumference = r => 2 * PI * r;

// exporting stuff by adding to module.exports object
module.exports.calculateArea = calculateArea; // exporting function named calculateArea
module.exports.calculateCircumference = calculateCircumference; // exporting function named calculateCircumference

// logging the contents of module.exports object after adding properties to it
console.log(module.exports);

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

$ node circle

Это выведет

{ 
   calculateArea: [Function: calculateArea],
   calculateCircumference: [Function: calculateCircumference] 
}

Конструкции — calculateArea и calculateCircumference, добавленные в module.exports, регистрируются в объекте module.exports. Таким образом, вы успешно добавили два свойства в объект module.exports, чтобы можно было экспортировать функции — calculateArea и calculateCircumference в другой модуль.

В этом методе вы сначала определили все конструкции, а затем использовали несколько операторов module.exports, где каждый оператор используется для добавления свойства к объекту module.exports.

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

...
// using object literal notation to export stuff by adding all at once to module.exports object
module.exports = {
  calculateArea : calculateArea,
  calculateCircumference : calculateCircumference
}
...

Еще один способ вывода — это использовать экспортирование на этапе вычислений нужных свойств и сразу же применить

// constant
const PI = 3.14; 

/**
 * Function to calculate area of a circle with given radius r
 * @param {number} r
 * @returns {number} area of circle 
 */
// adding function definition as the value corresponding to the key calculateArea while defining the function
module.exports.calculateArea = r => PI * r * r;

/**
 * Function to calculate circumference of a circle with given radius r
 * @param {number} r
 * @returns {number} circumference of circle 
 */
// adding function definition as the value corresponding to the key calculateCircumference while defining the function
module.exports.calculateCircumference = r => 2 * PI * r;

// no need to write any module.exports statement later

// logging the contents of module.exports object after adding properties to it
console.log(module.exports);

Функция require

Ключевое слово require относится к функции, которая используется для импорта всех конструкций, экспортированных с использованием объекта module.exports из другого модуля.

Если у вас есть модуль x, в который вы экспортируете некоторые конструкции, используя объект module.exports, и вы хотите импортировать эти экспортированные конструкции в модуль y, вам потребуется импортировать модуль x в модуле y с помощью функции require. Значение, возвращаемое функцией require в модуле y, равно объекту module.exports в модуле x.

функция require возвращает объект module.exports

Давайте разберемся в этом на примере, который мы обсуждали ранее. У вас уже есть модуль circle, из которого вы экспортируете функции calculateArea и calculateCircumference. Теперь давайте посмотрим, как вы можете использовать функцию require для импорта экспортированного материала в другой модуль.

Jodit — лучший WYSIWYG редактор

Отличный редактор WYSIWYG, написанный на чистом TypeScript без использования дополнительных библиотек. Это редактор файлов и редактор изображений. Позволяет также работать и в динамическом режиме, если вам нужно показать редактор в попап — диалоге. Пример с jsFiddle

Чат на PHP, Ajax, HTML, CSS

Разделим код проекта на несколько частей: база данных, серверная и клиентская.

Клиентская часть

Клиентский интерфейс взимодействия

<!DOCTYPE HTML>
    <html>
    <head>
        <title>PhpAjaxChat</title>
        <!-- У нас всё работает в UTF-8 -->
        <meta http-equiv="content-type" content="text/html; charset=UTF-8">

        <!-- Загружаем стили для чата -->
        <link rel="stylesheet" type="text/css" media="screen" href="style.css" />

        <!-- Подключаем jQuery -->
        <script src="jquery.js"></script>
        <!-- Подключаем скрипт чата -->
        <script src="script.js"></script>
    </head>
    <body>
        <div style="padding: 100px;">
            <h1>Php Ajax Chat</h1>
            <!-- Вот в этих 2-х div-ах будут идти наши сообщения из чата -->
            <div class="chat r4">
                <div id="chat_area"><!-- Сюда мы будем добавлять новые сообщения --></div>
            </div>
            <form id="pac_form" action=""><!-- Наша форма с именем, сообщением и кнопкой для отправки -->
                <table style="width: 100%;">
                    <tr>
                        <td>Имя:</td>
                        <td>Сообщение:</td>
                        <td></td>
                    </tr>
                    <tr>
                        <!-- Поле ввода имени -->
                        <td><input type="text" id="pac_name" class="r4" value="Гость"></td>
    
                        <!-- Поле ввода сообщения -->
                        <td style="width: 80%;"><input type="text" id="pac_text" class="r4" value=""></td>
    
                        <!-- Кнопка "Отправить" -->
                        <td><input type="submit" value="Отправить"></td>
                    </tr>
                </table>
            </form>
        </div>
    </body>
</html>

Скрипт работы на стороне клиента на JQuery/JavaScript

$(document).ready(function () {
                $("#pac_form").submit(Send); // вешаем на форму с именем и сообщением событие которое срабатывает когда нажата кнопка "Отправить" или "Enter"
                $("#pac_text").focus(); // по поле ввода сообщения ставим фокус
                setInterval("Load();", 2000); // создаём таймер который будет вызывать загрузку сообщений каждые 2 секунды (2000 миллисекунд)
            });

            // Функция для отправки сообщения
            function Send() {
                // Выполняем запрос к серверу с помощью jquery ajax: $.post(адрес, {параметры запроса}, функция которая вызывается по завершению запроса)
                $.post("ajax.php",
                        {
                            act: "send",  // указываем скрипту, что мы отправляем новое сообщение и его нужно записать
                            name: $("#pac_name").val(), // имя пользователя
                            text: $("#pac_text").val() //  сам текст сообщения
                        },
                        Load ); // по завершению отправки вызываем функцию загрузки новых сообщений Load()

                $("#pac_text").val(""); // очистим поле ввода сообщения
                $("#pac_text").focus(); // и поставим на него фокус

                return false; // очень важно из Send() вернуть false. Если этого не сделать то произойдёт отправка нашей формы, те страница перезагрузится
            }

            var last_message_id = 0; // номер последнего сообщения, что получил пользователь
            var load_in_process = false; // можем ли мы выполнять сейчас загрузку сообщений. Сначала стоит false, что значит - да, можем

            // Функция для загрузки сообщений
            function Load() {
                // Проверяем можем ли мы загружать сообщения. Это сделано для того, чтобы мы не начали загрузку заново, если старая загрузка ещё не закончилась.
                if(!load_in_process)
                {
                    load_in_process = true; // загрузка началась
                    // отсылаем запрос серверу, который вернёт нам javascript
                    $.post("ajax.php",
                            {
                                act: "load", // указываем на то что это загрузка сообщений
                                last: last_message_id, // передаём номер последнего сообщения который получил пользователь в прошлую загрузку
                                rand: (new Date()).getTime()
                            },
                            function (result) { // в эту функцию в качестве параметра передаётся javascript код, который мы должны выполнить
                                $(".chat").scrollTop($(".chat").get(0).scrollHeight); // прокручиваем сообщения вниз
                                load_in_process = false; // говорим что загрузка закончилась, можем теперь начать новую загрузку
                            });
                }
            }

Стили интерфейса

* {
    margin: 0;
    padding: 0;
    }

    body {
    font: normal normal normal 16px "Trebuchet MS", Arial, Times;
    color: #000000;
    }

    /* Важное свойство */
    .chat {
    height: 500px;
    overflow: auto; /* Это позволяет отображать полосу прокрутки */
    position: relative; /* Это позволяет съезжать тексту в слое, не растягивая страницу */
    text-align: left;
    border: solid #818181 1px;
    }

    .chat div {
    position: absolute; /* Страница остаётся того же размера */
    }

    .chat span {
    display: block;
    }

    input[type=text],textarea {
    width: 100%;
    font: normal normal normal 16px "Trebuchet MS", Arial, Times;
    border: solid #818181 1px;
    }

    /* Для CSS 3 */
    .r4 {
    -moz-border-radius: 4px;
    -khtml-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
    }

База данных

В этой таблице у нас будут храниться сообщения чата:

  • id – номер сообщения, он должен быть помечен как AUTO_INCREMENT для того что бы для каждого сообщения создавался уникальный индекс
  • name – имя пользователя отправившего сообщение
  • text – само сообщение
CREATE TABLE `messages` (
    `id` int(5) NOT NULL AUTO_INCREMENT,
    `name` char(255) character SET utf8 NOT NULL,
    `text` text character SET utf8,
    PRIMARY KEY  (`id`)
    );

 

Серверная часть

<?php
// настройки для подключения к MySQl
$config = array('hostname' => 'localhost', 'username' => 'root', 'password' => '', 'dbname' => 'pacdb');

// подключаемся к MySQL, если не вышло то выходим
if (!mysql_connect($config['hostname'], $config['username'], $config['password'])) {
    exit();
}
// Выбираем базу данных, если не вышло то выходим
if (!mysql_select_db($config['dbname'])) {
    exit();
}
mysql_query("SET NAMES 'utf8'"); // говорим MySQl'у то что мы будем работать с UTF-8

Header("Cache-Control: no-cache, must-revalidate"); // говорим браузеру что-бы он не кешировал эту страницу
Header("Pragma: no-cache");

Header("Content-Type: text/javascript; charset=utf-8"); // говорим браузеру что это javascript в кодировке UTF-8

// проверяем есть ли переменная act (send или load), которая указываем нам что делать
if (isset($_POST['act'])) {
    // $_POST['act'] - существует
    switch ($_POST['act']) {
        case "send" : // если она равняется send, вызываем функцию Send()
            Send();
            break;
        case "load" : // если она равняется load, вызываем функцию Load()
            Load();
            break;
        default : // если ни тому и не другому  - выходим
            exit();
    }
}

// Функция выполняем сохранение сообщения в базе данных
function Send()
{
    // тут мы получили две переменные переданные нашим java-скриптом при помощи ajax
    // это:  $_POST['name'] - имя пользователя
    // и $_POST['text'] - сообщение

    $name = substr($_POST['name'], 0, 200); // обрезаем до 200 символов
    $name = htmlspecialchars($name); // заменяем опасные теги (<h1>,<br>, и прочие) на безопасные
    $name = mysql_real_escape_string($name); // функция экранирует все спец-символы в unescaped_string , вследствие чего, её можно безопасно использовать в mysql_query()

    $text = substr($_POST['text'], 0, 200); // обрезаем до 200 символов
    $text = htmlspecialchars($text); // заменяем опасные теги (<h1>,<br>, и прочие) на безопасные
    $text = mysql_real_escape_string($text); // функция экранирует все спец-символы в unescaped_string , вследствие чего, её можно безопасно использовать в mysql_query()

    // добавляем новую запись в таблицу messages
    mysql_query("INSERT INTO messages (name,text) VALUES ('" . $name . "', '" . $text . "')");
    }



    // функция выполняем загрузку сообщений из базы данных и отправку их пользователю через ajax виде java-скрипта
    function Load()
    {
    // тут мы получили переменную переданную нашим java-скриптом при помощи ajax
    // это:  $_POST['last'] - номер последнего сообщения которое загрузилось у пользователя

    $last_message_id = intval($_POST['last']); // возвращает целое значение переменной

    // выполняем запрос к базе данных для получения 10 сообщений последних сообщений с номером большим чем $last_message_id
    $query = mysql_query("SELECT * FROM messages WHERE ( id > $last_message_id ) ORDER BY id DESC LIMIT 10");

    // проверяем есть ли какие-нибудь новые сообщения
    if (mysql_num_rows($query) > 0) {
    // начинаем формировать javascript который мы передадим клиенту
    $js = 'var chat = $("#chat_area");'; // получаем "указатель" на div, в который мы добавим новые сообщения

    // следующий конструкцией мы получаем массив сообщений из нашего запроса
    $messages = array();
    while ($row = mysql_fetch_array($query)) {
    $messages[] = $row;
    }

    // записываем номер последнего сообщения
    // [0] - это вернёт нам первый элемент в массиве $messages, но так как мы выполнили запрос с параметром "DESC" (в обратном порядке),
    // то это получается номер последнего сообщения в базе данных
    $last_message_id = $messages[0]['id'];

    // переворачиваем массив (теперь он в правильном порядке)
    $messages = array_reverse($messages);

    // идём по всем элементам массива $messages
    foreach ($messages as $value) {
    // продолжаем формировать скрипт для отправки пользователю
    $js .= 'chat.append("<span>' . $value['name'] . '» ' . $value['text'] . '</span>");'; // добавить сообщние (<span>Имя » текст сообщения</span>) в наш div
    }

    $js .= "last_message_id = $last_message_id;"; // запишем номер последнего полученного сообщения, что бы в следующий раз начать загрузку с этого сообщения

    // отправляем полученный код пользователю, где он будет выполнен при помощи функции eval()
    echo $js;
    }
}

?>

Автоматическая прокрутка скролла элемента вниз

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

Код HTML:

<ul id="list">
  <li>Item 1</li>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
  <li>Item 5</li>
  <li>Item 4</li>
  <li>Item 7</li>
  <li>Item 8</li>
  <li>Item 9</li>
  <li>Item 10</li>
  <li>Item 11</li>
  <li>Item 12</li>
  <li>Item 13</li>
</ul>

Код CSS:

#list {
  height: 100pt;
  overflow-y: scroll;
}

Код JavaScript:

var elem = document.getElementById('list');
elem.scrollTop = elem.scrollHeight;

Ниже пример в песочнице

Динамическое перемещение навигации вверх при скроллинге

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