diff options
author | suelockwood <deathbearbrown@gmail.com> | 2013-09-26 10:38:10 -0400 |
---|---|---|
committer | suelockwood <deathbearbrown@gmail.com> | 2013-09-30 16:25:46 -0400 |
commit | 62e6dacca9629c591316b5790aa8f8c5921870e2 (patch) | |
tree | 6cf812679d765ba324bcab2f798b83734989033d | |
parent | b14405041b87867878cd1088bfcca41f3b72f3c6 (diff) | |
download | couchdb-62e6dacca9629c591316b5790aa8f8c5921870e2.tar.gz |
New Replication UI and switching to _replicator
11 files changed, 654 insertions, 117 deletions
diff --git a/src/fauxton/app/addons/replication/assets/less/replication.less b/src/fauxton/app/addons/replication/assets/less/replication.less index a30196654..ea446ec17 100644 --- a/src/fauxton/app/addons/replication/assets/less/replication.less +++ b/src/fauxton/app/addons/replication/assets/less/replication.less @@ -25,6 +25,54 @@ form#replication { max-width: none; width: auto; + #create_target{ + input[type=radio]{display: none;} + label.btn { + margin-right: 0; + } + } + + .actions{ + padding: 15px 0; + } + #from_name { + margin-right: 20px; + } + .autharea { + display: inline-block; + vertical-align: top; + padding-top: 10px; + } + button.fonticon-replicate{ + padding: 15px 20px; + } + .span12{ + margin-left: 0; + padding-bottom: 20px; + } + .nav-tabs{ + margin-bottom: 0; + border: 0; + } + .dropdown-menu { + width: 85%; + } + .tab-content.small-tabs{ + margin-top: 0; + border: 1px solid #e3e3e3; + margin-bottom: 10px; + .tab-pane { + padding: 30px 20px; + } + > .active { + display: block; + background-color: #fff; + } + } + h3 { + margin-top: 0; + line-height: 27px + } .form_set{ width: 350px; display: inline-block; @@ -157,6 +205,7 @@ form#replication { } #replicationStatus{ + &.showHeader{ li.header{ display: block; @@ -194,3 +243,4 @@ form#replication { } } } + diff --git a/src/fauxton/app/addons/replication/resources.js b/src/fauxton/app/addons/replication/resources.js index 38ae13988..8c916c453 100644 --- a/src/fauxton/app/addons/replication/resources.js +++ b/src/fauxton/app/addons/replication/resources.js @@ -21,6 +21,9 @@ function (app, FauxtonAPI, ActiveTasks) { //these are probably dupes from the database modules. I'm going to keep them seperate for now. Replication.DBModel = Backbone.Model.extend({ + url: function(){ + return app.host + "/" + this.id; + }, label: function () { //for autocomplete return this.get("name"); @@ -60,9 +63,11 @@ function (app, FauxtonAPI, ActiveTasks) { Replication.Replicate = Backbone.Model.extend({ url: function(){ - return app.host + "/_replicate"; + return app.host + "/_replicator"; } }); + + return Replication; }); diff --git a/src/fauxton/app/addons/replication/route.js b/src/fauxton/app/addons/replication/route.js index 7ea318c01..0dd25a550 100644 --- a/src/fauxton/app/addons/replication/route.js +++ b/src/fauxton/app/addons/replication/route.js @@ -18,7 +18,7 @@ define([ ], function(app, FauxtonAPI, Replication, Views) { var RepRouteObject = FauxtonAPI.RouteObject.extend({ - layout: "one_pane", + layout: "one_pane_notabs", roles: ["_admin"], routes: { "replication": "defaultView", @@ -29,7 +29,7 @@ function(app, FauxtonAPI, Replication, Views) { return app.host+"/_replication"; }, crumbs: [ - {"name": "Replicate changes from: ", "link": "replication"} + {"name": "", "link": "replication"} ], defaultView: function(dbname){ this.databases = new Replication.DBList({}); diff --git a/src/fauxton/app/addons/replication/templates/authfields.html b/src/fauxton/app/addons/replication/templates/authfields.html new file mode 100644 index 000000000..9806dcfb4 --- /dev/null +++ b/src/fauxton/app/addons/replication/templates/authfields.html @@ -0,0 +1,16 @@ +<!-- +Licensed 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. +--> +<p>Authorize the use of this database for replication.</p> +<input class="input-medium" type="text" name="user_<%=type%>" size="30" placeholder="Username"> +<input class="input-medium next" type="password" name="password_<%=type%>" size="30" placeholder="Password" data-next-step="step<%=step%>"> diff --git a/src/fauxton/app/addons/replication/templates/form.html b/src/fauxton/app/addons/replication/templates/form.html index 32a87dca3..e75453efa 100644 --- a/src/fauxton/app/addons/replication/templates/form.html +++ b/src/fauxton/app/addons/replication/templates/form.html @@ -11,64 +11,67 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - <form id="replication" class="form-horizontal"> - <div class="from form_set local"> - <div class="btn-group"> - <button class="btn local-btn" type="button" value="local">Local</button> - <button class="btn remote-btn" type="button" value="remote">Remote</button> - </div> - - <div class="from_local local_option"> - <select id="from_name" name="source"> - <% _.each( databases, function( db, i ){ %> - <option value="<%=db.name%>" <% if(selectedDB == db.name){%>selected<%}%> ><%=db.name%></option> - <% }); %> - </select> - </div> - <div class="from_to_remote remote_option"> - <input type="text" id="from_url" name="source" size="30" value="http://"> - </div> + <!-- SOURCE --> + <div class="control-group" id="step1"> + <label class="control-label">FROM: </label> + <div class="source controls"> + <p>Select a database to replicate.</p> + <div id="source_form"></div> </div> - <div class="form_set middle"> - <span class="circle "></span> - <a href="#" title="Switch Target and Source" class="swap"> - <span class="fonticon-swap-arrows"></span> - </a> - </span> - </div> + </div> - <div class="to form_set local"> - <div class="btn-group"> - <button class="btn local-btn" type="button" value="local">Local</button> - <button class="btn remote-btn" type="button" value="remote">Remote</button> - </div> - <div class="to_local local_option"> - <input type="text" id="to_name" name="target" size="30" placeholder="database name"> - </div> + <div class="control-group hide" id="step2"> + <label class="control-label">TO:</label> + <div class="target controls"> + <p>Where do you want to replicate your data?</p> + <div class="btn-group" id="create_target"> + + <label for="existing-target" class="btn"> + Existing Database + </label> + + <label for="new-target" class="btn"> + New Database + </label> - <div class="to_remote remote_option"> - <input type="text" id="to_url" name="target" size="30" value="http://"> + <input type="radio" id="existing-target" name="create_target" class="next" data-next-step="step3" value="false"> + <input type="radio" id="new-target" name="create_target" class="next" data-next-step="step3" value="true"> + </div> </div> + </div> + <!--TARGET--> + <div class="control-group hide" id="step3"> + <div class="target controls"> + <p>Select a target database for your data.</p> + <div id="target_form"></div> - <div class="actions"> - <div class="control-group"> <label for="continuous"> <input type="checkbox" name="continuous" value="true" id="continuous"> - Continuous + Make this replication continuous. </label> - <label for="createTarget"> - <input type="checkbox" name="create_target" value="true" id="createTarget"> - Create Target <a href="<%=getDocUrl('replication_doc')%>" target="_blank"><i class="icon-question-sign" rel="tooltip" title="Create the target database"></i></a> - </label> </div> - <button class="btn btn-success btn-large save" type="submit">Replicate</button> </div> + <div class="control-group hide" id="step4"> + <label class="control-label">NAME: </label> + <div class="source controls"> + <p>Give this replication task an ID. (optional)</p> + <input type="text" id="repID" name="_id" size="30" value="" data-validation="optional" placeholder="e.g. my_rep"> + + <div class="actions"> + <button class="button green save btn-large fonticon-replicate" type="submit" >Replicate</button> + </div> + </div> + + </div> + + + </form> <div id="replicationStatus"></div> diff --git a/src/fauxton/app/addons/replication/templates/localremotetabs.html b/src/fauxton/app/addons/replication/templates/localremotetabs.html new file mode 100644 index 000000000..61337e13c --- /dev/null +++ b/src/fauxton/app/addons/replication/templates/localremotetabs.html @@ -0,0 +1,35 @@ +<!-- +Licensed 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. +--> +<ul class="nav nav-tabs" id="<%=type%>Tabs"> + <li class="active"> + <a href="#" class="btn local-btn" data-tab="<%=type%>_local">My Databases</a> + </li> + <li> + <a href="#" class="btn remote-btn" data-tab="<%=type%>_remote">Remote Database</a> + </li> +</ul> + +<div class="tab-content small-tabs"> + <div class="tab-pane active" id="<%=type%>_local"> + <input type="text" id="to_name" name="<%=type%>" size="30" placeholder="Select a <%=type%> database" class="permission auto"> + <div class="autharea authArea_<%=type%>"></div> + </div> + + <div class="tab-pane" id="<%=type%>_remote"> + <input type="text" id="to_url" name="<%=type%>" size="30" class="next" value="http://" data-next-step="step<%=step%>"> + <small>e.g. http://username:password@user.cloudant.com/database</small> + </div> +</div> + +<div id="options-here"></div> diff --git a/src/fauxton/app/addons/replication/templates/newdatabase.html b/src/fauxton/app/addons/replication/templates/newdatabase.html new file mode 100644 index 000000000..1bcc3bc79 --- /dev/null +++ b/src/fauxton/app/addons/replication/templates/newdatabase.html @@ -0,0 +1,34 @@ +<!-- +Licensed 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. +--> + +<ul class="nav nav-tabs" id="<%=type%>Tabs"> + <li class="active"> + <a href="#" class="btn local-btn" data-tab="<%=type%>_local">Create a new database locally</a> + </li> + <li> + <a href="#" class="btn remote-btn" data-tab="<%=type%>_remote">Create a new remote database</a> + </li> +</ul> + +<div class="tab-content small-tabs"> + <div class="tab-pane active" id="<%=type%>_local"> + <input type="text" id="to_name" name="<%=type%>" size="30" placeholder="Name your database" class="permission next" data-next-step="step<%=step%>"> + <div class="autharea authArea_<%=type%>"></div> + </div> + + <div class="tab-pane" id="<%=type%>_remote"> + <input type="text" id="to_url" name="<%=type%>" size="30" class="next" value="http://" data-next-step="step<%=step%>"> + <small>e.g. http://username:password@user.cloudant.com/database</small> + </div> +</div> diff --git a/src/fauxton/app/addons/replication/templates/options.html b/src/fauxton/app/addons/replication/templates/options.html new file mode 100644 index 000000000..5c6465e2d --- /dev/null +++ b/src/fauxton/app/addons/replication/templates/options.html @@ -0,0 +1,36 @@ +<!-- +Licensed 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. +--> + + +<span class="options off">Advanced Options</span> +<div class="advancedOptions hide"> + <h4>Apply filters</h4> + <p>Sometimes you don't want to transfer all documents from source to target. You can include one or more filter functions in a design document on the source and then tell the replicator to use them.</p> + <label for="filter">Enter the design doc and filter name</label> + <input type="text" placeholder="myddoc/myfilter" name="filter" id="filter"/> + <label for="query">Add query parameters (optional)</label> + <input type="text" placeholder='{"key":"value"}' name="query_params" id="query"/> + + <hr> + <h4>Named Document Replication</h4> + <p>Sometimes you only want to replicate some documents. For this simple case you do not need to write a filter function. Simply add the list of keys, separated by commas.</p> + <input type="text" placeholder="foo, bar, baz" name="doc_ids" id="doc_ids"/> + + <hr> + <h4>Replicate through a proxy</h4> + <p>Pass a "proxy" argument in the replication data to have replication go through an HTTP proxy</p> + <input type="text" placeholder="http://localhost:8888" name="proxy" id="proxy"/> + +</div> + diff --git a/src/fauxton/app/addons/replication/views.js b/src/fauxton/app/addons/replication/views.js index f4b96fd40..336693748 100644 --- a/src/fauxton/app/addons/replication/views.js +++ b/src/fauxton/app/addons/replication/views.js @@ -24,13 +24,13 @@ function(app, FauxtonAPI, Components, replication) { intervalId: null }; + app.temphost ="http://deathbear.cloudant.com"; _.extend(Events, Backbone.Events); // NOTES: http://wiki.apache.org/couchdb/Replication // Replication form view is huge // ----------------------------------- - // afterRender: autocomplete on the target input field // beforeRender: add the status table // disableFields: disable non active fields on submit // enableFields: enable field when radio btns are clicked @@ -46,76 +46,45 @@ function(app, FauxtonAPI, Components, replication) { template: "addons/replication/templates/form", events: { "submit #replication": "validate", - "click .btn-group .btn": "showFields", - "click .swap": "swapFields", - "click .options": "toggleAdvancedOptions" + "change .next": "nextStep", + "change #create_target input[type='radio']": "showTargetForm", + "click #create_target label": "createTargetActiveState" }, initialize: function(options){ this.status = options.status; this.selectedDB = options.selectedDB; this.newRepModel = new replication.Replicate({}); }, - afterRender: function(){ - this.dbSearchTypeahead = new Components.DbSearchTypeahead({ - dbLimit: 30, - el: "input#to_name" - }); - - this.dbSearchTypeahead.render(); - - }, - beforeRender: function(){ this.insertView("#replicationStatus", new View.ReplicationList({ collection: this.status })); + + this.insertView("#source_form",new View.LocalRemoteTabs({ + selectedDB: this.selectedDB ||"", + type: "source", + step: "2" + })); }, cleanup: function(){ clearInterval(pollingInfo.intervalId); }, + createTargetActiveState: function(e){ + var $currentTarget = this.$(e.currentTarget); + $currentTarget.parents("#create_target").find('.active').removeClass('active'); + $currentTarget.addClass('active'); + }, enableFields: function(){ this.$el.find('input','select').attr('disabled',false); }, disableFields: function(){ - this.$el.find('input:hidden','select:hidden').attr('disabled',true); - }, - showFields: function(e){ - var $currentTarget = this.$(e.currentTarget), - targetVal = $currentTarget.val(); - - if (targetVal === "local"){ - $currentTarget.parents('.form_set').addClass('local'); - }else{ - $currentTarget.parents('.form_set').removeClass('local'); - } + this.$el.find('input[type="text"]:hidden','select:hidden').not("[type='radio']").attr('disabled',true); }, establish: function(){ return [ this.collection.fetch(), this.status.fetch()]; }, - validate: function(e){ - e.preventDefault(); - var notification; - if (this.formValidation()){ - notification = FauxtonAPI.addNotification({ - msg: "Please enter every field.", - type: "error", - clear: true - }); - }else if (this.$('input#to_name').is(':visible') && !this.$('input[name=create_target]').is(':checked')){ - var alreadyExists = this.collection.where({"name":this.$('input#to_name').val()}); - if (alreadyExists.length === 0){ - notification = FauxtonAPI.addNotification({ - msg: "This database doesn't exist. Check create target if you want to create it.", - type: "error", - clear: true - }); - } - }else{ - this.submit(e); - } - }, - formValidation: function(e){ - var $remote = this.$el.find('input:visible'), + validationCheck: function(e){ + var $remote = this.$el.find('input:visible').not('[data-validation="optional"]'), error = false; for(var i=0; i<$remote.length; i++){ if ($remote[i].value =="http://" || $remote[i].value ===""){ @@ -124,12 +93,24 @@ function(app, FauxtonAPI, Components, replication) { } return error; }, + nextStep: function(e){ + this.$("#"+this.$(e.currentTarget).attr('data-next-step')).removeClass('hide'); + }, serialize: function(){ return { - databases: this.collection.toJSON(), - selectedDB: this.selectedDB + host: app.host+"/" }; }, + showTargetForm: function(e){ + if (this.targetForm){ this.targetForm.remove();} + var targetView = this.$('[name="create_target"]:checked').val()==="true"? "CreateTarget": "LocalRemoteTabs"; + this.targetForm = this.insertView("#target_form",new View[targetView]({ + type: "target", + step: "4" + })); + this.targetForm.render(); + this.nextStep(e); + }, startReplication: function(json){ var that = this; this.newRepModel.save(json,{ @@ -153,7 +134,7 @@ function(app, FauxtonAPI, Components, replication) { } }); this.enableFields(); - }, + }, updateButtonText: function(wait){ var $button = this.$('#replication button[type=submit]'); if(wait){ @@ -162,41 +143,241 @@ function(app, FauxtonAPI, Components, replication) { $button.text('Replication').attr('disabled', false); } }, + validate: function(e){ + e.preventDefault(); + var notification; + + if (this.validationCheck()){ + notification = FauxtonAPI.addNotification({ + msg: "Please enter every field.", + type: "error", + clear: true + }); + } else if (this.$('input#to_name').is(':visible') && !this.$('input[name=create_target]').is(':checked')){ + var alreadyExists = this.collection.where({"name":this.$('input#to_name').val()}); + if (alreadyExists.length === 0){ + notification = FauxtonAPI.addNotification({ + msg: "This database doesn't exist. Select New Database if you want to create it.", + type: "error", + clear: true + }); + }else{ + this.submit(e); + } + }else{ + this.submit(e); + } + }, submit: function(e){ - this.disableFields(); - var formJSON = {}; + this.disableFields(); + var formData = this.scrubFormData(e), + that = this; + this.updateButtonText(true); + if (this.collection.where({"name":"_replicator"}).length !==0){ + this.startReplication(formData); + } else { + var db = new this.collection.model(); + db.save({ + id: "_replicator", + name: "_replicator" + }).done(function(){ + that.startReplication(formData); + }); + } + + }, + setAuthHeaders: function(source,user,pass){ + var basicHeader = new FauxtonAPI.session.createBasicAuthHeader(user,pass), + json = {}; + json.url = app.temphost+"/"+source; + json.headers = { + "Authorization": basicHeader.basicAuthHeader + }; + return json; + }, + scrubFormData: function(e){ + var data = {}, + scrub = {}; _.map(this.$(e.currentTarget).serializeArray(), function(formData){ if(formData.value !== ''){ - formJSON[formData.name] = (formData.value ==="true"? true: formData.value.replace(/\s/g, '').toLowerCase()); + //clean booleans & whitespaces + if (formData.name == "_id" || formData.name == "create_target" ){ + data[formData.name] = (formData.value ==="true"? true: formData.value.replace(/\s/g, '').toLowerCase()); + } else { + //Lotta stuff needs to be scrubbed before it's in proper json to submit + scrub[formData.name] = formData.value.replace(/\s/g, '').toLowerCase(); + } } }); - this.updateButtonText(true); - this.startReplication(formJSON); - }, - swapFields: function(e){ + //username & password for source + if ( scrub.user_source && scrub.password_source){ + data.source = this.setAuthHeaders(scrub.source, scrub.user_source, scrub.password_source); + } else { + data.source = scrub.source; + } + + //username & password for target + if ( scrub.user_target && scrub.password_target){ + data.target = this.setAuthHeaders(scrub.target, scrub.user_target, scrub.password_target); + } else { + data.target = scrub.target; + } + + return data; + } + }); + + View.AdvancedOptions = FauxtonAPI.View.extend({ + className: "authenticate", + template: "addons/replication/templates/options", + events: { + "click .options": "toggleAdvancedOptions", + }, + toggleAdvancedOptions: function(e){ + this.$(e.currentTarget).toggleClass("off"); + this.$('.advancedOptions').toggle("hidden").find('input').removeAttr('disabled'); + } + }); + + + View.LocalRemoteTabs = FauxtonAPI.View.extend({ + template: "addons/replication/templates/localremotetabs", + events: { + "click .nav-tabs a": "tabs", + "change .permission": "showAuth" + }, + afterRender: function(){ + this.dbSearchTypeahead = new Components.DbSearchTypeahead({ + dbLimit: 30, + el: "input.auto", + updater: function(item){ + return app.host+"/"+item; + } + }); + this.dbSearchTypeahead.render(); + + this.preselectedDatabase(); + }, + initialize: function(options){ + this.type = options.type; + this.step = options.step; + this.selected = options.selectedDB || ""; + }, + preselectedDatabase: function(){ + //if selected database is passed through from the _all_dbs page + if (this.selected){ + this.$('input.auto').val(this.selected); + this.showAuthFields(); + } + }, + showAdvancedOptions: function(e){ + if (this.advancedOptions){ this.advancedOptions.remove();} + this.advancedOptions = this.insertView("#options-here", new View.AdvancedOptions({})); + this.advancedOptions.render(); + }, + showAuthFields: function(e){ + var dataAuthSelector = this.$('input.auto').attr('name'), + autharea = ".authArea_"+dataAuthSelector, + nextStep = this.step; + + if (this[dataAuthSelector]){ this[dataAuthSelector].remove();} + + this[dataAuthSelector] = this.insertView(autharea, new View.AuthFields({ + type: dataAuthSelector, + step: nextStep })); + this[dataAuthSelector].render(); + }, + showAuth: function(e){ + if (this.$(e.currentTarget).attr('name') === "source"){ + this.showAdvancedOptions(e); + } + this.showAuthFields(e); + }, + tabs: function(e){ e.preventDefault(); - //WALL O' VARIABLES - var $fromSelect = this.$('#from_name'), - $toSelect = this.$('#to_name'), - $toInput = this.$('#to_url'), - $fromInput = this.$('#from_url'), - fromSelectVal = $fromSelect.val(), - fromInputVal = $fromInput.val(), - toSelectVal = $toSelect.val(), - toInputVal = $toInput.val(); + var $currentTarget = this.$(e.currentTarget), + getTabID = "#"+$currentTarget.attr('data-tab'); - $fromSelect.val(toSelectVal); - $toSelect.val(fromSelectVal); + $currentTarget.parents('ul').find('.active').removeClass('active'); + $currentTarget.parents('li').addClass('active'); - $fromInput.val(toInputVal); - $toInput.val(fromInputVal); + $(getTabID).parents('.tab-content').find('.active').removeClass('active'); + $(getTabID).addClass('active'); + }, + serialize: function(){ + return { + step: this.step, + type: this.type + }; } }); + View.CreateTarget = FauxtonAPI.View.extend({ + template: "addons/replication/templates/newdatabase", + events: { + "click .nav-tabs a": "tabs", + "change .permission": "showAuth" + }, + initialize: function(options){ + this.type = options.type; + this.step = options.step; + }, + showAuthFields: function(e){ + var dataAuthSelector = this.$(e.currentTarget).attr('name'), + autharea = ".authArea_"+dataAuthSelector; + + if (this[dataAuthSelector]){ this[dataAuthSelector].remove();} + + this[dataAuthSelector] = this.insertView(autharea, new View.AuthFields({ + type:dataAuthSelector, + step:"4" + })); + this[dataAuthSelector].render(); + }, + showAuth: function(e){ + if (this.$(e.currentTarget).attr('name') === "source"){ + this.showAdvancedOptions(e); + } + this.showAuthFields(e); + }, + tabs: function(e){ + e.preventDefault(); + var $currentTarget = this.$(e.currentTarget), + getTabID = "#"+$currentTarget.attr('data-tab'); + + $currentTarget.parents('ul').find('.active').removeClass('active'); + $currentTarget.parents('li').addClass('active'); + + $(getTabID).parents('.tab-content').find('.active').removeClass('active'); + $(getTabID).addClass('active'); + }, + serialize: function(){ + return { + step: this.step, + type: this.type + }; + } + }); + + + View.AuthFields = FauxtonAPI.View.extend({ + template: "addons/replication/templates/authfields", + initialize: function(options){ + this.type = options.type; + this.step = options.step; + }, + serialize: function(){ + return { + step: this.step, + type: this.type + }; + } + }); View.ReplicationList = FauxtonAPI.View.extend({ tagName: "ul", + className: "testing", initialize: function(){ Events.bind('update:tasks', this.establish, this); this.listenTo(this.collection, "reset", this.render); diff --git a/src/fauxton/app/templates/layouts/one_pane_notabs.html b/src/fauxton/app/templates/layouts/one_pane_notabs.html new file mode 100644 index 000000000..f58661fce --- /dev/null +++ b/src/fauxton/app/templates/layouts/one_pane_notabs.html @@ -0,0 +1,27 @@ +<!-- +Licensed 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. +--> + +<div id="primary-navbar"></div> +<div id="dashboard" class="container-fluid one-pane"> + <div class="fixed-header"> + <div id="breadcrumbs"></div> + <div id="api-navbar"></div> + </div> + + + <div class="row-fluid content-area"> + <div id="dashboard-content" class="window-resizeable"></div> + </div> +</div> + diff --git a/src/fauxton/assets/js/plugins/happy.js b/src/fauxton/assets/js/plugins/happy.js new file mode 100644 index 000000000..5c4dc7c82 --- /dev/null +++ b/src/fauxton/assets/js/plugins/happy.js @@ -0,0 +1,150 @@ +// HAPPY JS. +// WARNING: this has been editted to support custom error message handling. +// $('#awesomeForm').isHappy({ +// fields: { +// // reference the field you're talking about, probably by `id` +// // but you could certainly do $('[name=name]') as well. +// '#yourName': { +// required: true, +// message: 'Might we inquire your name' +// }, +// '#email': { +// required: true, +// message: 'How are we to reach you sans email??', +// test: happy.email // this can be *any* function that returns true or false +// } +// }, +// errorHandling: function(error){console.log(error.message);} +// }); +// +// if you don't pass in a function for errorHandling, it behaves as designed. - Sue + +(function($){ + function trim(el) { + return (''.trim) ? el.val().trim() : $.trim(el.val()); + } + $.fn.isHappy = function (config) { + var fields = [], + item; + + function errorMessaging(error,selector){ + if(config.errorHandling){ + config.errorHandling(error); + }else{ + if ($("#"+error.id).length <= 0){ + var errorEl = $('<span id="'+error.id+'" class="unhappyMessage">'+error.message+'</span>'); + $(selector).after(errorEl); + } + } + } + function handleSubmit(e) { + var errors = false, i, l; + for (i = 0, l = fields.length; i < l; i += 1) { + if (!fields[i].testValid(true)) { + errors = true; + } + } + if (errors) { + if (isFunction(config.unHappy)) config.unHappy(); + return false; + } else if (config.testMode) { + if (window.console) console.warn('would have submitted'); + return false; + } + } + function isFunction (obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + } + function processField(opts, selector) { + var field = $(selector), + errorM = { + message: opts.message, + id: field.attr('name') + '_unhappy' + }; + // }, + // errorEl = $(error.id).length > 0 ? $(error.id) : getError(error); + + fields.push(field); + field.testValid = function (submit) { + var val, + el = $(this), + gotFunc, + error = false, + temp, + required = !!el.get(0).attributes.getNamedItem('required') || opts.required, + password = (field.attr('type') === 'password'), + arg = isFunction(opts.arg) ? opts.arg() : opts.arg; + + // clean it or trim it + if (isFunction(opts.clean)) { + val = opts.clean(el.val()); + } else if (!opts.trim && !password) { + val = trim(el); + } else { + val = el.val(); + } + + // write it back to the field + el.val(val); + + // get the value + gotFunc = ((val.length > 0 || required === 'sometimes') && isFunction(opts.test)); + + // check if we've got an error on our hands + if (submit === true && required === true && val.length === 0) { + error = true; + } else if (gotFunc) { + error = !opts.test(val, arg); + } + + if (error) { + el.addClass('unhappy'); + errorMessaging(errorM, el); + return false; + } else { + el.removeClass('unhappy'); + $("#"+field.attr('name') + '_unhappy').remove(); + return true; + } + }; + field.bind(config.when || 'blur', field.testValid); + } + + for (item in config.fields) { + processField(config.fields[item], item); + } + + if (config.submitButton) { + $(config.submitButton).click(handleSubmit); + } else { + this.bind('submit', handleSubmit); + } + return this; + }; +})(this.jQuery || this.Zepto); +var happy = { + USPhone: function (val) { + return (/^\(?(\d{3})\)?[\- ]?\d{3}[\- ]?\d{4}$/).test(val); + }, + + // matches mm/dd/yyyy (requires leading 0's (which may be a bit silly, what do you think?) + date: function (val) { + return (/^(?:0[1-9]|1[0-2])\/(?:0[1-9]|[12][0-9]|3[01])\/(?:\d{4})/).test(val); + }, + + email: function (val) { + return (/^(?:\w+\.?)*\w+@(?:\w+\.)+\w+$/).test(val); + }, + + minLength: function (val, length) { + return val.length >= length; + }, + + maxLength: function (val, length) { + return val.length <= length; + }, + + equal: function (val1, val2) { + return (val1 == val2); + } +}; |