October CMS перевод сайта. (Translate site). Часть 3

118

В прошлой статье мы добились перевода контента путем локализации моделей. Продолжим дальше дорабатывать наш сайт. Как сказал, я долокализировал валюту и цену. Цену мне пришлось перевести из типа Number в строку, чтобы плагин отработал. Тк в идеале цена 60 рублей идет как 1 доллар, то есть числовые значения разные. Можно было решить разными путями, я сделал так.

Локализация ссылок

В нашем случае рассматриваем ссылки, которые находятся в шапке. RainlabTranslate предусмотрено, форматирование ссылки при назначении ее через фильтр page.

<ul class="nav navbar-nav">
    <li class="separator hidden-xs"></li>
    <li class="{% if this.page.id == 'home' %}active{% endif %}"><a href="{{ 'home'|page }}">Basic concepts</a></li>
    <li class="{% if this.page.id == 'ajax' %}active{% endif %}"><a href="{{ 'ajax'|page }}">AJAX framework</a></li>
    <li class="{% if this.page.id == 'plugins' %}active{% endif %}"><a href="{{ 'plugins'|page }}">Plugin components</a></li>
    <li class="{% if this.page.id == 'plugins' %}active{% endif %}"><a href="{{'store'|page}}">Store</a></li>
</ul>

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

Переменные локализации

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

{{'Store' |_}}

Чтобы RainlabTranslate ее обнаружил нам необходимо просканировать.

Далее заполняем ее для всех локалей.

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

Локализация SEO

0
 

October CMS перевод сайта. (Translate site). Часть 2

74

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

Перевод кастомных компонентов и данных из моделей

Давайте установим Билдер, чтобы сделать небольшой плагин. https://octobercms.com/plugin/rainlab-builder

В нашей демо версии создадим мини каталог-магазин.

Не будем заморачиваться, добавим простые поля товара. Нам этого хватит.


Отлично. Уж извините 🙂 ничего быстрее не придумал, но буду продавать картошку и помидоры и еще что-нибудь. НО! интернационально на всех языках. Поэтому это круто.
Сделал пару товаров.

Добавлю компонентик с помощью которого выведу информацию на фронтенд.
Код:

<?php
namespace Alex\Store\Components;

use Cms\Classes\ComponentBase;
use Alex\Store\Models\Prod as ProdModel;


class Prod extends ComponentBase
{

    public $items;

    public function init()
    {
        
    }

    public function onRun()
    {

    }

    public function componentDetails()
    {
        return [
            'name' => 'Products',
            'description' => 'Some Products'
        ];
    }

    public function defineProperties()
    {
        return [
        ];
    }

}

Регистрируем компонент и выводим.

Plugin.php

<?php namespace Alex\Store;

use System\Classes\PluginBase;

class Plugin extends PluginBase
{
    public function registerComponents()
    { 
        return [
        'Alex\Store\Components\Prod' => 'prods'
        ];
    }

    public function registerSettings()
    {
    }
}

Шаблон:

title = "Store"
url = "/store"
layout = "default"

[prods]
==
<div class="jumbotron">
    <div class="container">
        {% component 'contenteditor' file="lang/home/store" class='' fixture='div'%}
    </div>
</div>

<div class="container">
    <div class="prods">
        {% for prod in prods.items %}
        <div class="prod">
            <div class="name">{{prod.name}}</div>
            <div class="descr">{{prod.descr}}</div>
            <div class="price">{{prod.price}}</div>
        </div>
        {% endfor %}
    </div>
</div>

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

public function onRun()
{
    $this->items =  ProdModel::get();
}

Как то так.

Все хорошо. Только продукты я вывел на русском. Хотя мы договорились, что дефолтный язык будет английским. Это я поправлю. Теперь надо подумать как локализовать данные из компонента.
Точнее как локализовать модель. Изначально я делал не столь гибко, тк не до конца разобрался. Я пошел по пути дополнения полей в модель. Это раздуло таблицу и к тому же вышло не гибко. У меня был случай на 2 языка. А что если их будет 2-5 десять?

