瀏覽代碼

HUE-62. Allow HtmlTable context menu for multiple rows

* moved ccs-form_submit linker up to global level
* added support for disabling/enabling a context menu
* added support for nested context menus
* added methods to register/unregister keyboards on JFrame from a behavior filter
* added plugin to manage HtmlTable keyboard instances properly
* added new global linker for submitting forms with an anchor tag (moved up from Beeswax)
Aaron Newton 15 年之前
父節點
當前提交
68185c020d

+ 26 - 26
apps/beeswax/src/beeswax/templates/choose_delimiter.mako

@@ -29,10 +29,10 @@ ${wrappers.head('Choose a Delimiter')}
     <div class="bw-choose-delimiter">
     <div class="bw-choose-delimiter">
         <div class="ccs-bc-section">
         <div class="ccs-bc-section">
           <form action ="${action}" method="POST">
           <form action ="${action}" method="POST">
-	    <div class="ccs-hidden">
-	      ${util.render_form(file_form)}
-	      ${comps.field(delim_form['file_type'])}
-	    </div>
+            <div class="ccs-hidden">
+              ${util.render_form(file_form)}
+              ${comps.field(delim_form['file_type'])}
+            </div>
             <a name="step2"></a>
             <a name="step2"></a>
             <dt>Step 2: Choose A Delimiter</dt>
             <dt>Step 2: Choose A Delimiter</dt>
             <dd>
             <dd>
@@ -43,45 +43,45 @@ ${wrappers.head('Choose a Delimiter')}
                       <p class="delimiter_confirmation">Beeswax has determined that this file is delimited by ${delim_readable}.  Is this correct?</p>
                       <p class="delimiter_confirmation">Beeswax has determined that this file is delimited by ${delim_readable}.  Is this correct?</p>
                       <label>
                       <label>
                         Yes
                         Yes
-			## TODO(marcus)  The YES button does nothing
+                        ## TODO(marcus)  The YES button does nothing
                         <input type="submit" name="submit_delim" value="Select this Delimiter" class="ccs-hidden"/>
                         <input type="submit" name="submit_delim" value="Select this Delimiter" class="ccs-hidden"/>
                       </label>
                       </label>
                       <label>
                       <label>
                         No
                         No
-			## TODO(marcus)  The NO + preview jump out of jframe
+                        ## TODO(marcus)  The NO + preview jump out of jframe
                         <input type="button" name="No" class="ccs-hidden"/>
                         <input type="button" name="No" class="ccs-hidden"/>
                       </label>
                       </label>
                     </div>
                     </div>
                   % endif
                   % endif
                   <div class="bw-select_delim" class="ccs-hidden">
                   <div class="bw-select_delim" class="ccs-hidden">
-		    ${comps.field(delim_form["delimiter"], render_default=True, help=r'Enter the column delimiter.  Must be a single character.  Use syntax like "\001" or "\t" for special characters.', klass="ccs-select-with-other")}
+                    ${comps.field(delim_form["delimiter"], render_default=True, help=r'Enter the column delimiter.  Must be a single character.  Use syntax like "\001" or "\t" for special characters.', klass="ccs-select-with-other")}
 
 
-		    <input class="ccs-hidden" type="submit" value="Preview" name="submit_preview"/>
-		    <input class="ccs-hidden" type="submit" value="Select this Delimiter" name="submit_delim"/>
+                    <input class="ccs-hidden" type="submit" value="Preview" name="submit_preview"/>
+                    <input class="ccs-hidden" type="submit" value="Select this Delimiter" name="submit_delim"/>
                   </div>
                   </div>
                   <div class="delimiter_preview_holder">
                   <div class="delimiter_preview_holder">
                     <table class="delimiter_preview">
                     <table class="delimiter_preview">
-		      <thead>
-			<tr>
-			  % for i in range(n_cols):
-			    <th>col_${i+1}</th>
-			  % endfor
-			</tr>
-		      </thead>
-		      <tbody>
-			% for row in fields_list:
-			  <tr>
-			    % for val in row:
-			    <td>${val}</td>
-			    % endfor
-			  </tr>
-			% endfor
-		      </tbody>
+                      <thead>
+                        <tr>
+                          % for i in range(n_cols):
+                            <th>col_${i+1}</th>
+                          % endfor
+                        </tr>
+                      </thead>
+                      <tbody>
+                        % for row in fields_list:
+                          <tr>
+                            % for val in row:
+                            <td>${val}</td>
+                            % endfor
+                          </tr>
+                        % endfor
+                      </tbody>
                     </table>
                     </table>
                   </div>
                   </div>
                 </dt>
                 </dt>
               </dl>
               </dl>
