Commits

Luke Stebbing committed 2c6df2a Merge

Merge.

Comments (0)

Files changed (3)

webhelpers/html/grid.py

 from webhelpers.html.builder import HTML, literal
 
 class Grid(object):
+    """
+    This class is designed to aid programmer in the task of creation of
+    tables/grids - structures that are mostly built from datasets.
     
-    def __init__(self, itemlist, columns, column_formats=None, start_number=1):
-        self.custom_record_format = None
-        self.labels = {}
-        self.exclude_ordering = ["_numbered"]
+    To create a grid at minimum one one needs to pass a dataset,
+    like a list of dictionaries, or sqlalchemy proxy or query object.
+    
+    grid = Grid(itemlist, ['_numbered','c1', 'c2','c4'])
+    where itemlist in this simple scenario is a list of dicts:
+    [{'c1':1,'c2'...}, {'c1'...}, ...]
+    
+    This helper also received the list that defines order in which
+    columns will be rendered - also keep note of special column name that can be
+    passed in list that defines order - ``_numbered`` - this adds additional
+    column that shows the number of item. For paging sql data there one can pass
+    ``start_number`` argument to the grid to define where to start counting.
+    Descendant sorting on ``_numbered`` column decrements the value, you can
+    change how numberign function behaves by overloading ``calc_row_no`` 
+    property.
+    
+    
+    Then printing the grid instance forces rendering of data into html output,
+    the headers are created and for every entry in list a table row will be
+    created. The names of the columns will get automatically converted for
+    humans ie. foo_bar becomes Foo Bar. If you want the title to be something
+    else you can change the grid.labels dict. If you want the column ``part_no``
+    to become ``Catalogue Number`` just do:
+    
+    grid.labels[``part_no``] = u'Catalogue Number'
+    
+    It may be desired to exclude some or all columns from generation sorting
+    urls (used by subclasses that are sorting aware). You can use grids
+    exclude_ordering property to pass list of columns that should not support
+    sorting. By default sorting is disabled - this ``exclude_ordering`` contains
+    every column name.
+        
+    Since various programmers have different needs, Grid is highly customizable.
+    By default grid attempts to read the value from dict directly by key.
+    For every column it will try to output value of current_row['colname'].
+    
+    Since very often this behavior needs to be overridden like we need date
+    formatted, use conditionals or generate a link one can use
+    the  ``column_formats`` dict and pass a rendering function/lambda to it. 
+    For example we want to apppend ``foo`` to part number:
+    
+    def custem_part_no_td(col_num, i, item):
+        return HTML.td(`Foo %s` % item[``part_no``])
+    
+    grid.labels[``part_no``] = custem_part_no_td
+    
+    You can customize the grids look and behavior by overloading grids instance
+    render functions:
+    
+    grid.default_column_format(self, column_number, i, record, column_name)
+    by default generates markup like:
+    <td class="cNO">VALUE</td>
+    
+    grid.default_header_column_format(self, column_number, column_name, 
+        header_label)
+    by default generates markup like:
+    <td class="cNO COLUMN_NAME">VALUE</td>
+        
+    grid.default_header_ordered_column_format(self, column_number, order, 
+        column_name, header_label)
+    Used by grids that support ordering of columns in the grid like, 
+    webhelpers.pylonslib.grid.GridPylons.
+    by default generates markup like:
+    <td class="cNO ordering ORDER_DIRECTION COLUMN_NAME">LABEL</td>
+    
+    grid.default_header_record_format(self, headers)
+    by default generates markup like:
+    <tr class="header">HEADERS_MARKUP</tr>
+    
+    grid.default_record_format(self, i, record, columns)
+    <tr class="ODD_OR_EVEN">RECORD_MARKUP</tr>
+    
+    grid.generate_header_link(self, column_number, column, label_text)
+    by default just sets the order direction and column properties for grid.
+    Actual link generation is handled by sublasses of Grid.
+    
+    grid.numbered_column_format(self, column_number, i, record)
+    by default generates markup like:
+    <td class="cNO">RECORD_NO</td>
+     
+     
+    """
+    def __init__(self, itemlist, columns, column_labels=None,
+                  column_formats=None, start_number=1,
+                 order_column=None, order_direction='asc'):
+        self.labels = column_labels or {}
+        self.exclude_ordering = columns
         self.itemlist = itemlist
