Browse Source

allow to activate/desactive content view in left side treeview

Damien ACCORSI 10 years ago
parent
commit
aa75de6267

+ 2 - 0
tracim/development.ini.base View File

@@ -122,6 +122,8 @@ website.title.color = #555
122 122
 website.home.subtitle = Default login: admin@admin.admin (password: admin@admin.admin)
123 123
 website.home.tag_line = <div class="text-center" style="font-weight: bold;">Collaboration, versionning and traceability</div>
124 124
 website.home.below_login_form = in case of problem, please contact the administrator.
125
+# Values may be 'all' or 'folders'
126
+website.treeview.content = all
125 127
 
126 128
 # The following base_url is used for links and icons
127 129
 # integrated in the email notifcations

+ 12 - 1
tracim/tracim/config/app_cfg.py View File

@@ -79,10 +79,16 @@ from tg.configuration.auth import TGAuthMetadata
79 79
 from sqlalchemy import and_
80 80
 #This tells to TurboGears how to retrieve the data for your user
81 81
 class ApplicationAuthMetadata(TGAuthMetadata):
82
+
82 83
     def __init__(self, sa_auth):
83 84
         self.sa_auth = sa_auth
85
+
84 86
     def authenticate(self, environ, identity):
85
-        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(self.sa_auth.user_class.is_active==True, self.sa_auth.user_class.email==identity['login'])).first()
87
+        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(
88
+            self.sa_auth.user_class.is_active==True,
89
+            self.sa_auth.user_class.email==identity['login']
90
+        )).first()
91
+
86 92
         if user and user.validate_password(identity['password']):
87 93
             return identity['login']
88 94
     def get_user(self, identity, userid):
@@ -222,6 +228,8 @@ class CFG(object):
222 228
         self.TRACKER_JS_PATH = tg.config.get('js_tracker_path')
223 229
         self.TRACKER_JS_CONTENT = self.get_tracker_js_content(self.TRACKER_JS_PATH)
224 230
 
231
+        self.WEBSITE_TREEVIEW_CONTENT = tg.config.get('website.treeview.content')
232
+
225 233
 
226 234
     def get_tracker_js_content(self, js_tracker_file_path = None):
227 235
         js_tracker_file_path = tg.config.get('js_tracker_path', None)
@@ -239,6 +247,9 @@ class CFG(object):
239 247
         ASYNC = 'ASYNC'
240 248
         SYNC = 'SYNC'
241 249
 
250
+        TREEVIEW_FOLDERS = 'folders'
251
+        TREEVIEW_ALL = 'all'
252
+
242 253
 #######
243 254
 #
244 255
 # INFO - D.A. - 2014-11-05

+ 32 - 7
tracim/tracim/controllers/workspace.py View File

@@ -5,6 +5,8 @@ from tg import tmpl_context
5 5
 from tg.i18n import ugettext as _
6 6
 from tg.predicates import not_anonymous
7 7
 
8
+from tracim.config.app_cfg import CFG
9
+
8 10
 from tracim.controllers import TIMRestController
9 11
 from tracim.controllers.content import UserWorkspaceFolderRestController
10 12
 
@@ -20,8 +22,6 @@ from tracim.model.data import Workspace
20 22
 from tracim.model.serializers import Context, CTX, DictLikeClass
21 23
 
22 24
 
23
-
24
-
25 25
 class UserWorkspaceRestController(TIMRestController):
26 26
 
27 27
     allow_only = not_anonymous()
@@ -88,6 +88,10 @@ class UserWorkspaceRestController(TIMRestController):
88 88
         # including the selected node, all parents (and their siblings)
89 89
         workspace, content = convert_id_into_instances(current_id)
90 90
 
91
+        # The following step allow to select the parent folder when content itself is not visible in the treeview
92
+        if content.type!=ContentType.Folder and CFG.CST.TREEVIEW_ALL!=CFG.get_instance().WEBSITE_TREEVIEW_CONTENT:
93
+            content = content.parent if content.parent else None
94
+
91 95
         # This is the init of the recursive-like build of the tree
92 96
         content_parent = content
93 97
         tree_items = []
