Browse Source

implement jstree menu with ajax dynamic loading

Damien Accorsi 10 years ago
parent
commit
f5981aadff

+ 8 - 48
pboard/pboard/controllers/api.py View File

@@ -24,59 +24,23 @@ from pboard.lib.base import BaseController
24 24
 from pboard.lib   import dbapi as pld
25 25
 from pboard.model import data as pmd
26 26
 from pboard.model import auth as pma
27
+from pboard.model import serializers as pms
27 28
 from pboard import model as pm
28 29
 from pboard.lib.auth import can_read, can_write
29 30
 
30
-__all__ = ['PODPublicApiController', 'PODApiController']
31
+from pboard.controllers import apimenu as pcam
31 32
 
32
-FIXME_ERROR_CODE=-1
33
-
34
-class PODPublicApiController(BaseController):
35
-
36
-    @expose()
37
-    def create_account(self, email='', password='', retyped_password='', **kw):
38
-      if email=='' or password=='' or retyped_password=='':
39
-        flash(_('Account creation error: please fill all the fields'), 'error')
40
-        redirect(lurl('/'))
41
-      elif password!=retyped_password:
42
-        flash(_('Account creation error: passwords do not match'), 'error')
43
-        redirect(lurl('/'))
44
-      else:
45
-        loExistingUser = pld.PODStaticController.getUserByEmailAddress(email)
46
-        if loExistingUser!=None:
47
-          flash(_('Account creation error: account already exist: %s') % (email), 'error')
48
-          redirect(lurl('/'))
49
-        
50
-        loNewAccount = pld.PODStaticController.createUser()
51
-        loNewAccount.email_address = email
52
-        loNewAccount.display_name  = email
53
-        loNewAccount.password      = password
54
-
55
-        loUserGroup = pld.PODStaticController.getGroup('user')
56
-        loUserGroup.users.append(loNewAccount)
57
-
58
-        pm.DBSession.add(loNewAccount)
59
-        pm.DBSession.flush()
60
-        pm.DBSession.refresh(loNewAccount)
61
-
62
-        loUserSpecificGroup = pld.PODStaticController.createGroup()
63 33
 
64
-        loUserSpecificGroup.group_id = 0-loNewAccount.user_id # group id of a given user is the opposite of the user id
65
-        loUserSpecificGroup.group_name = 'user_%d' % loNewAccount.user_id
66
-        loUserSpecificGroup.personnal_group = True
67
-        loUserSpecificGroup.users.append(loNewAccount)
68
-
69
-        pm.DBSession.flush()
70
-
71
-        flash(_('Account successfully created: %s') % (email), 'info')
72
-        redirect(lurl('/'))
34
+FIXME_ERROR_CODE=-1
73 35
 
74 36
 
75 37
 class PODApiController(BaseController):
76 38
     """Sample controller-wide authorization"""
77 39
     
78 40
     allow_only = tgp.in_group('user', msg=l_('You need to login in order to access this ressource'))
79
-    
41
+
42
+    menu = pcam.PODApiMenuController()
43
+
80 44
     @expose()
81 45
     def create_event(self, parent_id=None, data_label='', data_datetime=None, data_content='', data_reminder_datetime=None, add_reminder=False, **kw):
82 46
       loCurrentUser   = pld.PODStaticController.getCurrentUser()
@@ -170,7 +134,7 @@ class PODApiController(BaseController):
170 134
         loImage     = pil.open(loJpegBytes)
171 135
         loImage.thumbnail([140,140], pil.ANTIALIAS)
172 136
         
173
-        loResultBuffer = StringIO()
137
+        loResultBuffer = csio.StringIO()
174 138
         loImage.save(loResultBuffer,"JPEG")
175 139
         tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
176 140
         return loResultBuffer.getvalue()
@@ -356,10 +320,7 @@ class PODApiController(BaseController):
356 320
       loNode = loApiController.getNode(node_id)
357 321
 
358 322
       is_shared_b = False if is_shared=='off' else True
359
-      print(is_shared_b)
360
-      print(loNode.is_shared)
361
-      print(loNode.owner_id)
362
-      print(loCurrentUser.user_id)
323
+
363 324
 
364 325
       # Only the node owner can modify is_shared
365 326
       if is_shared_b != loNode.is_shared and loNode.owner_id != loCurrentUser.user_id:
@@ -407,4 +368,3 @@ class PODApiController(BaseController):
407 368
             comment_right.rights = liRightLevel
408 369
 
409 370
       redirect(lurl('/document/%s#tab-accessmanagement'%(loNode.node_id)))
410
-

+ 52 - 0
pboard/pboard/controllers/apimenu.py View File