Поэтому у RainlabTranslate реализован более гибкий подход. Мы будем использовать трейт для имплементации функционала перевода.


class Prod extends Model
{
    use \October\Rain\Database\Traits\Validation;

    public $implement = ['RainLab.Translate.Behaviors.TranslatableModel'];
    public $translatable = ['name','descr'];

Добавили трейт и указали поля для перевода. В итоге получили дефолтный функционал.

И при этом наша таблица осталась с теми же полями, но добавилась отдельная таблица Rainlab с атрибутами.

Отлично. Теперь под дефолтной локалью, кою я поменял на En выводятся товары на английском, под ru соответственно на русском.

Стоит добавить локализацию для валюты и цены. Тогда будет совсем ок.

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

0
 

October CMS перевод сайта. (Translate site). Часть 1

80

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

Данный гайд основывается на версии 1, но думаю и для второй будет актуален.

Вот список на который я опирался:
https://medium.com/@octobercms/building-responsive-multi-language-website-with-blog-and-static-pages-using-october-cms-94151610f1ff
https://habr.com/ru/post/305802/
https://medium.com/@octobercms/building-responsive-multi-language-website-with-blog-and-static-pages-using-october-cms-part-2-63ba31243f6a

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

Постановка задачи

  • локализация статического контента
  • локализация динамического контента в плагинах и компонентах
  • локализация интерфейса (меню, футер, функциональные элементы)
  • локализация ссылок

Что нам понадобиться

В моем случае я использовал смежный функционал двух плагинов: RainlabTranslate и ContentEditor.
Далее я объясню как их использовать и для чего каждый.

Локализация статики

Я буду показывать на demo теме октября. Ее переводом мы и займемся.

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

После установки переходим в настройку Транслейта и устанавливаем языки. Их название и локали.

Я добавил русский и сделал его по умолчанию.

Отлично. Теперь у нас доступен режим подраздела для наших локалей.
Можем перейти и попробовать: http://localhost:8888/ru и http://localhost:8888/en. Пока что никаких изменений.

Теперь задаем контент эдитор. Суть плагина в том, что мы оборачиваем контент в паршил, которые хранятся в директории контента themes/demo/content. Расширение транслейта происходит за счет того, что у нас создаются поддиректории для каждого языка автоматом.

Для контент эдитора я использую твиг тег такого вида:

{% component 'contenteditor' file="lang/home/numbers" class='' fixture='div'%}

Создаем директорию themes/demo/content/lang. И так же в главном layout обяъявляем компонент контент эдитора — [contenteditor]

И далее для примера переведем кусок контента на главной. Перенесем контент блок wellcome.htm в lang и импортируем в виде паршила контент эдитора.

В таком виде:

title = "Demonstration"
url = "/"
layout = "default"

==
<div class="jumbotron">
    <div class="container">
        {% component 'contenteditor' file="lang/home/welcome" class='' fixture='div'%}
    </div>
</div>

Отлично у нас появилась кнопка редактирования и область контента стала подсвеченной.

Теперь можем отредактировать контент под разными локалями http://localhost:8888/ru и http://localhost:8888/en. Кстати я передумал 🙂 основной язык все таки будет английский а переводить будем на русский. Поэтому на локаль en оставляем без изменений, а на локали ru переводим.
Как то так.

Таким же способом можно перевести и другие блоки достаточно внезависимости от сложности верстки. Тоже самое касается других страниц.
При этом если мы посмотрим на директорию контент, то увидим rulang папку. Так же будет и с остальными языками.

Плюсы:
— удобно для пользователя
— игнорируется сложность верстки
— редактирование любых страниц без использования StaticPages плагина
— сколько угодно языков

Минусы
— шаблон дробится на вставки из контент эдитора, что усложняет поиск элементов.

Дальнейший перевод рассмотрим в следующих частях.

0
 

October CMS добавляем экшн в контроллер лист (Controller list action)

124

Есть список, в моем случае например товаров. Необходимо добавить какое-либо действие для обработки списка. Мне было необхдимо сделать функционал клонирования товара.

Мы идем в директорию с плагином и собственно с контроллером, который модифицируем.
У меня для примера /alex/catalog/controllers/Products.php
Так же нам понадобиться шаблон тулбара.

Тут уже кнопка у меня добавлена. Изначально шаблон был без нее.
Шаблон находим /alex/catalog/controllers/products/_list_toolbar.htm

Сам шаблон

<div data-control="toolbar">
    <a href="<?= Backend::url('webfamily/catalog/products/create') ?>"
       class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
    <button
            class="btn btn-default oc-icon-trash-o"
            disabled="disabled"
            onclick="$(this).data('request-data', {
            checked: $('.control-list').listWidget('getChecked')
        })"
            data-request="onDelete"
            data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
            data-trigger-action="enable"
            data-trigger=".control-list input[type=checkbox]"
            data-trigger-condition="checked"
            data-request-success="$(this).prop('disabled', true)"
            data-stripe-load-indicator>
        <?= e(trans('backend::lang.list.delete_selected')) ?>
    </button>


    <button
            class="btn btn-primary oc-icon-copy"
            onclick="$(this).data('request-data', {
            checked: $('.control-list').listWidget('getChecked')
        })"
            data-request="onCopy"

    >Скопировать
    </button>
    <a href="<?= Backend::url('webfamily/catalog/products/import') ?>"
       class="btn btn-primary"><?= e(trans('webfamily.catalog::lang.common.import')) ?></a>