@@ -114,7 +118,6 @@ class UserWorkspaceRestController(TIMRestController):
114 118
 
115 119
         return Context(CTX.MENU_API_BUILD_FROM_TREE_ITEM).toDict(full_tree, 'd')
116 120
 
117
-
118 121
     def _build_sibling_list_of_workspaces(self, workspace: Workspace, child_contents: [NodeTreeItem], select_active_workspace = False, all_workspaces = True) -> [NodeTreeItem]:
119 122
 
120 123
         root_items = []
@@ -141,6 +144,7 @@ class UserWorkspaceRestController(TIMRestController):
141 144
 
142 145
         return root_items
143 146
 
147
+
144 148
     def _build_sibling_list_of_tree_items(self,
145 149
                                           workspace: Workspace,
146 150
                                           content: Content,
@@ -152,12 +156,25 @@ class UserWorkspaceRestController(TIMRestController):
152 156
         tree_items = []
153 157
 
154 158
         parent = content.parent if content else None
155
-        for child in api.get_child_folders(parent, workspace, allowed_content_types, ignored_item_ids):
159
+
160
+        viewable_content_types = self._get_treeviewable_content_types_or_none()
161
+        child_contents = api.get_child_folders(parent, workspace, allowed_content_types, ignored_item_ids, viewable_content_types)
162
+        for child in child_contents:
156 163
             children_to_add = children if child==content else []
157
-            is_selected = True if select_active_node and child==content else False
164
+            if child==content and select_active_node:
165
+                # The item is the requested node, so we select it
166
+                is_selected = True
167
+            elif content not in child_contents and select_active_node and child==content:
168
+                # The item is not present in the list, so we select its parent node
169
+                is_selected = True
170
+            else:
171
+                is_selected = False
172
+
158 173
             new_item = NodeTreeItem(child, children_to_add, is_selected)
159 174
             tree_items.append(new_item)
160 175
 
176
+        # This allow to show contents and folders group by type
177
+        tree_items = ContentApi.sort_tree_items(tree_items)
161 178
 
162 179
         return parent, tree_items
163 180
 
@@ -172,8 +189,16 @@ class UserWorkspaceRestController(TIMRestController):
172 189
 
173 190
         ignore_item_ids = [int(ignore_id)] if ignore_id else []
174 191
         workspace, content = convert_id_into_instances(id)
175
-        contents = ContentApi(tmpl_context.current_user).get_child_folders(content, workspace, [], ignore_item_ids)
176 192
 
177
-        dictified_contents = Context(CTX.MENU_API).toDict(contents, 'd')
193
+        viewable_content_types = self._get_treeviewable_content_types_or_none()
194
+        contents = ContentApi(tmpl_context.current_user).get_child_folders(content, workspace, [], ignore_item_ids, viewable_content_types)
195
+        # This allow to show contents and folders group by type
196
+        sorted_contents = ContentApi.sort_content(contents)
197
+
198
+        dictified_contents = Context(CTX.MENU_API).toDict(sorted_contents, 'd')
178 199
         return dictified_contents
179 200
 
201
+    def _get_treeviewable_content_types_or_none(self):
202
+        if CFG.get_instance().WEBSITE_TREEVIEW_CONTENT==CFG.CST.TREEVIEW_ALL:
203
+            return ContentType.Any
204
+        return None # None means "workspaces and folders"

+ 23 - 0
tracim/tracim/lib/__init__.py View File

@@ -38,3 +38,26 @@ class CST(object):
38 38
 # l_('November')
39 39
 # l_('December')
40 40
 
41
+
42
+def cmp_to_key(mycmp):
43
+    """
44
+    List sort related function
45
+
46
+    Convert a cmp= function into a key= function
47
+    """
48
+    class K(object):
49
+        def __init__(self, obj, *args):
50
+            self.obj = obj
51
+        def __lt__(self, other):
52
+            return mycmp(self.obj, other.obj) < 0
53
+        def __gt__(self, other):
54
+            return mycmp(self.obj, other.obj) > 0
55
+        def __eq__(self, other):
56
+            return mycmp(self.obj, other.obj) == 0
57
+        def __le__(self, other):
58
+            return mycmp(self.obj, other.obj) <= 0
59
+        def __ge__(self, other):
60
+            return mycmp(self.obj, other.obj) >= 0
61
+        def __ne__(self, other):
62
+            return mycmp(self.obj, other.obj) != 0
63
+    return K

+ 69 - 4
tracim/tracim/lib/content.py View File

@@ -5,20 +5,71 @@ __author__ = 'damien'
5 5
 import tg
6 6
 
7 7
 from sqlalchemy.orm.attributes import get_history
8
+from tracim.lib import cmp_to_key
8 9
 from tracim.lib.notifications import Notifier
9 10
 from tracim.model import DBSession
10 11
 from tracim.model.auth import User
11 12
 from tracim.model.data import ContentStatus, ContentRevisionRO, ActionDescription
12 13
 from tracim.model.data import Content
13 14
 from tracim.model.data import ContentType
15
+from tracim.model.data import NodeTreeItem
14 16
 from tracim.model.data import Workspace
15 17
 
18
+def compare_content_for_sorting_by_type_and_name(content1: Content, content2: Content):
19
+    """
20
+    :param content1:
21
+    :param content2:
22
+    :return:    1 if content1 > content2
23
+                -1 if content1 < content2
24
+                0 if content1 = content2
25
+    """
26
+
27
+    if content1.type==content2.type:
28
+        if content1.get_label().lower()>content2.get_label().lower():
29
+            return 1
30
+        elif content1.get_label().lower()<content2.get_label().lower():
31
+            return -1
32
+        return 0
33
+    else:
34
+        # TODO - D.A. - 2014-12-02 - Manage Content Types Dynamically
35
+        content_type_order = [ContentType.Folder, ContentType.Page, ContentType.Thread, ContentType.File]
36
+        result = content_type_order.index(content1.type)-content_type_order.index(content2.type)
37
+        if result<0:
38
+            return -1
39
+        elif result>0:
40
+            return 1
41
+        else:
42
+            return 0
43
+
44
+def compare_tree_items_for_sorting_by_type_and_name(item1: NodeTreeItem, item2: NodeTreeItem):
45
+    return compare_content_for_sorting_by_type_and_name(item1.node, item2.node)
46
+
47
+
16 48
 class ContentApi(object):
17 49
 
18
-    def __init__(self, current_user: User, show_archived=False, show_deleted=False):
50
+    def __init__(self, current_user: User, show_archived=False, show_deleted=False, all_content_in_treeview=True):
19 51
         self._user = current_user
20 52
         self._show_archived = show_archived
21 53
         self._show_deleted = show_deleted
54
+        self._show_all_type_of_contents_in_treeview = all_content_in_treeview
55
+
56
+
57
+    @classmethod
58
+    def sort_tree_items(cls, content_list: [NodeTreeItem])-> [Content]:
59
+        news = []
60
+        for item in content_list:
61
+            news.append(item)
62
+
63
+        content_list.sort(key=cmp_to_key(compare_tree_items_for_sorting_by_type_and_name))
64
+
65
+        return content_list
66
+
67
+
68
+    @classmethod
69
+    def sort_content(cls, content_list: [Content])-> [Content]:
70
+        content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
71
+        return content_list
72
+
22 73
 
23 74
     def _base_query(self, workspace: Workspace=None):
24 75
         result = DBSession.query(Content)
@@ -33,12 +84,26 @@ class ContentApi(object):
33 84
 
34 85
         return result
35 86
 
36
-    def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[]) -> [Content]:
87
+    def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> [Content]:
88
+        """
89
+        This method returns child items (folders or items) for left bar treeview.
90
+
91
+        :param parent:
92
+        :param workspace:
93
+        :param filter_by_allowed_content_types:
94
+        :param removed_item_ids:
95
+        :param allowed_node_types:
96
+        :return:
97
+        """
98
+        if not allowed_node_types:
99
+            allowed_node_types = [ContentType.Folder]
100
+        elif allowed_node_types==ContentType.Any:
101
+            allowed_node_types = ContentType.all()
37 102
 
