/doc/development-notes.txt

https://code.google.com/p/zotonic/ · Plain Text · 528 lines · 355 code · 173 blank · 0 comment · 0 complexity · dd5685da28b58491486ec8efdc76e7e4 MD5 · raw file

  1. == Form handling / Validation ==
  2. Add validations and form handling.
  3. event({submit, ...}, Context).
  4. Result is javascript, might be redirect javascript.
  5. Validation:
  6. - attach validations to data of the to be posted element
  7. - sign with nonce and md5
  8. - name="bla" add to the post: "z_v=bla:email:nonce:aa1b8db21157fae46b72e8731f4023da"
  9. wf:wire(continueButton, nameTextBox, #validate { validators=[
  10. #is_required { text="Required." },
  11. #custom { text="Must start with 'Rusty'.", tag=some_tag, function=fun custom_validator/2 }
  12. ]}),
  13. becomes:
  14. <input type="text" name="email" id="bla" value="" />
  15. {% validate id="bla"
  16. type={presence}
  17. type={email}
  18. type={not_in_use delegate="xxx"}
  19. %}
  20. When you don't supply the delegate then either:
  21. - there is a validator module with the name validator_xxxx (where xxxx = validation type)
  22. - there is a validator(some_extra_validate, ..) in the resource
  23. You can access:
  24. z_context:q()
  25. z_context:q_validated()
  26. Javascript needed:
  27. add-some-validation(#bla),
  28. $(#bla).data("z_validation", "pickled-postback-data");
  29. TODO:
  30. - add live validation refs in javascript - done.
  31. - add onsubmit handler - done.
  32. - add onsubmit handler after possible form insertion - done.
  33. - add pickled info per field in the data - done.
  34. - collect all pickled data on submit - done.
  35. - collect form fields on submit - done.
  36. - send ajax with form to postback (either generic "form" or postback wired to form) - done.
  37. - in postback handler:
  38. - catch form submit - done.
  39. - check all pickled validation handlers -done.
  40. - postback the invalid fields + error messages - done.
  41. - call event(submit, ...) of resource (or delegate - as wired to the form) - done.
  42. - add javascript z_validation_error(Id, Error) - done.
  43. - add more erlang validations - done.
  44. - add redirect action - done.
  45. == Redo the context vars - should not be in context to prevent duplicating on message passing ==
  46. Possible solutions:
  47. - Let the template server return the template and evaluate the template in the request thread (do not pass context around) - done.
  48. Will need some context vars for lookup of @include variables (which is ok).
  49. == Inline Templates ==
  50. Make it possible to translate inline templates, i.e. not from a file.
  51. == Depcache ==
  52. Make a depcache server, now the scomp server uses its own simple cache.
  53. It is better to make one with dependencies, so that the scomps can give MaxAge _and_ dependencies
  54. TODO:
  55. - check if is better to store big data in dict (avoid copying)
  56. == User support ==
  57. Need support for users.
  58. One user can have:
  59. - multiple sessions, with each multiple pages
  60. - leaves an unique id on a user agent (different per ua - store in database)
  61. - has unprotected info (nickname, shopping cart)
  62. - has private info (real name, address, e-mail)
  63. - is_anonymous flag
  64. When an anonymous user logs on then the data of the anonymous user gets merged into the data of the user.
  65. For this we need to have a merge strategy per variable - maybe put it in the varname?
  66. Strategies per user variable:
  67. - merge_bag
  68. - merge_set
  69. - replace_userdata
  70. - keep_userdata (default)
  71. - transient (will not be stored in db)
  72. User:define_var(Name, Attrs)
  73. Let modules 'hook' into the user merging / startup routines and solve the variables problem in that way?
  74. User has three property tables (which are reflected in the user process state):
  75. 1. Public (just user cookie is enough)
  76. 2. Protected (must have an autologon, or authenticated)
  77. 3. Private (must have recently authenticated < 30 minutes)
  78. Timestamps on user/person:
  79. 1. Created
  80. 2. Last Visit
  81. 3. Last Authenticated (only in user process)
  82. States of user/person:
  83. 1. Anonymous (expiry date - when do we delete this person)
  84. 2. User (which does not assume verified - just that this is a known user)
  85. Cookie on user-agent:
  86. 1. zpuid - per user agent different
  87. 2. in database coupled
  88. - coupled with person info
  89. - last visit timestamp
  90. - expiry timestamp
  91. - autologon check, as a expiry timestamp
  92. Flow:
  93. - On first visit:
  94. 1. Create new user, flag as anonymous user
  95. 2. Add cookie to user, set cookie "zpuid" - valid for 10 years or so - done.
  96. 3. Name of user is empty, no details known except for last visit
  97. 4. Set autologon of cookie to false - an anonymous user can't logon
  98. - On next visit: - done.
  99. 1. Grab user from db, using zpuid cookie
  100. 2. If no such user -> handle as first visit - done.
  101. 3. Set 'last visit' of user to now()
  102. 4. If autologon status set, mark user in session as logged on (protected stuff is visible)
  103. - On user creation:
  104. 1. Create new user
  105. 2. Send user an e-mail with account details
  106. 3. Log on as the new user (see below)
  107. - On user logon:
  108. 1. Find user record with username/password (or openid)
  109. 2. Set autologon status of zpuid cookie to checkbox value
  110. 3. If current user is anonymous -> Copy/merge public information over to new user
  111. 4. Change zpsid and zpuid (safety measure)
  112. - On user logoff:
  113. 1. Set user process state to 'public' (locking protected and private properties)
  114. == Scomps - code change ==
  115. Catch code changes for the scomps so that they can be re-initialised.
  116. - Ask question about this on erlang-questions group
  117. == Templates - production switch ==
  118. Options to disable the modification checks during production.
  119. == Templates - custom_tags ==
  120. Test & check include paths for the custom_tags
  121. == Templates - dependencies + change 'depend' to 'vary' ==
  122. Add:
  123. {% vary variable %}
  124. This adds a
  125. -varies(['variable']).
  126. property to the translated template. This will be used by the include scomp.
  127. Change depend parameter of scomps to 'vary' - done.
  128. == Google chart ==
  129. Check code, adapt calling interface to something that will work with our template system - done.
  130. == LOG ==
  131. Add rotating logger to the webmachine logger
  132. == Webmachine / MochiWeb ==
  133. Integrate webmachine and mochiweb in our source tree, check makefiles.
  134. == Translations and filter applications ==
  135. A translation is of the form:
  136. {_ english text _}
  137. or
  138. {% _ "english" nl="nederlands" fr="fran??ais" %}
  139. and is translated by the grammar as:
  140. {trans, [{en,"english text"},{nl,"nederlands"},...]}
  141. which is translated as:
  142. % ...
  143. Language = erlydtl_runtime:fetch_value(language, Variables),
  144. % ...
  145. z_trans:trans({trans, [{en, "english text"}]}, Language)
  146. % ...
  147. We always need to apply an extra runtime filter on a value to make sure that the value is not a {trans} from a database field.
  148. Also add the prefix '_' operator to strings:
  149. _"hello world"|escape
  150. or:
  151. {{ title|default:_"untitled" }}
  152. TODO:
  153. - Add the z_i18n directory for i18n modules - done
  154. - Add the {{ _ en="english text" }} construct - done
  155. - Delay z_trans:trans application for parameters of filters - nice to have - not done yet
  156. - Modify erlydtl filters so that optional {trans} parameter (and argument) are translated - done
  157. - Move language to #context{} - done.
  158. - Add all gettext functionality (get .po files, use them, refetch them)
  159. - Add scanning of different strings in the templates and erlang code (to .pot file)
  160. == Add Context as parameter to the template routines ==
  161. Pass the context to the different lookup routines, separate the context from the variables.
  162. Especially the erlydtl_runtime:value_lookup() routine must have the context as an extra parameter.
  163. TODO:
  164. - modify erlydtl compile - done.
  165. - modify erlydtl runtime lookups - done.
  166. - modify z_scomp to correctly handle contexts - done.
  167. - modify scomp behaviour + implementations - done.
  168. - modify calls to z_template, add vars parameter - done.
  169. == Handling of resources in Erlang code and templates ==
  170. (All below is done, some small variations in the real implementation vs. the notes below.)
  171. We want to have something like the anyMeta $thing in our templates and Erlang code.
  172. Possible actions on this 'thing' (resource or m_rsc)
  173. - Get property
  174. - Get list of media attached
  175. - Get list of all objects (optionally filtered by predicate)
  176. - Get list of all subjects (optionally filtered by predicate)
  177. Updates of properties / object / subject are done with separate function calls to m_rsc.
  178. -module(m_rsc).
  179. -export([
  180. rsc/0,
  181. exists/1,
  182. is_readable/2, is_writeable/2, is_owner/2, is_ingroup/2, is_me/2
  183. p/3,
  184. op/2, o/2, o/3. o/4,
  185. sp/2, s/2, s/3. s/4,
  186. media/2, media/3
  187. ]).
  188. rsc() -> fun(Id, _Context) -> #rsc{id=Id} end.
  189. exists(Id) -> true | false.
  190. is_readable(Id, Context) -> true | false.
  191. is_writeable(Id, Context) -> true | false.
  192. is_owner(Id, Context) -> true | false.
  193. is_ingroup(Id, Context) -> true | false.
  194. is_me(Id, Context) -> true | false.
  195. %% Perform access control checks, return 'undefined' on an error
  196. %% Unknown properties will be checked against the predicates, returns o(Predicate).
  197. p(Id, Predicate, Context) ->
  198. undefined | Value
  199. %% Return a list of all edge predicates of this resource
  200. op(Id, Context) ->
  201. [].
  202. %% Used for dereferencing object edges inside template expressions
  203. o(Id, Context) ->
  204. fun(P) -> o(Id, P, Context) end.
  205. o(Id, Predicate, Context) ->
  206. {rsc_list, []}.
  207. o(Id, Predicate, Index, Context) ->
  208. #rsc{id=SomeId}.
  209. %% Return a list of all edge predicates to this resource
  210. sp(Id, Context) ->
  211. [].
  212. %% Used for dereferencing subject edges inside template expressions
  213. s(Id, Context) ->
  214. fun(S) -> s(Id, P, Context) end.
  215. s(Id, Predicate, Context) ->
  216. {rsc_list, []}.
  217. s(Id, Predicate, Index, Context) ->
  218. #rsc{id=SomeId}.
  219. media(Id, Context) ->
  220. [].
  221. media(Id, Index, Context) ->
  222. undefined | MediaPropList.
  223. During template evaluation we can cache rsc proplists in the process dictionary.
  224. It is also possible to pre-assign resources as a batch in the controller.
  225. In template:
  226. {{ rsc[Id].title }} (can return a trans #trans record {trans, [{en,"english text"},{nl,"..."}]})
  227. or just:
  228. {{ rsc[Id] }}
  229. which will give #rsc{id=Id}, which will be evaluated to its id by the renderer.
  230. The following expressions are the same:
  231. {{ rsc[Id].author.name }}
  232. {{ rsc[Id].o.author.name }}
  233. {{ rsc[Id].o.author[1].name }}
  234. rsc => fun(Id) -> #rsc{id=Id} end.
  235. Id => #rsc{}
  236. o => fun (P) -> {rsc_list, []} end.
  237. author => {rsc_list, [#rsc{}|..]}
  238. 1 => #rsc{}
  239. name => Value
  240. In erlang this will be:
  241. m_rsc:p(m_rsc:p(Id, author, Context), name, Context);
  242. The expression
  243. {{ rsc[Id].author[1] }}
  244. gives as result:
  245. #rsc{id=AuthorId}
  246. Where the expression:
  247. {{ rsc[Id].author }}
  248. gives as result:
  249. [ #rsc{id=AuthorId1}, #rsc{id=AuthorId2}, ... ]
  250. The m_rsc:p() function returns the predicate of the first #rsc{} in a #rsc list.
  251. p(#rsc{id=Id}, Predicate) ->
  252. do_something;
  253. p(Id, Predicate) when is_integer(Id) ->
  254. p(#rsc{id=Id}, Predicate);
  255. p([R|_], Predicate) ->
  256. p(R, Predicate);
  257. p([], _Predicate) -> undefined;
  258. p(undefined, _Predicate) -> undefined;
  259. == (Product) Properties ==
  260. triplet:
  261. (property-id, optional-property-value-id, optional-term)
  262. property ->
  263. (group-id, property-text, [(property-value-id, text)])
  264. group ->
  265. (text)
  266. For template ->
  267. [
  268. {
  269. group-text,
  270. [
  271. {property-text, property-value-text},
  272. ...
  273. ]
  274. },
  275. ...
  276. ]
  277. == Module/ how they extend the system ==
  278. Modules:
  279. module_indexer:
  280. - listen to {scomp/ template/ action/ validator/ model} notifications, maps them to the ones
  281. found in the module folders - done.
  282. - listen to {module_activate/ module_deactivate} notifications, rescan the list of scomps (etc)
  283. after receiving, also instructs the dispatcher to reload the dispatch rules - done.
  284. - listen to {module_rescan} message, as send from the dev module to force a rescan when in
  285. development mode - done.
  286. module_admin
  287. - for overview of modules, activate/deactivate modules in the admin - done.
  288. admin
  289. - basic admin controllers - done.
  290. shop
  291. - the webshop site - done.
  292. google_chart
  293. - The google chart scomp, and also chart_pie and chart_pie3d - now still in mod_base
  294. base
  295. - all normal buttons, drag/drop, validators etc. - done.
  296. dropbox
  297. - handle new images in the upload folder, move & publish them - still a gen_server in src/support
  298. pivot
  299. - handles extraction of texts for indexing rsc entries - still a gen_server in src/support
  300. When looking up an item:
  301. Send a notification {item-kind, item-name, Args, Context}.
  302. {scomp, button, [{text,"Click Me"}], Context}
  303. The module manager will receive this notification and check against the known list of items.
  304. Other modules can also hook into the notification manager to intercept the message and return
  305. an alternative. This can be on the basis of the arguments or the supplied context.
  306. - done.
  307. Get a list of all admin menu items:
  308. {% for title, url in m.notify.admin_menu %}
  309. This sends a notification {admin_menu} to all modules.
  310. The resulting list is filtered from empty results and returned.
  311. - not done yet - will be implemented when needed.
  312. Call a scomp in a module:
  313. {% scompname arg=value %}
  314. The module manager scans the scomp directory of every module, so it can do a mapping
  315. from the name of scomps found there to scomps requested.
  316. Scomp names should be: scomp_<modulename>_<scompname>.erl
  317. It uses the -module_priority() attribute of the module or the scomp to sort the scomps, enabling
  318. a scomp to be overruled by another module.
  319. - above is done.
  320. LATER:
  321. A fallback is to send a notification {scomp, scompname, [Arg]}, the first answer that
  322. is non-undefined will be returned.
  323. The answer can be:
  324. - an iolist() for direct result
  325. - an atom, comprising the name of the module that should handle the scomp
  326. - a context, which should be updated with the result of the scomp rendering
  327. - a tuple {iolist(), Context}, comprising the rendered result of the scomp
  328. Call all scomps by all modules:
  329. {% all scompname arg=value %}
  330. - done.
  331. Include a template from a module:
  332. {% include "templatename.tpl" %}
  333. The module manager scans the templates directory in all modules. It can map a requested name to
  334. a found template. <modulename>_<templatename>.tpl
  335. It uses the -module_priority() attribute to sort the templates, enabling a template to be overruled
  336. by another module.
  337. - done.
  338. Include all templates by all modules:
  339. {% all include "templatename.tpl" %}
  340. - done.
  341. Refer to an action in a module:
  342. The module manager scans the actions directory of the every module, so it can do a mapping
  343. from the name of the actions. action_<modulename>_<actionname>.erl
  344. It uses the -module_priority() attribute of the module or the action to sort the actions, enabling
  345. an action to be overruled by another module.
  346. - done.
  347. Refer to a validator in a module:
  348. The module manager scans the validators directory of the every module, so it can do a mapping
  349. from the name of the validators. validator_<modulename>_<validator>.erl
  350. - done.
  351. Refer to a model in a module:
  352. The module manager scans the model directory of every module. It maps the names found to the
  353. names requested. m_<modulename>_<modelname>.erl
  354. - Will not do this for now, you should specify unique names for all models and address them in that way.
  355. Reason: not needed at this moment and extra messaging overhead to find the correct model.