@@ -0,0 +1,52 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import tg
4
+
5
+from pboard.lib import base as plb
6
+from pboard.lib import dbapi as pld
7
+from pboard.model import data as pmd
8
+from pboard.model import serializers as pms
9
+
10
+class PODApiMenuController(plb.BaseController):
11
+
12
+    @tg.expose('json')
13
+    @pms.PBNodeForMenu
14
+    def children(self, id='#'):
15
+        try:
16
+            real_id = int(id)
17
+        except Exception:
18
+            real_id = None
19
+
20
+        current_user = pld.PODStaticController.getCurrentUser()
21
+        api_controller = pld.PODUserFilteredApiController(current_user.user_id)
22
+
23
+        allowed_nodes = api_controller.getListOfAllowedNodes()
24
+        parent_node = api_controller.getNode(real_id)
25
+        child_nodes = api_controller.getChildNodesForMenu(parent_node, allowed_nodes)
26
+
27
+        return child_nodes
28
+
29
+    @tg.expose('json')
30
+    @pms.NodeTreeItemForMenu
31
+    def initialize(self, current_node_id=0, id='#'):
32
+        try:
33
+            real_id = int(id)
34
+        except Exception:
35
+            real_id = None
36
+
37
+        current_user = pld.PODStaticController.getCurrentUser()
38
+        api_controller = pld.PODUserFilteredApiController(current_user.user_id)
39
+
40
+        current_node = None
41
+        try:
42
+            current_node = api_controller.getNode(current_node_id)
43
+        except:
44
+            print("Node not found: {0}".format(current_node_id))
45
+
46
+        allowed_nodes = api_controller.getListOfAllowedNodes()
47
+        initial_menu_structure = api_controller.buildTreeListForMenu(current_node, pmd.PBNodeStatus.getVisibleIdsList(), allowed_nodes)
48
+
49
+        return initial_menu_structure
50
+
51
+
52
+

+ 51 - 0
pboard/pboard/controllers/apipublic.py View File

@@ -0,0 +1,51 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import tg
4
+from tg import _compat
5
+from pboard.lib import base as plb
6
+from pboard.lib import dbapi as pld
7
+from pboard import model as pm
8
+from pboard.model import data as pmd
9
+from pboard.model import serializers as pms
10
+
11
+from tg.i18n import ugettext as _
12
+
13
+class PODPublicApiController(plb.BaseController):
14
+
15
+    @tg.expose()
16
+    def create_account(self, email='', password='', retyped_password='', **kw):
17
+      if email=='' or password=='' or retyped_password=='':
18
+        tg.flash(_('Account creation error: please fill all the fields'), 'error')
19
+        tg.redirect(tg.lurl('/'))
20
+      elif password!=retyped_password:
21
+        tg.flash(_('Account creation error: passwords do not match'), 'error')
22
+        tg.redirect(tg.lurl('/'))
23
+      else:
24
+        loExistingUser = pld.PODStaticController.getUserByEmailAddress(email)
25
+        if loExistingUser!=None:
26
+          tg.flash(_('Account creation error: account already exist: %s') % (email), 'error')
27
+          tg.redirect(tg.lurl('/'))
28
+
29
+        loNewAccount = pld.PODStaticController.createUser()
30
+        loNewAccount.email_address = email
31
+        loNewAccount.display_name  = email
32
+        loNewAccount.password      = password
33
+
34
+        loUserGroup = pld.PODStaticController.getGroup('user')
35
+        loUserGroup.users.append(loNewAccount)
36
+
37
+        pm.DBSession.add(loNewAccount)
38
+        pm.DBSession.flush()
39
+        pm.DBSession.refresh(loNewAccount)
40
+
41
+        loUserSpecificGroup = pld.PODStaticController.createGroup()
42
+
43
+        loUserSpecificGroup.group_id = 0-loNewAccount.user_id # group id of a given user is the opposite of the user id
44
+        loUserSpecificGroup.group_name = 'user_%d' % loNewAccount.user_id
45
+        loUserSpecificGroup.personnal_group = True
46
+        loUserSpecificGroup.users.append(loNewAccount)
47
+
48
+        pm.DBSession.flush()
49
+
50
+        tg.flash(_('Account successfully created: %s') % (email), 'info')
51
+        tg.redirect(tg.lurl('/'))

+ 4 - 5
pboard/pboard/controllers/root.py View File

@@ -15,7 +15,8 @@ from pboard.lib.base import BaseController
15 15
 from pboard.controllers.error import ErrorController
16 16
 
17 17
 from pboard.lib import dbapi as pld
18
-from pboard.controllers import api as pbca
18
+from pboard.controllers import api as pca
19
+from pboard.controllers import apipublic as pcap
19 20
 from pboard.controllers import debug as pbcd
