Add global register feature

Tweak admin field app

Fix typo

Tweaks

Fix typo
This commit is contained in:
Nicolas Le Goff
2013-05-15 17:56:28 +02:00
committed by Romain Neutron
parent 9a9235a15e
commit 0539db7598
28 changed files with 1320 additions and 292 deletions

View File

@@ -40,6 +40,10 @@ class Fields implements ControllerProviderInterface
->assert('sbas_id', '\d+')
->bind('admin_fields');
$controllers->put('/{sbas_id}/fields', 'admin.fields.controller:updateFields')
->assert('sbas_id', '\d+')
->bind('admin_fields_register');
$controllers->get('/{sbas_id}/fields', 'admin.fields.controller:listFields')
->assert('sbas_id', '\d+')
->bind('admin_fields_list');
@@ -84,20 +88,94 @@ class Fields implements ControllerProviderInterface
return $controllers;
}
public function getLanguage(Application $app) {
public function updateFields(Application $app, Request $request, $sbas_id)
{
$json = array(
'success' => false,
// use to store the updated collection
'fields' => array(),
'messages' => array()
);
$databox = $app['phraseanet.appbox']->get_databox((int) $sbas_id);
$connection = $databox->get_connection();
$data = $this->getFieldsJsonFromRequest($app, $request);
// calculate max position
try {
$stmt = $connection->prepare('SELECT MAX(sorter) as max_position FROM metadatas_structure');
$stmt->execute();
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
$stmt->closeCursor();
$maxPosition = $row['max_position'] + 1;
} catch (\PDOException $e) {
$app->abort(500);
}
$connection->beginTransaction();
$i = 0;
foreach ($data as $jsonField) {
try {
$jsonField['sorter'] = $jsonField['sorter'] + $maxPosition;
$field = \databox_field::get_instance($app, $databox, $jsonField['id']);
$this->updateFieldWithData($app, $field, $jsonField);
$field->save();
$json['fields'][] = $field->toArray();
$i++;
} catch (\PDOException $e) {
if ($e->errorInfo[1] == 1062) {
$json['messages'][] = _(sprintf('Field name %s already exists', $jsonField['name']));
} else {
$json['messages'][] = _(sprintf('Field %s could not be saved, please retry or contact an administrator if problem persists', $jsonField['name']));
}
} catch (\Exception $e) {
if ($e instanceof \Exception_Databox_metadataDescriptionNotFound || $e->getPrevious() instanceof TagUnknown) {
$json['messages'][] = _(sprintf('Provided tag %s is unknown', $jsonField['tag']));
} else {
$json['messages'][] = _(sprintf('Field %s could not be saved, please retry or contact an administrator if problem persists', $jsonField['name']));
}
}
}
if ($i === count($data)) {
// update field position in database, this query forces to update all fields each time
$stmt = $connection->prepare(sprintf('UPDATE metadatas_structure SET sorter = (sorter - %s)', $maxPosition));
$row = $stmt->execute();
$stmt->closeCursor();
$connection->commit();
$json['success'] = true;
$json['messages'][] = _('Fields configuration has been saved');
// update field position in array
array_walk($json['fields'], function(&$field) use ($maxPosition) {
$field['sorter'] = $field['sorter'] - $maxPosition;
});
} else {
$connection->rollback();
}
return $app->json($json);
}
public function getLanguage(Application $app, Request $request)
{
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'),
'validation_name_exists' => _('Field name already exists'),
'validation_tag_invalid' => _('Field source is not valid'),
'field_error' => _('Field %s contains errors'),
));
}
public function displayApp(Application $app, $sbas_id) {
public function displayApp(Application $app, Request $request, $sbas_id)
{
return $app['twig']->render('/admin/fields/index.html.twig', array(
'sbas_id' => $sbas_id,
'js' => ''
'sbas_id' => $sbas_id
));
}
@@ -246,11 +324,7 @@ class Fields implements ControllerProviderInterface
$app->abort(400, 'Body must contain a valid JSON payload');
}
$required = array(
'name', 'multi', 'thumbtitle', 'tag', 'business', 'indexable',
'required', 'separator', 'readonly', 'type', 'tbranch', 'report',
'vocabulary-type', 'vocabulary-restricted', 'dces-element'
);
$required = $this->getMandatoryFieldProperties();
foreach ($required as $key) {
if (false === array_key_exists($key, $data)) {
@@ -261,9 +335,32 @@ class Fields implements ControllerProviderInterface
return $data;
}
private function getFieldsJsonFromRequest(Application $app, Request $request)
{
$body = $request->getContent();
$data = @json_decode($body, true);
if (JSON_ERROR_NONE !== json_last_error()) {
$app->abort(400, 'Body must contain a valid JSON payload');
}
$required = $this->getMandatoryFieldProperties();
foreach($data as $field) {
foreach ($required as $key) {
if (false === array_key_exists($key, $field)) {
$app->abort(400, sprintf('The entity must contain a key `%s`', $key));
}
}
}
return $data;
}
private function updateFieldWithData(Application $app, \databox_field $field, array $data)
{
$field
->set_name($data['name'])
->set_thumbtitle($data['thumbtitle'])
->set_tag(\databox_field::loadClassFromTagName($data['tag']))
->set_business($data['business'])
@@ -277,6 +374,10 @@ class Fields implements ControllerProviderInterface
->setVocabularyControl(null)
->setVocabularyRestricted(false);
if (isset($data['sorter'])) {
$field->set_position($data['sorter']);
}
try {
$vocabulary = VocabularyController::get($app, $data['vocabulary-type']);
$field->setVocabularyControl($vocabulary);
@@ -287,11 +388,20 @@ class Fields implements ControllerProviderInterface
$dces_element = null;
$class = 'databox_Field_DCES_' . $data['dces-element'];
$class = '\databox_Field_DCES_' . $data['dces-element'];
if (class_exists($class)) {
$dces_element = new $class();
}
$field->set_dces_element($dces_element);
}
private function getMandatoryFieldProperties()
{
return array(
'name', 'multi', 'thumbtitle', 'tag', 'business', 'indexable',
'required', 'separator', 'readonly', 'type', 'tbranch', 'report',
'vocabulary-type', 'vocabulary-restricted', 'dces-element'
);
}
}

View File

@@ -350,6 +350,7 @@ class databox_field implements cache_cacheableInterface
`report` = :report,
`type` = :type,
`tbranch` = :tbranch,
`sorter` = :position,
`thumbtitle` = :thumbtitle,
`VocabularyControlType` = :VocabularyControlType,
`RestrictToVocabularyControl` = :RestrictVocab
@@ -367,6 +368,7 @@ class databox_field implements cache_cacheableInterface
':report' => $this->report ? '1' : '0',
':type' => $this->type,
':tbranch' => $this->tbranch,
':position' => $this->position,
':thumbtitle' => $this->thumbtitle,
':VocabularyControlType' => $this->Vocabulary ? $this->Vocabulary->getType() : null,
':RestrictVocab' => $this->Vocabulary ? ($this->VocabularyRestriction ? '1' : '0') : '0',
@@ -417,6 +419,7 @@ class databox_field implements cache_cacheableInterface
}
$meta->setAttribute('thumbtitle', $this->thumbtitle);
$meta->setAttribute('meta_id', $this->id);
$meta->setAttribute('sorter', $this->position);
$this->delete_data_from_cache();
$this->databox->saveStructure($dom_struct);
@@ -798,6 +801,24 @@ class databox_field implements cache_cacheableInterface
{
return $this->name;
}
/**
*
* @return string
*/
public function get_position()
{
return $this->position;
}
/**
*
* @return string
*/
public function set_position($position)
{
$this->position = $position;
return $this;
}
/**
* Return true is the field is unknown
@@ -827,7 +848,7 @@ class databox_field implements cache_cacheableInterface
'readonly' => $this->readonly,
'multi' => $this->multi,
'indexable' => $this->indexable,
'dces-element' => $this->dces_element,
'dces-element' => $this->dces_element ? $this->dces_element->get_label(): null,
'vocabulary-type' => $this->Vocabulary ? $this->Vocabulary->getType() : null,
'vocabulary-restricted' => $this->VocabularyRestriction,
);

View File

@@ -1,20 +1,25 @@
{# include js templates #}
{% include 'admin/fields/templates.html.twig' %}
<div id="admin-field-app" class="container-fluid">
{# sbas_id is saved in the dom and used to fetch right models and collections #}
<input type="hidden" name="current_sbas_id" value="{{ sbas_id }}">
<div class="row-fluid" style="min-height:60px; border-bottom: 1px solid #000">
<div class="span4">
<button type="button" class="btn btn-large btn-success"><i class="icon-hdd icon-white"></i> {% trans %}Save all changes{% endtrans %}</button>
<div class="row-fluid row-top">
<div class="span4 save-block">
{# set loading state, this will be removed once backbone application is fully loaded #}
<img src="/skins/icons/loaderFFF.gif"/>
{% trans %}Loading database documentary fields ...{% endtrans %}
</div>
<div class="span8">
<div class="block-alert well-small"></div>
<div class="block-alert"></div>
</div>
</div>
<div class="row-fluid">
<div class="row-fluid row-bottom hidden">
<div class="left-block span4"></div>
<div class="right-block span8" style="border-left: 1px dashed #000"></div>
<div class="right-block span8"></div>
</div>
</div>
{# bootstrap admin field application #}
{# bootstrap admin field backbone application #}
<script src="/assets/requirejs/require.js"></script>
<script src="/scripts/apps/admin/fields/main.js"></script>

View File

@@ -3,7 +3,13 @@
<%= msg %>
</script>
<script type="text/template" id="modal_delete_confirm_template">
<script type="text/template" id="save_template">
<button type="button" class="btn btn-large btn-success save-all">
<i class="icon-hdd icon-white"></i> {% trans %}Save all changes{% endtrans %}
</button>
</script>
<script type="text/template" id="modal_template">
<div class="modal-body">
<p><%= msg %></p>
</div>
@@ -26,16 +32,17 @@
<h3>{% trans %}Add a new field{% endtrans %}</h3>
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="inputLabel">{% trans %}Label{% endtrans %}</label>
<label class="control-label" for="new-name"">{% trans %}Label{% endtrans %}</label>
<div class="controls">
<input type="text" id="new-name" class="input-block-level" placeholder="">
<span class="help-block"></span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputSource">{% trans %}Source{% endtrans %}</label>
<label class="control-label" for="new-source">{% trans %}Source{% endtrans %}</label>
<div class="controls">
<input type="text" id="new-source" class="input-block-level" placeholder="">
<span class="help-block"></span>
</div>
</div>
<div class="control-group">
@@ -52,7 +59,7 @@
</div>
</div>
<div class="row-fluid">
<div class="span12">
<div class="span12 list-block">
<ul id="collection-fields" class="unstyled"></ul>
</div>
</div>
@@ -62,56 +69,60 @@
<div class="edit-block">
<table>
<tr class="edit-order">
<td>{% trans %}Order{% endtrans %}:</td>
<td>{% trans %}Order{% endtrans %}</td>
<td><%= field.sorter %></td>
<td><button type="button" class="btn btn-danger delete-field pull-right"><i class="icon-trash icon-white"></i>delete</button></td>
</tr>
<tr class="edit-name">
<td colspan="2">
<input id="name" value="<%= field.name %>" class="input-block-level">
<tr class="edit-name ">
<td colspan="2" class="control-group <%= modelErrors && modelErrors.has('name') ? 'error' : '' %>">
<input id="name" type="text" value="<%= field.name %>" class="input-block-level">
<span class="help-block">
<% if(modelErrors && modelErrors.get('name')) { %>
<%= modelErrors.get('name').message %>
<% } %>
</span>
</td>
</tr>
<tr>
<td>{% trans %}Source{% endtrans %} : </td>
<td><input id="tag" type="text" val="<%= field.tag %>" class="input-block-level"/></td>
<td>{% trans %}Source{% endtrans %}</td>
<td class="control-group <%= modelErrors && modelErrors.has('tag') ? 'error' : '' %>">
<input id="tag" val="<%= field.tag %>" class="input-block-level"/>
<span class="help-block">
<% if(modelErrors && modelErrors.get('tag')) { %>
<%= modelErrors.get('tag').message %>
<% } %>
</span>
</td>
</tr>
<tr>
<td>{% trans %}DCES{% endtrans %} : </td>
<td>{% trans %}DCES{% endtrans %}</td>
<td class="dc-fields-subview"></td>
</tr>
<tr>
<td colspan="3" class="dces-help-block"></td>
<td colspan="2" class="dces-help-block info"></td>
</tr>
<% if(field.multi == true) { %>
<tr>
<td colspan="2">
<i class='icon-ok'></i> {% trans %}Multivalued{% endtrans %}
<% if(field.multi == true) { %>
<i class='icon-ok'></i>
<% } else { %>
<i class='icon-remove'></i>
<% } %> {% trans %}Multivalued{% endtrans %}
</td>
</tr>
<% } %>
</table>
<div class="edit-form">
<h4>{% trans %}Advanced field parameter{% endtrans %}</h4>
<h4>{% trans %}Advanced field parameters{% endtrans %}</h4>
<table>
<tr>
<td><label for="tbranch">{% trans %}Thesaurus branch{% endtrans %}</label></td>
<td><input id="tbranch" type="text" value="<%= field.tbranch %>"/></td>
</tr>
<tr>
<td><label for="vocabulary-type">{% trans %}Vocabulary type{% endtrans %}</label></td>
<td>
<select id="vocabulary-type" class="input-block-level">
<% _.each(vocabularyTypes, function(vocab) { %>
<option value="<%= vocab.type %>"><%= vocab.name %></option>
<% }); %>
</select>
</td>
</tr>
<tr>
<td><label for="type">{% trans %}Type{% endtrans %}</label></td>
<td>
<select id="type" class="input-block-level">
<option value=""></option>
<select id="type">
<option <%= field.type == '' ? 'selected' : '' %> value=""></option>
<option <%= field.type == 'string' ? 'selected' : '' %> value="string">string</option>
<option <%= field.type == 'text' ? 'selected' : '' %> value="text">text</option>
<option <%= field.type == 'number' ? 'selected' : '' %> value="number">number</option>
@@ -120,34 +131,77 @@
</td>
</tr>
<tr>
<td colspan="2"><label for="business" class="checkbox"><input id="business" type="checkbox" <%= field.business ? "checked='checked'" : "" %> />{% trans %}Business Fields{% endtrans %}</label></td>
<td><label for="vocabulary-type">{% trans %}Vocabulary type{% endtrans %}</label></td>
<td>
<select id="vocabulary-type">
<option <%= field['vocabulary-type'] == null ? 'selected' : '' %> value=''></option>
<% _.each(vocabularyTypes, function(vocab) { %>
<option <%= field['vocabulary-type'] == vocab.type ? 'selected' : '' %> value="<%= vocab.type %>"><%= vocab.name %></option>
<% }); %>
</select>
</td>
</tr>
<tr>
<td colspan="2"><label for="vocabulary-restricted" class="checkbox"><input id="vocabulary-restricted" type="checkbox" <%= field["vocavulary-restricted"] ? "checked='checked'" : "" %> />{% trans %}Limited vocabulary{% endtrans %}</label></td>
<td colspan="2">
<% if(field['vocabulary-type'] != null && field['vocabulary-type'] != '') { %>
<label for="vocabulary-restricted" class="checkbox">
<input id="vocabulary-restricted" type="checkbox" <%= field["vocabulary-restricted"] ? "checked='checked'" : "" %> />
{% trans %}Limited vocabulary{% endtrans %}
</label>
<% } %>
</td>
</tr>
<tr>
<td colspan="2">
<label for="business" class="checkbox">
<input id="business" type="checkbox" <%= field.business ? "checked='checked'" : "" %> />
{% trans %}Business Fields{% endtrans %}
</label>
</td>
</tr>
<tr>
<td><label for="separator">{% trans %}Separator{% endtrans %}</label></td>
<td><input id="separator" type="text" value="<%= field.separator %>" /></td>
</tr>
</table>
<h4>{% trans %}display & action settings{% endtrans %}</h4>
<h4>{% trans %}Display & action settings{% endtrans %}</h4>
<table>
<tr>
<td colspan="2"><label for="required" class="checkbox"><input id="required" type="checkbox" <%= field.required ? "checked='checked'" : "" %> />{% trans %}Mandatory{% endtrans %}</label></td>
<td>
<label for="required" class="checkbox">
<input id="required" type="checkbox" <%= field.required ? "checked='checked'" : "" %> />
{% trans %}Mandatory{% endtrans %}
</label>
</td>
</tr>
<tr>
<td colspan="2"><label for="indexable" class="checkbox"><input id="indexable" type="checkbox" <%= field.indexable ? "checked='checked'" : "" %> />{% trans %}Indexable{% endtrans %}</label></td>
<td>
<label for="indexable" class="checkbox">
<input id="indexable" type="checkbox" <%= field.indexable ? "checked='checked'" : "" %> />
{% trans %}Indexable{% endtrans %}
</label>
</td>
</tr>
<tr>
<td colspan="2"><label for="readonly" class="checkbox"><input id="readonly" type="checkbox" <%= field.readonly ? "checked='checked'" : "" %> />{% trans %}Read only{% endtrans %}</label></td>
<td>
<label for="readonly" class="checkbox">
<input id="readonly" type="checkbox" <%= field.readonly ? "checked='checked'" : "" %> />
{% trans %}Read only{% endtrans %}
</label>
</td>
</tr>
<tr>
<td colspan="2"><label for="report" class="checkbox"><input id="report" type="checkbox" <%= field.report ? "checked='checked'" : "" %> />{% trans %}Report{% endtrans %}</label></td>
<td>
<label for="report" class="checkbox">
<input id="report" type="checkbox" <%= field.report ? "checked='checked'" : "" %> />
{% trans %}Report{% endtrans %}
</label>
</td>
</tr>
<tr>
<td><label for="thumbtitle">{% trans %}Display thumbnails{% endtrans %}</label></td>
<td>
<select id="thumbtitle" class="input-block-level">
<select id="thumbtitle">
<option value="1" <%= field.thumbtitle == "1" ? "selected" : "" %> >{% trans 'Tous' %}</option>
<option value="0" <%= field.thumbtitle == "0" ? "selected" : "" %> >{% trans 'Aucun' %}</option>
<option value="fr" <%= field.thumbtitle == "fr" ? "selected" : "" %> >{% trans 'Francais' %}</option>
@@ -163,7 +217,6 @@
</div>
</script>
<script type="text/template" id="list_row_template">
<table>
<tr>
@@ -187,10 +240,18 @@
</script>
<script type="text/template" id="dc_fields_template">
<select id="dces-element" val="" class="input-block-level">
<select id="dces-element" class="input-block-level">
<option <%= field['dces-element'] == null ? 'selected' : '' %> value=''></option>
<% _.each(dces_elements, function(el) { %>
<option value="<%= el.label %>">DC:<%= el.label %></option>
<option <%= field['dces-element'] == el.label ? 'selected' : '' %> value="<%= el.label %>">DC:<%= el.label %></option>
<% }); %>
</select>
<div class="help-block"></div>
</script>
<script type="text/template" id="field_error_template">
<% if(messages.length > 0) { %>
<div class="well well-small">
<i class="icon-exclamation-sign"></i> {% trans %} Current configuration contains some errors {% endtrans %}
</div>
<% } %>
</script>

View File

@@ -7,6 +7,25 @@ use Alchemy\Phrasea\Vocabulary\Controller as VocabularyController;
class ControllerFieldsTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
{
public function testRoot()
{
$databoxes = self::$DI['app']['phraseanet.appbox']->get_databoxes();
$databox = array_shift($databoxes);
self::$DI['client']->request("GET", "/admin/fields/" . $databox->get_sbas_id());
$this->assertTrue(self::$DI['client']->getResponse()->isOk());
}
public function testLanguage()
{
self::$DI['client']->request("GET", "/admin/fields/language.json");
$response = self::$DI['client']->getResponse();
$this->assertTrue($response->isOk());
$this->assertEquals("application/json", $response->headers->get("content-type"));
}
public function testGetTag()
{
$tag = new ObjectName();
@@ -100,6 +119,99 @@ class ControllerFieldsTest extends \PhraseanetWebTestCaseAuthenticatedAbstract
}
}
public function testUpdateFields()
{
$databoxes = self::$DI['app']['phraseanet.appbox']->get_databoxes();
$databox = array_shift($databoxes);
$fieldObjects = array();
// create two fields
$fields = array(
array(
'sbas-id' => $databox->get_sbas_id(),
'name' => 'testfield' . mt_rand(),
'multi' => true,
'thumbtitle' => false,
'tag' => 'XMP:XMP',
'business' => false,
'indexable' => true,
'required' => true,
'separator' => '=;',
'readonly' => false,
'type' => 'string',
'tbranch' => '',
'report' => true,
'dces-element' => null,
'vocabulary-type' => null,
'vocabulary-restricted' => false,
), array(
'sbas-id' => $databox->get_sbas_id(),
'name' => 'testfield' . mt_rand(),
'multi' => true,
'thumbtitle' => false,
'tag' => 'XMP:XMP',
'business' => false,
'indexable' => true,
'required' => true,
'separator' => '=;',
'readonly' => false,
'type' => 'string',
'tbranch' => '',
'report' => true,
'dces-element' => null,
'vocabulary-type' => null,
'vocabulary-restricted' => false,
));
foreach($fields as $fieldData) {
$field = \databox_field::create(self::$DI['app'], $databox, $fieldData['name'], $fieldData['multi']);
$field
->set_thumbtitle($fieldData['thumbtitle'])
->set_tag(\databox_field::loadClassFromTagName($fieldData['tag']))
->set_business($fieldData['business'])
->set_indexable($fieldData['indexable'])
->set_required($fieldData['required'])
->set_separator($fieldData['separator'])
->set_readonly($fieldData['readonly'])
->set_type($fieldData['type'])
->set_tbranch($fieldData['tbranch'])
->set_report($fieldData['report'])
->setVocabularyControl(null)
->setVocabularyRestricted(false);
$field->save();
$fieldObjects[] = $field;
}
// get body
$body = $databox->get_meta_structure()->toArray();
// change some body data
$body[count($body) - 2]['business'] = true;
$body[count($body) - 2]['indexable'] = false;
$body[count($body) - 1]['readonly'] = true;
$body[count($body) - 1]['required'] = false;
self::$DI['client']->request("PUT", sprintf("/admin/fields/%d/fields", $databox->get_sbas_id()), array(), array(), array(), json_encode($body));
$response = self::$DI['client']->getResponse()->getContent();
$this->assertEquals("application/json", self::$DI['client']->getResponse()->headers->get("content-type"));
$data = json_decode($response, true);
$this->assertArrayHasKey('success', $data);
$this->assertArrayHasKey('messages', $data);
$this->assertArrayHasKey('fields', $data);
// expect last 2 fields from body equals last 2 fields from response
$this->assertEquals(array_splice($body, -2), array_splice($data['fields'], -2));
// delete created fields
foreach($fieldObjects as $field) {
$field->delete();
}
}
public function testCreateField()
{
$databoxes = self::$DI['app']['phraseanet.appbox']->get_databoxes();

View File

@@ -1,47 +1,83 @@
define([
'jquery',
'underscore',
'backbone',
'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) {
"jquery",
"underscore",
"backbone",
"i18n",
"apps/admin/fields/collections/fields",
"apps/admin/fields/collections/vocabularies",
"apps/admin/fields/collections/dcFields",
"apps/admin/fields/views/list",
"apps/admin/fields/views/save",
"apps/admin/fields/views/fieldError",
"apps/admin/fields/errors/errorManager"
], function(
$, _, Backbone, i18n, FieldsCollection, VocabulariesCollection,
DcFieldsCollection, FieldListView, SaveView, FieldErrorView, ErrorManager) {
var initialize = function() {
window.AdminFieldApp = {};
AdminFieldApp = {
$window : $(window),
$scope : $("#admin-field-app"),
$top : $(".row-top", this.$scope),
$bottom : $(".row-bottom", this.$scope),
$leftBlock : $(".left-block", this.$bottom),
$rightBlock : $(".right-block", this.$bottom),
resizeListBlock: function () {
var listBlock = $(".list-block", AdminFieldApp.$leftBlock);
listBlock.height(AdminFieldApp.$window.height() - listBlock.offset().top - 10);
},
resize: function () {
AdminFieldApp.resizeListBlock();
AdminFieldApp.$rightBlock.height(AdminFieldApp.$window.height() - AdminFieldApp.$rightBlock.offset().top - 10);
}
};
window.AdminFieldApp.sbas_id = $('input[name=current_sbas_id]').val();
// bind resize
AdminFieldApp.$window.bind("resize", AdminFieldApp.resize);
var fieldsCollection = new FieldsCollection(null, {
sbas_id : window.AdminFieldApp.sbas_id
// current sbas id
AdminFieldApp.sbas_id = $("input[name=current_sbas_id]", AdminFieldApp.scope).val();
// global errors
AdminFieldApp.errorManager = new ErrorManager();
_.extend(AdminFieldApp.errorManager, Backbone.Events);
// initiliaze collections
AdminFieldApp.fieldsCollection = new FieldsCollection(null, {
sbas_id : AdminFieldApp.sbas_id
});
AdminFieldApp.vocabularyCollection = new VocabulariesCollection();
AdminFieldApp.dcFieldsCollection = new DcFieldsCollection();
var vocabulariesCollection = new VocabulariesCollection();
var dcFieldsCollection = new DcFieldsCollection();
// load strings
i18n.init({ resGetPath: "/admin/fields/language.json"});
// load strings synchronously
i18n.init({ resGetPath: '/admin/fields/language.json', getAsync: false });
var requests = [
fieldsCollection.fetch(),
vocabulariesCollection.fetch(),
dcFieldsCollection.fetch()
];
$.when.apply($, requests).done(
// load all collections
$.when.apply($, [
AdminFieldApp.fieldsCollection.fetch(),
AdminFieldApp.vocabularyCollection.fetch(),
AdminFieldApp.dcFieldsCollection.fetch()
]).done(
function() {
window.AdminFieldApp.vocabularyCollection = vocabulariesCollection;
window.AdminFieldApp.dcFieldsCollection = dcFieldsCollection;
window.AdminFieldApp.fieldListView = new FieldListView({
collection: fieldsCollection,
el: $('.left-block')[0]
// register views
AdminFieldApp.saveView = new SaveView({
el: $(".save-block", AdminFieldApp.scope)
});
AdminFieldApp.fieldErrorView = new FieldErrorView();
AdminFieldApp.fieldListView = new FieldListView({
collection: AdminFieldApp.fieldsCollection,
el: AdminFieldApp.$leftBlock
});
// render views
AdminFieldApp.saveView.render();
AdminFieldApp.fieldListView.render();
// show bottom
AdminFieldApp.$bottom.removeClass("hidden");
AdminFieldApp.$window.trigger("resize");
window.AdminFieldApp.fieldListView.render();
// click on first item list
_.first(window.AdminFieldApp.fieldListView.itemViews).clickAction().animate();
_.first(AdminFieldApp.fieldListView.itemViews).clickAction().animate();
}
);
};

View File

@@ -1,12 +1,12 @@
define([
'underscore',
'backbone',
'models/dcField'
"underscore",
"backbone",
"models/dcField"
], function(_, Backbone, DcFieldModel) {
var DcFieldCollection = Backbone.Collection.extend({
model: DcFieldModel,
url: function() {
return '/admin/fields/dc-fields';
return "/admin/fields/dc-fields";
},
comparator: function(item) {
return item.get("label");

View File

@@ -1,7 +1,7 @@
define([
'underscore',
'backbone',
'models/field'
"underscore",
"backbone",
"models/field"
], function(_, Backbone, FieldModel) {
var FieldCollection = Backbone.Collection.extend({
initialize: function(models, options) {
@@ -12,7 +12,7 @@ define([
},
model: FieldModel,
url: function() {
return '/admin/fields/' + this.sbasId + '/fields';
return "/admin/fields/" + this.sbasId + "/fields";
},
search: function(letters) {
if (letters === "")
@@ -52,6 +52,10 @@ define([
}
return index - 1;
},
// save all collection
save: function(options) {
return Backbone.sync("update", this, options || {});
}
});

View File

@@ -1,12 +1,12 @@
define([
'underscore',
'backbone',
'models/vocabulary'
"underscore",
"backbone",
"models/vocabulary"
], function(_, Backbone, VocabularyModel) {
var VocabularyCollection = Backbone.Collection.extend({
model: VocabularyModel,
url: function() {
return '/admin/fields/vocabularies';
return "/admin/fields/vocabularies";
},
comparator: function(item) {
return item.get("name");

View File

@@ -0,0 +1,13 @@
define([
"jquery",
"underscore"
], function($, _) {
var Error = function (model, fieldId, message) {
this.model = model;
this.fieldId = fieldId;
this.message = message;
};
return Error;
});

View File

@@ -0,0 +1,125 @@
define([
"jquery",
"underscore",
"backbone",
"apps/admin/fields/errors/errorModel"
], function($, _, Backbone, ErrorModel) {
var ErrorManager = function() {
this.errors = {};
_.extend(this, Backbone.Events);
};
ErrorManager.prototype = {
addModelError: function (model) {
return this.errors[model.get("id")] = new ErrorModel(model.get("id"));
},
getModelError: function (model) {
if (this.containsModelError(model)) {
return this.errors[model.get("id")];
}
return null;
},
removeModelError: function (model) {
if (this.containsModelError(model)) {
delete this.errors[model.get("id")];
}
},
containsModelError: function (model) {
return "undefined" !== typeof this.errors[model.get("id")];
},
addModelFieldError: function(error) {
if (! error instanceof Error) {
throw "Item must be an error object";
}
var model = error.model;
var fieldId = error.fieldId;
if (!this.containsModelError(model)) {
this.addModelError(model);
}
this.getModelError(model).add(fieldId, error);
this.trigger("add-error", error);
return this;
},
removeModelFieldError: function(model, fieldId) {
var modelError = this.getModelError(model);
if (modelError) {
modelError.remove(fieldId);
this.trigger("remove-error", model, fieldId);
if (modelError.count() === 0) {
this.removeModelError(model);
if (!this.hasErrors()) {
this.trigger("no-error");
}
}
}
},
clearModelFieldErrors: function(model, fieldId) {
var modelError = this.getModelError(model);
if (modelError) {
modelError.clear();
this.removeModelError(model);
}
if (!this.hasErrors()) {
this.trigger("no-error");
}
},
containsModelFieldError: function (model, fieldId) {
var modelError = this.getModelError(model);
if (modelError) {
return modelError.has(fieldId);
}
return false;
},
getModelFieldError: function(model, fieldId) {
var modelError = this.getModelError(model);
if (modelError) {
return modelError.get(fieldId);
}
return null;
},
clearAll: function() {
this.errors = {};
this.trigger("no-error");
},
hasErrors: function () {
return !_.isEmpty(this.errors);
},
count: function () {
var count = 0;
for (var k in this.errors) {
if (this.errors.hasOwnProperty(k)) {
++count;
}
}
return count;
},
all: function () {
var errors = [];
_.each(this.errors, function(modelErrors) {
_.each(modelErrors.all(), function(error) {
errors.push(error);
});
});
return errors;
}
};
return ErrorManager;
});

View File

@@ -0,0 +1,50 @@
define([
"jquery",
"underscore"
], function($, _) {
var ErrorModel = function(id) {
this.id = id;
this.errors = {};
};
ErrorModel.prototype = {
add: function(id, error) {
if (! error instanceof Error) {
throw "Item must be an error object";
}
this.errors[id] = error;
},
get: function(id) {
if (this.has(id)) {
return this.errors[id];
}
return null;
},
has: function (id) {
return "undefined" !== typeof this.errors[id];
},
remove: function(id) {
if (this.has(id)) {
delete this.errors[id];
}
},
count: function() {
var count = 0;
for (var k in this.errors) {
if (this.errors.hasOwnProperty(k)) {
++count;
}
}
return count;
},
clear: function () {
this.errors = {};
},
all: function () {
return this.errors;
}
};
return ErrorModel;
});

View File

@@ -1,25 +1,27 @@
// configure AMD loading
require.config({
baseUrl: "/scripts",
paths: {
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',
bootstrap: '../skins/html5/bootstrap/js/bootstrap.min'
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",
bootstrap: "../skins/html5/bootstrap/js/bootstrap.min"
},
shim: {
twig: {
exports: 'Twig'
exports: "Twig"
},
bootstrap : ['jquery'],
bootstrap : ["jquery"],
jqueryui: {
deps: [ 'jquery' ]
deps: [ "jquery" ]
}
}
});
require(['apps/admin/fields/app'], function(App) {
// launch application
require(["apps/admin/fields/app"], function(App) {
App.initialize();
});

View File

@@ -1,9 +1,10 @@
define([
'underscore',
'backbone',
'i18n',
'bootstrap'
], function(_, Backbone, i18n, bootstrap) {
"jquery",
"underscore",
"backbone",
"i18n",
"bootstrap"
], function($, _, Backbone, i18n, bootstrap) {
var AlertView = Backbone.View.extend({
tagName: "div",
className: "alert",
@@ -15,7 +16,7 @@ define([
this.message = options.message || "";
}
// remove view when alert is closed
this.$el.bind('closed', function () {
this.$el.bind("closed", function () {
self.remove();
});
},
@@ -26,7 +27,7 @@ define([
this.$el.addClass("alert-" + this.alert).html(template).alert();
$('.block-alert').empty().append(this.$el);
$(".block-alert").empty().append(this.$el);
return this;
}

View File

@@ -1,26 +1,31 @@
define([
'underscore',
'backbone',
'i18n'
], function( _, Backbone, i18n, bootstrap) {
"jquery",
"underscore",
"backbone",
"i18n"
], function($, _, Backbone, i18n, bootstrap) {
var DcFieldsView = Backbone.View.extend({
tagName: "div",
className: "input-append",
events: {
"change select": "selectChangedAction"
initialize : function (options) {
this.field = options.field;
},
render: function() {
var template = _.template($("#dc_fields_template").html(), {
dces_elements: this.collection.toJSON()
dces_elements: this.collection.toJSON(),
field: this.field.toJSON()
});
this.$el.html(template);
var index = $("#dces-element", AdminFieldApp.$rightBlock)[0].selectedIndex - 1;
if (index > 0 ) {
$(".dces-help-block", AdminFieldApp.$rightBlock).html(
this.collection.at(index).get("definition")
);
}
return this;
},
selectChangedAction: function(e) {
var index = $(e.target)[0].selectedIndex;
this.$el.closest('table').find('.dces-help-block').empty().append(this.collection.at(index).get('definition'));
}
});

View File

@@ -1,36 +1,47 @@
define([
'underscore',
'backbone',
'i18n',
'apps/admin/fields/views/alert',
'apps/admin/fields/views/modal',
'apps/admin/fields/views/dcField',
], function(_, Backbone, i18n, AlertView, ModalView, DcFieldView) {
"jquery",
"underscore",
"backbone",
"i18n",
"apps/admin/fields/views/alert",
"apps/admin/fields/views/modal",
"apps/admin/fields/views/dcField",
"apps/admin/fields/errors/error"
], function($, _, Backbone, i18n, AlertView, ModalView, DcFieldView, Error) {
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.onModelFieldChange, this);
this.model.on('change:tag', this.onModelFieldChange, this);
this.model.on("change", this._onModelChange, this);
this.dcFieldsSubView = new DcFieldView({
collection: window.AdminFieldApp.dcFieldsCollection
collection: AdminFieldApp.dcFieldsCollection,
field: this.model
});
},
updateModel: function(model) {
// unbind event to previous model
this.model.off("change");
this.model = model;
return this;
},
render: function() {
var self = this;
var template = _.template($("#edit_template").html(), {
field: this.model.toJSON(),
vocabularyTypes: window.AdminFieldApp.vocabularyCollection.toJSON()
vocabularyTypes: AdminFieldApp.vocabularyCollection.toJSON(),
modelErrors: AdminFieldApp.errorManager.getModelError(this.model)
});
this.$el.empty().html(template);
this.assign({
'.dc-fields-subview' : this.dcFieldsSubView
this._assign({
".dc-fields-subview" : this.dcFieldsSubView
});
$("#tag", this.$el).autocomplete({
var completer = $("#tag", this.$el).autocomplete({
minLength: 2,
source: function(request, response) {
$.ajax({
url: "/admin/fields/tags/search",
@@ -47,72 +58,170 @@ define([
}));
}
});
},
close: function(e) {
var fieldTag = $(e.target);
var fieldTagId = fieldTag.attr("id");
var fieldTagValue = fieldTag.val();
// check for format tag
if ("" !== fieldTagValue && false === /[a-z]+:[a-z0-9]+/i.test(fieldTagValue)) {
fieldTag
.closest(".control-group")
.addClass("error")
.find(".help-block")
.empty()
.append(i18n.t("validation_tag_invalid"));
// add error
AdminFieldApp.errorManager.addModelFieldError(new Error(
self.model, fieldTagId, i18n.t("validation_tag_invalid")
));
} else if (fieldTag.closest(".control-group").hasClass("error")) {
// remove error
AdminFieldApp.errorManager.removeModelFieldError(
self.model, fieldTagId
);
fieldTag
.closest(".control-group")
.removeClass("error")
.find(".help-block")
.empty();
}
}).val(this.model.get('tag')).autocomplete("widget").addClass("ui-autocomplete-admin-field");
var data = {};
data[fieldTagId] = fieldTagValue;
self.model.set(data);
}
});
completer
.val(this.model.get("tag"))
.autocomplete("widget")
.addClass("ui-autocomplete-admin-field");
this.delegateEvents();
return this;
},
events: {
"click": "focusAction",
"click .delete-field": "deleteAction",
"keyup #name": "changeNameAction",
"focusout input[type=text]": "fieldChangedAction",
"change input[type=checkbox]": "fieldChangedAction",
"change select": "selectionChangedAction"
},
focusAction: function() {
var index = AdminFieldApp.fieldListView.collection.indexOf(this.model);
if (index >= 0) {
AdminFieldApp.fieldListView.itemViews[index].animate();
}
return this;
},
// on input name keyup check for errors
changeNameAction: function(event) {
var self = this;
var fieldName = $(event.target);
var fieldNameId = fieldName.attr("id");
var fieldNameValue = fieldName.val();
// check for duplicate field name
if ("" === fieldNameValue || "undefined" !== typeof AdminFieldApp.fieldListView.collection.find(function(model) {
return model.get("name").toLowerCase() === fieldNameValue.toLowerCase() && self.model.get("id") !== model.get("id");
})) {
fieldName
.closest(".control-group")
.addClass("error")
.find(".help-block")
.empty()
.append(i18n.t("validation_name_exists"));
// add error
AdminFieldApp.errorManager.addModelFieldError(new Error(
self.model, fieldNameId, i18n.t("" === fieldNameValue ? "validation_blank" : "validation_name_exists")
));
} else if (fieldName.closest(".control-group").hasClass("error")) {
fieldName
.closest(".control-group")
.removeClass("error")
.find(".help-block")
.empty();
// remove error
AdminFieldApp.errorManager.removeModelFieldError(
self.model, fieldNameId
);
}
},
selectionChangedAction: function(e) {
var field = $(e.currentTarget);
var value = $("option:selected", field).val();
var data = {};
data[field.attr('id')] = value;
data[field.attr("id")] = $("option:selected", field).val();
this.model.set(data);
return this;
},
fieldChangedAction: function(e) {
var field = $(e.currentTarget);
var fieldId = field.attr("id");
var data = {};
data[field.attr('id')] = field.is(":checkbox") ? field.is(":checked") : field.val();
data[fieldId] = field.is(":checkbox") ? field.is(":checked") : field.val();
this.model.set(data);
return this;
},
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')] })
message: i18n.t("are_you_sure_delete", {
postProcess: "sprintf",
sprintf: [this.model.get("name")]
})
});
// get collection index of previous and next model
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];
}
// get previous index if exists else next index - 1 as item is being deleted
var index = previousIndex ? previousIndex : (nextIndex ? nextIndex - 1 : -1);
modalView.render();
modalView.on('modal:confirm', function() {
modalView.on("modal:confirm", function() {
self.model.destroy({
success: function(model, response) {
AdminFieldApp.fieldListView.collection.remove(self.model);
self._selectModelView(index);
if (itemView) {
itemView.clickAction().animate();
}
new AlertView({alert: 'info', message: i18n.t("deleted_success", { postProcess: "sprintf", sprintf: [model.get('name')] })}).render();
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();
new AlertView({
alert: "error", message: i18n.t("something_wrong")
}).render();
}
});
});
return this;
},
onModelFieldChange: function() {
_onModelChange: function() {
AdminFieldApp.fieldListView.collection.remove(this.model, {silent: true});
AdminFieldApp.fieldListView.collection.add(this.model);
var index = AdminFieldApp.fieldListView.collection.indexOf(this.model);
this._selectModelView(index);
this.render();
},
assign: function(selector, view) {
// bind a subview to a DOM element
_assign: function(selector, view) {
var selectors;
if (_.isObject(selector)) {
selectors = selector;
@@ -124,6 +233,13 @@ define([
_.each(selectors, function(view, selector) {
view.setElement(this.$(selector)).render();
}, this);
},
// select temView by index in itemList
_selectModelView: function(index) {
// select previous or next itemview
if (index >= 0) {
AdminFieldApp.fieldListView.itemViews[index].clickAction().animate();
}
}
});

View File

@@ -0,0 +1,38 @@
define([
"jquery",
"underscore",
"backbone",
"i18n"
], function($, _, Backbone, i18n) {
var FieldErrorView = Backbone.View.extend({
initialize: function() {
AdminFieldApp.errorManager.on("add-error", this.render, this);
AdminFieldApp.errorManager.on("remove-error", this.render, this);
},
render: function() {
var messages = [];
var errors = AdminFieldApp.errorManager.all();
_.each(_.groupBy(errors, function(error) {
return error.model.get("name");
}), function(groupedErrors) {
_.each(groupedErrors, function(error) {
messages.push(i18n.t("field_error", {
postProcess: "sprintf",
sprintf: [error.model.get("name")]
}));
});
});
var template = _.template($("#field_error_template").html(), {
messages: messages
});
$(".block-alert").html(template);
return this;
}
});
return FieldErrorView;
});

View File

@@ -1,12 +1,13 @@
define([
'jqueryui',
'underscore',
'backbone',
'i18n',
'apps/admin/fields/views/listRow',
'apps/admin/fields/views/alert',
'models/field'
], function(jqueryui, _, Backbone, i18n, FieldListRowView, AlertView, FieldModel) {
"jquery",
"jqueryui",
"underscore",
"backbone",
"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",
@@ -16,14 +17,35 @@ define([
"update-sort": "updateSortAction"
},
initialize: function() {
// Store all single rendered views
var self = this;
// store all single rendered views
this.itemViews = [];
_.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);
AdminFieldApp.errorManager.on('add-error', function(error) {
var model = error.model;
var itemView = _.find(self.itemViews, function(view) {
return model.get('id') === view.model.get('id');
});
if ('undefined' !== typeof itemView) {
itemView.error(true);
}
});
AdminFieldApp.errorManager.on('remove-error', function(model) {
var itemView = _.find(self.itemViews, function(view) {
return model.get('id') === view.model.get('id');
});
if ('undefined' !== typeof itemView) {
itemView.error(false);
}
});
},
render: function() {
var template = _.template($("#item_list_view_template").html(), {});
@@ -35,6 +57,7 @@ define([
this._renderList(this.collection);
$("#new-source", this.$el).autocomplete({
minLength: 2,
source: function(request, response) {
$.ajax({
url: "/admin/fields/tags/search",
@@ -54,8 +77,11 @@ define([
}
}).autocomplete("widget").addClass("ui-autocomplete-admin-field");
AdminFieldApp.resizeListBlock();
return this;
},
// render list by appending single item view, also fill itemViews
_renderList: function(fields) {
var that = this;
@@ -63,45 +89,100 @@ define([
this.itemViews = [];
fields.each(function(field) {
var fieldErrors = AdminFieldApp.errorManager.getModelError(field);
var singleView = new FieldListRowView({
model: field,
id: 'field-' + field.get('id')
});
id: "field-" + field.get("id")
}).error(fieldErrors && fieldErrors.count() > 0);
that.$listEl.append(singleView.render().el);
that.itemViews.push(singleView);
});
this.$listEl.sortable({
handle: ".handle",
placeholder: "item-list-placeholder",
start: function(event, ui) {
ui.item.addClass("border-bottom");
},
stop: function(event, ui) {
ui.item.trigger('drop', ui.item.index());
ui.firstItemPosition = $("li:first", $(this).sortable('widget')).position().top;
ui.item.trigger("drop", ui);
}
});
this.$listEl.disableSelection();
this.$listEl.find('li:last').addClass('last');
this.$listEl.find("li:last").addClass("last");
return this;
},
searchAction: function(event) {
this._renderList(this.collection.search($("#live_search", this.$el).val()));
return this;
},
createAction: function(event) {
var self = this;
var formErrors = 0;
var fieldName = $("#new-name", this.$el);
var fieldNameValue = fieldName.val();
var fieldTag = $("#new-source", this.$el);
var fieldTagValue = fieldTag.val();
if ('' == fieldName.val()) {
fieldName.closest('.control-group').addClass('error').find('.help-block').empty().append(i18n.t('validation_blank'));
// check for empty field name
if ("" === fieldNameValue) {
fieldName
.closest(".control-group")
.addClass("error")
.find(".help-block")
.empty()
.append(i18n.t("validation_blank"));
formErrors++;
}
// check for duplicate field name
if ("undefined" !== typeof this.collection.find(function(model){
return model.get("name").toLowerCase() === fieldNameValue.toLowerCase();
})) {
fieldName
.closest(".control-group")
.addClass("error")
.find(".help-block")
.empty()
.append(i18n.t("validation_name_exists"));
formErrors++;
}
// check for format tag
if ("" !== fieldTagValue && false === /[a-z]+:[a-z0-9]+/i.test(fieldTagValue)) {
fieldTag
.closest(".control-group")
.addClass("error")
.find(".help-block")
.empty()
.append(i18n.t("validation_tag_invalid"));
formErrors++;
}
if (formErrors > 0 ) {
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')
"name": fieldNameValue,
"tag": fieldTagValue,
"multi": $("#new-multivalued", this.$el).is(":checked"),
"sorter": this.collection.max(function(model) {
return model.get("sorter");
}).get("sorter") + 1
});
field.save(null, {
@@ -109,36 +190,53 @@ define([
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();
}
new AlertView({
alert: response.success ? "success" : "error", message: response.message
}).render();
},
error: function(model, xhr, options) {
new AlertView({alert: 'error', message: i18n.t("something_wrong")}).render();
new AlertView({
alert: "error", message: i18n.t("something_wrong")}
).render();
self.toggleCreateFormAction();
}
});
return this;
},
toggleCreateFormAction: function(event) {
$('.add-field-block', this.$el).toggle();
var fieldBlock = $(".add-field-block", this.$el);
fieldBlock.is(":hidden") ? fieldBlock.show() : fieldBlock.hide();
AdminFieldApp.resizeListBlock();
return this;
},
updateSortAction: function(event, model, position) {
updateSortAction: function(event, model, ui) {
var position = ui.item.index();
this.collection.remove(model, {silent: true});
// reorder all collection model
this.collection.each(function(model, index) {
var ordinal = index;
if (index >= position) ordinal += 1;
model.set('sorter', ordinal);
model.set("sorter", ordinal);
});
model.set('sorter', position);
model.set("sorter", position);
this.collection.add(model, {at: position});
// update edit view
this.itemViews[0].animate(Math.abs(ui.firstItemPosition));
// update edit view model
AdminFieldApp.fieldEditView.model = this.collection.find(function(el) {
return el.get('id') === AdminFieldApp.fieldEditView.model.get('id');
return el.get("id") === AdminFieldApp.fieldEditView.model.get("id");
});
return this;
}
});

View File

@@ -1,15 +1,15 @@
define([
'underscore',
'backbone',
'apps/admin/fields/views/edit',
'apps/admin/fields/views/alert'
], function(_, Backbone, FieldEditView, AlertView) {
"jquery",
"underscore",
"backbone",
"apps/admin/fields/views/edit"
], function($, _, Backbone, FieldEditView) {
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);
this.model.on("destroy", this.remove, this);
},
events : {
"click .trigger-click": "clickAction",
@@ -17,51 +17,71 @@ define([
},
clickAction: function (e) {
this.select();
// first click create view else update model's view
if (typeof AdminFieldApp.fieldEditView === 'undefined') {
// first click create edit view else update model"s view
if (typeof AdminFieldApp.fieldEditView === "undefined") {
AdminFieldApp.fieldEditView = new FieldEditView({
el: $('.right-block')[0],
el: AdminFieldApp.$rightBlock,
model: this.model
});
} else {
AdminFieldApp.fieldEditView.model = this.model;
AdminFieldApp.fieldEditView.updateModel(this.model).initialize();
}
AdminFieldApp.fieldEditView.render();
return this;
},
dropAction: function(event, index) {
this.$el.trigger('update-sort', [this.model, index]);
dropAction: function(event, ui) {
this.$el.trigger("update-sort", [this.model, ui]);
return this;
},
render: function() {
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')
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')) {
// highlight view if edit view model match current view model
if (AdminFieldApp.fieldEditView
&& AdminFieldApp.fieldEditView.model.get("id") === this.model.get("id")) {
this.select();
}
return this;
},
// set selected class
// set selected class to current view
select: function () {
$("li", this.$el.closest('ul')).removeClass('selected');
this.$el.addClass('selected');
$("li", this.$el.closest("ul")).removeClass("selected");
this.$el.addClass("selected");
return this;
},
animate: function () {
var offset = this.$el.offset();
// scroll to current view in item list
animate: function (top) {
top = top || null;
this.$el.closest('div').animate({
scrollTop: offset.top - 20
});
if (null === top) {
top = $(".field-row").index(this.$el) * this.$el.height();
}
this.$el.closest("div").scrollTop(top);
return this;
},
// add error class to item
error: function (errored) {
if (errored) {
this.$el.addClass("error");
} else {
this.$el.removeClass("error");
}
return this;
}
});

View File

@@ -1,19 +1,20 @@
define([
'underscore',
'backbone',
'i18n',
'bootstrap'
], function(_, Backbone, i18n, bootstrap) {
"jquery",
"underscore",
"backbone",
"i18n",
"bootstrap"
], function($, _, Backbone, i18n, bootstrap) {
var ModalView = Backbone.View.extend({
tagName: "div",
className: "modal",
events: {
'click .confirm': 'confirmAction'
"click .confirm": "confirmAction"
},
initialize: function (options) {
var self = this;
// remove view when modal is closed
this.$el.on('hidden', function() {
this.$el.on("hidden", function() {
self.remove();
});
@@ -22,8 +23,8 @@ define([
}
},
render: function() {
var template = _.template($("#modal_delete_confirm_template").html(), {
msg: this.message || ''
var template = _.template($("#modal_template").html(), {
msg: this.message || ""
});
this.$el.html(template).modal();
@@ -31,8 +32,8 @@ define([
return this;
},
confirmAction: function () {
this.trigger('modal:confirm');
this.$el.modal('hide');
this.trigger("modal:confirm");
this.$el.modal("hide");
this.remove();
return this;

View File

@@ -0,0 +1,95 @@
define([
"jquery",
"underscore",
"backbone",
"i18n",
"bootstrap",
"apps/admin/fields/views/alert"
], function($, _, Backbone, i18n, bootstrap, AlertView) {
var SaveView = Backbone.View.extend({
initialize: function() {
var self = this;
this.previousAttributes = [];
this.$overlay = null;
AdminFieldApp.errorManager.on("add-error", function(errors) {
self._disableSaveButton(true);
});
AdminFieldApp.errorManager.on("no-error", function() {
self._disableSaveButton(false);
});
},
events: {
"click button.save-all" : "clickSaveAction"
},
clickSaveAction: function(event) {
var self = this;
if (this._isModelDesync()) {
this._loadingState(true);
AdminFieldApp.fieldsCollection.save({
success: function(response) {
// reset collection with new one
if (response.success) {
AdminFieldApp.fieldsCollection.reset(response.fields);
}
new AlertView({
alert: response.success ? "success" : "error",
message: response.messages.join("<br />")
}).render();
},
error: function(model, xhr, options) {
new AlertView({
alert: "error", message: i18n.t("something_wrong")
}).render();
}
}).done(function() {
self._loadingState(false);
});
}
return this;
},
render: function () {
var template = _.template($("#save_template").html());
this.$el.html(template);
return this;
},
// check whether model has changed or not
_isModelDesync: function () {
return "undefined" !== typeof AdminFieldApp.fieldsCollection.find(function(model) {
return !_.isEmpty(model.previousAttributes());
});
},
// create a transparent overlay on top of the application
_overlay: function(showOrHide) {
if(showOrHide && !this.$overlay) {
this.$overlay = $("<div>").addClass("overlay");
AdminFieldApp.$bottom.append(this.$overlay);
} else if (!showOrHide && this.$overlay) {
this.$overlay.remove();
this.$overlay = null;
}
},
_disableSaveButton: function (active) {
$("button.save-all", this.$el).attr("disabled", active);
},
// put application on loading state (add overlay, add spinner, disable global save button)
_loadingState: function(active) {
if (active) {
$(".save-block", AdminFieldApp.$top).addClass("loading");
$(".block-alert", AdminFieldApp.$top).empty();
} else {
$(".save-block", AdminFieldApp.$top).removeClass("loading");
}
this._disableSaveButton(active);
this._overlay(active);
}
});
return SaveView;
});

View File

@@ -1,10 +1,10 @@
define([
'underscore',
'backbone'
"underscore",
"backbone"
], function(_, Backbone) {
var DcFieldModel = Backbone.Model.extend({
urlRoot: function () {
return '/admin/fields/dc-fields';
return "/admin/fields/dc-fields";
}
});

View File

@@ -1,6 +1,6 @@
define([
'underscore',
'backbone'
"underscore",
"backbone"
], function(_, Backbone) {
var FieldModel = Backbone.Model.extend({
initialize : function(attributes, options) {
@@ -9,7 +9,7 @@ define([
}
},
urlRoot: function () {
return '/admin/fields/'+ this.get('sbas-id') +'/fields';
return "/admin/fields/"+ this.get("sbas-id") +"/fields";
},
defaults: {
"business": false,

View File

@@ -1,10 +1,10 @@
define([
'underscore',
'backbone'
"underscore",
"backbone"
], function(_, Backbone) {
var VocabularyModel = Backbone.Model.extend({
urlRoot: function () {
return '/admin/fields/vocabularies';
return "/admin/fields/vocabularies";
}
});

View File

@@ -1,4 +1,4 @@
define(['chai'], function(shai) {
define(["chai"], function(shai) {
window.expect = shai.expect;
window.assert = shai.assert;
});

View File

@@ -1,8 +1,8 @@
require.config({
baseUrl: "../../scripts",
paths: {
specs: 'tests/specs',
chai: '../assets/chai/chai'
specs: "tests/specs",
chai: "../assets/chai/chai"
},
shim : {
shai: {
@@ -12,7 +12,7 @@ require.config({
});
mocha.setup({
ui: 'bdd',
ui: "bdd",
ignoreLeaks: true
});

View File

@@ -1,5 +1,5 @@
define(function(require) {
it('should run', function () {
it("should run", function () {
expect(true).to.equal(true);
});
});

View File

@@ -1,69 +1,119 @@
#admin-field-app .row-top {
min-height: 60px;
border-bottom: 1px solid #000;
}
#admin-field-app .row-bottom {
position: relative;
}
#admin-field-app .right-block {
border-left: 1px dashed #000;
overflow: auto;
min-height: 200px;
}
#admin-field-app h4 {
padding: 10px 0;
}
#admin-field-app li {
#admin-field-app .left-block li {
background: #FFF;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
height: 55px;
}
#admin-field-app li table {
table-layout:fixed;
width:100%;
#admin-field-app .left-block li .trigger-click {
cursor: pointer;
}
#admin-field-app li .field-name {
#admin-field-app .left-block li.border-bottom{
border-bottom: 1px solid #ccc;
}
#admin-field-app .left-block li table {
table-layout: fixed;
width: 100%;
}
#admin-field-app .left-block li .field-name {
font-weight: bold;
font-size: 16px;
color: #666;
}
#admin-field-app li .handle {
#admin-field-app .left-block li .handle {
width: 10%;
vertical-align:middle;
text-align:center;
vertical-align: middle;
text-align: center;
cursor: move;
}
#admin-field-app li .trigger-click {
padding:10px;
#admin-field-app .left-block li .trigger-click {
padding: 10px;
}
#admin-field-app li .position {
#admin-field-app .left-block li .position {
width: 10%;
vertical-align:bottom;
text-align: center
vertical-align: bottom;
text-align: center;
}
#admin-field-app li .chip {
width:10%;
#admin-field-app .left-block li .chip {
width: 10%;
}
#admin-field-app li .handle, #admin-field-app li .position {
#admin-field-app .left-block li .handle, #admin-field-app li .position {
color: #ccc;
border-right: 1px solid #ccc;
}
#admin-field-app li.last {
#admin-field-app .left-block li.last {
border-bottom: 1px solid #ccc;
}
#admin-field-app li.selected {
#admin-field-app .left-block li.selected {
border-top-color: #0080FF;
background: #FFF;
color: #000;
}
#admin-field-app li.selected + li {
#admin-field-app .left-block li.last.selected {
border-bottom-color: #0080FF;
}
#admin-field-app .left-block li.last.selected.error {
border-bottom-color: #9d261d;
}
#admin-field-app .left-block li.selected + li {
border-top-color: #0080FF;
}
#admin-field-app li.selected .field-name {
#admin-field-app .left-block li.selected.error {
border-top-color: #9d261d;
}
#admin-field-app .left-block li.selected.error + li {
border-top-color: #9d261d;
}
#admin-field-app .left-block li.selected .field-name {
color: #0080FF;
}
#admin-field-app .left-block li.error .field-name {
color: #9d261d;
}
#admin-field-app .item-list-placeholder {
border-top: 1px solid #ccc;
background-color: red;
height: 70px;
}
#admin-field-app .add-field-block .control-label {
width: 80px;
text-align: left;
@@ -85,13 +135,42 @@
margin: 20px 0;
}
#admin-field-app #collection-fields {
height:450px;
#admin-field-app .list-block {
height: 450px;
min-height: 130px;
overflow: auto;
position: relative;
}
#admin-field-app .edit-block {
padding: 5px 20px
padding: 5px 20px;
}
#admin-field-app .edit-block table {
table-layout: fixed;
width: 100%;
}
#admin-field-app .edit-block table label {
margin: 0px;
width: auto;
}
#admin-field-app .edit-block table td:first-child {
width: 130px;
}
#admin-field-app .edit-block td {
height: 25px;
}
#admin-field-app .edit-block .dces-help-block {
height: auto;
}
#admin-field-app .info {
color: #aaa;
padding: 10px;
}
#admin-field-app .edit-block .edit-order {
@@ -103,17 +182,38 @@
}
#admin-field-app .edit-block input#name {
font-size:28px;
color:#0080FF;
height:42px;
line-height:42px;
font-weight:bold
font-size: 28px;
color: #0080FF;
height: 42px;
line-height: 42px;
font-weight: bold
}
#admin-field-app .overlay {
zoom: 1;
filter: alpha(opacity=50);
opacity: 0.5;
background: #fff;
position: absolute;
width: 100%;
height: 100%;
z-index: 2000;
}
#admin-field-app .list-field-error li {
color: #9d261d;
}
#admin-field-app .save-block.loading {
background: url('/skins/icons/loaderFFF.gif') #fff no-repeat center right;
}
/* jquery ui autocomplete style */
.ui-autocomplete-admin-field {
list-style-type: none;
overflow-y: scroll;
height: 180px;
overflow-y: auto;
overflow-x: hidden;
max-height: 180px;
background: #FFF;
max-width: 300px;
-webkit-box-shadow: 0 10px 6px -6px #777;
@@ -121,10 +221,25 @@
box-shadow: 0 10px 6px -6px #777;
}
.ui-autocomplete-admin-field li{
padding: 3px;
.ui-autocomplete-admin-field li {
padding: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ui-autocomplete-admin-field li:hover {
padding: 5px;
background: #ccc;
}
.ui-autocomplete-admin-field li a {
text-decoration: none;
}
.ui-autocomplete-admin-field li a:hover {
text-decoration: none;
background: none;
color: #000;
border: none;
}