diff --git a/package.json b/package.json index 76e75781594..fa95fe6c071 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "ampersand-collection-filterable": "^0.2.1", "ampersand-collection-lodash-mixin": "^2.0.1", "ampersand-collection-rest-mixin": "^5.0.0", + "ampersand-dom-bindings": "^3.7.0", "ampersand-filtered-subcollection": "^2.0.4", "ampersand-form-view": "^5.1.1", "ampersand-input-view": "^5.0.0", diff --git a/src/connect/behavior.js b/src/connect/behavior.js index eecd5ff69d6..caa7148bec3 100644 --- a/src/connect/behavior.js +++ b/src/connect/behavior.js @@ -1,9 +1,10 @@ var State = require('ampersand-state'); -var debug = require('debug')('scout:connect:behavior'); var assert = require('assert'); var Connection = require('../models/connection'); var _ = require('lodash'); +// var debug = require('debug')('scout:connect:behavior'); + module.exports = State.extend({ props: { view: 'any', @@ -79,9 +80,9 @@ module.exports = State.extend({ }, dispatch: function(action) { var newState = this.reduce(this.state, action); - if (newState !== this.state) { - debug('transition: (%s, %s) ==> %s', this.state, action, newState); - } + // if (newState !== this.state) { + // debug('transition: (%s, %s) ==> %s', this.state, action, newState); + // } this.state = newState; return this.state; }, @@ -100,7 +101,7 @@ module.exports = State.extend({ // check if the current state allows the given action if (this.validTransitions[state].indexOf(action) === -1) { - debug('ignoring action `%s` in state `%s`', action, state); + // debug('ignoring action `%s` in state `%s`', action, state); return state; } // general actions, independent of state diff --git a/src/connect/connect-form-view.js b/src/connect/connect-form-view.js index 77cb96a67e3..8c224bdc47e 100644 --- a/src/connect/connect-form-view.js +++ b/src/connect/connect-form-view.js @@ -4,7 +4,7 @@ var SelectView = require('ampersand-select-view'); var authOptions = require('./authentication'); var sslOptions = require('./ssl'); var FilteredCollection = require('ampersand-filtered-subcollection'); -var debug = require('debug')('scout:connect:connect-form-view'); +// var debug = require('debug')('scout:connect:connect-form-view'); require('bootstrap/js/popover'); @@ -66,7 +66,6 @@ var ConnectFormView = FormView.extend({ return; } var name = obj.hostname + ':' + obj.port; - debug('obj', obj); if (obj.authentication === 'MONGODB') { if (obj.mongodb_username) { name = obj.mongodb_username + '@' + name; @@ -189,7 +188,7 @@ var ConnectFormView = FormView.extend({ el: this.parent.queryByHook('saveas-subview'), name: 'name', label: 'Name', - placeholder: 'e.g. Shared Dev, Stats Box, PRODUCTION', + placeholder: 'e.g. Shared Dev, QA Box, PRODUCTION', required: false }) ]; diff --git a/src/connect/filereader-default.jade b/src/connect/filereader-default.jade index f3f3924f505..5899d26547a 100644 --- a/src/connect/filereader-default.jade +++ b/src/connect/filereader-default.jade @@ -3,4 +3,7 @@ .message.message-below.message-error(data-hook='message-container') p(data-hook='message-text') .form-item-file - input(type="file", name="uploadKeyFile", multiple) + .btn.btn-default(data-hook='load-file-button', style='text-transform: none;') + i.fa.fa-file + |   + span(data-hook='button-label')= buttonTitle diff --git a/src/connect/filereader-view.js b/src/connect/filereader-view.js new file mode 100644 index 00000000000..c5738efba61 --- /dev/null +++ b/src/connect/filereader-view.js @@ -0,0 +1,182 @@ +var InputView = require('./input-view'); +var _ = require('lodash'); +var path = require('path'); +var remote = window.require('remote'); +var dialog = remote.require('dialog'); +var format = require('util').format; +var bindings = require('ampersand-dom-bindings'); +var fileReaderTemplate = require('./filereader-default.jade'); + +// var debug = require('debug')('scout:connect:filereader-view'); + +module.exports = InputView.extend({ + template: fileReaderTemplate, + props: { + inputValue: { + type: 'array', + required: true, + default: function() { + return []; + } + }, + removed: { + type: 'boolean', + required: true, + default: false + } + }, + derived: { + buttonTitle: { + deps: ['inputValue'], + fn: function() { + if (this.inputValue.length === 0) { + return 'Choose certificate(s)'; + } else if (this.inputValue.length === 1) { + return path.basename(this.inputValue[0]); + } + return format('%d files selected', this.inputValue.length); + } + }, + numSelectedFiles: { + deps: ['inputValue'], + fn: function() { + return this.inputValue.length; + } + } + }, + events: { + 'click [data-hook=load-file-button]': 'loadFileButtonClicked' + }, + bindings: { + buttonTitle: { + type: 'text', + hook: 'button-label' + }, + 'label': [ + { + hook: 'label' + }, + { + type: 'toggle', + hook: 'label' + } + ], + 'message': { + type: 'text', + hook: 'message-text' + }, + 'showMessage': { + type: 'toggle', + hook: 'message-container' + } + }, + /** + * Set value to empty array in spec, instead of '', set appropriate + * invalidClass and validityClassSelector and always validate. + * @param {Object} spec the spec to set up this input view + */ + initialize: function(spec) { + spec = spec || {}; + _.defaults(spec, {value: []}); + this.invalidClass = 'has-error'; + this.validityClassSelector = '.form-item-file'; + InputView.prototype.initialize.call(this, spec); + }, + /** + * @todo (thomasr) + * Because ampersand-input-view still uses ampersand-view@8.x where + * the render/remove/render cycle doesn't correctly set up the bindings + * again, we need to re-initialize the bindings manually here. Once they + * upgrade to ampersand-view@9.x we can probably remove this entire + * render method. + * + * @return {Object} this + */ + render: function() { + this.renderWithTemplate(this); + this.input = this.queryByHook('load-file-button'); + if (this.removed) { + this._parsedBindings = bindings(this.bindings, this); + this._initializeBindings(); + this.removed = false; + } + this.setValue(this.inputValue, !this.required); + return this; + }, + /** + * Turn into no-op, as we don't work on input elements + * @see ampersand-input-view.js#handleTypeChange + */ + handleTypeChange: function() { + }, + /** + * Turn into identity, as we don't need to trim the value + * @param {Array} val the value to pass through + * @return {Array} return the unchanged value + */ + clean: function(val) { + return val; + }, + /** + * Turn into no-op, as we don't work on input elements + * @see ampersand-input-view.js#initInputBindings + */ + // initInputBindings: function() { + // }, + /** + * Only call ampersand-view's remove, we don't need to remove event listeners + * @see ampersand-input-view.js#remove + */ + remove: function() { + this.removed = true; + InputView.prototype.remove.apply(this, arguments); + }, + /** + * Not setting this.input.value here because our this.input is a div + * @param {Array} value the value to assign to this.inputValue + * @param {Boolean} skipValidation whether it should be validated or not + * @see ampersand-input-view.js#setValue + */ + setValue: function(value, skipValidation) { + this.inputValue = value; + if (!skipValidation && !this.getErrorMessage()) { + this.shouldValidate = true; + } else if (skipValidation) { + this.shouldValidate = false; + } + }, + /** + * Need to change the value empty check to empty arrays instead + * @return {String} error message + * @see ampersand-input-view.js#getErrorMessage + */ + getErrorMessage: function() { + var message = ''; + if (this.required && this.value.length === 0) { + return this.requiredMessage; + } + (this.tests || []).some(function(test) { + message = test.call(this, this.value) || ''; + return message; + }, this); + return message; + }, + /** + * Don't access this.input.value as we don't work on input elements + * @see ampersand-input-view.js#beforeSubmit + */ + beforeSubmit: function() { + // at the point where we've tried to submit, we want to validate + // everything from now on. + this.shouldValidate = true; + this.runTests(); + }, + loadFileButtonClicked: function() { + dialog.showOpenDialog({ + properties: ['openFile', 'multiSelections'] + }, function(filenames) { + this.inputValue = filenames || []; + this.handleChange(); + }.bind(this)); + } +}); diff --git a/src/connect/index.jade b/src/connect/index.jade index 98161035939..fa7efcb1c6c 100644 --- a/src/connect/index.jade +++ b/src/connect/index.jade @@ -21,7 +21,9 @@ - if (!method.enabled) classNames.push('hidden') - if (i === 0) classNames.push('active') div(class=classNames, id='auth-#{method._id}') - + + hr + div(data-hook='sslselect-subview') - for method, i in sslMethods diff --git a/src/connect/index.js b/src/connect/index.js index c9da201bb80..f2a81c8dca5 100644 --- a/src/connect/index.js +++ b/src/connect/index.js @@ -94,7 +94,9 @@ var ConnectView = View.extend({ 'input input[name=name]': 'onNameInputChanged', 'change input[name=name]': 'onNameInputChanged', 'input input': 'onAnyInputChanged', - 'change select': 'onAnyInputChanged' + 'change input': 'onAnyInputChanged', + 'change select': 'onAnyInputChanged', + 'click div.btn': 'onAnyInputChanged' }, /** @@ -292,14 +294,8 @@ var ConnectView = View.extend({ */ updateConnection: function() { if (this.connection) { - debug('updating existing connection from form data'); - // set previous auth fields - var authFields = Connection.getFieldNames(this.previousAuthMethod); - debug('authFields', authFields); this.connection.set(this.form.data); - debug('after', this.connection.serialize()); } else { - debug('creating new connection from form data'); this.connection = new Connection(this.form.data); } this.connection.is_favorite = true; @@ -339,10 +335,6 @@ var ConnectView = View.extend({ return; } app.statusbar.show(); - debug('trying to connect with URL %s and options %j', - connection.driver_url, - connection.driver_options - ); connection.test(function(err) { app.statusbar.hide(); diff --git a/src/connect/index.less b/src/connect/index.less index 4759d671bbc..4c3e311f74b 100644 --- a/src/connect/index.less +++ b/src/connect/index.less @@ -139,12 +139,12 @@ overflow: auto; } label { - width: 32%; + width: 35%; text-align: right; padding: 5px 15px 0 0; } input, select, .form-item-file { - width: 68%; + width: 65%; } .form-item-file { input { diff --git a/src/connect/input-view.js b/src/connect/input-view.js index 5f557b37db5..76a82d049cc 100644 --- a/src/connect/input-view.js +++ b/src/connect/input-view.js @@ -1,11 +1,9 @@ var InputView = require('ampersand-input-view'); -var _ = require('lodash'); - // var debug = require('debug')('scout:connect:input-view'); /** - * Need to overwrite render() method and pass in `this` for renderWithTemplate(), so that - * label gets correctly rendered on subsequent render() calls. + * Need to overwrite render() method and pass in `this` for renderWithTemplate() + * so that label gets correctly rendered on subsequent render() calls. */ module.exports = InputView.extend({ initialize: function() { @@ -23,38 +21,5 @@ module.exports = InputView.extend({ // if the field is not required this.setValue(this.inputValue, !this.required); return this; - }, - clean: function(val) { - if (this.type === 'number') { - return Number(val); - } else if (this.type === 'string') { - return val.trim(); - } - return val; - }, - /** - * overwriting InputView#beforeSubmit to handle `file` type correctly. - * For `file` type, the input view returns the actual path (provided by - * electron) rather than the browser fake path. - * @see https://github.com/atom/electron/blob/master/docs/api/file-object.md - */ - beforeSubmit: function() { - var value; - if (this.type === 'file') { - value = _.chain(this.input.files) - .map(function(file) { - return _.get(file, 'path', null); - }) - .filter() - .value(); - if (value.length === 0) { - value = ''; - } - } else { - value = this.input.value; - } - this.inputValue = this.clean(value); - this.shouldValidate = true; - this.runTests(); } }); diff --git a/src/connect/ssl.js b/src/connect/ssl.js index d4af8efc050..b90de9ea9e8 100644 --- a/src/connect/ssl.js +++ b/src/connect/ssl.js @@ -6,8 +6,8 @@ var SSLOptionCollection = require('./models/ssl-option-collection'); var InputView = require('./input-view'); +var FileReaderView = require('./filereader-view'); var inputTemplate = require('./input-default.jade'); -var fileReaderTemplate = require('./filereader-default.jade'); // var debug = require('debug')('scout:connect:ssl'); @@ -37,12 +37,9 @@ var SERVER = { // enabled: app.isFeatureEnabled('Connect with SSL SERVER'), enabled: true, fields: [ - new InputView({ - template: fileReaderTemplate, + new FileReaderView({ name: 'ssl_ca', - type: 'file', label: 'Certificate Authority', - placeholder: '', required: true }) ] @@ -57,36 +54,25 @@ var ALL = { // enabled: app.isFeatureEnabled('Connect with SSL ALL'), enabled: true, fields: [ - new InputView({ - template: fileReaderTemplate, + new FileReaderView({ name: 'ssl_ca', - type: 'file', label: 'Certificate Authority', - placeholder: '', required: true }), - new InputView({ - template: fileReaderTemplate, + new FileReaderView({ name: 'ssl_private_key', - type: 'file', label: 'Certificate Key', - placeholder: '', required: true }), - new InputView({ - template: fileReaderTemplate, + new FileReaderView({ name: 'ssl_certificate', - type: 'file', label: 'Certificate', - placeholder: '', required: true }), new InputView({ template: inputTemplate, name: 'ssl_private_key_password', - type: 'password', label: 'Private Key Password', - placeholder: '', required: false }) ]