-	      <a class="ccs-form_submit ccs-visible ccs-multipart-next" data-extraData="{'submit_delim': 'Step 3: Define Your Columns'}">
+              <a class="ccs-submit_form ccs-visible ccs-multipart-next" data-extra-data="{'submit_delim': 'Step 3: Define Your Columns'}">
               Step 3: Define Your Columns&raquo;
               Step 3: Define Your Columns&raquo;
               </a>
               </a>
               <input type="submit" name="submit_delim" value="Step 3: Define Your Columns" class="ccs-hidden"/>
               <input type="submit" name="submit_delim" value="Step 3: Define Your Columns" class="ccs-hidden"/>

+ 2 - 2
apps/beeswax/src/beeswax/templates/choose_file.mako

@@ -63,10 +63,10 @@ ${wrappers.head('Choose a File')}
                   )}
                   )}
                 </div>
                 </div>
                 ## TODO(marcus): Button style?
                 ## TODO(marcus): Button style?
-                <a class="ccs-multipart-next ccs-form_submit ccs-visible" style="display:none" data-extraData="{'submit_file' : 'Step 2: Choose Your Delimiter'}">
+                <a class="ccs-multipart-next ccs-submit_form ccs-visible" style="display:none" data-extra-data="{'submit_file' : 'Step 2: Choose Your Delimiter'}">
                   Step 2: Choose Your Delimiter&raquo;
                   Step 2: Choose Your Delimiter&raquo;
                 </a>
                 </a>
-                  <input type="submit" name="submit_file" value="Step 2: Choose Your Delimiter" class="ccs-hidden ccs-form_submit ccs-multipart-next ccs-visible"/>
+                  <input type="submit" name="submit_file" value="Step 2: Choose Your Delimiter" class="ccs-hidden ccs-submit_form ccs-multipart-next ccs-visible"/>
               </dl>
               </dl>
             </form>
             </form>
           </dd>
           </dd>

+ 31 - 31
apps/beeswax/src/beeswax/templates/define_columns.mako

@@ -29,15 +29,15 @@ ${wrappers.head('Define Columns')}
   <div class="bw-define-columns">
   <div class="bw-define-columns">
     <form action="${action}" method="POST">
     <form action="${action}" method="POST">
       <div class="ccs-hidden">
       <div class="ccs-hidden">
-	${util.render_form(file_form)}
-	${util.render_form(delim_form)}
-	${str(column_formset.management_form) | n}
+        ${util.render_form(file_form)}
+        ${util.render_form(delim_form)}
+        ${str(column_formset.management_form) | n}
       </div>
       </div>
       <div class="ccs-bc-section">
       <div class="ccs-bc-section">
       <dt> Step 3: Define Your Columns </dt>
       <dt> Step 3: Define Your Columns </dt>
       <%
       <%
-	n_rows = len(fields_list)
-	if n_rows > 2: n_rows = 2
+        n_rows = len(fields_list)
+        if n_rows > 2: n_rows = 2
       %>
       %>
       <dd>
       <dd>
           Beeswax has attempted to determine the types of your columns.  Please check them as you name the columns.
           Beeswax has attempted to determine the types of your columns.  Please check them as you name the columns.
@@ -46,44 +46,44 @@ ${wrappers.head('Define Columns')}
           <dt> </dt>
           <dt> </dt>
           <dd>
           <dd>
             <table class="row_headers ccs-visible" style="display:none">
             <table class="row_headers ccs-visible" style="display:none">
-	      % for i in range(n_rows):
-		<tr><td> Row ${i + 1} </td></tr>
-	      % endfor
+              % for i in range(n_rows):
+                <tr><td> Row ${i + 1} </td></tr>
+              % endfor
               <tr><td>&nbsp;</td></tr>
               <tr><td>&nbsp;</td></tr>
               <tr><td class="input_row_header"> Column Name </td></tr>
               <tr><td class="input_row_header"> Column Name </td></tr>
               <tr><td class="input_row_header"> Type </td></tr>
               <tr><td class="input_row_header"> Type </td></tr>
             </table>
             </table>
             <div class="table_holder">
             <div class="table_holder">
-	      <table class="data_table">
-		% for i, row in enumerate(fields_list[:n_rows]):
-		  <tr>
-		    <td class="ccs-hidden"> Row ${i + 1} </td>
-		    % for val in row:
-		      <td>${val}</td>
-		    % endfor
-		  </tr>
-		% endfor
+              <table class="data_table">
+                % for i, row in enumerate(fields_list[:n_rows]):
+                  <tr>
+                    <td class="ccs-hidden"> Row ${i + 1} </td>
+                    % for val in row:
+                      <td>${val}</td>
+                    % endfor
+                  </tr>
+                % endfor
                 <tr><td colspan="${n_cols}">&nbsp;</td></tr>
                 <tr><td colspan="${n_cols}">&nbsp;</td></tr>
                 <tr>
                 <tr>
