jQuery UI control в React на примере FormBuilder

jQuery UI — симпатичная библиотека. У неё есть недостатки — она тяжелее Boot­strap и довольно старая. Но бывает, что нужный компонент есть только под неё. А у тебя проект на одном из React-фреймворков…

Для примера попробуем подключить старый jQuery Form­Builder. Это просто отличный редактор форм.

Конечно, есть другие. Есть даже редактор форм под React… но он использует доисторические библиотеки и отныне не собирается. А их тех, которые собираются, Form­Builder — самый наглядный. В него не страшно пустить даже неопытного пользователя.

Но увы… большинство React-фреймворков давным-давно jQuery-free. Как же нам подключить компонент?

Для начала идём в package.json и добавляем туда form­Builder. К счастью, он уже собран в виде npm-пакета, так что нам не нужно упоминать про jQuery и прочие зависимости. NPM их и так подтянет.

  "dependencies": {
    // ...
    "formBuilder": "^2.9.8",
    // ...
}

А потом создаём новый компонент и пишем:

import React, { Component } from 'react';
import * as $ from 'jquery'

class FormBuilder extends Component {
  state = { };

  componentDidMount() {
    window.jQuery = $;
    window.$ = $;
    require('jquery-ui-sortable');
    require('formBuilder');
    this.$formBuilderContainer = $(this.refs.formBuilderEditor);
    this.$formBuilderEditor = this.$formBuilderContainer.formBuilder({
        i18n: {
          locale: 'ru-RU'
        }
      });
  }
  render() {
    return (
             <div ref="formBuilderEditor"></div>
            );
    }
}

export default FormBuilder;

Добавляем на нужную страницу, запускаем… и видим, что форма на месте и даже кнопки нажимаются!

Динамически создаём React-компоненты

Иногда нужно создавать React-компонент динамически и даже в цикле. Но ren­der(), само собой, не желает выполнять циклы и начинает ругаться.

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

let Block = React.createClass({
  createImage: function (image) {
    return <Image source={image} key={image} />;
  },

  createImages: function (images) {
    return images.map(this.createImage);
  },

  render: function () {
    return (
      <div className="container">
        <div className="row">
          <div className="col-sm-12 text-center">

            {this.createImages(data.images)}

          </div>
        </div>
      </div>
    );
  }
});

 

RegExp в Presto

Opera в своё время прославилась тем, что не поддерживала com­pile() у регулярных выражений. Дело было в движке Presto (которые почему-то не желает их копилировать).

И вот случилось — Opera переехала на Blink. Теперь com­pile() поддерживается.… но не в Opera Mini, которая как была на Presto, там так и остаётся.

Показать в JavaScript alert без остановки таймера

Вообще, использовать стандартный JavaScript alert даже для отладки — плохая идея. Для сообщений есть отличные окошки из Boot­strap JS или jQueryUI, для отладочной информации — console.log.

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

В браузерах, основанных на WebKit (Chrome, Safari, JavaFX WebEngine) открытый alert считается чрезвычайным событием и останавливает все таймеры своей нити. Поэтому показывать его надо в отдельной нити, вот так:

function showMessage(text){
  setTimeout(function() { alert(text); }, 1);
}

 

Получаем параметры GET из url

Оказывается, все параметры, передаваемые в URL get-запросом. лежат в свойстве location.search. Как-то так: “?foo=boo”.

$.extend({
    getUrlStr : function() {
        //used for unit test
        return location.search.substr(1);
    },
    getUrlVars : function(){
        var vars = {},
            hashes = this.getUrlStr();
        if(!hashes)
            return {};
        hashes = hashes.split('&');
 for(var i = 0, len = hashes.length; i < len; i++) {
  var hash = hashes[i].split('=');
  vars[hash[0]] = hash[1];
 }
 return vars;
     },
     urlVars : null,
     getUrlVar : function(key) {
 if(!key) return null;
 if(!this.urlVars) this.urlVars = this.getUrlVars();
 return (this.urlVars[key] !== undefined) ? this.urlVars[key] : null;
     },
});

Д. Босуэлл, Т. Фаучер — Читаемый код, или программирование как искусство

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

Например, более точные варианты названий для типичных функций:

send — deliv­er; dis­patch; announce; dis­trib­ute; route
find — search; extract; locate;recover
start — launch; cre­ate; begin; open
make — cre­ate; set­up; build; gen­er­ate; com­pose; new

Именованные интераторы для циклов (ui лучше, чем просто i).

Дополнительные постфиксы — size_mb, html_utf луче, чем size и html.

first/last — это включающие, а begin/end — исключающие границы.

Пометы в комментах:

TODO - задумано, но не сделано.
FIXME - известно, что есть проблема
HACK - неэлегантное решение проблемы
XXX - серьёзная проблема

Уже второе недоумение насчёт цикла do-while (которые непонятно, зачем нужен).

