Browse Source

HUE-957 [beeswax] Bulk UI operations

Introduced common action bar for my queries, saved queries and tables
Enrico Berti 13 years ago
parent
commit
b7136e2fd1

+ 162 - 98
apps/beeswax/src/beeswax/templates/list_designs.mako

@@ -19,120 +19,184 @@
     from desktop.views import commonheader, commonfooter
     from django.utils.translation import ugettext as _
 %>
-
+<%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 
 ${ commonheader(_('Saved Queries'), app_name, user, '100px') | n,unicode }
 
 ${layout.menubar(section='saved queries')}
+
 <div class="container-fluid">
-    <h1>${_('Saved Queries')}</h1>
-    <table class="table table-striped table-condensed datatables">
-        <thead>
-        <tr>
-            <th>${_('Name')}</th>
-            <th>${_('Description')}</th>
-            <th>${_('Owner')}</th>
-            <th>${_('Type')}</th>
-            <th>${_('Last Modified')}</th>
-            <th></th>
-        </tr>
-        </thead>
-        <tbody>
-            <%!
-                from beeswax import models
-            %>
-            % for design in page.object_list:
-                <%
-                    may_edit = user == design.owner
-                %>
-                <tr>
-                <td>
-                    % if may_edit:
-                        <a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
-                    % else:
-                    ${design.name}
-                    % endif
-                </td>
-                <td>
-                    % if design.desc:
-                        <p>${design.desc}</p>
-                    % endif
-                </td>
-                    <td>${design.owner.username}</td>
-                <td>
-                    ${_('Query')}
-                </td>
-                <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
-                <td>
-                <div class="btn-group">
-                    <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
-                        Options
-                        <span class="caret"></span>
-                    </a>
-                    <ul class="dropdown-menu">
-                    % if may_edit:
-                        <li><a href="${ url(app_name + ':execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
-                        <li><a href="javascript:void(0)" data-confirmation-url="${ url(app_name + ':delete_design', design_id=design.id) }" title="${_('Delete this query.')}" class="contextItem confirmationModal">${_('Delete')}</a></li>
-                        <li><a href="${ url(app_name + ':list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
-                    % endif
-                    <li><a href="${ url(app_name + ':clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
-                    </ul>
-                </div>
-                </td>
-                </tr>
-            % endfor
-        </tbody>
-    </table>
-    ${comps.pagination(page)}
+  <h1>${_('Saved Queries')}</h1>
+
+  <%actionbar:render>
+    <%def name="actions()">
+      <button id="editBtn" class="btn toolbarBtn" title="${_('Edit the selected query')}" disabled="disabled"><i class="icon-edit"></i> ${_('Edit')}</button>
+      <button id="cloneBtn" class="btn toolbarBtn" title="${_('Clone the selected query')}" disabled="disabled"><i class="icon-retweet"></i> ${_('Clone')}</button>
+      <button id="historyBtn" class="btn toolbarBtn" title="${_('View the usage history of the selected query')}" disabled="disabled"><i class="icon-tasks"></i> ${_('Usage history')}</button>
+      <button id="deleteBtn" class="btn toolbarBtn" title="${_('Delete the selected queries')}" disabled="disabled"><i class="icon-trash"></i>  ${_('Delete')}</button>
+    </%def>
+
+    <%def name="creation()">
+      <a class="btn" href="${ url(app_name + ':execute_query') }"><i class="icon-plus-sign"></i> ${_('Create New Query')}</a>
+    </%def>
+  </%actionbar:render>
+
+  <table class="table table-striped table-condensed datatables">
+    <thead>
+      <tr>
+        <th width="1%"><div class="hueCheckbox selectAll" data-selectables="savedCheck"></div></th>
+        <th>${_('Name')}</th>
+        <th>${_('Description')}</th>
+        <th>${_('Owner')}</th>
+        <th>${_('Type')}</th>
+        <th>${_('Last Modified')}</th>
+      </tr>
+    </thead>
+    <tbody>
+      <%!
+        from beeswax import models
+      %>
+      % for design in page.object_list:
+        <%
+          may_edit = user == design.owner
+        %>
+      <tr>
+        <td data-row-selector-exclude="true">
+          <div class="hueCheckbox savedCheck"
+            % if may_edit:
+              data-edit-url="${ url(app_name + ':execute_query', design_id=design.id) }"
+              data-delete-url="${ url(app_name + ':delete_design', design_id=design.id) }"
+              data-history-url="${ url(app_name + ':list_query_history') }?design_id=${design.id}"
+            % endif
+            data-clone-url="${ url(app_name + ':clone_design', design_id=design.id) }" data-row-selector-exclude="true"></div>
+        </td>
+        <td>
+        % if may_edit:
+          <a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
+        % else:
+          ${design.name}
+        % endif
+        </td>
+        <td>
+        % if design.desc:
+          ${design.desc}
+        % endif
+        </td>
+        <td>${design.owner.username}</td>
+        <td>
+          ${_('Query')}
+        </td>
+        <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
+      </tr>
+      % endfor
+    </tbody>
+  </table>
+  ${comps.pagination(page)}
 </div>
 
 <div id="deleteQuery" class="modal hide fade">