</div>

Как видно в шаблоны указаны все кнопки в том числе и новую мной созданную «Скопировать». На нее ставим data-request=«Ваш обработчик» и фунцкию onclick, я взял с копки удаления. Суть js забирать id-ники чекнутых итемов и передавать их на экшн.

Идем далее. Возвращаемся к контроллеру и прописываем обработчик.

/**
     * @return
     * Копирование товара
     */
    public function onCopy() {
        // Проверка на чекнутые товары
        if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
            // Первый чекнутый // Делаем действия либо с одним
            //  $copy_id = post("checked")[0];
            // Либо делаем действие со списком ID
              foreach ($checkedIds as $checkedId) {
                 // Делаем дела ........... 
             }       
            Flash::success('Товар скопирован');
        }
        return $this->listRefresh();
    }

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

0
 

Octber CMS Builder установка поля загрузки картинки

120

Когда я делал поля как обычно используя Page Builder, я наткнулся на ошибку: Model ‘Webfamily\Content\Models\Brands’ does not contain a definition for ‘image’.

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

Наше значение не указывается в таблице как поле.

Допустим моя задача сделать галлерею. Таблица состоит из Id и Image. Image — не указываем в таблице. Мы идем в нашу модель, в моем случае это Brands.php и прописываем поле image через attachOne (или attachMany).

<?php namespace Webfamily\Content\Models;

use Model;

/**
 * Model
 */
class Brands extends Model
{
    use \October\Rain\Database\Traits\Validation;
    
    /*
     * Disable timestamps by default.
     * Remove this line if timestamps are defined in the database table.
     */
    public $timestamps = false;


    /**
     * @var string The database table used by the model.
     */
    public $table = 'webfamily_content_brands';

    /**
     * @var array Validation rules
     */
    public $rules = [
        'image' => 'required',
    ];

    public $attributeNames = [
        'image' => 'Картинка',
    ];

    public $attachOne = [
        'image' => 'System\Models\File'
    ];
}

Соответсвенно в form.yml указываем наше поле из модели.

Ну и чтобы картинка отображался в на странице Контроллера прописываем image. Значение поля я ставлю partial и путь указываю $/webfamily/content/partials/_image3.htm.

Код паршила

<?php if($value): ?>
    <img src="<?php echo $value->path; ?>" style="max-width: 300px; max-height: 150px;">
<?php endif; ?>

И соответсвенно получаю результат.

0
 

October CMS как переопределить(заоверрайдить) методы Rain Lab Users. (Override Rain Lab)

141