Разоблачён миф о том, что из функции должен быть только один выход. Он тянется из чистого C, где нередко забывали вычистить память перед выходом. Но c тех пор появились исключения, деструкторы и очистка мусора. А в C простительно писать goto cleanup;

Вообще, если удаётся уменьшить количество отступов, — это хорошо.

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

В JavaScript и Python переменная, объявленная внутри цикла, будет видна и снаружи (с появлением let в новом стандарте JavaScript это уже не так). Вообще, область видимости переменных надо сужать. А самые лучшие переменные — константы, поэтому лучше всего задавать их один раз.

Переменные объявлять где используются, а не в начале блока. Объявлять всё в начале блока — дурная традиция из Pas­cal и раннего C (хотя в C99 уже разрешили объявлять где угодно).

Также есть приятные куски кода:

Макрос для C++, чтобы убрать warn­ing для неопределённого конструктора копирования или оператора присваивания.

#define DISALLOW_COPY_AND_ASSIGN(ClassName) 
  ClassName(const ClassName&); 
  void operator=(const ClassName&);

И потом писать:

class ClassName {
  private:
     DISALLOW_COPY_AND_ASSIGN(ClassName);
     ...
  public:
     ...
};

Параметры по умолчанию в JavaScript

Загружаем необязательные опции функции из указанных в default_options.

Для методов вида doSomething(main_data, options), где options не обязательны.

var JsOptionsHelper = (function(){
  function doLoadOptions(default_options, options){
    var result_options = {};
    if(!options) {
      for(var opt_key in default_options)
        result_options[opt_key] = default_options[opt_key];
    } else {
      for(var opt_key in default_options)
        result_options[opt_key] = (options[opt_key] !== undefined) ? options[opt_key] : default_options[opt_key];
    }
    return result_options;
  }
  return {
    loadOptions : doLoadOptions
  };
})();

JavaScript charset в браузере

Согласно стандарту HTML5, стандартная кодировка страницы — UTF-8. А вот с JavaScript всё сложнее.

Пусть у нас есть какой-то JSON. Напишем функцию, которая его возвращает:

function getOutlineJson() {
  return {
      "title" : "Элемент1"
  };
}

Сохраняем в отдельный файл, привязываем через <script>. Пытаемся вывести в консоль:

document.addEventListener('DOMContentLoaded', function() {
  console.log(getOutlineJson());
});

В консоли будет JSON с полями на неведомом языке. Хотя, как подтверждает view.encoding() в консоли Sub­lime, все файлы — в UTF-8.

А если добавить вывод чего-то кириллического прямо на экран?

document.addEventListener('DOMContentLoaded', function() {
  console.log("проверка консоли");
  console.log(getOutlineJson());
});

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

Лечится мета-тегом:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

Проверено в Fire­fox и Chrome.

eval в JavaScript

eval в JavaScript с отложенным выполнением:

var JsEvaluator = (function(){
  var errors = {
    WRONG_SYNTAX : "Синтаксическая ошибка в выражении "%SOURCE%"",
    NON_SYNTAX : "Ошибка %ERROR_TITLE% при разборе выражения "%SOURCE%""
  };
function null_func() { return null; }

return {
checkedEval : function(str_to_eval){
if(!str_to_eval)
return null_func;
var trimmed_str = str_to_eval.trim();
if(!trimmed_str)
return null_func;
try {
var result = eval(trimmed_str);
if(result === undefined)
return null_func;
} catch(e) {
var error_text = (!(e instanceof SyntaxError)) ?
errors.WRONG_SYNTAX.replace(/%SOURCE%/, trimmed_str) :
errors.NON_SYNTAX.replace(/%ERROR_TITLE%/, e.name).replace(/%SOURCE%/, trimmed_str);
console.error(error_text);
return null_func;
}
return function() { return eval(trimmed_str); };
}
};
})();

Возвращать именно функцию может быть полезно, если выражение в eval завязано на какую-то внешнюю переменную. Например, в str_to_eval у нас условие: “Param1 > 10”. Понятно, что его надо выполнить в момент проверки, а не инициализации. Поэтому выполнение в примере отложено.

Для eval выполняется правило последней строки, пришедшее в Ruby и R из For­tran — eval возвращает значение, которое вернула последняя выполненная строка переданного ему выражения. Это может быть в т.ч. строка с числом:

eval("1;2;4")
> 4

или даже со строковой константой:

eval("'boo'")
> "boo"

Как вариант, можно отказаться от ошибок и понимать “неправильный” JavaScript как строковую константу:

var JsEvaluator = (function(){
  function null_func() { return null; }
  return {
    checkedEval : function(str_to_eval){
      if(!str_to_eval)
        return null_func;
      var trimmed_str = str_to_eval.trim();
      if(!trimmed_str)
        return null_func;
      try {
        var result = eval(trimmed_str);
        if(result === undefined)
          return null_func;
      } catch(e) {
        return function() { return str_to_eval; };
      }
      return function() { return eval(trimmed_str); };
    }
  };
})();