Stefan Wittmann · May 3, 2016 go to post

You have to use the WRITE command instead of DO. I/O redirection is flawed with the DO command.

But, I do not see you using the new %Object and %Array classes. Instead your code builds a proxyObject and calls $toJSON() on it. Why are you not directly building %Object and %Array instances?

Stefan Wittmann · May 4, 2016 go to post

The navbar is not correctly initialized if it is the only content on the page as it is sized based on parent containers and siblings.

Just wrap it in a div, or a grid system and it will render correctly. You have to do that anyway if you want to add more elements to the page.

Stefan Wittmann · May 9, 2016 go to post

I would like to add that $zf(-3) and $zf(-4,1) calls can only be used to load what we call a callout library. This is a specific DLL that properly exports functions during linking and  exposes them in a ZFENTRY table. You can find more material about the callout gateway in this part of the documentation:

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

If you want to work with a specific DLL that is not a callout library, you have to wrap the library, e.g. in a Java jar library or a callout DLL and then call out from Caché Object Script.

The project Evgeny mentioned may be of help as well.

Stefan Wittmann · May 9, 2016 go to post

Kenneth, what is the use case for this? If you want to move Global data around, there are clearly optimized APIs for this task.

If you want to store JSON-like data you should be looking into the Document Data Model that is introduced with Caché 2016.2. The %ZEN.proxyObject has an interface for persisting dynamic data as well, but it clearly has been superseded by the new JSON capabilities available in Caché 2016.1 and up.

Without understanding your requirements, there are two potential approaches how you can serialize a Global in a JSON structure. One way is to represent the Global with a top-level object and all nodes in one big array. Each node is represented by an object, which includes the subscript as an array:

{"GlobalName":"Person","nodes":[

{"subscript":[],"value":1},

{"subscript":[1],"value":"Smith,John^M^1968-01-12"},

{"subscript":[1,"Address"],"value":"123 Main Street^Some City^CA^92627"}

]}

This serialization is easy to generate and consume as well. The complete subscript is available within each object, which makes the (de-)serialization logic simple. The downside of this approach is that you can only represent a Global as a whole. You could enhance this approach to start at a specific subnode, but you can not easily filter for specific sub-paths.

Another approach is to nest subnodes. This makes the (de-)serialization logic more complex as it has to be recursive:

{"GlobalName":"Person","nodes":[

{"subscript":[],"value":1,"children":[

{"subscript":[1],"value":"Smith,John^M^1968-01-12","children":[

{"subscript":["Address"],"value":"123 Main Street^Some City^CA^92627"}

]}

]}

]}

This allows you to easily skip sub-paths during processing, but the processing logic gets more complex. I realize there are more variations how you can represent a Global in JSON format, but I think there are two fundamental approaches, flat and nested.

Again it depends on what you want to achieve. I would be interested in learning more about what you have in mind.

Stefan Wittmann · May 13, 2016 go to post

Not sure why this is happening. This is working fine for me:

SELECT now() as tnow, max('2016-05-13 08:51:16') as latest, DATEDIFF (s,now(), max('2016-05-13 08:51:16')) as difference
FROM Sample.Person

tnow                                   latest                                  difference
2016-05-13 13:38:08    2016-05-13 08:51:16    -17212
2016-05-13 13:38:42    2016-05-13 08:51:16    -17246
2016-05-13 13:38:56    2016-05-13 08:51:16    -17260

I am using the function now().

Stefan Wittmann · May 17, 2016 go to post

When the user refreshes the page, the template defined in the parameter TEMPLATECLASS is loaded. Also, the default keys as specified in your XData block will be used to retrieve your layout and our data (initialLayoutKey and initialDocumentKey).

You can use the documentView's callback onload and load another template and set correct keys before the documentView actually starts the setup process.

Assuming the following documentView definition:

<mojo:documentView id="mainView"
onload="zenPage.initPage();"
initialDocumentKey="home"
initialLayoutKey="home"
ongetlayout = "return zenPage.getContent('layout',key,criteria);"
ongetdata = "return zenPage.getContent('data',key,criteria);"
>
<mojo:jQM-1.3.2-PageManager jQueryAnimation="flip" onPageShow="zenPage.onPageShow(layoutkey,documentkey);">
<mojo:jQM-1.3.2-Helper/>
<mojo:googleMaps-3-Helper/>
<mojo:HTML5Helper/>
<mojo:mojoDefaultHelper/>
</mojo:jQM-1.3.2-PageManager>
</mojo:documentView>

You can define the following method initPage:

ClientMethod initPage() [ Language = javascript ]
{
    zenPage.gotoArea('the area identifier you want to load')
    zen('mainView').initialLayoutKey = 'the layout key you want to use';
    zen('mainView').initalDocumentKey = 'the data key you want to use';
}

Please note that you are first loading your template and then set the initialLayoutKey and initialDocumentKey values, as the documentView hasn't actually done any work yet.

It is on you to store any relevant state somewhere locally, e.g. in a cookie or the localStorage. You can retrieve that state in your initPage() method and initialize your page accordingly.

Stefan Wittmann · May 17, 2016 go to post

This should work in general unless you are switching your template after you set the key with the criteria. Can you provide the code section where you set the key and request the refresh?

Stefan Wittmann · May 19, 2016 go to post

I am not able to view the image, but in general Object Synchronization can be used by multiple servers. The architecture is designed in such a way, that usually one node is taking the role of a master and all other nodes become clients to this master.

You can implement other schemes as well, as indicated by this paragraph in the documentation:

For object synchronization, the idea of client and server is by convention only. For any two databases, you can perform bidirectional updates; if there are more than two databases, you can choose what scheme you use to update all of them (such as local databases synchronizing with a main database independently).

There is no support for bi-directional updates at the same time, so you have to sync one direction first and after that happened you can sync the other way around. The more complex your mesh becomes the harder it becomes to resolve conflicts.

That's why the documentation recommends staying with a master / client scheme, as this reduces the complexity of resolving conflicts.

Also, you have to be aware that Object Synchronization is not built for real-time updates. You are synchronizing at discrete intervals.

Stefan Wittmann · May 23, 2016 go to post

Steve, the correct approach is to either

a) subclass the plugin in question and generate a different HTML base or invoke additional beautify code

b) build your own plugin if you want to support a new layout object

What you describe sounds like a specialized $navbar component, so you may want to subclass the bootstrap plugin and register a specialized $navbar widget, e.g. $mynavbar.

Stefan Wittmann · May 27, 2016 go to post

You cannot use macros in Zen runtime expression this documentation chapter covers runtime expressions:

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

If this question is actually about localizing text in Zen, please take a look at this documentation chapter:

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

Localize your text in %OnGetJSResources() and load it on the client-side via zenText(id).

Stefan Wittmann · May 30, 2016 go to post

I only managed to get my code down to 207 characters and it's even missing the whitespace->0 mapping, I saw that too late. Butwhoneedswhitespacestodayanyway?

In case you are curious here is my code:

 #define a(%x) $LG(o,$a($E(s,%x,%x))-96)
 s (x,o)="" f m=2:1:9{s r="" f n=1:1:$S(m=7:4,m=9:4,1:3){s r=r_m,o=o_$LB(r)}} f j=1:1:$L(s)-1{s x=x_$$$a(j) s:$E($$$a(j+1),1,1)=$E(x,$L(x),*) x=x_" "} q x_$$$a($L(s))

The first part actually builds a $LIST with the mapping from characters to the corresponding keypad sequence. The second part loops over the input string and does a lookup for the keypad sequence. A look-ahead checks if I have to add a whitespace to indicate a pause. Because of the look-ahead I have to treat the last character conversion outside of my main loop.