-    <form id="deleteQueryForm" action="" method="POST">
-        <div class="modal-header">
-            <a href="#" class="close" data-dismiss="modal">&times;</a>
-            <h3 id="deleteQueryMessage">${_('Confirm action')}</h3>
-        </div>
-        <div class="modal-footer">
-            <input type="button" class="btn" data-dismiss="modal" value="${_('Cancel')}" />
-            <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
-        </div>
-    </form>
+  <form id="deleteQueryForm" action="" method="POST">
+    <div class="modal-header">
+      <a href="#" class="close" data-dismiss="modal">&times;</a>
+      <h3 id="deleteQueryMessage">${_('Confirm action')}</h3>
+    </div>
+    <div class="modal-footer">
+      <input type="button" class="btn" data-dismiss="modal" value="${_('Cancel')}" />
+      <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
+    </div>
+  </form>
 </div>
 
 <script type="text/javascript" charset="utf-8">
-    $(document).ready(function(){
-        $(".datatables").dataTable({
-            "bPaginate": false,
-            "bLengthChange": false,
-            "bInfo": false,
-            "bFilter": false,
-            "aaSorting": [[0, 'asc']],
-            "aoColumns": [
-                null,
-                null,
-                null,
-                null,
-                { "sSortDataType": "dom-sort-value", "sType": "numeric" },
-                { "bSortable": false }
-            ],
-            "oLanguage": {
-                "sEmptyTable": "${_('No data available')}",
-                "sZeroRecords": "${_('No matching records')}",
-            }
-        });
+  $(document).ready(function () {
+    var savedQueries = $(".datatables").dataTable({
+      "sDom":"<'row'r>t<'row'<'span8'i><''p>>",
+      "bPaginate":false,
+      "bLengthChange":false,
+      "bInfo":false,
+      "aaSorting":[
+        [1, 'asc']
+      ],
+      "aoColumns":[
+        {"bSortable":false, "sWidth":"1%" },
+        null,
+        null,
+        null,
+        null,
+        { "sSortDataType":"dom-sort-value", "sType":"numeric" }
+      ],
+      "oLanguage":{
+        "sEmptyTable":"${_('No data available')}",
+        "sZeroRecords":"${_('No matching records')}",
+      }
+    });
 
-        $(".confirmationModal").live("click", function(){
-            $.getJSON($(this).attr("data-confirmation-url"), function(data){
-                $("#deleteQueryForm").attr("action", data.url);
-                $("#deleteQueryMessage").text(data.title);
-            });
-            $("#deleteQuery").modal("show");
-        });
+    $("#filterInput").keyup(function () {
+      savedQueries.fnFilter($(this).val());
+    });
+
+    $(".selectAll").click(function () {
+      if ($(this).attr("checked")) {
+        $(this).removeAttr("checked").removeClass("icon-ok");
+        $("." + $(this).data("selectables")).removeClass("icon-ok").removeAttr("checked");
+      }
+      else {
+        $(this).attr("checked", "checked").addClass("icon-ok");
+        $("." + $(this).data("selectables")).addClass("icon-ok").attr("checked", "checked");
+      }
+      toggleActions();
+    });
+
+    $(".savedCheck").click(function () {
+      if ($(this).attr("checked")) {
+        $(this).removeClass("icon-ok").removeAttr("checked");
+      }
+      else {
+        $(this).addClass("icon-ok").attr("checked", "checked");
+      }
+      $(".selectAll").removeAttr("checked").removeClass("icon-ok");
+      toggleActions();
+    });
+
+    function toggleActions() {
+      $(".toolbarBtn").attr("disabled", "disabled");
+      var selector = $(".hueCheckbox[checked='checked']");
+      if (selector.length == 1) {
+        if (selector.data("edit-url")) {
+          $("#editBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("edit-url");
+          });
+        }
+        if (selector.data("clone-url")) {
+          $("#cloneBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("clone-url")
+          });
+        }
+        if (selector.data("history-url")) {
+          $("#historyBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("history-url")
+          });
+        }
+        if (selector.data("delete-url")) {
+          $("#deleteBtn").removeAttr("disabled").data("confirmation-url", selector.data("delete-url"));
+        }
+      }
+    }
 
-        $("a[data-row-selector='true']").jHueRowSelector();
+    $("#deleteBtn").click(function () {
+      if ($(this).data("confirmation-url")) {
+        $.getJSON($(this).data("confirmation-url"), function (data) {
+          $("#deleteQueryForm").attr("action", data.url);
+          $("#deleteQueryMessage").text(data.title);
+        });
+        $("#deleteQuery").modal("show");
+      }
     });
+
+    $("a[data-row-selector='true']").jHueRowSelector();
+  });
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 244 - 194
apps/beeswax/src/beeswax/templates/my_queries.mako

