diff --git a/lib/Alchemy/Phrasea/Controller/Admin/Fields.php b/lib/Alchemy/Phrasea/Controller/Admin/Fields.php index f8e287e835..fc62bd5901 100644 --- a/lib/Alchemy/Phrasea/Controller/Admin/Fields.php +++ b/lib/Alchemy/Phrasea/Controller/Admin/Fields.php @@ -17,6 +17,7 @@ use Silex\Application; use Silex\ControllerProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use PHPExiftool\Exception\TagUnknown; class Fields implements ControllerProviderInterface { @@ -27,10 +28,18 @@ class Fields implements ControllerProviderInterface $app['admin.fields.controller'] = $this; $controllers->before(function(Request $request) use ($app) { - $app['firewall']->requireAccessToModule('admin') + $app['firewall'] + ->requireAccessToModule('admin') ->requireRight('bas_modify_struct'); }); + $controllers->get('/language.json', 'admin.fields.controller:getLanguage') + ->bind('admin_fields_language'); + + $controllers->get('/{sbas_id}', 'admin.fields.controller:displayApp') + ->assert('sbas_id', '\d+') + ->bind('admin_fields'); + $controllers->get('/{sbas_id}/fields', 'admin.fields.controller:listFields') ->assert('sbas_id', '\d+') ->bind('admin_fields_list'); @@ -75,9 +84,26 @@ class Fields implements ControllerProviderInterface return $controllers; } + public function getLanguage(Application $app) { + return $app->json(array( + 'something_wrong' => _('Something wrong happened, please try again or contact an admin if problem persists'), + 'created_success' => _('%s field has been created with success'), + 'deleted_success' => _('%s field has been deleted with success'), + 'are_you_sure_delete' => _('Do you really want to delete the field %s ?'), + 'validation_blank' => _('Field can not be blank'), + )); + } + + public function displayApp(Application $app, $sbas_id) { + return $app['twig']->render('/admin/fields/index.html.twig', array( + 'sbas_id' => $sbas_id, + 'js' => '' + )); + } + public function listDcFields(Application $app, Request $request) { - $data = $app['serializer']->serialize(\databox::get_available_dcfields(), 'json'); + $data = $app['serializer']->serialize(array_values(\databox::get_available_dcfields()), 'json'); return new Response($data, 200, array('content-type' => 'application/json')); } @@ -120,7 +146,7 @@ class Fields implements ControllerProviderInterface continue; } - $res[] = array( + $res[$tagname] = array( 'id' => $namespace . '/' . $tagname, 'label' => $datas['namespace'] . ' / ' . $datas['tagname'], 'value' => $datas['namespace'] . ':' . $datas['tagname'], @@ -129,6 +155,8 @@ class Fields implements ControllerProviderInterface } } + ksort($res); + return $app->json($res); } @@ -141,21 +169,40 @@ class Fields implements ControllerProviderInterface } public function createField(Application $app, Request $request, $sbas_id) { + $json = array( + 'success' => false, + 'message' => _('Something wrong happened, please try again or contact an admin if problem persists'), + 'field' => array() + ); + $headers = array(); $databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id); - $data = $this->getFieldJsonFromRequest($app, $request); - $field = \databox_field::create($app, $databox, $data['name'], $data['multi']); - $this->updateFieldWithData($app, $field, $data); - $field->save(); + try { + $field = \databox_field::create($app, $databox, $data['name'], $data['multi']); - return $app->json($field->toArray(), 201, array( - 'Location' => $app->path('admin_fields_show_field', array( - 'sbas_id' => $sbas_id, - 'id' => $field->get_id(), - )) - )); + $this->updateFieldWithData($app, $field, $data); + $field->save(); + + $json['success'] = true; + $headers['location'] = $app->path('admin_fields_show_field', array( + 'sbas_id' => $sbas_id, + 'id' => $field->get_id(), + )); + $json['message'] = _(sprintf('Tag name %s has been created successfully', $data['name'])); + $json['field'] = $field->toArray(); + } catch (\PDOException $e) { + if ($e->errorInfo[1] == 1062) { + $json['message'] = _(sprintf('Field name %s already exists', $data['name'])); + } + } catch (\Exception $e) { + if ($e instanceof \Exception_Databox_metadataDescriptionNotFound || $e->getPrevious() instanceof TagUnknown) { + $json['message'] = _(sprintf('Provided tag %s is unknown', $data['tag'])); + } + } + + return $app->json($json, 201, $headers); } public function listFields(Application $app, $sbas_id) { diff --git a/templates/web/admin/fields/index.html.twig b/templates/web/admin/fields/index.html.twig new file mode 100644 index 0000000000..f7bbb11eae --- /dev/null +++ b/templates/web/admin/fields/index.html.twig @@ -0,0 +1,27 @@ +{# empty Twig template #} + + +{% include 'admin/fields/templates.html.twig' %} + +
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/templates/web/admin/fields/templates.html.twig b/templates/web/admin/fields/templates.html.twig new file mode 100644 index 0000000000..fe6a31cd28 --- /dev/null +++ b/templates/web/admin/fields/templates.html.twig @@ -0,0 +1,201 @@ + + + + + + + + + + + + diff --git a/templates/web/admin/index.html.twig b/templates/web/admin/index.html.twig index ad05ea0b0f..ca0ecb342e 100644 --- a/templates/web/admin/index.html.twig +++ b/templates/web/admin/index.html.twig @@ -8,7 +8,7 @@ {% endblock %} {% block stylesheet %} - + {% endblock %} diff --git a/templates/web/admin/tree.html.twig b/templates/web/admin/tree.html.twig index 14bee0098d..59a05dae79 100644 --- a/templates/web/admin/tree.html.twig +++ b/templates/web/admin/tree.html.twig @@ -111,7 +111,7 @@
  • - + {% trans 'CHAMPS' %} diff --git a/www/scripts/apps/admin/fields/app.js b/www/scripts/apps/admin/fields/app.js index 887268d48a..1d911ff724 100644 --- a/www/scripts/apps/admin/fields/app.js +++ b/www/scripts/apps/admin/fields/app.js @@ -2,10 +2,48 @@ define([ 'jquery', 'underscore', 'backbone', - 'apps/admin/fields/router' -], function($, _, Backbone, Router) { + 'i18n', + 'apps/admin/fields/collections/fields', + 'apps/admin/fields/collections/vocabularies', + 'apps/admin/fields/collections/dcFields', + 'apps/admin/fields/views/list' +], function($, _, Backbone, i18n, FieldsCollection, VocabulariesCollection, DcFieldsCollection, FieldListView) { var initialize = function() { - Router.initialize(); + window.AdminFieldApp = {}; + + window.AdminFieldApp.sbas_id = $('input[name=current_sbas_id]').val(); + + var fieldsCollection = new FieldsCollection(null, { + sbas_id : window.AdminFieldApp.sbas_id + }); + + var vocabulariesCollection = new VocabulariesCollection(); + var dcFieldsCollection = new DcFieldsCollection(); + + // load strings synchronously + i18n.init({ resGetPath: '/admin/fields/language.json', getAsync: false }); + + var requests = [ + fieldsCollection.fetch(), + vocabulariesCollection.fetch(), + dcFieldsCollection.fetch() + ]; + + $.when.apply($, requests).done( + function() { + window.AdminFieldApp.vocabularyCollection = vocabulariesCollection; + window.AdminFieldApp.dcFieldsCollection = dcFieldsCollection; + + window.AdminFieldApp.fieldListView = new FieldListView({ + collection: fieldsCollection, + el: $('.left-block')[0] + }); + + window.AdminFieldApp.fieldListView.render(); + // click on first item list + _.first(window.AdminFieldApp.fieldListView.itemViews).clickAction().animate(); + } + ); }; return { diff --git a/www/scripts/apps/admin/fields/collections/dcFields.js b/www/scripts/apps/admin/fields/collections/dcFields.js new file mode 100644 index 0000000000..e4837e013b --- /dev/null +++ b/www/scripts/apps/admin/fields/collections/dcFields.js @@ -0,0 +1,17 @@ +define([ + 'underscore', + 'backbone', + 'models/dcField' +], function(_, Backbone, DcFieldModel) { + var DcFieldCollection = Backbone.Collection.extend({ + model: DcFieldModel, + url: function() { + return '/admin/fields/dc-fields'; + }, + comparator: function(item) { + return item.get("label"); + } + }); + + return DcFieldCollection; +}); diff --git a/www/scripts/apps/admin/fields/collections/fields.js b/www/scripts/apps/admin/fields/collections/fields.js index 21d2dfe9ab..4326571c0f 100644 --- a/www/scripts/apps/admin/fields/collections/fields.js +++ b/www/scripts/apps/admin/fields/collections/fields.js @@ -1,11 +1,58 @@ define([ 'underscore', 'backbone', - 'models/admin/field' + 'models/field' ], function(_, Backbone, FieldModel) { var FieldCollection = Backbone.Collection.extend({ + initialize: function(models, options) { + if (!"sbas_id" in options) { + throw "You must specify a sbasId option when creating a new field model" + } + this.sbasId = options.sbas_id; + }, model: FieldModel, - url: '/admin/fields/1/fields' + url: function() { + return '/admin/fields/' + this.sbasId + '/fields'; + }, + search: function(letters) { + if (letters === "") + return this; + + var pattern = new RegExp(letters, "gi"); + + return _(this.filter(function(data) { + return pattern.test(data.get("name")); + })); + }, + comparator: function(item) { + return item.get("sorter"); + }, + nextIndex: function(model) { + var index = this.indexOf(model); + + if (index < 0) { + throw "Model not found" + } + + if ((index + 1) === this.length) { + return null; + } + + return index + 1; + }, + previousIndex: function(model) { + var index = this.indexOf(model); + + if (index < 0) { + throw "Model not found" + } + + if (index === 0) { + return null; + } + + return index - 1; + } }); return FieldCollection; diff --git a/www/scripts/apps/admin/fields/collections/vocabularies.js b/www/scripts/apps/admin/fields/collections/vocabularies.js new file mode 100644 index 0000000000..2b86d4109d --- /dev/null +++ b/www/scripts/apps/admin/fields/collections/vocabularies.js @@ -0,0 +1,17 @@ +define([ + 'underscore', + 'backbone', + 'models/vocabulary' +], function(_, Backbone, VocabularyModel) { + var VocabularyCollection = Backbone.Collection.extend({ + model: VocabularyModel, + url: function() { + return '/admin/fields/vocabularies'; + }, + comparator: function(item) { + return item.get("name"); + } + }); + + return VocabularyCollection; +}); diff --git a/www/scripts/apps/admin/fields/main.js b/www/scripts/apps/admin/fields/main.js index 924276db79..748e149adb 100644 --- a/www/scripts/apps/admin/fields/main.js +++ b/www/scripts/apps/admin/fields/main.js @@ -1,15 +1,21 @@ require.config({ baseUrl: "/scripts", paths: { - jquery: '../assets/jquery/jquery', + jquery: '../include/minify/f=include/jslibs/jquery-1.7.1', + jqueryui: '../include/jslibs/jquery-ui-1.8.17/js/jquery-ui-1.8.17.custom.min', underscore: '../assets/underscore-amd/underscore', backbone: '../assets/backbone-amd/backbone', twig: '../assets/twig/twig', - i18n: '../assets/i18n/i18next.amd' + i18n: '../assets/i18n/i18next.amd', + bootstrap: ['//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min'] }, shim: { twig: { exports: 'Twig' + }, + bootstrap : ['jquery'], + jqueryui: { + deps: [ 'jquery' ] } } }); diff --git a/www/scripts/apps/admin/fields/router.js b/www/scripts/apps/admin/fields/router.js deleted file mode 100644 index ce49bf1060..0000000000 --- a/www/scripts/apps/admin/fields/router.js +++ /dev/null @@ -1,59 +0,0 @@ -define([ - 'jquery', - 'underscore', - 'backbone', - 'models/admin/field', - 'apps/admin/fields/views/edit', - 'apps/admin/fields/views/list', - 'apps/admin/fields/collections/fields' -], function($, _, Backbone, FieldModel, FieldEditView, FieldListView, FieldsCollection) { - var AppRouter = Backbone.Router.extend({ - routes: { - 'field/:id': "getField", - 'fields': 'showFields', - '*actions': 'defaultAction' - } - }); - - var initialize = function() { - var app_router = new AppRouter(); - - app_router.on('route:getField', function (id) { - var field = new FieldModel({id: id}); - - field.fetch().done(function() { - var fieldEditView = new FieldEditView({ - el: $('.right-block')[0], - model: field - }); - - fieldEditView.render(); - }); - }); - - app_router.on('route:showFields', function() { - var fieldsCollection = new FieldsCollection(); - fieldsCollection.fetch().done(function() { - var fieldListView = new FieldListView({ - collection: fieldsCollection, - el: $('ul#collection-fields')[0] - }); - - fieldListView.render(); - }); - }); - - app_router.on('route:defaultAction', function(actions) { - console.log('No route:', actions); - }); - - Backbone.history.start(); - - // Show fields on start up - app_router.navigate('fields', { trigger: true }); - }; - - return { - initialize: initialize - }; -}); diff --git a/www/scripts/apps/admin/fields/views/add.js b/www/scripts/apps/admin/fields/views/add.js new file mode 100644 index 0000000000..973d2cb754 --- /dev/null +++ b/www/scripts/apps/admin/fields/views/add.js @@ -0,0 +1,51 @@ +define([ + 'underscore', + 'backbone', + 'i18n' +], function( _, Backbone, i18n, bootstrap) { + var AddView = Backbone.View.extend({ + tagName: "div", + className: "add-field-block", + style: "display:none;", + events: { + "click .btn-submit-field": "createAction" + }, + initialize: function() {}, + render: function() { + var template = _.template($("#alert_template").html(), { + msg: this.msg + }); + + this.$el.after(template); + + return this; + }, + createAction: function(event) { + var self = this; + + var field = new FieldModel({ + "name": "AA" + new Date().getUTCMilliseconds(), + "tag": "IPTC:ObjectName" + }, { + sbas_id: AdminFieldApp.sbas_id + }); + + field.save(null, { + success: function(field) { + self.collection.add(field); + _.last(self.itemViews).clickAction().animate(); + new AlertView({alert: 'success', message: 'A new field has been created'}).render(); + }, + error: function() { + new AlertView({alert: 'error', message: 'Something wrong happened'}).render(); + } + }); + }, + destroy: function() { + this.remove(); + } + }); + + return AddView; +}); + diff --git a/www/scripts/apps/admin/fields/views/alert.js b/www/scripts/apps/admin/fields/views/alert.js new file mode 100644 index 0000000000..5b93fdbf85 --- /dev/null +++ b/www/scripts/apps/admin/fields/views/alert.js @@ -0,0 +1,36 @@ +define([ + 'underscore', + 'backbone', + 'i18n', + 'bootstrap' +], function(_, Backbone, i18n, bootstrap) { + var AlertView = Backbone.View.extend({ + tagName: "div", + className: "alert", + initialize: function(options) { + var self = this; + + if (options) { + this.alert = options.alert || "info"; + this.message = options.message || ""; + } + // remove view when alert is closed + this.$el.bind('closed', function () { + self.remove(); + }); + }, + render: function() { + var template = _.template($("#alert_template").html(), { + msg: this.message + }); + + this.$el.addClass("alert-" + this.alert).html(template).alert(); + + $('.block-alert').empty().append(this.$el); + + return this; + } + }); + + return AlertView; +}); \ No newline at end of file diff --git a/www/scripts/apps/admin/fields/views/dcField.js b/www/scripts/apps/admin/fields/views/dcField.js new file mode 100644 index 0000000000..2ad1310099 --- /dev/null +++ b/www/scripts/apps/admin/fields/views/dcField.js @@ -0,0 +1,29 @@ +define([ + 'underscore', + 'backbone', + 'i18n' +], function( _, Backbone, i18n, bootstrap) { + var DcFieldsView = Backbone.View.extend({ + tagName: "div", + className: "input-append", + events: { + "change select": "onChange" + }, + render: function() { + var template = _.template($("#dc_fields_template").html(), { + dces_elements: this.collection.toJSON() + }); + + this.$el.html(template); + + return this; + }, + onChange: function(e) { + var index = $(e.target)[0].selectedIndex; + this.$el.closest('table').find('.dces-help-block').empty().append(this.collection.at(index).get('definition')); + } + }); + + return DcFieldsView; +}); + diff --git a/www/scripts/apps/admin/fields/views/edit.js b/www/scripts/apps/admin/fields/views/edit.js index fb2816dc8b..f06b03c963 100644 --- a/www/scripts/apps/admin/fields/views/edit.js +++ b/www/scripts/apps/admin/fields/views/edit.js @@ -1,19 +1,133 @@ define([ - 'jquery', 'underscore', 'backbone', - 'i18n' -], function($, _, Backbone, i18n) { + 'i18n', + 'apps/admin/fields/views/alert', + 'apps/admin/fields/views/modal', + 'apps/admin/fields/views/dcField', +], function(_, Backbone, i18n, AlertView, ModalView, DcFieldView) { var FieldEditView = Backbone.View.extend({ tagName: "div", className: "field-edit", + initialize: function() { + this.model.on('change', this.render, this); + this.model.on('change:name', this.onFieldChange, this); + this.model.on('change:tag', this.onFieldChange, this); + + this.dcFieldsSubView = new DcFieldView({ + collection: window.AdminFieldApp.dcFieldsCollection + }); + }, render: function() { - this.el.innerHTML = ''; - this.el.innerHTML = Twig.render(fieldsEdit, { - field: this.model.attributes + var template = _.template($("#edit_template").html(), { + field: this.model.toJSON(), + vocabularyTypes: window.AdminFieldApp.vocabularyCollection.toJSON() + }); + + this.$el.empty().html(template); + + this.assign({ + '.dc-fields-subview' : this.dcFieldsSubView + }); + + $("#tag", this.$el).autocomplete({ + source: function(request, response) { + $.ajax({ + url: "/admin/fields/tags/search", + dataType: "json", + data: { + term: request.term + }, + success: function(data) { + response($.map(data, function(item) { + return { + label: item.label, + value: item.value + }; + })); + } + }); + } + }).val(this.model.get('tag')).autocomplete("widget").addClass("ui-autocomplete-admin-field"); + + return this; + }, + events: { + "click .delete-field": "deleteAction", + "focusout input[type=text]": "fieldChanged", + "change input[type=checkbox]": "fieldChanged", + "change select": "selectionChanged" + }, + selectionChanged: function(e) { + var field = $(e.currentTarget); + var value = $("option:selected", field).val(); + var data = {}; + data[field.attr('id')] = value; + this.model.set(data); + }, + fieldChanged: function(e) { + console.log('field change'); + var field = $(e.currentTarget); + var data = {}; + data[field.attr('id')] = field.is(":checkbox") ? field.is(":checked") : field.val(); + this.model.set(data); + }, + deleteAction: function() { + var self = this; + var modalView = new ModalView({ + model: this.model, + message: i18n.t("are_you_sure_delete", { postProcess: "sprintf", sprintf: [this.model.get('name')] }) + }); + var previousIndex = AdminFieldApp.fieldListView.collection.previousIndex(this.model); + var nextIndex = AdminFieldApp.fieldListView.collection.nextIndex(this.model); + var itemView; + + if (previousIndex) { + itemView = AdminFieldApp.fieldListView.itemViews[previousIndex]; + } else if (nextIndex) { + itemView = AdminFieldApp.fieldListView.itemViews[nextIndex]; + } + + modalView.render(); + modalView.on('modal:confirm', function() { + self.model.destroy({ + success: function(model, response) { + AdminFieldApp.fieldListView.collection.remove(self.model); + + if (itemView) { + itemView.clickAction().animate(); + } + + new AlertView({alert: 'info', message: i18n.t("deleted_success", { postProcess: "sprintf", sprintf: [model.get('name')] })}).render(); + }, + error: function(model, xhr) { + new AlertView({alert: 'error', message: i18n.t("something_wrong")}).render(); + } + }); }); return this; + }, + onFieldChange: function() { + console.log('on field changed'); + AdminFieldApp.fieldListView.collection.remove(this.model, {silent: true}); + AdminFieldApp.fieldListView.collection.add(this.model); + this.render(); + }, + assign: function(selector, view) { + var selectors; + if (_.isObject(selector)) { + selectors = selector; + } + else { + selectors = {}; + selectors[selector] = view; + } + if (!selectors) + return; + _.each(selectors, function(view, selector) { + view.setElement(this.$(selector)).render(); + }, this); } }); diff --git a/www/scripts/apps/admin/fields/views/list.js b/www/scripts/apps/admin/fields/views/list.js index 37c64af81b..cc2234e079 100644 --- a/www/scripts/apps/admin/fields/views/list.js +++ b/www/scripts/apps/admin/fields/views/list.js @@ -1,30 +1,145 @@ define([ - 'jquery', + 'jqueryui', 'underscore', 'backbone', - 'apps/admin/fields/views/listRow' -], function($, _, Backbone, FieldListRowView) { + 'i18n', + 'apps/admin/fields/views/listRow', + 'apps/admin/fields/views/alert', + 'models/field' +], function(jqueryui, _, Backbone, i18n, FieldListRowView, AlertView, FieldModel) { var FieldListView = Backbone.View.extend({ + events: { + "keyup #live_search": "searchAction", + "click .btn-submit-field": "createAction", + "click .btn-add-field": "toggleCreateFormAction", + "click .btn-cancel-field": "toggleCreateFormAction", + "update-sort": "onUpdateSort" + }, initialize: function() { - var that = this; - this._fieldViews = []; + // Store all single rendered views + this.itemViews = []; - this.collection.each(function(field) { - that._fieldViews.push(new FieldListRowView({ - model: field - })); - }); + _.bindAll(this, "render"); + // rerender whenever there is a change on the collection + this.collection.bind("reset", this.render, this); + this.collection.bind("add", this.render, this); + this.collection.bind("remove", this.render, this); }, render: function() { - var that = this; - $(this.el).empty(); + var template = _.template($("#item_list_view_template").html(), {}); - // Render each sub-view and append it to the parent view's element. - _(this._fieldViews).each(function(singleView) { - $(that.el).append(singleView.render().el); - }); + this.$el.empty().html(template); + + this.$listEl = $("ul#collection-fields", this.$el); + + this._renderList(this.collection); + + $("#new-source", this.$el).autocomplete({ + source: function(request, response) { + $.ajax({ + url: "/admin/fields/tags/search", + dataType: "json", + data: { + term: request.term + }, + success: function(data) { + response($.map(data, function(item) { + return { + label: item.label, + value: item.value + }; + })); + } + }); + } + }).autocomplete("widget").addClass("ui-autocomplete-admin-field"); return this; + }, + _renderList: function(fields) { + var that = this; + + this.$listEl.empty(); + this.itemViews = []; + + fields.each(function(field) { + var singleView = new FieldListRowView({ + model: field, + id: 'field-' + field.get('id') + }); + that.$listEl.append(singleView.render().el); + that.itemViews.push(singleView); + }); + + this.$listEl.sortable({ + handle: ".handle", + start: function () { + console.log('start', AdminFieldApp.fieldEditView.model.get('id')); + }, + stop: function(event, ui) { + ui.item.trigger('drop', ui.item.index()); + } + }); + + this.$listEl.disableSelection(); + + this.$listEl.find('li:last').addClass('last'); + + return this; + }, + searchAction: function(event) { + this._renderList(this.collection.search($("#live_search", this.$el).val())); + }, + createAction: function(event) { + var self = this; + + var fieldName = $("#new-name", this.$el); + + if ('' == fieldName.val()) { + fieldName.closest('.control-group').addClass('error').find('.help-block').empty().append(i18n.t('validation_blank')); + return; + } + + var field = new FieldModel({ + "sbas-id": AdminFieldApp.sbas_id, + "name": fieldName.val(), + "tag": $("#new-source", this.$el).val(), + "multi": $("#new-multivalued", this.$el).is(':checked') + }); + + field.save(null, { + success: function(field, response, options) { + if (response.success) { + self.collection.add(field); + _.last(self.itemViews).clickAction().animate(); + new AlertView({alert: 'success', message: response.message }).render(); + } else { + new AlertView({alert: 'warning', message: response.message}).render(); + } + }, + error: function(model, xhr, options) { + new AlertView({alert: 'error', message: i18n.t("something_wrong")}).render(); + self.toggleCreateFormAction(); + } + }); + }, + toggleCreateFormAction: function(event) { + $('.add-field-block', this.$el).toggle(); + }, + onUpdateSort: function(event, model, position) { + this.collection.remove(model, {silent: true}); + + this.collection.each(function(model, index) { + var ordinal = index; + if (index >= position) ordinal += 1; + model.set('sorter', ordinal); + }); + + model.set('sorter', position); + this.collection.add(model, {at: position}); + + // update edit view + AdminFieldApp.fieldEditView.model = this.collection.find(function(el) { return el.get('id') === AdminFieldApp.fieldEditView.model.get('id') }); } }); diff --git a/www/scripts/apps/admin/fields/views/listRow.js b/www/scripts/apps/admin/fields/views/listRow.js index 06828d76e1..3195a2e343 100644 --- a/www/scripts/apps/admin/fields/views/listRow.js +++ b/www/scripts/apps/admin/fields/views/listRow.js @@ -1,19 +1,67 @@ define([ - 'jquery', 'underscore', - 'backbone' -], function($, _, Backbone) { + 'backbone', + 'apps/admin/fields/views/edit', + 'apps/admin/fields/views/alert' +], function(_, Backbone, FieldEditView, AlertView) { var FieldListRowView = Backbone.View.extend({ tagName: "li", className: "field-row", + initialize: function() { + // destroy view is model is deleted + this.model.on('destroy', this.remove, this); + }, + events : { + "click .trigger-click": "clickAction", + "drop" : "dropAction" + }, + clickAction: function (e) { + this.select(); + // first click create view else update model's view + if (typeof AdminFieldApp.fieldEditView === 'undefined') { + AdminFieldApp.fieldEditView = new FieldEditView({ + el: $('.right-block')[0], + model: this.model + }); + } else { + AdminFieldApp.fieldEditView.model = this.model; + } + + AdminFieldApp.fieldEditView.render(); + + return this; + }, + dropAction: function(event, index) { + this.$el.trigger('update-sort', [this.model, index]); + }, render: function() { - this.el.innerHTML = Twig.render(fieldsRow, { + var template = _.template($("#list_row_template").html(), { id: this.model.get('id'), + position: this.model.get('sorter'), name: this.model.get('name'), tag: this.model.get('tag') }); + this.$el.empty().html(template); + + if (AdminFieldApp.fieldEditView && AdminFieldApp.fieldEditView.model.get('id') === this.model.get('id')) { + this.select(); + } return this; + }, + // set selected class + select: function () { + $("li", this.$el.closest('ul')).removeClass('selected'); + this.$el.addClass('selected'); + + return this; + }, + animate: function () { + var offset = this.$el.offset(); + + this.$el.closest('div').animate({ + scrollTop: offset.top - 20 + }); } }); diff --git a/www/scripts/apps/admin/fields/views/modal.js b/www/scripts/apps/admin/fields/views/modal.js new file mode 100644 index 0000000000..418937676d --- /dev/null +++ b/www/scripts/apps/admin/fields/views/modal.js @@ -0,0 +1,44 @@ +define([ + 'underscore', + 'backbone', + 'i18n', + 'bootstrap' +], function(_, Backbone, i18n, bootstrap) { + var ModalView = Backbone.View.extend({ + tagName: "div", + className: "modal", + events: { + 'click .confirm': 'confirmAction' + }, + initialize: function (options) { + var self = this; + // remove view when modal is closed + this.$el.on('hidden', function() { + self.remove(); + }); + + if (options) { + this.message = options.message; + } + }, + render: function() { + var template = _.template($("#modal_delete_confirm_template").html(), { + msg: this.message || '' + }); + + this.$el.html(template).modal(); + + return this; + }, + confirmAction: function () { + this.trigger('modal:confirm'); + this.$el.modal('hide'); + this.remove(); + + return this; + } + }); + + return ModalView; +}); + diff --git a/www/scripts/models/admin/field.js b/www/scripts/models/admin/field.js deleted file mode 100644 index 966d36369c..0000000000 --- a/www/scripts/models/admin/field.js +++ /dev/null @@ -1,11 +0,0 @@ -define([ - 'underscore', - 'backbone' -], function(_, Backbone) { - var FieldModel = Backbone.Model.extend({ - urlRoot: '/admin/fields/1/fields' - }); - - // Return the model for the module - return FieldModel; -}); diff --git a/www/scripts/models/dcField.js b/www/scripts/models/dcField.js new file mode 100644 index 0000000000..fa9ab138ba --- /dev/null +++ b/www/scripts/models/dcField.js @@ -0,0 +1,13 @@ +define([ + 'underscore', + 'backbone' +], function(_, Backbone) { + var DcFieldModel = Backbone.Model.extend({ + urlRoot: function () { + return '/admin/fields/dc-fields'; + } + }); + + // Return the model for the module + return DcFieldModel; +}); diff --git a/www/scripts/models/field.js b/www/scripts/models/field.js new file mode 100644 index 0000000000..2881228e98 --- /dev/null +++ b/www/scripts/models/field.js @@ -0,0 +1,33 @@ +define([ + 'underscore', + 'backbone' +], function(_, Backbone) { + var FieldModel = Backbone.Model.extend({ + initialize : function(attributes, options) { + if (attributes && ! "sbas-id" in attributes) { + throw "You must set a sbas id"; + } + }, + urlRoot: function () { + return '/admin/fields/'+ this.get('sbas-id') +'/fields'; + }, + defaults: { + "business": false, + "type": "string", + "thumbtitle": "0", + "tbranch": "", + "separator": "", + "required": false, + "report": true, + "readonly": false, + "multi": false, + "indexable": true, + "dces-element": null, + "vocabulary-type": null, + "vocabulary-restricted": false + } + }); + + // Return the model for the module + return FieldModel; +}); diff --git a/www/scripts/models/vocabulary.js b/www/scripts/models/vocabulary.js new file mode 100644 index 0000000000..7dcc9439ce --- /dev/null +++ b/www/scripts/models/vocabulary.js @@ -0,0 +1,13 @@ +define([ + 'underscore', + 'backbone' +], function(_, Backbone) { + var VocabularyModel = Backbone.Model.extend({ + urlRoot: function () { + return '/admin/fields/vocabularies'; + } + }); + + // Return the model for the module + return VocabularyModel; +}); diff --git a/www/skins/admin/css/fields.css b/www/skins/admin/css/fields.css new file mode 100644 index 0000000000..2b138563eb --- /dev/null +++ b/www/skins/admin/css/fields.css @@ -0,0 +1,73 @@ +#admin-field-app li { + background: #FFF; + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; +} + +#admin-field-app li .field-name { + font-weight: bold; + font-size: 16px; + color: #666; +} + +#admin-field-app li .handle, #admin-field-app li .position { + color: #ccc; + border-right: 1px solid #ccc; +} + +#admin-field-app li.last { + border-bottom: 1px solid #ccc; +} + +#admin-field-app li.selected { + border-top-color: #0080FF; + background: #FFF; + color: #000; +} + +#admin-field-app li.selected + li { + border-top-color: #0080FF; +} + +#admin-field-app li.selected .field-name { + color: #0080FF; +} + +#admin-field-app .add-field-block .control-label { + width: 80px; + text-align: left; +} + +#admin-field-app .add-field-block .controls { + margin-left: 100px; +} + +#admin-field-app .edit-form label { + width: 160px; +} + +#admin-field-app .edit-form select { + min-width: 220px; +} + +.ui-autocomplete-admin-field { + list-style-type: none; + overflow-y: scroll; + height: 180px; + background: #FFF; + max-width: 300px; + -webkit-box-shadow: 0 10px 6px -6px #777; + -moz-box-shadow: 0 10px 6px -6px #777; + box-shadow: 0 10px 6px -6px #777; +} + +.ui-autocomplete-admin-field li{ + padding: 3px; +} + +.ui-autocomplete-admin-field li a { + text-decoration: none; +} + +