20 21
 
21 22
 from pboard import model as pm
@@ -46,11 +47,11 @@ class RootController(BaseController):
46 47
         config_type = tgat.TGAdminConfig
47 48
     )
48 49
 
49
-    api   = pbca.PODApiController()
50
+    api   = pca.PODApiController()
50 51
     debug = pbcd.DebugController()
51 52
     error = ErrorController()
52 53
 
53
-    public_api = pbca.PODPublicApiController()
54
+    public_api = pcap.PODPublicApiController()
54 55
 
55 56
     def _before(self, *args, **kw):
56 57
         tmpl_context.project_name = "pboard"
@@ -143,11 +144,9 @@ class RootController(BaseController):
143 144
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
144 145
         # the correct behavior should be to redirect to setup page
145 146
         loMenuItems = loApiController.buildTreeListForMenu(loCurrentNode, pbmd.PBNodeStatus.getVisibleIdsList(), llAccessibleNodes)
146
-        nodes_as_tree_for_select_field = loApiController.DIRTY_OLDbuildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
147 147
 
148 148
         return dict(
149 149
             menu_node_list=loMenuItems,
150
-            root_node_list_for_select_field=nodes_as_tree_for_select_field,
151 150
             current_node=loCurrentNode,
152 151
             node_status_list = loNodeStatusList,
153 152
             keywords = highlight,

+ 125 - 21
pboard/pboard/lib/dbapi.py View File

@@ -9,6 +9,7 @@ from sqlalchemy import Table, ForeignKey, Column
9 9
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
10 10
 from sqlalchemy.orm import relation, synonym
11 11
 from sqlalchemy.orm import joinedload_all
12
+import sqlalchemy.orm as sqlao
12 13
 import sqlalchemy as sqla
13 14
 
14 15
 from pboard.model import DeclarativeBase, metadata, DBSession
@@ -82,7 +83,7 @@ class PODUserFilteredApiController(object):
82 83
     self._iCurrentUserId       = piUserId
83 84
     self._iExtraUserIdList     = piExtraUserIdList
84 85
     self._iUserIdFilteringList = None
85
-  
86
+    self._cache_allowed_nodes = None
86 87
 
87 88
   def _getUserIdListForFiltering(self):
88 89
     if self._iUserIdFilteringList==None:
@@ -127,7 +128,7 @@ class PODUserFilteredApiController(object):
127 128
     lsNodeIdFiltering = lsSqlSelectQuery % (str(self._iCurrentUserId))
128 129
 
129 130
     if liNodeId!=None and liNodeId!=0:
130
-      return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren"))\
131
+      return DBSession.query(pbmd.PBNode).options(sqlao.joinedload_all("_oParent"), sqlao.joinedload_all("_lAllChildren"))\
131 132
         .filter(pbmd.PBNode.node_id==liNodeId)\
132 133
         .filter(
133 134
           sqla.or_(
@@ -146,28 +147,75 @@ class PODUserFilteredApiController(object):
146 147
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).order_by(pbmd.PBNode.updated_at.desc()).limit(piMaxNodeNb).all()
147 148
 
148 149
 
149
-  def getListOfAllowedNodes(self) -> pbmd.PBNode:
150
-    lsSqlQuery = """
151
-        SELECT
152
-            pgn.node_id
153
-        FROM
154
-            pod_group_node AS pgn
155
-            join pod_user_group AS pug ON pug.group_id = pgn.group_id
156
-        WHERE
157
-            pgn.rights > 0
158
-            AND pug.user_id = :owner_id
159
-        UNION
150
+  def getListOfAllowedNodes(self, reset_cache=False) -> pbmd.PBNode:
151
+      if self._cache_allowed_nodes==None or reset_cache==True:
152
+        lsSqlQuery = """
160 153
             SELECT
161
-                node_id
154
+                pn.node_id,
155
+                pn.parent_id,
156
+                pn.node_order,
157
+                pn.node_type,
158
+                pn.created_at,
159
+                pn.updated_at,
160
+                pn.data_label,
161
+                pn.data_content,
162
+                pn.data_datetime,
163
+                pn.node_status,
164
+                pn.data_reminder_datetime,
165
+                pn.parent_tree_path,
166
+                pn.node_depth,
167
+                pn.owner_id,
168
+                pn.is_shared,
169
+                pn.is_public,
170
+                pn.public_url_key
162 171
             FROM
163
-                pod_nodes
172
+                pod_group_node AS pgn
173
+                join pod_user_group AS pug ON pug.group_id = pgn.group_id
174
+                join pod_nodes AS pn ON pgn.node_id=pn.node_id
164 175
             WHERE
165
-            owner_id=:owner_id;
166
-    """
167
-
168
-    loNodeListResult = DBSession.query(pbmd.PBNode).from_statement(lsSqlQuery).params(owner_id=self._iCurrentUserId)
169
-
170
-    return loNodeListResult.all()
176
+                pn.node_type='data'
177
+                AND pn.node_status NOT IN ('deleted', 'closed')
178
+                AND pn.node_id=pgn.node_id
179
+                AND pgn.rights > 0
180
+                AND pug.user_id = 3
181
+
182
+            UNION
183
+                SELECT
184
+                    pn.node_id,
185
+                    pn.parent_id,
186
+                    pn.node_order,
187
+                    pn.node_type,
188
+                    pn.created_at,
189
+                    pn.updated_at,
190
+                    pn.data_label,
191
+                    pn.data_content,
192
+                    pn.data_datetime,
193
+                    pn.node_status,
194
+                    pn.data_reminder_datetime,
195
+                    pn.parent_tree_path,
196
+                    pn.node_depth,
197
+                    pn.owner_id,
198
+                    pn.is_shared,
199
+                    pn.is_public,
200
+                    pn.public_url_key
201
+                FROM
202
+                    pod_nodes AS pn
203
+                WHERE
204
+                    pn.node_type='data'
205
+                    AND pn.node_status NOT IN ('deleted', 'closed')
206
+                    AND pn.owner_id=:owner_id
207
+
208
+            ORDER BY node_id ASC
209
+        """
210
+
211
+        loNodeListResult = DBSession.query(pbmd.PBNode).\
212
+            from_statement(lsSqlQuery).\
213
+            params(owner_id=self._iCurrentUserId)
214
+        allowed_nodes = loNodeListResult.all()
215
+
216
+        self._cache_allowed_nodes = allowed_nodes
217
+
218
+      return self._cache_allowed_nodes
171 219
 
172 220
 
173 221
   def searchNodesByText(self, plKeywordList: [str], piMaxNodeNb=100):
@@ -197,6 +245,47 @@ class PODUserFilteredApiController(object):
197 245
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_status==psNodeStatus).order_by(pbmd.PBNode.updated_at).limit(piMaxNodeNb).all()
198 246
 
199 247
 
248
+  def getChildNodesForMenu(self, poParentNode: pbmd.PBNode, allowed_nodes: [pbmd.PBNode]) -> [pbmd.PBNode]:
249
+    visible_child_nodes = []
250
+
251
+    if poParentNode!=None:
252
+        # standard case
253
+        print(" -------------- BEFORE @@@@@@@@@@@@@@@@ ")
254
+        all_child_nodes = poParentNode.getChildren()
255
+        print("@@@@@@@@@@@@@@@@ AFTER @@@@@@@@@@@@@@@@ ")
256
+        for child_node in all_child_nodes:
257
+          if child_node in allowed_nodes:
258
+            visible_child_nodes.append(child_node)
259
+    else:
260
+        # Root case
261
+        parent_nodes = DBSession
262
+        for allowed_node in allowed_nodes:
263
+            print("     @@@@@@@@@@@@@@@@ BEFORE @@@@@@@@@@@@@@@@ ")
264
+            # loParent = allowed_node._oParent
265
+            # print("@@@@@@@@@@@@@@@@ AFTER @@@@@@@@@@@@@@@@ {0}".format(allowed_node._oParent.node_id if allowed_node._oParent!=None else '0'))
266
+            # print("==== EXTRA {0}".format(allowed_node._oParent.node_id if allowed_node._oParent!=None else '0'))
267
+            print("====> ")
268
+
269
+            if allowed_node._oParent is None or allowed_node._oParent==allowed_node:
270
+                # D.A. - HACK - 2014-05-27
271
+                # I don't know why, but as from now (with use of sqlao.contains_eager in getListOfAllowedNodes()
272
+                # the root items have at first iteration itself as parent
273
+                print("==== EXTRA END")
274
+                # this is a root item => add it
275
+                visible_child_nodes.append(allowed_node)
276
+            else:
277
+                if allowed_node._oParent not in allowed_nodes:
278
+                    # the node is visible but not the parent => put it at the root
279
+                    visible_child_nodes.append(allowed_node)
280
+                else:
281
+                    print("==== EXTRA END 2 {0}".format(allowed_node._oParent.node_id))
282
+
283
+    print(" @@@@@@@@@@@@@@@@ PRE FAILURE @@@@@@@@@@@@@@@@ ")
284
+    return visible_child_nodes
285
+
286
+
287
+
288
+
200 289
   def buildTreeListForMenu(self, poCurrentNode: pbmd.PBNode, plViewableStatusId: [], plAllowedNodes: [pbmd.PBNode]) -> [pbmd.NodeTreeItem]:
201 290
     # The algorithm is:
202 291
     # 1. build an intermediate tree containing only current node and parent path
@@ -209,8 +298,10 @@ class PODUserFilteredApiController(object):
209 298
     previous_tree_item = None
210 299
     tmp_children_nodes = []
211 300
 
301
+    print("============================> ",poCurrentNode)
212 302
     if poCurrentNode is not None:
213 303
         breadcrumb_nodes = poCurrentNode.getBreadCrumbNodes()
304
+        print("====================> NODES", breadcrumb_nodes)
214 305
         breadcrumb_nodes.append(poCurrentNode) # by default the current node is not included
215 306
 
216 307
         for breadcrumb_node in reversed(breadcrumb_nodes):
@@ -218,25 +309,38 @@ class PODUserFilteredApiController(object):
218 309
                 # First iteration. We add all current_node children
219 310
                 for child_node in breadcrumb_node.getChildren():
220 311
                     child_item = pbmd.NodeTreeItem(child_node, [])
312
+                    assert(isinstance(child_item, pbmd.NodeTreeItem))
221 313
                     tmp_children_nodes.append(child_item)
222 314
                 previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
223 315
             else:
224 316
                 tmp_children_nodes = []
225 317
                 for child_node in breadcrumb_node.getChildren():
226 318
                     if child_node == previous_tree_item.node:
319
+                        assert(isinstance(previous_tree_item, pbmd.NodeTreeItem))
227 320
                         tmp_children_nodes.append(previous_tree_item)
228 321
                     else:
229 322
                         sibling_node = pbmd.NodeTreeItem(child_node, [])
323
+                        assert(isinstance(sibling_node, pbmd.NodeTreeItem))
230 324
                         tmp_children_nodes.append(sibling_node)
231 325
 
232 326
                 previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
233 327
 
234 328
     for node in plAllowedNodes:
329
+        # This part of the algorithm insert root items
235 330
         if node.parent_id==0 or node.parent_id is None:
236 331
             if previous_tree_item is not None and node == previous_tree_item.node:
332
+                assert(isinstance(previous_tree_item, pbmd.NodeTreeItem))
237 333
                 node_tree.append(previous_tree_item)
238 334
             else:
239 335
                 node_tree.append(pbmd.NodeTreeItem(node, []))
336
+        else:
337
+            # Try to add nodes shared with me but with a parent which is not shared
338
+            if node.owner_id!=self._iCurrentUserId:
339
+                # this is a node shared with me
340
+                if node._oParent!=None and node._oParent not in plAllowedNodes:
341
+                    # the node is shared but not the parent => put it in the root
342
+                    node_tree.append(pbmd.NodeTreeItem(node))
343
+
240 344
 
241 345
     return node_tree
242 346
 

+ 4 - 0
pboard/pboard/lib/helpers.py View File

@@ -65,6 +65,10 @@ class ID(object):
65 65
       return 'add-file-modal-form'
66 66
 
67 67
   @classmethod
68
+  def MoveDocumentModalForm(cls, poNode):
69
+      return 'move-document-modal-form-{0}'.format(poNode.node_id)
70
+
71
+  @classmethod
68 72
   def AddEventModalForm(cls, poNode=None):
69 73
     if poNode:
70 74
       return 'add-event-modal-form-%d'%poNode.node_id

+ 95 - 0
pboard/pboard/model/serializers.py View File

@@ -0,0 +1,95 @@
1
+# -*- coding: utf-8 -*-
2
+import tg
3
+from pboard.model import data as pmd
4
+
5
+def PBNodeForMenu(func):
6
+
7
+    def process_item(item: pmd.PBNode):
8
+        """ convert given item into a dictionnary """
9
+        url = tg.url('/document/', dict(node_id=item.node_id)) ## FIXME - 2014-05-27 - Make this more flexible
10
+        print("########## BEFORE ##########")
11
+        new_item = dict(
12
+            id = item.node_id,
13
+            children = item.getChildNb()>0,
14
+            text = item.data_label,
15
+            # parent = item._oParent.node_id if (item._oParent!=None) else '#',
16
+            a_attr = { "href" : url },
17
+            li_attr = { "title": item.data_label }
18
+        )
19
+        print("########## AFTER ##########")
20
+        return new_item
21
+
22
+    def pre_serialize(*args, **kws):
23
+        initial_result = func(*args, **kws)
24
+        real_result = None
25
+
26
+        print("DEBUG ===================>", initial_result)
27
+        if isinstance(initial_result, list):
28
+            real_result = list()
29
+            for value_item in initial_result:
30
+                real_result.append(process_item(value_item))
31
+        else:
32
+            # We suppose here that we have an object only
33
+            real_result = process_item(initial_result)
34
+
35
+        return dict(d = real_result)
36
+
37
+    return pre_serialize
38
+
39
+
40
+def NodeTreeItemForMenu(func):
41
+    """ works with structure NodeTreeItem """
42
+    def process_item(structure_item: pmd.NodeTreeItem, current_node_id=None):
43
+        """ convert given item into a dictionnary """
44
+
45
+        item = structure_item.node
46
+        url = tg.url('/document/', dict(node_id=item.node_id)) ## FIXME - 2014-05-27 - Make this more flexible
47
+        children = []
48
+        for child_item in structure_item.children:
49
+            children.append(process_item(child_item, current_node_id))
50
+        # print("########## BEFORE ##########")
51
+
52
+        children_field_value = None
53
+        if len(children)>0:
54
+            children_field_value = children
55
+        elif item.getChildNb()>0:
56
+            children_field_value = True
57
+        else:
58
+            children_field_value = False
59
+
60
+        new_item_state = dict(
61
+            opened = len(children)>0,
62
+            selected = current_node_id!=None and item.node_id==current_node_id,
63
+        )
64
+
65
+        new_item = dict(
66
+            id = item.node_id,
67
+            children = children_field_value,
68
+            text = item.data_label,
69
+            # parent = item._oParent.node_id if (item._oParent!=None) else '#',
70
+            state = new_item_state,
71
+            a_attr = { "href" : url },
72
+            li_attr = { "title": item.data_label }
73
+        )
74
+        # print("########## AFTER ##########")
75
+        return new_item
76
+
77
+    def pre_serialize(*args, **kws):
78
+        initial_result = func(*args, **kws)
79
+        real_result = None
80
+
81
+        current_node_id = None
82
+        if "current_node_id" in kws:
83
+            current_node_id = int(kws['current_node_id'])
84
+
85
+        if isinstance(initial_result, list):
86
+            real_result = list()
87
+            for value_item in initial_result:
88
+                real_result.append(process_item(value_item, current_node_id))
89
+        else:
90
+            # We suppose here that we have an object only
91
+            real_result = process_item(initial_result, current_node_id)
92
+
93
+        return dict(d = real_result)
94
+
95
+    return pre_serialize

BIN
pboard/pboard/public/img/turbogears_logo.png View File


BIN
pboard/pboard/public/img/turbogears_logo_big.png View File


BIN
pboard/pboard/public/img/under_the_hood_blue.png View File


+ 104 - 22
pboard/pboard/templates/document-widgets.mak View File

@@ -24,18 +24,18 @@
24 24
   % endif
25 25
 </%def>
26 26
 
27
-<%def name="ToolbarMenuItemModal(psTargetModalId, psIconClasses, psMenuLabel)">
28
-  <li><a href="#${psTargetModalId}" role="button" data-toggle="modal"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
27
+<%def name="ToolbarMenuItemModal(psTargetModalId, psIconClasses, psMenuLabel, psItemClasses='')">
28
+  <li class="${psItemClasses}"><a href="#${psTargetModalId}" role="button" data-toggle="modal"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
29 29
 </%def>
30 30
 
31 31
 <%def name="ToolbarMenuItemInline(psTargetId, psIconClasses, psMenuLabel)">
32 32
   <li><a href="#${psTargetId}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
33 33
 </%def>
34
-<%def name="ToolbarMenuItemLink(psTargetUrl, psIconClasses, psMenuLabel, psLinkCss='', psLinkTitle='')">
34
+<%def name="ToolbarMenuItemLink(psTargetUrl, psIconClasses, psMenuLabel, psLinkCss='', psLinkTitle='', psOnClick='')">
35 35
   % if psTargetUrl=='#':
36
-    <li class="disabled"><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
36
+    <li class="disabled"><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}" onclick="${psOnClick}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
37 37
   % else:
