Close

30.01.2015

Еще один шаг к лучему GUI

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

в прошлой версии системы я синхронизировал DOM клиента и сервера и это позволяло писать весь код на сторон сервера.

Проблема была в том, что это оказалось не в тренде, появилось много Browser библиотек, новые стандарты, разгон JS, новые возможности в браузере, все переориентировались…

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

Итак, ответы на  чаяния client-side разработчика:

1) Простота. Хочу понимать, как это работает без многоуровневых диаграмм. Фреймворк и принцип его работы должны быть просты и понятны.

четыре составляющих нашего решения, которые слабо связаны, и при желании могут быть заменены на аналоги:

  • AngularLight — минималистическая библиотека 2way биндинга — связующее звено для моделей и DOM (Dirty)
    • почему я выбрал именно AngularLight — тема для отдельной дискуссии
  • aplib — собственная разработка для удобной работы с моделями
  • концепция описания компонентов — набор рекомендаций, который позволит просто делать компоненты, которые можно легко использовать в самых разных подходах к web gui.
  • роутинг. (про него тут не буду рассказывать)

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

дальше можете переходить к следующему разделу, если не интересна более техническая информация:

мини туториал, чтобы понять принцип работы

— откройте консоль в браузере в котором загружен alight.

— Наберите

scope = alight.Scope();
scope.$watch('title', function(value) {
    console.log('title =', value)
}) ;
scope.title = 'new'
scope.$scan() ;
// print
scope.title = 'linux' ;
scope.$scan() ;
// print
scope.title = 'linux';
scope.$scan() // do nothing

не сложно ведь, да? тут мы сделали  контекст,  начали следить за полем  ‘title’, и когда сделали $scan(), то система обнаружила, что значения поменялись и вызвала наш код.

Это основа. вокруг нее крутится способ делать директивы
— наберите

s.r = 'blabla'
s.$evalText('trtrtr {{v}} вот так вот {{r+v}}')

— посмотрите на API он очень прост http://angularlight.org/doc/api.html так работают текстовые директивы, в {{}} скобках могут быть выражения, или вызовы методов, или вообще разные функции (и текстовые директивы)

вот как работают директивы: официальная документация http://angularlight.org/doc/directives/

но лучше привести свой пример (НЕ ТИПОВОЙ, т.к. SVG, а не HTML)

Мне нужно было под слоем html разместить svg линии, и каждую линию я рисовал своей директивой, вот код директивы

alight.directives.sup.path=function(e,name,scope){
	var node=scope[name];
	scope.$watch(name+'._edgeCoord()._edgeCoord', function(c){
		if(c[0]){
			var v='M'+c[0]+','+c[1];
			v+='C'+c[2]+','+c[3]+',';
			v+=c[4]+','+c[5]+',';
			v+=c[6]+','+c[7] ;
			$(e).attr('d', v);
			$(e).attr('visibility', 'visible');
			$('svg').attr('width' ,(scope.nodes.reduce(function(prev, node){ return Math.max(prev, node.left)}, -1) +20)*dpi.get(false) );
			$('svg').attr('height',(scope.nodes.reduce(function(prev, node){ return Math.max(prev, node.top)}, -1) +20)*dpi.get(false) );
		}else{
			$(e).attr('d', 'M0,0');
			$(e).attr('visibility', 'hidden')
		}
	}, {readOnly : true});
}

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

<path 	fill="none" 
		stroke="rgba(0,0,0, 0.7)" 
		stroke-width="2" 
		stroke-linecap="round" 
		style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); stroke-linecap: round;"
		d="M0,0M1,1" 

		al-repeat="nodeid in helper.nodesIDs()"
		sup-path="nodeid"

		></path>

Например, опытные кодеры заметят связи с конкретным контекстом помимо прав самой директивы ( nodeid in helper.nodeIDs() )   а также доступ к другим элементам не только к внутренним для e, например, (  или $(‘svg’)  ). для моего случая это допустимо, и позволяет быстрее решить задачу, быстрее получить прототип… просто AL в основном для HTML, но я показал, что даже с SVG использовать  его удобно.

Cуть такая, есть директивы а) текстовые — они позволяют вставить в разметку текст или в атрибуты значения из модели, например так:

а есть директивы  б) “для элемента”, которые могут сами что -то делать с элементом, как sup-path из примера выше — более гибкий вариант.