38 103
         parent_id = parent.content_id if parent else None
39 104
         folders = self._base_query(workspace).\
40 105
             filter(Content.parent_id==parent_id).\
41
-            filter(Content.type==ContentType.Folder).\
106
+            filter(Content.type.in_(allowed_node_types)).\
42 107
             filter(Content.content_id.notin_(removed_item_ids)).\
43 108
             all()
44 109
 
@@ -49,7 +114,7 @@ class ContentApi(object):
49 114
         result = []
50 115
         for folder in folders:
51 116
             for allowed_content_type in filter_by_allowed_content_types:
52
-                if folder.properties['allowed_content'][allowed_content_type]==True:
117
+                if folder.type==ContentType.Folder and folder.properties['allowed_content'][allowed_content_type]==True:
53 118
                     result.append(folder)
54 119
 
55 120
         return result

+ 1 - 1
tracim/tracim/lib/helpers.py View File

@@ -131,7 +131,7 @@ def convert_id_into_instances(id: str) -> (Workspace, Content):
131 131
     try:
132 132
         content_data = content_str.split(CST.TREEVIEW_MENU.ID_SEPARATOR)
133 133
         content_id = int(content_data[1])
134
-        content = ContentApi(tg.tmpl_context.current_user).get_one(content_id, ContentType.Folder)
134
+        content = ContentApi(tg.tmpl_context.current_user).get_one(content_id, ContentType.Any)
135 135
     except (IndexError, ValueError) as e:
