Android Linux Windows

Wp.template — HTML шаблоны для Javascript в WordPress. Использование паттернов проектирования в javaScript: Порождающие паттерны Работа с конфиденциальностью

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

В условиях когда клиентская часть веб-приложений становится все более толстой, бизнес-логика неумолимо переползает на клиент, а на суверенитет серверных технологий все более смело посягает node.js нельзя не задуматься о приемах проектирования архитектуры на javaScript. И в этом деле нам несомненно должны помочь паттерны проектирования - шаблонные приемы решения часто встречающихся задач. Паттерны помогают построить архитектуру, которая потребует от вас наименьших усилий при необходимости внести изменения. Но не стоит воспринимать их как панацею, т.е., грубо говоря, если качество кода «не фонтан», он кишит хардкодом и жесткой связью между логически независимыми модулями, то никакие паттерны его не спасут. Но если стоит задача спроектировать масштабируемую архитектуру, то паттерны могут стать хорошим подспорьем.
Но впрочем эта статья не о паттернах проектирования как таковых, а о их применении в javaScript. В первой части этой статьи я напишу о применении порождающих паттернах.

Singleton Если бы стояла задача описать этот паттерн одной фразой, то она получилась бы примерно следующей: Singleton - это класс, который может иметь только один экземпляр.
Самым простым и очевидным решением в javaScript для реализации этого паттерна является использование объектов:

Var app = { property1: "value", property2: "value", ... method1: function () { ... }, ... }

Этот способ имеет как свои преимущества, так и недостатки. Его просто описать, многие его используют не догадываясь о существовании каких-либо паттернов и эта форма записи будет понятна любому javaScript разработчику. Но у него есть и существенный недостаток: основная цель паттерна singleton - обеспечить доступ к объекту без использования глобальных переменных, а данный способ предоставляет доступ к переменной app только в текущей области видимости. Это означает, что к объекту app мы сможем обратиться из любого места приложения только в том случае если он будет глобальным. Чаще всего это крайне неприемлемо, хорошим стилем разработки на javaScript является использование максимум одной глобальной переменной, в которой инкапсулируется все необходимое. А это означает, что приведенный выше подход мы сможем использовать максимум один раз в приложении.
Второй способ чуть более сложен, но зато и более универсален:

Function SomeFunction () { if (typeof (SomeFunction.instance) == "object") { return SomeFunction.instance; } this.property1 = "value"; this.property2 = "value"; SomeFunction.instance = this; return this; } SomeFunction.prototype.method1 = function () { }

Теперь, используя любую модульную систему (например requirejs) мы в любом месте нашего приложения сможем подключить файл с описанием этой функции-конструктора и получим доступ к нашему объекту, выполнив:

Var someObj = new SomeFunction ();

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

Function SomeFunction () { var instance; SomeFunction = function () { return instance; } this.property1 = "value"; this.property2 = "value"; instance = this; }

Казалось бы вот оно, решение всех проблем, но на место старых проблем приходят новые. А именно: все свойства, занесенные в прототип конструктора после создания экземпляра не будут доступны, т.к. по сути будут записаны в старый конструктор, а не в свежеопределенный. Но и из этой ситуации есть достойный выход:

Function SomeFunction () { var instance; SomeFunction = function () { return instance; } SomeFunction.prototype = this; instance = new SomeFunction (); instance.constructor = SomeFunction; instance.property1 = "value"; instance.property2 = "value"; return instance; }

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

Define(, function () { var instance = null; function SomeFunction() { if (instance) { return instance; } this.property1 = "value"; this.property2 = "value"; instance = this; }; return SomeFunction; });

Factory method У фабричного метода две основных цели:
1) Не использовать явно конкретные классы
2) Объединить вместе часто используемые методы инициализации объектов
Простейшей реализацией фабричного метода является такой пример:

Function Foo () { //... } function Bar () { //... } function factory (type) { switch (type) { case "foo": return new Foo(); case "bar": return new Bar(); } }

Соответственно создание объектов будет выглядеть так:

Foo = factory("foo"); bar = factory("bar");

Можно использовать более элегантное решение:

Function PetFactory() { }; PetFactory.register = function(name, PetConstructor) { if (name instanceof Function) { PetConstructor = name; name = null; } if (!(PetConstructor instanceof Function)) { throw { name: "Error", message: "PetConstructor is not function" } } this = PetConstructor; }; PetFactory.create = function(petName) { var PetConstructor = this; if (!(PetConstructor instanceof Function)) { throw { name: "Error", message: "constructor "" + petName + "" undefined" } } return new PetConstructor(); };

В этом случае мы не ограничиваем себя количеством классов, которые может порождать фабрика, можем добавлять их сколько угодно таким способом:

PetFactory.register("dog", function() { this.say = function () { console.log("gav"); } });

Ну или таким:

Function Cat() { } Cat.prototype.say = function () { console.log("meow"); } PetFactory.register(Cat);

Abstract Factory Абстрактная фабрика применяется для создания группы взаимосвязанных или взаимозависимых объектов.
Предположим у нас есть несколько всплывающих окон, которые состоят из одинаковых элементов, но элементы эти по-разному выглядят и по-разному реагируют на действия пользователя. Каждый из этих элементов будет создаваться фабричным методом, а это значит, что для каждого вида всплывающих окон нужна своя фабрика объектов.
Для примера опишем фабрику BluePopupFactory, она имеет точно такую же структуру как PetFactory, поэтому опустим подробности и просто будем ее использовать.

Function BluePopup () { //создание всплывающего окна } BluePopup.prototype.attach = function (elemens) { //присоединение других ui-элементов к окну } BluePopupFactory.register("popup", BluePopup); function BluePopupButton () { //создание кнопки для синего всплывающего окна } BluePopupButton.prototype.setText = function (text) { //установка текста на кнопке } BluePopupFactory.register("button", BluePopupButton); function BluePopupTitle () { //создание заголовка для синего окна } BluePopupTitle.prototype.setText = function (text) { //установка текста заголовка } BluePopupFactory.register("title", BluePopupTitle);

Наверное у нас должен быть некий класс, отвечающий за элементы интерфейса.

Function UI () { //класс, отвечающий за ui-элементы }

И в него мы добавим метод createPopup:

UI.createPopup = function (factory) { var popup = factory.create("popup"), buttonOk = factory.create("button"), buttonCancel = factory.create("button"), title = factory.create("title"); buttonOk.setText("OK"); buttonCancel.setText("Cancel"); title.setText("Untitled"); popup.attach(); }

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

Var newPopup = UI.createPopup(BluePopupFactory);

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

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

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

Шаблоны проектирования схожи с архитектурными чертежами в том, что показывают как правильно и быстро решить проблему в разработке программного обеспечения. Они структурированы с использованием лучших практик, которые помогают обеспечить стабильность и во многих случаях безопасность наших веб-приложений. Поскольку JavaScript не является традиционным объектно-ориентированным языком программирования , определение шаблонов проектирования в нем может быть немного сложным, но не невозможным.

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

Типы шаблонов проектирования

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

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

Структурные шаблоны : Структурные шаблоны обеспечивают способы управления отношениями между объектами, а также создают структуру классов. Одним из способов достижения этого является использование наследования и композиции для создания большого объекта из небольших объектов.

Поведенческие шаблоны : Поведенческие шаблоны - это шаблоны, которые фокусируются на взаимодействии между объектами. В то время как порождающие шаблоны описывают некий момент времени, а структурные шаблоны описывают более или менее статическую структуру, поведенческие шаблоны описывают процесс или поток.

Создание шаблона модуль

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

// а здесь у нас код

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

Пример реализуемого модуля показан ниже:

Const options = {
username: "Michail",
host: "сайт"
};

Const Configuration = (function(params) {

// возвращает публично доступных данных

Return {
login: login
};

Const username = params.username \|\| "",
server = params.server \|\| "",
password = params.password \|\| "";

Function checkPass()
{
if (this.password === "") {
alert("no password!");
return false;
}

Return true;
}

Function checkUsrname()
{

If (this.username === "")
{
alert("no username!");
return false;
}

Return true;
}

Function login()
{
if (checkPass() && checkUsrname())
{
// выполнить авторизацию
}
}

})(options);

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

Каждый разработчик стремится писать удобный и легко читаемый код. Как только приложения становятся больше, структурирование кода становится важной частью.
Шаблоны проектирования играют важную роль в решении этой задачи, обеспечивая организационную структуру распространенных вопросов в конкретных обстоятельствах.
При создании приложений веб-разработчики JavaScript часто взаимодействуют с шаблонами проектирования неосознанно.

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

В этом посте я хотел бы обсудить распространённые шаблоны чтобы улучшить ваши знания в программировании и погрузиться глубже в JavaScript.

Шаблоны проектирования включают в себя следующее:

— Модуль

— Прототип

— Наблюдатель

— Одиночка

Каждый шаблон состоит из множества свойств, но я выделяю следующие ключевые моменты:

1.Контекст: Где/при каких обстоятельствах используется тот или иной шаблон?

2. Проблема: Какую проблему мы пытаемся решить?

3. Решение: Как использовать это шаблон для решения этой проблемы?

4.Реализация: Как выглядит реализация?

# Шаблон Модуль (Module )

В JavaScript модули являются наиболее распространенными шаблонами проектирования для обеспечения независимости каких-то частей кода от других компонентов. Это обеспечивает слабую связь для поддержания хорошо структурированного кода.
Для тех, кто знаком с объектно-ориентированными языками, модули - это «классы» в JavaScript . Одно из многих преимуществ классов - инкапсуляция – защита состояния и поведения от доступа из других классов.
Шаблон модуля дает доступ публичным и частным уровням (плюс менее защищенным и привилегированным).

Этот язык UML описывает интерфейс прототипа используется для клонирования конкретных реализаций.

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

var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = "Tesla"; this.make = "Model S"; } TeslaModelS.prototype.go = function() { // Вращаются колеса } TeslaModelS.prototype.stop = function() { }

This . numWheels = 4 ;

This . make = "Model S" ;

TeslaModelS . prototype . go = function () {

// Вращаются колеса

TeslaModelS . prototype . stop = function () {

// Применяются тормозные колодки

Конструктор позволяет создавать один объект TeslaModelS . При создании нового объекта TeslaModelS , он сохранит состояние, инициализированное в конструкторе. Кроме того, поддержание функции go и stop несложно, так как мы объявили их при помощи прототипов. Такой же способ расширения функции с использованием прототипа описан ниже:

var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = "Tesla"; this.make = "Model S"; } TeslaModelS.prototype = { go: function() { // Вращаются колеса }, stop: function() { // Применяются тормозные колодки } }

var TeslaModelS = function () {

This . numWheels = 4 ;

This . manufacturer = "Tesla" ;

This . make = "Model S" ;

TeslaModelS . prototype = {

Go : function () {

// Вращаются колеса

Stop : function () {

// Применяются тормозные колодки

REVEALING PROTOTYPE PATTERN

Так же как и шаблон модуль шаблон прототип имеет вариацию Revealing . Revealing паттерн обеспечивает инкапсуляцию с публичными и приватными членами.

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

var TeslaModelS = function() { this.numWheels = 4; this.manufacturer = "Tesla"; this.make = "Model S"; } TeslaModelS.prototype = function() { var go = function() { // Вращаются колеса }; var stop = function() { // Применяются тормозные колодки }; return { pressBrakePedal: stop, pressGasPedal: go } }();

var TeslaModelS = function () {

This . numWheels = 4 ;

This . manufacturer = "Tesla" ;

This . make = "Model S" ;

TeslaModelS . prototype = function () {

Var go = function () {

// Вращаются колеса

} ;

Var stop = function () {

// Применяются тормозные колодки

} ;

Return {

PressBrakePedal : stop ,

PressGasPedal : go

} () ;

Обратите внимание, как функции Stop и Go будут защищены от возвращенного объекта в связи с нахождением за пределами области видимости возвращаемого объекта. Поскольку JavaScript изначально поддерживает прототипное наследование, нет необходимости переписывать базовые элементы(или особенности или черты).

# Шаблон Наблюдатель (Observer )

Бывает так, что одна часть приложения изменяется, а другие части нуждаются в обновлении. В Angular j s, если $scope объекта обновляется, событие может быть запущено для уведомления другого компонента. Шаблон Observer включает в себя то, что, если объект изменен, то он передает (broadcasts) зависимым объектам, что изменение произошло.

Другой яркий пример архитектура модель-представление-контроллер (MVC); представление обновляется когда изменяется модель. Одним из преимуществ является разрыв связи представления от модели для уменьшения зависимостей.

Как показано на схеме UML, необходимые объекты это subject, observer , и concrete . Объект subject содержит ссылки на concrete observers для уведомления любых изменениях. Объект observer является абстрактным классом, позволяющий concrete observers реализовывать метод уведомления.

Давайте взглянем на пример AngularJS , который включает в себя шаблон Observer через управление событиями.

// Controller 1 $scope.$on("nameChanged", function(event, args) { $scope.name = args.name; }); ... // Controller 2 $scope.userNameChanged = function(name) { $scope.$emit("nameChanged", {name: name}); };

// Controller 1

$ scope . $ on ("nameChanged" , function (event , args ) {

$ scope . name = args . name ;

} ) ;

. . .

// Controller 2

$ scope . userNameChanged = function (name ) {

$ scope . $ emit ("nameChanged" , { name : name } ) ;

С шаблоном Observer важно различать независимый это объект или subject .

Важно отметить, что, хотя шаблон Observer и предоставляет много преимуществ, но одним из недостатков является значительное падение производительности, так как количество «наблюдателей» (observers ) увеличено. Один из самых пользующихся дурной славой наблюдателей являются watchers . В AngularJS мы можем наблюдать (watch ) переменные, функции и объекты. Цикл $$digest работает и уведомляет каждого из watchers новыми значениями всякий раз, когда область объекта изменяется.

Мы можем создать наши собственные Subjects и Observers в JavaScript . Давайте посмотрим, как это реализуется:

var Subject = function() { this.observers = ; return { subscribeObserver: function(observer) { this.observers.push(observer); }, unsubscribeObserver: function(observer) { var index = this.observers.indexOf(observer); if(index > -1) { this.observers.splice(index, 1); } }, notifyObserver: function(observer) { var index = this.observers.indexOf(observer); if(index > -1) { this.observers.notify(index); } }, notifyAllObservers: function() { for(var i = 0; i < this.observers.length; i++){ this.observers[i].notify(i); }; } }; }; var Observer = function() { return { notify: function(index) { console.log("Observer " + index + " is notified!"); } } } var subject = new Subject(); var observer1 = new Observer(); var observer2 = new Observer(); var observer3 = new Observer(); var observer4 = new Observer(); subject.subscribeObserver(observer1); subject.subscribeObserver(observer2); subject.subscribeObserver(observer3); subject.subscribeObserver(observer4); subject.notifyObserver(observer2); // Observer 2 is notified! subject.notifyAllObservers(); // Observer 1 is notified! // Observer 2 is notified! // Observer 3 is notified! // Observer 4 is notified!

var Subject = function () {

This . observers = ;

Return {

SubscribeObserver : function (observer ) {

This . observers . push (observer ) ;

} ,

UnsubscribeObserver : function (observer ) {

If (index & gt ; - 1 ) {

This . observers . splice (index , 1 ) ;

} ,

NotifyObserver : function (observer ) {

Var index = this . observers . indexOf (observer ) ;

If (index & gt ; - 1 ) {

This . observers [ index ] . notify (index ) ;

} ,

NotifyAllObservers : function () {

For (var i = 0 ; i & lt ; this . observers . length ; i ++ ) {

This . observers [ i ] . notify (i ) ;

} ;

} ;

var Observer = function () {

Return {

Notify : function (index ) {

Console . log ("Observer " + index + " is notified!" ) ;

var subject = new Subject () ;

var observer1 = new Observer () ;

var observer2 = new Observer () ;

В WordPress повсюду используются шаблоны и Javascript там не исключение. В этой заметке поговорим про встроенную в WordPress возможность создавать HTML шаблоны, которые затем можно использовать в JS. Создаются и используются такие шаблоны очень просто, впрочем как и многое другое в WordPress.

Есть много способов создавать шаблоны в Javascript, для них даже придумана отдельная спецификация именуемая Mustache . Она реализована на многих языках, включая Javascript. Например, библиотека Handlebars использует эту спецификацию и даже немного её расширяет. Или популярная мини-библиотека Underscore .

С версии 3.5 WordPress уже имеет в своем ядре удобный шаблонизатор для JS. Он например используется в админке при создании блоков для медиа-загрузчика. В основе лежит вышеупомянутая библиотека Underscore , синтаксис немного переделан, чтобы больше соответствовать спецификации Mustache .

Для создания шаблонов в WordPress есть метод wp.template

wp.template(id)

Создает объект шаблона из HTML кода. Чтобы получить готовый HTML код для использования в JS, в созданный объект нужно передать данные для заполнения шаблона.

Возвращает

Function. Функцию, в которую нужно передать данные для интерполяции шаблона.

Использование var template = wp.template(id); var HTML = template(data); id(строка)

Идентификатор HTML элемента который содержит HTML код шаблона. HTML элемент должен иметь указанный тут атрибут id с префиксом tmpl- .

Например, если тут указать foo , то HTML элемент должен иметь id id="tmpl-foo" .

Data(объект) JS объект данных, которые будут использованы для заполнения шаблона. Например: { text: "Привет" } .

Заполнение шаблона (интерполяция)
  • {{{data.unescaped}}} - неочищенные данные.
  • {{data.escaped}} - очищенные данные.
  • - обработать js (eval).
Префикс data.

data в шаблоне - это объект исходных данных. В шаблоне нужно использовать именно ключ data .

Чтобы соответствовать структуре данных возвращаемых функциями: wp_send_json_success() и wp_send_json_error() , wp.template оборачивает все полученные данные в переменную data . Поэтому перед каждым параметром в шаблоне нужно указывать data. , иначе мы получим ошибку: {property} is not defined .

Правильно {{{data.name}}}

Неправильно {{{name}}}

Пример шаблона

Это будет просто выведено.

Выведем значение переменной escapedValue {{data.escapedValue}}.

Если данные содержат разметку, выводим без экранирования:

{{{data.unescapedValue}}}

Когда нужно выполнить какую-то логику.

Будет выведено, только если data.trueValue = true.

Создание и генерация шаблона Создание шаблона

Чтобы шаблон никак не фигурировал в DOM дереве, его принято создавать в теге script с указанием типа type="text/html" .

Привет {{{data.name}}}

Атрибут id должен начинаться с tmpl- , все что после этого префикса будет затем использовано в функции wp.template("my-template") .

Создание шаблона в теге script это хак, который отлично подходит для создания html элемента, который никак не используется браузером. Когда указан непонятный для браузера тип он просто игнорирует html тег, а это нам и нужно.

Шаблон также можно создать в любом другом HTML элементе (например в , который затем можно скрыть), единственное что нужно это указать id атрибут.

Также для создания шаблонов есть специальный HTML тег , однако он не поддерживается в IE . Но в целом он довольно актуален.

Генерация шаблона

wp.template() возвращает функцию, поэтому не пытайтесь передать результат в html элемент или вывести результат в консоль. Обычно результат wp.template() передается в переменную, а затем эта переменная используется как функция и в неё передаются данные, которыми должен быть заполнен шаблон.

Пример (шаблон указан выше)

// JS var template = wp.template("my-template"), data = { name: "Виктор" }; jQuery(".my-element").html(template(data));

В результате получим в HTML:

Привет Виктор

Пример комментирования на AJAX с использование шаблона

Создаем шаблон и подключаем скрипт в файле темы functions.php:

  • {{{data.gravatar}}} {{data.comment_author}}