я рекомендую придерживаться следующих правил, чтобы избежать проблем: (это касается не только AL (Angular Light), но и других подобных библиотек).

  1. следи за зависимостями в модели — модель должна исключать гонку изменений по цепочке подписок
  2. не подписывайся на изменения какой-то части модели, если в обработке эта часть модели будет изменяться (ajax на изменение)
  3. если по событию DOM меняется состояние модели и по этому же событию хочется обработать изменение — не делай так, лучше подпишись на обработку события, а обработку произведи в каллбеке для setTimeout( callback, 0); — так будет гарантировано, что обработка вызовется после изменения всех состояний, и состояние которое она прочитает будет корректным (порядок срабатывания каллбеков при обработке событий не гарантируется браузерами)
  4. подписывайся на изменения части модели только если знаешь, что в череде обработок и изменений не будет зацикливания — если прослеживается dataflow без рекурсии. если прослеживается — все обработки лучше подписывать на dom события, не на части модели.
  5. не используй параллельно с AL «jQuery» для «лазания по DOM» — нарушаешь свободу дизайнера менять интерфейс независимо от разработчика. (это можно в деклатаривных директивах, но не в БЛ коде)
  6. не вызывай $scan до тех пор пока это не станет необходимо — вызывай его как можно реже.
  7. в больших моделях конструируй свои контексты и привязывай их (например для компонентов), чтобы добавить зернистость, увеличить производительность и не засорять родительские контексты, благо , что в AL это в удовольствие — не то, что в других фреймворках.
  8. Используй , по возможности, дочерние контексты вместо корневых
  9. пиши свои директивы и библиотеки директив. в AL это очень просто, проще нет нигде. производительность и наглядность увеличивается в разы.

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

2) Высокая скорость разработки. Не хочу городить тонны кода, чтобы выполнить простецкую операцию.

  1. ты не должен сам делать операции вообще. смысл декларативного подхода в том, что ты говоришь:  “это должно быть. Работаешь только со своей моделью, остальное делает система сама”.
  2. скорость разработки. Основная трудоёмкость связана не самим фреймворком, много труда уходит на работу со своими моделями.
  3. Прочие фреймворки (и AL тоже) уделяют внимание именно отображению модели в DOM, но не самой модели и работе с ней. Для того чтобы упростить с ней работу нужно иное средство.

Тут я предлагаю воспользоваться специально разработанной для этого библиотекой “aplib”, предназначенной для упрощения и сокращения объёма кода, работающего с вашей моделью (ViewModel) .

TODO ссылка на aplib, ссылка на видео с ее использованием и принципом работы.

 Итак, модульная библиотека для работы с моделями я назвал ее aplib.js.

Эта штука сильно упрощает работу с dirty check моделями, может быть использована для AngularLight а также для Angular и вообще где угодно в коде.

важно: на данный момент поддерживаются браузеры где есть JS getter \ setter, т.е. ЛЮБОЙ, но IE>=11. При моем желании я могу сделать версию под любой (старый) браузер, но лично я смысла пока в этом не вижу совсем.

проблемы с которыми борется данная библиотека хорошо/подробно писаны тут: Gui_проблемы.

ситуация:

  • есть модель на JS, которая пришла в браузер, например с сервера (AJAX, либо встроена в код страницы) не важно как
  • в этой модели, например,  иерархия/список иерархии/списков итд объектов — по смыслу это объекты определенного класса с каким то способом идентификации (идентификаторами). или контейнеры для них
  • возможно, там уже один и тот же объект фигурирует несколько раз в разных местах, но это JSON и после десериализации это для JS VM разные объекты (разные копии объекта заданного класса с одинаковым ид)
  • объект каждого класса может содержать или нет свойственный ему набор полей, для каждого поля нам известно его значение по умолчанию
  • в процессе работы JS  эти объекты будут меняться и связываться друг с другом иначе чем были, буду появляться мапы одних объектов/ид на другие объекты/ид, какие-то списки, появляться новые объекты

 типовые задачи:

  1. если у объекта некоего класса по нашему разумению не хватает свойственных классу полей — они должны появится, чтобы у нашего кода было меньше шансов вылетать, мы не ддолжны за этим следить
  2. у каждого класса объектов могут быть методы (для целей GUI) — они должны появится
  3. у каждого класса объектов могут быть методы для работ с серверной частью (Бизнес логика) — она должна иметь возможность тоже там появится
  4. много копий одного и тогоже логического объекта должны меняться синхронно, не важно какую копию я меняю.
  5. простой доступ ко всем копиям по ид. PUSH с сервера — нужно для объекта с ид=3, класса=мой_объект изменить поле=название на новое значение. как быстро его найти и сделать чтобы это касалось всех таких объектов?
  6. Аспекты, расширение базовых операций. если любые действия с объектом должны логироваться или специально обрабатываться, например, синхронизироваться с сервером — это должно происходить само

 код в студию: описание наших классов (классы Бизнес-логики)

для решения задач требуется воспользоваться aplib.js и всего делов 😉

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