-		  % for form in column_formset.forms:
-		    <td>
-		      ${comps.field(form["column_name"],
-			render_default=False,
-			help="Column name",
-		      )}
-		      ${comps.field(form["column_type"],
-			render_default=True,
-			help="Type for this column",
-		      )}
-		      ${str(form["_exists"]) | n}
-		    </td>
-		  %endfor
+                  % for form in column_formset.forms:
+                    <td>
+                      ${comps.field(form["column_name"],
+                        render_default=False,
+                        help="Column name",
+                      )}
+                      ${comps.field(form["column_type"],
+                        render_default=True,
+                        help="Type for this column",
+                      )}
+                      ${str(form["_exists"]) | n}
+                    </td>
+                  %endfor
                 </tr>
                 </tr>
               </table>
               </table>
             </div>
             </div>
           </dd>
           </dd>
           </dl>
           </dl>
-          <a class="ccs-form_submit ccs-visible ccs-multipart-next" data-extraData="{'submit_create': 'Finish Creating Table'}" style="display:none">
+          <a class="ccs-submit_form ccs-visible ccs-multipart-next" data-extra-data="{'submit_create': 'Finish Creating Table'}" style="display:none">
           Finish Creating Table
           Finish Creating Table
           </a>
           </a>
           <input type="submit" name="submit_create" value="Finish Creating Table" class="ccs-hidden"/>
           <input type="submit" name="submit_create" value="Finish Creating Table" class="ccs-hidden"/>

+ 0 - 7
apps/beeswax/static/js/Source/Beeswax/CCS.Beeswax.js

