Customizing the UI theme

The UI uses Twitter Bootstrap to control the styling and layout. The default theme is simply the default theme for Bootstrap. There's a number of additional themes included. If you'd like to create your own theme, see http://bootswatch.com/help/ for instructions on creating a theme for Bootstrap. Once complete, you'll have a css file containing your custom theme styles. To install your theme:

  • Copy your custom theme css file to the "Content" subfolder

  • Open the config.js file and navigate to the themes array. Then, add an entry for your theme. For example:

      MyCustomTheme: 'https://' + 
          window.location.href.split('/')[2] + window.contextPath +
        'Content/my-custom-theme.css',
    

Customizing the Preview window

The Preview window has its own style sheet, PreviewCustom.css in the Content folder. For example, to change the amount of space at the top of the window, change padding-top to the desired value.

Customizing the UI behavior

There are several pieces making up the UI architecture of Stonefield Query. In order to make changes, it's necessary to have an understanding of each component. In this discussion, we'll use the main Reports Explorer view as an example to illustrate how the different pieces work together.

Views and view models

Views are defined in .cshtml files. ReportsExplorer.cshtml has the layout information for the Reports Explorer view. An entry for each view and dialog is included in Start.cshtml; for example:

@RenderPage("/Views/ReportsExplorer.cshtml")

Each view is bound to a view model; for the Reports Explorer, this is defined in vm.reportsExplorer.js. This binding is defined in binder.js; for example:

ko.applyBindings(vm.reportsExplorer,
    getView(config.viewIDs.reportsExplorer));

The bindings work through the data-bind attributes in the html. For example, near the top of ReportsExplorer.cshtml, you'll see this attribute:

data-bind="command: showFolderViewCommand"

This binds the HTML control to the showFolderViewCommand defined in vm.reportsExplorer. Clicks to this element are handled by this object in the vm.

These views are defined as "routes" in routeConfig.js. A route contains an ID (such as "#reports-explorer-view"), a navigation hash (such as "#/reportsExplorer"), a title, and a function to be called when the view is activated.

Data and models

When a view is made visible in the UI, its activate function is called. In the activate method of vm.reportsExplorer.js, the important call is getData(). This function calls into the dataContext object to retrieve the data necessary for this view.

The dataContext is an object that all view models use to get their data. In particular, the dataContext.report object is defined with two methods: dataService.reports.getReports and modelGenerator.report

The dataService items are where data is actually retrieved from the server. The modelGenerator is used to turn this raw data into objects that can be used with the bindings mentioned earlier.

In dataService.reports.js, the getReports function simply makes an Amplify.js request of ID 'reports'. This request is defined at the top of the js, to use a GET request to /api/reports. This means that this request is handled by the Get() method of ReportsController.cs on the server.

The Get() method returns a set of ReportHeaderModel objects. The purpose of these header objects is to define the structure of the information that's returned from this controller call. This is defined in ReportHeaderModel.cs.

When this data is received on the client side, each item is passed through model.observableGenerator.report. This creates an object that's compatible with the binding system.

To summarize:

  • Views are defined in .cshtml files. To add a new view, first create a .cshtml file for it.

  • Each view needs a view model. View models are defined in vm..js files. Create and add the vm..js file for the new view.

  • Views and view models are bound together in binder.js. Add a binding to this file to link the view and view model.

  • View Models retrieve data by calling dataContext functions. If your view requires data from the server, add a function to the dataContext.js file that returns the data.

  • dataContext.js methods generally get their data by calling dataService functions, so if necessary, also add a dataService function for defining the API call for the data.

  • DataService functions retrieve data from the server through /api/* calls. Edit the config.js file to add an definition for the new API call.

  • These API calls are handled by the appropriate method on a controller class. Add a new controller class to the Controllers folder to handle the API request.

  • The controllers return Model objects, which format the necessary information from SQ.NET objects. Add a new model class to the Models folder that defines the structure of the model.

  • The client passes this data through an observableGenerator function, so it can be bound to an HTML view. Edit the model.observableGenerator.js file and add a function for mapping the properties of the model to a javascript object with observable properties on it.

  • The observable generator function typically creates a new instance of a model..js file with observable properties. Add a new model..js file for your model.

Customizing security

If you want to add security control to some things that are currently available to all users, do the following:

  • Create a user resource for the item in your project's Settings.xml file. To do that, select the User Resource panel in Studio, choose Create User Resource from the Objects menu, and enter a name for the resource.

  • Go to the element you want to hide in the appropriate cshtml file. For example, suppose you want to add security to the General page of the options dialog, which is defined in Options.cshtml. Locate the tab for the general options and add controlAccess binding to it. Out of the box, it appears as:

      <li class="active"><a href="javascript:void(0);"
          data-toggle="tab"
          data-target="#options-group-general"
          data-bind="text:languageResources.generalOptionsCaption">
          </a></li>
    

    After you add the binding, it appears as:

      <li class="active"><a href="javascript:void(0);"
          data-toggle="tab"
          data-target="#options-group-general"
          data-bind="text: languageResources.generalOptionsCaption,
          controlAccess: { type: 'resource', value: 'OptionsGeneralTab',
          action: 'disable' }"></a></li>
    

Once you do this, this resource item appears in the Resources list for a role in the Security dialog and you can toggle whether you want that role to have access.