We recently launched Mail.Ru for Business. In the
last post, we already talked about how to organize corporate mail on Mail.Ru, and today we’ll discuss in more detail the technologies that we used in the implementation. When working on the project, we implemented a number of technical solutions on the server and client side, which eventually allowed us to make the service more convenient. In this post we will describe in more detail how our client part is technologically arranged.
Under the cut - about single-page, collecting errors, templating and moving from one URL to another without reloading the page.
')
Single-pageThe interface of the Mail.Ru for Business main page is quite simple and convenient. It is on this page that the process of adding domains begins. After registering the first domain you will be available admin. All together - the main and admin panel (and other hidden pages) is one full-fledged single-page application.

To implement single-page applications, techniques were used that were successfully used in the development of the Post, Calendar and Address Book. For example, pages are rendered on the client side using the Fest template engine (more on it - just below), and all communication with the server is limited to accessing API methods.
Some techniques, on the contrary, were applied for the first time, and I am very glad that I managed to bring something new in the development process :)
Multi-authorizationIn addition to beauty and convenience, single-page gives us access to multi-authorization buns. We also have multi-authorization on the main Mail; for the same mail with domains and beautiful, this, as they say, is what the doctor ordered. You can simultaneously sit in the corporate and personal box, and quickly switch between them. Again, the page does not reload.
CSS animationWe decided to abandon support for older browsers, because we believe that domain administrators are quite advanced users. For example, IE, we support, starting with the 8th version and above. Thanks to this, our hands were untied in terms of using modern buns. Highlighted among them is CSS animation. As you know, users like everything on the page to be beautiful and smooth. You can, of course, implement this “beautifully and smoothly” with the same scripts, but if the browser itself gives us the opportunity to organize dynamics using standard tools, why not use this?
The main work on the implementation of animation is performed using CSS.
.panel { … top: -110px; transition: top 0.5s 0; } .panel__show { top: 0; }
The script we just run it at the right time for us.
var $panel = $('#id1') , $wrap= $('#id2') , offsetTop = $wrap.offset().top + $wrap.outerHeight() ; $(window).scroll(function() { var show = this.scrollTop() > offsetTop; $panel.toggleClass('panel__show', show); }.throttle(200, $(window)));
But we decided to go ahead and make the animation even for those users who use browsers that do not support these great CSS buns. For them, we make animation using jQuery. In order to determine whether a browser supports CSS-beauty or not, we use the standard browser-based function
CSS.supports :
var cssTransitions = CSS.supports("transition", "all 1s 1s") || CSS.supports("-webkit-transition", "all 1s 1s")
CSS.supports is something like a standardized
Modernizr , only native and performing one specific task - checking for support for a particular CSS property. Native support is in Chrome 28+, FireFox 22+, Opera 12.1+ browsers. For other browsers, we use a
polyfile written by one of our mail developers. This polyfil is by default available on almost any Mail.Ru project and is successfully used in some specific situations.
FestFest is our development. It is a general purpose template engine with which we create pages. Fest is suitable for templating both on the client side and on the server. Its main advantage is the speed of work: the templates are “assembled” on the client really quickly. Another feature is modularity support at the template level: we create one template and use it for many different pages or forms.
All forms that are present on the page - adding a domain, users, popups, adding / deleting an administrator - are built on the same template. Due to the universality of Fest, we can allow ourselves to create completely different controls inside the same template: pop-ups with dropdowns, pop-ups without dropdowns, pages with check-boxes. The formation of all these forms occurs in one place.
History APIAs you can see, we consider it a good practice to use third-party developments, if they are good and suitable for our purposes: why reinvent the wheel? So, in our project we actively use the
History API polifil - fork of the
library from
Devote . Application History API greatly simplifies the life of developers. Mail.Ru for business “magically” moves from one URL to another without reloading the page, depending on what actions the user performed. The History API allows us to transfer the necessary parameters that are not displayed in the UI, and voila: a new slide is drawn. At the same time, if the user reloads the page, then he will be exactly where he was.
How does all this work?
The API provides us with access to the History type object that each tab has and is located at the window.history address in the object model. Using JavaScript, we can manipulate the address bar, “navigating” through the pages using the .pushState (state, title, url) methods, which add a new url to the browser history and .replaceState (state, title, url), which change the current url without adding a new item to the story.
The state property of the history object will always indicate the current “state” of the current url. For example, if we made pushState ({data: “test1”}, “”, “url1”) and pushState ({data: “test2”}, “”, “url2”) - i.e. made two “transitions” to the pages with the addition of items to the browser history and were on the “url2” page, and the user clicked “Back” in the browser and was on the “url1” page, then the value of the history.state.data property will be “test1” - what we need.
This is especially convenient on the main page: if the user wanted to go to a very specific address, but is not currently authorized, then we will pass the state object containing the desired address to pushState, and after authorization we will redirect it to where he wanted to go - in this way he will not have to go over the pens again. The entire transition logic is executed in the browser, without reloading the page, while the page address looks “humanely”, without hashes (“#”).
Then, after authorization, we understand where the user wanted to go, and transfer him to the destination.
RequireJS vs SingleFileA great singe-page application is always a lot of js-modules. Some modules are a common functionality that is almost always needed, some modules are needed only on separate pages. When developing a fairly complex functionality, the number of js-files grows very quickly. At the same time, the developer needs to constantly think about dependencies and how to connect js-files. Making a mistake in the order or forgetting to connect the necessary file, you can get an error in the most unpredictable place. AMD API just solves this problem. The developer simply needs to specify a list of dependencies and declare the modules in a special way, and
RequireJS will take care of loading these modules in the correct order.
But it would be wrong to force the user to wait for the download of multiple files every time he logs into our application. Therefore, we use RequireJS only at the development stage, and the “build” is going into battle - one js-file that contains all the modules necessary for the application to work. The
build is done by the
r.js library, which is part of the RequireJS project.
It is clear that in most cases the entire set of modules is not needed and it may seem that some of them are connected in vain, increasing the amount of data transmitted to the browser. But this is only at first glance. In fact, gzip-compression of the js-files on the server virtually eliminates this problem. Moreover, we save on http-requests. As a result, one big js-file will load faster than several small ones, even taking into account that small files will be loaded “lazily” - i.e. only as needed. For detailed acquaintance with the question I suggest reading the article
Loading and initializing JavaScript or independent research on the topic.
Build projectSuch things as the assembly of js-files described in the previous section should not be done by hands. Before laying out the production, in addition to assembling js-files, you need to collect css from scss-files, “compile” fest-templates and transfer the result to the js-collector, you need to update the version number in configs, etc. All these things needed to be automated somehow.
It was decided to use
Grunt - this is a general-purpose task manager written in js under nodejs. It allows a js developer to write tasks for building a project on a server. For Grunt, there is a database of ready-made tasks, from which, for example,
grunt-contrib-sass and
grunt-contrib-requirejs were taken . Grunt starts the necessary tasks automatically when git push is executed.
Error collectionA web developer is useful to know about errors on the client side. It would be even cooler somewhere to collect them, store them and then correct them. We use a third-party Sentry platform for this.
Sentry is an open source project that works with a whole scattering of languages and platforms. It allows you to track both server and client errors and make statistics on them. This platform is also used, for example, in Mozilla and Instagram.
Sentry is able to send letters with a description of errors. At the same time, it is possible not to fear that identical reports about minor problems will score the box: the parameters of messages that are worthy of disturbing the developer can be easily configured.
With the help of dashboards, we can not only track errors in real time, but also filter them by various parameters - from the source of the bug to the status that we ourselves assigned to it.
Especially indispensable Sentry when catching exotic bugs that we can not reproduce. Agree, having before your eyes a line of code and a description of the error (including the frequency of appearance and the browser), it is much more convenient to understand what went wrong.
However, a simple description of the error and the line number may not be enough. For some exotic or complexly reproducible errors, additional information is needed to analyze this error. For example, for the error “$ is not defined”, you need to understand, and have jQuery loaded in us? To collect browser errors, we use the
Raven.js library, which has one undocumented ability to call the dataCallback handler before each error is sent to the server:
var ravenSetTimeout = typeof setTimeout === 'function' && setTimeout; var options = { dataCallback: function(obj){ var userData; if( typeof obj === "object" ) { userData = obj["sentry.interfaces.User"]; if( !userData ) { userData = obj["sentry.interfaces.User"] = {}; } userData.inFrame = window.parent !== window; userData.jQueryVersion = typeof jQuery === 'function' && jQuery.fn.jquery; userData.setTimeoutBody = typeof setTimeout === 'function' && (setTimeout + "").contains("[native code]") ? "[native]" : ravenSetTimeout ? setTimeout === ravenSetTimeout ? "[raven]" : "[other]" : "[none]" ; } return obj; } }; Raven.config("…", options).install();
In this example, we check if we are running in the iframe, the jQuery version (if it is loaded at all), and the setTimeout code (since it can be replaced). We collect a little more statistical information to correctly diagnose errors. The value of the property “sentry.interfaces.User” will be displayed in the Sentry interface in a special field.
DiscussThis is how our Mail for business works. I hope it will delight users with beauty, convenience and functionality. You can read more about Mail.Ru for Business right
here . Let me remind you that the service is now in beta and we will be happy to receive your feedback and suggestions via mail at
feedback@biz.mail.ru or in the comments to this post.
We are pleased to provide any support to Habr users who want to try our new service.
Olga Alekseeva,
Yegor Khalimonenko (
termi )
Mail frontend development team