Skip to content

Loading…

External download builder #102

Closed
wants to merge 1 commit into from

2 participants

@rxaviers
jQuery Foundation member

Abstract DownloadBuilder core functionalities below.

  • Handle check/uncheck of dependencies/dependents;
  • Handle toggleAll logic;

The code responsible for those has been encapsulated away. Our jQuery UI Download Builder doesnt need to handle with that anymore, but instead use a very simple API.

More about it: http://rxaviers.github.com/download-builder

@rxaviers
jQuery Foundation member

Comments, Suggestions, Complaints? @scottgonzalez @jzaefferer @gnarf37

Note the only meaningful commit here is 2f07731 (it's based on top of handlebars changes)

@jzaefferer
jQuery Foundation member

Will come back to this once #101 is merged. Though one thing: I have a big problem with that organization repo. I don't have a problem with GitHub organizations in general, but when used they need to represent an actual organization. So far that's your project, so it belongs on your account. If you look through Scott's or my profile, you will find plenty repositories that are (central) dependencies of jQuery projects. That's fine!

@rxaviers
jQuery Foundation member

If you need me to move that into my account, please just let me know, not a problem. Although, I wanted it to be more independent, not attached to my account/profile, like what grunt does.

@jzaefferer
jQuery Foundation member

Yes, please move it. I support the independence idea, but that only makes sense when you aren't the only one involved. And you can still hand out write access to your own repos.

@rxaviers
jQuery Foundation member

Ccing @gseguin and @toddparker for their thoughts about it, since jQuery mobile is also in the loop.

@rxaviers
jQuery Foundation member
@rxaviers
jQuery Foundation member

Hi @jzaefferer, we have #101 merged. So, if you could take a look I appreciate. Thanks

@jzaefferer
jQuery Foundation member

Let's get on Skype tomorrow and discuss this. I have several concerns, though you can likely address most of those pretty quickly. If not, we can figure out the alternatives. Just ping me when you see me online?

@jzaefferer jzaefferer closed this
@rxaviers rxaviers deleted the external-download-builder branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
2 Gruntfile.js
@@ -54,7 +54,7 @@ grunt.initConfig({
},
// DownloadBuilder minified frontend bundle
download: {
- src: [ "app/src/external/event_emitter.min.js", "app/src/external/handlebars.runtime.js", "app/src/template/download.js", "app/src/external/lzma.js", "app/src/hash.js", "app/src/querystring.js", "app/src/model.js", "app/src/download.js" ],
+ src: [ "app/src/external/download-builder.min.js", "app/src/external/event_emitter.min.js", "app/src/external/handlebars.runtime.js", "app/src/template/download.js", "app/src/external/lzma.js", "app/src/hash.js", "app/src/querystring.js", "app/src/model.js", "app/src/download.js" ],
dest: "app/resources/download.all.min.js"
},
// ThemeRoller minified frontend bundle
View
241 app/src/download.js
@@ -1,5 +1,5 @@
/*jshint jquery: true, browser: true */
-/*global Hash: false, JST: false, Model: false */
+/*global DownloadBuilder: false, Hash: false, JST: false, Model: false */
/*!
* jQuery UI DownloadBuilder client-side JavaScript file
* http://jqueryui.com/download/
@@ -8,11 +8,11 @@
* Released under the MIT license.
* http://jquery.org/license
*/
-(function( $, Hash, JST, Model, undefined ) {
+(function( $, DownloadBuilder, Hash, JST, Model, undefined ) {
- var dependencies, dependents, model,
+ var allComponents, dependencies, dependents, model,
componentsLoad = $.Deferred(),
- downloadBuilder = $( "#download-builder" ),
+ downloadBuilder = $( "#download-builder" ),
themesLoad = $.Deferred(),
baseVars = downloadBuilder.data( "base-vars" ),
downloadJqueryuiHost = downloadBuilder.data( "download-jqueryui-host" ),
@@ -45,203 +45,76 @@
return dfd;
}
- function allComponents() {
- return $( "#download-builder .component-group-list input[type=checkbox]" );
- }
-
- function allGroup( referenceElement ) {
- return $( referenceElement ).closest( ".component-group" ).find( ".component-group-list input[type=checkbox]" );
- }
-
- function _check( elem, value, options ) {
- var modelUpdates = {},
- depElem = $();
- options = options || {};
-
- elem.each(function() {
- var elem = $( this ),
- name = elem.attr( "name" );
-
- // Handle dependencies
- if ( value && !options.skipDependencies ) {
- if ( dependencies[ name ] ) {
- // Whenever a checkbox is activated, also activate all dependencies
- depElem = depElem.add( dependencies[ name ] );
- }
- } else if ( dependents[ name ] && !options.skipDependencies ) {
- // Whenever a checkbox is deactivated, also deactivate all dependents
- depElem = depElem.add( dependents[ name ] );
- }
-
- elem.prop( "checked", value );
-
- modelUpdates[ name ] = value;
- });
-
- // Update dependencies
- if ( depElem.length ) {
- _check( depElem, value );
- }
-
- // Update toggle all
- if ( value ) {
- // Set group toggle all if all components of its group are checked
- if ( !allGroup( elem ).filter( ":not(:checked)" ).length ) {
- $( elem ).closest( ".component-group" ).find( ".toggle input[type=checkbox]" ).prop( "checked", true );
- }
- // Set toggle all if all components are checked
- if ( !allComponents().filter( ":not(:checked)" ).length ) {
- $( elem ).closest( ".components" ).prev().find( ".toggleAll input[type=checkbox]" ).prop( "checked", true );
- }
- } else {
- // Unset group toggle all if no components of its group are checked
- if ( !allGroup( elem ).filter( ":checked" ).length ) {
- $( elem ).closest( ".component-group" ).find( ".toggle input[type=checkbox]" ).prop( "checked", false );
- }
- // Unset toggle all if no components are checked
- if ( !allComponents().filter( ":checked" ).length ) {
- $( elem ).closest( ".components" ).prev().find( ".toggleAll input[type=checkbox]" ).prop( "checked", false);
- }
- }
-
- model.set( modelUpdates );
- downloadOnOff();
- }
-
- function check( event, elem, value, options ) {
- var consolidatedDependents, consolidatedNames;
-
- // Uncheck validations
- if ( !value ) {
- consolidatedDependents = $();
- consolidatedNames = [];
- elem.each(function() {
- var name = $( this ).attr( "name" );
- if ( dependents[ name ] && dependents[ name ].filter( ":checked" ).not( elem ).length > 0 ) {
- consolidatedNames.push( name );
- consolidatedDependents = consolidatedDependents.add( dependents[ name ].filter( ":checked" ).not( elem ) );
- }
- });
-
- // Validate if uncheck is allowed when it has dependents
- if ( consolidatedDependents.length > 0 ) {
- event.preventDefault();
- $( "<div>" )
- .attr( "title", "Remove " + consolidatedNames.join( ", " ) + "?" )
- .append(
- $( "<p>" ).html(
- "Are you sure you want to remove <b>" + consolidatedNames.join( ", " ) + "</b>? The following " + pluralize( consolidatedDependents.length, "component", "components" ) + " " + pluralize( consolidatedDependents.length, "depends", "depend" ) + " on it and will be removed: " + consolidatedDependents.map(function() {
- return "<b>" + this.name + "</b>";
- }).toArray().join( ", " ) + "."
- )
- )
- .dialog({
- modal: true,
- buttons: {
- "Remove": function() {
- _check( elem, value, options );
- $( this ).remove();
- },
- "Cancel": function() {
- $( this ).remove();
- }
- }
- })
- .dialog( "widget" ).addClass( "download-builder-dialog" );
- } else {
- _check( elem, value, options );
- }
-
- // Check validations (none)
- } else {
- _check( elem, value, options );
- }
- }
-
function downloadOnOff() {
- if ( !allComponents().filter( ":checked" ).length && $( "#theme" ).val() === "none" ) {
+ if ( !$( "input[type=checkbox][data-dependencies]" ).filter( ":checked" ).length && $( "#theme" ).val() === "none" ) {
$( "#download-builder input[type=submit]" ).prop( "disabled", true ).addClass( "ui-state-disabled" );
} else {
$( "#download-builder input[type=submit]" ).prop( "disabled", false ).removeClass( "ui-state-disabled" );
}
}
- function drawToggleAll( className ) {
- return $( "<label>" )
- .addClass( className )
- .text( " Toggle All" )
- .prepend(
- $( "<input type=checkbox>" )
- .prop( "checked", true )
- .addClass( "ui-widget-content" )
- );
- }
-
function initComponents( components ) {
- var toggleAll;
- dependencies = {};
- dependents = {};
-
$( "#download-builder .components-area" ).html( JST[ "components.html" ]( components ) );
+ downloadBuilder = new DownloadBuilder( "#download-builder" ).on({
+ "check": function( event, components, value, extra ) {
+ if ( extra.affectedDependents && extra.affectedDependents.length ) {
+ event.preventDefault();
+ $( "<div>" )
+ .attr( "title", "Remove " + extra.affectedComponentNames.join( ", " ) + "?" )
+ .append(
+ $( "<p>" ).html(
+ "Are you sure you want to remove <b>" + extra.affectedComponentNames.join( ", " ) + "</b>? The following " + pluralize( extra.affectedDependents.length, "component", "components" ) + " " + pluralize( extra.affectedDependents.length, "depends", "depend" ) + " on it and will be removed: " + extra.affectedDependents.map(function() {
+ return "<b>" + this.name + "</b>";
+ }).toArray().join( ", " ) + "."
+ )
+ )
+ .dialog({
+ modal: true,
+ buttons: {
+ "Remove": function() {
+ event.defaultAction();
+ $( this ).remove();
+ },
+ "Cancel": function() {
+ $( this ).remove();
+ }
+ }
+ })
+ .dialog( "widget" ).addClass( "download-builder-dialog" );
+ }
+ },
+ "accumulated-change": function( event, components, value ) {
+ var changes = {};
+ components.each(function() {
+ var component = $( this );
+ changes[ component.attr( "name" ) ] = value;
+ });
+ model.set( changes );
+ downloadOnOff();
+ }
+ });
- model.setOrderedComponents(
- $( "#download-builder .components-area input[type=checkbox]" ).map(function() {
- return this.name;
- })
- );
-
- // Initializes dependencies and dependents auxiliary variables.
- $( "#download-builder input[type=checkbox]" ).each(function() {
- var checkbox = $( this ),
- thisDependencies = checkbox.data( "dependencies" ),
- thisName = checkbox.attr( "name" );
+ // Zip categories' components.
+ allComponents = $.map( components.categories, function( category ) {
+ return category.components;
+ });
- if ( !thisName || !thisDependencies ) {
- return;
- }
- thisDependencies = thisDependencies.split( "," );
- dependencies[ thisName ] = $();
- $.each( thisDependencies, function() {
- var dependecy = this,
- dependecyElem = $( "[name=" + this + "]" );
- dependencies[ thisName ] = dependencies[ thisName ].add( dependecyElem );
- if ( !dependents[ dependecy ] ) {
- dependents[ dependecy ] = $();
- }
- dependents[ dependecy ] = dependents[ dependecy ].add( checkbox );
- });
+ // Flatten it and return name only.
+ allComponents = $.map( allComponents, function( component ) {
+ return component.name;
});
- // Generating toggle all checkboxes
- if ( !( toggleAll = $( "#download-builder .toggleAll" ) ).length ) {
- toggleAll = drawToggleAll( "toggleAll" ).insertAfter( $( "#download-builder .components-area" ).prev().find( "h2" ) );
- }
- $( "#download-builder .component-group h3" ).after( drawToggleAll( "toggle" ) );
+ model.setOrderedComponents( allComponents );
/* Remember components check/uncheck selection
- If a component is checked/unchecked, it should keep its check-state in a subsequent version-change or page-load;
- If a component is loaded in the page and there is no previous check-state for it, it should be checked unless it has an unchecked dependency;
*/
- allComponents().each(function() {
- var elem = $( this ),
- name = elem.attr( "name" );
+ $.each( allComponents, function() {
+ var name = this,
+ elem = $( "input[name=\"" + name + "\"]" );
if ( model.has( name ) && model.get( name ) === false ) {
- _check( elem, false );
- }
- });
-
- // Binds click handlers on components checkboxes
- toggleAll.find( "input[type=checkbox]" ).on( "click", function( event ) {
- check( event, allComponents(), $( this ).prop( "checked" ), {
- skipDependencies: true
- });
- });
- $( "#download-builder .components-area input[type=checkbox]" ).on( "click", function( event ) {
- var target = $( event.target );
- if ( target.parent().is( ".toggle" ) ) {
- check( event, allGroup( this ), $( this ).prop( "checked" ) );
- } else {
- check( event, $( this ), $( this ).prop( "checked" ) );
+ downloadBuilder.set( elem, false );
}
});
@@ -296,7 +169,7 @@
model.attributes[ attribute ] = value = false;
}
if ( value === false && $( this ).prop( "checked" ) !== value ) {
- _check( $( this ), false );
+ downloadBuilder.set( $( this ), false );
}
// Ignore checked-components in the model
if ( value ) {
@@ -412,4 +285,4 @@
}
});
-}( jQuery, Hash, JST, Model ) );
+}( jQuery, DownloadBuilder, Hash, JST, Model ) );
View
4 app/src/external/download-builder.min.js
@@ -0,0 +1,4 @@
+/*!
+ * DownloadBuilder http://git.io/r3JMkQ
+ */
+(function(exports,$,undefined){var accumulateChange,allGroup,defaultCheckAction,flushChanges,groupToggleAlls,triggerCheck;var defaults={components:"input[data-dependencies][name][type=checkbox]",toggleAll:"input[type=checkbox].toggle-all",groupToggleAll:'input[class^="toggle-all-"][type=checkbox]'};function DownloadBuilder(element,options){var self=this;options=$.extend({},DownloadBuilder.defaults,options);this.element=$(element);var componentsSelector=this.componentsSelector=options.components;var toggleAllSelector=options.toggleAll;var groupToggleAllSelector=options.groupToggleAll;this.allComponents=this.element.find(options.components);this.toggleAll=this.element.find(toggleAllSelector);this.groupToggleAlls=this.element.find(groupToggleAllSelector);var dependencies=this.dependencies={};var dependents=this.dependents={};this.allComponents.each(function(){var component=$(this),thisDependencies=component.data("dependencies"),thisName=component.attr("name");if(!thisName||!thisDependencies){return}thisDependencies=thisDependencies.split(",");dependencies[thisName]=$();$.each(thisDependencies,function(){var dependecy=this,dependecyElem=$("[name="+this+"]");dependencies[thisName]=dependencies[thisName].add(dependecyElem);if(!dependents[dependecy]){dependents[dependecy]=$()}dependents[dependecy]=dependents[dependecy].add(component)})});this.element.on("click",toggleAllSelector,function(event){var target=$(event.target);triggerCheck.call(self,event,self.allComponents,target.prop("checked"),{skipDependencies:true})}).on("click",groupToggleAllSelector,function(event){var target=$(event.target);triggerCheck.call(self,event,allGroup.call(self,target),target.prop("checked"))}).on("click",componentsSelector,function(event){var target=$(event.target);triggerCheck.call(self,event,target,target.prop("checked"))}).on("change",componentsSelector,function(event){var target=$(event.target);accumulateChange.call(self,target)})}DownloadBuilder.defaults=defaults;accumulateChange=function(component){if(!this.accChangedComponents){this.accChangedComponents=$()}this.accChangedComponents=this.accChangedComponents.add(component)};allGroup=function(groupToggleAll){var container;var components=$();for(container=groupToggleAll.parent();container.length&&!components.length;container=container.parent()){components=container.find(this.componentsSelector)}return components};defaultCheckAction=function check(components,value,options){var self=this;var depElements=$();var dependencies=this.dependencies;var dependents=this.dependents;components.each(function(){var component=$(this),name=component.attr("name");if(value&&!options.skipDependencies){if(dependencies[name]){depElements=depElements.add(dependencies[name])}}else if(dependents[name]&&!options.skipDependencies){depElements=depElements.add(dependents[name])}if(component.prop("checked")!==value){component.prop("checked",value).trigger("change")}});if(depElements.length){defaultCheckAction.call(this,depElements,value,{skipFlush:true})}if(!options.skipFlush){flushChanges.call(this,value)}};flushChanges=function(value){var event=$.Event("accumulated-change");if(this.accChangedComponents){updateToggleAlls.call(this,this.accChangedComponents,value);this.element.trigger(event,[this.accChangedComponents,value]);delete this.accChangedComponents}};groupToggleAlls=function(components){var self=this;var groupToggleAlls=$();if(!this.component2groupToggleAll){this.component2groupToggleAll={};this.groupToggleAlls.each(function(){var groupToggleAll=$(this);allGroup.call(self,groupToggleAll).each(function(){var component=$(this);var componentName=component.attr("name");self.component2groupToggleAll[componentName]=groupToggleAll})})}components.each(function(){var component=$(this);var componentName=component.attr("name");var groupToggleAll=self.component2groupToggleAll[componentName];groupToggleAlls=groupToggleAlls.add(groupToggleAll)});return groupToggleAlls};triggerCheck=function(clickEvent,components,value,options){var self=this;var extra={};options=options||{};if(!value&&!options.skipDependencies){var affectedComponentNames=[];var affectedDependents=$();var dependents=this.dependents;components.each(function(){var name=$(this).attr("name");if(!dependents[name]){return}var checkedDependents=dependents[name].filter(":checked").not(components);if(checkedDependents.length){affectedComponentNames.push(name);affectedDependents=affectedDependents.add(checkedDependents)}});extra.affectedComponentNames=affectedComponentNames;extra.affectedDependents=affectedDependents}var event=$.Event("check");event.defaultAction=function(){accumulateChange.call(self,components);defaultCheckAction.call(self,components,value,options)};this.element.trigger(event,[components,value,extra]);if(event.isDefaultPrevented()){clickEvent.preventDefault()}else{event.defaultAction()}};updateToggleAlls=function(components,value){var self=this;if(value){groupToggleAlls.call(this,components).each(function(){var groupToggleAll=$(this);var groupComponents=allGroup.call(self,groupToggleAll);if(!groupComponents.filter(":not(:checked)").length){groupToggleAll.prop("checked",true)}});if(!this.allComponents.filter(":not(:checked)").length){this.toggleAll.prop("checked",true)}}else{groupToggleAlls.call(this,components).each(function(){var groupToggleAll=$(this);var groupComponents=allGroup.call(self,groupToggleAll);if(!groupComponents.filter(":checked").length){groupToggleAll.prop("checked",false)}});if(!this.allComponents.filter(":checked").length){this.toggleAll.prop("checked",false)}}};DownloadBuilder.prototype={on:function(){this.element.on.apply(this.element,arguments);return this},set:function(component,value){defaultCheckAction.call(this,$(component),value,{})}};exports.DownloadBuilder=DownloadBuilder})(this,jQuery);
View
5 lib/release.js
@@ -163,8 +163,9 @@ Release.prototype = {
this._categories = this.components().reduce(function( arr, component ) {
if ( !map[ component.category ] ) {
var category = _.extend({
- components: []
- }, categories[component.category]);
+ components: [],
+ shortname: component.category
+ }, categories[ component.category ]);
map[ component.category ] = category;
arr.push( category );
}
View
4 template/download/components.html
@@ -2,6 +2,10 @@
<div class="ui-widget ui-widget-content component-group clearfix">
<div class="component-group-desc">
<h3>{{name}}</h3>
+ <label>
+ <input type="checkbox" checked="" class="toggle-all-{{shortname}} ui-widget-content">
+ Toggle All
+ </label>
<p>{{description}}</p>
</div>
<div class="component-group-list">
View
5 template/download/index.html
@@ -24,6 +24,10 @@
</div>
<div class="download-builder-header">
<h2>Components</h2>
+ <label>
+ <input type="checkbox" checked="" class="toggle-all ui-widget-content">
+ Toggle All
+ </label>
</div>
<div class="components-area"></div>
<div class="download-builder-header">
@@ -38,6 +42,7 @@
{{#if production}}
<script src="/resources/download.all.min.js"></script>
{{else}}
+ <script src="/src/external/download-builder.min.js"></script>
<script src="/src/external/event_emitter.min.js"></script>
<script src="/src/external/handlebars.runtime.js"></script>
<script src="/src/template/download.js"></script>
Something went wrong with that request. Please try again.