136 136
         content = None
137 137
 

+ 0 - 1
tracim/tracim/lib/workspace.py View File

@@ -86,7 +86,6 @@ class WorkspaceApi(object):
86 86
     def get_notifiable_roles(self, workspace: Workspace) -> [UserRoleInWorkspace]:
87 87
         roles = []
88 88
         for role in workspace.roles:
89
-            print(role.user.email)
90 89
             if role.do_notify==True and role.user!=self._user:
91 90
                 roles.append(role)
92 91
         return roles

+ 39 - 1
tracim/tracim/model/data.py View File

@@ -56,7 +56,9 @@ class Workspace(DeclarativeBase):
56 56
                 return role.role
57 57
         return UserRoleInWorkspace.NOT_APPLICABLE
58 58
 
59
-
59
+    def get_label(self):
60
+        """ this method is for interoperability with Content class"""
61
+        return self.label
60 62
 
61 63
 class UserRoleInWorkspace(DeclarativeBase):
62 64
 
@@ -276,12 +278,24 @@ class ContentType(object):
276 278
         'comment': 'apps/internet-group-chat',
277 279
     }
278 280
 
281
+    _ORDER_WEIGHT = {
282
+        'folder': 0,
283
+        'page': 1,
284
+        'thread': 2,
285
+        'file': 3,
286
+        'comment': 4,
287
+    }
288
+
279 289
     @classmethod
280 290
     def icon(cls, type: str):
281 291
         assert(type in ContentType._ICONS) # DYN_REMOVE
282 292
         return ContentType._ICONS[type]
283 293
 
284 294
     @classmethod
295
+    def all(cls):
296
+        return cls.allowed_types()
297
+
298
+    @classmethod
285 299
     def allowed_types(cls):
286 300
         return [cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page]
287 301
 
@@ -294,6 +308,27 @@ class ContentType(object):
294 308
                 allowed_types.append(item)
295 309
         return allowed_types
296 310
 
311
+    @classmethod
312
+    def fill_url(cls, content: 'Content'):
313
+        # TODO - DYNDATATYPE - D.A. - 2014-12-02
314
+        # Make this code dynamic loading data types
315
+
316
+        if content.type==ContentType.Folder:
317
+            return '/workspaces/{}/folders/{}'.format(content.workspace_id, content.content_id)
318
+        elif content.type==ContentType.File:
319
+            return '/workspaces/{}/folders/{}/files/{}'.format(content.workspace_id, content.parent_id, content.content_id)
320
+        elif content.type==ContentType.Thread:
321
+            return '/workspaces/{}/folders/{}/threads/{}'.format(content.workspace_id, content.parent_id, content.content_id)
322
+        elif content.type==ContentType.Page:
323
+            return '/workspaces/{}/folders/{}/pages/{}'.format(content.workspace_id, content.parent_id, content.content_id)
324
+
325
+    @classmethod
326
+    def fill_url_for_workspace(cls, workspace: Workspace):
327
+        # TODO - DYNDATATYPE - D.A. - 2014-12-02
328
+        # Make this code dynamic loading data types
329
+        return '/workspaces/{}'.format(workspace.workspace_id)
330
+
331
+
297 332
 class Content(DeclarativeBase):