Допустим у вас появилась задача похожую на мою. Мне было необходимо подделать метод onActivate.
Соответсвенно, если мы сделаем это напрямую в плагине в классе Account, то через последующее обновление наши изменения сотрутся.

Поэтому мы идем следующем путем.

У нас есть компонент login.htm. Изначально он использует компонент Account.php Rainlab.

title = "Вход"
url = "/login"
layout = "default"

[account]
forceSecure = 0
==
<main class="page__main">
     {% partial 'user_account/singin' %}
</main>

Допустим у вас Плагин Myplugin. В нем мы создадим свой Аккаунт компонент и отнаследуем от Rainlab.

<?php namespace Alexti\Myplugin\Components;

class Account2 extends \RainLab\User\Components\Account
{
    public function componentDetails()
    {
        return [
            'name' => 'Account2',
            'description' => 'Account2'
        ];
    }

    /**
     * Activate the user
     * @param string $code Activation code
     */
    public function onActivate($code = null)
    {
       // do something
    }
}

Соответсвенно наш компонент надо зарегистрировать в Plugin.php

public function registerComponents()
{
    return [
        'Alexti\Myplugin\Components\Account2' => 'Account2',
   ]
}


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

title = "Вход"
url = "/login"
layout = "default"

[Account2]
forceSecure = 0
==
<main class="page__main">
     {% partial 'user_account/singin' %}
</main>
0
 

Установка WordPress Мультисайт(multisite) на локальном сервере. Режим поддоменов.

187

Добрый день. Сегодня расскажу как поставить мультисайт на локальном сервере.

Для чего он нужен? Для организации подразделов или поддоменом, при это используя только один инстанс WordPress (т.е один каталог CMS). Более подробно вы можете ознакомиться в документации.

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

Проблема

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

Итого мы имеем чисто установленный вордпресс на localhost.

Хорошо. Теперь переходим к установки мультисайта по документации.
Заходим в wp-config.php и проставляем объявление:

define( 'WP_ALLOW_MULTISITE', true );

Теперь у нас в панели инструментов появилась настройка сети.

Переходим. И собственно тут мы и сталкиваемся с проблемой.
По идее по гайду у нас должен появится выбор между двумя режимами: поддомены и подкаталоги
https://wp-kama.ru/wp-content/uploads/2016/09/multisite-install2.png.

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

Решение

Я не нашел явного решения через локалхост и через MAMP. Возможно есть какой то подход.

Мое решение выглядет так. Не понимаю в чем ограничение localhost, возможно это связано с зарезервированым названием. Мы изменяем нашу сборку Docker.
В сервисе wordpress мы прописываем hostname — с названием нашего произвольного сайта.

version: '3'
services:
  db:
    image: mysql:8
    container_name: mysql
    restart: always
    command: "--default-authentication-plugin=mysql_native_password"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdb
      MYSQL_USER: user
      MYSQL_PASSWORD: password


  wordpress:
    image: wordpress:4.9.8
    container_name: wordpress
    restart: always
    volumes:
      - ./:/var/www/html/
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wpdb
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: password
    hostname: mysite.ru
    ports:
      - 80:80
      # - 443:443

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: password

Перзапускаем докер.
Отлично. Но нужно прописать алиас на локальной машине для нашего сайта. В Mac/Linux заходим директорию etc и выбираем файл hosts.

sudo nano /etc/hosts

Прописываем алиас и сохраняем от имени администратора.

Отлично теперь наш сайт доступен по урлу mysite.ru (или тот который вы прописали).

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

И теперь заходим в те же настройки сети в админку и мы видим желаемый выбор конфигурации.

Жмем установить идем дальше по инструкции

После установки появляется стандартная панель по управлению сайтами.

Для проверки давайте добавим поддомен. У меня это будет test.
Его соответсвенно надо зарегистрировать в /etc/hosts

Все поддомен активен.

0
 

Установка WordPress через Docker. Простой docker-compose. (Install wordpress with docker-compose)

180

Создаем файл docker-compose.yml

