--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Base page for Afform admin
+ */
+class CRM_AfformAdmin_Page_Base extends CRM_Core_Page {
+
+ public function run() {
+ $breadCrumb = [
+ 'title' => ts('Forms'),
+ 'url' => CRM_Utils_System::url('civicrm/admin/afform', NULL, FALSE, '/'),
+ ];
+ CRM_Utils_System::appendBreadCrumb([$breadCrumb]);
+
+ // Load angular module
+ $loader = new Civi\Angular\AngularLoader();
+ $loader->setPageName('civicrm/admin/afform');
+ $loader->useApp();
+ $loader->load();
+ parent::run();
+ }
+
+}
class CRM_AfformAdmin_Utils {
+ /**
+ * @return array
+ */
+ public static function getAdminSettings() {
+ return [
+ 'afform_type' => \Civi\Api4\OptionValue::get(FALSE)
+ ->addSelect('name', 'label', 'icon')
+ ->addWhere('is_active', '=', TRUE)
+ ->addWhere('option_group_id:name', '=', 'afform_type')
+ ->addOrderBy('weight', 'ASC')
+ ->execute(),
+ ];
+ }
+
/**
* Loads metadata for the gui editor.
*
* FIXME: This is a prototype and should get broken out into separate callbacks with hooks, events, etc.
+ * @return array
*/
- public static function getAngularSettings() {
+ public static function getGuiSettings() {
$getFieldParams = [
'checkPermissions' => FALSE,
'includeCustom' => TRUE,
+++ /dev/null
-<div ng-if="!routeParams.name">
- <af-admin-list></af-admin-list>
-</div>
-
-<div ng-if="routeParams.name">
- <af-gui-editor name="routeParams.name"></af-gui-editor>
-</div>
+++ /dev/null
-{
- "title": "Afform Administration",
- "server_route": "civicrm/admin/afform",
- "permission": "administer CiviCRM",
- "requires": [
- "afGuiEditor"
- ]
-}
--- /dev/null
+<?php
+// Angular module for afform gui editor
+return [
+ 'js' => [
+ 'ang/afAdmin.js',
+ 'ang/afAdmin/*.js',
+ 'ang/afAdmin/*/*.js',
+ ],
+ 'css' => [],
+ 'partials' => ['ang/afAdmin'],
+ 'requires' => ['api4', 'afGuiEditor', 'crmRouteBinder'],
+ 'settingsFactory' => ['CRM_AfformAdmin_Utils', 'getAdminSettings'],
+ 'basePages' => ['civicrm/admin/afform'],
+ 'bundles' => ['bootstrap3'],
+];
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+ angular.module('afAdmin', CRM.angRequires('afAdmin'))
+
+ .config(function($routeProvider) {
+ $routeProvider.when('/', {
+ controller: 'afAdminList',
+ reloadOnSearch: false,
+ templateUrl: '~/afAdmin/afAdminList.html',
+ resolve: {
+ // Load data for lists
+ afforms: function(crmApi4) {
+ return crmApi4('Afform', 'get', {
+ select: ['name', 'title', 'type', 'is_public', 'server_route', 'has_local', 'has_base'],
+ orderBy: {title: 'ASC'}
+ });
+ }
+ }
+ });
+ $routeProvider.when('/create/:type', {
+ controller: 'afAdminGui',
+ template: '<af-gui-editor type="$ctrl.type"></af-gui-editor>',
+ });
+ $routeProvider.when('/edit/:name', {
+ controller: 'afAdminGui',
+ template: '<af-gui-editor name="$ctrl.name"></af-gui-editor>',
+ });
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('afAdmin').controller('afAdminGui', function($scope, $routeParams) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = $scope.$ctrl = this;
+
+ // Edit mode
+ this.name = $routeParams.name;
+ // Create mode
+ this.type = $routeParams.type;
+
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+(function(angular, $, _) {
+ "use strict";
+
+ angular.module('afAdmin').controller('afAdminList', function($scope, afforms, crmApi4, crmStatus) {
+ var ts = $scope.ts = CRM.ts(),
+ ctrl = $scope.$ctrl = this;
+
+ $scope.crmUrl = CRM.url;
+
+ this.tabs = CRM.afAdmin.afform_type;
+
+ this.afforms = _.transform(afforms, function(afforms, afform) {
+ var type = afform.type || 'system';
+ afforms[type] = afforms[type] || [];
+ afforms[type].push(afform);
+ }, {});
+
+ $scope.$bindToRoute({
+ expr: '$ctrl.tab',
+ param: 'tab',
+ format: 'raw',
+ default: ctrl.tabs[0].name
+ });
+
+ this.revert = function(afform) {
+ var index = _.findIndex(ctrl.afforms[ctrl.tab], {name: afform.name});
+ if (index > -1) {
+ var apiOps = [['Afform', 'revert', {where: [['name', '=', afform.name]]}]];
+ if (afform.has_base) {
+ apiOps.push(['Afform', 'get', {
+ where: [['name', '=', afform.name]],
+ select: ['name', 'title', 'type', 'is_public', 'server_route', 'has_local', 'has_base']
+ }, 0]);
+ }
+ var apiCall = crmStatus(
+ afform.has_base ? {start: ts('Reverting...')} : {start: ts('Deleting...'), success: ts('Deleted')},
+ crmApi4(apiOps)
+ );
+ if (afform.has_base) {
+ afform.has_local = false;
+ apiCall.then(function(result) {
+ ctrl.afforms[ctrl.tab][index] = result[1];
+ });
+ } else {
+ ctrl.afforms[ctrl.tab].splice(index, 1);
+ }
+ }
+ };
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+<div id="bootstrap-theme" class="afadmin-list">
+ <h1 crm-page-title>{{:: ts('Configurable Forms') }}</h1>
+
+ <div class="form-inline text-right">
+ <a class="btn btn-primary" href="#/create/form">
+ <i class="crm-i fa-plus"></i>
+ {{:: ts('New Form') }}
+ </a>
+ </div>
+
+ <ul class="nav nav-tabs">
+ <li role="presentation" ng-repeat="tab in $ctrl.tabs" ng-class="{active: tab.name === $ctrl.tab}">
+ <a href ng-click="$ctrl.tab = tab.name"><i class="crm-i {{ tab.icon }}"></i> {{:: tab.label }}</a>
+ </li>
+ </ul>
+
+ <div class="form-inline">
+ <input class="form-control" type="search" ng-model="$ctrl.search" placeholder="{{:: ts('Filter') }}">
+ </div>
+
+ <table>
+ <thead>
+ <tr>
+ <th>{{:: ts('Title') }}</th>
+ <th>{{:: ts('Name') }}</th>
+ <th>{{:: ts('Server Route') }}</th>
+ <th>{{:: ts('Frontend?') }}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="afform in $ctrl.afforms[$ctrl.tab] | filter:$ctrl.search">
+ <td>{{afform.title}}</td>
+ <td>
+ <code>{{afform.name}}</code>
+ </td>
+ <td>
+ <a ng-if="afform.server_route" ng-href="{{ crmUrl(afform.server_route) }}" target="_blank">
+ <code>{{afform.server_route}}</code>
+ </a>
+ </td>
+ <td>{{afform.is_public ? ts('Frontend') : ts('Backend')}}</td>
+ <td>
+ <a href="#/edit/{{ afform.name }}" class="btn btn-xs btn-primary">{{ ts('Edit') }}</a>
+ <a href ng-if="afform.has_local" class="btn btn-xs btn-danger" crm-confirm="{type: afform.has_base ? 'revert' : 'delete', obj: afform}" on-yes="$ctrl.revert(afform)">
+ {{ afform.has_base ? ts('Revert') : ts('Delete') }}
+ </a>
+ </td>
+ </tr>
+ <tr ng-if="!$ctrl.afforms[$ctrl.tab] || $ctrl.afforms[$ctrl.tab].length === 0">
+ <td colspan="9">
+ <p class="messages status no-popup text-center">
+ {{:: ts('None Found')}}
+ </p>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+++ /dev/null
-<a href="#!/?name=0" class="btn btn-default">
- <i class="crm-i fa-plus"></i> {{:: ts('New Form') }}
-</a>
-<div
- af-api4="['Afform', 'get', {select: ['name','title','is_public','server_route','has_local','has_base'], orderBy: {name:'ASC'}}]"
- af-api4-ctrl="listCtrl">
-
- <div ng-if="apiData.result.length == 0">
- {{:: ts('None found.') }}
- </div>
-
- <table>
- <thead>
- <tr>
- <th>{{:: ts('Name') }}</th>
- <th>{{:: ts('Title') }}</th>
- <th>{{:: ts('Server Route') }}</th>
- <th>{{:: ts('Frontend?') }}</th>
- <th></th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="availForm in listCtrl.result">
- <td>
- <a ng-href="#!/?name={{availForm.name}}">{{availForm.name}}</a>
- </td>
- <td>{{availForm.title}}</td>
- <td>
- <a ng-if="availForm.server_route" ng-href="{{crmUrl(availForm.server_route)}}" target="_blank">
- <code>{{availForm.server_route}}</code>
- </a>
- </td>
- <td>{{availForm.is_public ? ts('Frontend') : ts('Backend')}}</td>
- <td>
- <!--<a ng-click="crmStatus({start: ts('Reverting...'), success: ts('Reverted')}, crmApi4('Afform', 'revert', {where: [['name', '=', availForm.name]]}))">{{ts('Revert')}}</a>-->
- <a af-api4-action="['Afform', 'revert', {where: [['name','=', availForm.name]]}]"
- af-api4-start-msg="ts('Reverting...')"
- af-api4-success-msg="ts('Reverted')"
- af-api4-success="listCtrl.refresh()"
- class="btn btn-xs btn-default"
- ng-if="availForm.has_local && availForm.has_base"
- >{{:: ts('Revert') }}</a>
- <a af-api4-action="['Afform', 'revert', {where: [['name','=', availForm.name]]}]"
- af-api4-start-msg="ts('Deleting...')"
- af-api4-success-msg="ts('Deleted')"
- af-api4-success="listCtrl.refresh()"
- class="btn btn-xs btn-default"
- ng-if="availForm.has_local && !availForm.has_base"
- >{{:: ts('Delete') }}</a>
- </td>
- </tr>
- </tbody>
- </table>
-
-</div>
'css' => ['ang/afGuiEditor.css'],
'partials' => ['ang/afGuiEditor'],
'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4', 'crmMonaco', 'ui.sortable'],
- 'settingsFactory' => ['CRM_AfformAdmin_Utils', 'getAngularSettings'],
+ 'settingsFactory' => ['CRM_AfformAdmin_Utils', 'getGuiSettings'],
'basePages' => [],
'exports' => [
'af-gui-editor' => 'E',
angular.module('afGuiEditor').component('afGuiEditor', {
templateUrl: '~/afGuiEditor/afGuiEditor.html',
bindings: {
+ type: '<',
name: '<'
},
controllerAs: 'editor',
var newForm = {
title: '',
permission: 'access CiviCRM',
+ type: 'form',
layout: [{
'#tag': 'af-form',
ctrl: 'afform',
$scope.afform = _.findWhere(afforms, {name: editor.name});
if (!$scope.afform) {
$scope.afform = _.cloneDeep(newForm);
- if (editor.name != '0') {
+ if (editor.name) {
alert('Error: unknown form "' + editor.name + '"');
}
}
editor.layout = afGui.findRecursive($scope.afform.layout, {'#tag': 'af-form'})[0];
$scope.entities = afGui.findRecursive(editor.layout['#children'], {'#tag': 'af-entity'}, 'name');
- if (editor.name == '0') {
+ if (!editor.name) {
editor.addEntity('Individual');
editor.layout['#children'].push(afGui.meta.elements.submit.element);
}
// Set changesSaved to true on initial load, false thereafter whenever changes are made to the model
- $scope.changesSaved = editor.name == '0' ? false : 1;
+ $scope.changesSaved = !editor.name ? false : 1;
$scope.$watch('afform', function () {
$scope.changesSaved = $scope.changesSaved === 1;
}, true);
.then(function (data) {
$scope.saving = false;
$scope.afform.name = data[0].name;
- // FIXME: This causes an unnecessary reload when saving a new form
- $location.search('name', data[0].name);
+ $location.url('/edit/' + data[0].name);
});
};
-<div id="afGuiEditor" class="crm-flex-box">
- <div id="afGuiEditor-palette" class="crm-flex-3" ng-include="'~/afGuiEditor/afGuiEditorPalette.html'"></div>
- <div id="afGuiEditor-canvas" class="crm-flex-5" ng-include="'~/afGuiEditor/afGuiEditorCanvas.html'"></div>
+<div id="bootstrap-theme">
+ <div id="afGuiEditor" class="crm-flex-box">
+ <div id="afGuiEditor-palette" class="crm-flex-3" ng-include="'~/afGuiEditor/afGuiEditorPalette.html'"></div>
+ <div id="afGuiEditor-canvas" class="crm-flex-5" ng-include="'~/afGuiEditor/afGuiEditorCanvas.html'"></div>
+ </div>
</div>
--- /dev/null
+<?xml version="1.0"?>
+<menu>
+ <item>
+ <path>civicrm/admin/afform</path>
+ <page_callback>CRM_AfformAdmin_Page_Base</page_callback>
+ <access_arguments>administer CiviCRM</access_arguments>
+ </item>
+</menu>
[
'name' => 'name',
],
+ [
+ 'name' => 'type',
+ ],
[
'name' => 'requires',
],
{
"title": "Household Name (default)",
+ "type": "block",
"block": "Household"
}
{
"title": "Individual Name (default)",
+ "type": "block",
"block": "Individual"
}
{
"title": "Organization Name (default)",
+ "type": "block",
"block": "Organization"
}
{
"title": "Address Block (default)",
+ "type": "block",
"block": "Contact",
"join": "Address",
"repeat": true
{
"title": "Email (default)",
+ "type": "block",
"block": "Contact",
"join": "Email",
"repeat": true
{
"title": "IM (default)",
+ "type": "block",
"block": "Contact",
"join": "IM",
"repeat": true
{
"title": "Phone (default)",
+ "type": "block",
"block": "Contact",
"join": "Phone",
"repeat": true
{
"title": "Website (default)",
+ "type": "block",
"block": "Contact",
"join": "Website",
"repeat": true