Merge branch 'develop' into dynamic-partials

This commit is contained in:
alekseybobkov 2015-01-03 16:41:25 -08:00
commit 7e5ce04a79
30 changed files with 538 additions and 509 deletions

View File

@ -1,5 +1,8 @@
* **Build 17x** (2014-12-xx)
- Improved asset caching (`cms.enableAssetCache`), when enabled the server will send a *304 Not Modified* header.
- Introduced new *Table* widget and *DataTable* form widget.
- There is now a simpler way for sending mail via `Mail::sendTo()`.
- The List Filter query can now be extended with controller override `listFilterExtendQuery()`.
* **Build 171** (2014-12-17)
- Add new methods `propertyName()` and `paramName()` to Component base class for accessing names of external properties.

View File

@ -56,6 +56,10 @@ class ServiceProvider extends ModuleServiceProvider
$manager->registerFormWidget('Backend\FormWidgets\DataGrid', [
'label' => 'Data Grid',
'code' => 'datagrid'
]); // @deprecated if year >= 2015
$manager->registerFormWidget('Backend\FormWidgets\DataTable', [
'label' => 'Data Table',
'code' => 'datatable'
]);
$manager->registerFormWidget('Backend\FormWidgets\RecordFinder', [
'label' => 'Record Finder',
@ -138,11 +142,11 @@ class ServiceProvider extends ModuleServiceProvider
$manager->registerPermissions('October.Backend', [
'backend.access_dashboard' => [
'label' => 'system::lang.permissions.view_the_dashboard',
'tab' => 'System'
'tab' => 'system::lang.permissions.name'
],
'backend.manage_users' => [
'backend.manage_users' => [
'label' => 'system::lang.permissions.manage_other_administrators',
'tab' => 'System'
'tab' => 'system::lang.permissions.name'
],
]);
});

View File

@ -217,6 +217,13 @@ class ListController extends ControllerBehavior
return $widget->onRefresh();
});
/*
* Extend the query of the list of options
*/
$filterWidget->bindEvent('filter.extendQuery', function($query, $scope) {
$this->controller->listFilterExtendQuery($query, $scope);
});
// Apply predefined filter values
$widget->addFilter([$filterWidget, 'applyAllScopesToQuery']);
@ -351,6 +358,16 @@ class ListController extends ControllerBehavior
{
}
/**
* Controller override: Extend the query used for populating the filter
* options before the default query is processed.
* @param October\Rain\Database\Builder $query
* @param array $scope
*/
public function listFilterExtendQuery($query, $scope)
{
}
/**
* Returns a CSS class name for a list row (<tr class="...">).
* @param Model $record The populated model used for the column

View File

@ -65,14 +65,14 @@ abstract class TableDataSourceBase
/**
* Returns a set of records from the data source.
* @param integer $count Specifies the number of records to return.
* @return array Returns the records.
* @return array Returns the records.
* If there are no more records, returns an empty array.
*/
public function readRecords($count = 10)
{
$result = $this->getRecords($this->offset, $count);
$this->offset += count($result);
return $result;
}
}

View File

@ -0,0 +1,136 @@
<?php namespace Backend\FormWidgets;
use Lang;
use Backend\Widgets\Table;
use Backend\Classes\FormWidgetBase;
use System\Classes\ApplicationException;
/**
* Data Table
* Renders a table field.
*
* @package october\backend
* @author Alexey Bobkov, Samuel Georges
*/
class DataTable extends FormWidgetBase
{
/**
* {@inheritDoc}
*/
public $defaultAlias = 'datatable';
/**
* @var string Table size
*/
protected $size = 'large';
/**
* @var Backend\Widgets\Table Table widget
*/
protected $table;
/**
* {@inheritDoc}
*/
public function init()
{
$this->size = $this->getConfig('size', $this->size);
$this->table = $this->makeTableWidget();
$this->table->bindToController();
}
/**
* @return Backend\Widgets\Table The table to be displayed.
*/
public function getTable()
{
return $this->table;
}
/**
* {@inheritDoc}
*/
public function render()
{
$this->prepareVars();
return $this->makePartial('datatable');
}
/**
* Prepares the list data
*/
public function prepareVars()
{
$this->populateTableWidget();
$this->vars['table'] = $this->table;
$this->vars['size'] = $this->size;
}
/**
* {@inheritDoc}
*/
public function getSaveData($value)
{
$dataSource = $this->table->getDataSource();
$result = [];
while ($records = $dataSource->readRecords()) {
$result += $records;
}
return $result;
}
/*
* Populate data
*/
protected function populateTableWidget()
{
$dataSource = $this->table->getDataSource();
$records = $this->getLoadData() ?: [];
$dataSource->initRecords((array) $records);
}
protected function makeTableWidget()
{
$config = $this->makeConfig((array) $this->config);
$config->dataSource = 'client';
$config->alias = $this->alias . 'Table';
$table = new Table($this->controller, $config);
$table->bindEvent('table.getDropdownOptions', [$this, 'getDataTableOptions']);
return $table;
}
/**
* Looks at the model for getXXXDataTableOptions or getDataTableOptions methods
* to obtain values for autocomplete field types.
* @param string $field Table field name
* @param string $data Data for the entire table
* @return array
*/
public function getDataTableOptions($field, $data)
{
$methodName = 'get'.studly_case($this->fieldName).'DataTableOptions';
if (!$this->model->methodExists($methodName) && !$this->model->methodExists('getDataTableOptions')) {
throw new ApplicationException(Lang::get('backend::lang.model.missing_method', ['class' => get_class($this->model), 'method' => 'getDataTableOptions']));
}
if ($this->model->methodExists($methodName)) {
$result = $this->model->$methodName($field, $data);
}
else {
$result = $this->model->getDataTableOptions($this->fieldName, $field, $data);
}
if (!is_array($result)) {
$result = [];
}
return $result;
}
}

View File

