📜 ⬆️ ⬇️

Tabris.js - get to know each other quickly and write Hello World


Tabris.js is another cross-platform (Android, IOS) mobile framework. It differs from the overwhelming majority of such tools in that it is not a wrapper over standard or Chrome-based WebView. Tabris is a collection of native components available from javascript. The closest analogues to me are: Telerik Native Script , Appcelerator and React Native .


So, Tabris has gone from the inefficient (but so convenient) HTML5 + WebView and offers to write applications completely in javascript with a single code base for IOS and Android platforms. At the same time, we can fully use JS-libraries, npm-modules and Cordova plugins - but only those that do not work with the DOM, because it’s not in our application.

You can compile the application:

In any tariff plan, the functionality is the same, does not affect the code, only affects the project build. I don’t know how fast the local build is, but their cloud takes about a couple of minutes for a simple application, which is generally normal, as for me.
')
There are also tariff plans for companies (Organization) - the price tag starts at $ 5,000 / year. On these plans, you also get the source code, respectively, you can modify the functionality. In detail on the topic of working with source codes, it is better to find out in the support - I did not find specific information, but the FAQ says about writing your own plugins.



To speed up development and debugging, you can use their application for IOS or Android . Log in via GitHub, the application contains simple examples, the ability to run the application from a remote server (convenient for local development), and also synchronizes the scripts added to your account on the Tabris website. In addition, the application already contains some useful Cordova-plugins: camera, dialogs, device-motion, barcodescanner and others. Therefore, you can start developing without worrying about installing them - great for a quick start and check.

Getting to the development


Before you write something - read the documentation . The main interest for us are Widgets (Widgets), which are natively implemented components. The main component in most cases is Page (Page), and the rest are already attached to it, whether it is a TextView or ScrollView with many elements inside.

Since there are a lot of well-documented examples both on the website and inside the application, it would be strange to simply output “Hello World !!”. Therefore, we will make the application a little more complicated and a little more impressive, which will make it possible to feel the difference with the HTML5-based application - the search for public images on Flickr.

Creating an application starts with a minimum package.json :

{ "name": "flickr-search", "description": "Search Flickr public images by tag", "main": "app.js", "dependencies": { "tabris": "^1.2.0" } } 


We will need to make requests to the Flickr API, and although Tabris contains XMLHttpRequest in the core, I still prefer to drop extra lines of code by connecting a more convenient fetch module, as well as add Promise. Accordingly, package.json will take the following form:

 { "name": "flickr-search", "description": "Search Flickr public images by tag", "main": "app.js", "dependencies": { "tabris": "^1.2.0", "promise": "^6.1.0", "whatwg-fetch": "^0.9.0" } } 

After command npm install and we are almost ready to write code. I recommend to immediately raise the local server and enter its address in the url tab of the Tabris application. Thus, you can immediately view the application as you write code. If you already have a node http-server installed, you can simply command the http-server in the project folder, or you can install it locally in the project. Of course, you can use Apache, etc.



Our application will be single-page, it needs to have an input line for the search, and as a result we will display an image + name. For the input line use the usual TextInput , and to display the results of the CollectionView . During initialization, the application will display random results from Flickr (without tag search), so we use the CollectionView with Pull-to-Refresh component so that the user can update the images.

We connect the modules and create the necessary layout:

Scratch app.js
 Promise = require("promise"); require("whatwg-fetch"); var page = tabris.create("Page", { title: "Flickr Search", topLevel: true }); var tagInput = tabris.create("TextInput", { layoutData: { left: 8, right: 8, top: 8 }, message: "Search..." }).on("accept", loadItems).appendTo(page); var view = tabris.create("CollectionView", { layoutData: { left: 0, top: [tagInput, 8], right: 0, bottom: 0 }, itemHeight: 200, refreshEnabled: true, initializeCell: function(cell) { var imageView = tabris.create("ImageView", { layoutData: { top: 0, left: 0, right: 0, bottom: 0 }, scaleMode: 'fill' }).appendTo(cell); var titleComposite = tabris.create("Composite", { background: "rgba(0,0,0,0.8)", top: 0, right: 0, left: 0 }).appendTo(cell); var textView = tabris.create("TextView", { layoutData: { left: 30, top: 5, bottom: 5, right: 30 }, alignment: "center", font: "16px Roboto, sans-serif", textColor: "#fff" }).appendTo(titleComposite); cell.on("change:item", function(widget, item) { imageView.set("image", { src: item.media.m }); item.title ? textView.set("text", item.title) : textView.set("text", 'No Title'); }); } }).on("refresh", function() { loadItems(); }).appendTo(page); page.open(); function loadItems() { view.set({ refreshIndicator: true, refreshMessage: "loading..." }); } 



Everything is quite transparent and clear from the code - we created a Page (Page), titled it, added a text input to enter the search string and initialized the CollectionView , placing it below the search string.

Each cell of the CollectionView was set in height and the Image component (ImageView) , Composite layer and TextView layer were added to the cell.

We stretched the ImageView over the entire width and height of the cell (setting {left: 0, right: 0, top: 0, bottom: 0}), set the container “fill” mode ({scaleMode: 'fill'}).

In order for the name of the image to be distinguishable in its background, I created a composite layer with a slight transparency ({background: “rgba (0,0,0,0.8)”}) and already placed the text on this layer, specifying its color, size and center alignment.

Also in the change: item handler, we made a billet to fill the cell with data from the API.

The loadItems () function so far simply makes the Pull-to-Refresh element update indicator visible, so opening our application and swiping down will see the following:



To "revive" the application we will make a request to the Flickr API. Flickr can give data in different formats, and we could connect the necessary library and parse at least Atom Feed, at least CSV.

But it’s much easier to work with JSON, and you don’t need to drag on extra dependencies. The bad luck is that Flickr gives JSON-P. Since we are not in the browser, we cannot inject the script into <head /> for execution, and using eval () is also not the best option.

