diff --git a/ui/jstests/test_manage_taxonomies.jsx b/ui/jstests/test_manage_taxonomies.jsx index 5f43eac8..7e079741 100644 --- a/ui/jstests/test_manage_taxonomies.jsx +++ b/ui/jstests/test_manage_taxonomies.jsx @@ -36,6 +36,37 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', } ] }; + var learningResourceTypes = { + "count": 8, + "next": null, + "previous": null, + "results": [ + { + "name": "course" + }, + { + "name": "chapter" + }, + { + "name": "sequential" + }, + { + "name": "vertical" + }, + { + "name": "html" + }, + { + "name": "video" + }, + { + "name": "discussion" + }, + { + "name": "problem" + } + ] + }; /** * Common assertions for Add terms, use in term and taxonomy * components tests @@ -63,39 +94,9 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', assert.equal(itemList.length, 3); return devItems; } + QUnit.module('Test taxonomies panel', { beforeEach: function() { - var learningResourceTypes = { - "count": 8, - "next": null, - "previous": null, - "results": [ - { - "name": "course" - }, - { - "name": "chapter" - }, - { - "name": "sequential" - }, - { - "name": "vertical" - }, - { - "name": "html" - }, - { - "name": "video" - }, - { - "name": "discussion" - }, - { - "name": "problem" - } - ] - }; var listOfVocabularies = { "count": 1, "next": null, @@ -126,11 +127,6 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', "label": "test", "weight": 1 }; - var duplicateVocabularyResponse = { - "non_field_errors":[ - "The fields repository, name must make a unique set." - ] - }; TestUtils.setup(); TestUtils.initMockjax({ url: "/api/v1/repositories/repo/vocabularies/easy/terms/", @@ -154,19 +150,6 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', dataType: 'json', type: "POST" }); - TestUtils.initMockjax({ - url: "/api/v1/repositories/repo2/vocabularies/", - type: "POST", - status: 400 - }); - TestUtils.initMockjax({ - url: "/api/v1/repositories/repo3/vocabularies/", - contentType: "application/json; charset=utf-8", - responseText: duplicateVocabularyResponse, - dataType: 'json', - type: "POST", - status: 400 - }); TestUtils.initMockjax({ url: "/api/v1/repositories/repo/vocabularies/", contentType: "application/json; charset=utf-8", @@ -476,12 +459,20 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', var addTermCalled = 0; var deleteVocabularyCalled = 0; + var editVocabularyButton; + var editVocabularyCalled = 0; + var addTerm = function() { addTermCalled += 1; }; var deleteVocabulary = function() { deleteVocabularyCalled += 1; }; + + var editVocabulary = function() { + editVocabularyCalled += 1; + }; + var reportMessage = function() {}; var refreshCount = 0; @@ -502,6 +493,12 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', component, 'panel-body' ); + var buttons = React.addons.TestUtils. + scryRenderedDOMComponentsWithClass( + component, + 'fa-pencil' + ); + editVocabularyButton = buttons[0]; var itemList = React.addons.TestUtils. scryRenderedDOMComponentsWithTag( devItems, @@ -542,12 +539,26 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', component.state.newTermLabel ); React.addons.TestUtils.Simulate.keyUp(textbox, {key: "x"}); - assert.equal(addTermCalled, 0); + component.forceUpdate(function () { + assert.equal(addTermCalled, 0); - React.addons.TestUtils.Simulate.keyUp(textbox, {key: "Enter"}); - waitForAjax(1, function () { - assert.equal(addTermCalled, 1); - done(); + React.addons.TestUtils.Simulate.click(editVocabularyButton); + component.forceUpdate(function () { + assert.equal(editVocabularyCalled, 1); + node = React.findDOMNode(component); + var textbox = $(node).find("input")[0]; + assert.equal( + 'test12', + component.state.newTermLabel + ); + React.addons.TestUtils.Simulate.keyUp( + textbox, {key: "Enter"} + ); + waitForAjax(1, function () { + assert.equal(addTermCalled, 1); + done(); + }); + }); }); }); }); @@ -556,6 +567,7 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', React.addons.TestUtils. renderIntoDocument( + ); + } + ); + + QUnit.test('Assert that update vocabulary works', + function(assert) { + assert.ok(AddVocabulary, "class object not found"); + var done = assert.async(); + var removePanelVisibility = false; + var vocabularyUpdateResponse = { + "id": 1, + "slug": "difficulty", + "name": "TestB", + "description": "TestB", + "vocabulary_type": "f", + "required": false, + "weight": 2147483647, + "learning_resource_types": [ + "course", "chapter" + ], + "multi_terms": true, + }; + + var refreshFromAPI = function() {}; + var editVocabulary = function() {}; + var closeTaxonomyPanel = function() {}; + var updateParent = function(vocab) { + assert.equal(vocabulary.id, vocab.id); + assert.notEqual(vocabulary.name, vocab.name); + assert.notEqual(vocabulary.description, vocab.description); + assert.notEqual( + vocabulary.learning_resource_types.length, + vocab.learning_resource_types.length + ); + }; + + //update vocabulary + TestUtils.initMockjax({ + url: "/api/v1/repositories/repo4/vocabularies/" + + vocabulary.slug + "/", + contentType: "application/json; charset=utf-8", + responseText: vocabularyUpdateResponse, + dataType: 'json', + type: "PATCH" + }); + + var afterMount = function(component) { + var formNode = React.addons.TestUtils. + findRenderedDOMComponentWithClass( + component, + 'form-horizontal' + ); + assert.ok(formNode); + //test form submission + var inputNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'input' + ); + var buttonNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'button' + ); + var updateButton = buttonNodes[0]; + var inputVocabularyName = inputNodes[0]; + var inputVocabularyDesc = inputNodes[1]; + var checkboxCourse = inputNodes[2]; + React.addons.TestUtils.Simulate.change( - inputVocabularyDesc, {target: {value: "TestB"}} + inputVocabularyName, {target: {value: "TestB"}} + ); + component.forceUpdate(function() { + assert.equal(component.state.name, "TestB"); + React.addons.TestUtils.Simulate.change( + inputVocabularyDesc, {target: {value: "TestB"}} + ); + component.forceUpdate(function () { + assert.equal(component.state.description, "TestB"); + React.addons.TestUtils.Simulate.change( + checkboxCourse, + {target: {value: 'course', checked: true}} + ); + component.forceUpdate(function () { + React.addons.TestUtils.Simulate.click(updateButton); + component.forceUpdate(function() { + waitForAjax(1, function() { + //testing state is reset + assert.equal( + component.state.name, + '' + ); + assert.equal( + component.state.description, + '' + ); + assert.equal( + component.state.vocabularyType, + 'm' + ); + assert.equal( + component.state.learningResourceTypes.length, + 0 + ); + assert.equal(component.state.multiTerms, false); + done(); + }); + }); + }); + }); + }); + }; + React.addons.TestUtils. + renderIntoDocument( + ); + } + ); + + QUnit.test('Assert that update vocabulary fail with name ' + + 'or description blank', + function(assert) { + assert.ok(AddVocabulary, "class object not found"); + var removePanelVisibility = false; + var refreshFromAPI = function() {}; + var closeTaxonomyPanel = function () { + }; + var editVocabulary = function () { + }; + var updateParent = function () { + }; + var afterMount = function(component) { + var formNode = React.addons.TestUtils. + findRenderedDOMComponentWithClass( + component, + 'form-horizontal' + ); + assert.ok(formNode); + //test form submission + var inputNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'input' + ); + var buttonNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'button' + ); + var updateButton = buttonNodes[0]; + var inputVocabularyName = inputNodes[0]; + var inputVocabularyDesc = inputNodes[1]; + React.addons.TestUtils.Simulate.change( - checkboxCourse, - {target: {value: 'course', checked: true}} + inputVocabularyName, {target: {value: ""}} ); - React.addons.TestUtils.Simulate.submit(formNode); - waitForAjax(1, function() { - // Error is caused by a 400 status code - assert.deepEqual( - component.state.message, - { - error: 'A Vocabulary named "" already exists.' + - ' Please choose a different name.' - } + component.forceUpdate(function() { + assert.equal(component.state.name, ""); + React.addons.TestUtils.Simulate.change( + inputVocabularyDesc, {target: {value: "TestB"}} + ); + component.forceUpdate(function () { + assert.equal(component.state.description, "TestB"); + React.addons.TestUtils.Simulate.click(updateButton); + component.forceUpdate(function () { + assert.deepEqual( + component.state.message, + {error: 'Please enter vocabulary name.'} + ); + React.addons.TestUtils.Simulate.change( + inputVocabularyName, {target: {value: "Name of colours"}} + ); + component.forceUpdate(function () { + assert.equal(component.state.name, "Name of colours"); + React.addons.TestUtils.Simulate.change( + inputVocabularyDesc, {target: {value: ""}} + ); + component.forceUpdate(function () { + assert.equal(component.state.description, ""); + React.addons.TestUtils.Simulate.click(updateButton); + component.forceUpdate(function () { + assert.deepEqual( + component.state.message, + {error: 'Please enter vocabulary description.'} + ); + }); + }); + }); + }); + }); + }); + }; + React.addons.TestUtils. + renderIntoDocument( + + ); + } + ); + + QUnit.test('Assert that ajax call fail in update vocabulary', + function(assert) { + assert.ok(AddVocabulary, "class object not found"); + var done = assert.async(); + var closeTaxonomyPanel = function() {}; + var editVocabulary = function() {}; + var updateParent = function() {}; + var refreshCount = 0; + var refreshFromAPI = function() { + refreshCount++; + }; + + TestUtils.initMockjax({ + url: '/api/v1/repositories/repo' + + '/vocabularies/' + vocabulary.slug + "/", + type: "PATCH", + status: 400 + }); + + var removePanelVisibility = false; + var afterMount = function(component) { + var formNode = React.addons.TestUtils. + findRenderedDOMComponentWithClass( + component, + 'form-horizontal' + ); + assert.ok(formNode); + //test form submission + var inputNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'input' + ); + var buttonNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'button' + ); + var updateButton = buttonNodes[0]; + var inputVocabularyName = inputNodes[0]; + var inputVocabularyDesc = inputNodes[1]; + var checkboxChapter = inputNodes[2]; + + React.addons.TestUtils.Simulate.change( + inputVocabularyName, {target: {value: "TestB"}} + ); + component.forceUpdate(function() { + assert.equal(component.state.name, "TestB"); + React.addons.TestUtils.Simulate.change( + inputVocabularyDesc, {target: {value: "TestB"}} ); assert.equal(refreshCount, 0); - done(); + component.forceUpdate(function() { + assert.equal(component.state.description, "TestB"); + React.addons.TestUtils.Simulate.change( + checkboxChapter, + {target: {value: 'course', checked: true}} + ); + component.forceUpdate(function() { + React.addons.TestUtils.Simulate.click(updateButton); + component.forceUpdate(function() { + waitForAjax(1, function() { + // Error is caused by a 400 status code + assert.deepEqual( + component.state.message, + {error: "There was a problem with updating the Vocabulary."} + ); + done(); + }); + }); + }); + }); }); }; React.addons.TestUtils. renderIntoDocument( + ); + } + ); + + QUnit.test('Assert that update vocabulary works in TaxonomyComponent', + function(assert) { + assert.ok(TaxonomyComponent, "class object not found"); + var vocabularyUpdateResponse = { + "id": 1, + "slug": "difficulty", + "name": "TestA", + "description": "TestA", + "vocabulary_type": "f", + "required": false, + "weight": 2147483647, + "learning_resource_types": [ + "course", "chapter" + ], + "multi_terms": true, + }; + TestUtils.initMockjax({ + url: "/api/v1/repositories/repo/vocabularies/" + + vocabulary.slug + "/", + contentType: "application/json; charset=utf-8", + responseText: vocabularyUpdateResponse, + dataType: 'json', + type: "PATCH" + }); + var removePanelVisibility = false; + var setVocabularyActionTabName = function() {}; + var closeTaxonomyPanel = function() {}; + var refreshCount = 0; + var refreshFromAPI = function() { + refreshCount++; + }; + + var done = assert.async(); + var afterMount = function(component) { + waitForAjax(2, function() { + assert.equal( + component.state.vocabularies.length, + 1 + ); + var buttons = React.addons.TestUtils. + scryRenderedDOMComponentsWithClass( + component, + 'fa-pencil' + ); + var editVocabularyButton = buttons[0]; + React.addons.TestUtils.Simulate.click(editVocabularyButton); + component.forceUpdate(function() { + var formNode = React.addons.TestUtils. + findRenderedDOMComponentWithClass( + component, + 'form-horizontal' + ); + assert.ok(formNode); + //test form submission + var inputNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'input' + ); + var buttonForm = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'button' + ); + var updateButton = buttonForm[0]; + var inputVocabularyName = inputNodes[0]; + var inputVocabularyDesc = inputNodes[1]; + React.addons.TestUtils.Simulate.change( + inputVocabularyName, {target: {value: "TestA"}} + ); + component.forceUpdate(function() { + React.addons.TestUtils.Simulate.change( + inputVocabularyDesc, {target: {value: "TestA"}} + ); + component.forceUpdate(function() { + React.addons.TestUtils.Simulate.click(updateButton); + component.forceUpdate(function() { + waitForAjax(1, function() { + assert.equal( + component.state.vocabularies.length, + 1 + ); + assert.equal( + component.state.vocabularies[0].vocabulary.name, + vocabularyUpdateResponse.name + ); + assert.equal( + component.state.vocabularies[0].vocabulary.id, + vocabularyUpdateResponse.id + ); + assert.equal( + component.state.vocabularies[0].vocabulary.description, + vocabularyUpdateResponse.description + ); + done(); + }); + }); + }); + }); + }); + }); + }; + React.addons.TestUtils.renderIntoDocument + ( + + ); + } + ); + + QUnit.test('Assert that taxonomy panel close', + function(assert) { + assert.ok(TaxonomyComponent, "class object not found"); + var done = assert.async(); + var removePanelVisibility = true; + var closeTaxonomyPanelCount = 0; + var setVocabularyActionTabNameCount = 0; + var closeTaxonomyPanel = function() { + closeTaxonomyPanelCount += 1; + }; + var setVocabularyActionTabName = function() { + setVocabularyActionTabNameCount += 1; + }; + var refreshCount = 0; + var refreshFromAPI = function() { + refreshCount++; + }; + var afterMount = function(component) { + assert.equal(closeTaxonomyPanelCount, 1); + waitForAjax(2, function() { + assert.equal( + component.state.vocabularies.length, + 1 + ); + var buttons = React.addons.TestUtils. + scryRenderedDOMComponentsWithClass( + component, + 'fa-pencil' + ); + var editVocabularyButton = buttons[0]; + React.addons.TestUtils.Simulate.click(editVocabularyButton); + component.forceUpdate(function() { + var formNode = React.addons.TestUtils. + findRenderedDOMComponentWithClass( + component, + 'form-horizontal' + ); + var messageNode = React.addons.TestUtils. + findRenderedDOMComponentWithClass( + component, + 'alert-dismissible' + ); + assert.ok(formNode); + //test form submission + var inputNodes = React.addons.TestUtils. + scryRenderedDOMComponentsWithTag( + formNode, + 'input' + ); + var inputVocabularyName = inputNodes[0]; + var inputVocabularyDesc = inputNodes[1]; + // Test for user made changes on edit mode and try exit + // panel without saving changes. Assert that he will get a warning + // message to save changes or cancel edit mode. + React.addons.TestUtils.Simulate.change( + inputVocabularyName, {target: {value: "TestA"}} + ); + component.forceUpdate(function() { + React.addons.TestUtils.Simulate.change( + inputVocabularyDesc, {target: {value: "TestA"}} + ); + component.forceUpdate(function() { + assert.equal( + $(React.findDOMNode(messageNode)).html(), + 'Please save changes or cancel before exit.' + ); + done(); + }); + }); + }); + }); + }; + React.addons.TestUtils.renderIntoDocument + ( + ); @@ -1539,7 +2124,10 @@ define(['QUnit', 'jquery', 'manage_taxonomies', 'react', function(assert) { var container = document.createElement("div"); assert.equal(0, $(container).find("input").size()); - ManageTaxonomies.loader("repo", function() {}, function() {}, container); + ManageTaxonomies.loader( + "repo", container, function() {}, function() {}, function() {}, + function() {}, function() {} + ); assert.equal(5, $(container).find("input").size()); } ); diff --git a/ui/static/ui/css/mit-lore.css b/ui/static/ui/css/mit-lore.css index 9601c055..e15d0ccc 100644 --- a/ui/static/ui/css/mit-lore.css +++ b/ui/static/ui/css/mit-lore.css @@ -591,3 +591,11 @@ input.repo-page-status { -ms-user-select: none; user-select: none; } + +.hide-vocab-cancel { + display: none; +} + +.show-vocab-cancel { + display: inline; +} diff --git a/ui/static/ui/js/listing.js b/ui/static/ui/js/listing.js index bddcf23a..6d49b506 100644 --- a/ui/static/ui/js/listing.js +++ b/ui/static/ui/js/listing.js @@ -82,6 +82,30 @@ define('listing', }); } + var hideTaxonomyPanel = function() { + $('.cd-panel-2').removeClass('is-visible'); + }; + + var setVocabularyActionTabName = function(isEditMode) { + var actionTabName = 'Add Vocabulary'; + if (isEditMode) { + actionTabName = 'Edit Vocabulary'; + } + $('.add-edit-vocabulary-tab').html(actionTabName); + }; + + var loadManageTaxonomies = function (isHideTaxonomyPanel) { + ManageTaxonomies.loader( + repoSlug, + $('#taxonomy-component')[0], + isHideTaxonomyPanel, + hideTaxonomyPanel, + showConfirmationDialog, + setVocabularyActionTabName, + refreshFromAPI + ); + }; + $('[data-toggle=popover]').popover(); //Close panels on escape keypress $(document).keyup(function(event) { @@ -90,7 +114,7 @@ define('listing', $('.cd-panel').removeClass('is-visible'); } if ($('.cd-panel-2').hasClass('is-visible')) { - $('.cd-panel-2').removeClass('is-visible'); + loadManageTaxonomies(true); } if ($('.cd-panel-exports').hasClass('is-visible')) { $('.cd-panel-exports').removeClass('is-visible'); @@ -114,6 +138,7 @@ define('listing', //open the lateral panel $('.btn-taxonomies').on('click', function (event) { event.preventDefault(); + loadManageTaxonomies(false); $('.cd-panel-2').addClass('is-visible'); }); @@ -121,7 +146,7 @@ define('listing', $('.cd-panel-2').on('click', function (event) { if ($(event.target).is('.cd-panel-2') || $(event.target).is('.cd-panel-close')) { - $('.cd-panel-2').removeClass('is-visible'); + loadManageTaxonomies(true); event.preventDefault(); } }); @@ -493,11 +518,6 @@ define('listing', // Initial refresh to populate page. refreshFromAPI(); - ManageTaxonomies.loader( - repoSlug, - refreshFromAPI, - showConfirmationDialog, - $('#taxonomy-component')[0]); }; return { diff --git a/ui/static/ui/js/manage_taxonomies.jsx b/ui/static/ui/js/manage_taxonomies.jsx index 7c627223..7f0cd418 100644 --- a/ui/static/ui/js/manage_taxonomies.jsx +++ b/ui/static/ui/js/manage_taxonomies.jsx @@ -140,7 +140,7 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'],

- + @@ -209,6 +209,9 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], }); } }, + onEdit: function() { + this.props.editVocabulary(this.props.vocabulary); + }, onKeyUp: function(e) { if (e.key === "Enter") { this.handleAddTermClick(); @@ -259,6 +262,7 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], updateTerm={thiz.props.updateTerm} vocabulary={obj.vocabulary} deleteVocabulary={thiz.props.deleteVocabulary} + editVocabulary={thiz.props.editVocabulary} terms={obj.terms} key={obj.vocabulary.slug} repoSlug={repoSlug} @@ -295,11 +299,11 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], vocabularyType: 'm', learningResourceTypes: [], multiTerms: false, + slug: '' }; }, updateLearningResourceType: function(e) { var checkedType = e.target.value; - var newTypes; if (e.target.checked) { if (!_.includes(this.state.learningResourceTypes, checkedType)) { @@ -323,9 +327,46 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], updateMultiTerms: function(e) { this.setState({multiTerms: e.target.checked}); }, + showErrorMessage: function(errorText) { + // Condition to avoid infinite loop (render again for same message) + if (!_.isEmpty(this.state.message)) { + if (!_.isEmpty(this.state.message.error) && + !_.isEqual(this.state.message.error, errorText)) { + this.setState({ + message: { + error: errorText + } + }); + } + } else { + this.setState({ + message: { + error: errorText + } + }); + } + }, submitForm: function(e) { - var API_ROOT_VOCAB_URL = '/api/v1/repositories/' + this.props.repoSlug + + if (_.isEmpty(this.state.name)) { + this.showErrorMessage('Please enter vocabulary name.'); + return; + } + + if (_.isEmpty(this.state.description)) { + this.showErrorMessage('Please enter vocabulary description.'); + return; + } + + var API_ROOT_VOCAB_URL; + var method = "POST"; + if (this.state.slug) { + API_ROOT_VOCAB_URL = '/api/v1/repositories/' + this.props.repoSlug + + '/vocabularies/' + this.state.slug + "/"; + method = "PATCH"; + } else { + API_ROOT_VOCAB_URL = '/api/v1/repositories/' + this.props.repoSlug + '/vocabularies/'; + } e.preventDefault(); var thiz = this; var vocabularyData = { @@ -339,7 +380,7 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], }; $.ajax({ - type: "POST", + type: method, url: API_ROOT_VOCAB_URL, data: JSON.stringify(vocabularyData), contentType: "application/json" @@ -350,41 +391,111 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], for (i = 0; i < jsonData.non_field_errors.length ; i++) { if (jsonData.non_field_errors[i] === 'The fields repository, name must make a unique set.') { - thiz.setState({ - message: { - error: 'A Vocabulary named "' + vocabularyData.name + - '" already exists. Please choose a different name.' - } - }); + thiz.showErrorMessage('A Vocabulary named "' + + vocabularyData.name + '" already exists.' + + ' Please choose a different name.'); break; } } } else { - thiz.setState({ - message: { - error: 'There was a problem adding the Vocabulary.' - } - }); + var message = 'There was a problem adding the Vocabulary.'; + if (thiz.isEditModeOpen()) { + message = 'There was a problem with updating the Vocabulary.'; + } + thiz.showErrorMessage(message); } }).done(function(data) { // Reset state (and eventually update the vocab tab + var isEditMode = thiz.isEditModeOpen(); thiz.props.updateParent(data); - thiz.replaceState(thiz.getInitialState()); - // Switch to taxonomy panel and scroll to new vocab (bottom) - $('[href=#tab-taxonomies]').tab('show'); - var scrollableDiv = $( - ".cd-panel-2 .cd-panel-container .cd-panel-content" - ); - scrollableDiv.animate( - {scrollTop: scrollableDiv.prop('scrollHeight')}, - 500 - ); + thiz.resetFrom(); + if (!isEditMode) { + var scrollableDiv = $( + ".cd-panel-2 .cd-panel-container .cd-panel-content" + ); + scrollableDiv.animate( + {scrollTop: scrollableDiv.prop('scrollHeight')}, + 500 + ); + } thiz.props.refreshFromAPI(); }); }, + isEditModeOpen: function() { + var isEditMode = false; + if (this.state.slug) { + isEditMode = true; + } + return isEditMode; + }, + resetFrom: function(event) { + if (event) { + event.preventDefault(); + } + this.replaceState(this.getInitialState()); + this.props.editVocabulary(undefined); //reset vocabulary in edit mode + this.setState({shouldRenderComponent: false}); + }, + componentWillMount: function() { + this.updateStateAndHandleClosePanel(this.props); + }, + updateStateAndHandleClosePanel: function(props) { + var unSaveErrorMessage = 'Please save changes or cancel before exit.'; + if ( + !_.isEmpty(props.vocabulary) && + !props.removePanelVisibility + ) { + //update state for edit mode + this.setState({ + name: props.vocabulary.name, + description: props.vocabulary.description, + vocabularyType: props.vocabulary.vocabulary_type, + learningResourceTypes: props.vocabulary.learning_resource_types, + multiTerms: props.vocabulary.multi_terms, + slug: props.vocabulary.slug + }); + } + // Before exit check if their is any change in manage taxonomy form + // if there is a change then give warning else close panel + if (props.removePanelVisibility) { + if (!_.isEmpty(props.vocabulary)) { + if (!_.isEqual(props.vocabulary.name, this.state.name)) { + this.showErrorMessage(unSaveErrorMessage); + } else if (!_.isEqual(props.vocabulary.description, + this.state.description)) { + this.showErrorMessage(unSaveErrorMessage); + } else if (!_.isEqual(props.vocabulary.vocabulary_type, + this.state.vocabularyType)) { + this.showErrorMessage(unSaveErrorMessage); + } else if (!_.isEqual(props.vocabulary.learning_resource_types, + this.state.learningResourceTypes)) { + this.showErrorMessage(unSaveErrorMessage); + } else if (!_.isEqual(props.vocabulary.multi_terms, + this.state.multiTerms)) { + this.showErrorMessage(unSaveErrorMessage); + } else if (!_.isEqual(props.vocabulary.slug, this.state.slug)) { + this.showErrorMessage(unSaveErrorMessage); + }else { + this.resetFrom(); + props.closeTaxonomyPanel(); + } + } else { + props.closeTaxonomyPanel(); + } + } + }, + componentWillReceiveProps: function(nextProps) { + this.updateStateAndHandleClosePanel(nextProps); + }, render: function() { var thiz = this; + var cancelBtnClass = "btn btn-lg btn-primary hide-vocab-cancel"; + var submitButtonText = "Save"; + if (this.props.vocabulary) { + cancelBtnClass = "btn btn-lg btn-primary show-vocab-cancel"; + submitButtonText = "Update"; + } var checkboxes = _.map(this.props.learningResourceTypes, function(type) { var checked = _.includes(thiz.state.learningResourceTypes, type); return ( @@ -402,7 +513,7 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], }); return ( -
+

- +

); @@ -461,17 +575,43 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], getInitialState: function() { return { vocabularies: [], - learningResourceTypes: [] + learningResourceTypes: [], + vocabularyInEdit: undefined, + removePanelVisibility: false }; }, + editVocabulary: function(vocab) { + this.setState({vocabularyInEdit: vocab}); + if (vocab) { + $('[href=#tab-vocab]').tab('show'); + this.props.setVocabularyActionTabName(true); + } else { + $('[href=#tab-taxonomies]').tab('show'); + this.props.setVocabularyActionTabName(false); + } + }, addVocabulary: function(vocab) { // Wrap vocab in expected structure - var newVocab = { - terms: [], - vocabulary: vocab - }; - var vocabularies = this.state.vocabularies; - this.setState({vocabularies: vocabularies.concat([newVocab])}); + var editIndex = -1; + var vocabularies = _.map(this.state.vocabularies, function(tuple) { + return { + terms: tuple.terms, + vocabulary: tuple.vocabulary + }; + }); + editIndex = _.findIndex(vocabularies, function(vocabularyObj) { + return vocabularyObj.vocabulary.id === vocab.id; + }); + if (editIndex > -1) { + vocabularies[editIndex].vocabulary = vocab; + this.setState({vocabularies: vocabularies}); + } else { + var newVocab = { + terms: [], + vocabulary: vocab + }; + this.setState({vocabularies: vocabularies.concat([newVocab])}); + } }, addTerm: function(vocabSlug, newTerm) { var vocabularies = _.map(this.state.vocabularies, function(tuple) { @@ -516,6 +656,7 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'],
); }, + closeTaxonomyPanel: function() { + this.props.closeTaxonomyPanel(); + }, + componentWillReceiveProps: function(nextProps) { + this.setState( + { + removePanelVisibility: nextProps.removePanelVisibility + } + ); + }, + componentWillMount : function() { + this.setState( + { + removePanelVisibility: this.props.removePanelVisibility + } + ); + }, componentDidMount: function() { var thiz = this; @@ -567,13 +729,18 @@ define('manage_taxonomies', ['react', 'lodash', 'jquery', 'utils', 'bootstrap'], 'AddTermsComponent': AddTermsComponent, 'AddVocabulary': AddVocabulary, 'TaxonomyComponent': TaxonomyComponent, - 'loader': function (repoSlug, refreshFromAPI, showConfirmationDialog, - container) { + 'loader': function ( + repoSlug, container, removePanelVisibility, closeTaxonomyPanel, + showConfirmationDialog, setVocabularyActionTabName, refreshFromAPI + ) { React.render( - , + renderConfirmationDialog={showConfirmationDialog} + setVocabularyActionTabName={setVocabularyActionTabName} + />, container ); } diff --git a/ui/templates/includes/taxonomy_panel.html b/ui/templates/includes/taxonomy_panel.html index 8a670d87..f9d89d27 100644 --- a/ui/templates/includes/taxonomy_panel.html +++ b/ui/templates/includes/taxonomy_panel.html @@ -1,6 +1,6 @@
-

Manage Taxonomies

+

Manage Taxonomy

Close
@@ -9,13 +9,12 @@

Manage Taxonomies

diff --git a/ui/templates/repo_base.html b/ui/templates/repo_base.html index 4775a3d3..064aa007 100644 --- a/ui/templates/repo_base.html +++ b/ui/templates/repo_base.html @@ -8,7 +8,7 @@ {% endif %} {% if 'manage_taxonomy' in perms_on_cur_repo %}
  • - Manage Taxonomies + Manage Taxonomy
  • {% endif %}