38
-    <li><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
38
+    <li><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}" onclick="${psOnClick}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
39 39
   % endif
40 40
 </%def>
41 41
 
@@ -44,6 +44,7 @@
44 44
   <div id="${psDivId}">
45 45
     <div class="btn-group">
46 46
       ${POD.EditButton('current-document-content-edit-button', True)}
47
+
47 48
       <button class="btn btn-small"  data-toggle="dropdown" href="#"> 
48 49
         <i class="fa  fa-signal"></i>
49 50
         ${_("Change status")}
@@ -72,6 +73,7 @@
72 73
       % endfor
73 74
       </ul>
74 75
     </div>
76
+    
75 77
     <div class="btn-group">
76 78
       <button class="btn btn-small btn-success"  data-toggle="dropdown" href="#">
77 79
         <i class="fa fa-plus"></i> ${_('Add')}
@@ -102,26 +104,23 @@
102 104
         <li><p class="pod-grey"><i class="fa fa-danger"></i> coming soon!</p></li>
103 105
       </ul>
104 106
     </div>
105
-    <div class="btn-group ">
106
-      <a
107
-        class="btn btn-small btn-warning"
108
-        href="#"
109
-        data-toggle="dropdown"
110
-        title="${_('Move to')}"
111
-        ><i class="fa fa-arrows"></i></a>
107
+
108
+    <div class="btn-group pull-right">
109
+      <button class="btn btn-small btn-link"  data-toggle="dropdown" href="#">
110
+        ${_('more ...')}
111
+      </button>
112 112
       <ul class="dropdown-menu">
