// Copyright 2017 The Nomulus Authors. All Rights Reserved. // // 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. goog.provide('registry.EditItem'); goog.require('goog.dom'); goog.require('goog.dom.classlist'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.soy'); goog.require('registry.Component'); goog.require('registry.soy.console'); goog.require('registry.util'); goog.forwardDeclare('registry.Console'); /** * An editable item, with Edit and Save/Cancel buttons in the appbar. * @param {!registry.Console} cons * @param {function()} itemTmpl * @constructor * @extends {registry.Component} */ registry.EditItem = function(cons, itemTmpl) { registry.EditItem.base(this, 'constructor', cons); /** * @type {!Function} */ this.itemTmpl = itemTmpl; /** * Optional current target resource id. * @type {?string} */ this.id = null; /** * Transitional id for next resource during create. * @type {?string} */ this.nextId = null; }; goog.inherits(registry.EditItem, registry.Component); /** @override */ registry.EditItem.prototype.bindToDom = function(id) { registry.EditItem.base(this, 'bindToDom', id); this.id = id; this.nextId = null; this.setupAppbar(); }; /** Setup appbar save/edit buttons. */ registry.EditItem.prototype.setupAppbar = function() { goog.soy.renderElement(goog.dom.getRequiredElement('reg-appbar'), registry.soy.console.appbarButtons); goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-add'), goog.events.EventType.CLICK, goog.bind(this.add, this)); goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-edit'), goog.events.EventType.CLICK, goog.bind(this.edit, this)); goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-save'), goog.events.EventType.CLICK, goog.bind(this.save, this)); goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-cancel'), goog.events.EventType.CLICK, goog.bind(this.cancel, this)); goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-back'), goog.events.EventType.CLICK, goog.bind(this.back, this)); if (this.id) { registry.util.setVisible('reg-app-btns-edit', true); } else { registry.util.setVisible('reg-app-btn-add', true); } }; /** * Retrieve item from server. Overrides should callback to * `#handleFetchItem(string, !Object)`. * @param {string} id item id. */ registry.EditItem.prototype.fetchItem = goog.abstractMethod; /** * Handle result decoding and display. * @param {string} id The requested ID/name, for error message. * @param {!Object} rsp The requested object. */ registry.EditItem.prototype.handleFetchItem = goog.abstractMethod; /** Subclasses should override to continue processing after fetch. */ registry.EditItem.prototype.processItem = function() {}; /** * Show the item. * @param {!Object} objArgs */ registry.EditItem.prototype.renderItem = function(objArgs) { goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'), this.itemTmpl, objArgs); }; /** * @return {boolean} if the component is currently being edited. */ registry.EditItem.prototype.isEditing = function() { return goog.dom.classlist.contains( goog.dom.getElement('reg-app'), goog.getCssName('editing')); }; /** * Toggles the editing state of a component. This will first hide the * elements of the `shown` class, then adds the `editing` * style to the component, then shows the elements that had the `hidden` * class. */ registry.EditItem.prototype.toggleEdit = function() { // Toggle appbar buttons. var addBtn = goog.dom.getRequiredElement('reg-app-btn-add'); var editBtns = goog.dom.getRequiredElement('reg-app-btns-edit'); var saveBtns = goog.dom.getRequiredElement('reg-app-btns-save'); var editing = goog.dom.classlist.contains(saveBtns, registry.util.cssShown); if (editing) { registry.util.setVisible(saveBtns, false); if (this.id) { registry.util.setVisible(editBtns, true); } else { registry.util.setVisible(addBtn, true); } } else { if (this.id) { registry.util.setVisible(editBtns, false); } else { registry.util.setVisible(addBtn, false); } registry.util.setVisible(saveBtns, true); } // Then page contents. var parentElt = goog.dom.getElement('reg-content'); var shownCssName = goog.getCssName('shown'); var hiddenCssName = goog.getCssName('hidden'); var shown = goog.dom.getElementsByClass(shownCssName, parentElt); var hidden = goog.dom.getElementsByClass(hiddenCssName, parentElt); for (var i = 0; i < shown.length; i++) { goog.dom.classlist.addRemove(shown[i], shownCssName, hiddenCssName); } // Then add editing styles. var editingCssName = goog.getCssName('editing'); var startingEdit = !goog.dom.classlist.contains(parentElt, editingCssName); if (startingEdit) { goog.dom.classlist.remove(parentElt, editingCssName); } else { goog.dom.classlist.add(parentElt, editingCssName); } // The show hiddens. for (var i = 0; i < hidden.length; i++) { goog.dom.classlist.addRemove(hidden[i], hiddenCssName, shownCssName); } }; /** * Subclasses should override to enhance the default model. * @return {!Object.} */ registry.EditItem.prototype.newModel = function() { return {item: {}}; }; // N.B. setting these as abstract precludes their correct binding in // setupAppbar. /** Show add item panel. */ registry.EditItem.prototype.add = function() {}; /** Go back from item to collection view. */ registry.EditItem.prototype.back = function() {}; /** Sets up initial edit model state and then called edit(objArgs). */ registry.EditItem.prototype.edit = function() { var objArgs = this.model; if (objArgs == null) { objArgs = this.newModel(); } // XXX: This is vestigial. In the new msg format, this pollutes the // server-model state. Should be carried elsewhere. objArgs.readonly = false; this.renderItem(objArgs); this.setupEditor(objArgs); this.toggleEdit(); }; /** * Save the current item. Calls either create if creating a new * object or update otherwise. */ registry.EditItem.prototype.save = function() { if (this.model == null) { this.sendCreate(); } else { this.sendUpdate(); } }; /** * Resets to non-editing, readonly state, or visits home screen if the * page was in on a create panel. */ registry.EditItem.prototype.cancel = function() { this.toggleEdit(); // XXX: The presence of a model is sufficient for non-collection pages, but an // empty id also means go to the collection in collection pages. Should // be simplified. if (this.model && this.id != '') { this.model.readonly = true; this.renderItem(this.model); } else { this.bindToDom(''); } }; /** * Called after this.renderItem(), to allow for further setup of * editing. * @param {!Object} objArgs */ registry.EditItem.prototype.setupEditor = function(objArgs) {}; // XXX: These should really take @param {object} which is the form. Alas the // only override which doesn't work this way, ResourceComponent.sendCreate // breaks this opportunity. Hmmm... /** Subclasses should extract form values and send them to the server. */ registry.EditItem.prototype.sendCreate = goog.abstractMethod; /** Subclasses should extract form values and send them to the server. */ registry.EditItem.prototype.sendUpdate = goog.abstractMethod; /** Subclasses should extract form values and send them to the server. */ registry.EditItem.prototype.sendDelete = goog.abstractMethod; /** * Subclasses should override to populate update queryParams with form * fields as needed. `queryParams.nextId` MUST be set to the * new object's ID. * @param {!Object} queryParams */ registry.EditItem.prototype.prepareUpdate = goog.abstractMethod; /** * Subclasses should provide a function to parse either XML or JSON response * from server and return a result object as described below. * @param {!Object} rsp Decoded XML/JSON response from the server. * @return {!Object} a result object describing next steps. On * success, if next is defined, visit(ret.next) is called, otherwise * if err is set, the butterbar message is set to it. */ registry.EditItem.prototype.handleUpdateResponse = goog.abstractMethod;