@@ -19,6 +19,7 @@ from django.template.defaultfilters import timesince
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+<%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%!  from beeswax.views import collapse_whitespace %>
@@ -33,224 +34,273 @@ ${layout.menubar(section='my queries')}
 </style>
 
 <div class="container-fluid">
-    <h1>${_('My Queries')}</h1>
+  <h1>${_('My Queries')}</h1>
 
-    <div class="well hueWell">
-        <div class="btn-group pull-right">
-            <a class="btn" href="/beeswax/">${_('Create New Query')}</a>
-        </div>
+  <%actionbar:render>
+    <%def name="actions()">
+      <button id="viewBtn" class="btn toolbarBtn" title="${_('View the result of the selected')}" disabled="disabled"><i class="icon-eye-open"></i> ${_('View result')}</button>
+      <button id="editBtn" class="btn toolbarBtn" title="${_('Edit the selected query')}" disabled="disabled"><i class="icon-edit"></i> ${_('Edit')}</button>
+      <button id="cloneBtn" class="btn toolbarBtn" title="${_('Clone the selected query')}" disabled="disabled"><i class="icon-retweet"></i> ${_('Clone')}</button>
+      <button id="historyBtn" class="btn toolbarBtn" title="${_('View the usage history of the selected query')}" disabled="disabled"><i class="icon-tasks"></i> ${_('Usage history')}</button>
+      <button id="deleteBtn" class="btn toolbarBtn" title="${_('Delete the selected queries')}" disabled="disabled"><i class="icon-trash"></i>  ${_('Delete')}</button>
+    </%def>
 
-        <form class="form-search">
-            ${_('Filter:')} <input type="text" placeholder="${_('Search for name, description, etc...')}" class="input-xlarge search-query" id="filterInput">
-        </form>
-    </div>
-
-    <ul class="nav nav-tabs">
-        <li class="active"><a href="#recentSavedQueries" data-toggle="tab">${_('Recent Saved Queries')} &nbsp;<span id="recentSavedQueriesFilterCnt" class="badge badge-info hide"></span></a></li>
-        <li><a href="#recentRunQueries" data-toggle="tab">${_('Recent Run Queries')}  &nbsp;<span id="recentRunQueriesFilterCnt" class="badge badge-info hide"></span></a></li>
-    </ul>
+    <%def name="creation()">
+      <a class="btn" href="${ url(app_name + ':execute_query') }"><i class="icon-plus-sign"></i> ${_('Create New Query')}</a>
+    </%def>
+  </%actionbar:render>
 
-    <div class="tab-content">
-            <div class="active tab-pane" id="recentSavedQueries">
+  <ul class="nav nav-tabs">
+    <li class="active"><a href="#recentSavedQueries" data-toggle="tab">${_('Recent Saved Queries')} &nbsp;<span id="recentSavedQueriesFilterCnt" class="badge badge-info"></span></a></li>
+    <li><a href="#recentRunQueries" data-toggle="tab">${_('Recent Run Queries')}  &nbsp;<span id="recentRunQueriesFilterCnt" class="badge badge-info"></span></a></li>
+  </ul>
 