113
-        <li >
114
-          <div class="btn-warning strong" ><strong><i class="fa fa-magic"></i> ${_("Move the document...")}</strong><br/></div>
115
-          <div class="pod-grey"><i>move the document to...</i></div>
113
+        <li>
114
+          <div class="strong" ><strong><i class="fa fa-magic"></i> ${_('Advanced actions...')}</strong><br/></div>
115
+          <div class="pod-grey"><i>${_('power user actions...')}</i></div>
116 116
         </li>
117
-        ${node_treeview_for_set_parent_menu(poNode.node_id, plRootNodes)}
117
+##
118
+## Here are MOVE and DELETE buttons
119
+##
120
+        ${ToolbarMenuItemModal(h.ID.MoveDocumentModalForm(current_node), 'fa fa-arrows', _('Move'), 'btn-warning')}
121
+        ${ToolbarMenuItemLink(tg.url('/api/edit_status', dict(node_id=poNode.node_id, node_status='deleted')), 'fa fa-trash-o', _('Delete'), 'btn-danger', _('Delete the current document'), 'return confirm(\'{0}\');'.format('Delete current document?'))}
122
+
118 123
       </ul>
119
-      <a
120
-        class="btn btn-small btn-danger"
121
-        href='${tg.url('/api/edit_status', dict(node_id=poNode.node_id, node_status='deleted'))}'
122
-        id='current-document-force-delete-button' onclick="return confirm('${_('Delete current document?')}');"
123
-        title="${_('Delete')}"
124
-        ><i class="fa fa-trash-o"></i></a>
125 124
     </div>