Наша сборка будет состоять из 3-ех стандартных сервисов:

  • Сервис WordPress. Будет включать в себя сервер и файлы CMS
  • Сервис для БД, в нашем случае MySQL
  • Ну и так же поставим phpMyAdmin

Службы будут описаны и указаны в файле docker-compose.yml, который сначала необходимо создать в нашем каталоге:

mkdir wordpress-docker
cd wordpress-docker
touch docker-compose.yml

Можем открыть папку в любом удобном редакторе

У нас должен быть пустой файл docker-compose.yml внутри папки wordpress-docker. Открываем файл и прописываем сервис для mysql.

version: '3'
services:
  db:
    image: mysql:8
    container_name: mysql
    restart: always
    command: "--default-authentication-plugin=mysql_native_password"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdb
      MYSQL_USER: user
      MYSQL_PASSWORD: password

В разделе сервисов мы добавили первое, которое называется db. Этот сервис связан образом mysql и, следовательно, предоставляет необходимый экземпляр базы данных MySQL.

command: "--default-authentication-plugin=mysql_native_password"

Эта команда

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

Добавляем WordPress сервис

Добавляем соотвествующий кусок кода описывающий сервис

wordpress:
    image: wordpress
    container_name: wordpress
    restart: always
    volumes:
      - ./:/var/www/html/
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wpdb
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: password
    ports:
      - 8080:80
      - 443:443

Здесь мы используем образ wordpress. Вы можете указать любую версию вида wordpress:4.9.8. Я буду использовать последнюю актуальную.


Свойство volumes используется для подключения каталога /var/www/html контейнера к локальной папке приложения. Все изменения, которые мы будем вносит в файлы папки нашего приложения буду отображать сразу в контейнере.


Кроме этого, мы снова используем раздел environment для установки значений для четырех переменных: WORDPRESS_DB_HOST, WORDPRESS_DB_NAME, WORDPRESS_DB_USER и WORDPRESS_DB_PASSWORD.
Значение, присвоенное WORDPRESS_DB_HOST, должно быть идентификатором сервиса базы данных ( в нашем случае db).

И мы обязательно сопоставим внутренний порт 80 с внешним портом 8080, чтобы WordPress был доступен через http://localhost:8080.

Добавляем phpMyAdmin

Вставляем код

phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: password

Здесь мы используем образ phpmyadmin/phpmyadmin (https://hub.docker.com/r/phpmyadmin/phpmyadmin).
Включаем доступ к базе данных для phpMyAdmin. Устанавливаем две переменные среды для этой службы:
PMA_HOST: необходимо указать имя службы базы данных (в нашем случае db)
MYSQL_ROOT_PASSWORD: необходимо установить пароль root MySQL (в нашем случае это просто password)

Внутренний порт 80 (на котором по умолчанию доступен phpMyAdmin) сопоставлен с внешним портом 3333, чтобы мы могли получить доступ к веб-интерфейсу пользователя phpMyAdmin с помощью URL http://localhost:3333 позже.

Итоговый файл docker-compose.yml

Это полная версия получившегося файла:

version: '3'
services:
  db:
    image: mysql:8
    container_name: mysql
    restart: always
    command: "--default-authentication-plugin=mysql_native_password"
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: wpdb
      MYSQL_USER: user
      MYSQL_PASSWORD: password


  wordpress:
    image: wordpress:4.9.8
    container_name: wordpress
    restart: always
    volumes:
      - ./:/var/www/html/
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wpdb
      WORDPRESS_DB_USER: user
      WORDPRESS_DB_PASSWORD: password
    ports:
      - 8080:80
      - 443:443

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - 3333:80
    environment:
      PMA_HOST: db
      MYSQL_ROOT_PASSWORT: password

В целом все. Нам остается только запустить установщик наших сервисов:

docker-compose up

Заходим на наш http://localhost:8080 и получаем стартовую страницу установки:

Отлично все получилось.

0
 

Простой node.js cli

203

Привет. Сделаем простой node.js cli. У меня к примеру были задачи, что для какого скрипта надо было установить ряд настроек перед запуском. Собственно для сбора настроек я и решил воспользоваться простым терминальным интерфейсом.

Вначале я использовал стандартный модуль https://nodejsdev.ru/api/readline/. Но мне захотелось чуть более продвинутый интерфейс.

Давайте создадим простое приложение для расчета ИМТ(индекс массы тела).

Для этого мы будем использовать модуль inquirer.

Создаем проект. Устанавливаем модуль и вставляем изначальный шаблон. У меня это будет файл imt.js.

npm init
npm install inquirer
var inquirer = require('inquirer');
inquirer
  .prompt([
    /* Pass your questions in here */
  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
  })

Отлично.
Первый блок это блок вопросов. В нем мы описываем наши вопросы в виде массива объектов. Вопросы бывают разного типа.

Допустим мы спросим про имя (это будет строка), про пол (селект), рост (будет число) и вес (тоже будет число).

Вот что у нас получается.

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
        type: 'input',
        name: 'name',
        message: 'What your name?',
        default: 'noname'
    }, 
    {
        type: 'list',
        name: 'gender',
        message: 'Choose a gender',
        choices: ['male', 'female'],
        default: 'nogender'
    },
    {
        type: 'number',
        name: 'height',
        message: 'What is your height',
        default: '0'
    }, 
    {
        type: 'number',
        name: 'weight',
        message: 'What is your weight(kg)?',
        default: '0'
    }, 
    

  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
    console.log(answers)
  })
  