-                 <table id="recentSavedQueriesTable" class="table table-striped table-condensed datatables">
-                      <thead>
-                        <tr>
-                          <th>${_('Name')}</th>
-                          <th>${_('Desc')}</th>
-                          <th>${_('Type')}</th>
-                          <th>${_('Last Modified')}</th>
-                          <th></th>
-                        </tr>
-                      </thead>
-                      <tbody>
-                      <%!
-                        from beeswax import models
-                      %>
-                      % for design in q_page.object_list:
-                        <tr>
-                          <td>
-                            <a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
-                          </td>
-                          <td>
-                            % if design.desc:
-                             <p>${design.desc}</p>
-                            % endif
-                          </td>
-                          <td>
-                            ${_('Query')}
-                          </td>
-                          <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
-                          <td>
-                            <div class="btn-group">
-                                <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
-                                  ${_('Options')}
-                                  <span class="caret"></span>
-                                </a>
-                                <ul class="dropdown-menu">
-                                  <li><a href="${ url(app_name + ':execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
-                                  <li><a href="javascript:void(0)" data-confirmation-url="${ url(app_name + ':delete_design', design_id=design.id) }" title="${_('Delete this query.')}" class="contextItem confirmationModal">${_('Delete')}</a></li>
-                                  <li><a href="${ url(app_name + ':list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
-                                  <li><a href="${ url(app_name + ':clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
-                                </ul>
-                            </div>
-                          </td>
-                        </tr>
-                      % endfor
-                      </tbody>
-                    </table>
-                    % if q_page.number != q_page.num_pages():
-                      <a href="${ url(app_name + ':list_designs') }?user=${request.user.username|u}" >${_('View all my queries')} &raquo;</a>
-                    % endif
-            </div>
+  <div class="tab-content">
+    <div class="active tab-pane" id="recentSavedQueries">
+      <table id="recentSavedQueriesTable" class="table table-striped table-condensed datatables">
+        <thead>
+          <tr>
+            <th width="1%"><div class="hueCheckbox selectAll" data-selectables="savedCheck"></div></th>
+            <th>${_('Name')}</th>
+            <th>${_('Desc')}</th>
+            <th>${_('Type')}</th>
+            <th>${_('Last Modified')}</th>
+          </tr>
+        </thead>
+        <tbody>
+        <%!
+          from beeswax import models
+        %>
+        % for design in q_page.object_list:
+          <tr>
+            <td data-row-selector-exclude="true">
+              <div class="hueCheckbox savedCheck" data-edit-url="${ url(app_name + ':execute_query', design_id=design.id) }" data-delete-url="${ url(app_name + ':delete_design', design_id=design.id) }"
+                   data-history-url="${ url(app_name + ':list_query_history') }?design_id=${design.id}" data-clone-url="${ url(app_name + ':clone_design', design_id=design.id) }" data-row-selector-exclude="true"></div>
+            </td>
+            <td>
+              <a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
+            </td>
+            <td>
+              % if design.desc:
+              ${design.desc}
+              % endif
+            </td>
+            <td>
+              ${_('Query')}
+            </td>
+            <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
+          </tr>
+        % endfor
+        </tbody>
+      </table>
+      % if q_page.number != q_page.num_pages():
+        <a href="${ url(app_name + ':list_designs') }?user=${request.user.username|u}" >${_('View all my queries')} &raquo;</a>
+      % endif
+    </div>
 
-            <div class="tab-pane" id="recentRunQueries">
-                <table id="recentRunQueriesTable" class="table table-striped table-condensed datatables">
-                  <thead>
-                    <tr>
-                      <th>${_('Time')}</th>
-                      <th>${_('Name')}</th>
-                      <th>${_('Query')}</th>
-                      <th>${_('State')}</th>
-                      <th>${_('Result')}</th>
-                    </tr>
-                  </thead>
-                  <tbody>
-                  <%!
-                    from beeswax import models, views
-                  %>
-                  % for query in h_page.object_list:
-                    <%
-                    qcontext = ""
-                    try:
-                      design = query.design
-                      qcontext = views.make_query_context('design', design.id)
-                    except:
-                      pass
-                    %>
-                    <tr>
-                      <td data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
-                      <td><a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a></td>
-                      <td>
-                        <p>
-                          % if len(query.query) > 100:
-                            <code>${collapse_whitespace(query.query[:100])}...</code>
-                          % else:
-                            <code>${ collapse_whitespace(query.query) }</code>
-                          % endif
-                        </p>
-                      </td>
-                      <td>${models.QueryHistory.STATE[query.last_state]}</td>
-                      <td>
-                        % if qcontext and query.last_state != models.QueryHistory.STATE.expired.index:
-                          <a href="${ url(app_name + ':watch_query', id=query.id) }?context=${qcontext|u}">${_('View')}</a>
-                        % else:
-                          ~
-                        % endif
-                      </td>
-                    </tr>
-                  % endfor
-                  </tbody>
-                </table>
-                % if h_page.number != h_page.num_pages():
-                  <a href="${ url(app_name + ':list_query_history') }">${_('View my entire query history')} &raquo;</a>
+    <div class="tab-pane" id="recentRunQueries">
+      <table id="recentRunQueriesTable" class="table table-striped table-condensed datatables">
+        <thead>
+          <tr>
+            <th width="1%"><div class="hueCheckbox selectAll" data-selectables="runCheck"></div></th>
+            <th>${_('Time')}</th>
+            <th>${_('Name')}</th>
+            <th>${_('Query')}</th>
+            <th>${_('State')}</th>
+          </tr>
+        </thead>
+        <tbody>
+        <%!
+          from beeswax import models, views
+        %>
+        % for query in h_page.object_list:
+        <%
+          qcontext = ""
+          try:
+            design = query.design
+            qcontext = views.make_query_context('design', design.id)
+          except:
+            pass
+        %>
+          <tr>
+            <td width="1%" data-row-selector-exclude="true">
+              <div class="hueCheckbox runCheck" data-edit-url="${ url(app_name + ':execute_query', design_id=design.id) }"
+                % if qcontext and query.last_state != models.QueryHistory.STATE.expired.index:
+                  data-view-url="${ url(app_name + ':watch_query', id=query.id) }?context=${qcontext|u}"
                 % endif
