UNPKG

apostrophe

Version:

The Apostrophe Content Management System.

367 lines (321 loc) • 11.5 kB
// An editor modal for creating and updating pieces. An instance of this modal is created // each time you click "Add" or click to edit an existing piece. Relies on // [apostrophe-schemas](/reference/modules/apostrophe-schemas) to edit the fields. apos.define('apostrophe-pieces-editor-modal', { extend: 'apostrophe-modal', beforeConstruct: function(self, options) { if (!options.source) { if (options.create) { options.source = 'create-modal'; } else { options.source = 'editor-modal'; } } }, construct: function(self, options) { self.schema = options.schema; self._id = options._id; self.name = options.name; self.copying = options.copying; self.manager = options.manager; self.beforeShow = function(callback) { self.$form = self.$el.find('[data-apos-form]'); self.$form.on('change', self.onChange); self.link('apos-trash', function($el) { self.trash($el); }); self.link('apos-rescue', function($el) { self.rescue($el); }); self.link('apos-copy', function($el) { self.copy($el); }); self.link('apos-versions', function($el) { self.versions($el); }); if (self._id) { return self.edit(self._id, callback); } else { return self.create(callback); } }; // Make sure the field indicated by options.field is initially visible self.afterShow = function() { if (self.options.field) { var $el = apos.schemas.findFieldset(self.$el, self.options.field); if ($el.length) { apos.schemas.showGroupContaining($el); $el.scrollintoview(); setTimeout(function() { $el.find('input,select,textarea').first().focus(); }, 250); } } }; self.edit = function(_id, callback) { return apos.docs.lockAndWatch(_id, function(err) { if (err) { return callback(err); } return self.open('retrieve', { _id: _id }, callback); }); }; self.create = function(callback) { var piece = apos.schemas.newInstance(self.schema); if (self.options.copying) { _.assign(piece, self.options.copying); delete piece._id; } return self.populate(piece, callback); }; self.open = function(verb, data, callback) { self.api(verb, data, function(result) { if (result.status === 'notfound') { apos.notify('That item does not exist.', { type: 'error' }); return callback('error'); } else if (result.status !== 'ok') { apos.notify('An error occurred. Please try again.', { type: 'error', dismiss: true }); return callback('error'); } self._id = result.data._id; var object = result.data; var $trash = self.$el.find('[data-apos-trash]'); var $rescue = self.$el.find('[data-apos-rescue]'); if (object.trash) { $trash.hide(); } else { $rescue.hide(); } return self.populate(object, callback); }, function() { apos.notify('An error occurred. Please try again.', { type: 'error', dismiss: true }); return callback('network'); }); }; self.populate = function(piece, callback) { return async.series({ beforePopulate: function(callback) { return self.beforePopulate(piece, callback); }, populate: function(callback) { return apos.schemas.populate(self.$form, self.schema, piece, callback); }, afterPopulate: function(callback) { return self.afterPopulate(piece, callback); } }, callback); }; self.beforePopulate = function(piece, callback) { return setImmediate(callback); }; self.afterPopulate = function(piece, callback) { return setImmediate(callback); }; self.saveContent = function(callback) { var piece = { _id: self._id }; return async.series({ before: function(callback) { return self.beforeConvert(piece, callback); }, convert: function(callback) { return apos.schemas.convert(self.$form, self.schema, piece, callback); }, after: function(callback) { return self.afterConvert(piece, callback); } }, function(err) { if (err) { return callback(err); } var verb = piece._id ? 'update' : 'insert'; if (self.copying) { // Give the server side a chance to copy in any // anonymous areas, which would not be a part // of the schema piece._copyingId = self.copying._id; } self.api(verb, piece, function(result) { if (result.status !== 'ok') { self.displayError(result); return callback('error'); } self.unsavedChanges = false; // Make the saved piece available to methods like copy() self.savedPiece = result.data; return self.displayResponse(result, callback); }, function() { // This is a network error. No useful information // is available, display a simple notification apos.notify('An error occurred. Please try again', { type: 'error', dismiss: true }); return callback(err); }); }); }; // Calls getErrorMessage with result.status and passes the // returned string to apos.notify. This method is a good // candidate for overrides because it has access to the // entire result object. Invoked when result.status // is not `ok`. self.displayError = function(result) { apos.notify(self.getErrorMessage(result.status), { type: 'error' }); }; self.getErrorMessage = function(err, result) { apos.utils.error(err); return 'An error occurred. Please try again.'; }; self.beforeConvert = function(piece, callback) { return setImmediate(callback); }; self.afterConvert = function(piece, callback) { return setImmediate(callback); }; // Update the display in response to this item being saved. // // If the piece is brand new and the server provided // a `_url` property and set `contextual: true` for this // type of piece, or the piece has been updated and // apos.pages.piece._id (the in-context piece) matches the // id of the piece just edited, go to `_url`. // // In any case, the main content area is refreshed and the manage // view, if open, refreshes its list (`apos.change` is invoked). // This will all make sense if the URL hasn't changed, and do no // harm if it has. self.displayResponse = function(result, callback) { if (self.manager.options.contextual) { if ((self.options.create && result.data._url) || (apos.contextPiece && result.data._url && (result.data._id === apos.contextPiece._id))) { // If the response contains a `_url`, we should redirect to the // _url to edit the piece contextually window.location.href = result.data._url; } } if (self.options.create) { // apos event so the chooser can figure out it should auto-select this apos.emit('pieceInserted', result.data); } apos.change(result.data); return setImmediate(callback); }; self.onChange = function(e) { if (!self.unsavedChanges) { self.unsavedChanges = true; } }; self.trash = function($el, next) { if (self.trashing || !confirm("Are you sure you want to trash this " + self.options.label + "?")) { return; } var piece = { _id: self._id }; self.trashing = true; $el.addClass('apos-busy'); self.api('trash', piece, function(result) { self.trashing = false; $el.removeClass('apos-busy'); if (result.status !== 'ok') { apos.notify('An error occurred. Please try again.', { type: 'error', dismiss: true }); if (next) { return next('error'); } return; } apos.change(self.name); apos.modalSupport.closeTopModal(); if (next) { return next(null); } // Redirect to list view if we are on the the page of piece we deleted if (apos.contextPiece && apos.contextPiece._id === piece._id) { window.location.href = apos.pages.page._url; } }, function(err) { self.trashing = false; $el.removeClass('apos-busy'); apos.notify('An error occurred. Please try again', { type: 'error', dismiss: true }); if (next) { return next(err); } }); }; self.rescue = function($el, next) { if (self.rescuing) { return; } self.rescuing = true; var piece = { _id: self._id }; $el.addClass('apos-busy'); self.api('rescue', piece, function(result) { self.rescuing = false; if (result.status !== 'ok') { apos.notify('An error occurred. Please try again.', { type: 'error', dismiss: true }); if (next) { return next('error'); } return; } apos.change(self.name); apos.modalSupport.closeTopModal(); if (next) { return next(null); } }, function(err) { $el.removeClass('apos-busy'); apos.notify('An error occurred. Please try again', { type: 'error', dismiss: true }); if (next) { return next(err); } }); }; self.versions = function($el) { apos.versions.edit(self._id, function() { // After reverting, the easiest way to roll back the // editor modal is to just cancel it and open another one // with the same moog type and options. We also use // apos.change to signal to the list view and anything // else that cares. -Tom self.hide(); apos.change(self.name); apos.create(self.__meta.name, self.options); }); }; // Save this modal, then open a new modal to create a new piece of // this type that starts out as a copy of the current piece self.copy = function($el) { // We must save successfully before we can start a new editor for the copy // Stub out displayResponse so contextual pieces don't refresh the page, // preventing our callback from ever being invoked var superDisplayResponse = self.displayResponse; self.displayResponse = function(result, callback) { return callback(null); }; return self.save(function(err) { // Restore displayResponse in case this modal lives on self.displayResponse = superDisplayResponse; if (err) { return; } // This modal should close now, launch a new modal // for a new piece var _options = _.clone(self.options); _options.copying = self.savedPiece; _options.create = true; delete _options._id; delete _options.source; apos.create(self.__meta.name, _options); }); }; var superAfterHide = self.afterHide; self.afterHide = function() { superAfterHide(); if (self._id) { apos.docs.unlock(self._id, function() {}); } }; } });