Можем запустить код и посмотреть.

node imt.js

Мой результат

Отлично. На выходе мы получили console.log c объектом данных которые мы заполнили. У нас в вопросах есть поле default, как не сложно догадаться это значение, которое выставляет, если ответ был пропущен. Что ж давайте сделаем простую валидацию, чтобы все поля были обязательны для заполнения.

Для этого измени некоторые default значения, и воспользуемся методом validate. Это функция, параметром которого является наш ответ на вопрос, а тело собственно проверка. Если проверка пройдена то возвращаем true.

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
        type: 'input',
        name: 'name',
        message: 'What your name?',
        default: '',
        validate: async (input) => {
            if (input == '' ) {
               return 'Please tell me your name';
            }
            return true;
         }
    }, 
    {
        type: 'list',
        name: 'gender',
        message: 'Choose a gender',
        choices: ['male', 'female'],
        default: null,
    },
    {
        type: 'number',
        name: 'height',
        message: 'What is your height(cm)',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your height in centimeters';
            }
            return true;
         }
    }, 
    {
        type: 'number',
        name: 'weight',
        message: 'What is your weight(kg)?',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your weight in kilograms';
            }
            return true;
         }
    }, 
    

  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
    console.log(answers)
  })
  

Теперь у нас появилась проверка

Почти все. Осталось добавить формулу рассчета массы тела из получившихся данных.

  1. Необходимо рассчитать индекс массы тела для мужчины ростом 185 см, вес которого равен 94 кг.
  2. Расчет ИМТ начнем с возведения роста в метрах в квадрат (для этого необходимо умножить число на само себя): 1,85×1,85=3,4225.
  3. Разделим вес в килограммах на полученное значение: I=94/3,4225=27,47.

Выведем индекс и заключение.

var inquirer = require('inquirer');
inquirer
  .prompt([
    {
        type: 'input',
        name: 'name',
        message: 'What your name?',
        default: '',
        validate: async (input) => {
            if (input == '' ) {
               return 'Please tell me your name';
            }
            return true;
         }
    }, 
    {
        type: 'list',
        name: 'gender',
        message: 'Choose a gender',
        choices: ['male', 'female'],
        default: null,
    },
    {
        type: 'number',
        name: 'height',
        message: 'What is your height(cm)',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your height in centimeters';
            }
            return true;
         }
    }, 
    {
        type: 'number',
        name: 'weight',
        message: 'What is your weight(kg)?',
        default: 0,
        validate: async (input) => {
            if (input == 0 ) {
               return 'Please indicate your weight in kilograms';
            }
            return true;
         }
    }, 
    

  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
    console.log(answers)
    let squareHeight =  Math.pow((answers.height/100), 2)
    let index = answers.weight/squareHeight

    console.log('Your index ', index)
    if(index < 16) console.log('Pronounced lack of body weight')
    if(index > 16 && index < 18.5) console.log('Body weight deficit')
    if(index > 18.5 && index < 25) console.log('Normal weight')
    if(index > 25 && index < 30) console.log('Pre-obesity')
    if(index > 30 && index < 35) console.log('First-degree obesity')
    if(index > 35 && index < 42) console.log('Second-degree obesity')
    if(index > 42 ) console.log('Third-degree obesity')
   
  })
  

