ui-harness.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /* appjet:version 0.1 */
  2. /* appjet:library */
  3. // Copyright (c) 2009, Herbert Vojčík
  4. // Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)
  5. var _ = import({}, "lib-support/useful");
  6. var _testView, _showNoCode, _tabs, _code;
  7. /**
  8. * Call this to say you will use the harness.
  9. * Enables autodispatch with dispatchPlus, showNoCode and testView,
  10. * as well as prepares harness components.
  11. * You should call harness(path) afterwards to actually fill it with contents.
  12. @param fb if evaluates to true, uses facebook tags to build harness
  13. for facebook views.
  14. */
  15. function prepareHarness(fb) {
  16. import(_, "lib-app/mvp", "lib-app/dispatch-plus", "lib-app/liveui");
  17. if (fb) import(_, "lib-app/facebook-ext");
  18. var _canvas, _prints = DIV();
  19. page.setView(fb ? _.box(
  20. _tabs = _.box(_.lazy(_renderFbTabs)),
  21. _canvas = _.box(DIV({style:"margin-top: 3em"},
  22. _.data("view")
  23. )),
  24. _code = _.box(
  25. DIV(HR(), P("Code of scenario: "), CODE(_.data()))
  26. )
  27. ) : _.box(TABLE({cellspacing:0, cellpadding:0, style:"width: 100%"},
  28. TR(
  29. _tabs = _.box(
  30. TD({rowspan:2,style:"width: 150px; vertical-align: top"}, _.lazy(_renderTabs))
  31. ),
  32. _canvas = _.box(TD({style:_.seq(
  33. "padding:20px;",
  34. "vertical-align:top;",
  35. "background-color:", _.outerdata("col")
  36. )}, _.data("view")))
  37. ),
  38. TR(
  39. _code = _.box(
  40. TD({style:"padding:20px"}, HR(), P("Code of scenario: "), CODE(_.data()))
  41. )
  42. )
  43. )));
  44. _canvas.given({view:_prints});
  45. page.setPresenter(_.dispatchPlus);
  46. page.setViewModel({col:"inherit"});
  47. // var _oldPageBodyWrite = page.body.write;
  48. /**@ignore*/
  49. /* page.body.write = function(text) {
  50. _prints.push(html(text));
  51. };*/
  52. _testView = function(v) {
  53. _canvas.model.view = v;
  54. // page.body.write = _oldPageBodyWrite;
  55. };
  56. _showNoCode = function() { _code[0].length = 0; };
  57. }
  58. function _renderTabs(model) {
  59. var list = DIV();
  60. model.forEach(function(x, i) {
  61. list.push(DIV({style: "padding: 10px; background-color: "+x.col},
  62. link(x.link, x.text)
  63. ));
  64. });
  65. return list;
  66. }
  67. function _renderFbTabs(model) {
  68. var inputs = model.map(function(x){
  69. return x.link+"_"+x.text;
  70. });
  71. return fb.ui.tabs(inputs);
  72. }
  73. function _toLink(fname) {
  74. return fname.split("_")[1].split("$").join("-");
  75. }
  76. function _toText(fname) {
  77. return fname.split("_")[1].split("$").join(" ");
  78. }
  79. /**
  80. * When you use view/content object that you manipulate,
  81. * not the print functions, call this to show it.
  82. */
  83. function testView(v) {
  84. if (!_testView) throw "prepareHarness not called.";
  85. _testView(v);
  86. }
  87. /**
  88. * Suppresses showing code of the scenario. Effectively turns
  89. * the UI harness to simple tabbed-page-view application.
  90. */
  91. function showNoCode() {
  92. if (!_showNoCode) throw "prepareHarness not called.";
  93. _showNoCode();
  94. }
  95. /**
  96. * This function creates the harness.
  97. * In non-fb case, it is called automatically.
  98. * In fb case, you should call it manually.
  99. @param uiPath "path" to ui scenario (/name-of-scenario).
  100. When using lib-dispatch-plus, it is request.pathTail.
  101. */
  102. function harness(uiPath) {
  103. page.setPresenter(function() {});
  104. var prefix = request.method.toLowerCase()+"ui_";
  105. var getuis = keys(appjet._internal.global).filter(function(x) {
  106. return x !== "getui_main" && x.substring(0, prefix.length) === prefix;
  107. });
  108. var step = Math.floor(getuis.length / 2);
  109. var oddNumber = 2*step+1;
  110. function nthColour(i) {
  111. function _rem(x) { return x - Math.floor(x); }
  112. var c = 1;
  113. _.hsv2rgb(_rem(i*step/oddNumber), 0.05, 0.95).
  114. forEach(function(x) { c = (c<<8)+x; });
  115. return "#" + c.toString(16).substring(1);
  116. }
  117. var tabsInfo = getuis.map(function(x, i) {
  118. var result = {
  119. link:_toLink(x),
  120. text: _toText(x),
  121. col: nthColour(i)
  122. };
  123. if ("/"+result.link === uiPath) {
  124. _.extend(page.viewModel, result);
  125. }
  126. return result;
  127. });
  128. _tabs.given(tabsInfo);
  129. request.fullPath = "ui" + uiPath;
  130. var prefix = _.dispatchPlus[request.method]+"ui_";
  131. var fname = prefix+uiPath.substring(1).replace(/-/g, '$$');
  132. _code.given((fname !== prefix && appjet._internal.global[fname]) || "");
  133. _.dispatchPlus();
  134. }
  135. /**@ignore*/
  136. function getui_() {
  137. print(
  138. H1("Scenario '", request.pathTail, "' not found!"),
  139. P("Select from scenarios in the tabs, or create some ",
  140. "by creating functions with names of the form ",
  141. "getui_name$of$scenario"
  142. )
  143. );
  144. }
  145. /**@ignore*/
  146. function postui_() {
  147. print(
  148. H1("Scenario '", request.pathTail, "' not found!"),
  149. P("Select from scenarios in the tabs, or create some ",
  150. "by creating functions with names of the form ",
  151. "postui_name$of$scenario"
  152. )
  153. );
  154. }
  155. /* appjet:server */
  156. import("lib-support/ui-harness");
  157. // ====== Simulated import(appjet.appName) ======
  158. import("lib-app/liveui");
  159. var _lorem = "Lorem ipsum dolor amet. ";
  160. /**@ignore*/
  161. function renderUsingPrint(text) {
  162. print(H1("Using print functions"));
  163. printp("<",text,">; entity: &amp;");
  164. }
  165. /**@ignore*/
  166. function getView() {
  167. return box(
  168. H1("Using testView function"),
  169. P("<",data(),">; entity: &amp;")
  170. );
  171. }
  172. // ====== End of simulated import(appjet.appName) ======
  173. /**@ignore*/
  174. function getui_main() {
  175. printp(
  176. "This is harness for UIs. It is useful in scenario, ",
  177. "where you put UI of your application in a separate library, ",
  178. "because it's right to separate the domain (data and computation) ",
  179. "from the presentation (UI)."
  180. );
  181. printp(
  182. "You use this harness by importing it in /*appjet:server*/ part ",
  183. "of the UI library, then importing the library itself. ",
  184. "Then, you create scenarios of using the parts of your UI, ",
  185. "each in a function of form getui_name$of$scenario. ",
  186. "The harness automatically finds them and presents tabs ",
  187. "for selecting them. This way, you can develop UI ",
  188. "without resorting to real application running ",
  189. "and see how UI use cases look immediately without clicking through the app itself."
  190. );
  191. printp(
  192. "Though primarily aimed at testing UIs, you can use it ",
  193. "as simple tabbing interface, if your app only shows pages. ",
  194. "In this case, call showNoCode() to suppress showing code."
  195. );
  196. printp(
  197. "Create getui_main function to show the initial screen ",
  198. "(like this one). It is not included in tabs, ",
  199. "it is only used for the main screen."
  200. );
  201. }
  202. /**@ignore*/
  203. function getui_using$print() {
  204. renderUsingPrint(_lorem);
  205. }
  206. /**@ignore*/
  207. function getui_using$print$longer() {
  208. renderUsingPrint(_lorem+_lorem);
  209. }
  210. /**@ignore*/
  211. function getui_setting$test$view() {
  212. var v = getView();
  213. testView(v);
  214. v.given(_lorem+" && "+_lorem);
  215. }
  216. /* appjet:disabled */
  217. // In case you want to use harness only in some cases,
  218. // inspire by this code:
  219. import("lib-dispatch-plus");
  220. // for path /no-harness
  221. /**@ignore*/
  222. function get_no$harness() {
  223. print(H1("This is normal output"));
  224. printp("No harness code is intervening.");
  225. }
  226. // for other paths, including /
  227. /**@ignore*/
  228. function get_() {
  229. prepareHarness();
  230. harness(request.pathTail);
  231. }
  232. dispatchPlus();
  233. /* appjet:server */
  234. import("lib-app/patches");
  235. dispatchShouldSearchHere();
  236. prepareHarness();
  237. // prepareHarness includes mvp with autodispatch and dispatchPlus
  238. // so only creating get_ function is enough.
  239. /**@ignore*/
  240. function get_() {
  241. harness(request.pathTail);
  242. }
  243. import({}, "lib-app/mvp").mvp();