126 125
   </div>
127 126
 </%def>
@@ -537,3 +536,86 @@
537 536
     </div>
538 537
   </div>
539 538
 </%def>
539
+
540
+<%def name="MoveDocumentModalDialog(poNode, psPostUrl, psModalId, psTitle)">
541
+  <div
542
+    id="${psModalId}"
543
+    class="modal hide"
544
+    tabindex="-1"
545
+    role="dialog"
546
+    aria-labelledby="myModalLabel"
547
+    aria-hidden="true">
548
+    
549
+    <div class="modal-header">
550
+    ## MODAL HEADER
551
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
552
+      <h3 id="myModalLabel">${psTitle}</h3>
553
+    ## MODAL HEADER [END]
554
+    </div>
555
+
556
+    <div class="modal-body">
557
+    ## MODAL BODY
558
+          <p>${_('Select the destination:')}</p>
559
+          <div id="${psModalId}-new-parent-selector-tree"></div>
560
+          <script>
561
+            $(function () {
562
+              $('#${psModalId}-new-parent-selector-tree').jstree({
563
+                "plugins" : [ "wholerow"],
564
+                'core' : {
565
+
566
+                  'error': function (error) {
567
+                    console.log('Error ' + error.toString())
568
+                  },
569
+                  'data' : {
570
+                    'dataType': 'json',
571
+                    'contentType': 'application/json; charset=utf-8',
572
+                    'url' : function (node) {
573
+                         return '${tg.url("/api/menu/children")}';
574
+                    },
575
+                    'data' : function(node) {
576
+                      console.log("NODE => "+JSON.stringify(node))
577
+                      return {
578
+                        'id' : node.id
579
+                      };
580
+                    },
581
+                    'success': function (new_data) {
582
+                      console.log('loaded new menu data' + new_data)
583
+                      return new_data;
584
+                    },
585
+                  },
586
+                }
587
+              });
588
+              
589
+              $('#${psModalId}-new-parent-selector-tree').on("select_node.jstree", function (e, data) {
590
+                new_parent_id_selected = data.selected[0];
591
+                $("#${psModalId}-form-new-parent-field").attr("value", new_parent_id_selected)
592
+                console.log("About to move document "+${poNode.node_id}+" as child of "+new_parent_id_selected);
593
+              });
594
+            });
595
+          </script>
596
+          <form id='${psModalId}-form' method="POST" action="${psPostUrl}" enctype="multipart/form-data">
597
+            <input type="hidden" name="node_id" value="${poNode.node_id}"/>
598
+            <input type="hidden" name="new_parent_id" value="-1" id="${psModalId}-form-new-parent-field" />
599
+          </form>
600
+    ## MODAL BODY [END]
601
+    </div>
602
+    
603
+    <div class="modal-footer">
604
+    ## MODAL FOOTER
605
+      <button class="btn" data-dismiss="modal" aria-hidden="true">
606
+        <i class="fa fa-ban"></i> ${_('Cancel')}
607
+      </button>
608
+      <button class="btn btn-success" id="${psModalId}-form-submit-button">
609
+        <i class="fa fa-check"></i> ${_('Save changes')}
610
+      </button>
611
+      <script>
612
+        $('#${psModalId}-form-submit-button').click(function(){
613
+          $('#${psModalId}-textarea-wysiwyg').cleanHtml();
614
+          $('#${psModalId}-textarea').val($('#${psModalId}-textarea-wysiwyg').html());
615
+          $('#${psModalId}-form')[0].submit();
616
+        });
617
+      </script>
618
+    ## MODAL FOOTER [END]
619
+    </div>
620
+  </div>
621
+</%def>

