/modules/mod_menu/mod_menu.erl

https://code.google.com/p/zotonic/ · Erlang · 197 lines · 126 code · 33 blank · 38 comment · 0 complexity · 24f2afa40630466c29841f7205275694 MD5 · raw file

  1. %% @author Marc Worrell <marc@worrell.nl>
  2. %% @copyright 2009 Marc Worrell
  3. %% Date: 2009-07-12
  4. %% @doc Menu module. Supports menus in Zotonic. Adds admin interface to define the menu.
  5. %% Copyright 2009 Marc Worrell
  6. %%
  7. %% Licensed under the Apache License, Version 2.0 (the "License");
  8. %% you may not use this file except in compliance with the License.
  9. %% You may obtain a copy of the License at
  10. %%
  11. %% http://www.apache.org/licenses/LICENSE-2.0
  12. %%
  13. %% Unless required by applicable law or agreed to in writing, software
  14. %% distributed under the License is distributed on an "AS IS" BASIS,
  15. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. %% See the License for the specific language governing permissions and
  17. %% limitations under the License.
  18. -module(mod_menu).
  19. -author("Marc Worrell <marc@worrell.nl>").
  20. -mod_title("Menus").
  21. -mod_description("Menus in Zotonic, adds amdin interface to define the menu.").
  22. -include("zotonic.hrl").
  23. %% interface functions
  24. -export([
  25. init/1,
  26. datamodel/1,
  27. get_menu/1,
  28. get_menu/2,
  29. set_menu/3,
  30. observe_menu_get_rsc_ids/2,
  31. observe_menu_save/2,
  32. test/0,
  33. menu_flat/2
  34. ]).
  35. %% @doc The datamodel for the menu routines.
  36. datamodel(Context) ->
  37. [
  38. {categories,
  39. [
  40. {menu, categorization,
  41. [{title, <<"Page menu">>}]
  42. }
  43. ]
  44. },
  45. {resources,
  46. case z_install_defaultdata:default_menu(m_site:get(skeleton, Context)) of
  47. undefined ->
  48. [];
  49. Menu ->
  50. [
  51. {main_menu,
  52. menu,
  53. [{title, <<"Main menu">>},
  54. {menu, Menu}
  55. ]
  56. }
  57. ]
  58. end
  59. }
  60. ].
  61. %% @doc Initializes the module (after the datamodel is installed).
  62. init(Context) ->
  63. case m_config:get(menu, menu_default, Context) of
  64. undefined -> ok;
  65. Props ->
  66. %% upgrade from previous menu
  67. OldMenu = proplists:get_value(menu, Props, []),
  68. ?zInfo("Upgrading old menu structure", Context),
  69. set_menu(OldMenu, Context),
  70. m_config:delete(menu, menu_default, Context)
  71. end.
  72. %% @doc Notifier handler to get all menu ids for the default menu.
  73. observe_menu_get_rsc_ids(menu_get_rsc_ids, Context) ->
  74. Menu = get_menu(Context),
  75. {ok, menu_ids(Menu, [])}.
  76. menu_ids([], Acc) ->
  77. Acc;
  78. menu_ids([{Id,SubMenu}|T], Acc) ->
  79. Acc1 = menu_ids(SubMenu, [Id|Acc]),
  80. menu_ids(T, Acc1);
  81. menu_ids([H|T], Acc) ->
  82. menu_ids(T, [H|Acc]).
  83. %% @doc Observer the 'menu_save' notification
  84. observe_menu_save({menu_save, MenuId, Menu}, Context) ->
  85. set_menu(MenuId, Menu, Context).
  86. %% @doc Fetch the default menu. Performs validation/visibility checking on the menu items.
  87. %% @spec get_menu(Context) -> list()
  88. get_menu(Context) ->
  89. get_menu(m_rsc:rid(main_menu, Context), Context).
  90. %% @doc Fetch a menu structure from a rsc. Performs validation/visibility checking on the menu items.
  91. %% @spec get_menu(Id, Context) -> list()
  92. get_menu(Id, Context) ->
  93. case m_rsc:p(Id, menu, Context) of
  94. undefined -> [];
  95. <<>> -> [];
  96. Menu -> remove_invisible(validate(Menu, []), [], Context)
  97. end.
  98. validate([], Acc) ->
  99. lists:reverse(Acc);
  100. validate([{_M,_L} = M|Ms], Acc) ->
  101. validate(Ms, [M|Acc]);
  102. validate([M|Ms], Acc) ->
  103. validate(Ms, [{M,[]}|Acc]).
  104. %% Remove invisible menu items
  105. remove_invisible([], Acc, _Context) ->
  106. lists:reverse(Acc);
  107. remove_invisible([{Id,Sub}|Rest], Acc, Context) ->
  108. case m_rsc:is_visible(Id, Context) of
  109. true -> remove_invisible(Rest, [{Id,remove_invisible(Sub, [], Context)} | Acc], Context);
  110. false -> remove_invisible(Rest, Acc, Context)
  111. end;
  112. remove_invisible([Id|Rest], Acc, Context) ->
  113. case m_rsc:is_visible(Id, Context) of
  114. true -> remove_invisible(Rest, [Id | Acc], Context);
  115. false -> remove_invisible(Rest, Acc, Context)
  116. end.
  117. %% @doc Save the default menu.
  118. set_menu(Menu, Context) ->
  119. Id = m_rsc:rid(main_menu, Context),
  120. set_menu(Id, Menu, Context).
  121. %% @doc Save the current menu.
  122. set_menu(Id, Menu, Context) ->
  123. m_rsc:update(Id, [{menu, Menu}], Context).
  124. menu_flat(undefined, _Context) ->
  125. [];
  126. menu_flat(<<>>, _Context) ->
  127. [];
  128. menu_flat(X, Context) ->
  129. menu_flat(X, [1], [], Context).
  130. menu_flat([], _Path, Acc, _Context) ->
  131. lists:reverse(Acc);
  132. menu_flat([ {MenuId, []} | Rest], [Idx|PR], Acc, Context ) ->
  133. [ {m_rsc:rid(MenuId, Context), [Idx|PR], undefined} ]
  134. ++ menu_flat(Rest, [Idx+1|PR], [], Context)
  135. ++ Acc;
  136. menu_flat([ {MenuId, Children} | Rest], [Idx|PR], Acc, Context ) ->
  137. [ {m_rsc:rid(MenuId, Context), [Idx|PR], down} ]
  138. ++ menu_flat(Children, [1,Idx|PR], [], Context)
  139. ++ [{undefined, undefined, up}]
  140. ++ menu_flat(Rest, [Idx+1|PR], [], Context)
  141. ++ Acc;
  142. menu_flat([ MenuId | Rest ], P, A, C) when is_integer(MenuId) ->
  143. %% oldschool notation fallback
  144. menu_flat([{MenuId, []} | Rest], P, A, C).
  145. %% @doc test function
  146. %% 111 [1]
  147. %% - 44 [1,1]
  148. %% - - 555 [1,1,1]
  149. %% - - 666 [1,1,2]
  150. %% 222 [2]
  151. %% - 333 [2,1]
  152. test() ->
  153. [
  154. {111, [1], down },
  155. {444, [1,1], down},
  156. {555, [1,1,1], undefined},
  157. {666, [2,1,1], undefined},
  158. {undefined, undefined, up},
  159. {undefined, undefined, up},
  160. {222, [2], down},
  161. {333, [1,2], undefined},
  162. {undefined, undefined, up}
  163. ]
  164. = menu_flat([{111, [{444, [{555, []}, {666, []} ]}]}, {222, [{333, []}]}], x).