@@ -85,13 +85,6 @@ ART.Sheet.define('splitview.bw-editor', {
 					sections.each(function(section) {
 					sections.each(function(section) {
 						section.setStyle('display', show.contains(section) ? 'block' : 'none');
 						section.setStyle('display', show.contains(section) ? 'block' : 'none');
 					});
 					});
-				},
-				'.ccs-form_submit': function(event, el) {
-				/*
-					submit the form that the element is in.
-					el.getParent('form').formUpdate();
-				*/
-					el.getParent('form').retrieve('form.request').setOptions({extraData: el.getJSONData('extraData')}).send();
 				}
 				}
 			});
 			});
 			this.jframe.addFilters({
 			this.jframe.addFilters({

+ 2 - 2
apps/jframegallery/src/jframegallery/templates/context-menu.html

@@ -25,8 +25,8 @@ limitations under the License.
 			I show a menu if you right click anywhere in me. Or you can click <a class="show-options">this link</a> to show it.
 			I show a menu if you right click anywhere in me. Or you can click <a class="show-options">this link</a> to show it.
 			<ul class="context-menu ccs-hidden">
 			<ul class="context-menu ccs-hidden">
 				<li class="cm-one">Item 1</li>
 				<li class="cm-one">Item 1</li>
-				<li class="cm-two">Item 1</li>
-				<li class="cm-three">Item 1</li>
+				<li class="cm-two">Item 2</li>
+				<li class="cm-three">Item 3</li>
 			</ul>
 			</ul>
 		</div>
 		</div>
 	</body>
 	</body>

+ 126 - 0
apps/jframegallery/src/jframegallery/templates/html-table.multi-select.menu.html

@@ -0,0 +1,126 @@
+{% comment %}
+Licensed to Cloudera, Inc. under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  Cloudera, Inc. licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+{% endcomment %}
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+	<head>
+		<title>HtmlTable Multi-Select Menu</title>
+	</head>
+	<body>
+		<div class="ccs-shared">
+			
+			<p>
+				Right click a row in the table below to get a menu. Select more than one row (hold shift) and right click and get a different menu.
+			</p>
+		
+		<table data-filters="HtmlTable, ContextMenu" class="multiselect selectable sortable" cellpadding="0" cellspacing="0"
+		data-context-menu-actions="[{'events':['contextmenu'], 'menu':'ul.table-context-menu'}]">
+			<ul class="table-context-menu context-menu ccs-hidden">
+				<li class="cm-one">multi-select option 1</li>
+				<li class="cm-two">multi-select option 2</li>
+				<li class="cm-three">multi-select option 3</li>
+			</ul>
+			<thead>
+				<tr>
+					<th>
+								ID
+					</th>
+					<th>
+								TimeZone
+					</th>
+					<th>
+								Name
+					</th>
+					<th>
+								GEO Latitude
+					</th>
+					<th>
+								GEO Longitude
+					</th>
+				</tr>
+			</thead>
+			<tbody>
+				<tr data-filters="ContextMenu" data-context-menu-actions="[{'events':['contextmenu','click:relay(a.show-options)'],'menu':'ul.context-menu'}]">
+					<ul class="context-menu ccs-hidden">
+						<li class="cm-one">Item 1</li>
+						<li class="cm-two">Item 2</li>
+						<li class="cm-three">Item 3</li>
+					</ul>
+					<td>
+						22
+					</td>
+					<td>
+						New York City
+					</td>
+					<td>
+						America/New_York
+					</td>
+					<td>
+						40.7255
+					</td>
+					<td>
+						-73.9983
+					</td>
+				</tr>
+				<tr data-filters="ContextMenu" data-context-menu-actions="[{'events':['contextmenu','click:relay(a.show-options)'],'menu':'ul.context-menu'}]">
+					<ul class="context-menu ccs-hidden">
+						<li class="cm-one">Item 1</li>
+						<li class="cm-two">Item 2</li>
+						<li class="cm-three">Item 3</li>
+					</ul>
+					<td>
+						23
+					</td>
+					<td>
+						San Francisco
+					</td>
+					<td>
+						America/Los_Angeles
+					</td>
+					<td>
+						37.7587
+					</td>
+					<td>
+						-122.433
+					</td>
+				</tr>
+				<tr data-filters="ContextMenu" data-context-menu-actions="[{'events':['contextmenu','click:relay(a.show-options)'],'menu':'ul.context-menu'}]">
+					<ul class="context-menu ccs-hidden">
+						<li class="cm-one">Item 1</li>
+						<li class="cm-two">Item 2</li>
+						<li class="cm-three">Item 3</li>
+					</ul>
+					<td>
+						24
+					</td>
+					<td>
+						Boston
+					</td>
+					<td>
+						America/New_York
+					</td>
+					<td>
+						42.3583
+					</td>
+					<td>
+						-71.0603
+					</td>
+				</tr>
+			</tbody>
+		</table>
+	</body>
+</html>

+ 57 - 0
apps/jframegallery/src/jframegallery/templates/submit.link.html

@@ -0,0 +1,57 @@
+{% comment %}
+Licensed to Cloudera, Inc. under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  Cloudera, Inc. licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+		http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+{% endcomment %}
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+	<head>
+		<title>Submit Links</title>
+	</head>
+	<body>
+
+		<p>Click either submit button to update the box below. The box should have an ajax spinner and then update with the content in the button.</p>
+
+		<form action="/jframegallery/submit.link.html" method="post" style="margin: 6px 0px 0px;">
+			<input type="submit" name="button" value="fetch html (button 1)"/>
+			<input type="submit" name="button" value="fetch html (button 2)"/>
+			<a class="ccs-submit_form" data-extra-data="{'link':'fetch html (link 1)'}">fetch html (link 1)</a>
+			<a class="ccs-submit_form" data-extra-data="{'link':'fetch html (link 2)'}">fetch html (link 2)</a>
+		</form>
+
+		<div style="position: relative; margin-top: 10px">
+			<div id="update" style="padding: 10px; width: 200px; border: 1px solid black; height: 100px; overflow:hidden;">
+				{% if post_vars %}
+					<div class="jframe_padded">
+						You submitted the following values:
+						<ul>
+							{% for post_var, val in post_vars %}
+							 <li>{{post_var}}: {{val}}</li>
+							{% endfor %}
+						</ul>
+					</div>
+				{% else %}
+					this box should get new text when you click the input above.
+				{% endif %}
+			</div>
+		</div>
+
+		<script src="/depender/build?require=Widgets/Behavior.FormRequest,More/Spinner"></script>
+		<script>
+		new Behavior().apply(document.body);
+		</script>
+
+	</body>
+</html>

+ 2 - 0
desktop/core/static/js/Source/BehaviorFilters/Behavior.ContextMenu.js

@@ -33,6 +33,7 @@ var JframeContextMenu = new Class({
 		this.applyDelegates = this.options.applyDelegates;
 		this.applyDelegates = this.options.applyDelegates;
 	},
 	},
 	show: function(x, y){
 	show: function(x, y){
+		if (this.disabled || !this.activeMenu) return;
 		//when the menu is shown, put the place holder after the menu
 		//when the menu is shown, put the place holder after the menu
 		this._placeHolder.inject(this.activeMenu, 'after');
 		this._placeHolder.inject(this.activeMenu, 'after');
 		//move the menu into the container
 		//move the menu into the container
@@ -42,6 +43,7 @@ var JframeContextMenu = new Class({
 		this.parent(x, y);
 		this.parent(x, y);
 	},
 	},
 	hide: function(){
 	hide: function(){
+		if (this.disabled || !this.activeMenu) return;
 		//when we hide the menu, put the menu back where it was and pop the placeholder out of the DOM
 		//when we hide the menu, put the menu back where it was and pop the placeholder out of the DOM
 		this.activeMenu.inject(this._placeHolder, 'after');
 		this.activeMenu.inject(this._placeHolder, 'after');
 		this._placeHolder.dispose();
 		this._placeHolder.dispose();

+ 6 - 6
desktop/core/static/js/Source/BehaviorFilters/Behavior.FilterInput.js

@@ -33,13 +33,13 @@ Behavior.addGlobalFilters({
 	FilterInput: function(element, methods){
 	FilterInput: function(element, methods){
 		var container = methods.getContentElement();
 		var container = methods.getContentElement();
 		//this method will find all the elements and check them for the value
 		//this method will find all the elements and check them for the value
+		var selector = element.get('data', 'filter-elements');
+		if (!selector) {
+			dbug.warn('warning, there was no selector defined for data-filter-elements for this element: ', element);
+			return;
+		}
+		var filterParents = element.get('data', 'filter-parents');
 		var filter = function (){
 		var filter = function (){
-			var selector = element.get('data', 'filter-elements');
-			var filterParents = element.get('data', 'filter-parents');
-			if (!selector) {
-				dbug.warn('warning, there was no selector defined for data-filter-elements for this element: ', element);
-				return;
-			}
 			var elements = container.getElements(selector);
 			var elements = container.getElements(selector);
 			if (!elements.length) {
 			if (!elements.length) {
 				dbug.warn('warning, this filter element %o has data-filter-elements selector defined as %s but this selector matches no elements.', element, selector);
 				dbug.warn('warning, this filter element %o has data-filter-elements selector defined as %s but this selector matches no elements.', element, selector);

+ 32 - 0
desktop/core/static/js/Source/BehaviorFilters/Behavior.HtmlTableKeyboard.js

@@ -0,0 +1,32 @@
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+/*
+---
+description: Makes form validator instances scroll the jframe to the errors.
+provides: [Behavior.HtmlTableKeyboard]
+requires: [Widgets/Behavior.HtmlTable]
+script: Behavior.HtmlTableKeyboard.js
+...
+*/
+
+Behavior.addGlobalPlugin('HtmlTable', 'HtmlTableJFrame', function(element, methods){
+
+	var table = element.retrieve('HtmlTable');
+	if (table.keyboard) {
+		methods.registerKeyboard(table.keyboard);
+		this.markForCleanup(element, function(){
+			methods.unregisterKeyboard(table.keyboard);
+		});
+	}
+
+});

+ 65 - 0
desktop/core/static/js/Source/BehaviorFilters/Behavior.HtmlTableMultiSelectMenu.js

@@ -0,0 +1,65 @@
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+/*
+---
+description: Adds support for HtmlTable context menus for multiple selected rows.
+provides: [Behavior.HtmlTableMultiSelectMenu]
+requires: [Widgets/Behavior.HtmlTable, /Behavior.ContextMenu]
+script: Behavior.HtmlTableMultiSelectMenu.js
+...
+*/
+Behavior.addGlobalPlugin('HtmlTable', 'HtmlTableMutiSelect', function(element, methods){
+	var table = element.retrieve('HtmlTable');
+	var previousSelected;
+	//check if the table has a context menu for bulk edits
+	//and we can multi-select things
+	if (table.options.allowMultiSelect && element.hasDataFilter('ContextMenu')) {
+		//wait for a short period to get the table's context menu; we have no guarantee
+		//that its been created before this filter runs
+		var menu;
+		(function(){
+			menu = element.retrieve('ContextMenu');
+			if (menu) menu.disable();
+		}).delay(10);
+		//when the user selects a row
+		table.addEvent('rowFocus', function(row, selectedRows){
+			//if there is no context menu on the table, then exit
+			if (!menu) return;
+			var action;
+			//if there were a previously selected group of menus, re-enable them.
+			if (previousSelected) {
+				previousSelected.each(function(menu){
+					menu.enable();
+				});
+				previousSelected.empty();
+			}
+			//if the user has selected more than one row
+			if (selectedRows.length > 1) {
+				//enable the table's menu
+				menu.enable();
+				//loop through the selected rows and disable their menus
+				//this allows the right click event to travel past the table row level and up to the table
+				//so the bulk action menu is displayed
+				previousSelected = selectedRows.map(function(tr){
+					var menu = tr.retrieve('ContextMenu');
+					if (menu) menu.disable();
+					return menu;
+				}).clean();
+			} else {
+				//otherwise, if there aren't multiple rows selected, disable the table's menu
+				menu.disable();
+			}
+		});
+	}
+
+});

+ 31 - 14
desktop/core/static/js/Source/CCS/CCS.ContextMenu.js

@@ -48,8 +48,12 @@ script: CCS.ContextMenu.js
 	document.addEvents({
 	document.addEvents({
 		mousedown: function(e) {
 		mousedown: function(e) {
 			//if there is a menu visible, hide it on any click
 			//if there is a menu visible, hide it on any click
-			var active = $(CCS.ContextMenu.active);
-			if (active && e.target != active && !active.hasChild(e.target)) CCS.ContextMenu.active.hide();
+			var menu = CCS.ContextMenu.active;
+			var active = $(menu);
+			if (active && e.target != active && !active.hasChild(e.target)) {
+				active.hide();
+				menu.fireEvent('hide', active);
+			}
 		}.bind(this),
 		}.bind(this),
 		keyup: function(e) {
 		keyup: function(e) {
 			//or if the user hits escape
 			//or if the user hits escape
@@ -64,7 +68,10 @@ script: CCS.ContextMenu.js
 		},
 		},
 		initialize: function(delegate, options){
 		initialize: function(delegate, options){
 			this.setOptions(options);
 			this.setOptions(options);
-			this.delegate = $(delegate);
+			this.delegate = $(delegate).store('ContextMenu', this);
+			this._eventStopper = function(){
+				if (this.disabled) return false;
+			};
 			this.attach();
 			this.attach();
 		},
 		},
 		detachers: [],
 		detachers: [],
@@ -81,9 +88,9 @@ script: CCS.ContextMenu.js
 						//if there is no menu, but there was data, we want to kill right-click support
 						//if there is no menu, but there was data, we want to kill right-click support
 						//on this element; we assume that the right-click behavior is still intended,
 						//on this element; we assume that the right-click behavior is still intended,
 						//but there's simply no menu
 						//but there's simply no menu
-						this.delegate.addEvent('contextmenu', eventStopper);
+						this.delegate.addEvent('contextmenu', this._eventStopper);
 						this.detachers.push(function(){
 						this.detachers.push(function(){
-							this.delegate.removeEvent('contextmenu', eventStopper);
+							this.delegate.removeEvent('contextmenu', this._eventStopper);
 						}.bind(this));
 						}.bind(this));
 						return;
 						return;
 					}
 					}
@@ -94,17 +101,19 @@ script: CCS.ContextMenu.js
 						click: this.hide.bind(this)
 						click: this.hide.bind(this)
 					});
 					});
 					this.detachers.push(function(){
 					this.detachers.push(function(){
-						menu.removeEvent('contextmenu', eventStopper);
-					});
+						menu.removeEvent('contextmenu', this._eventStopper);
+					}.bind(this));
 					//for each event defined in the data, delegate that event to the container
 					//for each event defined in the data, delegate that event to the container
 					//contextmenu, click:relay(selector), etc
 					//contextmenu, click:relay(selector), etc
 					data.events.each(function(event) {
 					data.events.each(function(event) {
 						events[event] = function(e){
 						events[event] = function(e){
-							e.preventDefault();
-							//let's only show one menu like this at a time
-							this.activeMenu = menu;
-							//put the menu on the mouse
-							this.show(e.page.x, e.page.y);
+							if (!this.disabled) {
+								e.preventDefault();
+								//let's only show one menu like this at a time
+								this.activeMenu = menu;
+								//put the menu on the mouse
+								this.show(e.page.x, e.page.y);
+							}
 						}.bind(this);
 						}.bind(this);
 					}, this);
 					}, this);
 					this.delegate.addEvents(events);
 					this.delegate.addEvents(events);
@@ -120,14 +129,21 @@ script: CCS.ContextMenu.js
 				fn();
 				fn();
 			});
 			});
 		},
 		},
+		disable: function(){
+			this.hide();
+			this.disabled = true;
+		},
+		enable: function(){
+			this.disabled = false;
+		},
 		toElement: function(){
 		toElement: function(){
 			return this.activeMenu;
 			return this.activeMenu;
 		},
 		},
 		//shows the menu at the given x/y position
 		//shows the menu at the given x/y position
 		show: function(x, y){
 		show: function(x, y){
+			if (this.disabled) return;
 			//if there is a menu visible, hide it on any click
 			//if there is a menu visible, hide it on any click
 			if (CCS.ContextMenu.active && CCS.ContextMenu.active != this) CCS.ContextMenu.active.hide();
 			if (CCS.ContextMenu.active && CCS.ContextMenu.active != this) CCS.ContextMenu.active.hide();
-			
 			if (this.activeMenu) {
 			if (this.activeMenu) {
 				this.activeMenu.show();
 				this.activeMenu.show();
 				this.position(x, y);
 				this.position(x, y);
@@ -157,11 +173,12 @@ script: CCS.ContextMenu.js
 		},
 		},
 		//hides the active menu
 		//hides the active menu
 		hide: function(){
 		hide: function(){
+			if (this.disabled) return;
 			var menu = this.activeMenu;
 			var menu = this.activeMenu;
 			if (menu) menu.hide();
 			if (menu) menu.hide();
 			this.activeMenu = null;
 			this.activeMenu = null;
 			if (CCS.ContextMenu.active == this) CCS.ContextMenu.active = null;
 			if (CCS.ContextMenu.active == this) CCS.ContextMenu.active = null;
-			this.fireEvent('hide', menu);
+			if (menu) this.fireEvent('hide', menu);
 		}
 		}
 	});
 	});
 
 

+ 24 - 23
desktop/core/static/js/Source/CCS/CCS.JBrowser.js

@@ -21,39 +21,40 @@ requires:
  - /CCS.JFrame
  - /CCS.JFrame
  - Widgets/ART.Browser
  - Widgets/ART.Browser
  - Widgets/ART.SolidWindow
  - Widgets/ART.SolidWindow
+ - /CCS.JFrame.Alert
+ - /CCS.JFrame.ArtButtons
  - /CCS.JFrame.AutoRefresh
  - /CCS.JFrame.AutoRefresh
+ - /CCS.JFrame.CheckAllOrNone
+ - /CCS.JFrame.Chooser
  - /CCS.JFrame.Collapsible
  - /CCS.JFrame.Collapsible
- - /CCS.JFrame.DoubleClickDelegate
- - /CCS.JFrame.HtmlTable
- - /CCS.JFrame.OverText
- - /CCS.JFrame.SubmitOnChange
+ - /CCS.JFrame.ConfirmAndPost
  - /CCS.JFrame.ContextMenu
  - /CCS.JFrame.ContextMenu
+ - /CCS.JFrame.DataGroupToggle
+ - /CCS.JFrame.DoubleClickDelegate
+ - /CCS.JFrame.FakeRefresh
+ - /CCS.JFrame.FilterInput
  - /CCS.JFrame.FormRequest
  - /CCS.JFrame.FormRequest
- - /CCS.JFrame.NoOverflow
- - /CCS.JFrame.SplitView
- - /CCS.JFrame.Tabs
+ - /CCS.JFrame.FormValidator
  - /CCS.JFrame.FitText
  - /CCS.JFrame.FitText
- - /CCS.JFrame.ConfirmAndPost
+ - /CCS.JFrame.HtmlTable
+ - /CCS.JFrame.Input
+ - /CCS.JFrame.Nav
+ - /CCS.JFrame.NoOverflow
+ - /CCS.JFrame.OverText
+ - /CCS.JFrame.PartialRefresh
+ - /CCS.JFrame.Prompt
  - /CCS.JFrame.PromptAndPost
  - /CCS.JFrame.PromptAndPost
  - /CCS.JFrame.Refresh
  - /CCS.JFrame.Refresh
+ - /CCS.JFrame.SelectWithOther
+ - /CCS.JFrame.SideBySideSelect
  - /CCS.JFrame.SizeTo
  - /CCS.JFrame.SizeTo
- - /CCS.JFrame.FilterInput
- - /CCS.JFrame.FakeRefresh
+ - /CCS.JFrame.SplitView
+ - /CCS.JFrame.SubmitLink
+ - /CCS.JFrame.SubmitOnChange
+ - /CCS.JFrame.Tabs
  - /CCS.JFrame.Target
  - /CCS.JFrame.Target
- - /CCS.JFrame.Chooser
- - /CCS.JFrame.Alert
- - /CCS.JFrame.Prompt
- - /CCS.JFrame.PartialRefresh
- - /CCS.JFrame.ArtButtons
- - /CCS.JFrame.SideBySideSelect
- - /CCS.JFrame.CheckAllOrNone
- - /CCS.JFrame.FormValidator
- - /CCS.JFrame.DataGroupToggle
- - /CCS.JFrame.ToggleHistory
- - /CCS.JFrame.Nav
  - /CCS.JFrame.Tips
  - /CCS.JFrame.Tips
- - /CCS.JFrame.SelectWithOther
- - /CCS.JFrame.Input
+ - /CCS.JFrame.ToggleHistory
 script: CCS.JBrowser.js
 script: CCS.JBrowser.js
 
 
 ...
 ...

+ 9 - 0
desktop/core/static/js/Source/CCS/CCS.JFrame.js

@@ -33,6 +33,7 @@ requires:
  - Widgets/Behavior
  - Widgets/Behavior
  - Widgets/Behavior.ArtButton
  - Widgets/Behavior.ArtButton
  - Widgets/Behavior.ArtInput
  - Widgets/Behavior.ArtInput
+ - Widgets/Behavior.FormRequest
  - Widgets/Behavior.FormValidator
  - Widgets/Behavior.FormValidator
  - Widgets/Behavior.HtmlTable
  - Widgets/Behavior.HtmlTable
  - Widgets/Behavior.OverText
  - Widgets/Behavior.OverText
@@ -40,6 +41,8 @@ requires:
  - /Behavior.ContextMenu
  - /Behavior.ContextMenu
  - /Behavior.FilterInput
  - /Behavior.FilterInput
  - /Behavior.FitText
  - /Behavior.FitText
+ - /Behavior.HtmlTableKeyboard
+ - /Behavior.HtmlTableMultiSelectMenu
  - /Behavior.MultiChecks
  - /Behavior.MultiChecks
  - /Behavior.SideBySideSelect
  - /Behavior.SideBySideSelect
  - /Behavior.SizeTo
  - /Behavior.SizeTo
@@ -133,6 +136,12 @@ CCS.JFrame = new Class({
 						this._requestSuccessHandler(request, text);
 						this._requestSuccessHandler(request, text);
 					}.bind(this)
 					}.bind(this)
 				});
 				});
+			}.bind(this),
+			registerKeyboard: function(keyboard){
+				this.keyboard.manage(keyboard);
+			}.bind(this),
+			unregisterKeyboard: function(keyboard){
+				this.keyboard.drop(keyboard);
 			}.bind(this)
 			}.bind(this)
 		});
 		});
 		this.addEvent('resize', this.behavior.resize.bind(this.behavior));
 		this.addEvent('resize', this.behavior.resize.bind(this.behavior));