298 333
 
299 334
     __tablename__ = 'contents'
@@ -379,6 +414,9 @@ class Content(DeclarativeBase):
379 414
                     child_nb = child_nb+1
380 415
         return child_nb
381 416
 
417
+    def get_label(self):
418
+        return self.label if self.label else self.file_name if self.file_name else ''
419
+
382 420
     def get_status(self) -> ContentStatus:
383 421
         return ContentStatus(self.status, self.type.__str__())
384 422
 

+ 13 - 11
tracim/tracim/model/serializers.py View File

@@ -278,9 +278,9 @@ def serialize_content_for_menu_api(content: Content, context: Context):
278 278
     result = DictLikeClass(
279 279
         id = CST.TREEVIEW_MENU.ID_TEMPLATE__FULL.format(workspace_id, content_id),
280 280
         children = True, # TODO: make this dynamic
281
-        text = content.label,
282
-        a_attr = { 'href' : context.url('/workspaces/{}/folders/{}'.format(workspace_id, content_id)) },
283
-        li_attr = { 'title': content.label, 'class': 'tracim-tree-item-is-a-folder' },
281
+        text = content.get_label(),
282
+        a_attr = { 'href' : context.url(ContentType.fill_url(content)) },
283
+        li_attr = { 'title': content.get_label(), 'class': 'tracim-tree-item-is-a-folder' },
284 284
         type = content.type,
285 285
         state = { 'opened': False, 'selected': False }
286 286
     )
@@ -770,22 +770,24 @@ def serialize_workspace_for_menu_api(workspace: Workspace, context: Context):
770 770
 @pod_serializer(NodeTreeItem, CTX.MENU_API_BUILD_FROM_TREE_ITEM)
771 771
 def serialize_node_tree_item_for_menu_api_tree(item: NodeTreeItem, context: Context):
772 772
     if isinstance(item.node, Content):
773
+        ContentType.fill_url(item.node)
773 774
         return DictLikeClass(
774 775
             id=CST.TREEVIEW_MENU.ID_TEMPLATE__FULL.format(item.node.workspace_id, item.node.content_id),
775
-            children=True if len(item.children)<=0 else context.toDict(item.children),
776
-            text=item.node.label,
777
-            a_attr={'href': context.url('/workspaces/{}/folders/{}'.format(item.node.workspace_id, item.node.content_id)) },
778
-            li_attr={'title': item.node.label, 'class': 'tracim-tree-item-is-a-folder'},
779
-            type='folder',
780
-            state={'opened': True if len(item.children)>0 else False, 'selected': item.is_selected}
776
+            children=True if ContentType.Folder==item.node.type and len(item.children)<=0 else context.toDict(item.children),
777
+            text=item.node.get_label(),
778
+            a_attr={'href': context.url(ContentType.fill_url(item.node)) },
779
+            li_attr={'title': item.node.get_label()},
780
+            # type='folder',
781
+            type=item.node.type,
782
+            state={'opened': True if ContentType.Folder==item.node.type and len(item.children)>0 else False, 'selected': item.is_selected}
781 783
         )
782 784
     elif isinstance(item.node, Workspace):
783 785
         return DictLikeClass(
784 786
             id=CST.TREEVIEW_MENU.ID_TEMPLATE__WORKSPACE_ONLY.format(item.node.workspace_id),
785 787
             children=True if len(item.children)<=0 else context.toDict(item.children),
786 788
             text=item.node.label,
787
-            a_attr={'href': context.url('/workspaces/{}'.format(item.node.workspace_id))},
788
-            li_attr={'title': item.node.label, 'class': 'tracim-tree-item-is-a-workspace'},
789
+            a_attr={'href': context.url(ContentType.fill_url_for_workspace(item.node))},
790
+            li_attr={'title': item.node.get_label()},
789 791
             type='workspace',
790 792
             state={'opened': True if len(item.children)>0 else False, 'selected': item.is_selected}
791 793
         )

