basis.js
and what tools you can use. As an example, several simple ideas will be created, the issue of modularity and organization of project files will be raised.Google Chrome
)basis.js
do not require assembly during development. But for their work requires a web server. Any web server may be suitable, but it is better to use the dev-server included in basisjs-tools
, as it provides more opportunities for development.basisjs-tools
is a set of console tools written in javascript
and running under node.js
This kit includes a collector, a dev server and a code generator. It is installed as a normal npm
module: > npm install -g basisjs-tools
-g
flag), then the basis
command will be available in the console. > basis server
8000
(this can be changed using the --port
or -p
flag). Now you can open http://localhost:8000
in the browser and make sure that the server is working. Nevertheless, it gives an error, since the folder of our project is still empty. Let's fix this.basis.js
to the project. To do this, you can either clone the project from the repository , or use bower
. > bower install basis
html
file of the application, which so far will only basis.js
- index.html
. <!doctype html> <html> <head> <meta charset="utf-8"> <title>My first app on basis.js</title> </head> <body> <script src="bower_components/basisjs/src/basis.js" basis-config=""></script> </body> </html>
basis-config
attribute of the <script>
.basis.js
find the <script>
to which it was connected. This is necessary in order to determine the path to the basis.js
sources and allow the paths to its modules. <!doctype html> <html> <head> <meta charset="utf-8"> <title>My first app on basis.js</title> </head> <body> <script src="bower_components/basisjs/src/basis.js" basis-config=""></script> <script> basis.require('basis.ui'); var view = new basis.ui.Node({ container: document.body, template: '<h1>Hello world!</h1>' }); </script> </body> </html>
basis.ui
module, using the basis.require
function. This function works in almost the same way as the require
function in node.js
and can connect modules by their name or file name. In this case, basis.ui
is the name of the module. As we will see, this function can “connect” any file by its name.basis.ui
module, since it provides everything needed to build the interface. This module requires other modules, but this does not need to be taken care of, leave it basis.js
. It is necessary to connect only what is directly used in the code that you write.basis.ui.Node
. Do not be confused by the name Node
, instead of the traditional View
. The fact is that in basis.js
all components and views are embedded in each other. So, a block may look like a whole, but in fact it may consist of a set of nested representations (nodes).DOM
. But we will come back to this.container
property, we specified where to put the DOM
view fragment when it is created. This must be a DOM
element. And in the template
property indicated the description of the template. This description is specified in the config for example. Such an opportunity to specify the description of the template in the config line is convenient for prototyping and examples. But for published (production) applications it is not used and we will redo it later.hello.js
and transfer to it what was specified in the <script>
.index.html
: <!doctype html> <html> <head> <meta charset="utf-8"> <title>My first app on basis.js</title> </head> <body> <script src="bower_components/basisjs/src/basis.js" basis-config=""></script> <script> basis.require('./hello.js'); </script> </body> </html>
basis.require
function is basis.require
, but this time the path to the file is passed to it. It is important that the file path starts with " ./
", " ../
" or " /
". So basis.require
will unambiguously understand that the value is the path to the file, and not the name of the module. The same convention applies to node.js
hello.tmpl
. Then the presentation code will look like this: basis.require('basis.ui'); var view = new basis.ui.Node({ container: document.body, template: basis.resource('./hello.tmpl') });
basis.resource
. This function creates an "interface" to the file. This approach makes it possible to determine which files are needed, without downloading them until there is no need for it.basis.resource
is a function with additional methods. Calling such a function, or its fetch
method, causes the file to be loaded. The file is loaded only once, and the result is cached. More details can be found in Resources (modularity) .basis.require('./file.name')
equivalent to basis.resource('./file.name').fetch()
.basis.require
. But templates are often described in classes, and for these cases it is not necessary to load the file until the first instance of the class is created. We will see this in other examples. Therefore, for consistency: when assigning a template, it is better to always use basis.resource
.__filename
, and the folder where the module is located from the variable __dirname
.require
and resource
become available. They work in the same way as basis.require
and basis.resource
, except for how relative file paths are resolved. If a relative path is passed for the functions basis.require
and basis.resource
, then it resolves the relative html
file (in our case, this is index.html
). At the same time, require
and resource
allow such paths relative to the module (that is, its __dirname
).require
and resource
. Thus, the hello.js
code hello.js
bit simpler: require('basis.ui'); var view = new basis.ui.Node({ container: document.body, template: resource('./hello.tmpl') });
javascript
modules, but also to other types of content. So, for example, if the template description is in a separate file, then when it is changed, it is not necessary to refresh the page. As soon as changes are saved, all instances of views that use the modified template independently update their DOM
fragments. And all this happens without reloading the page, while maintaining the current state of the application.css
, localization files and other file types. The only changes that require reloading the page are changing the html
file and changing the javascript
modules that have already been initialized.basisjs-tools
dev server. This is one of the main reasons why it is worth using it, and not a regular web server.hello.css
, such as: h1 { color: red; }
hello.tmpl
template a bit ( hello.tmpl
): <b:style src="./hello.css"/> <h1>Hello world!</h1>
<b:style>
tag to the template. This tag says that when this template is used, the specified style file must be connected to the page. Relative paths are resolved relative to the template file. One template can include an arbitrary number of style files. We do not need to worry about adding and deleting styles, the framework takes care of this.DOM
fragment. Unlike most template engines, basis.js
templates basis.js
not have direct access to presentation properties. And they can use only those values ​​that the representation itself provides to the template.binding
property in the description of the instance or class inherited from basis.ui.Node
. Values ​​are specified as an object, where the key is the name that will be available in the template, and the value is the function that calculates the value for the template. To such functions, the only parameter is the template owner, that is, the representation itself. This is how you can provide the name
value to the template: require('basis.ui'); var view = new basis.ui.Node({ container: document.body, name: 'world', template: resource('./hello.tmpl'), binding: { name: function(node){ return node.name; } } });
binding
property is an auto-extensible property . When a new value is set for a property, when an instance or class is created, the new value extends the previous one by adding and overriding the previous values. By default, basis.ui.Node
already has several useful values that can be used along with the name
we defined.hello.tmpl
) to use the name
. <b:style src="./hello.css"/> <h1>Hello, {name}!</h1>
{name}
, to insert the value as plain text.basis.js
template basis.js
works with DOM
nodes. For this description will be created the element <h1>
, which will contain the three text nodes " Hello,
", " {name}
" and " !
". The first and last will be static and their text will not change. But the average will be assigned the value from the view (its nodeValue
property will change). <b:style src="./hello.css"/> <div> <h1>Hello, {name}!</h1> <input value="{name}" event-keyup="setName"/> </div>
<input>
element has been added to the template. For its value
attribute, the same binding is used as in the header - {name}
. But this only works for writing to the DOM
.DOM
fragment, an attribute is added to the desired element, the name of which is the name of the event with the prefix " event-
". We can add the execution of an action to any element on any event. And there can be several actions for one event, the main thing is to separate the names of actions by a space.event-keyup
, which requires the view to perform a setName
action when the keyup
event keyup
. If the view does not have any action defined, then we will see a warning message on the console and nothing more will happen.action
property. It works like binding
, but only describes actions. Functions in action
receive a parameter event object. This is not an original event, but a copy of it with additional methods and properties (the original event is stored in its event_
property).hello.js
): require('basis.ui'); var view = new basis.ui.Node({ container: document.body, name: 'world', template: resource('./hello.tmpl'), binding: { name: function(node){ return node.name; } }, action: { setName: function(event){ this.name = event.sender.value; this.updateBind('name'); } } });
event.sender
, and this is the element at which the event occurred - <input>
. In order for the view to re-evaluate the value and pass it to the template, we called the updateBind
method.data
property and changed by the update
method. When values ​​in data
change, the update
event is fired. We use this mechanism to store the name: require('basis.ui'); var view = new basis.ui.Node({ container: document.body, data: { name: 'world' }, template: resource('./hello.tmpl'), binding: { name: { events: 'update', getter: function(node){ return node.data.name; } } }, action: { setName: function(event){ this.update({ name: event.sender.value }); } } });
updateBind
not called explicitly. But to describe the bindig it took more code. Fortunately, binders have helpers that reduce the description of common situations. Synchronization with the field of data
one of them. Such a binding can be written in a shorter form, like this: require('basis.ui'); var view = new basis.ui.Node({ container: document.body, data: { name: 'world' }, template: resource('./hello.tmpl'), binding: { name: 'data:name' }, action: { setName: function(event){ this.update({ name: event.sender.value }); } } });
binding
for this. And the template intercepts and transfers to representation of an event, causing actions from action
. In fact, binding
and action
main points of contact between the view and the template. At the same time, the representation knows practically nothing about the device of the template, and the template knows about the presentation device. All logic ( javascript
) is on the side of the view, and work with the display ( DOM
) is on the side of the template. So, in most cases, a complete separation of logic and representation is achieved.list.js
file with the following content: require('basis.ui'); var list = new basis.ui.Node({ container: document.body, template: resource('./list.tmpl') }); var Item = basis.ui.Node.subclass({ template: resource('./item.tmpl'), binding: { name: function(node){ return node.name; } } }); list.appendChild(new Item({ name: 'foo' })); list.appendChild(new Item({ name: 'bar' })); list.appendChild(new Item({ name: 'baz' }));
hello.js
, but new constructions have been added.basis.js
them, we note that the component.js approach is used in basis.js
. So, if we make, for example, a list, then it will be not one presentation, but several. One view is the list itself. And each element of the list is also a representation. So we separately describe the behavior of the list, and the behavior of the elements of the list. A little more detail about this approach, for example, is described in the report “Component Approach: Boring, Uninteresting, Unpromising”: slides and video .childNodes
property), and for them, the view in which they are nested is the parent (the link is stored in the parentNode
property).basis.ui.Node
. This class contains the template file and simple binding. After that, three instances of this class were created and added to the list.DOM
principles are used to organize the tree of representations. Use the appendChild
and insertBefore
methods for insertion, removeChild
to remove, and replaceChild
to replace. There are also non-standard methods: setChildNodes
allows setChildNodes
to set a list of child views, and clear
deletes all child views in one go. require('basis.ui'); var list = new basis.ui.Node({ container: document.body, template: resource('./list.tmpl') }); var Item = basis.ui.Node.subclass({ template: resource('./item.tmpl'), binding: { name: function(node){ return node.name; } } }); list.setChildNodes([ new Item({ name: 'foo' }), new Item({ name: 'bar' }), new Item({ name: 'baz' }) ]);
require('basis.ui'); var Item = basis.ui.Node.subclass({ template: resource('./item.tmpl'), binding: { name: function(node){ return node.name; } } }); var list = new basis.ui.Node({ container: document.body, template: resource('./list.tmpl'), childNodes: [ new Item({ name: 'foo' }), new Item({ name: 'bar' }), new Item({ name: 'baz' }) ] });
childClass
and childFactory
. The first defines an instance class that can be added as a child node. And the second property defines the function that is passed, added as a child node, a value that is not an instance childClass
. The task of such a function is to create a suitable instance. By default, this function creates an instance childClass
using the transferred value as a config: basis.ui.Node.prototype.childFactory = function(value){ return new this.childClass(value); };
childClass
. Then it will be possible to add new items to the list not only by creating an instance Item
, but also by passing the config. require('basis.ui'); var Item = basis.ui.Node.subclass({ template: resource('./item.tmpl'), binding: { name: function(node){ return node.name; } } }); var list = new basis.ui.Node({ container: document.body, template: resource('./list.tmpl'), childClass: Item, childNodes: [ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] });
Item
is not used anywhere else, so it makes no sense to save it to a variable. This class can be immediately set in the config. But this is not all that we can do. When a new class or instance is created and some of its property is a class, and we want to create a new class based on it, it is not necessary to create a class explicitly; you can simply specify an object with extensions of the new class. It sounds difficult, but, in fact, it’s all about the fact that we don’t have to specify basis.ui.Node.subclass
, you can just pass the object. And we get: require('basis.ui'); var list = new basis.ui.Node({ container: document.body, template: resource('./list.tmpl'), childClass: { template: resource('./item.tmpl'), binding: { name: function(node){ return node.name; } } }, childNodes: [ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] });
list.tmpl
: <div id="mylist"> <h2> </h2> <ul{childNodesElement}/> </div>
ul
is an unfamiliar construction {childNodesElement}
. Meet this is also a marker. So we say that we want to refer to this element by name childNodesElement
. In fact, we personally do not need this link yet. But it is necessary for the list view to understand where to insert DOM
fragments of child nodes. If you do not specify it, then the child nodes will be inserted into the root element (in our case it is <div id="mylist">
).DOM
directly, this is what performances do. We only suggest what and where to place. And since representations are involved in moving nodes, they know perfectly well where they are and try to do their job as optimally as possible. Moreover, this is why it is possible to update templates without reloading the page. When the description of the template changes, the presentation creates a new DOM
fragment and transfers everything from the old fragment to the new one.item.tmpl
): <li> {name} </li>
<!doctype html> <html> <head> <meta charset="utf-8"> <title>My first app on basis.js</title> </head> <body> <script src="bower_components/basisjs/src/basis.js" basis-config=""></script> <script> basis.require('./hello.js'); basis.require('./list.js'); </script> </body> </html>
DOM
fragment. Plug-ins, their views, may also include other child views, and they are their child views, etc. Thus, representations determine which representations are included in them, but not vice versa. Usually, child views do not know who and how includes them.container
, since their location will determine the parent view. And secondly, it is necessary that the module returns the view itself so that it can be used. To do this, use exports
or module.exports
(all as in node.js
).hello.js
take the following form: require('basis.ui'); module.exports = new basis.ui.Node({ data: { name: 'world' }, template: resource('./hello.tmpl'), binding: { name: 'data:name' }, action: { setName: function(event){ this.update({ name: event.sender.value }); } } });
list.js
) is: require('basis.ui'); module.exports = new basis.ui.Node({ template: resource('./list.tmpl'), childClass: { template: resource('./item.tmpl'), binding: { name: function(node){ return node.name; } } }, childNodes: [ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] });
app.js
: require('basis.ui'); new basis.ui.Node({ container: document.body, childNodes: [ require('./hello.js'), require('./list.js') ] });
<div>
. While we are satisfied.index.html
: <!doctype html> <html> <head> <meta charset="utf-8"> <title>My first app on basis.js</title> </head> <body> <script src="bower_components/basisjs/src/basis.js" basis-config=""></script> <script> basis.require('./app.js'); </script> </body> </html>
basis.require
were replaced by one. But do not write it, but use the option autoload
in basis-config
: <!doctype html> <html> <head> <meta charset="utf-8"> <title>My first app on basis.js</title> </head> <body> <script src="bower_components/basisjs/src/basis.js" basis-config="autoload: 'app'"></script> </body> </html>
<div>
. For this you must use - satellites.satellite
. In binders, you can use a helper satellite:
to provide a template with the ability to place their DOM
fragments within its DOM
fragment. At the same time, the root element of the satellite is transmitted to the template (after all, they operate in terms of DOM
), and the point to insert is defined in the template itself.app.js
with the use of satellites: require('basis.ui'); new basis.ui.Node({ container: document.body, template: resource('./app.tmpl'), binding: { hello: 'satellite:hello', list: 'satellite:list' }, satellite: { hello: require('./hello.js'), list: require('./list.js') } });
require('basis.ui'); new basis.ui.Node({ container: document.body, template: resource('./app.tmpl'), binding: { hello: require('./hello.js'), list: require('./list.js') } });
basis.ui.Node
. In this case, he implicitly becomes a satellite with the name of binding. <div> <div id="sidebar"> <!--{list}--> </div> <div id="content"> <!--{hello}--> </div> </div>
basis.js
this is usually 800-1200 files. It is inconvenient and unreasonable to store all files in one folder. Let's try to restructure the file location.hello
and transfer files related to this module (ie hello.js
, hello.tmpl
and hello.css
). As well as the folder list
in which we transfer list.js
, list.tmpl
and item.tmpl
. All that is left for us is to change the ways of connecting modules to app.js
: require('basis.ui'); new basis.ui.Node({ container: document.body, template: resource('./app.tmpl'), binding: { hello: require('./hello/hello.js'), // list: require('./list/list.js') // } });
src
and put there all the files and folders except bower_components
and index.html
. After that, you need to fix one path to index.html
: <!-- --> <script src="bower_components/basisjs/src/basis.js" basis-config="autoload: 'app'"></script> <!-- --> <script src="bower_components/basisjs/src/basis.js" basis-config="autoload: 'src/app'"></script>
module
. The main javascript
file of the module is called index.js
. Templates and all that applies to them (styles, images, etc.) are located in a folder template
. This is the most frequent project organization structure at the moment.basis.js
are two auxiliary tools: devpanel
and a plugin for Google Chrome
.devpanel
- This is a small panel with buttons that can be dragged. It looks like this:app.js
): /** @cut */ require('basis.devpanel');
/** @cut */
, it allows you to cut lines at the assembly. We do not need to show this panel to users, right?Google Web Store
here on this link . For his work is necessary devpanel
, as it provides an API to work with basis.js
.basisjs-tools
.basisjs-tools
.html
file as input (in our case index.html
). He analyzes it finds the tags <script>
, <link rel="stylesheet">
, <style>
and others. Understands what files are connected. Then proceeds to analyze these files, finds them in the design to connect other files (such as javascript
calls basis.require
, basis.resource
and others, and in the css
- @import
,url(..)
etc.). So, recursively processing all files, the collector builds an application graph. After that, it analyzes the links, rebuilds and optimizes files. And the result of their work is stored in a separate folder, in the form of a much smaller number of files. > basis build
index.html
, script.js
and style.css
located in the folder build
. These three files are our application in assembled form. All you need to do after assembly is to copy the contents of the folder build
to the server. All the necessary files for the application will be in it.build
: > basis build --help
javascript
and css
you can perform by specifying the flag --pack
(or its short version -p
): > basis build --pack
--verbose
, you can see all of his actions in detail. But we should not take care of it. After all, our main task is not to build, but to create applications and do cool stuff.basis.js
, tried various options for creating views and organizing project files. The acquired knowledge will help in the study of the remaining parts of the manual.Source: https://habr.com/ru/post/216577/
All Articles