Описание «ваших классов»

ap.jsdesc['server'] = { // server - Имя нашего класса
	fields:{            // набор полей со значениями по умолчанию
			name: 'имя',
			address: 'http://127.0.0.1:8888', 
			connected: false,
			s_ostype:'Linux',
			s_db_type: 'mysql',
			r_db_state: '',
			r_nginx_state: '',
			r_jboss_state: '',

			checked: false, 
	},
	methods:{
			getid:{      // один из методов, который появится во всех экземплярах этого класса
				code : function(){
					return { getid: ap.walk.jslabels.objid.id(this) }  // this - это сам экземпляр класса, возвращаем словарь
				}
			}
	}
};

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

допустим, вам из ajax пришла структура данных, в которой объекты типа server помечены метками objid, тогда опишем эти метки.

ap.walk.jslabels = {
	objid :{
		classname :function(o){
			var a = $(o).attr('objid');
			return a.split('_')[0];
		}, 
		id : function(o){
			var a = $(o).attr('objid');
			return a.split('_')[1];
		}
	}
}

тут есть две функции, одна возвращает имя класса, другая ид объекта. “o” —  объект.

при таком описании, если objid=”server_5” получится, что система распознает объект как экземпляр сервера с ид=5.

 давайте приведем пример как это работает. например из ajax к вам пришла структура

servers=[
   {name:'s1', objid:"server_1"}, 
   {name:'s2', objid:'server_2'}, 
   {  someinfo:1, 
       S: {
          objid:'server_2',
          address:'http://192.168.11.1'
       }
   }, 
   {a:1}, {b:2}, {c:3}
];
servers

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

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

а теперь сделаем с ними:

ap.walk.walkJS(servers);
servers

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

лог моей консоли:

servers[0].name
"s1"
servers[0].address
"http://127.0.0.1:8888"
servers[1].getid().getid
"2"
servers[1].name
"s2"
servers[1].address
"http://192.168.11.1"

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

ap.walk.getObject('server', 2).name='s2_2';

эта маленькая штучка делает код работы с моделями очень чистым и простым — больше не надо лазать по всей модели чтобы поменять какой-то объект, и помнить где он может быть. ООООЧЕНЬ ускоряет разработку. т.к. вы просто работаете с данными и не делаете лишних действий совсем, это удобно делать прямо на JS. 

Лирика:

кроме  того, модификации происходят более глубокие, например, появляется возможность избежать кодирования на JS, т.к. происходит синхронизация View модели , аналогичная синхронизации DOM дерева из прошлой итерации системы (ссылка на верху статьи).  зачем это надо ? да ведь манипуляция с View происходит из View контроллера, который часть контроллера вообще. а он часть Бизнес-логики. а ее лучше располагать на сервере.

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

логика для View может быть в браузере, остальная логика может быть на сервере или в браузере…

возможно это долгий спор, но я вам скажу факт: когда я делаю прототипы приложений я пишу логику на сервере и не пишу ее в браузере, данный механизм автоматической связи моделей клиента и сервера ускоряет мою работу СУЩЕСТВЕННО.

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

3) Гибкость (чтобы не приходилось городить костыли, если реальная задача чуть уходит от той, что показана в презенташке на офф. сайте).

вам повезло, APLIB — РАСШИРЯЕМАЯ.

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

например,

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

 TODO ссылка на пример, в котором к работе с моделями

  •  добавляется логирование записей в заданные поля экземпляров какого-то класса,
  • оборачивается метод
  • добавляется свой раздел в описание модели “sync”

============   ОСТАЛЬНОЕ УБРАТЬ — СЛИШКОМ МНОГО TODO  ========

4) Удобная связь с данными. Чтобы было удобно делать запросы и удобно использовать/отрисовывать полученные данные.

AL как раз и делает эту связь . Я считаю — она простая и удобная, и главное контролируемая.

> удобно делать запросы…

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

> удобно отрисовывать данные…

Имеете ввиду canvas? — Да, бывает — в декларативном мире императивные операции иногда тоже нужны.

Например, есть модель, часть ее отрисовывается на DOM за счет биндингов, а часть ее отрисовывается на CANVAS какими-то функциями… Тут есть применение для $watch. TODO пример

короче это тоже удобно делать.

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

5) Модульность. Без нее нынче никуда.

 Это отдельная проблема решается отдельными средствами.

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

Фреймворк не обязан навязывать решение этой мелкой частной проблемы.

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

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

третья составляющая решения — компоненты. Например, Polymer — барахло, но вектор у них верный.