-            </div>
+                data-row-selector-exclude="true"></div>
+            </td>
+            <td width="10%" data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
+            <td width="20%"><a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a></td>
+            <td width="60%">
+              % if len(query.query) > 100:
+              <code>${collapse_whitespace(query.query[:100])}...</code>
+              % else:
+              <code>${ collapse_whitespace(query.query) }</code>
+              % endif
+            </td>
+            <td width="9%">${models.QueryHistory.STATE[query.last_state]}</td>
+          </tr>
+        % endfor
+        </tbody>
+      </table>
+      % if h_page.number != h_page.num_pages():
+        <a href="${ url(app_name + ':list_query_history') }">${_('View my entire query history')} &raquo;</a>
+      % endif
     </div>
+  </div>
 </div>
 
 <div id="deleteQuery" class="modal hide fade">
-    <form id="deleteQueryForm" action="" method="POST">
+  <form id="deleteQueryForm" action="" method="POST">
     <div class="modal-header">
-        <a href="#" class="close" data-dismiss="modal">&times;</a>
-        <h3 id="deleteQueryMessage">${_('Confirm action')}</h3>
+      <a href="#" class="close" data-dismiss="modal">&times;</a>
+      <h3 id="deleteQueryMessage">${_('Confirm action')}</h3>
     </div>
     <div class="modal-footer">
-        <input type="button" class="btn" data-dismiss="modal" value="${_('Cancel')}" />
-        <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
+      <input type="button" class="btn" data-dismiss="modal" value="${_('Cancel')}"/>
+      <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
     </div>
-    </form>
+  </form>
 </div>
 
 <script type="text/javascript" charset="utf-8">
