Basics: Core | Unit | Load | Data | View | MVVM | Example >> Advanced | FAQs
There are no dependencies, just reference one JavaScript file:
<script src="pro.all.min.js"></script><!-- <3 KB -->
<!-- Or -->
<script src="pro.all.js"></script> <!-- <23 KB -->
The framework defines short aliases for popular DOM-methods:
pro.id('element-id'); // Gets element by id
pro.class('class-name'); // Gets elements by class name
pro.tag('tag-name'); // Gets elements by tag name
document.on('some-event', fn); // Adds an event listener
document.no('some-event', fn); // Removes an event listener
let element = pro.element('div|span|a|...'); // Creates a new element of specified tag
element.proId('child-element-id'); // Gets child element by id
element.proClass('class-name'); // Gets child elements by class name
element.proSelector('css-selector'); // Gets child elements by specified selector
element.proTag('tag-name'); // Gets child elements by tag name
element.to('attribute', value); // Adds attribute with optional value, e.g. element.to('hidden')
element.out('attribute'); //Removes attribute from element, e.g. element.out('hidden')
element.is('attribute'); // Checks that element has attribute
element.toClass('class-name'); // Adds css-class to element
element.outClass('class-name'); // Removes css-class from element
element.on('event', listener); // Adds an event listener
element.no('event', listener); // Removes an event listener
element.toChildFree(); // Removes all childs (makes an element child free)
pro.core | To top >>
Provides sync event-based programming model with fluent on/once/no/out
interface. Sync model means that when any component triggers some event, all it's listeners are executed immediately.
Use pro.core
constructor-function to create complex Pro-JS components:
var component = new pro.core();
/* Subscribe on event. If event was already triggered, listener is executed
immediately with last event object. To override this pass the third 'skipLast' argument as true. */
component.on('event', function (eventData /*, function callback() { 'I am optional'; } */) {
console.log('Event was triggered: ' + eventData);
}/*, true */);
// Triggers event. Optional the third callback can be executed after all listeners (* bug here *)
component.out('event', 23 /*, function () { console.log('Well done!'); } */);
// Event was triggered: 23
// Well done!
// The same as this short alias:
component.event(23 /*, function () { ... } */);
pro.unit | To top >>
Introduces app-unit with states concept and own DI:
var app = new pro.Unit(); // Initializes a new application unit
app.unit('NewsStore') // Defines 'NewsStore' unit inside of the application
.out(function () { // Initialization function. 'this' refers to the unit itself
var me = this;
this.on('load-news', function (eventModel) {
var newsModel = retrieveNews(eventModel);
me.out('news-loaded', newsModel); // Notify all listeners
//me.newsLoaded(newsModel);
// Syntax sugar for the line above in case some listener exists
});
...
// Or notify about smth else
this.out('any-event', eventDataIsOptional);
});
app.unit('NewsList') // Defines 'NewsList' unit
.on('NewsStore') // Optional list of unit dependencies
.out(function (newsStore) {
var me = this;
me.state('no-news') // Defines state
.to(function () { // Execute on entering into the state
proId('blank-text').out('hidden'); // Removes 'hidden' attribute
}) // Returns state object with 'out' method
.out(function () { // Optinal, execute on leaving the state
proId('blank-text').to('hidden'); // Adds 'hidden' attribute
}) // Returns current unit object
.state('news')
.to(function (news) { ... });
me.to('no-news'); // Go to initial state
newsStore.on('news-loaded', function (newsList) {
if (newsList && newsList.length > 0) {
me.to('news', newsList);
} else {
me.out('empty'); // Notify 'NewsList' unit subscribers that news list is empty
}
});
});
Define hierarchical states (separated with periods, e.g. 'news.expanded'). See sources for more details.
In case you have to violate SOLID world, consider Service Locator
sin:
// Somewhere you can not define unit with injected dependency
// and have access only to your application instance
app.on('auth-unit', function (authUnit) {
authUnit.to('login');
});
pro.load | To top >>
Subscribes on DOM-tree traversal and loads HTML content for elements with 'pro-load' tags:
<div pro-load="news-component.html"></div>
Content for the element above will be downloaded from the specified url. Nested 'pro-load'-elements are supported, content for them is loaded immediately.
To handle situations with html missing, subscribe on pro.load
404 event:
pro.load.on(404, function (elementInfo) {
// console.log(elementInfo.url + ' was not loaded');
// elementInfo.element.innerHTML = 'Content is missing';
});
Subscribe on success loading event to manipulate with loaded markup:
pro.load.on(200, function (elementInfo) {
// elementInfo.element
// elementInfo.url
});
You can subscribe on any status code in a similar way.
In case your code unit depends on this markup, use pro.load
object:
pro.load.on('news-component.html', function (newsContainerDiv) {
// After markup loading and all pro.load.on(200, ...) listeners
app.unit('NewsList')
.on('NewsStore')
.out(function (newsStore) { ... });
});
pro.data | To top >>
Introduces observable wrapper over JS-objects and arrays:
var sampleModel = { topic: 'Sample', text: 'Observable model' },
news = pro.data(sampleModel); // Or define empty observable: 'pro.data();'
news.topic.on(function (topic) {
// On topic change
}); // See the line below which triggers this listener
news.topic('New topic');
news.on(function (model) {
// On the whole news change
}); // See the line below which triggers this listener
// As well as the listener above, because topic is changed too
news({ topic: 'New article', text: 'Text' });
// Read current value:
var topic = news.topic(); // Evaluated into 'New article'
var value = news(); // Evaluated into an object: { topic: 'New article', text: 'Text' }
Observable arrays:
var modelArray = [model],
newsList = pro.data(modelArray);
newsList[0].topic.on(function (topic) {
// On the 0-th element's topic change
}); // See the line below which triggers this listener
newsList[0].topic('Indexation as for ordinal array!');
newsList[0].on(function (article) {
// On the first news change
}); // See the line below which triggers this listener
// As well as the listener above, because topic is changed too
newsList[0]({ topic: 'Whole article changed', text: 'Text' });
function onChange(list) {
// On news list change
}
newsList.on(onChange); // See the line below which triggers this listener
// As well as two listeners above
newsList([]); // * Here I have a bug - only the last listener was executed
// Read current value:
var value = newsList(); // Evaluated into an empty array
Use no
method to unsubscribe listener from data changed event
newsList.no(onChange);
Initial object is changed with observable as well.
pro.view | To top >>
Introduces UI-view which can be binded to model. Sample with markup loaded via pro-load
:
...
<article pro-load="news-template.html" hidden></article>
</body>
news-template.html
markup:
<h2 pro="text(topic)"></h2>
<p pro="text(content)"></p>
pro
tag will be explained in the next section.
pro.load.once('news-template.html', function (view) {
'use strict';
// Define view named 'news-view'
pro.view.name('news-view')(function () {
return view.cloneNode(true);
// Or create new element
// Or get element by css-class / id
}) // It was markup-factory function
.on(function (model) { // Executed on model binding
this.out('hidden');
});
});
Now you can mention this view in two ways:
- Imperative way via JS:
newsList.forEach(function (newsModel) {
pro.view.out('news-view', newsModel, function (newsNode) {
// Model-binded view node is passed
proId('news-container').appendChild(newsNode);
});
});
- Declarative way via MVVM-pattern introduced in
pro.mvvm.js
-file below:
pro.mvvm | To top >>
Implements Model-View-ViewModel pattern. ViewModel here is an observable pro.data
object binded to HTML-element as following:
var model = {
newsList: [],
nextPageUrl: '/next',
styles: { display: 'none' },
placeholder: 'Your placeholder',
modelValue: 'Default value'
},
viewModel = pro.data(model);
pro.mvvm.to(proId('news-container'), viewModel);
After that you need change only view model - markup is updated on the fly!
Here is how markup looks like:
<div id="news-container" pro="css(styles)">
<input pro="place(placeholder); value(modelValue)" />
<div pro="show(newsList.length === 0)">There are no news to read.</div>
<div pro="each(newsList, view('news-view'))">
<!-- Here will be inserted list of news view -->
</div>
<div pro="hide(newsList.length === 0)">Happy reading!</div>
<a pro="href(nextPageUrl)">Next</a>
</div>
pro
-attributes in markup contain valid JS-expressions with predefined list of hacks: show
, hide
, each
, view
, text
, html
, href
, value
, css
, place
- for DOM-manipulation with element.
Markup is reevaluated on every model change. Extension point for custom hacks will be added later.
viewModel.newsList([{ topic: '...', text: '...' }]);
// The html above will be immediately updated
Continue on advanced level