Предисловие
Началось все с того, что нужно было разработать новый интерфейс для MS-SQL отчетов. Разработать его нужно было в уже готовом проекте, который достался «по наследству». Сам проект написан на ajax. Коллега очень просил сделать интерфейс «как в Delphi», собственно так и назовем наш проект. Для реализации формы я выбрал react, т.к. на ajax писать скучно и геморройно, а react на данный момент я освоил вполне хорошо. Чтобы не было проблемы с совместимостью, интерфейс буду подгружать как фрейм. В предыдущих проектах я использовал umd пакеты react и тут возник вопрос, нужно было подключить календарь. Самому писать его было лень — нашел пару готовых, ни один из них в таком режиме не заводился. В связи с этим пришлось озадачиться с упаковкой проекта webpack-ом. И тут было куча мануалов, которые ни к чему не приводили, т.к. на актуальную 4-ю версию вебпака их просто не было. Официальный мануал тоже довольно запутан. Чуть ниже я опишу минимальный конфиг. Сам интерфейс построен на той же логике, но немного в иной реализации. (Исходники фронтэнда можно посмотреть тут: https://github.com/namedudko/farmin-mssqlreportfrontend , а реализацию, с отключенным бэкэндом, тут: https://test.sergdudko.tk/mssql/form1/ ). Логика обработки данных в интерфейсе такова:
- Непосредственная логика полностью управляется в диспатчере редакса.
- Изначально в state загружается клон данных редакса.
- В визуализации компонента в качестве свойств элементов DOM можно ссылаться прямо на редакс только в том случае, если эти свойства const. Иначе это должен быть либо клон редакса, либо state компонента.
- При изменении мы выкидываем новое свойство элемента DOM в обработчик, который обновляет данные в редаксе.
- При монтировании компонента мы подписываем state компонента на соответствующие данные в редаксе с обязательной проверкой, что они не эквивалентны. Если проверку опустить, даже PureComponent рендерится чаще чем нужно.
- Возвращаемся к пункту 3.
В примере я запихнул всю логику в state компонента. Но сам принцип связи DOM элементов не изменился.
Webpack
Для начала установим зависимости:
npm i webpack --save
npm i babel-cli babel-core babel-loader babel-preset-env babel-preset-react prop-types uglifyjs-webpack-plugin webpack-cli webpack-dev-server webpack-merge --save-dev
Предполагаю, что глобально у вас уже стоит webpack (у меня был версии 4.8.3) и webpack-cli.
Итак, минимальный конфиг для упаковки react приложения. Создадим в корне файл .babelrc:
{ "presets": ["env", "react"] }
Если, конечно, вы не пишете на ES5/ES6 синтаксисе — можете обойтись и без него. Далее нам нужен корректный package.json, рассмотрим на примере моего, разбирать по полям все не стану — только основное:
{ "name": "delphiform", "version": "1.0.3", "description": "Forms for React 16+", "main": "./umd/delphiform.js", "dependencies": { "lodash": "^4.17.10", "react": "^16.3.2", "webpack": "^4.8.3" }, "devDependencies": { "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-loader": "^7.1.4", "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "prop-types": "^15.6.1", "uglifyjs-webpack-plugin": "^1.2.5", "webpack-cli": "^2.1.3", "webpack-dev-server": "^3.1.4", "webpack-merge": "^4.1.2" }, "scripts": { "build": "webpack --optimize-minimize --mode production" }, "repository": { "type": "git", "url": "https://github.com/namedudko/delphiform/" }, "keywords": [ "react", "reactjs", "form", "forms", "react-forms", "reactdom", "delphiform" ], "links": { "npm": "https://www.npmjs.com/package/delphiform", "homepage": "https://github.com/namedudko/delphiform/#README", "repository": "https://github.com/namedudko/delphiform/", "bugs": "https://github.com/namedudko/delphiform/issues" }, "publisher": { "username": "Name Surname", "email": "admin@sergdudko.tk" }, "author": "Name Surname", "license": "MIT", "style": "./umd/delphiform.css" }
- name — наименование пакета в npm
- version — версия пакета в npm
- main — путь к первому скрипту пакета
- dependencies — зависимости, которые потянет пакет. Вебпак у меня не обязательный, просто без него не упакуется.
- scripts — скрипты запускаемые командой npm run имя_скрипта
Ну и сам конфиг webpack.config.js:
const path = require('path'); const webpack = require("webpack"); module.exports = { module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader" } } ] }, plugins: [], entry: { filename: path.resolve(__dirname, './src/') + '/delphiform.js' }, output: { path: path.resolve(__dirname, './umd/'), filename: 'delphiform.js', libraryTarget: "umd" } };
Если не указать entry — он будет искать скрипт, указанный в main package.json, что в намем случае не вариант — в пакете будет уже скомпилированный umd (см. libraryTarget) пакет.
Компонент
В перспективе хочется разнообразить библиотеку компонентов, а значит её нужно предварительно создать. Для удобство сделаю компоненты в разных скриптах. Для этого создам первый скрипт-связку:
/** Delphi Form v1.0.0 * * * Copyright (c) 2018-present, * by Name Surname (admin@sergdudko.tk). * * LICENSE MIT. */ "use strict" import _ from 'lodash'; import React from 'react'; import DelphiForm1 from './DelphiForm1.js'; import DelphiForm2 from './DelphiForm2.js'; module.exports.form1 = DelphiForm1; module.exports.form2 = DelphiForm2;
Соответственно нам теперь нужно создать два скрипта DelphiForm1.js и DelphiForm2.js в которых описать компоненты доступные по ссылкам form1 и form2 нашего модуля.
В DelphiForm2.js я запишу просто тестовую форму, чтобы она выводила хоть что-то (слово TEST).
/** Delphi Form v1.0.0 * * * Copyright (c) 2018-present, * by Name Surname (admin@sergdudko.tk). * * LICENSE MIT. */ "use strict" import _ from 'lodash'; import React from 'react'; module.exports = class DelphiForm2 extends React.PureComponent{ constructor(props, context){ super(props, context); } render() { return ( <div className="DelphiForm2"> TEST </div> ); } };
А DelphiForm1.js будет наш компонент формы. Визуально он будет представлять из себя:
Логика работы компонента:
Мы получаем данные в props. Допустимые данные
- store — объект json, где ключами являются уникальные идентификаторы (далее uid), а значением наименование.
- selected — массив uid-ов, которые будут изначально выбраны (нужно для реализации сохранения состояния компонента).
- name — имя компонента.
- callback — функция обратного вызова, в которую будет возвращен массив selected при инициализации компонента и далее будет возвращаться массив выбранных элементов из правой формы.
Изначально мы загружаем в ObjSearch — весь props.store объект, при этом переводим в верхний регистр все значения (нужно для реализации регистронезависимого поиска). В SelectedArr — загружаем массив props.selected, далее будем работать с SelectedArr — хранилище правой формы, оно же выбрасывается в callback при обновлении. SearchStr — строка поиска при монтировании пуста. Т.к. стока поиска пуста, то в SearchArr — массив uid/хранилище левой формы будут включены все ключи props.store, за исключением тех что уже отобраны в SelectedArr. И эти же ключи будут включены в StoreArr. При клике по левой форме мы удаляем uid из SearchArr и StoreArr и добавляем его в SelectedArr. При клике по правой форме — с точностью наоборот, удаляем uid из SelectedArr и добавляем в SearchArr и StoreArr. При вводе строки поиска, если длинна строки меньше 2 символов в SearchArr клонируем данные из StoreArr. Если больше двух символов то проходим по значениям массива StoreArr. Эти значения подставляем в ключи объекта ObjSearch и получаем его значения. В случае, если в строке (значение объекта ObjSearch) встречается строка из «строки поиска», то добавляем значение массива StoreArr (соответствующий данному значению в объекте ObjSearch) в массив SearchArr. Для визуализации формируем внутренние кнопки из массивов SearchArr и SelectedArr соответственно для левой и правой формы, а подписи берем из объекта props.store (там значения в нормальном регистре).
Код компонента:
/** Delphi Form v1.0.3 * * * Copyright (c) 2018-present, * by Name Surname (admin@sergdudko.tk). * * LICENSE MIT. */ "use strict" import _ from 'lodash'; import React from 'react'; module.exports = class DelphiForm1 extends React.PureComponent{ constructor(props, context){ super(props, context); this.state = { StoreArr: [], //полученные uid элементов ObjSearch: {}, //полученный объект uid->name, где name в верхнем регистре SearchArr: [], //найденные (отображаемые в левом окне) элементы SelectedArr: [], //выбранные (отображаемые в правом окне) элементы SearchStr: "" //строка поиска }; } componentWillMount(){ //загружаем первоначальное состояние компонента try{ let self = this; if(typeof(self.props.store) === 'object'){ let this_StoreArr = [], this_store = {SelectedArr:[], ObjSearch:{}}, StoreArrobject = _.clone(self.props.store); if(typeof(self.props.selected) === 'object'){ for(let i = 0; i < self.props.selected.length; i++){ if(typeof(self.props.store[self.props.selected[i]]) !== 'undefined'){ this_store['SelectedArr'].push(_.clone(self.props.selected[i])); } } } for(let key in StoreArrobject){ this_store['ObjSearch'][key] = StoreArrobject[key].toUpperCase(); if(this_store['SelectedArr'].indexOf(key) === -1){ this_StoreArr.push(key); } } this_store['StoreArr'] = _.clone(this_StoreArr); this_store['SearchArr'] = _.clone(this_StoreArr); self.setState(this_store); } } catch(e){ window.console.log(e); }; } componentWillReceiveProps(nextProps){ //при обновлении пропсов перезагружаем состояние компонента try{ let self = this; if(typeof(nextProps.callback) === 'function'){ self.callback = nextProps.callback; } if(typeof(nextProps.store) === 'object'){ let this_StoreArr = [], this_store = {SelectedArr:[], ObjSearch:{}}, StoreArrobject = _.clone(nextProps.store); if(typeof(nextProps.selected) === 'object'){ for(let i = 0; i < nextProps.selected.length; i++){ if(typeof(nextProps.store[nextProps.selected[i]]) !== 'undefined'){ this_store['SelectedArr'].push(_.clone(nextProps.selected[i])); } } } for(let key in StoreArrobject){ this_store['ObjSearch'][key] = StoreArrobject[key].toUpperCase(); if(this_store['SelectedArr'].indexOf(key) === -1){ this_StoreArr.push(key); } } this_store['StoreArr'] = _.clone(this_StoreArr); this_store['SearchArr'] = _.clone(this_StoreArr); self.setState(this_store); } } catch(e){ window.console.log(e); }; } componentDidMount() { //при монтировании компонента привязываем функцию обратного вызова let self = this; if(typeof(self.props.callback) === 'function'){ self.callback = self.props.callback; } self.callback(_.clone(self.state.SelectedArr)); } componentWillUpdate(nextProps, nextState) { //при обновлении компонента выбрасываем отобраные uid в функцию обратного вызова let self = this; if (!_.isEqual(nextState.SelectedArr, self.state.SelectedArr)) { self.callback(_.clone(nextState.SelectedArr)); } } onClickHandler(e){ let self = this; let tempstore = JSON.parse(JSON.stringify(self.state)); switch(e.target.id){ case 'add': if(tempstore['SelectedArr'].indexOf(e.target.name) === -1){ tempstore['SelectedArr'].push(e.target.name); } let index_Store = tempstore['StoreArr'].indexOf(e.target.name); if(index_Store !== -1){ tempstore['StoreArr'].splice(index_Store, 1); } let index_Search = tempstore['SearchArr'].indexOf(e.target.name); if(index_Search !== -1){ tempstore['SearchArr'].splice(index_Search, 1); } break; case 'del': let index_Selected = tempstore['SelectedArr'].indexOf(e.target.name); if(index_Selected !== -1){ tempstore['SelectedArr'].splice(index_Selected, 1); } if(tempstore['StoreArr'].indexOf(e.target.name) === -1){ tempstore['StoreArr'].push(e.target.name); } if(tempstore['SearchArr'].indexOf(e.target.name) === -1){ tempstore['SearchArr'] = _.clone(self.SearchHandler(_.clone(tempstore['StoreArr']), tempstore['SearchStr'])); } break; } if(!_.isEqual(self.state, tempstore)){ self.setState(tempstore); } } onChangeHandler(e){ let self = this; let tempstore = JSON.parse(JSON.stringify(self.state)); switch(e.target.name){ case 'SearchStr': tempstore['SearchStr'] = e.target.value; tempstore['SearchArr'] = _.clone(self.SearchHandler(_.clone(tempstore['StoreArr']), e.target.value)); break; } if(!_.isEqual(self.state, tempstore)){ self.setState(_.clone(tempstore)); } } SearchHandler(store, string){ //формирование результатов поиска let self = this, returnArray = _.clone(store); if(typeof(store) === 'object'){ if(typeof(string) === 'string'){ if(string.length > 1){ returnArray = []; for(let i = 0; i < store.length; i++){ if(self.state.ObjSearch[store[i]].indexOf(string.toUpperCase()) !== -1){ returnArray.push(store[i]); } } } } } return returnArray; } callback(data){ window.console.log(data); } render() { let DelphiForm = new Array; let DelphiFormLeft = new Array; let DelphiFormRight= new Array; for(let i = 0; i < this.state.SearchArr.length; i++){ DelphiFormLeft.push(<div title={this.props.store[this.state.SearchArr[i]]}><input type="button" className="DelphiForm1Button" onClick={this.onClickHandler.bind(this)} id='add' name={_.clone(this.state.SearchArr[i])} value={this.props.store[this.state.SearchArr[i]]} /></div>); } for(let i = 0; i < this.state.SelectedArr.length; i++){ DelphiFormRight.push(<div title={this.props.store[this.state.SelectedArr[i]]}><input type="button" className="DelphiForm1Button" onClick={this.onClickHandler.bind(this)} id='del' name={_.clone(this.state.SelectedArr[i])} value={this.props.store[this.state.SelectedArr[i]]} /></div>); } let DelphiFormString = <input type="text" className="DelphiForm1SearchInp" name="SearchStr" onChange={this.onChangeHandler.bind(this)} value={this.state.SearchStr} />; DelphiForm.push(<div className="DelphiForm1SearchString">{DelphiFormString}</div>); DelphiForm.push(<div className="DelphiForm1Search">Позиции для отбора<div className="DelphiForm1SearchDiv">{DelphiFormLeft}</div></div>); DelphiForm.push(<div className="DelphiForm1Filtr">Отобранные позиции<div className="DelphiForm1FiltrDiv">{DelphiFormRight}</div></div>); return ( <div className="DelphiForm1"> <div className="DelphiForm1Name">{this.props.name}</div> {DelphiForm} </div> ); } };
Варианты подключения компонента:
- npm i delphiform —save
- import DelphiForm from ‘delphiform’; и далее DelphiForm.form1
- var DelphiForm = require(‘delphiform’); и далее DelphiForm.form1
- <script src=»./umd/delphiform.js»><script> и далее window.form1
Не забываем подключить CSS <link rel=»stylesheet» href=»./umd/delphiform.css»>
Пример инициализации компонента:
"use strict" import React from 'react'; import ReactDOM from 'react-dom'; import DelphiForm from 'delphiform'; class TEST extends React.PureComponent{ constructor(props, context){ super(props, context); this.state = { store:{ '14014265-6A12-4AFE-A40F-E4CE6E27DE10': 'Аптека №1 ООО "Фитобел" (г. Минск, ул.Бурдейного 13-5)', 'D7040BD4-0C3E-414B-A559-79791575A91C': 'Аптека №2 ООО "Фитобел" (г. Минск, ул. В. Хоружей-8, пав. 38)', '80060CB5-FCCD-4E47-A523-D45A866EE554': 'Аптека №3 ООО "Фитобел" (г. Минск, ул. Лобанка-26, пом. 4)', 'BFD523DF-FB43-4BB2-A3CD-4017C6C7A533': 'Аптека №4 ООО "Фитобел" (г. Минск, пр. Дзержинского, 104, пом. 7)', '911C6676-7661-4A69-A7F1-C333E42D275E': 'Аптека №5 ООО "Фитобел" (г. Минск, ул. В.Хоружей 6Б, пом. 9)', '70032A36-D7E9-4BF2-A889-B02B6B278EC8': 'Аптека №6 ООО "Фитобел" (г. Минск, ул.Кунцевщина 2А, пом.6)', 'ACB559D6-9E06-42BF-AC13-064B3D3381B8': 'Аптека №7 ООО "Фитобел" (г. Минск, ул.Сурганова 57А пом.4)', '93E1AA76-9E7D-42C5-A68D-9DF0C7980DA6': 'Аптека №8 ООО "Фитобел" (г. Минск, ул. 50лет Победы, 8 пом. 56 )', '65C98749-F7C4-49F4-BDE9-A36D907BEDF5': 'Аптека №9 ООО "Фитобел" (г. Минск, ул. Есенина 27)', '37BB4995-2EEF-4349-B5D3-16167FF0603D': 'Аптека №10 ООО "Фитобел" (г. Минск, ул.Наполеона Орды 23)', '682BB6CB-17AC-4B9B-8D6B-0E6CD57B9980': 'Аптека №11 ООО "Фитобел" (Минский р-н, АГ Лесной, ул. Н. Н. Александрова, д. 10, пом.81)', '46A50D91-758A-459F-9DA5-2BF2425BD887': 'Аптека №12 ООО "Фитобел" (г. Витебск, ул.Ленина 26А)', '29B20FC1-C334-49FA-9F67-CED0E9BF8568': 'Аптека №14 ООО "Фитобел" (г. Минск, ул. Кальварийская, 2-113)', 'CBB782D0-05B2-42EB-9C1F-B3E60103085D': 'Аптека №15 ООО "Фитобел" (г. Минск, ул. Ангарская, 62А)', '74586A62-CECF-4F3B-B64B-5508CB874291': 'Аптека №16 ООО "Фитобел" (г. Минск ул. Калиновского, 3-7Н)', 'F1278B22-34AF-4ABB-8DF4-4FCDAD8488A8': 'Аптека №17 ООО "Фитобел" (г. Минск, ул.Нововиленская, 10-104)', '4FA6F0CE-26C3-4BF3-B30A-69356E80FA5E': 'Аптека №18 ООО "Фитобел" (г. Минск, ул.Карла Либкнехта 108/32)', 'F8BCC010-527F-45CE-9019-74FD9164A506': 'Аптека №19 ООО "Фитобел" (г.Минск, ул. В. Голубка,2-2)', '1FD3B0B8-7B0F-4640-80E5-FD0004E8F3AD': 'Аптека №20 ООО "Фитобел" (г. Минск, пр. Партизанский, 67)', '49C2A45A-33B5-4929-A64D-31F395100871': 'Аптека №21 ООО "Фитобел" (г. Жодино, ул. Рокоссовского, 9)', '3634A2B4-88FF-4C56-B6B1-15438EB37ED2': 'Аптека №22 ООО "Фитобел" (г. Минск, ул. Илимская, 27)', '621965EA-2ABC-4F4B-AABC-8F8C26AD3E15': 'Аптека №23 ООО "Фитобел" (г. Минск, ул. Налибокская, 39)', 'CF9BF992-0A97-4C27-A7B9-8625C3499C5F': 'Аптека №24 ООО "Фитобел" (г. Барановичи, бульвар Штоккерау, 3-1)', '266805FC-E8A7-412D-A9F3-E5A4AF540BFA': 'Аптека №25 ООО "Фитобел" (г. Борисов, ул. Гагарина, 4А)', '7D9C669B-AC9A-4FE3-BB5D-747A4C2667E7': 'Аптека №26 ООО "Фитобел" ( г. Минск, ул. Слободская, 47)', '4E3521C9-638B-46DD-9027-DB5821DB0BB4': 'Аптека №27 ООО "Фитобел" (г. Минск, ул. Гуляма Якубова, 14, пом.4)', '8051E801-95E4-478B-8CDF-E2803743DDC0': 'Аптека №28 ООО "Фитобел" (г. Минск, ул. Уборевича, 75)', 'CA9C9EC6-525D-474F-A7A1-3E0F6B054AA6': 'Аптека №29 ООО "Фитобел" (Минская область, Минский р-он, Боровлянский с/с, д.Копище, ул. Лопатина, 4А-1а)', '3C7E8A08-84E3-40AF-9069-714AAAF6BAFA': 'Аптека №30 ООО "Фитобел" (Минская область, Минский р-он, Боровлянский с/с, а.г. Лесной, 37-79)', '7548B828-1FE2-43F8-B6DE-844BB66A209B': 'Аптека №31 ООО "Фитобел" (г. Минск, ул. Колесникова П.Р., дом № 15, помещение 1)', 'A1243A9A-E71B-4E8D-84F8-BACD9B1016B7': 'Аптека №32 ООО "Фитобел" (г. Минск, ул. Некрасова, д.3А)', 'AB1A51E8-9E30-4317-B5BB-E19BD8ED068A': 'Аптека №33 ООО "Фитобел" (г.Барановичи, ул. Борисовская, 4-1)', 'CA75ECE6-1741-46CC-AC28-1221E255EC97': 'Аптека №1 "Не Ска" ООО (г. Минск, пр-т. Дзержинского 119-862)', 'D75C5403-37C1-438F-84B3-8498DCFD078E': 'Аптека №2 "Не Ска" ООО (г. Минск, ул. Казимировская, д. 35)', '9EB42DA6-DF55-4E74-BABE-2A4CF560E7EF': 'Аптека №3 "Не Ска" ООО (Лида, ул. Коммунистическая, д. 50)', '33172B85-8E29-4BDB-B409-CFB59D165394': 'Аптека №4 "Не Ска" ООО (г. Минск, ул. Голубева И.П., д. 24)', 'AC732CA0-D570-4A09-8906-0046B00D6CF9': 'Аптека №5 "Не Ска" ООО (г. Минск, ул. Рафиева Н., д. 56)', '7D582258-2564-4969-861F-5A9971B2FFC7': 'Аптека №6 "Не Ска" ООО (г. Минск, ул. Иосифа Жиновича, д. 7)', 'CCFB2056-E9E0-4B1A-99A8-1A75AA07515A': 'Аптека №7 "Не Ска" ООО (г.Солигорск,ул.Октябрьская,38)', 'BC8F7416-DAD4-4CA1-AE2F-6E4BDAFA8ADC': 'Аптека №8 "Не Ска" ООО (г. Минск, тр. Игуменский, д. 11 )', '31AA6C3E-9B12-4B7D-B4F6-257D662A8E38': 'Аптека №9 "Не Ска" ООО (Лида, ул. Победы д.43 пом 2)', '131542B2-6C61-43DC-BF89-4C3218CD4C62': 'Аптека №10 "Не Ска" ООО (г. Минск, ул.Кижеватова, д.7, корп. 2, пом.4)', '5C9EE302-6E12-472D-95B5-4C6ED17B8846': 'Аптека №11 "Не Ска" ООО (г. Молодечно, ул. В.Гостинец, дом № 143Б)', '8884D92E-AABF-4794-98A4-C5E64C48C0ED': 'Аптека №12 "Не Ска" ООО (г. Молодечно, ул. Ф.Скорины, дом № 16A-3)', 'B99D466E-4C00-4F18-9621-A1C925047D24': 'Аптека №14 "Не Ска" ООО (г. Гомель, ул. М.Г.Ефремова 5-266)', '82A1261B-9951-4DAB-BBDC-8E5E2DAC43BE': 'Аптека №15 "Не Ска" ООО (г. Гомель, пр. Ленина 51)', 'D38727B1-7CBA-44EC-991E-79FAC867B29E': 'Аптека №16 "Не Ска" ООО (г. Гомель, ул. Свердлова 3А-3)', '59A77C4F-46D9-47E3-B261-210BBA3908C7': 'Аптека №17 "Не Ска" ООО (г. Гомель, ул. Юбилейная 8-3)', '4F5B18AB-36C4-426C-B1EC-15B28A0F40C8': 'Аптека №18 "Не Ска" ООО (г. Гомель, ул. Барыкина ,100-109)', 'F2BB9CF0-BA1A-4714-9E3C-9A8C0F44CB32': 'Аптека №19 "Не Ска" ООО (г. Гомель, ул. Хатаевича 9)', '67F01F35-ADE2-44F0-B5DE-E5B79913D7FC': 'Аптека №20 "Не Ска" ООО (г. Гомель, ул. Владимирова 29-144)', '7FB88CFF-F765-4472-8B29-7CA587F859F0': 'Аптека №21 "Не Ска" ООО (г. Гомель, ул. Интернациональная 48-154)', 'ABA40F89-CA53-4860-A23B-6A0ABE970F20': 'Аптека №22 "Не Ска" ООО (г. Борисов, ул. Лопатина, дом № 44)', 'CC1EF8C8-062E-4492-AA02-F171E6E0105F': 'Аптека №23 "Не Ска" ООО (г. Минск, ул. Лынькова Михася, дом № 99 )', 'FAAD016A-BE14-456E-9128-9A6AD4DD4A15': 'Аптека №24 "Не Ска" ООО (г.Гомель, ул. Григория Денисенко, 4-3)', '567BA474-A800-439A-9A11-B6CAE850166B': 'Аптека №25 "Не Ска" ООО (г.Минск, ул. Космонавтов, 28)', 'F6DD46DD-EAF4-4255-9719-13ECBC27412C': 'Аптека №26 "Не Ска" ООО (г. Жлобин, ул. Козлова, 17а)', 'CC5128BF-2AF2-4E39-8950-B1EBDA837F0F': 'Аптека №27 "Не Ска" ООО (г. Речица, ул. Хлуса, 59а-1)', 'AB966D72-7EC5-4C5E-9926-A85D6F04645B': 'Аптека 1 ООО "Комповид" (г.Гродно, пр-т Я.Купалы, 80/3)', 'AB2B213B-71CF-492B-A052-AC072951C4F7': 'Аптека 2 ООО "Комповид" (Гродненская обл, аг. Озеры, Гродненского р-на)', '02154086-58E3-43CC-A8CA-FFA4ED704508': 'Аптека 3 ООО "Комповид" (Гродненская обл., г. Ошмяны, ул. Советская, д. 106, пом. 11)', '40C7D118-A1A1-4759-BEDC-4224AB86AF5F': 'Аптека 4 ООО "Комповид" (Гродненская обл., г. Сморгонь, ул. Ленина, дом 3-2)', '34BBE3F2-9935-4292-B844-E4E3E2FEB3FC': 'Аптека 6 ООО "Комповид" (Гродненская обл.,г. Волковыск, ул. Зенитчиков, 46)', '15766EE3-EA15-41F2-A620-2B9AFE2542DB': 'Аптека 7 ООО "Комповид" (Минская обл., г. Молодечно, ул. Буховщина, дом № 60)', 'EC4CAE90-0AE0-49EF-8399-07C8B4D8282C': 'Аптека 8 ООО "Комповид" (Гродненская обл., г.п. Красносельский, Волковысского района, ул. Школьная, )', 'B8C1808C-7F54-4A85-A542-F9229FA20E49': 'Аптека 11 ООО "Комповид" (Гродненская обл., г. Волковыск, ул. Ф. Скорины, 11, пом. 72)', 'C0217296-36DC-423A-A58E-6FB3DEECA515': 'Аптека 9 ООО "Комповид" (г. Гродно, пер.Поповича, 10-101)', 'A551C82C-16E4-4BE9-BA42-C529C6AC9D40': 'Аптека 12 ООО "Комповид" ( г.Гродно, ул.Кремко, 2-89)', '54CCDBDD-F9CE-4B11-9ACA-F9761FC6350A': 'Аптека №1 ООО "ПартнерМедитек" (г.Минск, ул. Червякова 8)', '6B507809-919F-4591-B1C2-41B8C2A8E615': 'Аптека №2 ООО "ПартнерМедитек" (г.Минск, ст.м."Пушкинска", подз. переход №1)', '5A6440A9-1608-46FD-BFC4-7779906B23CC': 'Аптека №3 ООО "ПартнерМедитек" (г.Минск, ул. Ландера, 36а)', '676FFA37-1327-4B79-BF75-BFF0153AB567': 'Аптека №4 ООО "ПартнерМедитек" (г.Минск, ул. Райниса, 17)', 'B5A4057D-052B-4C69-8B43-00889F94E181': 'Аптека №5 ООО "ПартнерМедитек" (г.Минск, ул. Калиновского, 55Г)', 'D43E1A51-7C09-4E22-80D8-09A28CC97CEE': 'Аптека №6 ООО "ПартнерМедитек" (г.Минск, ул. Сухаревская, 6)', '685805A7-E356-4161-ADD0-27F2B1213133': 'Аптека №7 ООО "ПартнерМедитек" (г.Минск, пр. Партизанский, 84А-2)', '16911021-DC46-4BD9-B842-00879278BE63': 'Аптека №8 ООО "ПартнерМедитек" (г.Минск, ул. Жуковского, д. 7, пом. 26Н)', 'F4DE6CD3-C5FB-41C2-9E5F-30A701B9B609': 'Аптека №9 ООО "ПартнерМедитек" (г.Минск, ул. Плеханова, 40-11Н/4-5)', 'D9163999-F575-428F-9209-EC1DA664DCC2': 'Аптека на Казинца (г. Минск, ул.Казинца, 49)' }, selected:[ 'D9163999-F575-428F-9209-EC1DA664DCC2', '15766EE3-EA15-41F2-A620-2B9AFE2542DB', '14014265-6A12-4AFE-A40F-E4CE6E27DE10', 'D7040BD4-0C3E-414B-A559-79791575A91C' ], name:"Тестовая форма", callbackV: 0 }; } componentDidMount() { //выкину изменение состояния в window (global) и снимок состояния в консоль var self = this; let testNewState = function(newstate){ self.setState(newstate); } console.log(self.state); window.testNewState = testNewState; let testNewCallback = function(newcallback){ self.DelphiFormHandler = newcallback; self.setState({callbackV: self.state.callbackV + 1}); } window.testNewCallback = testNewCallback; } DelphiFormHandler(e){ window.alert(e); } render() { return ( <div className="TEST"> <DelphiForm.form1 store={this.state.store} selected={this.state.selected} name={this.state.name} callback={this.DelphiFormHandler} /> </div> ); } }; ReactDOM.render( <TEST />, document.getElementById('DelphiFormTest') );
Публикация пакета в npm
В корневой директории создаем:
- файл лицензии LICENSE
- файл описания модуля README.md
- файл настроек игнорирования .npmignore с указанием соответствующих файлов и папок:
npm-debug.log .gitignore node_modules img
Синтаксис аналогичен GITHUB.
Если еще не зарегистрированы, регистрируемся (хотя удобней на сайте www.npmjs.com):
npm adduser
Далее авторизуемся:
npm login
(нужно будет ввести учетные данные)
Публикуем модуль:
npm publish
После публикации пакет будет доступен по ссылке https://www.npmjs.com/package/имя_пакета, в моем случае это https://www.npmjs.com/package/delphiform.