All Projects → evrone → normas

evrone / normas

Licence: MIT license
Normal Lightweight Javascript Framework for server-side render compatible with Turbolinks

Programming Languages

javascript
184084 projects - #8 most used programming language
CSS
56736 projects

Projects that are alternatives of or similar to normas

javascript-without-jquery
Tips and practical examples
Stars: ✭ 47 (+46.88%)
Mutual labels:  event-listener, dom-manipulation
necktie
Necktie – a simple DOM binding tool
Stars: ✭ 43 (+34.38%)
Mutual labels:  mutationobserver, dom-manipulation
transceiver
Channel based event bus with request/reply pattern, using promises. For node & browser.
Stars: ✭ 25 (-21.87%)
Mutual labels:  event-listener
ttall
Laravel fronend preset for TTALL stack - Tailwindcss | Turbolinks | Alpine.js | Laravel | Livewire 🚀
Stars: ✭ 50 (+56.25%)
Mutual labels:  turbolinks
sinatras-skeleton
Basic Sinatra Skeleton MVC CRUD App with Sprockets, Warden, ActiveRecord and PostgresQL
Stars: ✭ 13 (-59.37%)
Mutual labels:  turbolinks
keycloak-session-restrictor
Simple event-listener for Keycloak which restricts the current user sessions to one (last one wins) only. Demo purposes only!
Stars: ✭ 48 (+50%)
Mutual labels:  event-listener
stimulus reflex
Build reactive applications with the Rails tooling you already know and love.
Stars: ✭ 2,001 (+6153.13%)
Mutual labels:  turbolinks
cqrs
A foundational package for Command Query Responsibility Segregation (CQRS) compatible with Laravel.
Stars: ✭ 37 (+15.63%)
Mutual labels:  event-listener
faucet-pipeline-spring-boot-starter
faucet-pipeline for Spring Boot
Stars: ✭ 17 (-46.87%)
Mutual labels:  turbolinks
vue-on-rails
Easy way to mount/destroy Vue.js components with Ruby on Rails and Turbolinks 5
Stars: ✭ 17 (-46.87%)
Mutual labels:  turbolinks
micro-typed-events
The smallest, most convenient typesafe TS event emitter you'll ever need
Stars: ✭ 39 (+21.88%)
Mutual labels:  event-listener
boxdrop
Dropbox Clone built with StimulusReflex
Stars: ✭ 66 (+106.25%)
Mutual labels:  turbolinks
ng-event-options
Enable event options (capture, passive, ...) inside angular templates
Stars: ✭ 15 (-53.12%)
Mutual labels:  event-listener
stimulus-turbolinks
Stimulus + Vue.js + Turbolinks test Rails app
Stars: ✭ 33 (+3.13%)
Mutual labels:  turbolinks
three-onEvent
Add an EventListener for Object3d in your three.js project.(support click,hover or gaze)
Stars: ✭ 55 (+71.88%)
Mutual labels:  event-listener
tsee
Typed EventEmitter implemented with tsargs
Stars: ✭ 22 (-31.25%)
Mutual labels:  event-listener
CRUD-Laravel-Livewire-SPA
CRUD Laravel 7 & Livewire (SPA) Single Page Application
Stars: ✭ 34 (+6.25%)
Mutual labels:  turbolinks
ng2-events
Supercharge your Angular2+ event handling
Stars: ✭ 17 (-46.87%)
Mutual labels:  event-listener
Event Dispatcher
The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.
Stars: ✭ 7,946 (+24731.25%)
Mutual labels:  event-listener
stimulus-turbolinks-demo
A Stimulus + Turbolinks Demo of an app called Proposahoy!
Stars: ✭ 30 (-6.25%)
Mutual labels:  turbolinks

Normas stability npm gzip size dependencies

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.

Sponsored by Evrone

Table of Contents

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.

  1. Add the normas package to your application: yarn add normas or npm install --save normas.
  2. Create your normas.js instance module (ex in your js/lib)
  3. Import Normas class from package, configure and export your normas instance for usage in other app-files:
import Normas from 'normas';

export default new Normas({
  debugMode: process.env.NODE_ENV === 'development',
  logging: {
    eventsTable: true,
    content: true,
  },
});

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.

import normas from 'lib/normas';

normas.listenEvents('click body', () => alert('Body click!'));

normas.listenToElement('select', $select => console.log('See new select!', $select));

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.listenEvents({
  click: handleDocumentClick,
  'click   .js-player .js-player__pause': handleClickPause1,
  '.js-player': {
    'player:load': handleLoad,
    'player:play/player:stop': handlePlayback,
    'click   .js-player__pause': handleClickPause2,
    '.js-player__pause': {
      click: ($pause, event) => {
        alert($pause.closest('.js-player'));
      },
    },
  },
});

normas.listenEvents('.js-player', {
  'player:load': handleLoad,
  'player:play/player:stop': handlePlayback,
  'click   .js-player__pause': handleClickPause2,
  '.js-player__pause': {
    click: handleClickPause3,
  },
});

normas.listenEvents('click/mouseenter    .js-player .js-player__pause', handleClickPause1);
normas.listenEvents('.js-player .js-player__pause', 'click/mouseenter', handleClickPause1);