-    $(document).ready(function(){
-        var recentSavedQueries = $("#recentSavedQueriesTable").dataTable({
-            "sDom": "<'row'r>t<'row'<'span8'i><''p>>",
-            "bPaginate": false,
-            "bLengthChange": false,
-            "bInfo": false,
-            "aaSorting": [[3, 'desc']],
-            "aoColumns": [
-                null,
-                null,
-                null,
-                { "sSortDataType": "dom-sort-value", "sType": "numeric" },
-                { "bSortable": false }
-            ],
-            "oLanguage": {
-	            "sEmptyTable": "${_('No data available')}",
-	            "sZeroRecords": "${_('No matching records')}"
-	        }
-        });
+  $(document).ready(function () {
 
-        var recentRunQueries = $("#recentRunQueriesTable").dataTable({
-            "sDom": "<'row'r>t<'row'<'span8'i><''p>>",
-            "bPaginate": false,
-            "bLengthChange": false,
-            "bInfo": false,
-            "aaSorting": [[0, 'desc']],
-            "aoColumns": [
-                { "sSortDataType": "dom-sort-value", "sType": "numeric" },
-                null,
-                null,
-                null,
-                { "bSortable": false }
-            ],
-            "oLanguage": {
-                "sEmptyTable": "${_('No data available')}",
-                "sZeroRecords": "${_('No matching records')}"
-            }
-        });
+    updateQueryCounters();
 
+    var recentSavedQueries = $("#recentSavedQueriesTable").dataTable({
+      "sDom":"<'row'r>t<'row'<'span8'i><''p>>",
+      "bPaginate":false,
+      "bLengthChange":false,
+      "bInfo":false,
+      "aaSorting":[
+        [4, 'desc']
+      ],
+      "aoColumns":[
+        {"bSortable":false, "sWidth":"1%" },
+        {"sWidth":"30%"},
+        {"sWidth":"40%"},
+        {"sWidth":"9%"},
+        { "sSortDataType":"dom-sort-value", "sType":"numeric", "sWidth":"20%" }
+      ],
+      "oLanguage":{
+        "sEmptyTable":"${_('No data available')}",
+        "sZeroRecords":"${_('No matching records')}"
+      }
+    });
 
-        $("#filterInput").keyup(function() {
-            recentSavedQueries.fnFilter($(this).val());
-            recentRunQueries.fnFilter($(this).val());
-            if ($.trim($(this).val()) != ""){
-                var recentSavedQueriesCnt = $("#recentSavedQueriesTable tbody tr").length;
-                var isRecentSavedQueriesEmpty = ($("#recentSavedQueries tbody tr td.dataTables_empty").length == 1)
-                if (recentSavedQueriesCnt > 0 && !isRecentSavedQueriesEmpty){
-                    $("#recentSavedQueriesFilterCnt").text(recentSavedQueriesCnt).show();
-                }
-                else {
-                    $("#recentSavedQueriesFilterCnt").hide().text("");
-                }
+    var recentRunQueries = $("#recentRunQueriesTable").dataTable({
+      "sDom":"<'row'r>t<'row'<'span8'i><''p>>",
+      "bPaginate":false,
+      "bLengthChange":false,
+      "bInfo":false,
+      "aaSorting":[
+        [1, 'desc']
+      ],
+      "aoColumns":[
+        {"bSortable":false},
+        { "sSortDataType":"dom-sort-value", "sType":"numeric" },
+        null,
+        null,
+        null
+      ],
+      "oLanguage":{
+        "sEmptyTable":"${_('No data available')}",
+        "sZeroRecords":"${_('No matching records')}"
+      }
+    });
 
-                var recentRunQueriesCnt = $("#recentRunQueriesTable tbody tr").length;
-                var isRecentRunQueriesEmpty = ($("#recentRunQueriesTable tbody tr td.dataTables_empty").length == 1)
-                if (recentRunQueriesCnt > 0 && !isRecentRunQueriesEmpty){
-                    $("#recentRunQueriesFilterCnt").text(recentRunQueriesCnt).show();
-                }
-                else {
-                    $("#recentRunQueriesFilterCnt").hide().text("");
-                }
-            }
-            else {
-                $("#recentSavedQueriesFilterCnt").hide().text("");
-                $("#recentRunQueriesFilterCnt").hide().text("");
-            }
-        });
+    $("#filterInput").keyup(function () {
+      recentSavedQueries.fnFilter($(this).val());
+      recentRunQueries.fnFilter($(this).val());
+      updateQueryCounters();
+    });
 
-        $(".confirmationModal").live("click", function(){
-            $.getJSON($(this).attr("data-confirmation-url"), function(data){
-                $("#deleteQueryForm").attr("action", data.url);
-                $("#deleteQueryMessage").text(data.title);
-            });
-            $("#deleteQuery").modal("show");
-        });
+    $(".selectAll").click(function () {
+      if ($(this).attr("checked")) {
+        $(this).removeAttr("checked").removeClass("icon-ok");
+        $("." + $(this).data("selectables")).removeClass("icon-ok").removeAttr("checked");
+      }
+      else {
+        $(this).attr("checked", "checked").addClass("icon-ok");
+        $("." + $(this).data("selectables")).addClass("icon-ok").attr("checked", "checked");
+      }
+      toggleActions();
+    });
 
-        $("a[data-row-selector='true']").jHueRowSelector();
+    $(".savedCheck").click(function () {
+      $(".runCheck").removeClass("icon-ok").removeAttr("checked");
     });
+
+    $(".runCheck").click(function () {
+      $(".savedCheck").removeClass("icon-ok").removeAttr("checked");
+    });
+
+    $(".savedCheck, .runCheck").click(function () {
+      if ($(this).attr("checked")) {
+        $(this).removeClass("icon-ok").removeAttr("checked");
+      }
+      else {
+        $(this).addClass("icon-ok").attr("checked", "checked");
+      }
+      $(".selectAll").removeAttr("checked").removeClass("icon-ok");
+      toggleActions();
+    });
+
+    function toggleActions() {
+      $(".toolbarBtn").attr("disabled", "disabled");
+      var selector = $(".hueCheckbox[checked='checked']");
+      if (selector.length == 1) {
+        if (selector.data("view-url")) {
+          $("#viewBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("view-url")
+          });
+        }
+        if (selector.data("edit-url")) {
+          $("#editBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("edit-url");
+          });
+        }
+        if (selector.data("clone-url")) {
+          $("#cloneBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("clone-url")
+          });
+        }
+        if (selector.data("history-url")) {
+          $("#historyBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("history-url")
+          });
+        }
+        if (selector.data("delete-url")) {
+          $("#deleteBtn").removeAttr("disabled").data("confirmation-url", selector.data("delete-url"));
+        }
+      }
+    }
+
+    function updateQueryCounters() {
+      $("#recentSavedQueriesFilterCnt").text($("#recentSavedQueriesTable tbody tr").length);
+      $("#recentRunQueriesFilterCnt").text($("#recentRunQueriesTable tbody tr").length);
+      if ($("#recentSavedQueries tbody tr td.dataTables_empty").length == 1) {
+        $("#recentSavedQueriesFilterCnt").text("0");
+      }
+      if ($("#recentRunQueries tbody tr td.dataTables_empty").length == 1) {
+        $("#recentRunQueriesFilterCnt").text("0");
+      }
+    }
+
+    $("#deleteBtn").click(function () {
+      if ($(this).data("confirmation-url")) {
+        $.getJSON($(this).data("confirmation-url"), function (data) {
+          $("#deleteQueryForm").attr("action", data.url);
+          $("#deleteQueryMessage").text(data.title);
+        });
+        $("#deleteQuery").modal("show");
+      }
+    });
+
+    $("a[data-row-selector='true']").jHueRowSelector();
+  });
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 140 - 58
apps/beeswax/src/beeswax/templates/show_tables.mako