@ -126,8 +126,9 @@ class DatePicker extends FormWidgetBase
return null;
}
if ($this->mode == 'datetime') {
$value .= ' ' . post(self::TIME_PREFIX.$this->formField->getName(false)) . ':00';
$timeValue = post(self::TIME_PREFIX . $this->formField->getName(false));
if ($this->mode == 'datetime' && $timeValue) {
$value .= ' ' . $timeValue . ':00';
}
elseif ($this->mode == 'time') {
$value .= ':00';

View File

@ -0,0 +1,8 @@
<div
id="<?= $this->getId() ?>"
class="field-datatable size-<?= $size ?>">
<?= $table->render() ?>
</div>

View File

@ -1,207 +0,0 @@
html {
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 10px;
font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif;
color: #333;
}
code,
pre {
font-family: Menlo, Monaco, monospace, sans-serif;
}
div,
p,
ul,
ol,
table,
dl,
blockquote,
td,
th,
pre {
font-size: 14px;
line-height: 1.5rem;
}
div {
border: 1px dashed #bbb !important;
}
a {
color: #15c;
text-decoration: underline;
}
.redactor_placeholder {
color: #999 !important;
display: block !important;
margin-bottom: 10px !important;
}
object,
embed,
video,
img {
max-width: 100%;
width: auto;
}
video,
img {
height: auto;
}
div,
p,
ul,
ol,
table,
dl,
blockquote,
pre {
margin: 0;
margin-bottom: 15px;
border: none;
background: none;
box-shadow: none;
}
iframe,
object,
hr {
margin-bottom: 15px;
}
blockquote {
margin-left: 3em;
color: #777;
font-style: italic;
}
ul,
ol {
padding-left: 2em;
}
ul ul,
ol ol,
ul ol,
ol ul {
margin: 2px;
padding: 0;
padding-left: 2em;
border: none;
}
dl dt { font-weight: bold; }
dd { margin-left: 1em;}
table {
border-collapse: collapse;
font-size: 1em;
}
table td {
padding: 5px;
border: 1px solid #ddd;
vertical-align: top;
}
table thead td {
border-bottom: 2px solid #000;
font-weight: bold;
}
code {
background-color: #d8d7d7;
}
pre {
overflow: auto;
padding: 1em;
border: 1px solid #ddd;
border-radius: 3px;
background: #f8f8f8;
white-space: pre;
font-size: 90%;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
}
h1,
h2,
h3,
h4,
h5 {
margin: 0;
padding: 0;
background: none;
color: #333;
font-weight: bold;
}
h1 {
margin-bottom: 10px;
font-size: 36px;
line-height: 40px;
}
h2 {
margin-bottom: 15px;
font-size: 30px;
line-height: 38px;
}
h3 {
margin-bottom: 10px;
font-size: 24px;
line-height: 30px;
}
h4 {
margin-bottom: 10px;
font-size: 18px;
line-height: 24px;
}
h5 {
margin-bottom: 10px;
font-size: 1em;
}
table {
width: 100%;
}
body.redactor_editor_wym {
background: #f4f4f4;
}
.redactor_editor_wym div,
.redactor_editor_wym p,
.redactor_editor_wym ul,
.redactor_editor_wym ol,
.redactor_editor_wym table,
.redactor_editor_wym dl,
.redactor_editor_wym pre,
.redactor_editor_wym h1,
.redactor_editor_wym h2,
.redactor_editor_wym h3,
.redactor_editor_wym h4,
.redactor_editor_wym h5,
.redactor_editor_wym blockquote {
margin: 0 0 10px 0;
padding: 7px 10px;
border: 1px solid #e4e4e4;
background-color: #fff;
}
.redactor_editor_wym div {
border: 1px dashed #bbb !important;
}
.redactor_editor_wym pre {
border: 2px dashed #e4e4e4 !important;
background-color: #fafafa !important;
}
.redactor_editor_wym code {
background-color: #f4f4f4 !important;
}
.redactor_editor_wym ul,
.redactor_editor_wym ol {
padding-left: 2em !important;
}
.redactor_editor_wym ul li ul,
.redactor_editor_wym ul li ol,
.redactor_editor_wym ol li ol,
.redactor_editor_wym ol li ul {
border: none !important;
}

View File

@ -2,26 +2,26 @@
return [
'auth' => [
'title' => 'Administration Area',
'title' => 'Administration Area'
],
'field' => [
'invalid_type' => 'Invalid field type used :type.',
'options_method_not_exists' => 'The model class :model must define a method :method() returning options for the ":field" form field.',
'options_method_not_exists' => "The model class :model must define a method :method() returning options for the ':field' form field."
],
'widget' => [
'not_registered' => "A widget class name ':name' has not been registered",
'not_bound' => "A widget with class name ':name' has not been bound to the controller",
'not_bound' => "A widget with class name ':name' has not been bound to the controller"
],
'page' => [
'untitled' => "Untitled",
'untitled' => 'Untitled',
'access_denied' => [
'label' => "Access denied",
'label' => 'Access denied',
'help' => "You don't have the required permissions to view this page.",
'cms_link' => "Return to the back-end",
],
'cms_link' => 'Return to the back-end'
]
],
'partial' => [
'not_found' => "The partial ':name' is not found.",
'not_found' => "The partial ':name' is not found."
],
'account' => [
'sign_out' => 'Sign out',
@ -30,21 +30,21 @@ return [
'restore' => 'Restore',
'login_placeholder' => 'login',
'password_placeholder' => 'password',
'forgot_password' => "Forgot your password?",
'enter_email' => "Enter your email",
'enter_login' => "Enter your login",
'email_placeholder' => "email",
'enter_new_password' => "Enter a new password",
'password_reset' => "Password Reset",
'restore_success' => "An email has been sent to your email address with password restore instructions.",
'forgot_password' => 'Forgot your password?',
'enter_email' => 'Enter your email',
'enter_login' => 'Enter your login',
'email_placeholder' => 'email',
'enter_new_password' => 'Enter a new password',
'password_reset' => 'Password Reset',
'restore_success' => 'An email has been sent to your email address with password restore instructions.',
'restore_error' => "A user could not be found with a login value of ':login'",
'reset_success' => "Your password has been successfully reset. You may now sign in.",
'reset_error' => "Invalid password reset data supplied. Please try again!",
'reset_fail' => "Unable to reset your password!",
'reset_success' => 'Your password has been successfully reset. You may now sign in.',
'reset_error' => 'Invalid password reset data supplied. Please try again!',
'reset_fail' => 'Unable to reset your password!',
'apply' => 'Apply',
'cancel' => 'Cancel',
'delete' => 'Delete',
'ok' => 'OK',
'ok' => 'OK'
],
'dashboard' => [
'menu_label' => 'Dashboard',
@ -66,7 +66,7 @@ return [
'widget_title_default' => 'System status',
'online' => 'online',
'maintenance' => 'in maintenance',
'update_available' => '{0} updates available!|{1} update available!|[2,Inf] updates available!',
'update_available' => '{0} updates available!|{1} update available!|[2,Inf] updates available!'
]
],
'user' => [
@ -75,18 +75,19 @@ return [
'menu_description' => 'Manage back-end administrator users, groups and permissions.',
'list_title' => 'Manage Administrators',
'new' => 'New Administrator',
'login' => "Login",
'first_name' => "First Name",
'last_name' => "Last Name",
'full_name' => "Full Name",
'email' => "Email",
'groups' => "Groups",
'groups_comment' => "Specify which groups this person belongs to.",
'avatar' => "Avatar",
'password' => "Password",
'password_confirmation' => "Confirm Password",
'superuser' => "Super User",
'superuser_comment' => "Check this box to allow this person to access all areas.",
'login' => 'Login',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'full_name' => 'Full Name',
'email' => 'Email',
'groups' => 'Groups',
'groups_comment' => 'Specify which groups this person belongs to.',
'avatar' => 'Avatar',
'password' => 'Password',
'password_confirmation' => 'Confirm Password',
'permissions' => 'Permissions',
'superuser' => 'Super User',
'superuser_comment' => 'Check this box to allow this person to access all areas.',
'send_invite' => 'Send invitation by email',
'send_invite_comment' => 'Use this checkbox to send an invitation to the user by email',
'delete_confirm' => 'Do you really want to delete this administrator?',
@ -97,15 +98,11 @@ return [
'group' => [
'name' => 'Group',
'name_field' => 'Name',
'description_field' => 'Description',
'is_new_user_default_field' => 'Add new administrators to this group by default',
'code_field' => 'Code',
'code_comment' => 'Enter a unique code if you want to access it with the API.',
'menu_label' => 'Groups',
'list_title' => 'Manage Groups',
'new' => 'New Administrator Group',
'delete_confirm' => 'Do you really want to delete this administrator group?',
'return' => 'Return to the group list',
'return' => 'Return to the group list'
],
'preferences' => [
'not_authenticated' => 'There is no an authenticated user to load or save preferences for.'
@ -137,17 +134,17 @@ return [
'description_label' => 'Description'
],
'form' => [
'create_title' => "New :name",
'update_title' => "Edit :name",
'preview_title' => "Preview :name",
'create_title' => 'New :name',
'update_title' => 'Edit :name',
'preview_title' => 'Preview :name',
'create_success' => 'The :name has been created successfully',
'update_success' => 'The :name has been updated successfully',
'delete_success' => 'The :name has been deleted successfully',
'missing_id' => "Form record ID has not been specified.",
'missing_id' => 'Form record ID has not been specified.',
'missing_model' => 'Form behavior used in :class does not have a model defined.',
'missing_definition' => "Form behavior does not contain a field for ':field'.",
'not_found' => 'Form record with an ID of :id could not be found.',
'action_confirm' => "Are you sure?",
'action_confirm' => 'Are you sure?',
'create' => 'Create',
'create_and_close' => 'Create and close',
'creating' => 'Creating...',
@ -159,9 +156,6 @@ return [
'delete' => 'Delete',
'deleting' => 'Deleting...',
'deleting_name' => 'Deleting :name...',
'reset_default' => 'Reset to default',
'resetting' => 'Resetting',
'resetting_name' => 'Resetting :name',
'undefined_tab' => 'Misc',
'field_off' => 'Off',
'field_on' => 'On',
@ -182,41 +176,39 @@ return [
'select_placeholder' => 'please select',
'insert_row' => 'Insert Row',
'delete_row' => 'Delete Row',
'concurrency_file_changed_title' => "File was changed",
'concurrency_file_changed_description' => "The file you're editing has been changed on disk by another user. You can either reload the file and lose your changes or override the file on the disk.",
'concurrency_file_changed_title' => 'File was changed',
'concurrency_file_changed_description' => "The file you're editing has been changed on disk by another user. You can either reload the file and lose your changes or override the file on the disk."
],
'relation' => [
'missing_definition' => "Relation behavior does not contain a definition for ':field'.",
'missing_model' => "Relation behavior used in :class does not have a model defined.",
'invalid_action_single' => "This action cannot be performed on a singular relationship.",
'invalid_action_multi' => "This action cannot be performed on a multiple relationship.",
'help' => "Click on an item to add",
'related_data' => "Related :name data",
'add' => "Add",
'add_selected' => "Add selected",
'add_a_new' => "Add a new :name",
'cancel' => "Cancel",
'close' => "Close",
'add_name' => "Add :name",
'create' => "Create",
'create_name' => "Create :name",
'update' => "Update",
'update_name' => "Update :name",
'preview' => "Preview",
'preview_name' => "Preview :name",
'remove' => "Remove",
'remove_name' => "Remove :name",
'delete' => "Delete",
'delete_name' => "Delete :name",
'delete_confirm' => "Are you sure?",
'missing_model' => 'Relation behavior used in :class does not have a model defined.',
'invalid_action_single' => 'This action cannot be performed on a singular relationship.',
'invalid_action_multi' => 'This action cannot be performed on a multiple relationship.',
'help' => 'Click on an item to add',
'related_data' => 'Related :name data',
'add' => 'Add',
'add_selected' => 'Add selected',
'add_a_new' => 'Add a new :name',
'cancel' => 'Cancel',
'add_name' => 'Add :name',
'create' => 'Create',
'create_name' => 'Create :name',
'update' => 'Update',
'update_name' => 'Update :name',
'remove' => 'Remove',
'remove_name' => 'Remove :name',
'delete' => 'Delete',
'delete_name' => 'Delete :name',
'delete_confirm' => 'Are you sure?'
],
'model' => [
'name' => "Model",
'name' => 'Model',
'not_found' => "Model ':class' with an ID of :id could not be found",
'missing_id' => "There is no ID specified for looking up the model record.",
'missing_id' => 'There is no ID specified for looking up the model record.',
'missing_relation' => "Model ':class' does not contain a definition for ':relation'.",
'missing_method' => "Model ':class' does not contain a method ':method'.",
'invalid_class' => "Model :model used in :class is not valid, it must inherit the \Model class.",
'mass_assignment_failed' => "Mass assignment failed for Model attribute ':attribute'.",
'mass_assignment_failed' => "Mass assignment failed for Model attribute ':attribute'."
],
'warnings' => [
'tips' => 'System configuration tips',
@ -235,14 +227,14 @@ return [
'highlight_active_line' => 'Highlight active line',
'show_invisibles' => 'Show invisible characters',
'show_gutter' => 'Show gutter',
'theme' => 'Color scheme',
'theme' => 'Color scheme'
],
'tooltips' => [
'preview_website' => 'Preview the website'
],
'mysettings' => [
'menu_label' => 'My Settings',
'menu_description' => 'Settings relate to your administration account',
'menu_description' => 'Settings relate to your administration account'
],
'myaccount' => [
'menu_label' => 'My account',
@ -271,7 +263,7 @@ return [
'menu_label' => 'Back-end preferences',
'menu_description' => 'Manage your account preferences such as desired language.',
'locale' => 'Language',
'locale_comment' => 'Select your desired locale for language use.',
'locale_comment' => 'Select your desired locale for language use.'
],
'access_log' => [
'hint' => 'This log displays a list of successful sign in attempts by administrators. Records are kept for a total of :days days.',
@ -282,7 +274,7 @@ return [
'ip_address' => 'IP address',
'first_name' => 'First name',
'last_name' => 'Last name',
'email' => 'Email',
'email' => 'Email'
],
'filter' => [
'all' => 'all'

View File

@ -97,6 +97,10 @@ return [
'group' => [
'name' => 'Csoport',
'name_field' => 'Név',
'description_field' => 'Leírás',
'is_new_user_default_field' => 'Az új webhelygazdák hozzáadása alapértelmezésként ehhez a csoporthoz',
'code_field' => 'Kód',
'code_comment' => 'Adjon meg egy egyedi kódot, ha az API-val kíván hozzáférni.',
'menu_label' => 'Csoportok',
'list_title' => 'Csoportok kezelése',
'new' => 'Új webhelygazda csoport',
@ -192,11 +196,14 @@ return [
'add_selected' => "Kijelöltek hozzáadása",
'add_a_new' => "Új :name hozzáadása",
'cancel' => "Mégse",
'close' => "Bezárás",
'add_name' => ":name hozzáadása",
'create' => "Létrehozás",
'create_name' => ":name létrehozása",
'update' => "Frissítés",
'update_name' => "A(z) :name frissítése",
'preview' => "Előnézet",
'preview_name' => "Előnézet neve",
'remove' => "Eltávolítás",
'remove_name' => "A(z) :name eltávolítása",
'delete' => "Törlés",

View File

@ -40,14 +40,14 @@ tabs:
permissions[superuser]:
context: [create, update]
tab: Permissions
tab: backend::lang.user.permissions
label: backend::lang.user.superuser
type: checkbox
comment: backend::lang.user.superuser_comment
groups:
context: [create, update]
tab: Permissions
tab: backend::lang.user.permissions
label: backend::lang.user.groups
commentAbove: backend::lang.user.groups_comment
type: checkboxlist

View File

@ -208,12 +208,16 @@ class Filter extends WidgetBase
protected function getOptionsFromModel($scope, $searchQuery = null)
{
$model = $this->scopeModels[$scope->scopeName];
$query = $model->newQuery();
$this->fireEvent('filter.extendQuery', [$query, $scope]);
if (!$searchQuery) {
return $model->all();
return $query->get();
}
$searchFields = [$model->getKeyName(), $this->getScopeNameColumn($scope)];
return $model->searchWhere($searchQuery, $searchFields)->get();
return $query->searchWhere($searchQuery, $searchFields)->get();
}
/**

View File

@ -710,7 +710,7 @@ class Form extends WidgetBase
}
$fieldName = $field->fieldName;
$defaultValue = (!$this->model->exists) && !empty($field->defaults) ? $field->defaults : null;
$defaultValue = (!$this->model->exists && $field->defaults !== '') ? $field->defaults : null;
/*
* Array field name, eg: field[key][key2][key3]

View File

@ -32,7 +32,7 @@ class Table extends WidgetBase
protected $dataSource = null;
protected $recordsKeyColumn;
protected $recordsKeyFrom;
protected $dataSourceAliases = [
'client' => '\Backend\Classes\TableClientMemoryDataSource'
@ -45,19 +45,22 @@ class Table extends WidgetBase
{
$this->columns = $this->getConfig('columns', []);
$this->recordsKeyColumn = $this->getConfig('key_column', 'id');
$this->recordsKeyFrom = $this->getConfig('keyFrom', 'id');
$dataSourceClass = $this->getConfig('data_source');
if (!strlen($dataSourceClass))
$dataSourceClass = $this->getConfig('dataSource');
if (!strlen($dataSourceClass)) {
throw new SystemException('The Table widget data source is not specified in the configuration.');
}
if (array_key_exists($dataSourceClass, $this->dataSourceAliases))
if (array_key_exists($dataSourceClass, $this->dataSourceAliases)) {
$dataSourceClass = $this->dataSourceAliases[$dataSourceClass];
}
if (!class_exists($dataSourceClass))
if (!class_exists($dataSourceClass)) {
throw new SystemException(sprintf('The Table widget data source class "%s" is could not be found.', $dataSourceClass));
}
$this->dataSource = new $dataSourceClass($this->recordsKeyColumn);
$this->dataSource = new $dataSourceClass($this->recordsKeyFrom);
if (Request::method() == 'POST' && $this->isClientDataSource()) {
$requestDataField = $this->alias.'TableData';
@ -94,10 +97,10 @@ class Table extends WidgetBase
public function prepareVars()
{
$this->vars['columns'] = $this->prepareColumnsArray();
$this->vars['recordsKeyColumn'] = $this->recordsKeyColumn;
$this->vars['recordsKeyFrom'] = $this->recordsKeyFrom;
$this->vars['recordsPerPage'] = $this->getConfig('records_per_page', false) ?: 'false';
$this->vars['postbackHandlerName'] = $this->getConfig('postback_handler_name', 'onSave');
$this->vars['recordsPerPage'] = $this->getConfig('recordsPerPage', false) ?: 'false';
$this->vars['postbackHandlerName'] = $this->getConfig('postbackHandlerName', 'onSave');
$this->vars['adding'] = $this->getConfig('adding', true);
$this->vars['deleting'] = $this->getConfig('deleting', true);
$this->vars['toolbar'] = $this->getConfig('toolbar', true);
@ -106,9 +109,9 @@ class Table extends WidgetBase
$isClientDataSource = $this->isClientDataSource();
$this->vars['clientDataSourceClass'] = $isClientDataSource ? 'client' : 'server';
$this->vars['data'] = $isClientDataSource ?
json_encode($this->dataSource->getAllRecords()) :
[];
$this->vars['data'] = $isClientDataSource
? json_encode($this->dataSource->getAllRecords())
: [];
}
//
@ -150,7 +153,7 @@ class Table extends WidgetBase
return $result;
}
protected function isClientDataSource()
protected function isClientDataSource()
{
return $this->dataSource instanceof \Backend\Classes\TableClientMemoryDataSource;
}
@ -167,8 +170,9 @@ class Table extends WidgetBase
$eventResults = $this->fireEvent('table.getDropdownOptions', [$columnName, $rowData]);
$options = [];
if (count($eventResults))
if (count($eventResults)) {
$options = $eventResults[0];
}
return [
'options' => $options

View File

@ -79,7 +79,7 @@ The options below are listed in the JavaScript notation. Corresponding data attr
- `rowSorting` - enables the drag & drop row sorting. The sorting cannot be used with the pagination (`recordsPerPage` is not `null` or `false`).
- `keyColumn` - specifies the name of the key column. The default value is **id**.
- `postback` - post the client-memory data source data to the server automatically when the parent form gets submitted. The default value is `true`. The option is used only with client-memory data sources. When enabled, the data source data is available in the widget's server-side data source: `$table->getDataSource()->getRecords();` The data postback occurs only of the request handler name matches the `postbackHandlerName` option value.
- `postbackHandlerName` - AJAX data handler name for the automatic data postback. The data will be posted only when the AJAX requests posts data to this handler. The default value is **onSave**.
- `postbackHandlerName` - AJAX data handler name for the automatic data postback. The data will be posted only when the AJAX request posts data matching this handler name. The default value is **onSave**.
- `adding` - determines whether users can add new records. Default value is **true**.
- `deleting` - determines whether users can delete records. Default value is **true**.
- `toolbar` - determines whether the toolbar is visible. The default value is **true**.
@ -144,19 +144,19 @@ If the `options` element is not presented in the configuration, the options will
**TODO:** Document the AJAX interface
The drop-down options could depend on other columns. This works only with AJAX-based drop-downs. The column a drop-down depends on are defined with the `depends_on` property:
The drop-down options could depend on other columns. This works only with AJAX-based drop-downs. The column a drop-down depends on are defined with the `dependsOn` property:
state:
title: State
type: dropdown
depends_on: country
dependsOn: country
Multiple fields are allowed as well:
state:
title: State
type: dropdown
depends_on: [country, language]
dependsOn: [country, language]
**Note:** Dependent drop-down should always be defined after their master columns.
@ -166,17 +166,17 @@ Multiple fields are allowed as well:
The widget is configured with YAML file. Required parameters:
* `columns` - the columns definitions, see below
* `data_source` - The data source class. Should specify the full qualified data source class name or alias. See the data source aliases below.
* `key_column` - name of the key column. The default value is **id**.
* `records_per_page` - number of records per page. If not specified, the pagination will be disabled.
* `postback_handler_name` - AJAX data handler name for the automatic data postback. The data will be posted only when the AJAX requests posts data to this handler. The default value is **onSave**. This parameter is applicable only with client-memory data sources.
* `columns` - the columns definitions, see below.
* `dataSource` - The data source class. Should specify the full qualified data source class name or alias. See the data source aliases below.
* `keyFrom` - name of the key column. The default value is **id**.
* `recordsPerPage` - number of records per page. If not specified, the pagination will be disabled.
* `postbackHandlerName` - AJAX data handler name for the automatic data postback. The data will be posted only when the AJAX requests posts data to this handler. The default value is **onSave**. This parameter is applicable only with client-memory data sources.
* `adding` - indicates if record deleting is allowed, default is **true**.
* `deleting` - indicates if record deleting is allowed, default is **true**.
* `toolbar` - specifies if the toolbar should be visible, default is **true**.
* `height` - specifies the data table height, in pixels. The default value is **false** - the height is not limited.
The `data_source` parameter can take aliases for some data source classes for the simpler configuration syntax. Known aliases are:
The `dataSource` parameter can take aliases for some data source classes for the simpler configuration syntax. Known aliases are:
* `client` = \Backend\Classes\TableClientMemoryDataSource
@ -187,9 +187,9 @@ Columns are defined as array with the `columns` property. The array keys corresp
- `title`
- `type` (string, checkbox, dropdown, autocomplete)
- `width` - sets the column width, can be specified in percents (10%) or pixels (50px). There could be a single column without the width specified. It will be stretched to take the available space.
- `readonly`
- `readOnly`
- `options` (for drop-down elements and autocomplete types)
- `depends_on` (from drop-down elements)
- `dependsOn` (from drop-down elements)
## Events
@ -245,9 +245,9 @@ $dataSource->purge();
## Reading data from the data source
The server-side data sources (PHP) automatically maintain the actual data, but that mechanism for the client-memory and server-memory data sources is different.
The server-side data sources (PHP) automatically maintain the actual data, but that mechanism for the client-memory and server-memory data sources is different.
In case of the client-memory data source, the table widget adds the data records to the POST, when the form is saved (see `postback` and `postbackHandlerName` options). On the server side the data is inserted to the data source by the table widget.
In case of the client-memory data source, the table widget adds the data records to the POST, when the form is saved using the AJAX Framework (see `postback` and `postbackHandlerName` options). The table data will be injected automatically to the AJAX request when the `postback` value is `true` and the `postbackHandlerName` matches the exact handler name of the request. On the server side the data is inserted to the data source and can be accessed using the PHP example below.
The server-memory data source always automatically maintain its contents in synch with the client using AJAX, and POSTing data is not required.

View File

@ -1,7 +1,7 @@
/*
* General control styling
*/
.table-control .table-container {
.control-table .table-container {
border: 1px solid #808c8d;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
@ -9,18 +9,18 @@
overflow: hidden;
margin-bottom: 15px;
}
.table-control table {
.control-table table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.table-control table td,
.table-control table th {
.control-table table td,
.control-table table th {
padding: 0;
font-size: 13px;
color: #555555;
}
.table-control table [data-view-container] {
.control-table table [data-view-container] {
padding: 5px 10px;
width: 100%;
white-space: nowrap;
@ -28,7 +28,7 @@
text-overflow: ellipsis;
min-height: 28px;
}
.table-control table.headers:after {
.control-table table.headers:after {
content: ' ';
display: block;
position: absolute;
@ -37,7 +37,7 @@
margin-top: -1px;
border-bottom: 1px solid #bdc3c7;
}
.table-control table.headers th {
.control-table table.headers th {
padding: 7px 10px;
font-size: 13px;
text-transform: uppercase;
@ -49,84 +49,84 @@
overflow: hidden;
text-overflow: ellipsis;
}
.table-control table.headers th [data-view-container] {
.control-table table.headers th [data-view-container] {
padding-bottom: 6px;
}
.table-control table.headers th:last-child {
.control-table table.headers th:last-child {
border-right: none;
}
.table-control table.data td {
.control-table table.data td {
border: 1px solid #ecf0f1;
/* TODO: this should be applied only when the control is active */
}
.table-control table.data td .content-container {
.control-table table.data td .content-container {
position: relative;
padding: 1px;
}
.table-control table.data td.active {
.control-table table.data td.active {
border-color: #5fb6f5 !important;
}
.table-control table.data td.active .content-container {
.control-table table.data td.active .content-container {
padding: 0;
border: 1px solid #5fb6f5;
}
.table-control table.data td.active .content-container:before,
.table-control table.data td.active .content-container:after {
.control-table table.data td.active .content-container:before,
.control-table table.data td.active .content-container:after {
content: ' ';
background: #5fb6f5;
position: absolute;
left: -2px;
top: -2px;
}
.table-control table.data td.active .content-container:before {
.control-table table.data td.active .content-container:before {
width: 1px;
bottom: -2px;
}
.table-control table.data td.active .content-container:after {
.control-table table.data td.active .content-container:after {
right: -2px;
height: 1px;
}
.table-control table.data tr {
.control-table table.data tr {
background-color: white;
}
.table-control table.data tr:nth-child(2n) {
.control-table table.data tr:nth-child(2n) {
background-color: #fbfbfb;
}
.table-control table.data tr:first-child td {
.control-table table.data tr:first-child td {
border-top: none;
}
.table-control table.data tr:last-child td {
.control-table table.data tr:last-child td {
border-bottom: none;
}
.table-control table.data td:first-child {
.control-table table.data td:first-child {
border-left: none;
}
.table-control table.data td:last-child {
.control-table table.data td:last-child {
border-right: none;
}
.table-control .control-scrollbar > div {
.control-table .control-scrollbar > div {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
overflow: hidden;
}
.table-control .control-scrollbar table.data tr:last-child td {
.control-table .control-scrollbar table.data tr:last-child td {
border-bottom: 1px solid #ecf0f1;
}
.table-control .toolbar {
.control-table .toolbar {
background: white;
border-bottom: 1px solid #bdc3c7;
}
.table-control .toolbar a {
.control-table .toolbar a {
color: #323e50;
padding: 10px;
opacity: 0.5;
filter: alpha(opacity=50);
}
.table-control .toolbar a:hover {
.control-table .toolbar a:hover {
opacity: 1;
filter: alpha(opacity=100);
}
.table-control .toolbar a:before {
.control-table .toolbar a:before {
display: inline-block;
content: ' ';
width: 16px;
@ -138,17 +138,17 @@
background-position: 0 0;
background-size: 32px auto;
}
.table-control .toolbar a.add-table-row-above:before {
.control-table .toolbar a.add-table-row-above:before {
background-position: 0 -56px;
}
.table-control .toolbar a.delete-table-row:before {
.control-table .toolbar a.delete-table-row:before {
background-position: 0 -113px;
}
.table-control .pagination ul {
.control-table .pagination ul {
padding: 0;
margin-bottom: 15px;
}
.table-control .pagination ul li {
.control-table .pagination ul li {
list-style: none;
padding: 4px 6px;
-webkit-border-radius: 2px;
@ -160,32 +160,32 @@
background: #ecf0f1;
line-height: 100%;
}
.table-control .pagination ul li a {
.control-table .pagination ul li a {
text-decoration: none;
color: #95a5a6;
}
.table-control .pagination ul li.active {
.control-table .pagination ul li.active {
background: #5fb6f5;
}
.table-control .pagination ul li.active a {
.control-table .pagination ul li.active a {
color: #ffffff;
}
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.table-control .toolbar a:before {
.control-table .toolbar a:before {
background-position: 0px -9px;
background-size: 16px auto;
}
.table-control .toolbar a.add-table-row-above:before {
.control-table .toolbar a.add-table-row-above:before {
background-position: 0 -39px;
}
.table-control .toolbar a.delete-table-row:before {
.control-table .toolbar a.delete-table-row:before {
background-position: 0 -66px;
}
}
/*
* String editor
*/
.table-control td[data-column-type=string] input[type=text] {
.control-table td[data-column-type=string] input[type=text] {
width: 100%;
height: 100%;
display: block;
@ -196,7 +196,7 @@
/*
* Checkbox editor
*/
.table-control td[data-column-type=checkbox] div[data-checkbox-element] {
.control-table td[data-column-type=checkbox] div[data-checkbox-element] {
width: 16px;
height: 16px;
border-radius: 2px;
@ -209,14 +209,14 @@
-ms-user-select: none;
user-select: none;
}
.table-control td[data-column-type=checkbox] div[data-checkbox-element]:hover {
.control-table td[data-column-type=checkbox] div[data-checkbox-element]:hover {
border-color: #808080;
color: #4d4d4d;
}
.table-control td[data-column-type=checkbox] div[data-checkbox-element].checked {
.control-table td[data-column-type=checkbox] div[data-checkbox-element].checked {
border-width: 2px;
}
.table-control td[data-column-type=checkbox] div[data-checkbox-element].checked:before {
.control-table td[data-column-type=checkbox] div[data-checkbox-element].checked:before {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
@ -229,25 +229,25 @@
left: 1px;
top: -4px;
}
.table-control td[data-column-type=checkbox] div[data-checkbox-element]:focus {
.control-table td[data-column-type=checkbox] div[data-checkbox-element]:focus {
border-color: #5fb6f5;
outline: none;
}
/*
* Dropdown editor
*/
.table-control td[data-column-type=dropdown] {
.control-table td[data-column-type=dropdown] {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.table-control td[data-column-type=dropdown] [data-view-container] {
.control-table td[data-column-type=dropdown] [data-view-container] {
padding-right: 20px;
position: relative;
cursor: pointer;
}
.table-control td[data-column-type=dropdown] [data-view-container]:after {
.control-table td[data-column-type=dropdown] [data-view-container]:after {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
@ -262,13 +262,13 @@
top: 8px;
right: 10px;
}
.table-control td[data-column-type=dropdown] [data-view-container]:hover:after {
.control-table td[data-column-type=dropdown] [data-view-container]:hover:after {
color: #0181b9;
}
.table-control td[data-column-type=dropdown] [data-dropdown-open=true] {
.control-table td[data-column-type=dropdown] [data-dropdown-open=true] {
background: white;
}
.table-control td[data-column-type=dropdown] [data-dropdown-open=true] [data-view-container]:after {
.control-table td[data-column-type=dropdown] [data-dropdown-open=true] [data-view-container]:after {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
@ -277,10 +277,10 @@
*margin-right: .3em;
content: "\f106";
}
.table-control td[data-column-type=dropdown] .content-container {
.control-table td[data-column-type=dropdown] .content-container {
outline: none;
}
html.cssanimations .table-control td[data-column-type=dropdown] [data-view-container].loading:after {
html.cssanimations .control-table td[data-column-type=dropdown] [data-view-container].loading:after {
background-image: url(../../../../assets/images/loading-indicator-transparent.svg);
background-size: 15px 15px;
background-position: 50% 50%;

View File

@ -25,7 +25,7 @@
if (dataString === null || dataString === undefined)
throw new Error('The required data-data attribute is not found on the table control element.')
this.data = JSON.parse(dataString)
};
@ -71,11 +71,10 @@
Client.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
if (placement === 'bottom') {
// Add record to the bottom of the dataset
this.data.push(recordData)
} else if (placement == 'above' || placement == 'below') {
}
else if (placement == 'above' || placement == 'below') {
// Add record above or below the passed record key
var recordIndex = this.getIndexOfKey(relativeToKey)
if (placement == 'below')
recordIndex ++
@ -98,8 +97,10 @@
if (recordIndex !== -1) {
recordData[this.tableObj.options.keyColumn] = key
this.data[recordIndex] = recordData
} else
}
else {
throw new Error('Record with they key '+key+ ' is not found in the data set')
}
}
/*
@ -124,16 +125,18 @@
this.data.push(newRecordData)
this.getRecords(offset, count, onSuccess)
} else
}
else {
throw new Error('Record with they key '+key+ ' is not found in the data set')
}
}
Client.prototype.getIndexOfKey = function(key) {
var keyColumn = this.tableObj.options.keyColumn
return this.data.map(function(record) {
return record[keyColumn]
}).indexOf(parseInt(key))
return record[keyColumn] + ""
}).indexOf(key + "")
}
Client.prototype.getAllData = function() {

View File

@ -278,9 +278,9 @@
if (this.options.columns[i].width)
header.setAttribute('style', 'width: '+this.options.columns[i].width)
header.textContent !== undefined ?
header.textContent = this.options.columns[i].title :
header.innerText = this.options.columns[i].title
header.textContent !== undefined
? header.textContent = this.options.columns[i].title
: header.innerText = this.options.columns[i].title
row.appendChild(header)
}
@ -395,7 +395,7 @@
Table.prototype.scrollCellIntoView = function() {
if (!this.options.height || !this.activeCell)
return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').gotoElement(this.activeCell)
}
@ -605,7 +605,7 @@
Table.prototype.getToolbar = function() {
return this.tableContainer.querySelector('div.toolbar')
}
}
// EVENT HANDLERS
// ============================
@ -675,13 +675,13 @@
cmd = target.getAttribute('data-cmd')
switch (cmd) {
case 'record-add-below':
case 'record-add-below':
this.addRecord('below')
break
case 'record-add-above':
case 'record-add-above':
this.addRecord('above')
break
case 'record-delete':
case 'record-delete':
this.deleteRecord()
break
}

View File

@ -230,7 +230,7 @@
DropdownProcessor.prototype.createOptionsCachingKey = function(row) {
var cachingKey = 'non-dependent',
dependsOn = this.columnConfiguration.depends_on
dependsOn = this.columnConfiguration.dependsOn
if (dependsOn) {
if (typeof dependsOn == 'object') {
@ -277,8 +277,7 @@
DropdownProcessor.prototype.onItemClick = function(ev) {
var target = this.tableObj.getEventTarget(ev)
if (target.tagName == 'LI')
{
if (target.tagName == 'LI') {
this.updateCellFromSelectedItem(target)
var selected = this.findSelectedItem()
@ -349,11 +348,11 @@
// Determine if this drop-down depends on the changed column
// and update the option list if necessary
if (!this.columnConfiguration.depends_on)
if (!this.columnConfiguration.dependsOn)
return
var dependsOnColumn = false,
dependsOn = this.columnConfiguration.depends_on
dependsOn = this.columnConfiguration.dependsOn
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ ) {
@ -362,8 +361,10 @@
break
}
}
} else
}
else {
dependsOnColumn = dependsOn == columnName
}
if (!dependsOnColumn)
return
@ -372,9 +373,9 @@
viewContainer = this.getViewContainer(cellElement)
this.fetchOptions(cellElement, function rowValueChangedFetchOptions(options) {
var value = options[currentValue] !== undefined ?
options[currentValue] :
'...'
var value = options[currentValue] !== undefined
? options[currentValue]
: '...'
viewContainer.textContent = value
viewContainer = null

View File

@ -4,7 +4,7 @@
* General control styling
*/
.table-control {
.control-table {
.table-container {
border: 1px solid #808c8d;
.border-radius(4px);
@ -43,7 +43,7 @@
margin-top: -1px;
border-bottom: 1px solid #bdc3c7;
}
th {
padding: 7px 10px;
font-size: 13px;
@ -108,12 +108,12 @@
background-color: white;
}
tr:nth-child(2n) {
tr:nth-child(2n) {
background-color: #fbfbfb;
}
}
table.data {
table.data {
tr:first-child td {
border-top: none;
}
@ -211,7 +211,7 @@
}
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.table-control .toolbar {
.control-table .toolbar {
a {
&:before {
background-position: 0px -9px;
@ -233,7 +233,7 @@
* String editor
*/
.table-control {
.control-table {
td[data-column-type=string] {
input[type=text] {
width: 100%;
@ -250,7 +250,7 @@
* Checkbox editor
*/
.table-control {
.control-table {
td[data-column-type=checkbox] {
div[data-checkbox-element] {
width: 16px;
@ -292,7 +292,7 @@
* Dropdown editor
*/
.table-control {
.control-table {
td[data-column-type=dropdown] {
.user-select(none);
@ -334,7 +334,7 @@
}
html.cssanimations {
.table-control td[data-column-type=dropdown] {
.control-table td[data-column-type=dropdown] {
[data-view-container].loading:after {
background-image:url(../../../../assets/images/loading-indicator-transparent.svg);
background-size: 15px 15px;

View File

@ -1,6 +1,7 @@
<div
<div
id="<?= $this->getId() ?>"
data-control="table"
class="table-control"
class="control-table"
data-columns="<?= e(json_encode($columns)) ?>"
data-data="<?= e($data) ?>"
data-postback-handler-name="<?= e($postbackHandlerName) ?>"
@ -9,6 +10,6 @@
data-toolbar="<?= e($toolbar) ?>"
data-height="<?= e($height) ?>"
data-records-per-page="<?= e($recordsPerPage) ?>"
data-key-column="<?= e($recordsKeyColumn) ?>"
data-key-column="<?= e($recordsKeyFrom) ?>"
data-client-data-source-class="<?= e($clientDataSourceClass) ?>"
data-alias="<?= e($this->alias) ?>"></div>

View File

@ -39,7 +39,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'cms::lang.page.menu_label',
'icon' => 'icon-copy',
'url' => 'javascript:;',
'attributes' => ['data-menu-item'=>'pages'],
'attributes' => ['data-menu-item' => 'pages'],
'permissions' => ['cms.manage_pages'],
'counterLabel' => 'cms::lang.page.unsaved_label',
],
@ -47,7 +47,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'cms::lang.partial.menu_label',
'icon' => 'icon-tags',
'url' => 'javascript:;',
'attributes' => ['data-menu-item'=>'partials'],
'attributes' => ['data-menu-item' => 'partials'],
'permissions' => ['cms.manage_partials'],
'counterLabel' => 'cms::lang.partial.unsaved_label',
],
@ -55,7 +55,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'cms::lang.layout.menu_label',
'icon' => 'icon-th-large',
'url' => 'javascript:;',
'attributes' => ['data-menu-item'=>'layouts'],
'attributes' => ['data-menu-item' => 'layouts'],
'permissions' => ['cms.manage_layouts'],
'counterLabel' => 'cms::lang.layout.unsaved_label',
],
@ -63,7 +63,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'cms::lang.content.menu_label',
'icon' => 'icon-file-text-o',
'url' => 'javascript:;',
'attributes' => ['data-menu-item'=>'content'],
'attributes' => ['data-menu-item' => 'content'],
'permissions' => ['cms.manage_content'],
'counterLabel' => 'cms::lang.content.unsaved_label',
],
@ -71,7 +71,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'cms::lang.asset.menu_label',
'icon' => 'icon-picture-o',
'url' => 'javascript:;',
'attributes' => ['data-menu-item'=>'assets'],
'attributes' => ['data-menu-item' => 'assets'],
'permissions' => ['cms.manage_assets'],
'counterLabel' => 'cms::lang.asset.unsaved_label',
],
@ -79,7 +79,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'cms::lang.component.menu_label',
'icon' => 'icon-puzzle-piece',
'url' => 'javascript:;',
'attributes' => ['data-menu-item'=>'components'],
'attributes' => ['data-menu-item' => 'components'],
'permissions' => ['cms.manage_pages', 'cms.manage_layouts', 'cms.manage_partials']
]
]
@ -93,12 +93,30 @@ class ServiceProvider extends ModuleServiceProvider
*/
BackendAuth::registerCallback(function ($manager) {
$manager->registerPermissions('October.Cms', [
'cms.manage_content' => ['label' => 'cms::lang.permissions.manage_content', 'tab' => 'Cms'],
'cms.manage_assets' => ['label' => 'cms::lang.permissions.manage_assets', 'tab' => 'Cms'],
'cms.manage_pages' => ['label' => 'cms::lang.permissions.manage_pages', 'tab' => 'Cms'],
'cms.manage_layouts' => ['label' => 'cms::lang.permissions.manage_layouts', 'tab' => 'Cms'],
'cms.manage_partials' => ['label' => 'cms::lang.permissions.manage_partials', 'tab' => 'Cms'],
'cms.manage_themes' => ['label' => 'cms::lang.permissions.manage_themes', 'tab' => 'Cms']
'cms.manage_content' => [
'label' => 'cms::lang.permissions.manage_content',
'tab' => 'cms::lang.permissions.name'
],
'cms.manage_assets' => [
'label' => 'cms::lang.permissions.manage_assets',
'tab' => 'cms::lang.permissions.name'
],
'cms.manage_pages' => [
'label' => 'cms::lang.permissions.manage_pages',
'tab' => 'cms::lang.permissions.name'
],
'cms.manage_layouts' => [
'label' => 'cms::lang.permissions.manage_layouts',
'tab' => 'cms::lang.permissions.name'
],
'cms.manage_partials' => [
'label' => 'cms::lang.permissions.manage_partials',
'tab' => 'cms::lang.permissions.name'
],
'cms.manage_themes' => [
'label' => 'cms::lang.permissions.manage_themes',
'tab' => 'cms::lang.permissions.name'
]
]);
});

View File

@ -2,6 +2,7 @@
use Str;
use Lang;
use Event;
use Config;
use Cms\Classes\CodeBase;
use Cms\Classes\CmsException;
@ -167,6 +168,47 @@ abstract class ComponentBase extends Extendable
return $this->alias;
}
/**
* Renders a requested partial in context of this component,
* see Cms\Classes\Controller@renderPartial for usage.
*/
public function renderPartial()
{
$this->controller->setComponentContext($this);
return call_user_func_array([$this->controller, 'renderPartial'], func_get_args());
}
/**
* Executes the event cycle when running an AJAX handler.
* @return boolean Returns true if the handler was found. Returns false otherwise.
*/
public function runAjaxHandler($handler)
{
/*
* Extensibility
*/
if (
($event = $this->fireEvent('component.beforeRunAjaxHandler', [$handler], true)) ||
($event = Event::fire('cms.component.beforeRunAjaxHandler', [$this, $handler], true))
) {
return $event;
}
$result = $this->$handler();
/*
* Extensibility
*/
if (
($event = $this->fireEvent('component.runAjaxHandler', [$handler, $result], true)) ||
($event = Event::fire('cms.component.runAjaxHandler', [$this, $handler, $result], true))
) {
return $event;
}
return $result;
}
//
// External properties
//

View File

@ -84,11 +84,6 @@ class Controller extends BaseController
*/
protected $pageContents;
/**
* @var string Alias name of an executing component.
*/
protected $componentContext;
/**
* @var array A list of variables to pass to the page.
*/
@ -99,8 +94,19 @@ class Controller extends BaseController
*/
protected $statusCode = 200;
/**
* @var self Cache of self
*/
protected static $instance = null;
/**
* @var Cms\Classes\ComponentBase Object of the active component, used internally.
*/
protected $componentContext;
/**
* @var array Component partial stack, used internally.
*/
protected $partialComponentStack = [];
/**
@ -538,7 +544,7 @@ class Controller extends BaseController
if ($componentObj && method_exists($componentObj, $handlerName)) {
$this->componentContext = $componentObj;
$result = $componentObj->$handlerName();
$result = $componentObj->runAjaxHandler($handlerName);
return ($result) ?: true;
}
}
@ -561,7 +567,7 @@ class Controller extends BaseController
*/
if (($componentObj = $this->findComponentByHandler($handler)) !== null) {
$this->componentContext = $componentObj;
$result = $componentObj->$handler();
$result = $componentObj->runAjaxHandler($handler);
return ($result) ?: true;
}
}
@ -701,7 +707,7 @@ class Controller extends BaseController
}
elseif (($componentObj = $this->findComponentByPartial($partialName)) === null) {
if ($throwException) {
throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$name]));
throw new CmsException(Lang::get('cms::lang.partial.not_found', ['name'=>$partialName]));
}
else {
return false;
@ -1032,7 +1038,7 @@ class Controller extends BaseController
* Searches the layout and page components by an alias
* @return ComponentBase The component object, if found
*/
protected function findComponentByName($name)
public function findComponentByName($name)
{
if (isset($this->page->components[$name])) {
return $this->page->components[$name];
@ -1054,7 +1060,7 @@ class Controller extends BaseController
* Searches the layout and page components by an AJAX handler
* @return ComponentBase The component object, if found
*/
protected function findComponentByHandler($handler)
public function findComponentByHandler($handler)
{
foreach ($this->page->components as $component) {
if (method_exists($component, $handler)) {
@ -1075,7 +1081,7 @@ class Controller extends BaseController
* Searches the layout and page components by a partial file
* @return ComponentBase The component object, if found
*/
protected function findComponentByPartial($partial)
public function findComponentByPartial($partial)
{
foreach ($this->page->components as $component) {
$fileName = ComponentPartial::getFilePath($component, $partial);
@ -1102,6 +1108,16 @@ class Controller extends BaseController
return null;
}
/**
* Set the component context manually, used by Components when calling renderPartial.
* @param ComponentBase $component
* @return void
*/
public function setComponentContext(ComponentBase $component)
{
$this->componentContext = $component;
}
/**
* Sets component property values from partial parameters.
* The property values should be defined as {{ param }}.

View File

@ -3,23 +3,23 @@
return [
'cms_object' => [
'invalid_file' => 'Invalid file name: :name. File names can contain only alphanumeric symbols, underscores, dashes and dots. Some examples of correct file names: page.htm, page, subdirectory/page',
'invalid_property' => 'The property ":name" cannot be set',
'file_already_exists' => 'File ":name" already exists.',
'error_saving' => 'Error saving file ":name". Please check write permissions.',
'invalid_property' => "The property ':name' cannot be set",
'file_already_exists' => "File ':name' already exists.",
'error_saving' => "Error saving file ':name'. Please check write permissions.",
'error_creating_directory' => 'Error creating directory :name. Please check write permissions.',
'invalid_file_extension'=>'Invalid file extension: :invalid. Allowed extensions are: :allowed.',
'error_deleting' => 'Error deleting the template file ":name". Please check write permissions.',
'error_deleting' => "Error deleting the template file ':name'. Please check write permissions.",
'delete_success' => 'Templates were successfully deleted: :count.',
'file_name_required' => 'The File Name field is required.'
],
'theme' => [
'active' => [
'not_set' => "The active theme is not set.",
'not_found' => "The active theme is not found.",
'not_set' => 'The active theme is not set.',
'not_found' => 'The active theme is not found.'
],
'edit' => [
'not_set' => "The edit theme is not set.",
'not_found' => "The edit theme is not found.",
'not_set' => 'The edit theme is not set.',
'not_found' => 'The edit theme is not found.',
'not_match' => "The object you're trying to access doesn't belong to the theme being edited. Please reload the page."
],
'settings_menu' => 'Front-end theme',
@ -27,22 +27,22 @@ return [
'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace.',
'activate_button' => 'Activate',
'active_button' => 'Activate',
'customize_button' => 'Customize',
'customize_button' => 'Customize'
],
'maintenance' => [
'settings_menu' => 'Maintenance mode',
'settings_menu_description' => 'Configure the maintenance mode page and toggle the setting.',
'is_enabled' => 'Enable maintenance mode',
'is_enabled_comment' => 'When activated website visitors will see the page chosen below.',
'is_enabled_comment' => 'When activated website visitors will see the page chosen below.'
],
'page' => [
'not_found' => [
'label' => "Page not found",
'help' => "The requested page cannot be found.",
'label' => 'Page not found',
'help' => 'The requested page cannot be found.'
],
'custom_error' => [
'label' => "Page error",
'help' => "We're sorry, but something went wrong and the page cannot be displayed.",
'label' => 'Page error',
'help' => "We're sorry, but something went wrong and the page cannot be displayed."
],
'menu_label' => 'Pages',
'unsaved_label' => 'Unsaved page(s)',
@ -63,7 +63,7 @@ return [
'delete_confirm_single' => 'Do you really want delete this layout?'
],
'partial' => [
'invalid_name' => "Invalid partial name: :name.",
'invalid_name' => 'Invalid partial name: :name.',
'not_found' => "The partial ':name' is not found.",
'menu_label' => 'Partials',
'unsaved_label' => 'Unsaved partial(s)',
@ -82,11 +82,11 @@ return [
'new' => 'New content file'
],
'ajax_handler' => [
'invalid_name' => "Invalid AJAX handler name: :name.",
'not_found' => "AJAX handler ':name' was not found.",
'invalid_name' => 'Invalid AJAX handler name: :name.',
'not_found' => "AJAX handler ':name' was not found."
],
'cms' => [
'menu_label' => "CMS"
'menu_label' => 'CMS'
],
'sidebar' => [
'add' => 'Add',
@ -113,7 +113,7 @@ return [
'exit_fullscreen' => 'Exit fullscreen mode'
],
'asset' => [
'menu_label' => "Assets",
'menu_label' => 'Assets',
'unsaved_label' => 'Unsaved asset(s)',
'drop_down_add_title' => 'Add...',
'drop_down_operation_title' => 'Action...',
@ -141,7 +141,7 @@ return [
'too_large' => 'The uploaded file is too large. The maximum allowed file size is :max_size',
'type_not_allowed' => 'Only the following file types are allowed: :allowed_types',
'file_not_valid' => 'File is not valid',
'error_uploading_file' => 'Error uploading file ":name": :error',
'error_uploading_file' => "Error uploading file ':name': :error",
'move_please_select' => 'please select',
'move_destination' => 'Destination directory',
'move_popup_title' => 'Move assets',
@ -155,23 +155,24 @@ return [
'path' => 'Path'
],
'component' => [
'menu_label' => "Components",
'unnamed' => "Unnamed",
'no_description' => "No description provided",
'alias' => "Alias",
'alias_description' => "A unique name given to this component when using it in the page or layout code.",
'validation_message' => "Component aliases are required and can contain only Latin symbols, digits, and underscores. The aliases should start with a Latin symbol.",
'invalid_request' => "The template cannot be saved because of invalid component data.",
'menu_label' => 'Components',
'unnamed' => 'Unnamed',
'no_description' => 'No description provided',
'alias' => 'Alias',
'alias_description' => 'A unique name given to this component when using it in the page or layout code.',
'validation_message' => 'Component aliases are required and can contain only Latin symbols, digits, and underscores. The aliases should start with a Latin symbol.',
'invalid_request' => 'The template cannot be saved because of invalid component data.',
'no_records' => 'No components found',
'not_found' => "The component ':name' is not found.",
'method_not_found' => "The component ':name' does not contain a method ':method'.",
'method_not_found' => "The component ':name' does not contain a method ':method'."
],
'template' => [
'invalid_type' => "Unknown template type.",
'not_found' => "The requested template was not found.",
'saved'=> "The template has been successfully saved."
'invalid_type' => 'Unknown template type.',
'not_found' => 'The requested template was not found.',
'saved'=> 'The template has been successfully saved.'
],
'permissions' => [
'name' => 'Cms',
'manage_content' => 'Manage content',
'manage_assets' => 'Manage assets',
'manage_pages' => 'Manage pages',

View File

@ -33,7 +33,7 @@ return [
'settings_menu' => 'Karbantartás mód',
'settings_menu_description' => 'A karbantartás mód lap konfigurálása, és a beűllítás ki-/bekapcsolása.',
'is_enabled' => 'A karbantartás mód engedélyezése',
'is_enabled_comment' => 'Aktiválása esetén a webhely látogatói az alább bejelölt lapot fogják látni.',
'is_enabled_comment' => 'Aktiválása esetén a webhely látogatói az alább kiválasztott lapot fogják látni.',
],
'page' => [
'not_found' => [

View File

@ -204,15 +204,15 @@ class ServiceProvider extends ModuleServiceProvider
$manager->registerPermissions('October.System', [
'system.manage_settings' => [
'label' => 'system::lang.permissions.manage_system_settings',
'tab' => 'System'
'tab' => 'system::lang.permissions.name'
],
'system.manage_updates' => [
'label' => 'system::lang.permissions.manage_software_updates',
'tab' => 'System'
'tab' => 'system::lang.permissions.name'
],
'system.manage_mail_templates' => [
'label' => 'system::lang.permissions.manage_mail_templates',
'tab' => 'System'
'tab' => 'system::lang.permissions.name'
],
]);
});

View File

@ -366,34 +366,10 @@ if (window.jQuery === undefined)
$(this).request()
})
document.addEventListener('click', function nativeClickListener(ev){
// Faster native click listener. This implementation doesn't use
// jQuery until it's really necessary.
var target = ev.target ? ev.target : ev.srcElement
if (target.getAttribute('data-request') == null)
return
var tagName = target.tagName
if (tagName == 'A' || tagName == 'BUTTON') {
$(target).request()
return false;
}
if (tagName == 'INPUT') {
var type = target.getAttribute('type').toLowerCase()
if (type == 'button' || type == 'submit') {
$(target).request()
return false;
}
}
});
// $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function(){
// $(this).request()
// return false
// })
$(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function(){
$(this).request()
return false
})
$(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function(e){
if (e.keyCode == 13) {

View File

@ -23,10 +23,10 @@ return [
'tr' => 'Turkish',
],
'directory' => [
'create_fail' => "Cannot create directory: :name",
'create_fail' => 'Cannot create directory: :name',
],
'file' => [
'create_fail' => "Cannot create file: :name",
'create_fail' => 'Cannot create file: :name',
],
'combiner' => [
'not_found' => "The combiner file ':name' is not found.",
@ -66,13 +66,13 @@ return [
'disabled_help' => 'Plugins that are disabled are ignored by the application.',
'selected_amount' => 'Plugins selected: :amount',
'remove_confirm' => 'Are you sure?',
'remove_success' => "Successfully removed those plugins from the system.",
'remove_success' => 'Successfully removed those plugins from the system.',
'refresh_confirm' => 'Are you sure?',
'refresh_success' => "Successfully refreshed those plugins in the system.",
'refresh_success' => 'Successfully refreshed those plugins in the system.',
'disable_confirm' => 'Are you sure?',
'disable_success' => "Successfully disabled those plugins.",
'enable_success' => "Successfully enabled those plugins.",
'unknown_plugin' => "Plugin has been removed from the file system.",
'disable_success' => 'Successfully disabled those plugins.',
'enable_success' => 'Successfully enabled those plugins.',
'unknown_plugin' => 'Plugin has been removed from the file system.',
],
'project' => [
'name' => 'Project',
@ -114,6 +114,7 @@ return [
'smtp_password' => 'Password',
'smtp_port' => 'SMTP Port',
'smtp_ssl' => 'SSL connection required',
'sendmail' => 'Sendmail',
'sendmail_path' => 'Sendmail Path',
'sendmail_path_comment' => 'Please specify the path of the sendmail program.',
'mailgun' => 'Mailgun',
@ -242,6 +243,7 @@ return [
'status_code' => 'Status',
],
'permissions' => [
'name' => 'System',
'manage_system_settings' => 'Manage system settings',
'manage_software_updates' => 'Manage software updates',
'manage_mail_templates' => 'Manage mail templates',

View File

@ -97,6 +97,7 @@ return [
'search' => 'Keresés'
],
'mail' => [
'log_file' => 'Naplófájl',
'menu_label' => 'Levelezés konfigurálása',
'menu_description' => 'Az e-mail küldés konfigurációjának kezelése.',
'general' => 'Általános',
@ -113,7 +114,6 @@ return [
'smtp_password' => 'Jelszó',
'smtp_port' => 'SMTP port',
'smtp_ssl' => 'SSL-kapcsolat szükséges',
'sendmail' => 'Sendmail',
'sendmail_path' => 'Sendmail elérési útja',
'sendmail_path_comment' => 'Adja meg a Sendmail program elérési útját.',
'mailgun' => 'Mailgun',
@ -191,7 +191,7 @@ return [
],
'none' => [
'label' => 'Nincsenek frissítések',
'help' => 'Nem találhatók új frissítések.',
'help' => 'Nem található új frissítés.',
],
],
'server' => [