Skip to content

Chaise Dev Guide

Hongsuda edited this page Feb 23, 2017 · 41 revisions

This is a guide for people who develop Chaise. Because this is not an exhaustive guide, consider looking through @johnpapa's Angular 1 style guide to understand the spirit and conventions of Angular development.

Angular-related

  • Use controllerAs syntax instead of $scope whenever possible and refrain from using $rootscope
  • Angular allows for users to define a module and later extend that module with new services, controllers, factories, providers, and so on. These other components should be defined in separate files to avoid having one single *.app.js file.

One-Time Binding

  • Use one-time binding for improved performance.
  • If you know an Angular expression won't change its value after the first digest (e.g. displaying an ERMrest table name), prepend the binding with :: to benefit from one-time binding. For more details about this, see the One-Time Binding section of the doc on Angular expressions.

General

Extracting common code

  • When writing functionality that can already be found in another part of Chaise, that's usually a good candidate for pulling this functionality out and into the chaise/common folder. Instead of refactoring the common functionality out of an app, a good practice is to simply copy the existing functionality into the common folder and then have the existing functionality call the code in chaise/common. Actual refactoring will occur in a later cycle. This minimizes disruptions to existing apps and encourages code reuse.
  • An example: I'm working on a Feature X for the RecordEdit app, and this function has been already been written into the Search app. Instead of duplicating the function in both apps, I copy the code for Feature X from Search into chaise/common and genericize the function as necessary. In Data Entry, I simply call the code in chaise/common to get Feature X. In Search, the function's body is replaced with a call to the Feature X function in chaise/common.

Session

The Navbar fetches the session object for information to display in the Navbar. Each app needs to also individually fetch the session so that we can make sure the session is available before trying to do anything with the reference. NOTE: This was causing a race condition before when we were relying on the session being fetched in the navbar and attaching it to $rootScope.

Catch-all

  • Every time a promise is called, always use the catch for error case and catch_all function. If you want the global handler to handle the error, throw that error in the handler function. Example:
catch_all() /* catch all error cases service */
promise.then(handle_success, null).catch(handle_my_errors).catch(catch_all);
/* or */
.then(handle_success).catch(handle_my_errors).catch(catch_all);

\\handle_my_errors
function handle_my_errors(err) {
    //handle the error according to your requirement
    // else throw the error to be caught by the catch_all
    throw err;
}
  • try/catch:
try {

} catch (err) {
  // catch your specific error
  //   ...
  // else call the catch_all()
  //   catch_all();
} 
  • In other cases: call a wrapper function that will wrap your function with catch_all
catcher(call_back_function);  
/* Jessie: add the rest of the code snippet here */

Use variable vs string in lookup

The purpose of using variables or enumeration is to avoid rewriting (or copy-and-pasting) the same string in multiple places.

  • if you need to make a lookup based on a tag name only in one call site, simply use a string (e.g., "tag:isrd...")
  • if you have more than one use for the tag name string, but only in one script comprising ERMrest, then define it toward the top of the closure, and keep it local to that closure (e.g., var _tag_default = "tag:...default")
  • if you have more than one call site in more than one script, then define the variable in the utility.js script and add it to the module (e.g., module._tag_default = "tag:...default") and it may then be used by code in different ermrestjs scripts (e.g., referencing module._tag_default somewhere). Note that this is not being added to the public interface. Code outside of the various ermrestjs scripts are not intended to use these variables. Hence we follow the underscore prefix convention _variableName, which by convention indicates that the variable should be considered private to the module and clients are at least warned not to use it.

Ermrestjs#68 contains detail discussion related to this topic.

Naming Conventions

There are a few naming conventions that are being used across the apps. This pertains to variables, module names, and file names.

  • File names should be written in camel case (camelCase) with identifying information separated by . (*.controller.js, *.app.js, *.html).
  • Angular modules need to be defined like the following chaise.*. Chaise identifies the set of apps it applies to and the * is that modules purpose in chaise.
  • Service, Factory, Provider, Controller, and other angular classes should be defined with camel case text leading with a capital letter. For example: ErrorDialogController is the convention for naming controllers. Don't shorten the text to ctrl because we should be using controller as syntax and want to have a more readable structure to our code.
  • Variables should follow a similar naming convention using camel case text. Variables and functions that are prefixed with an underscore _, should be treated as private variables and used with caution.
  • Folder names should be different from file names. Of course folders don't have an extension so it's more apparent that they are folders, but developers should use - separated names for folders, i.e. common\templates\data-link.

Placeholder hyperlinks

When using <a></a> tags to trigger in-page functionality instead of linking to an external website, do not use the href attribute. In the past it may have been common to use <a href="#" onclick="myJsFunc();">Link</a> or <a href="javascript:void(0)" onclick="myJsFunc();">Link</a> in order to satisfy validation requirements and other reasons; however, the HTML5 spec now allows anchor tags without href and instruct browsers to treat these as placeholder hyperlinks.

Communication Between Parent and Child Tabs

Use Case

We want to open the RecordEdit in a new tab from different apps to add new records or edit existing records of some table. We may want to pass the prefill data to the RecordEdit app. Upon submission of the changes, we want to see our originating app/tab updated with the new or modified records without refreshing the whole page. We want to avoid using cookies (sent from/to the server with each http response/request) or local storage (no expiration date and requires clean up).

Concept

Parent tab defines function calls on its window object, which are used by the child tab to communicate with. The child tab first calls window.opener to get a handle of the parent's window object.

Workflow

  1. parent defines entry points which consult local hashmaps etc.
    • window.getPrefill(url) function
    • window.childChanged(url, ...) function
  2. parent sets up hashmap contents using child url as key
  3. parent launches child (and captures window object to variable)
  4. child starts to run
  5. child does conditional prefill startup
    • have window.opener, or skip prefill
    • have window.opener.getPrefill, or skip prefill
    • prefill = window.opener.getPrefill(child_location)
    • prefill != null, or skip prefill
  6. child does remaining app logic
  7. child does submit/data change
  8. child does conditional signalling after server update success
    • have window.opener, or skip signal
    • have window.opener.childChanged, or skip signal
    • window.opener.childChanged(child_location, ...)
  9. parent can poll (or listen?) for child window close status if needed
Clone this wiki locally