Get an inside look behind the more challenging and interesting parts of the Web Report Designer’s latest growth.
Interesting architectural changes under the hood, business object support and quality-of-life improvements in the Telerik Web Report Designer.
In this blog post we take a close look at the latest features of the designer that will improve your overall experience while working with it. Stay tuned to find out more.
The property editors are one of the major parts of the web-based Report designers’ code base. We have a lot of different types of properties, with their own specific logic, both for the data they represent and the way they should be displayed and communicate with the design surface—they are jam-packed with action.
Doing this with jQuery and Kendo UI presented us with a fair bit of challenges. For starters, we kept HTML templates in the TypeScript files—not a major problem for smaller bits of HTML, but it proved to be an issue with bigger, more complex templates. Combined with the fact that DOM manipulation was done primarily through jQuery and keeping the TypeScript models up to date was done through events, reading and tracing the logic of the code had become a challenge.
State management was also proving to be an issue—we were using custom events for everything, again not aiding us in keeping things simple. The complexity had reached an all-time high.
We decided that it was time for a change. One of the most effective ways to reduce the clutter was to move the HTML templates to their own files. But this sounds simpler than it is—we have a great number of templates and we did not want to load them from the server, as there would have been a noticeable effect on performance. Also, this would have required storing them as resources, which would further complicate things.
We also wanted to keep the templates in HTML files, not as variables in a TypeScript file, to be able to take advantage of IntelliSense, automatic closing and updating of tags and all the other benefits that IDEs provide.
That’s how we came up with our simple, yet effective HTML to TypeScript template generator. Its job is to watch for HTML file changes (add, remove and edit) and generate a TypeScript file, which has an object that contains the templates from the file system. The structure of the object resembles the folder structure. For example, if we have this file/folder structure:
CatsAndDogs
catsAndDogs.html
Cat
cat.html
Dog
dog.html
The resulting templates file will be:
export const Templates = {
CatsAndDogs: {
catsAndDogs: `...`,
Cat: {
cat: `...`
},
Dog: {
dog: `...`
}
}
}
This way we could start moving the HTML out of the TypeScript files with relative ease and with no performance considerations. We started executing our plan and then we noticed something …
Having moved some templates, we noticed that we could benefit from using Kendo UI’s MVVM to simplify the way we worked with the DOM. Moving parts of the property editor logic to Observable Objects and then using a View to connect the HTML and JavaScript was our initial plan, but it again involved some manual, repetitive work and some questions—when should the binding happen, what if we want to execute something before/after the binding phase has occurred, when should unbinding happen as event handlers were also involved.
Also, we found a way to improve the traceability issues—we started using strongly typed Observables for state management and communication, but again we didn’t have one clear, established way of doing things.
And then we had a light-bulb moment—we could join the Observable Objects and Views in one, add lifecycle (before/after init/destroy) hooks and have one uniform way of using the MVVM pattern.
We called them Components. Now, if you are coming from the Angular/React/Vue world, you are familiar with the idea of Components, but, if you are not, here is a small recap of the main goals of a component:
We easily accomplished the first three points, but the challenge came with supporting child components and bindings. We wanted to be able to use an HTML element name for the component in our views and declaratively create bindings:
<cat name=”Name” onCatMoving=”catMoving”></cat>
But we also didn’t want to affect performance.
The solution is pretty elegant—we use TypeScript decorators to register components in a global manager, and to find child components we use an attribute, data-wrd-component
:
<cat data-wrd-component name=”Name” onCatMoving=”catMoving”></cat>
After rendering the Component (Kendo View) we search for all elements marked with “data-wrd-component,” do a lookup in the registry for their synthetic name, create an instance of the component class and insert the rendering result in the synthetic element:
<cat data-wrd-component name=”Name” onCatMoving=”catMoving”>
<img src=”cat.png”>
</cat>
Alright, this covers the rendering of child components, but what about bindings? We needed Input (parent-child) and Output (child-parent) bindings.
We again used decorators to mark fields/properties as Inputs/Outputs. For the Input bindings, Kendo UI had us covered. As Components are Observable Objects, we could bind to the “change” event and update the required property in the child component.
For Output bindings, we used an event emitter. The “onCatMoving” from the example above is an Event Emitter in the child component, while the “catMoving” is a function in the parent component. We automatically wire things up and the child component must only call the emit method of the Event Emitter to update the parent.
Overall, our creation strategy looks like this:
We were finally able to start utilizing the components in the property editors. After a fair bit of refactoring, the property editors evolved to be smaller, simpler and more efficient. We split some editors in multiple components, started relying on component bindings to pass required data and used the out-of-the-box Kendo UI data-binding to work with the DOM.
Utilizing all of this, we were able to improve our tests a fair bit, while also making it easier to write new ones. We were also able to fix a lot of issues in existing editors, without worrying for unintentional side effects, and improve the speed of creation and quality of new editors.
Seeing that using the component approach works for us, we have started using it for all applicable things in the Web Report Designer.
The object data source gives us the ability to use business class objects from a custom assembly as a data source. But how is this accomplished in the context of a web application? Also how are security risks negated? These are all good questions that we are going to answer.
First things first—we need to load the assemblies on the server, where the Web Report Designer service is running. Then we need to get the information for the types that are in them, so that we can send that information to the frontend. But which assemblies should be used? Using all available assemblies is a serious security risk, as this would give users the ability to execute anything they want—or, to put it otherwise, remote code execution. To avoid this, we use the assembly references element, which gives developers the ability to include only the assemblies that they want to be available.
But sometimes assemblies have inherited assembly names, which we might not want to include. For that reason, we have the clear
and remove
elements, which can remove all the inherited names or remove a particular one, respectively.
All right, having overcome this problem, we can continue with collecting our data. We load the assemblies that are in the assembly reference element, and with the help of reflection we get the types that are in them. We collect some basic information about them, like namespace and name, and serialize it to JSON. This information is then displayed in the Object Data Source Wizard’s first page.
For the second page, where we select the data member, we send the chosen type back to the server and again with the help of reflection we collect the property/method/constructor information for the given type, serialize it to JSON and dispatch it to the frontend.
It is important to mention that validation is run on each step of the configuration, so that we don’t give access to anything outside of the registered assemblies.
If the chosen data member has parameters, we will display a parameter configuration page in the wizard, where the real and design-time values for the parameters can be set.
Having everything configured, we can send the data source information to the server, where we load the assemblies and types, and get the information for the data member, so that we can display a preview of the results. And that’s it—the Object Data Source is ready to be used in your report!
For more information related to configuration, you can take a look at the ObjectDataSource Wizard page. For an example, you can open the Speakers demo report, which uses a preconfigured business objects library (available from R3 2021 SP1).
Adding support for something like copy or paste can get complicated, depending on what needs to be copied and from where the copied data should be accessed.
The style modification support is a good example for that. The style information must be copied from one component, a textbox for example, then paste must be available both from the context menu and from a shortcut for all other components that have styles. Moreover, paste should be supported for multiple components.
Our solution is to use the Command pattern and have all the logic associated with how a style should be copied/pasted, which component(s) have been targeted and what should be refreshed encapsulated in a command. The command takes care of everything, knows if it’s available for execution (e.g., the copy/paste/reset style commands cannot be executed on Data Sources, and the commands are aware of that!), and in this way the only responsibility of the user of the command is to call it (also, in some cases, check if any UI element should be displayed by querying the command availability).
Using the same approach, we have implemented a large part of our UI interactions, including menu actions, component creation and selection, keeping everything well organized and easy for future use.
The Web Report Designer continues to evolve and change both on the inside and the outside—it has matured quite a lot—and we are thrilled about that!
In this article we took a head-first dive in the more challenging and interesting parts of the Web Report Designer’s latest growth. I hope you enjoyed reading the article and that I was able to give some insights on the inner workings of our web-based designer. In case you are interested in something particular or if I couldn’t explain some of the concepts well enough or with sufficient detail, please a drop a line in the comments—I would appreciate it highly.
Telerik Reporting is a complete, easy-to-use and powerful .NET embedded reporting tool for web and desktop applications that supports: Blazor, ASP.NET Core, ASP.NET MVC, ASP.NET AJAX, HTML5/JS, Angular, WPF, WinForms and UWP. Also available as a part of our Telerik DevCraft bundle, Reporting allows you to create, style, view and export rich, interactive and reusable reports to attractively present analytical and any business data. Add reports to any business application through report viewer controls. Export the ready reports in more than 15 formats.
If you still have not tried it, you can start a free trial to take a closer look. We also provide a support service we are proud of and resources that will help you along the way.
Kaloyan Pavlov was a Software Developer in the Telerik Reporting division.