+ 1 - 1
tracim/tracim/templates/user_workspace_folder_file_get_one.mak View File

@@ -8,7 +8,7 @@
8 8
 
9 9
 <%def name="SIDEBAR_LEFT_CONTENT()">
10 10
     <h4>${_('Workspaces')}</h4>
11
-    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__folder_{}'.format(result.file.workspace.id, result.file.parent.id))}
11
+    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__item_{}'.format(result.file.workspace.id, result.file.id))}
12 12
     <hr/>
13 13
 </%def>
14 14
 

+ 1 - 1
tracim/tracim/templates/user_workspace_folder_get_one.mak View File

@@ -9,7 +9,7 @@
9 9
 
10 10
 <%def name="SIDEBAR_LEFT_CONTENT()">
11 11
     <h4>${_('Workspaces')}</h4>
12
-    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__folder_{}'.format(result.folder.workspace.id, result.folder.id))}
12
+    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__item_{}'.format(result.folder.workspace.id, result.folder.id))}
13 13
     <hr/>
14 14
 </%def>
15 15
 

+ 1 - 1
tracim/tracim/templates/user_workspace_folder_page_get_one.mak View File

@@ -9,7 +9,7 @@
9 9
 
10 10
 <%def name="SIDEBAR_LEFT_CONTENT()">
11 11
     <h4>${_('Workspaces')}</h4>
12
-    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__folder_{}'.format(result.page.workspace.id, result.page.parent.id))}
12
+    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__item_{}'.format(result.page.workspace.id, result.page.id))}
13 13
     <hr/>
14 14
 </%def>
15 15
 

+ 2 - 1
tracim/tracim/templates/user_workspace_folder_thread_get_one.mak View File

@@ -9,7 +9,7 @@
9 9
 
10 10
 <%def name="SIDEBAR_LEFT_CONTENT()">
11 11
     <h4>${_('Workspaces')}</h4>
12
-    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__folder_{}'.format(result.thread.workspace.id, result.thread.parent.id))}
12
+    ${WIDGETS.TREEVIEW('sidebar-left-menu', 'workspace_{}__item_{}'.format(result.thread.workspace.id, result.thread.id))}
13 13
     <hr/>
14 14
 </%def>
15 15
 
@@ -19,6 +19,7 @@
19 19
 
20 20
 <%def name="REQUIRED_DIALOGS()">
21 21
     ${TIM.MODAL_DIALOG('thread-edit-modal-dialog', 'modal-lg')}
22
+    ${TIM.MODAL_DIALOG('thread-move-modal-dialog')}
22 23
 </%def>
23 24
 
24 25
 ############################################################################

+ 10 - 1
tracim/tracim/templates/user_workspace_widgets.mak View File

@@ -142,6 +142,15 @@
142 142
                         "default" : {
143 143
                             "icon" : "${TIM.ICO_URL(16, 'places/jstree-folder')}"
144 144
                         },
145
+                        "page" : {
146
+                            "icon" : "${TIM.ICO_URL(16, 'mimetypes/text-html')}"
147
+                        },
148
+                        "file" : {
149
+                            "icon" : "${TIM.ICO_URL(16, 'status/mail-attachment')}"
150
+                        },
151
+                        "thread" : {
152
+                            "icon" : "${TIM.ICO_URL(16, 'apps/internet-group-chat')}"
153
+                        },
145 154
                         "workspace" : {
146 155
                             "icon" : "${TIM.ICO_URL(16, 'places/folder-remote')}"
147 156
                         },
@@ -167,7 +176,7 @@
167 176
                                 };
168 177
                             },
169 178
                             'success': function (new_data) {
170
-                                console.log('loaded new menu data' + new_data)
179
+                                console.log('loaded new menu data' + JSON.stringify(new_data))
171 180
                                 return new_data;
172 181
                             },
173 182
                         },