+ 54 - 5
pboard/pboard/templates/document.mak View File

@@ -69,10 +69,58 @@
69 69
   </div>
70 70
 
71 71
   <div class="row">
72
-    <div id='application-left-panel' class="span3">
73
-      <div>
74
-        ${node_treeview(menu_node_list)}
75
-      </div>
72
+    <div id='application-left-panel' class="span3" >
73
+      <link rel="stylesheet" href="${tg.url('/jstree/dist/themes/default/style.min.css')}" />
74
+      <script src="${tg.url('/jstree/dist/jstree.js')}"></script>
75
+      <style>
76
+        #mypodtree {overflow:hidden;}
77
+        #mypodtree:hover {overflow:visible; }
78
+      </style>
79
+      <div id="mypodtree"></div>
80
+      <script>
81
+        $(function () {
82
+          $('#mypodtree').jstree({
83
+            "plugins" : [ "wholerow"],
84
+            'core' : {
85
+
86
+              'error': function (error) {
87
+                console.log('Error ' + error.toString())
88
+              },
89
+              'data' : {
90
+                'dataType': 'json',
91
+                'contentType': 'application/json; charset=utf-8',
92
+                'url' : function (node) {
93
+                  if (node.id==='#') {
94
+                    return '${tg.url("/api/menu/initialize", dict(current_node_id=current_node.node_id if current_node else 0))}';
95
+                  } else {
96
+                    return '${tg.url("/api/menu/children")}';
97
+                  }
98
+                },
99
+                'data' : function(node) {
100
+                  console.log("NODE => "+JSON.stringify(node))
101
+                  return {
102
+                    'id' : node.id
103
+                  };
104
+                },
105
+                'success': function (new_data) {
106
+                  console.log('loaded new menu data' + new_data)
107
+                  return new_data;
108
+                },
109
+              },
110
+            }
111
+          });
112
+          
113
+          $('#mypodtree').on("select_node.jstree", function (e, data) {
114
+            url = "${tg.url('/document/')}"+data.selected[0];
115
+            console.log("Opening document: "+url);
116
+            location.href = url;
117
+          });
118
+        });
119
+      </script>
120
+## INFO - D.A. - 2014-05-28 - Hide old school menu
121
+##      <div>
122
+##        ${node_treeview(menu_node_list)}
123
+##      </div>
76 124
     </div>
77 125
     <div id='application-main-panel' class="span9">
78 126
 
@@ -106,7 +154,8 @@
106 154
           ${DOC.EventEditModalDialog(current_node.node_id, None, tg.url('/api/create_event'), h.ID.AddEventModalForm(current_node), _('Add an event'))}
107 155
           ${DOC.ContactEditModalDialog(current_node.node_id, None, tg.url('/api/create_contact'), h.ID.AddContactModalForm(current_node), _('Add a new contact'))}
108 156
           ${DOC.FileEditModalDialog(current_node.node_id, None, tg.url('/api/create_file'), h.ID.AddFileModalForm(current_node), _('Add a new file'))}
109
-          
157
+          ${DOC.MoveDocumentModalDialog(current_node, tg.url('/api/set_parent_node'), h.ID.MoveDocumentModalForm(current_node), _('Move the document'))}
158
+
110 159
           <div class="tabbable">
111 160
             <ul class="nav nav-tabs" style="margin-bottom: 0em;">
112 161
                 <li>${DOC.MetadataTab('#subdocuments', 'tab', _('Subdocuments'), 'fa-file-text-o', current_node.getChildren())}</li>