I don’t think we’ve written anything comprehensive along those lines. Some ideas:
Keep everything as simple and as general as you can. Don’t leave unused or unneeded functionality in your templates or any other code. When starting from a sample, make sure you understand every line of code.
Additional information for individual nodes or links should be additional properties on the model data object. The values should be JSON-serializable if you are using Model.toJson and Model.fromJson. The property names should be prefixed with “_” if you don’t want to serialize those properties.
You might want to define your templates by writing functions that generate the common parts of templates. Notice how in some of the bigger samples there are functions named nodeStyle() or textStyle() which return property settings and bindings for Nodes or for TextBlocks, respectively. This makes it possible to share style definitions between different elements in a template (e.g. multiple TextBlocks) or between different templates. It also makes templates look simpler.
You might want to define Adornments, especially tooltips or context menus, separately from the node or link templates. This keeps templates simpler looking and allows sharing between templates.
Avoid TwoWay Bindings unless they are really needed because some code (typically a tool or command) actually modifies the GraphObject rather than the data.
Generally there are behaviors that you can implement as DiagramEvent listeners or as GraphObject (or subclass) event handlers. Simpler apps can often implement DiagramEvents; more complex apps typically need the behavior to be implemented as object event handlers, because Diagram-level listeners would require too much special casing on the particular object.
When you want new behaviors, try to extend/subclass the CommandHandler or existing Tool. If none of the existing Tools can be made to meet your requirements, you might need to implement a new Tool. Tools are needed whenever the behavior depends on some sequence of mouse or touch events.
All changes to models or model data or GraphObjects/Panels/Parts/Nodes/Links should occur within a transaction. Keep transactions short – don’t do XHR synchronously in a listener or event handler.
Can you describe what it is that is making implementing new features more difficult?
The main problem for me is file structure.
We have got several templates, several groups, a lot of listeners and one controller (we use Angularjs), state and config files.
Sometimes I need to change some behavior and I don’t know what file I should do change. For instance some handlers are in Tool some handlers are in templates like TextEdited at TextBlock or mouseDragEnter.
Also there is problem with Transanctions (we need to comit if backend response OK, and rolback if backend response FORBIDDEN), we need to remember transaction name, it is easy to remember if diagram changes from one place, but we could change it from several places (For instance: I could add new child Node from diagram by clicking on parent Node and add new Node from page) so I have to write logic for remembering transaction name on controller side and on diagram side.
My Ideas for refactoring are:
Allocate logic to proper files. (for instance smth.templates.js, smth.transanctions.js, smth.layouts.js, smth.tools.js etc) all actions or handlers use ONLY at Tool class files and forbid to use TextEdited, mouseDragEnter and so.
Create gojs diagram as Independent Component, it should help to reuse it at another Environment (now we use Angular and all logic is written in Angular way and it will be impossible to use our code at another framework like Angular2 or React). Also, it is difficult to replace our code to plunker when we encounter with bugs and we need to write you and show where exactly we found a bug.
We have got a very long roadmap for our project and I don’t want to rewrite project from scratch when understanding that our code becomes a mess.
I want to write supportable code for a long term. This is the reason of Why I ask you about best practices.
I think this is an age-old problem for all applications, not just ones that do diagramming. My belief is that only you can decide what policies to follow. Ultimately everything boils down to whether or not it can do the job, and in your case I don’t know how you might need to change things in the future, so preparing for refactoring is hard for me to judge.
And in any case I don’t see why you would need to completely “rewrite project from scratch”, unless you start writing code that isn’t clearly focused and organized.
Whether you put event handlers at “diagram” level or at “object” level often doesn’t matter too much, and you can easily move them from one level to the other. (But of course some events can only be handled at one level, so in those cases you don’t have any option anyway.)
I would think that trying to define a general Diagram component would be counterproductive – it would be such a large wrapper around the GoJS API, and you wouldn’t gain anything.
Any transaction names that you use are normally only used for debugging information, so that you can find out what transaction is ongoing or what transaction happened before. I don’t see any need to try to maintain the same transaction name for the (short!) transaction that happens while the user is performing some action or some gestures and the (short!) transaction that happens after receiving information from the server.
In as much as possible I would try to implement validation logic in the browser so that it “never” happens that you need to undo state changes based on some response from the server. Remember that you cannot control whether the client ever gets a response from the server, much less soon enough for users not to notice.