@@ -17,13 +17,12 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
-
+<%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="layout" file="layout.mako" />
 
 ${ commonheader(_('Tables'), app_name, user, '100px') | n,unicode }
 ${layout.menubar(section='tables')}
 
-
 <div class="container-fluid">
     <h1>${_('Tables')}</h1>
     <div class="row-fluid">
@@ -52,21 +51,33 @@ ${layout.menubar(section='tables')}
             </div>
         </div>
         <div class="span9">
+          <%actionbar:render>
+            <%def name="actions()">
+                <button id="viewBtn" class="btn toolbarBtn" title="${_('Browse the selected table')}" disabled="disabled"><i class="icon-eye-open"></i> ${_('View')}</button>
+                <button id="browseBtn" class="btn toolbarBtn" title="${_('Browse the selected table')}" disabled="disabled"><i class="icon-list"></i> ${_('Browse Data')}</button>
+                <button id="dropBtn" class="btn toolbarBtn" title="${_('Delete the selected tables')}" disabled="disabled"><i class="icon-trash"></i>  ${_('Drop')}</button>
+            </%def>
+          </%actionbar:render>
             <table class="table table-condensed table-striped datatables">
                 <thead>
-                    <tr>
-                        <th>${_('Table Name')}</th>
-                        <th>&nbsp;</th>
-                    </tr>
+                  <tr>
+                    <th width="1%"><div class="hueCheckbox selectAll" data-selectables="tableCheck"></div></th>
+                    <th>${_('Table Name')}</th>
+                  </tr>
                 </thead>
                 <tbody>
                 % for table in tables:
-                    <tr>
-                        <td>
-                            <a href="${ url(app_name + ':describe_table', database=database, table=table) }" data-row-selector="true">${ table }</a>
-                        </td>
-                        <td><a href="${ url(app_name + ':read_table', database=database, table=table) }" class="btn">${_('Browse Data')}</a></td>
-                    </tr>
+                  <tr>
+                    <td data-row-selector-exclude="true" width="1%">
+                      <div class="hueCheckbox tableCheck"
+                           data-view-url="${ url(app_name + ':describe_table', database=database, table=table) }"
+                           data-browse-url="${ url(app_name + ':read_table', database=database, table=table) }"
+                           data-drop-url="${ url(app_name + ':drop_table', database=database, table=table) }" data-row-selector-exclude="true"></div>
+                    </td>
+                    <td>
+                      <a href="${ url(app_name + ':describe_table', database=database, table=table) }" data-row-selector="true">${ table }</a>
+                    </td>
+                  </tr>
                 % endfor
                 </tbody>
             </table>
@@ -78,70 +89,141 @@ ${layout.menubar(section='tables')}
 
 % if not examples_installed:
 <div id="installSamples" class="modal hide fade">
-    <div class="modal-header">
-        <a href="#" class="close" data-dismiss="modal">&times;</a>
-        <h3>${_('Install samples')}</h3>
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <h3>${_('Install samples')}</h3>
+  </div>
+  <div class="modal-body">
+    <div id="installSamplesMessage">
+
     </div>
-    <div class="modal-body">
-      <div id="installSamplesMessage">
+  </div>
+  <div class="modal-footer">
+    <a href="#" class="btn" data-dismiss="modal">${_('Cancel')}</a>
+    <a href="#" id="installSamplesBtn" class="btn btn-primary">${_('Yes, install samples')}</a>
+  </div>
+</div>
+% endif
 
-      </div>
+<div id="dropTable" class="modal hide fade">
+  <form id="dropTableForm" action="" method="POST">
+    <div class="modal-header">
+      <a href="#" class="close" data-dismiss="modal">&times;</a>
+      <h3 id="dropTableMessage">${_('Confirm action')}</h3>
     </div>
     <div class="modal-footer">
-        <a href="#" class="btn" data-dismiss="modal">${_('Cancel')}</a>
-        <a href="#" id="installSamplesBtn" class="btn btn-primary">${_('Yes, install samples')}</a>
+      <input type="button" class="btn" data-dismiss="modal" value="${_('Cancel')}" />
+      <input type="submit" class="btn btn-danger" value="${_('Yes')}"/>
     </div>
+  </form>
 </div>
-% endif
 
 <script src="/static/ext/js/jquery/plugins/jquery.cookie.js"></script>
 
 <script type="text/javascript" charset="utf-8">