Tricks I used:

  • Replace if statements with $SELECT
  • Use SET statement to set multiple variables to the same values
  • Built a macro for code you need multiple times (I need the lookup three times, in my main loop, for the look-ahead and for the last character conversion) 

Not an optimal algorithm and it's even lacking a piece, but still it is a fun exercise.

Stefan Wittmann · Jun 6, 2016 go to post

I think you have two options:

  1. Open the generated class directly (*.cls instead of *.bpl)
  2. Open the Business process and go to View->View Other Code (Control + Shift + V)
Stefan Wittmann · Jun 9, 2016 go to post

Chris,

we are currently working on REST discovery and documentation, so expect more in the future. Currently, your best option is to manually document your URI paths. Personally, I find Swagger a very good tool to document REST endpoints as it also allows you to test interfaces and to generate clients for multiple languages.

Stefan Wittmann · Jun 9, 2016 go to post

Hi Danny,

great to see you here.

The standard credentials you can use on a new system are _SYSTEM / SYS. You can manage the users in the management portal later on.

Stefan Wittmann · Jun 21, 2016 go to post

Hi Chris,

unfortunately, the information in the Caché Technology Guide that you refer to is outdated. We do support JPA (Java Persistence API) via Hibernate and are currently in the process of updating our dialect. We provide no special tooling for EclipseLink.

I will make sure that the information on the website is updated.

Stefan Wittmann · Jul 8, 2016 go to post

The document ID is exposed implicitly as a virtual column named "_documentID". Here is a simple example that you can run in your SAMPLES namespace:

SELECT _documentID, Name, DOB, SSN FROM JSON_TABLE('People','$' %TYPE 'Sample.Document.People')
Stefan Wittmann · Jul 12, 2016 go to post

Hi Simcha,

the best way to leverage this widget is to build your own bootstrap dual listbox plugin. Take a look at the code of the other plugins and make sure to check the documentation for creating helper plugins before you start:

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

Let us know if you encounter any difficulties.

N.B.: I should mention that you can also just create the proper HTML code in your layout, but then you have to manually call the initialization and refresh methods in your application code all the time.

Stefan Wittmann · Jul 15, 2016 go to post

As Kyle mentioned your code should be:

<button caption="Save" onselect="zenPage.saveRecord();" />

It appears the section Lisa linked to is not correctly displaying onselect multiple times.

Stefan Wittmann · Jul 15, 2016 go to post

I haven't done this before, but I am pretty sure you have to call %Save in order to apply your changes.

Stefan Wittmann · Jul 19, 2016 go to post

Hi Fabio,

yes, in theory, there is a way how you can load other templates, but I do not encourage this approach as loading a template can be expensive.

It is better to use one of the following two approaches:

1) Generic utility functions should be placed in a javascript file that can be included

2) You can also build a base template that includes your utility functions and let your other template subclass the base template.

The second approach makes sense if it is unlikely that a given template that requires these utility functions will be loaded and you want to save the footprint. Otherwise, I would always stick to option 1.

Stefan Wittmann · Jul 19, 2016 go to post

SPAs can work with the browser back and forward button if the URL is updated and the page is pushed to the history stack.  The page Eduard refers to actually mentions this. The jQuery Mobile plugins do implement and support this, you can verify this by opening

/csp/samples/ZMdemo.jQM145.HomePage.cls?

in your browser (point it to your local instance) and play with it.

Currently, our Bootstrap plugin implementation does not support this, because we only implemented a helper plugin and not a page manager. Page managers are responsible for the overall behavior of the SPA.

Stefan Wittmann · Jul 28, 2016 go to post

I haven't dealt with SSEs yet, but I can say that WebSockets work like a charm. 

SSEs are not using a special protocol, they work within the HTTP specification and are build on the server by setting the "Content-Type" header to "text/event-stream". From my understanding, this can be easily implemented with a CSP page. 

The reasons why SSEs are not as popular as WebSockets are:

SSEs have some interesting advantages over WS like the use of a simpler protocol, automatic reconnection, and event IDs.

Stefan Wittmann · Aug 22, 2016 go to post

Try passing in the stream object. You are operating on a string, that is why you are getting MAXSTRING errors. This should work:

TRY
{
Set RequestObj = ##class(%Object).$fromJSON(%request.Content)
} CATCH(Exception) {
Set Status=Exception.AsStatus()
}
Stefan Wittmann · Aug 25, 2016 go to post

Steve,

the onevent callback is used for events like viewport adjustments and you should call this method for your custom event handlers.

Zen Mojo does not register to all possible events on a page in order to throw them at onevent. Page performance would be horrible. If you are interested in any additional events, like onkeydown, you have to register them in addition.

HTH,

Stefan

Stefan Wittmann · Aug 29, 2016 go to post

Whether you should be using the default transport layer of Zen and Zen Mojo or built your own REST interface depends on a couple of things:

  1. Your skill set and your development resources
  2. The size of your application
  3. How many different front-end interfaces you have to serve

If you have to serve multiple front-ends, e.g. a native mobile app, a web app and some data to a reporting layer then a REST interface makes you more flexible to actually meet the data needs of each of them without duplicating too much code.

If you just want to get your Zen/Zen Mojo application working without too much extra learning, the standard transport way is just fine.

In general REST interfaces are just a clean way to build your communication layer. If you make your REST calls in the Zen Mojo onGetContent method you will still get the benefits of client-side caching within Zen Mojo. The only real difference is that your data retrieval code now lives in a separate class (a subclass of %CSP.REST) instead of a Zen Mojo template.  

REST interfaces are indeed easier to test and debug as they can be tested by tools. The Zen / Zen Mojo client/server communication requires some knowledge if you really want to know what is going on.

HTH,

Stefan

Stefan Wittmann · Sep 1, 2016 go to post

As I mentioned before the onevent method is not directly called for most events. The onevent method is only called for viewport changes by default.

Here is an example how you can register your own events. Everything happens in the homepage class.

Step 1) Subscribe to the onPageShow callback

Most pageManagers implement the onPageShow method to let you know when a certain layout has finished rendering. This is a complete documentView example:

<mojo:documentView
 id="mainView"
ongetdata="return zenPage.getContent('data',key,criteria);"
ongetlayout="return zenPage.getContent('layout',key,criteria);"
initialDocumentKey="login"
initialLayoutKey="login"
>
<mojo:jQM-1.4.3-PageManager onPageShow="zenPage.onPageShow(layoutkey,documentkey);">
<mojo:HTML5Helper/>
<mojo:jQM-1.4.3-Helper/>
<mojo:mojoDefaultHelper/>
</mojo:jQM-1.4.3-PageManager>
</mojo:documentView>

Step 2) Implement your logic

I'll just paste the code in here, as I have documented the methods individually:

/// Gets called when a certain layout has been rendered.
/// In this case we are registering additional events and
/// forward them to the onevent callback method in the template.
ClientMethod onPageShow(
    layoutKey,
    documentKey) [ Language = javascript ]
{
    if (layoutKey=='login') {
        zenPage.registerEventHandler('txt-user','keydown');
    }
}

/// Register an event to a layout object by key.
ClientMethod registerEventHandler(
    key,
    eventType) [ Language = javascript ]
{
    var element = zen('mainView').getItemByKey(key);
    element.$findElement('').addEventListener(eventType,new Function('evt','return zenPage.myCustomEventHandler("'+eventType+'","'+key+'");'),false);
}

/// Forward an event to the onevent method in the template.
ClientMethod myCustomEventHandler(
    evtType,
    key) [ Language = javascript ]
{
    var item = zen('mainView').getItemByKey(key);
    var template = zenPage.getTemplate();
    template.onevent(evtType,key,item.$findElement('').value,'mainView');
}

Zen Mojo does not provide a lot of special methods for this task. It involves some coding.