Normas
Normal Lightweight Javascript Framework for server-side render
At the moment, the project is in the stage of active development and will be ready for production in early 2018. Now you can clone this repo and try example with Rails.
Feel free to start watching and ⭐ project in order not miss the release or updates.
Table of Contents
- ✨ Philosophy
- 🏗 Installation
- 🛠 Usage and project structure
- 🚦 Events listening
- 🛂 Content control
- 🗺 Navigation
- 🏭 Views
- 🔦 Debugging
- ⚙ Helpers
- 🎨 SCSS addons
- 🔌 Integrations
- 📝 Roadmap
- 🤝 Contributing
✨ Philosophy
A lot of people in the world have done, are doing and will do in the future multi-page applications on Ruby on Rails, Phoenix, Express, Django, Flask, ASP.NET etc. This is a fairly stable approach for medium and serious applications with advanced business logic.
But developers constantly have a headache when try organizing a big-app for thin client. Collisions between the scripts and callback-hell, causes people to seek refuge in the new hyped "frameworks", But they require a more complex organization of the application code and generate a new cluster of problems inherent in the thick client.
It should be understood that Normas is ideally suited for multi-page applications. Describe your tracking once and it will work magically correctly for any cases of changing content and pages, automatically compatible with Turbolinks and any other custom content change. Your application can not be distinguished from a SPA/PWA.
It does not oblige to avoid React.js, Vue.js etc libs. You can use them partially, for interactive fragments in the form that they are just one of the custom components. Read more in the Integrations section.
🏗 Installation
Your application can use the normas
npm package
to install Normas as a module for build with tools like
webpack or rollup.
- Add the
normas
package to your application:yarn add normas
ornpm install --save normas
. - Create your
normas.js
instance module (ex in yourjs/lib
) - Import
Normas
class from package, configure and export yournormas
instance for usage in other app-files:
; ;
Full list of logging options see in Debugging section.
🛠 Usage and project structure
In 90% of cases, it is sufficient to use two methods:
normas.listenEvents
and
normas.listenToElement
.
Also, for organizing more complex widgets, there is Views-system.
All you need to do is import your normas
instance
and use it for all event bindings and content-flow.
; normas; normas;
Normas does not limit file structure organization, but it is strongly recommended to split app-logic into separate files and group them into folders according to functionality.
In all examples, Normas instance called normas
, but if you call it app
, you'll be dead right!
There is everything to ensure that your app-code does not crack at the seams.
🚦 Events listening
listenEvents
normas; normas; normas;normas;
listenEventsOnElement
normas;
forgetEvents
&& forgetEventsOnElement
const $myElement = ;const listeningArgs = normas;
trigger
normas;...normas; // unlike jQuery `.trigger('cart:update', [itemId, amount])`
Events logging
By default Normas collects information about events listening
started with a difference of less than 20ms
and displays in batches as soon as events cease to be registered.
There is a way to enable synchronous logging: the option logging: { eventsDebounced: false }
.
If you need a more visible list of events, use option logging: { eventsTable: true }
.
Full list of logging options see in Debugging section.
🛂 Content control
👂 Content listening
Don't use DOM-ready-like wrapping (like $(() => { ... });
),
because app may use Turbolinks + many dynamic components.
listenToElement
💎 Top level of content listening is listenToElement(elementSelector, enter[, leave = null][, { delay: 0 }])
:
normas;
Options:
delay: Number
Delay in milliseconds from detect new element to fireenter
. If content disappears beforedelay
,enter
will not fire.silent: Boolean
Mute events logging.
listenToContent
📰 If there is something to do with the appearance of content,
whether it's walking through the pages, or processing of the content appearing,
you need to turn in listenToContent([enter][, leave])
:
normas;
where second callback (on leave content) not necessary.
listenToPage
🗺 If something needs to be done when enter or/and leave the page (navigation,
ie, the processing does not need randomly appearing in the content, such as popup),
you can wrap in a listenToPage([enter][, leave])
:
normas;
📣 Content broadcasting
🤖 Mutation Observer
Currently, Mutation Observer is enabled by default and is used to track changes in the DOM tree. You can turn it off using option when construct your normas instance and go into the manual content control mode. This will require more care in your content management code.
;
🙌 Manual content broadcasting
If you make app for IE <= 10, I sympathize with you. Mutation Observer not work for some part of your users. You must use Manual content broadcasting when manipulate DOM-tree.
For broadcast events about content life use sayAboutContentEnter
and sayAboutContentLeave
:
let $content = ;$content;normas;...normas;$content;
normas;
normas;
🗺 Navigation
visit(location)
refreshPage()
setHash(hash)
back()
replaceLocation(url)
pushLocation(url)
sayAboutPageLoading(state)
🏭 Views
If you like organize some instantiated code into classes, likeness Evrobone/Backbone-view, for this there is an analog in the Normas, but only very powerful and convenient.
For use in project, you will need to construct app-instance from extended Normas-class:
;; const NormasWithViews = ; const normas = logging: construct: false viewOptions: logging: construct: true ); ;
Then make some html
:
Make your own views like this and cooler! ✨
; // Define your view-class extends from `normas.View`View // Define selector for binding, like `el: '.b-my-player'` in Evrobone. static selector = '.b-my-player'; // List of options that will be exposed to properties of view-instance. static reflectOptions = 'mediaUrl'; // Events notation compliant with `listenEvents` static events = 'click .b-my-player__full-screen': 'gotoFullScreen' '.b-my-player__playback-controls': 'click .b-my-player__play': 'play' ; { // ... your actions after instance initialized } { // ... your actions before events unbinding } { ; } { ; } // Register your view for auto-bindingnormas;
🔦 Debugging
The installation section describes that you are making your own application instance, which can be configured with logging options.
; ;
All *Grouping
properties can be a string 'groupCollapsed'
.
There are special versions of bundles (normas/js/dist/**/*.production
) for the size-paranoids,
in which debugging and logging is removed.
Size of production version of main bundle is less than 4 kB!
;; ;
⚙ Helpers
Normas has built-in helpers, which he uses to create magic. You can use them in your code, and in some cases reduce the included code.
compact(array)
debounce(func, wait)
groupBy(array, key)
groupByInArray(array, key)
flatten(array)
deepMerge(destination, source)
filter(collection, conditions)
find(collection, conditions)
jQuery additions
Built-in jQuery $.fn.*
helpers:
$someElement ;
🎨 SCSS addons
Normas package includes additional scss-files that will help in styling your application.
To be continued...
🔌 Integrations
Turbolinks integration
For integration with Turbolinks you need use extended Normas class and construct instance with your Turbolinks instance:
;;; const NormasWithTurbolinks = ; const normas = Turbolinks enablings: // turbolinks: false, // you can disable Turbolinks integration ); ;
React.js integration
Just import integration module and use it:
;; // or may be you use global Normas instance like `app`;;; // optional normasReact); ;; normasReact;
If you want to understand the mechanism, or realize your own, look at the source.
If you use Ruby on Rails, you can define in your app/helpers/*_helper.rb
:
html_options[:data] ||= {} html_options[:data].reverse_merge!(react_component: component_name, props: props) content_tag :div, '', html_options end
To be continued...
Browser Support
Latest ✔ | Latest ✔ | Latest ✔ | 11+ ✔ | 9.1+ ✔ | Latest ✔ |
📝 Roadmap
- Extend logging
- More documentation
- More examples of usage with actual javascript plugins and libs
- Improve code style and quality
- Improve debugging
- Optional jQuery usage
- Tests
- Upgrade to Babel 7
- Use TypeScript
- Example on node.js with Express.js
🤝 Contributing
If you want to get involved, please do so by creating issues or submitting pull requests. Before undertaking any major PR effort, please check the existing issues. If there isn't one, please file a new issue so we can discuss and assign the work so effort is not duplicated. Thank you!