-    $(document).ready(function(){
-        $(".datatables").dataTable({
-            "bPaginate": false,
-            "bLengthChange": false,
-            "bInfo": false,
-            "bFilter": false,
-            "aoColumns": [
-                null,
-                { "sWidth": "130px", "bSortable" : false }
-             ],
-            "oLanguage": {
-                "sEmptyTable": "${_('No data available')}",
-                "sZeroRecords": "${_('No matching records')}",
-            }
-        });
+  $(document).ready(function () {
+    var tables = $(".datatables").dataTable({
+      "sDom":"<'row'r>t<'row'<'span8'i><''p>>",
+      "bPaginate":false,
+      "bLengthChange":false,
+      "bInfo":false,
+      "bFilter":true,
+      "aoColumns":[
+        {"bSortable":false, "sWidth":"1%" },
+        null
+      ],
+      "oLanguage":{
+        "sEmptyTable":"${_('No data available')}",
+        "sZeroRecords":"${_('No matching records')}",
+      }
+    });
+
+    $("#filterInput").keyup(function () {
+      tables.fnFilter($(this).val());
+    });
 
-        $("a[data-row-selector='true']").jHueRowSelector();
+    $("a[data-row-selector='true']").jHueRowSelector();
 
-        $("#id_database").change(function(){
-             $.cookie("hueBeeswaxLastDatabase", $(this).val(), {expires: 90});
-             $('#db_form').submit();
-        });
+    $("#id_database").change(function () {
+      $.cookie("hueBeeswaxLastDatabase", $(this).val(), {expires:90});
+      $('#db_form').submit();
+    });
 
-        % if not examples_installed:
-        $.getJSON("${ url(app_name + ':install_examples') }", function(data){
-            $("#installSamplesMessage").text(data.title);
+    % if not examples_installed:
+        $.getJSON("${ url(app_name + ':install_examples') }", function (data) {
+          $("#installSamplesMessage").text(data.title);
         });
 
-        $("#installSamplesBtn").click(function(){
-            $.post(
-                "${ url(app_name + ':install_examples') }",
-                { submit: "Submit" },
-                function(result){
-                    if (result.creationSucceeded){
-                        window.location.href = "${ url(app_name + ':show_tables') }";
-                    }
-                    else {
-                        var message = "${_('There was an error processing your request:')} " + result.message;
-                        $("#installSamplesMessage").addClass("alert").addClass("alert-error").text(message);
-                    }
+        $("#installSamplesBtn").click(function () {
+          $.post(
+              "${ url(app_name + ':install_examples') }",
+              { submit:"Submit" },
+              function (result) {
+                if (result.creationSucceeded) {
+                  window.location.href = "${ url(app_name + ':show_tables') }";
                 }
-            );
+                else {
+                  var message = "${_('There was an error processing your request:')} " + result.message;
+                  $("#installSamplesMessage").addClass("alert").addClass("alert-error").text(message);
+                }
+              }
+          );
+        });
+    % endif
+
+    $(".selectAll").click(function () {
+      if ($(this).attr("checked")) {
+        $(this).removeAttr("checked").removeClass("icon-ok");
+        $("." + $(this).data("selectables")).removeClass("icon-ok").removeAttr("checked");
+      }
+      else {
+        $(this).attr("checked", "checked").addClass("icon-ok");
+        $("." + $(this).data("selectables")).addClass("icon-ok").attr("checked", "checked");
+      }
+      toggleActions();
+    });
+
+    $(".tableCheck").click(function () {
+      if ($(this).attr("checked")) {
+        $(this).removeClass("icon-ok").removeAttr("checked");
+      }
+      else {
+        $(this).addClass("icon-ok").attr("checked", "checked");
+      }
+      $(".selectAll").removeAttr("checked").removeClass("icon-ok");
+      toggleActions();
+    });
+
+    function toggleActions() {
+      $(".toolbarBtn").attr("disabled", "disabled");
+      var selector = $(".hueCheckbox[checked='checked']");
+      if (selector.length == 1) {
+        if (selector.data("view-url")) {
+          $("#viewBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("view-url");
+          });
+        }
+        if (selector.data("browse-url")) {
+          $("#browseBtn").removeAttr("disabled").click(function () {
+            location.href = selector.data("browse-url")
+          });
+        }
+        if (selector.data("drop-url")) {
+          $("#dropBtn").removeAttr("disabled").data("confirmation-url", selector.data("drop-url"));
+        }
+      }
+    }
+
+    $("#dropBtn").click(function () {
+      if ($(this).data("confirmation-url")) {
+        $.getJSON($(this).data("confirmation-url"), function (data) {
+          $("#dropTableForm").attr("action", data.url);
+          $("#dropTableMessage").text(data.title);
         });
-        % endif
+        $("#dropTable").modal("show");
+      }
     });
+  });
 </script>
 
 ${ commonfooter(messages) | n,unicode }