Наш итоговый результат

0
 

Клавиатура Microsft Sculp Ergonomic. Мнение программиста.

281

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

Немного предыстории

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

На данный момент у меня есть клавиатура 1-ого поколения. Ей я уже пользуюсь лет пять. Она в прекрасном состоянии.

На работе пользовался клавиатурой 2-ого поколения. Она еще более приятная, ход клавиш быстрее и отзывчивее.

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

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

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

Об эргономике и биомеханике

Пару слов о мышке.

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

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

По мышке собственно все.
Главное, чтобы она была подобной формы.

О клавиатуре.

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

  1. Печатная нагрузка
  2. Непечатная нагрузка.

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

Первый тип гораздо больше дает нагрузку. Это виды деятельности, такие как программирование, работа с текстами (переписки, статьи, книги, блогерство, журналистика).

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

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

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

Естественное положение вы видите на картинке.

Есть модели клавиатур такого плана.

Есть некоторые самоделки. Мне не очень нравится механика и высокий ход клавиш, хотя идея интересная. Но цена такого занятия около 10-15 тыс. рублей.

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

О Microsoft Sculpt

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

Скажу вкратце, приехала коробка, довольно большая. В ней собственно три вещи: клавиатура, numpad и мышка. Ну и понятная инструкция.

Первые впечатления положительные. Хорошая, качественная сборка.
Мышка удобная, что самое прикольное — silent, то есть клики приглушенные. Колесико также работает тихо и плавно. Есть пару удобных дополнительных клавиш: win и back.

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

Нумпад. Мне он в принципе не особо был нужен. Просто шел в комплекте. Но я приспособил для доп. клавиш и расположил слева. Иногда удобнее нажимать enter или delete левой рукой и не тянуться.

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

Но стоит сказать, что не все клавиши такого вида. Верхние другого хода — более жестковатые. Не знаю зачем так сделали, может из технических соображений. Мне не очень, но не критично.
И еще — это пробел. Он отличается. Ход у него похож на механический. В нем есть небольшой минус — в отличие от других клавиш, пробел немного «лязгает» при нажатии, как если вспомнить старые клавиатуры. Мне это так же не очень понравилось, хотя критичным недостатком не назвал бы. Думаю, возможно, тут тоже были технические издержки.

Первое впечатление — непривычно, конечно. Расположение иное, руки располагаются немного по другому. Так как между половинами есть разделение, приходится более чисто использовать 10-ти пальцевый метод. На привыкание ушло 2-3 дня.

Сама клава в целом более громоздкая, в сравнении с Apple, но все равно достаточно небольшая в отношении других эргономичных вариантов. Приятная подставка под руки — мягкая, удобная.

После месяца использования.
Как будто всегда с ней работал. Уже привык. Стали меньше напрягаться кисти, руки и лопатки. Усовершенствовалась печать 10-ти пальцевым методом, так как нет возможности читерить. Иногда чуть путаюсь, когда сажусь за ноутбук или другие клавиатуры, но не критично, ничего не забывается. Это как разные языки, когда садишься, то переключаешься.

Плюсы:

  1. Качество — цена, с учетом что идет набор клавиатура + мышка. За 6 тысяч считаю отличным предложением.
  2. Эргономика, соответственно вклад в здоровье.
  3. Без проводов.

Замечания:

  1. Чуть громоздкая
  2. Лязгающий пробел
  3. Необычный формфактор, но это понятно, требует привыкание и использование 10-ти пальцевого метода печати.


+1