listenEventsOnElement

normas.listenEventsOnElement($myElement, /* events notation like for `listenEvents` */);

forgetEvents && forgetEventsOnElement

const $myElement = $('.my-element:first');
const listeningArgs = normas.listenEventsOnElement($myElement, {
  'click .inner-element': ($innerElement, event) => {
    alert($innerElement[0] === event.currentTarget && $(event.currentTarget).closest($myElement).length > 0);
    normas.forgetEventsOnElement($myElement, listeningArgs);
  },
});

trigger

normas.listenEvents('cart:update', (itemId, amount) => ...);
...
normas.trigger('cart:update', itemId, amount); // 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.listenToElement('.js-element',
  $element => { /* $element already in DOM in this callback */ },
  $element => { /* $element disappear after this callback */ },
  {
    delay: 100, // delay for `enter` callback
    silent: true, // dont log in development mode
  },
);

Options:

  • delay: Number Delay in milliseconds from detect new element to fire enter. If content disappears before delay, 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.listenToContent(
  $content => { /* $content already in DOM in this callback */ },
  $content => { /* $content disappear after this callback */ }
);

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.listenToPage(
  $page => { /* page ready or body replaced by Turbolinks */ },
  $page => { /* page prepare to cache and disappearing */ }
);

📣 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.

export default new Normas({
  ...
  enablings: {
    mutations: false,
  },
  ...
});

🙌 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 = $('<div>Content</div>');
$content.appendTo($('body'));
normas.sayAboutContentEnter($content);
...
normas.sayAboutContentLeave($content);
$content.remove();
normas.replaceContentInner($container, content);
normas.replaceContent($content, $newContent);

🗺 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:

import Normas from 'normas';
import normasViews from 'normas/dist/js/extensions/views';

const NormasWithViews = normasViews(Normas);

const normas = new NormasWithViews({
  logging: {
    construct: false,
  },
  viewOptions: {
    logging: {
      construct: true,
    },
  }
);

export default normas;

Then make some html:

<div class="b-my-player" data-media-url="https://media.url/">
  <i class="b-my-player__full-screen"></i>
  <div class="b-my-player__playback-controls">
    <i class="b-my-player__play"></i>
  </div>
</div>

Make your own views like this and cooler!

import normas from 'lib/normas';

// Define your view-class extends from `normas.View`
class MyPlayer extends normas.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',
    },
  };

  initialize(options) {
    // ... your actions after instance initialized
  }

  terminate() {
    // ... your actions before events unbinding
  }

  gotoFullScreen() {
    alert('No fullscreen :)');
  }

  play() {
    alert(`Play! ${this.mediaUrl}`);
  }
}

// Register your view for auto-binding
normas.registerView(MyPlayer);

🔦 Debugging

The installation section describes that you are making your own application instance, which can be configured with logging options.

import Normas from 'normas';

export default new Normas({
  debugMode: process.env.NODE_ENV === 'development', // default `true`
  // logging works only when `debugMode === true`
  logging: {                  // detailed logging settings, mostly default `true`
    // Core level options
    hideInstancesOf: [],      // list of constructors whose instances will be muted, ex: [Element, $, Normas.View, Normas]
    construct: true,          // logs about constructing
    constructGrouping: true,  // group logging about constructing
    events: true,             // logs about events listening
    eventsDebounced: true,    // events collect in debounced by 20ms batches
    eventsTable: false,       // events subscriptions info as table, default `false`, because massive
    // App level options
    elements: true,           // logs about element enter and leave
    content: false,           // logs about content enter and leave, default `false`, because noisy
    contentGrouping: true,    // group logging under content lifecycle
    navigation: true,         // logs in navigation mixin (page events)
    navigationGrouping: true, // group logging under page events
  },
});

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! gzip size

import Normas from 'normas/dist/js/normas.production';
import normasViews from 'normas/dist/js/extensions/views.production';
export default new normasViews(Normas);

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
  .filter$($element => $element.data('inMemoryData') )
  .filter('.jquery-chain')
  .each((index, element) => { $(element).addClass(`.jquery-too_${index}`); })
  .each$(($element, index) => { $element.removeClass(`.jquery-too_${index}`); });

🎨 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:

import Normas from 'normas';
import normasTurbolinks from 'normas/dist/js/integrations/turbolinks';
import Turbolinks from 'turbolinks';

const NormasWithTurbolinks = normasTurbolinks(Normas);

const normas = new NormasWithTurbolinks({
  Turbolinks,
  enablings: {
    // turbolinks: false, // you can disable Turbolinks integration
  },
);

export default normas;

React.js integration

Just import integration module and use it:

import normasReact from 'normas/dist/js/integrations/react';
import normas from 'lib/normas'; // or may be you use global Normas instance like `app`
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'; // optional

normasReact.init({ normas, React, ReactDOM, PropTypes /* optional */ }));

import ComponentA from 'components/ComponentA';
import ComponentB from 'components/ComponentB';

normasReact.registerComponents({
  ComponentA,
  ComponentB,
});

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:

  def react_component(component_name, props = nil, html_options = {})
    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

Chrome Firefox Edge IE Safari Opera
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!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].