In a specific case, eval () could also be used - the probability of receiving “malicious” code from Flickr is low, the execution speed is also unlikely to drop noticeably (don't forget that this code execution passes without different optimizations).
Therefore, it seems to me that it will be good practice to create a function dynamically using the Function constructor, in a large project this will also get rid of global functions / variables, and with the advantage we get the ability to handle errors with try..catch - and this simplifies debugging and the developer’s life in whole In this simple application, our loadItems () function takes the form:

 function loadItems() { view.set({ refreshIndicator: true, refreshMessage: "loading..." }); fetch("https://api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=JSON_CALLBACK&tags=" + tagInput.get('text')).then(function(response) { var dyn_function = new Function("JSON_CALLBACK", response._bodyInit); dyn_function(function(json) { if (json.items && json.items.length) { view.set({ items: json.items, refreshIndicator: false, refreshMessage: "refreshed" }); } else { navigator.notification.alert('Nothing found with tag: ' + tagInput.get('text'), null, 'Result'); view.set({ refreshIndicator: false, refreshMessage: "refreshed" }); } }) }).catch(function(error) { console.log('request failed:', error) }) } 


Here we use navigator.notification.alert () because we know that the Tabris application already contains org.apache.cordova.dialogs . When building you will need to add a plugin depending.

By launching loadItems () during application initialization, a search with an empty parameter will be performed and we will get random images (and so with each refresh with an empty tag).



Let's now animate every element of our collection. Similar things (especially when scrolling the page) on HTML5 do not behave very "smoothly" - that will be a clear difference. Create a simple appearance effect on the right while increasing transparency:

 function animateFadeInFromRight(widget, delay) { widget.set({ opacity: 0.0, transform: { translationX: 150 } }); widget.animate({ opacity: 1.0, transform: { translationX: 0 } }, { duration: 500, delay: delay, easing: "ease-out" }); } 


And add the effect to the collection view cells:

 cell.on("change:item", function(widget, item) { animateFadeInFromRight(widget, 500); imageView.set("image", { src: item.media.m }); item.title ? textView.set("text", item.title) : textView.set("text", 'No Title'); }); 


On this, perhaps finish:


Full listing app.js
 Promise = require("promise"); require("whatwg-fetch"); var page = tabris.create("Page", { title: "Flickr Search", topLevel: true }); var tagInput = tabris.create("TextInput", { layoutData: { left: 8, right: 8, top: 8 }, message: "Search..." }).on("accept", loadItems).appendTo(page); var view = tabris.create("CollectionView", { layoutData: { left: 0, top: [tagInput, 8], right: 0, bottom: 0 }, itemHeight: 200, refreshEnabled: true, initializeCell: function(cell) { var imageView = tabris.create("ImageView", { layoutData: { top: 0, left: 0, right: 0, bottom: 0 }, scaleMode: 'fill' }).appendTo(cell); var titleComposite = tabris.create("Composite", { background: "rgba(0,0,0,0.8)", top: 0, right: 0, left: 0 }).appendTo(cell); var textView = tabris.create("TextView", { layoutData: { left: 30, top: 5, bottom: 5, right: 30 }, alignment: "center", font: "16px Roboto, sans-serif", textColor: "#fff" }).appendTo(titleComposite); cell.on("change:item", function(widget, item) { animateFadeInFromRight(widget, 500); imageView.set("image", { src: item.media.m }); item.title ? textView.set("text", item.title) : textView.set("text", 'No Title'); }); } }).on("refresh", function() { loadItems(); }).appendTo(page); function loadItems() { view.set({ refreshIndicator: true, refreshMessage: "loading..." }); fetch("https://api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=JSON_CALLBACK&tags=" + tagInput.get('text')).then(function(response) { var dyn_function = new Function("JSON_CALLBACK", response._bodyInit); dyn_function(function(json) { if (json.items && json.items.length) { view.set({ items: json.items, refreshIndicator: false, refreshMessage: "refreshed" }); } else { navigator.notification.alert('Nothing found with tag: ' + tagInput.get('text'), null, 'Result'); view.set({ refreshIndicator: false, refreshMessage: "refreshed" }); } }) }).catch(function(error) { console.log('request failed:', error) }) } function animateFadeInFromRight(widget, delay) { widget.set({ opacity: 0.0, transform: { translationX: 150 } }); widget.animate({ opacity: 1.0, transform: { translationX: 0 } }, { duration: 500, delay: delay, easing: "ease-out" }); } loadItems(); page.open(); 



For the build, you need to create a typical Cordova config.xml , where you can simply specify the necessary plugins (the cloud collector installs the necessary plugins himself, the npm modules can be read from package.json):

 <?xml version='1.0' encoding='utf-8'?> <widget id="my.flickr_search.app" version="1.0.0"> <name>Flickr Search</name> <description> Search Flickr public images by tag </description> <preference name="Fullscreen" value="true" /> <plugin name="cordova-plugin-dialogs" version="1.1.1" /> </widget> 


After we push the project on Github and create the application in the Tabris admin panel, select our repository:

Create App


After validation, the build settings will be available:

Build app

Github Code

An .apk file of the application takes about 10Mb - more than bare Cordova (~ 2-3Mb), but less project with Chrome WebView (~ 19Mb). At the same time, we have a more productive application on native components.

The advantages also include the speed of development and support for a large number of js-modules and cordova plugins . In React Native, for example, there are still few plug-ins for working with hardware. Since Tabris is compatible with projects on Cordova, you can use it in “narrow” places - for example, for large lists.

It is a pity that for the possibility of a local build, you will have to pay $ 50, but if we are not talking about a single project, then I think the point is completely there. However, in the cloud without problems, you can knock the application all with the same functionality.
The Tabris community is not so big, but there will be a demand - it will grow.

In general, we have a fairly competitive framework for developing mobile applications and games, with good performance, which can be recommended at least for the development of demos and prototypes, or even full-fledged applications.

Source: https://habr.com/ru/post/266301/


All Articles