-
Notifications
You must be signed in to change notification settings - Fork 6
Chaise Dev Guide
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.
- 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.
- 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.
- 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 thecommon
folder and then have the existing functionality call the code inchaise/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 inchaise/common
to get Feature X. In Search, the function's body is replaced with a call to the Feature X function inchaise/common
.
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.
- 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 synchronous cases (e.g. non-promise) : 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 */
function catcher(fn) {
try {
fn();
} catch (e) {
catch_all();
}
}
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.
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 toctrl
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
.
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.
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).
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.
- parent defines entry points which consult local hashmaps etc.
-
window.getPrefill(url)
function -
window.childChanged(url, ...)
function
-
- parent sets up hashmap contents using child url as key
- parent launches child (and captures window object to variable)
- child starts to run
- 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
- have
- child does remaining app logic
- child does submit/data change
- 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, ...)
- have
- parent can poll (or listen?) for child window close status if needed
- ACLs In ERMrestJS and Chaise
- Facet Examples
- Facets JSON Structure
- Logging
- Model Annotation
- Model-based Logic and Heuristics
- Preformat Annotation Guide
- Export Annotation Guide
- Pseudo-Column Logic & Heuristics
- Table Alternatives
- Intro to Docker
- Chaise Dev Guide
- Dev Onboarding
- ERMrest 101
- ERMrest Howto
- ERMrestJS Dev Guide
- Extend Javascript Array
- Custom CSS guide
- Towards a style guide