+ 35 - 0
desktop/core/static/js/Source/JFrameLinkers/CCS.JFrame.SubmitLink.js

@@ -0,0 +1,35 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/*
+---
+description: When the user clicks a link with the class '.ccs-submit_form' submit the parent form.
+provides: [CCS.JFrame.SubmitLink]
+requires: [/CCS.JFrame]
+script: CCS.JFrame.SubmitLink.js
+
+...
+*/
+
+CCS.JFrame.addGlobalLinkers({
+	/*
+		submit the form that the element is in.
+	*/
+	'.ccs-submit_form': function(event, el) {
+		el.getParent('form').retrieve('form.request').setOptions({extraData: el.getJSONData('extra-data')}).send();
+	}
+
+});

+ 7 - 4
desktop/core/static/js/package.yml

@@ -38,14 +38,15 @@ sources: [
   Source/UI/StickyWin.Drawer.js,
   Source/UI/StickyWin.Drawer.js,
   Source/UI/StickyWin.UI.Solid.js,
   Source/UI/StickyWin.UI.Solid.js,
   Source/UI/ThumbTack.js,
   Source/UI/ThumbTack.js,
-  Source/JFrameLinkers/CCS.JFrame.Target.js,
   Source/JFrameLinkers/CCS.JFrame.CheckAllOrNone.js,
   Source/JFrameLinkers/CCS.JFrame.CheckAllOrNone.js,
+  Source/JFrameLinkers/CCS.JFrame.Chooser.js,
+  Source/JFrameLinkers/CCS.JFrame.ConfirmAndPost.js,
+  Source/JFrameLinkers/CCS.JFrame.FakeRefresh.js,
   Source/JFrameLinkers/CCS.JFrame.Nav.js,
   Source/JFrameLinkers/CCS.JFrame.Nav.js,
   Source/JFrameLinkers/CCS.JFrame.PromptAndPost.js,
   Source/JFrameLinkers/CCS.JFrame.PromptAndPost.js,
-  Source/JFrameLinkers/CCS.JFrame.ConfirmAndPost.js,
   Source/JFrameLinkers/CCS.JFrame.Refresh.js,
   Source/JFrameLinkers/CCS.JFrame.Refresh.js,
-  Source/JFrameLinkers/CCS.JFrame.FakeRefresh.js,
-  Source/JFrameLinkers/CCS.JFrame.Chooser.js,
+  Source/JFrameLinkers/CCS.JFrame.SubmitLink.js,
+  Source/JFrameLinkers/CCS.JFrame.Target.js,
   Source/Native/String.CCS.js,
   Source/Native/String.CCS.js,
   Source/Native/Element.Data.js,
   Source/Native/Element.Data.js,
   Source/StaticThirdParty/DynamicTextarea.js,
   Source/StaticThirdParty/DynamicTextarea.js,
@@ -54,6 +55,8 @@ sources: [
   Source/BehaviorFilters/Behavior.ContextMenu.js,
   Source/BehaviorFilters/Behavior.ContextMenu.js,
   Source/BehaviorFilters/Behavior.FilterInput.js,
   Source/BehaviorFilters/Behavior.FilterInput.js,
   Source/BehaviorFilters/Behavior.FitText.js,
   Source/BehaviorFilters/Behavior.FitText.js,
+  Source/BehaviorFilters/Behavior.HtmlTableMultiSelectMenu.js,
+  Source/BehaviorFilters/Behavior.HtmlTableKeyboard.js,
   Source/BehaviorFilters/Behavior.MultiChecks.js,
   Source/BehaviorFilters/Behavior.MultiChecks.js,
   Source/BehaviorFilters/Behavior.SideBySideSelect.js,
   Source/BehaviorFilters/Behavior.SideBySideSelect.js,
   Source/BehaviorFilters/Behavior.SizeTo.js,
   Source/BehaviorFilters/Behavior.SizeTo.js,

+ 1 - 1
ext/thirdparty/js/art-widgets.hash

@@ -1 +1 @@
-4d07560e1defc325ef10b92d968d6472d87db133
+a55bab170a37fb0f855a8d291460ce7a07dc92f3

+ 1 - 1
ext/thirdparty/js/mootools-more.hash

@@ -1 +1 @@
-4074ee73688f1e70ce22176d83d2a2fb8af983e6
+a89b413b51aaaf425514c8ec7e77596a787cbede