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+') ->assert('sbas_id', '\d+')
->bind('admin_fields'); ->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') $controllers->get('/{sbas_id}/fields', 'admin.fields.controller:listFields')
->assert('sbas_id', '\d+') ->assert('sbas_id', '\d+')
->bind('admin_fields_list'); ->bind('admin_fields_list');
@@ -84,20 +88,94 @@ class Fields implements ControllerProviderInterface
return $controllers; 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( return $app->json(array(
'something_wrong' => _('Something wrong happened, please try again or contact an admin if problem persists'), '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'),
'deleted_success' => _('%s field has been deleted with success'), 'are_you_sure_delete' => _('Do you really want to delete the field %s ?'),
'are_you_sure_delete' => _('Do you really want to delete the field %s ?'), 'validation_blank' => _('Field can not be blank'),
'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( return $app['twig']->render('/admin/fields/index.html.twig', array(
'sbas_id' => $sbas_id, 'sbas_id' => $sbas_id
'js' => ''
)); ));
} }
@@ -192,9 +270,9 @@ class Fields implements ControllerProviderInterface
$json['field'] = $field->toArray(); $json['field'] = $field->toArray();
} catch (\PDOException $e) { } catch (\PDOException $e) {
if ($e->errorInfo[1] == 1062) { if ($e->errorInfo[1] == 1062) {
$json['message'] = _(sprintf('Field name %s already exists', $data['name'])); $json['message'] = _(sprintf('Field name %s already exists', $data['name']));
} }
} catch (\Exception $e) { } catch (\Exception $e) {
if ($e instanceof \Exception_Databox_metadataDescriptionNotFound || $e->getPrevious() instanceof TagUnknown) { if ($e instanceof \Exception_Databox_metadataDescriptionNotFound || $e->getPrevious() instanceof TagUnknown) {
$json['message'] = _(sprintf('Provided tag %s is unknown', $data['tag'])); $json['message'] = _(sprintf('Provided tag %s is unknown', $data['tag']));
} }
@@ -246,11 +324,7 @@ class Fields implements ControllerProviderInterface
$app->abort(400, 'Body must contain a valid JSON payload'); $app->abort(400, 'Body must contain a valid JSON payload');
} }
$required = array( $required = $this->getMandatoryFieldProperties();
'name', 'multi', 'thumbtitle', 'tag', 'business', 'indexable',
'required', 'separator', 'readonly', 'type', 'tbranch', 'report',
'vocabulary-type', 'vocabulary-restricted', 'dces-element'
);
foreach ($required as $key) { foreach ($required as $key) {
if (false === array_key_exists($key, $data)) { if (false === array_key_exists($key, $data)) {
@@ -261,9 +335,32 @@ class Fields implements ControllerProviderInterface
return $data; 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) private function updateFieldWithData(Application $app, \databox_field $field, array $data)
{ {
$field $field
->set_name($data['name'])
->set_thumbtitle($data['thumbtitle']) ->set_thumbtitle($data['thumbtitle'])
->set_tag(\databox_field::loadClassFromTagName($data['tag'])) ->set_tag(\databox_field::loadClassFromTagName($data['tag']))
->set_business($data['business']) ->set_business($data['business'])
@@ -277,6 +374,10 @@ class Fields implements ControllerProviderInterface
->setVocabularyControl(null) ->setVocabularyControl(null)
->setVocabularyRestricted(false); ->setVocabularyRestricted(false);
if (isset($data['sorter'])) {
$field->set_position($data['sorter']);
}
try { try {
$vocabulary = VocabularyController::get($app, $data['vocabulary-type']); $vocabulary = VocabularyController::get($app, $data['vocabulary-type']);
$field->setVocabularyControl($vocabulary); $field->setVocabularyControl($vocabulary);
@@ -287,11 +388,20 @@ class Fields implements ControllerProviderInterface
$dces_element = null; $dces_element = null;
$class = 'databox_Field_DCES_' . $data['dces-element']; $class = '\databox_Field_DCES_' . $data['dces-element'];
if (class_exists($class)) { if (class_exists($class)) {
$dces_element = new $class(); $dces_element = new $class();
} }
$field->set_dces_element($dces_element); $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, `report` = :report,
`type` = :type, `type` = :type,
`tbranch` = :tbranch, `tbranch` = :tbranch,
`sorter` = :position,
`thumbtitle` = :thumbtitle, `thumbtitle` = :thumbtitle,
`VocabularyControlType` = :VocabularyControlType, `VocabularyControlType` = :VocabularyControlType,
`RestrictToVocabularyControl` = :RestrictVocab `RestrictToVocabularyControl` = :RestrictVocab
@@ -367,6 +368,7 @@ class databox_field implements cache_cacheableInterface
':report' => $this->report ? '1' : '0', ':report' => $this->report ? '1' : '0',
':type' => $this->type, ':type' => $this->type,
':tbranch' => $this->tbranch, ':tbranch' => $this->tbranch,
':position' => $this->position,
':thumbtitle' => $this->thumbtitle, ':thumbtitle' => $this->thumbtitle,
':VocabularyControlType' => $this->Vocabulary ? $this->Vocabulary->getType() : null, ':VocabularyControlType' => $this->Vocabulary ? $this->Vocabulary->getType() : null,
':RestrictVocab' => $this->Vocabulary ? ($this->VocabularyRestriction ? '1' : '0') : '0', ':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('thumbtitle', $this->thumbtitle);
$meta->setAttribute('meta_id', $this->id); $meta->setAttribute('meta_id', $this->id);
$meta->setAttribute('sorter', $this->position);
$this->delete_data_from_cache(); $this->delete_data_from_cache();
$this->databox->saveStructure($dom_struct); $this->databox->saveStructure($dom_struct);
@@ -798,6 +801,24 @@ class databox_field implements cache_cacheableInterface
{ {
return $this->name; 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 * Return true is the field is unknown
@@ -827,7 +848,7 @@ class databox_field implements cache_cacheableInterface
'readonly' => $this->readonly, 'readonly' => $this->readonly,
'multi' => $this->multi, 'multi' => $this->multi,
'indexable' => $this->indexable, '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-type' => $this->Vocabulary ? $this->Vocabulary->getType() : null,
'vocabulary-restricted' => $this->VocabularyRestriction, 'vocabulary-restricted' => $this->VocabularyRestriction,
); );

View File

@@ -1,20 +1,25 @@
{# include js templates #}
{% include 'admin/fields/templates.html.twig' %} {% include 'admin/fields/templates.html.twig' %}
<div id="admin-field-app" class="container-fluid"> <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 }}"> <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="row-fluid row-top">
<div class="span4"> <div class="span4 save-block">
<button type="button" class="btn btn-large btn-success"><i class="icon-hdd icon-white"></i> {% trans %}Save all changes{% endtrans %}</button> {# 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>
<div class="span8"> <div class="span8">
<div class="block-alert well-small"></div> <div class="block-alert"></div>
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid row-bottom hidden">
<div class="left-block span4"></div> <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>
</div> </div>
{# bootstrap admin field application #}
{# bootstrap admin field backbone application #}
<script src="/assets/requirejs/require.js"></script> <script src="/assets/requirejs/require.js"></script>
<script src="/scripts/apps/admin/fields/main.js"></script> <script src="/scripts/apps/admin/fields/main.js"></script>

View File

@@ -3,7 +3,13 @@
<%= msg %> <%= msg %>
</script> </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"> <div class="modal-body">
<p><%= msg %></p> <p><%= msg %></p>
</div> </div>
@@ -26,16 +32,17 @@
<h3>{% trans %}Add a new field{% endtrans %}</h3> <h3>{% trans %}Add a new field{% endtrans %}</h3>
<form class="form-horizontal"> <form class="form-horizontal">
<div class="control-group"> <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"> <div class="controls">
<input type="text" id="new-name" class="input-block-level" placeholder=""> <input type="text" id="new-name" class="input-block-level" placeholder="">
<span class="help-block"></span> <span class="help-block"></span>
</div> </div>
</div> </div>
<div class="control-group"> <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"> <div class="controls">
<input type="text" id="new-source" class="input-block-level" placeholder=""> <input type="text" id="new-source" class="input-block-level" placeholder="">
<span class="help-block"></span>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
@@ -52,7 +59,7 @@
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="span12"> <div class="span12 list-block">
<ul id="collection-fields" class="unstyled"></ul> <ul id="collection-fields" class="unstyled"></ul>
</div> </div>
</div> </div>
@@ -62,56 +69,60 @@
<div class="edit-block"> <div class="edit-block">
<table> <table>
<tr class="edit-order"> <tr class="edit-order">
<td>{% trans %}Order{% endtrans %}:</td> <td>{% trans %}Order{% endtrans %}</td>
<td><%= field.sorter %></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> <td><button type="button" class="btn btn-danger delete-field pull-right"><i class="icon-trash icon-white"></i>delete</button></td>
</tr> </tr>
<tr class="edit-name"> <tr class="edit-name ">
<td colspan="2"> <td colspan="2" class="control-group <%= modelErrors && modelErrors.has('name') ? 'error' : '' %>">
<input id="name" value="<%= field.name %>" class="input-block-level"> <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> </td>
</tr> </tr>
<tr> <tr>
<td>{% trans %}Source{% endtrans %} : </td> <td>{% trans %}Source{% endtrans %}</td>
<td><input id="tag" type="text" val="<%= field.tag %>" class="input-block-level"/></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>
<tr> <tr>
<td>{% trans %}DCES{% endtrans %} : </td> <td>{% trans %}DCES{% endtrans %}</td>
<td class="dc-fields-subview"></td> <td class="dc-fields-subview"></td>
</tr> </tr>
<tr> <tr>
<td colspan="3" class="dces-help-block"></td> <td colspan="2" class="dces-help-block info"></td>
</tr>
<tr>
<td colspan="2">
<% if(field.multi == true) { %>
<i class='icon-ok'></i>
<% } else { %>
<i class='icon-remove'></i>
<% } %> {% trans %}Multivalued{% endtrans %}
</td>
</tr> </tr>
<% if(field.multi == true) { %>
<tr>
<td colspan="2">
<i class='icon-ok'></i> {% trans %}Multivalued{% endtrans %}
</td>
</tr>
<% } %>
</table> </table>
<div class="edit-form"> <div class="edit-form">
<h4>{% trans %}Advanced field parameter{% endtrans %}</h4> <h4>{% trans %}Advanced field parameters{% endtrans %}</h4>
<table> <table>
<tr> <tr>
<td><label for="tbranch">{% trans %}Thesaurus branch{% endtrans %}</label></td> <td><label for="tbranch">{% trans %}Thesaurus branch{% endtrans %}</label></td>
<td><input id="tbranch" type="text" value="<%= field.tbranch %>"/></td> <td><input id="tbranch" type="text" value="<%= field.tbranch %>"/></td>
</tr> </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> <tr>
<td><label for="type">{% trans %}Type{% endtrans %}</label></td> <td><label for="type">{% trans %}Type{% endtrans %}</label></td>
<td> <td>
<select id="type" class="input-block-level"> <select id="type">
<option value=""></option> <option <%= field.type == '' ? 'selected' : '' %> value=""></option>
<option <%= field.type == 'string' ? 'selected' : '' %> value="string">string</option> <option <%= field.type == 'string' ? 'selected' : '' %> value="string">string</option>
<option <%= field.type == 'text' ? 'selected' : '' %> value="text">text</option> <option <%= field.type == 'text' ? 'selected' : '' %> value="text">text</option>
<option <%= field.type == 'number' ? 'selected' : '' %> value="number">number</option> <option <%= field.type == 'number' ? 'selected' : '' %> value="number">number</option>
@@ -120,34 +131,77 @@
</td> </td>
</tr> </tr>
<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>
<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>
<tr> <tr>
<td><label for="separator">{% trans %}Separator{% endtrans %}</label></td> <td><label for="separator">{% trans %}Separator{% endtrans %}</label></td>
<td><input id="separator" type="text" value="<%= field.separator %>" /></td> <td><input id="separator" type="text" value="<%= field.separator %>" /></td>
</tr> </tr>
</table> </table>
<h4>{% trans %}display & action settings{% endtrans %}</h4> <h4>{% trans %}Display & action settings{% endtrans %}</h4>
<table> <table>
<tr> <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>
<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>
<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>
<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>
<tr> <tr>
<td><label for="thumbtitle">{% trans %}Display thumbnails{% endtrans %}</label></td> <td><label for="thumbtitle">{% trans %}Display thumbnails{% endtrans %}</label></td>
<td> <td>
<select id="thumbtitle" class="input-block-level"> <select id="thumbtitle">
<option value="1" <%= field.thumbtitle == "1" ? "selected" : "" %> >{% trans 'Tous' %}</option> <option value="1" <%= field.thumbtitle == "1" ? "selected" : "" %> >{% trans 'Tous' %}</option>
<option value="0" <%= field.thumbtitle == "0" ? "selected" : "" %> >{% trans 'Aucun' %}</option> <option value="0" <%= field.thumbtitle == "0" ? "selected" : "" %> >{% trans 'Aucun' %}</option>
<option value="fr" <%= field.thumbtitle == "fr" ? "selected" : "" %> >{% trans 'Francais' %}</option> <option value="fr" <%= field.thumbtitle == "fr" ? "selected" : "" %> >{% trans 'Francais' %}</option>
@@ -163,7 +217,6 @@
</div> </div>
</script> </script>
<script type="text/template" id="list_row_template"> <script type="text/template" id="list_row_template">
<table> <table>
<tr> <tr>
@@ -187,10 +240,18 @@
</script> </script>
<script type="text/template" id="dc_fields_template"> <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) { %> <% _.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> </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> </script>

View File

@@ -7,6 +7,25 @@ use Alchemy\Phrasea\Vocabulary\Controller as VocabularyController;
class ControllerFieldsTest extends \PhraseanetWebTestCaseAuthenticatedAbstract 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() public function testGetTag()
{ {
$tag = new ObjectName(); $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() public function testCreateField()
{ {
$databoxes = self::$DI['app']['phraseanet.appbox']->get_databoxes(); $databoxes = self::$DI['app']['phraseanet.appbox']->get_databoxes();

View File

@@ -1,47 +1,83 @@
define([ define([
'jquery', "jquery",
'underscore', "underscore",
'backbone', "backbone",
'i18n', "i18n",
'apps/admin/fields/collections/fields', "apps/admin/fields/collections/fields",
'apps/admin/fields/collections/vocabularies', "apps/admin/fields/collections/vocabularies",
'apps/admin/fields/collections/dcFields', "apps/admin/fields/collections/dcFields",
'apps/admin/fields/views/list' "apps/admin/fields/views/list",
], function($, _, Backbone, i18n, FieldsCollection, VocabulariesCollection, DcFieldsCollection, FieldListView) { "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() { 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, { // current sbas id
sbas_id : window.AdminFieldApp.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(); // load strings
var dcFieldsCollection = new DcFieldsCollection(); i18n.init({ resGetPath: "/admin/fields/language.json"});
// load strings synchronously // load all collections
i18n.init({ resGetPath: '/admin/fields/language.json', getAsync: false }); $.when.apply($, [
AdminFieldApp.fieldsCollection.fetch(),
var requests = [ AdminFieldApp.vocabularyCollection.fetch(),
fieldsCollection.fetch(), AdminFieldApp.dcFieldsCollection.fetch()
vocabulariesCollection.fetch(), ]).done(
dcFieldsCollection.fetch()
];
$.when.apply($, requests).done(
function() { function() {
window.AdminFieldApp.vocabularyCollection = vocabulariesCollection; // register views
window.AdminFieldApp.dcFieldsCollection = dcFieldsCollection; AdminFieldApp.saveView = new SaveView({
el: $(".save-block", AdminFieldApp.scope)
window.AdminFieldApp.fieldListView = new FieldListView({
collection: fieldsCollection,
el: $('.left-block')[0]
}); });
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 // 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([ define([
'underscore', "underscore",
'backbone', "backbone",
'models/dcField' "models/dcField"
], function(_, Backbone, DcFieldModel) { ], function(_, Backbone, DcFieldModel) {
var DcFieldCollection = Backbone.Collection.extend({ var DcFieldCollection = Backbone.Collection.extend({
model: DcFieldModel, model: DcFieldModel,
url: function() { url: function() {
return '/admin/fields/dc-fields'; return "/admin/fields/dc-fields";
}, },
comparator: function(item) { comparator: function(item) {
return item.get("label"); return item.get("label");

View File

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

View File

@@ -1,12 +1,12 @@
define([ define([
'underscore', "underscore",
'backbone', "backbone",
'models/vocabulary' "models/vocabulary"
], function(_, Backbone, VocabularyModel) { ], function(_, Backbone, VocabularyModel) {
var VocabularyCollection = Backbone.Collection.extend({ var VocabularyCollection = Backbone.Collection.extend({
model: VocabularyModel, model: VocabularyModel,
url: function() { url: function() {
return '/admin/fields/vocabularies'; return "/admin/fields/vocabularies";
}, },
comparator: function(item) { comparator: function(item) {
return item.get("name"); 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({ require.config({
baseUrl: "/scripts", baseUrl: "/scripts",
paths: { paths: {
jquery: '../include/minify/f=include/jslibs/jquery-1.7.1', 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', jqueryui: "../include/jslibs/jquery-ui-1.8.17/js/jquery-ui-1.8.17.custom.min",
underscore: '../assets/underscore-amd/underscore', underscore: "../assets/underscore-amd/underscore",
backbone: '../assets/backbone-amd/backbone', backbone: "../assets/backbone-amd/backbone",
twig: '../assets/twig/twig', twig: "../assets/twig/twig",
i18n: '../assets/i18n/i18next.amd', i18n: "../assets/i18n/i18next.amd",
bootstrap: '../skins/html5/bootstrap/js/bootstrap.min' bootstrap: "../skins/html5/bootstrap/js/bootstrap.min"
}, },
shim: { shim: {
twig: { twig: {
exports: 'Twig' exports: "Twig"
}, },
bootstrap : ['jquery'], bootstrap : ["jquery"],
jqueryui: { jqueryui: {
deps: [ 'jquery' ] deps: [ "jquery" ]
} }
} }
}); });
require(['apps/admin/fields/app'], function(App) { // launch application
require(["apps/admin/fields/app"], function(App) {
App.initialize(); App.initialize();
}); });

View File

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

View File

@@ -1,26 +1,31 @@
define([ define([
'underscore', "jquery",
'backbone', "underscore",
'i18n' "backbone",
], function( _, Backbone, i18n, bootstrap) { "i18n"
], function($, _, Backbone, i18n, bootstrap) {
var DcFieldsView = Backbone.View.extend({ var DcFieldsView = Backbone.View.extend({
tagName: "div", tagName: "div",
className: "input-append", className: "input-append",
events: { initialize : function (options) {
"change select": "selectChangedAction" this.field = options.field;
}, },
render: function() { render: function() {
var template = _.template($("#dc_fields_template").html(), { 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); 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; 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([ define([
'underscore', "jquery",
'backbone', "underscore",
'i18n', "backbone",
'apps/admin/fields/views/alert', "i18n",
'apps/admin/fields/views/modal', "apps/admin/fields/views/alert",
'apps/admin/fields/views/dcField', "apps/admin/fields/views/modal",
], function(_, Backbone, i18n, AlertView, ModalView, DcFieldView) { "apps/admin/fields/views/dcField",
"apps/admin/fields/errors/error"
], function($, _, Backbone, i18n, AlertView, ModalView, DcFieldView, Error) {
var FieldEditView = Backbone.View.extend({ var FieldEditView = Backbone.View.extend({
tagName: "div", tagName: "div",
className: "field-edit", className: "field-edit",
initialize: function() { initialize: function() {
this.model.on('change', this.render, this); this.model.on("change", this._onModelChange, this);
this.model.on('change:name', this.onModelFieldChange, this);
this.model.on('change:tag', this.onModelFieldChange, this);
this.dcFieldsSubView = new DcFieldView({ 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() { render: function() {
var self = this;
var template = _.template($("#edit_template").html(), { var template = _.template($("#edit_template").html(), {
field: this.model.toJSON(), 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.$el.empty().html(template);
this.assign({ this._assign({
'.dc-fields-subview' : this.dcFieldsSubView ".dc-fields-subview" : this.dcFieldsSubView
}); });
$("#tag", this.$el).autocomplete({ var completer = $("#tag", this.$el).autocomplete({
minLength: 2,
source: function(request, response) { source: function(request, response) {
$.ajax({ $.ajax({
url: "/admin/fields/tags/search", 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();
}
var data = {};
data[fieldTagId] = fieldTagValue;
self.model.set(data);
} }
}).val(this.model.get('tag')).autocomplete("widget").addClass("ui-autocomplete-admin-field"); });
completer
.val(this.model.get("tag"))
.autocomplete("widget")
.addClass("ui-autocomplete-admin-field");
this.delegateEvents();
return this; return this;
}, },
events: { events: {
"click": "focusAction",
"click .delete-field": "deleteAction", "click .delete-field": "deleteAction",
"keyup #name": "changeNameAction",
"focusout input[type=text]": "fieldChangedAction", "focusout input[type=text]": "fieldChangedAction",
"change input[type=checkbox]": "fieldChangedAction", "change input[type=checkbox]": "fieldChangedAction",
"change select": "selectionChangedAction" "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) { selectionChangedAction: function(e) {
var field = $(e.currentTarget); var field = $(e.currentTarget);
var value = $("option:selected", field).val();
var data = {}; var data = {};
data[field.attr('id')] = value; data[field.attr("id")] = $("option:selected", field).val();
this.model.set(data); this.model.set(data);
return this;
}, },
fieldChangedAction: function(e) { fieldChangedAction: function(e) {
var field = $(e.currentTarget); var field = $(e.currentTarget);
var fieldId = field.attr("id");
var data = {}; 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); this.model.set(data);
return this;
}, },
deleteAction: function() { deleteAction: function() {
var self = this; var self = this;
var modalView = new ModalView({ var modalView = new ModalView({
model: this.model, 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")]
})
}); });
var previousIndex = AdminFieldApp.fieldListView.collection.previousIndex(this.model);
var nextIndex = AdminFieldApp.fieldListView.collection.nextIndex(this.model);
var itemView;
if (previousIndex) { // get collection index of previous and next model
itemView = AdminFieldApp.fieldListView.itemViews[previousIndex]; var previousIndex = AdminFieldApp.fieldListView.collection.previousIndex(this.model);
} else if (nextIndex) { var nextIndex = AdminFieldApp.fieldListView.collection.nextIndex(this.model);
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.render();
modalView.on('modal:confirm', function() { modalView.on("modal:confirm", function() {
self.model.destroy({ self.model.destroy({
success: function(model, response) { success: function(model, response) {
AdminFieldApp.fieldListView.collection.remove(self.model); AdminFieldApp.fieldListView.collection.remove(self.model);
self._selectModelView(index);
if (itemView) { new AlertView({alert: "info", message: i18n.t("deleted_success", {
itemView.clickAction().animate(); postProcess: "sprintf",
} sprintf: [model.get("name")]
})
new AlertView({alert: 'info', message: i18n.t("deleted_success", { postProcess: "sprintf", sprintf: [model.get('name')] })}).render(); }).render();
}, },
error: function(model, xhr) { 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; return this;
}, },
onModelFieldChange: function() { _onModelChange: function() {
AdminFieldApp.fieldListView.collection.remove(this.model, {silent: true}); AdminFieldApp.fieldListView.collection.remove(this.model, {silent: true});
AdminFieldApp.fieldListView.collection.add(this.model); AdminFieldApp.fieldListView.collection.add(this.model);
var index = AdminFieldApp.fieldListView.collection.indexOf(this.model);
this._selectModelView(index);
this.render(); this.render();
}, },
assign: function(selector, view) { // bind a subview to a DOM element
_assign: function(selector, view) {
var selectors; var selectors;
if (_.isObject(selector)) { if (_.isObject(selector)) {
selectors = selector; selectors = selector;
@@ -124,6 +233,13 @@ define([
_.each(selectors, function(view, selector) { _.each(selectors, function(view, selector) {
view.setElement(this.$(selector)).render(); view.setElement(this.$(selector)).render();
}, this); }, 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([ define([
'jqueryui', "jquery",
'underscore', "jqueryui",
'backbone', "underscore",
'i18n', "backbone",
'apps/admin/fields/views/listRow', "i18n",
'apps/admin/fields/views/alert', "apps/admin/fields/views/listRow",
'models/field' "apps/admin/fields/views/alert",
], function(jqueryui, _, Backbone, i18n, FieldListRowView, AlertView, FieldModel) { "models/field"
], function($, jqueryui, _, Backbone, i18n, FieldListRowView, AlertView, FieldModel) {
var FieldListView = Backbone.View.extend({ var FieldListView = Backbone.View.extend({
events: { events: {
"keyup #live_search": "searchAction", "keyup #live_search": "searchAction",
@@ -16,14 +17,35 @@ define([
"update-sort": "updateSortAction" "update-sort": "updateSortAction"
}, },
initialize: function() { initialize: function() {
// Store all single rendered views var self = this;
// store all single rendered views
this.itemViews = []; this.itemViews = [];
_.bindAll(this, "render");
// rerender whenever there is a change on the collection // rerender whenever there is a change on the collection
this.collection.bind("reset", this.render, this); this.collection.bind("reset", this.render, this);
this.collection.bind("add", this.render, this); this.collection.bind("add", this.render, this);
this.collection.bind("remove", 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() { render: function() {
var template = _.template($("#item_list_view_template").html(), {}); var template = _.template($("#item_list_view_template").html(), {});
@@ -35,6 +57,7 @@ define([
this._renderList(this.collection); this._renderList(this.collection);
$("#new-source", this.$el).autocomplete({ $("#new-source", this.$el).autocomplete({
minLength: 2,
source: function(request, response) { source: function(request, response) {
$.ajax({ $.ajax({
url: "/admin/fields/tags/search", url: "/admin/fields/tags/search",
@@ -54,8 +77,11 @@ define([
} }
}).autocomplete("widget").addClass("ui-autocomplete-admin-field"); }).autocomplete("widget").addClass("ui-autocomplete-admin-field");
AdminFieldApp.resizeListBlock();
return this; return this;
}, },
// render list by appending single item view, also fill itemViews
_renderList: function(fields) { _renderList: function(fields) {
var that = this; var that = this;
@@ -63,45 +89,100 @@ define([
this.itemViews = []; this.itemViews = [];
fields.each(function(field) { fields.each(function(field) {
var fieldErrors = AdminFieldApp.errorManager.getModelError(field);
var singleView = new FieldListRowView({ var singleView = new FieldListRowView({
model: field, model: field,
id: 'field-' + field.get('id') id: "field-" + field.get("id")
}); }).error(fieldErrors && fieldErrors.count() > 0);
that.$listEl.append(singleView.render().el); that.$listEl.append(singleView.render().el);
that.itemViews.push(singleView); that.itemViews.push(singleView);
}); });
this.$listEl.sortable({ this.$listEl.sortable({
handle: ".handle", handle: ".handle",
placeholder: "item-list-placeholder",
start: function(event, ui) {
ui.item.addClass("border-bottom");
},
stop: function(event, ui) { 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.disableSelection();
this.$listEl.find('li:last').addClass('last'); this.$listEl.find("li:last").addClass("last");
return this; return this;
}, },
searchAction: function(event) { searchAction: function(event) {
this._renderList(this.collection.search($("#live_search", this.$el).val())); this._renderList(this.collection.search($("#live_search", this.$el).val()));
return this;
}, },
createAction: function(event) { createAction: function(event) {
var self = this; var self = this;
var formErrors = 0;
var fieldName = $("#new-name", this.$el); var fieldName = $("#new-name", this.$el);
var fieldNameValue = fieldName.val();
var fieldTag = $("#new-source", this.$el);
var fieldTagValue = fieldTag.val();
if ('' == fieldName.val()) { // check for empty field name
fieldName.closest('.control-group').addClass('error').find('.help-block').empty().append(i18n.t('validation_blank')); 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; return;
} }
var field = new FieldModel({ var field = new FieldModel({
"sbas-id": AdminFieldApp.sbas_id, "sbas-id": AdminFieldApp.sbas_id,
"name": fieldName.val(), "name": fieldNameValue,
"tag": $("#new-source", this.$el).val(), "tag": fieldTagValue,
"multi": $("#new-multivalued", this.$el).is(':checked') "multi": $("#new-multivalued", this.$el).is(":checked"),
"sorter": this.collection.max(function(model) {
return model.get("sorter");
}).get("sorter") + 1
}); });
field.save(null, { field.save(null, {
@@ -109,36 +190,53 @@ define([
if (response.success) { if (response.success) {
self.collection.add(field); self.collection.add(field);
_.last(self.itemViews).clickAction().animate(); _.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) { 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(); self.toggleCreateFormAction();
} }
}); });
return this;
}, },
toggleCreateFormAction: function(event) { 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}); this.collection.remove(model, {silent: true});
// reorder all collection model
this.collection.each(function(model, index) { this.collection.each(function(model, index) {
var ordinal = index; var ordinal = index;
if (index >= position) ordinal += 1; 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}); 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) { 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([ define([
'underscore', "jquery",
'backbone', "underscore",
'apps/admin/fields/views/edit', "backbone",
'apps/admin/fields/views/alert' "apps/admin/fields/views/edit"
], function(_, Backbone, FieldEditView, AlertView) { ], function($, _, Backbone, FieldEditView) {
var FieldListRowView = Backbone.View.extend({ var FieldListRowView = Backbone.View.extend({
tagName: "li", tagName: "li",
className: "field-row", className: "field-row",
initialize: function() { initialize: function() {
// destroy view is model is deleted // destroy view is model is deleted
this.model.on('destroy', this.remove, this); this.model.on("destroy", this.remove, this);
}, },
events : { events : {
"click .trigger-click": "clickAction", "click .trigger-click": "clickAction",
@@ -17,51 +17,71 @@ define([
}, },
clickAction: function (e) { clickAction: function (e) {
this.select(); this.select();
// first click create view else update model's view // first click create edit view else update model"s view
if (typeof AdminFieldApp.fieldEditView === 'undefined') { if (typeof AdminFieldApp.fieldEditView === "undefined") {
AdminFieldApp.fieldEditView = new FieldEditView({ AdminFieldApp.fieldEditView = new FieldEditView({
el: $('.right-block')[0], el: AdminFieldApp.$rightBlock,
model: this.model model: this.model
}); });
} else { } else {
AdminFieldApp.fieldEditView.model = this.model; AdminFieldApp.fieldEditView.updateModel(this.model).initialize();
} }
AdminFieldApp.fieldEditView.render(); AdminFieldApp.fieldEditView.render();
return this; return this;
}, },
dropAction: function(event, index) { dropAction: function(event, ui) {
this.$el.trigger('update-sort', [this.model, index]); this.$el.trigger("update-sort", [this.model, ui]);
return this;
}, },
render: function() { render: function() {
var template = _.template($("#list_row_template").html(), { var template = _.template($("#list_row_template").html(), {
id: this.model.get('id'), id: this.model.get("id"),
position: this.model.get('sorter'), position: this.model.get("sorter"),
name: this.model.get('name'), name: this.model.get("name"),
tag: this.model.get('tag') tag: this.model.get("tag")
}); });
this.$el.empty().html(template); 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(); this.select();
} }
return this; return this;
}, },
// set selected class // set selected class to current view
select: function () { select: function () {
$("li", this.$el.closest('ul')).removeClass('selected'); $("li", this.$el.closest("ul")).removeClass("selected");
this.$el.addClass('selected'); this.$el.addClass("selected");
return this; return this;
}, },
animate: function () { // scroll to current view in item list
var offset = this.$el.offset(); animate: function (top) {
top = top || null;
if (null === top) {
top = $(".field-row").index(this.$el) * this.$el.height();
}
this.$el.closest('div').animate({ this.$el.closest("div").scrollTop(top);
scrollTop: offset.top - 20
}); 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([ define([
'underscore', "jquery",
'backbone', "underscore",
'i18n', "backbone",
'bootstrap' "i18n",
], function(_, Backbone, i18n, bootstrap) { "bootstrap"
], function($, _, Backbone, i18n, bootstrap) {
var ModalView = Backbone.View.extend({ var ModalView = Backbone.View.extend({
tagName: "div", tagName: "div",
className: "modal", className: "modal",
events: { events: {
'click .confirm': 'confirmAction' "click .confirm": "confirmAction"
}, },
initialize: function (options) { initialize: function (options) {
var self = this; var self = this;
// remove view when modal is closed // remove view when modal is closed
this.$el.on('hidden', function() { this.$el.on("hidden", function() {
self.remove(); self.remove();
}); });
@@ -22,8 +23,8 @@ define([
} }
}, },
render: function() { render: function() {
var template = _.template($("#modal_delete_confirm_template").html(), { var template = _.template($("#modal_template").html(), {
msg: this.message || '' msg: this.message || ""
}); });
this.$el.html(template).modal(); this.$el.html(template).modal();
@@ -31,8 +32,8 @@ define([
return this; return this;
}, },
confirmAction: function () { confirmAction: function () {
this.trigger('modal:confirm'); this.trigger("modal:confirm");
this.$el.modal('hide'); this.$el.modal("hide");
this.remove(); this.remove();
return this; 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([ define([
'underscore', "underscore",
'backbone' "backbone"
], function(_, Backbone) { ], function(_, Backbone) {
var DcFieldModel = Backbone.Model.extend({ var DcFieldModel = Backbone.Model.extend({
urlRoot: function () { urlRoot: function () {
return '/admin/fields/dc-fields'; return "/admin/fields/dc-fields";
} }
}); });

View File

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

View File

@@ -1,10 +1,10 @@
define([ define([
'underscore', "underscore",
'backbone' "backbone"
], function(_, Backbone) { ], function(_, Backbone) {
var VocabularyModel = Backbone.Model.extend({ var VocabularyModel = Backbone.Model.extend({
urlRoot: function () { 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.expect = shai.expect;
window.assert = shai.assert; window.assert = shai.assert;
}); });

View File

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

View File

@@ -1,5 +1,5 @@
define(function(require) { define(function(require) {
it('should run', function () { it("should run", function () {
expect(true).to.equal(true); 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 { #admin-field-app h4 {
padding: 10px 0; padding: 10px 0;
} }
#admin-field-app li { #admin-field-app .left-block li {
background: #FFF; background: #FFF;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
border-left: 1px solid #ccc; border-left: 1px solid #ccc;
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
height: 55px;
} }
#admin-field-app li table { #admin-field-app .left-block li .trigger-click {
table-layout:fixed; cursor: pointer;
width:100%;
} }
#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-weight: bold;
font-size: 16px; font-size: 16px;
color: #666; color: #666;
} }
#admin-field-app li .handle { #admin-field-app .left-block li .handle {
width: 10%; width: 10%;
vertical-align:middle; vertical-align: middle;
text-align:center; text-align: center;
cursor: move; cursor: move;
} }
#admin-field-app li .trigger-click { #admin-field-app .left-block li .trigger-click {
padding:10px; padding: 10px;
} }
#admin-field-app li .position { #admin-field-app .left-block li .position {
width: 10%; width: 10%;
vertical-align:bottom; vertical-align: bottom;
text-align: center text-align: center;
} }
#admin-field-app li .chip { #admin-field-app .left-block li .chip {
width:10%; 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; color: #ccc;
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
} }
#admin-field-app li.last { #admin-field-app .left-block li.last {
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
} }
#admin-field-app li.selected { #admin-field-app .left-block li.selected {
border-top-color: #0080FF; border-top-color: #0080FF;
background: #FFF; background: #FFF;
color: #000; 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; 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; 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 { #admin-field-app .add-field-block .control-label {
width: 80px; width: 80px;
text-align: left; text-align: left;
@@ -85,13 +135,42 @@
margin: 20px 0; margin: 20px 0;
} }
#admin-field-app #collection-fields { #admin-field-app .list-block {
height:450px; height: 450px;
min-height: 130px;
overflow: auto; overflow: auto;
position: relative;
} }
#admin-field-app .edit-block { #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 { #admin-field-app .edit-block .edit-order {
@@ -103,17 +182,38 @@
} }
#admin-field-app .edit-block input#name { #admin-field-app .edit-block input#name {
font-size:28px; font-size: 28px;
color:#0080FF; color: #0080FF;
height:42px; height: 42px;
line-height:42px; line-height: 42px;
font-weight:bold 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 { .ui-autocomplete-admin-field {
list-style-type: none; list-style-type: none;
overflow-y: scroll; overflow-y: auto;
height: 180px; overflow-x: hidden;
max-height: 180px;
background: #FFF; background: #FFF;
max-width: 300px; max-width: 300px;
-webkit-box-shadow: 0 10px 6px -6px #777; -webkit-box-shadow: 0 10px 6px -6px #777;
@@ -121,10 +221,25 @@
box-shadow: 0 10px 6px -6px #777; box-shadow: 0 10px 6px -6px #777;
} }
.ui-autocomplete-admin-field li{ .ui-autocomplete-admin-field li {
padding: 3px; 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 { .ui-autocomplete-admin-field li a {
text-decoration: none; text-decoration: none;
} }
.ui-autocomplete-admin-field li a:hover {
text-decoration: none;
background: none;
color: #000;
border: none;
}