Есть возможность описать компонент так, что его легко будет использовать из :

  • простой JS  или Jquery код, на подобии jquery-ui рекомендаций
  • Mustache like templates, Текстовые шаблонизаторы
  • DOM based шаблонизаторы
  • dirty биндинги
  • observable биндинги
  • крепить внешнюю модель в качестве контроллера компонентов
  • прочие пока не известные методы.

все эти способы можно перемешивать. Ещё ни один фреймворк  не предоставил нам компонентов столь свободных в стиле использования.

Это сильно расширяет $ рынок для написанного компонента.

Но здесь не то место чтобы описывать идеологию компонентов (а без идеологии в случае с ними — никуда). Это предмет отдельной статьи.

6) Удобство отладки. В том фремворке, с которым приходится работать сейчас, очень часто возникает ситуация, когда все падает и выдается сообщение типа «что-то пошло не так». Без стектрейса, без указания, где хоть примерно упало. Просто упало и все.

 Понимаю, из-за этого невозможно пользоваться, например, handlebars, который выдает при компиляции шаблона описание ошибки, которое ссылается не туда и вообще бесполезно…

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

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

 TODO как отлаживать сложные ошибки

 Что надежно уменьшает число ошибок ?

TDD — Test driven development, орг. способы менее надежны.

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

 

7) Естественно, сайт на выходе не должен тормозить.

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

AL один из самых быстрых биндингов (и быстрее кода на jquery), по балансу цена/качество он лучше всех рассморенных (одна из главных причин для его выбора)

Ссылка, убедитесь в производительности сами

8) Сгененированного кода не должно быть очень много. Иначе исходный код страницы станет нечитаем. Да и вообще, хотелось бы как можно меньше генерируемого кода.

 сгенерированного кода вообще нет.

есть только наглядный не замусоренный исходный код

В html — верстка, компоненты, красОты. JS кода мало — там View модели и роутинг.  Может быть часть БЛ в JS, может не быть.  всякие лаяуты лучше оформить в виде декларативных компонентов… короче КПД кода близок к 80%.

9) Должна быть возможность масштабировать полученное приложение.

 масштабировать gui? в каком смысле?

если имеется ввиду  делать приложения из готовых приложений и делать их композиции… тогда:

это все архитектурой определяется, а именно:

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

 Это зависит от разработчиков приложений! ну не может фреймворк этого однозначно задать.

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

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

 


 

До новых встреч!

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

особенно на мой взгляд интересны

  • варианты которые я пробовал, и к каким выводам приходил.
  • сравнения фреймворков.
  • истории реальных применений
  • или как я нечаянно написал свой Ember.js а потом узнал, что он уже есть (блин как две капли воды получилось) и тут интересен именно путь, видимо разработчики Ember в мыслях шли тем же путем.  и в итоге сейчас я досконально могу рассказать минусы подобных решений.
  • размышления о возможности совмещения фреймворков и способа описания компонентов
  • критика существующих решений. тут накопилась гора из критики  23-х фреймворков, которые я анализировал когда делал прошлую версию системы, и в этот раз добавилось еще с десяток, но это уже по большей части клиентские JS штуки.
  • сравнение концептов и архитектур современных библиотек биндинга, особенно глубоко я сравнивал  Angular и Knockout. ну прямо диаметрально противоположные… хотя и разные весовые категории, но концепты очень выпячены у них. а также базирующиеся на пересоздании узлов DOM, и  с шаблонными подходами, типа dombars…  смотрел и на экзотику типа transparency.
  • еще интересно : на каждом из этих фреймворках я написал одно и то же не самое простое  приложение, и приходилось каждый раз придумывать свой костыль, хоть один но надо было. Тут интересно сравнить эти костыли и решить для себя, что наиболее приемлемо.

 

3 Comments on “Еще один шаг к лучему GUI

Александр Петров
31.01.2015 в 13:06

> мне нужно было под слоем html разместить svg линии, и каждую линию я рисовал своей директивой, вот код директивы

было бы хорошо показать результат (скриншот, например). По коду не очень понятно, что происходит.

>тут сразу много всего можно обсудить,а в коде я использовал эту директиву так:
< пусто?>

>проблемы с которыми борется данная библиотека хорошо/подробно писаны тут: Gui_проблемы.

эта ссылка (Gui_проблемы), наверное, доступна только участникам проекта в Redmine

Ответить
Александр Петров
31.01.2015 в 13:07

И я бы сократил заметку до 3х первых пунктов — они хорошие. конструктивные. остальное — уже просто разговор с невидимым читателем, кода нет, много todo, много просто мыслей «обовсем»

Ответить
Михаил
02.02.2015 в 14:45

сократить заметку можно, но там же есть ответы…, может ответы сократить сделать меньше слов?
ладно, думаю надо ее менять.

Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *