yandex-ui / noscript Goto Github PK
View Code? Open in Web Editor NEWNoscript: JavaScript MVC Framework for building SPA
Home Page: http://yandex-ui.github.io/noscript/
License: MIT License
Noscript: JavaScript MVC Framework for building SPA
Home Page: http://yandex-ui.github.io/noscript/
License: MIT License
Сейчас генерируется такой json:
{
views: {
app: {
left: {
block1: true
},
right: {
block2: true
}
}
},
models: {},
params: params,
location: document.location
}
Все модели запихиваются в один объект
{
'model.id': 'data'
}
У этого варианта есть существенный минус - нельзя записать две одинаковых моделей с разными ключами.
Мы используем такой формат:
{
"block": [
{
"block": [
{
"name": "block",
"lazy": false,
"handlersKeys": {
"handler1": 0,
"handler2": 0
},
"block": []
}
],
"handlersKeys": {
"handler1": 0,
"handler2": 1
},
"name": "app",
"lazy": false,
"params": {}
}
],
"handlers": [
{
"name": "handler1",
"key": 0,
"data": {}
},
{
"name": "handler2",
"key": 0,
"data": {}
},
{
"name": "handler2",
"key": 1,
"data": {}
}
]
}
Еще можно сделать вот так и запихать данные прямо к блоку.
{
"block": [
{
"block": [
{
"name": "block",
"lazy": false,
"handlersKeys": {
"handler1": {},
"handler2": {}
},
"block": []
}
],
"handlersKeys": {
"handler1": {},
"handler2": {}
},
"name": "app",
"lazy": false,
"params": {}
}
]
}
Мысли, идеи?
Например, для lazy-view init вызывается два раза, а show/hide никогда.
Надо все перепроверить и покрыть тестами.
Делать им отдельный класс no-box не имеет смысла.
Видимо будет лучше, если noscript будет в каком-нибудь другом неймспейсе.
Например, ns.
.
А no.
останется для модулей из nommon.
Я уже, кажется, задавал этот вопрос. Но не помню к чему пришли. :)
Сейчас в боксе не может быть lazy-view, причем это ограничение приходит из-за того, что у box нет no.layout.view(), из которого можно узнать типы view внутри него.
Создавать внутри бокса фейковые view только ради того, чтобы сделать lazy, мне кажется странным
Чтобы можно было их биндить без ручного $(this.node).on(...)
+ они должны автоматически анбиндится при destory
Пока сделаем простой вариант для переходов по страницам
Сейчас у нас есть некие константные флаги в no.model.js.
Они реализованы как свойства no.Model.prototype
.
Плюс от этого в том, что можно коротко писать в разных местах this.STATUS_FOO
и т.д.
Минус -- невозможно эти флаги использовать вне методов модели. Ну, можно считать это типа инкапсуляцией...
У меня в no.layout.js тоже возникла нужда в кое-каких константах. В частности, для боксов, блоков и асинхронных блоков. Никаких прототипов у меня там нет, так что куда их запихать -- непонятно.
Можно написать так:
no.layout.BOX = 'box';
no.layout.VIEW = 'view';
...
Но длинновато выходит.
Я вот предлагаю такой вариант. Сделать один файлик no.consts.js
И положить туда все в таком виде:
// Константы для моделей
no.M = {};
no.M.STATUS_FOO = 'foo';
...
// Константы для лэйаутов.
no.L = {};
no.L.BOX = 'box';
// альтернатива -- использовать подчеркивание:
no.L_VIEW = 'view';
...
...
У нас не так много сущностей в принципе: model, view, layout, request, update, ...
Вполне можно им однобуквенный префикс выдать.
/cc @doochik
Сейчас есть такой формат
no.router.routes = [
'/', '-> /inbox',
'/inbox', 'messages',
'/message/{mid:int}', 'message'
];
Вроде все ок, но мне не нравится плоский массив.
Можно переделать на хеш и надеятся на тот же порядок элементов в нем (что впринципе сейчас работает).
no.router.routes = {
'/': '-> /inbox',
'/inbox': 'messages',
'/message/{mid:int}': 'message'
};
Можно переделать на двумерный массив
no.router.routes = [
['/', '-> /inbox'],
['/inbox', 'messages'],
['/message/{mid:int}', 'message']
];
// Базовый лэйаут.
no.layout.define('app', {
'app': {
'head': true,
'@left': {
'folders': true,
'labels': true
},
'@right': {}
}
});
// Наследование.
no.layout.define('messages', {
'app @right': {
'messages': true
}
}, 'app');
// Альтернативный вариант (кажется, такой вариант хуже,
// если будет вложенность побольше, то будет очень громоздко).
no.layout.define('messages', {
'app': {
'@right': {
'messages': true
}
}
}, 'app');
// Еще наследование, но уже с интерполяцией.
no.layout.define('setup', {
'app @left': {
'setup-menu': true
},
'app @right': {
'setup-{ .tab }': true
}
}, 'app');
// Тоже самое, но с функцией.
no.layout.define('setup', {
'app @left': {
'setup-menu': true
},
'app @right': function(params) {
if (params.tab) {
return {
// FIXME: Не уверен, что тут нужна интерполяция.
'setup-{ .tab }': true
};
}
return {
'setup-index': true
};
}
}, 'app');
Discuss.
Так как все работает через события, вроде как логично сделать по аналогии с событиями в no.view
no.Model.define('model', {
events: {
changed: [
// массив, потому что иногда приходится вешать несколько несвязных обработчиков
function(){...},
function(){...}
]
}
})
Может уберём из репа общий .jshintrc
?
А то сейчас там куча ошибок при анализе файлов.
А у меня стоит git hook для проверки всех js файлов изменённых jshint'ом при коммите.
Бывает такое:
Сейчас такое поведение было при листании фоток, когда не приходили комменты: вдруг фотка (текущая) заменялась предыдущей.
Можно сделать через проверку:
update.id < no.Update.update_counter
- это будет означать, что текущий update - не последний созданный, а значит его не надо дальше выполнять.
Вопрос только: не будет ли у нас когда-то ситуации с несколькими update-ами на одной и той же странице.
Нужно задать:
из #8:
Если есть do-запрос, в котором есть дублирующий запрос другой модели, то этот запрос должен быть более приоритетный, чем первый. Пример, request1 = [model1], request2 = [do-action-with-model1, model1].
@pasaran предложил декларативно указывать do-моделям какие модели он инвалидирует при успешном запросе.
надо собрать разные варианты и подумать
Кажется, надо добавить наследование событий.
Актуально как для no.View
так и для no.Model
.
Вообще есть мысль, что эти два объекта сильно похожи:
params
key
и какой-то кэшno.Events
амиВозникает ощущение, что view это специфический тип модели со своими правилами кэширования, у вас нет?
/cc #52
У многих блоков нужно делать что-то на htmlinit
или show
. Если я правильно поняла, для этого надо каждому из них добавлять в events такой кусок: 'htmlinit .': 'onhtmlinit'
и тд. Может быть, сделать, чтобы стандартные методы биндились по умолчанию на эти события?
Отложенные - это когда дорисовка происходит не сразу, а по какому-то действию или событию.
Например, различная доп.информация с сервера.
Есть балковые запросы. В первый запрос запрашиваем 100 ключей, в следующий запрашиваются тоже 100, но 90 из них пересекаются с первым запросом.
Хочется, чтобы во второй запрос ушли 10 уникальных ключей, но в ответ подклеились остальные 90 из кеша.
Обсуждаем...
@pasaran @shirokoff @artjock @chestozo @mmoo
Сейчас у нас есть такой кейс:
если у view не заданы params
мы строим ключ по параметрам его моделей.
Вопроса 2:
если у модели есть дефольтное значение параметра — надо ли его включать в ключ?
если у view 2 модели, у обеих есть параметр p
(num_per_page
какой-нибудь), но с разным дефольтным значением и в урле этот параметр не задан: какое значение должно попасть в ключ view? Если вообще должно попасть.
Пока писал вопрос номер 2 понял, что эти дефольтные параметры, видимо, не надо в ключ view прокидывать. Но вопрос оставлю, вдруг кто-то считает иначе.
/link #18
Обсуждаем...
@pasaran @shirokoff @artjock @mmoo @chestozo
Надо как-то указывать root и уметь строить от него правильные урлы.
Если у меня приложение находится по адресу example.com/some-root-path/...
, то no.page из location должен сам вынимать все, что после /some-root-path/
, а после преобразований (редирект, например) правильно выстраивать его обратно.
Связано с #5:
при отмене no.update
хочется иметь возможность стопнуть (мягко) выполняемый no.Request
.
Мягко подразумевает:
дозапросить запрошенные данные, положить их в кэш, но новые данные не запрашивать!
Вот у нас по коду встречается разное:
var _a = {};
var b_ = {};
Давайте договоримся, что когда использовать.
К примеру, я помню такие правила:
this._p
window_
Но вот зачем писать внутри функции var _v = 42;
я не понял.
Давайте классы сделаем не просто view
, а no-view
или типа того? Мало ли что там в вёрстке.
Есть балковые запросы. В первый запрос запрашиваем 100 ключей, в следующий запрашиваются тоже 100, но 90 из них пересекаются с первым запросом.
Хочется, чтобы во второй запрос ушли 10 уникальных ключей, но в ответ подклеились остальные 90 из кеша.
Обсуждаем...
@pasaran @shirokoff @artjock @chestozo @mmoo
https://developer.mozilla.org/en-US/docs/DOM/element.insertAdjacentHTML
innerHTML понятно проиграет
нужно найти тесты
Пример:
no.layout.register('photo', {
// ...
'counters': {
"div#preview a.original": {
'click': { 'cid': 123, 'name': 'photo.original.click' },
'show': { 'cid': 124, 'name': 'photo.original.show' [, 'element': '.contols-panel' ] }
}
}
});
Аналогично для no.View.register
Кейс такой:
есть фотка из альбома A
есть фотка из альбома B
когда показывается фотка из альбома A также показывается сам альбом A в слайдере
По клику на фотку -- нужно показать следующую фотку.
Это делает слайдер: он слушает глобальное событие photo:next
и когда слышит его -- берёт следующую по очереди фотку и перезагружает страницу.
Если мы посмотрели вначале фотку из альбома A, а потом посмотрели фотку из альбома B у нас будет 2 слайдера на странице:
один текущий видимый по альбому B
второй скрытый по альбому A
Глобальное событие слушают оба.
Оба пролистнут на следущую фотку, когда услышат глобальное событие.
Решений вижу несколько:
photo:next
, а photo:album:3:next:
. Проблему решает, но гемороя добавляет.Сейчас роутер умееть только по урлу определить какой лейаут показывать. Этого не всегда достаточно. Хочется иметь алиасы для различных страниц.
Например,
/inbox
хочется преобразовывать в /folder/<id>
/unread
-> /folder/<id>/?filter=unread
Пока я себе это представляю, что page.go должен работать в два прохода, сначала алиасы (реврайты), потом уже router.
Причем эти алиасы могут работать по той же схеме и с такими же декларациями, что и роутер. Можно даже прямо в него это все зашить.
Модуль для запроса моделей с сервера.
Есть два типа запросов: get (запрос на чтение) / put (запрос на изменение, do-запрос).
no.request должен:
Я сейчас вижу, что js файлы подключаются только в тестах.
Я, к примеру, не понял, откуда в noscript появился no.jpath()
:)
Гипотетическая ситуация, но более-менее приближенная к реальной.
Вот есть к примеру три блока (условно): тулбар, список писем, письмо.
В тулбаре есть кнопки с действиями с письмами.
В списке писем хранится информация про выделенные письма.
Письмо (если оно открыто) -- какбэ тоже выделено.
Тулбар хочет в какой-то момент получить список выделенных писем. Как это сделать?
С ходу. Тулбар кидает глобальное событие get-selection
. Список писем и письмо подписываются на него как get-selection@show
-- тогда разные списки писем и письма не будут все на него отвечать, ответит только тот блок, который сейчас виден.
Как он может ответить? Ну можно делать no.events.trigger('get-selection', callback)
-- вроде ок, но тут есть нюанс, если в данный момент нет вообще ни одного письма или списка писем, то никто не ответит и callback
не будет вызван. Т.е. определить, что ничего не выбрано в этом случае не удастся.
Кидать в ответ событие и ловить его в тулбаре -- примерно так же.
Короче, нужно придумать простой способ запросить у какого-нибудь блока (не конкретного!) что-нибудь и либо получить этот ответ, либо сразу узнать, что ответа нет.
(да, конкретно в этом случае лучше, видимо, не запрашивать список выделенных, а кидать его сразу, при каждом изменении выделения, но не суть)
Смотрим на тред #49 )
Мне не нравится, что события надо писать вот так htmlinit .
.
Давайте поменяем семантику или сделаем ее по-умолчанию? А для глобальных событий указывать, например, *
И сделать объявления типов прямо в урле /page/{pege_id:id}
Я бы сделал это параметром. Иначе сейчас никак нельзя сделать, чтобы две одинаковые do-модели, не делали два одинаковых запроса.
#8
Необходима базовая имплементация бесконечных списков. Например: список писем в Я.Почте или список файлов в Я.Диске.
Можно декларировать как-то так:
no.router.routes = [
'/', '-> /inbox',
'/somepath', '-> http://example.com/',
]
когда нет данных, или нет моделей
Нужно делать (issue #39)
issue #26
Cейчас это примерно такое дерево:
{
models: {},
layout: {},
params: {}
}
Проблемы:
В models
кладуться все модели сплошным списком и проблема может возникнуть, когда там будет две модели с одним и тем же model.id
.
Ещё одна проблема, когда мы рендерим какую-то часть дерева и хотим обратиться к другой части дерева, но в контексте какой-то родительской ноды:
Решения могут быть:
apply chroot(/.layout.view1) view1-template
apply { view: /.layout.view1, params: /.params } view1-template
Почти сделано в (issue #21)
что-то про контексты и неймспейсы в событиях (issue #40)
Может переименовать в bulk request (issue #33 )
Иногда ключ view нужно строить по некоторому набору параметров из урла, а не по всему набору.
Пример:
есть view "слайдер с фотками"
В этом слайдере отображаются фотки из текущего контекста.
Контекстом может быть: альбом, или, к примеру, популярные фотки.
// view slider.
no.View.define('slider', {
params: {
context: null,
author-login: null,
album-id: null
}
});
// Контекст альбома:
var url = '/user/vasya/album/3/view/77';
var slider_view_key = 'author-login=vasya&album-id=3'; // Ключ ок.
// В контексте популярных фоток.
var url = '/popular/user/vasya/album/3/view/77';
var slider_view_key = 'context=popular&author-login=vasya&album-id=3'; // Такой ключ -- это fail.
var slider_view_key_good = 'context=popular'; // Нужно так!
Можно взять из Дарьи.
Экшены обрабатывают click, dblclick, mouseenter, mouseleave, submit
.
class="no-action" data-action="name" data-params="foo=1&bar=2"
Надо сразу предусмотреть, чтобы иметь возможность получить инстанс View в рамках, которого вызван экшен (находится нода event.currentTarget).
Так же мы реализовали удобную возможность дергать счетчики Метрики при вызове. Сейчас это параметр metrika, но мне кажется надо это вынести в отдельный data-атрибут data-counter=""
Или как угодно оно называется.
Сейчас есть приватный метод, который вызывается апдейтером.
Но ему нужно какой-то там tree передать, layout и т.д.
Я хочу быстро получить объект, который можно засунуть в шаблонизатор для этого блока. Со всеми делами -- параметрами страницы, моделями и т.д.
И более того, хочу так уметь писать:
var html = view.tmpl(mode) // mode -- опциональный параметр
при этом соберется нужное дерево, запустится шаблонизатор с нужной модой.
Предположим есть блок, в котором есть бокс. И в боксе лежат какие-то другие блоки.
Если мы по какой-то причине инвалидировали верхний блок, то для него будет сгенерен новый хтмл, но бокс не пересоздастся и не инвалидируется.
Т.е. бокс будет по-прежнему смотреть на старую ноду и перестанет работать (точнее он будет там где-то на детаченной ноде работать).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.