+        self.columns = columns
+        self.column_formats = column_formats or {}
         if "_numbered" in columns:
             self.labels["_numbered"] = "#"
-        self.columns = columns
-        self.column_formats = column_formats or {}
-        self._start_number = start_number
+        if "_numbered" not in self.column_formats: 
+            self.column_formats["_numbered"] = self.numbered_column_format 
+        self.start_number = start_number
+        self.order_dir = order_direction
+        self.order_column = order_column
+    
+    def calc_row_no(self, i, column):
+        if self.order_dir == 'dsc' and self.order_column == column:
+            return self.start_number - i
+        else:
+            return self.start_number + i
         
-
     def make_headers(self):
         header_columns = []
             
                 label_text = column.replace("_", " ").title()
             # handle non clickable columns
             if column in self.exclude_ordering:
-                header = self.default_header_column_format(i + 1, column, 
+                header = self.default_header_column_format(i + 1, column,
                     label_text)
             # handle clickable columns
             else:
-                header = self.generate_header_link(i + 1, column, label_text)
-                if header is None:
-                    header = self.default_header_column_format(i + 1, column, 
-                        label_text)                    
+                header = self.generate_header_link(i + 1, column, label_text)                
             header_columns.append(header)               
         return HTML(*header_columns)
     
         columns = []        
         for col_num, column in enumerate(self.columns):
             if column in self.column_formats:
-                columns.append(self.column_formats[column](col_num, i, record))
+                r = self.column_formats[column](col_num,
+                                                self. calc_row_no(i, column),
+                                                record)
             else:
-                if column == "_numbered":
-                    r = self.numbered_column_format(col_num, 
-                        i + self._start_number, record)
-                else:
-                    r = self.default_column_format(col_num, i, record, column)
-                columns.append(r)
+                r = self.default_column_format(col_num,
+                                               self.calc_row_no(i, column),
+                                               record, column)
+            columns.append(r)
         return HTML(*columns)
     
     def __html__(self):
         headers = self.make_headers()
         r = self.default_header_record_format(headers)
         records.append(r)
+        # now lets render the actual item grid
         for i, record in enumerate(self.itemlist):
-            if i % 2 == 0:
-                class_name = "even"
+            columns = self.make_columns(i, record)
+            if hasattr(self, 'custom_record_format'):
+                r = self.custom_record_format(i, record, columns)
             else:
-                class_name = "odd"
-            columns = self.make_columns(i, record)
-            if self.custom_record_format is None:
-                r = self.default_record_format(i, record, class_name, columns)
-            else:
-                r = self.custom_record_format(i, record, class_name, columns)
+                r = self.default_record_format(i, record, columns)
             records.append(r)
         return HTML(*records)
     
         return self.__html__()
 
     def generate_header_link(self, column_number, column, label_text):
-        return None
+        # Is the current column the one we're ordering on?
+        if (column == self.order_column):
+            return self.default_header_ordered_column_format(column_number,
+                                                             column,
+                                                             label_text)
+        else:
+            return self.default_header_column_format(column_number, column,
+                                                     label_text)
 
     #### Default HTML tag formats ####
 
     def numbered_column_format(self, column_number, i, record):
         class_name = "c%s" % (column_number)
         return HTML.tag("td", i, class_=class_name)
-    
-    def default_record_format(self, i, record, class_name, columns):
-        return HTML.tag("tr", columns, class_=class_name)
 
-    def default_record_format(self, i, record, class_name, columns):
+    def default_record_format(self, i, record, columns):
+        if i % 2 == 0:
+            class_name = "even"
+        else:
+            class_name = "odd"
         return HTML.tag("tr", columns, class_=class_name)
 
     def default_header_record_format(self, headers):
         return HTML.tag("tr", headers, class_="header")
 
-    def default_header_ordered_column_format(self, column_number, order, 
-        column_name, header_label):
+    def default_header_ordered_column_format(self, column_number, column_name,
+                                             header_label):
         header_label = HTML(header_label, HTML.tag("span", class_="marker"))
         if column_name == "_numbered":
             column_name = "numbered"
-        class_name = "c%s ordering %s %s" % (column_number, order, column_name)
+        class_name = "c%s ordering %s %s" % (column_number, self.order_dir, column_name)
         return HTML.tag("td", header_label, class_=class_name)
 
-    def default_header_column_format(self, column_number, column_name, 
+    def default_header_column_format(self, column_number, column_name,
         header_label):
         if column_name == "_numbered":
             column_name = "numbered"

webhelpers/html/grid_demo.py

              {"group_name": "foo4", "options": "lalala4", "id":4},
              ]
 
-#### Demo base class ####
 class _DemoBase(object):
     title = None
     description = None
             return HTML.td(a)
         
         g = Grid(test_data, columns=["_numbered","group_name","options"])
-        g.labels = {
-            "options":'FOOBAAR'
-                    }
-        g.column_formats = {
-            "options": options_td,
-            }
+        g.labels["options"] = 'FOOBAAR'
+        g.column_formats["options"] = options_td
         return g
 
+class OrderShiftDemo(_DemoBase):
+    name = "OrderShift"
+    description = """\
+This table shows a grid with order starting from 10."""
+
+    def get_grid(self):
+        """
+        order direction demo
+        """
+        
+        g = Grid(test_data, columns=["_numbered","group_name","options"],
+                 start_number=10
+                 )
+        return g
+
+
+class OrderingDirectionHeaderAwareDemo(_DemoBase):
+    name = "OrderDirectionHeaderAwareDemo"
+    description = """\
+This table shows a grid that has a markup indicating order direction.
+Options column has sorting set to "asc" """
+
+    def get_grid(self):
+        """
+        order direction demo
+        """
+        
+        g = Grid(test_data, columns=["_numbered","group_name","options"],
+                 order_column='options', order_direction='asc'
+                 )
+        #enable ordering support
+        g.exclude_ordering = []
+        return g
 demos = subclasses_only(_DemoBase, globals())
 
 #demos = [BasicDemo, CustomColumnDemo]

webhelpers/pylonslib/grid.py

     pass
 
 class GridPylons(grid.Grid):
+    """
+    Subclass of Grid that can handle header link generation for quick building
+    of tables that support ordering of their contents, paginated results etc.
+    """
     
     def __init__(self, request, *args, **kw):
         self.request = request
         super(GridPylons, self).__init__(*args, **kw)
     
     def generate_header_link(self, column_number, column, label_text):
+        """ This handles generation of link and then decides to call
+        self.default_header_ordered_column_format 
+        or 
+        self.default_header_column_format 
+        based on if current column is the one that is used for sorting or not
+        """ 
         from pylons import url
         # this will handle possible URL generation
         request_copy = self.request.copy().GET
-        if "order_col" in request_copy and "order_dir" in request_copy:
-            self.order_column = request_copy.pop("order_col")
-            self.order_dir = request_copy.pop("order_dir")
-        else:
-            self.order_column = None
-            self.order_dir = None
-            
-        if (column == self.order_column and self.order_dir == "asc"):
+        self.order_column = request_copy.pop("order_col", None)
+        self.order_dir = request_copy.pop("order_dir", None)
+        if column == self.order_column and self.order_dir == "asc":
             new_order_dir = "dsc"
         else:
             new_order_dir = "asc"
                                **request_copy)
         label_text = HTML.tag("a", href=url_href, c=label_text)
         # Is the current column the one we're ordering on?
-        if (column == self.order_column):
+        if column == self.order_column:
             return self.default_header_ordered_column_format(column_number,
-                                                             self.order_dir,
-                                                             column, 
+                                                             column,
                                                              label_text)
         else:
             return self.default_header_column_format(column_number, column,
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.