diff options
Diffstat (limited to 'main/app/sprinkles/FormGenerator')
35 files changed, 2138 insertions, 0 deletions
diff --git a/main/app/sprinkles/FormGenerator/.gitignore b/main/app/sprinkles/FormGenerator/.gitignore new file mode 100755 index 0000000..3afbe61 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/.gitignore @@ -0,0 +1,3 @@ + +.DS_Store +assets/vendor/
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/CHANGELOG.md b/main/app/sprinkles/FormGenerator/CHANGELOG.md new file mode 100755 index 0000000..0975d22 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/CHANGELOG.md @@ -0,0 +1,78 @@ +# Change Log + +## 2.2.10 +- Added support for Repository + +## 2.2.9 +- Fix issue when setting data that is a collection +- `formSuccess` and `confirmSuccess` events now include the request data as a second argument + +## 2.2.8 +- Fix icon in textarea macro + +## 2.2.7 +- Added `modal-large` template file. + +## 2.2.6 +- Fix issue with `binary` checkbox tests. +- Fix Text input style when no icon is added + +## 2.2.5 +- Added `binary` option for checkbox to disable UF binary checkbox system (bool; default true). + +## 2.2.4 +- Add necessary HTML to disable submit and cancel button in modal form. + +## 2.2.3 +- New `$form->setOptions` function to set options of a select element. Shortcut for using `setInputArgument` and `setValue`. + +## 2.2.2 +- Fix issue with error alert no displaying on confirmation dialog + +## 2.2.1 +- Initialize ufAlert if not already done +- Autofocus first form field when modal is displayed + +## 2.2.0 +- Refactored the javascript plugin +- Added new events +- Added new `redirectAfterSuccess` boolean option + +## 2.1.2 +- Fix warning with select macro + +## 2.1.1 +- Fix issue with the select macro +- Renamed macro templates with the `*.html.twig` extension + +## 2.1.0 +- Completely refactored how form fields are parsed, including how default value are defined. Each input type now defines it's own class for defining default values and transforming some input. +- Twig templates updated to reflect the new parser. +- Twig macros changed from `*.generate(name, value)` to `*.generate(input)`. +- **`Bool` type changed to `checkbox`**. +- Removed the `number` Twig template (Will use the text input one). +- Added unit tests. +- Support for any attributes in the schema. For example, if you need to add a data attribute to a field, your schema would be: +``` +"myField" : { + "form" : { + "type" : "text", + "label" : "My Field", + "icon" : "fa-pencil", + "data-something" : "blah" + } +} +``` + +## 2.0.0 +- Updated for UserFrosting v4.1.x + +The custom `RequestSchema` have been removed. Instead of building the form directly on the schema using `$schema->initForm()`, you now create a new Form using `$form = new Form($schema)` and go on from there. Handling complex schema can now be done using the new loader system from UF 4.1. + +`$schema->generateForm();` has also been changed to `$form->generate();`. + +## 1.0.1 +- Bug fixes + +## 1.0.0 +- Initial release diff --git a/main/app/sprinkles/FormGenerator/LICENSE b/main/app/sprinkles/FormGenerator/LICENSE new file mode 100755 index 0000000..09386f7 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Louis Charette + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/main/app/sprinkles/FormGenerator/README.md b/main/app/sprinkles/FormGenerator/README.md new file mode 100755 index 0000000..2924ede --- /dev/null +++ b/main/app/sprinkles/FormGenerator/README.md @@ -0,0 +1,352 @@ +# Form Generator Sprinkle for [UserFrosting 4](https://www.userfrosting.com) +This Sprinkle provides helper classes, Twig template and JavaScript plugins to generate HTML forms, modals and confirm modal bases on UserFrosting/[validation schemas](https://learn.userfrosting.com/routes-and-controllers/client-input/validation). + +> This version only works with UserFrosting 4.1.x ! + +# Help and Contributing + +If you need help using this sprinkle or found any bug, feels free to open an issue or submit a pull request. You can also find me on the [UserFrosting Chat](https://chat.userfrosting.com/) most of the time for direct support. + +<a href='https://ko-fi.com/A7052ICP' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi4.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a> + +# Installation +Edit UserFrosting `app/sprinkles.json` file and add the following to the `require` list : `"lcharette/uf_formgenerator": "^2.0.0"`. Also add `FormGenerator` to the `base` list. For example: + +``` +{ + "require": { + "lcharette/uf_formgenerator": "^2.0.0" + }, + "base": [ + "core", + "account", + "admin", + "FormGenerator" + ] +} +``` + +Run `composer update` then `php bakery bake` to install the sprinkle. + +# Features and usage +Before starting with _FormGenerator_, you should read the main UserFrosting guide to familiarize yourself with _validation schemas_: (https://learn.userfrosting.com/routes-and-controllers/client-input/validation). + +## Form generation +### Defining the fields in the schema +This sprinkle uses the `schemas` used by UserFrosting to validate form data to build form. To achieve this, a new `form` key is simply added to the fields found in a `schema` file. + +For example, here's a simple `schema` used to validate a form used to create a `project`. The form will contain a `name`, `description` and `status` fields. + +``` +{ + "name" : { + "validators" : { + "length" : { + "min" : 1, + "max" : 100 + }, + "required" : { + "message" : "PROJECT.VALIDATE.REQUIRED_NAME" + } + } + }, + "description" : { + "validators" : {} + }, + "status" : { + "validators" : { + "member_of" : { + "values" : [ + "0", "1" + ] + }, + "required" : { + "message" : "PROJECT.VALIDATE.STATUS" + } + } + } +} +``` +> Note: FormGenerator works with json and YAML schemas. + +At this point, with typical UserFrosting setup, you would be going into your controller and Twig files to manually create your HTML form. This can be easy if you have a two or three fields, but can be a pain with a dozen fields and more. This is where FormGenerator steps in with the use of a new `form` attribute. Let's add it to our `project` form : + +``` +{ + "name" : { + "validators" : { + "length" : { + "min" : 1, + "max" : 100 + }, + "required" : { + "message" : "VALIDATE.REQUIRED_NAME" + } + }, + "form" : { + "type" : "text", + "label" : "NAME", + "icon" : "fa-flag", + "placeholder" : "NAME" + } + }, + "description" : { + "validators" : {}, + "form" : { + "type" : "textarea", + "label" : "DESCRIPTION", + "icon" : "fa-pencil", + "placeholder" : "DESCRIPTION", + "rows" : 5 + } + }, + "status" : { + "validators" : { + "member_of" : { + "values" : [ + "0", "1" + ] + }, + "required" : { + "message" : "VALIDATE.STATUS" + } + }, + "form" : { + "type" : "select", + "label" : "STATUS", + "options" : { + "0" : "Active", + "1" : "Disabled" + } + } + } +} +``` + +Let's look closer at the `name` field : + +``` +"form" : { + "type" : "text", + "label" : "PROJECT.NAME", + "icon" : "fa-flag", + "placeholder" : "PROJECT.NAME" +} +``` + +Here you can see that we define the `type`, `label`, `icon` and `placeholder` value for this `name` field. You can define any standard [form attributes](http://www.w3schools.com/html/html_form_attributes.asp), plus the `icon`, `label` and `default` attributes. `data-*` attributes can also be defined in your schema if you need them. For the `select` element, a special `options` attribute containing an array of `key : value` can be used to define the dropdown options. The select options (as any other attributes) can also be set in PHP (see further below). + +And of course, the values of the `label` and `placeholder` attributes can be defined using _translation keys_. + +Currently, FormGenerator supports the following form elements : +- text (and any input supported by the HTML5 standard : number, tel, password, etc.) +- textarea +- select +- checkbox +- hidden +- alert (Display a static alert box in the form) + +### The controller part +Once your fields defined in the `schema` json or yaml file, you need to load that schema in your controller. + +First thing to do is add FormGenerator's `Form` class to your "use" list : +`use UserFrosting\Sprinkle\FormGenerator\Form;` + +Next, where you load the schema and setup the `validator`, you simply add the new Form creation: +``` +// Load validator rules +$schema = new RequestSchema("schema://project.json"); +$validator = new JqueryValidationAdapter($schema, $this->ci->translator); + +// Create the form +$form = new Form($schema, $project); +``` + +In this example, `$project` can contain the default (or current value) of the fields. A data collection fetched from the database with eloquent can also be passed directly. That second argument can also be omited to create an empty form. + +Last thing to do is send the fields to Twig. In the list of retuned variables to the template, add the `fields` variable: +``` +$this->ci->view->render($response, "pages/myPage.html.twig", [ + "fields" => $form->generate(), + "validators" => $validator->rules('json', true) +]); + +``` + +### The Twig template part + +Now it's time to display the form in `myPage.html.twig` ! + +``` +<form name="MyForm" method="post" action="/Path/to/Controller/Handling/Form"> + {% include "forms/csrf.html.twig" %} + <div id="form-alerts"></div> + <div class="row"> + <div class="col-sm-8"> + {% include 'FormGenerator/FormGenerator.html.twig' %} + </div> + </div> + <div class="row"> + <button type="submit" class="btn btn-block btn-lg btn-success">Submit</button> + </div> +</form> +``` + +That's it! No need to list all the field manually. The ones defined in the `fields` variable will be displayed by `FormGenerator/FormGenerator.html.twig`. Note that this will only load the fields, not the form itself. The `<form>` tag and `submit` button needs to be added manually. + +## Modal form +What if you want to show a form in a modal window? Well, FormGenerator makes it even easier! It's basically three steps: +1. Setup your form schema (described above) +2. Setup the form in your controller +3. Call the modal from your template + +### Setup the form in your controller +With your schema in hand, it's time to create a controller and route to load your modal. The controller code will be like any basic UserFrosting modal, plus the `$form` part above and one changes in the `render` part. For example : + +``` +$this->ci->view->render($response, "FormGenerator/modal.html.twig", [ + "box_id" => $get['box_id'], + "box_title" => "PROJECT.CREATE", + "submit_button" => "CREATE", + "form_action" => '/project/create', + "fields" => $form->generate(), + "validators" => $validator->rules('json', true) +]); +``` + +As you can see, instead of rendering your own Twig template, you simply have to specify FormGenerator's modal template. This template requires the following variables: +1. `box_id`: This should always be `$get['box_id']`. This is used by the JavaScript code to actually display the modal. +2. `box_title`: The title of the modal. +3. `submit_button`: The label of the submit button. Optional. Default to `SUBMIT` (localized). +4. `form_action`: The route where the form will be sent +5. `fields`: The fields. Should always be `$form->generate()` +6. `validators`: Client side validators + +### Call the modal from your template +So at this point you have a controller that displays the modal at a `/path/to/controller` route. Time to show that modal. Again, two steps: + +First, define a link or a button that will call the modal when clicked. For example : +``` +<button class="btn btn-success js-displayForm" data-toggle="modal" data-formUrl="/path/to/controller">Create</button> +``` + +The important part here is the `data-formUrl` attribute. This is the route that will load your form. `js-displayForm` is used here to bind the button to the action. + +Second, load the FormGenerator JavaScript widget. Add this to your Twig file: +``` +{% block scripts_page %} + {{ assets.js('js/FormGenerator') | raw }} +{% endblock %} +``` + +By default, the `formGenerator` plugin will bind a **form modal** to every element with the `js-displayForm` class. + +## Modal confirmation + +One side features of FormGenerator is the ability to add a confirmation modal to your pages with simple HTML5 attributes. The process is similar to adding a modal form, without the need to create any controller or route. + +Let's look at a delete button / confirmation for our `project` : +``` +<a href="#" class="btn btn-danger js-displayConfirm" + data-confirm-title="Delete project ?" + data-confirm-message="Are you sure you want to delete this project?" + data-confirm-button="Yes, delete project" + data-post-url="/porject/delete" +data-toggle="modal"><i class="fa fa-trash-o"></i> Delete</a> +``` +(Note that content of data attributes can be translation keys) + +If not aready done, make sure the FormGenerator assets are included in your template. +``` +{% block scripts_page %} + {{ assets.js('js/FormGenerator') | raw }} +{% endblock %} +``` + +By default, the `formGenerator` plugin will bind a **confirmation modal** to every element with the `js-displayConfirm` class. + +## Advance usage + +### Defining attributes in PHP + +#### setInputArgument + +Form field input attributes can also be added or edited from PHP. This can be usefull when dynamically defining a Select input options. To do this, simply use the `setInputArgument($inputName, $property, $data)` method. For example, to add a list to a `clients` select : + +``` +// Get clients from the db model +$clients = Clients::all(); + +$form = new Form($schema); +$form->setInputArgument('clients', 'options', $clients); +``` + +#### setData + +If you want to set the form values once the form instance is created, you can use the `setData($data)` method: + +``` +$form = new Form($schema); +$form->setData($clients, $project); +``` + +#### setValue + +Similar to the `setData` method, you can set a specific input value using the `setValue($inputName, $value)` method : + +``` +$currentClient = ... + +$form = new Form($schema, $project); +$form->setValue('clients', $currentClient); +``` + +#### setFormNamespace + +When dealing with multiple form on the same page or a dynamic number of input (you can use the new `Loader` system in 4.1 to build dynamic schemas!), it can be useful to wrap form elements in an array using the `setFormNamespace($namespace)` method. This can also your the input names [to contains dot syntaxt](http://stackoverflow.com/a/20365198/445757). + +For example, `$form->setFormNamespace("data");` will transform all the input names from `<input name="foo" [...] />` to `<input name="data[foo]" [...] />`. + +### Javascript Plugin + +By default, the `formGenerator` plugin will bind a **form modal** to every element with the `js-displayForm` class and will bind a **confirmation modal** to every element with the `js-displayConfirm` class. You can + +#### Options +The following options are available: + +Just pass an object with those + - `mainAlertElement` (jQuery element). The element on the main page where the main alerts will be displayed. Default to `$('#alerts-page')`. + - `redirectAfterSuccess` (bool). If set to true, the page will reload when the form submission or confirmation is succesful. Default to `true`. + +Example: +``` +$(".project-edit-button").formGenerator({redirectAfterSuccess: false}); +``` + +#### Events +You can listen for some events returned by FormGenerator. Those events can be used to apply some actions when the modal is displayed or the form is successfully sent. For example, this is can be used with `redirectAfterSuccess` on `false` to refresh the data on the page when the form is submitted successfully. + +- `formSuccess.formGenerator` +- `displayForm.formGenerator` +- `displayConfirmation.formGenerator` +- `confirmSuccess.formGenerator` +- `error.formGenerator` + +Example: +``` +$(".project-edit-button").on("formSuccess.formGenerator", function () { + // Refresh data +}); +``` + +# Working example + +See the [UF_FormGeneratorExample](https://github.com/lcharette/UF_FormGeneratorExample) repo for an example of the FormGenerator full code. + +# Running tests + +FormGenerator comes with some unit tests. Before submitting a new Pull Request, you need to make sure all tests are a go. With the sprinkle added to your UserFrosting installation, simply execute the `php bakery test` command to run the tests. + +# Licence + +By [Louis Charette](https://github.com/lcharette). Copyright (c) 2017, free to use in personal and commercial software as per the MIT license. diff --git a/main/app/sprinkles/FormGenerator/asset-bundles.json b/main/app/sprinkles/FormGenerator/asset-bundles.json new file mode 100755 index 0000000..b763d55 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/asset-bundles.json @@ -0,0 +1,17 @@ +{ + "bundle": { + "js/FormGenerator": { + "scripts": [ + "vendor/bootstrap3-typeahead/bootstrap3-typeahead.js", + "js/widget-formGenerator.js" + ], + "options": { + "result": { + "type": { + "scripts": "plain" + } + } + } + } + } +} diff --git a/main/app/sprinkles/FormGenerator/assets/js/widget-formGenerator.js b/main/app/sprinkles/FormGenerator/assets/js/widget-formGenerator.js new file mode 100755 index 0000000..52743f6 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/assets/js/widget-formGenerator.js @@ -0,0 +1,348 @@ +/*! + * FormGenerator Plugin + * + * JQuery plugin for the UserFrosting FormGenerator Sprinkle + * Based on UserFrosting v3 + * + * @package UF_FormGenerator + * @author Louis Charette + * @link https://github.com/lcharette/UF_FormGenerator + * @license MIT + */ + +;(function($, window, document, undefined) { + "use strict"; + + // Define plugin name and defaults. + var pluginName = "formGenerator", + defaults = { + DEBUG : false, + mainAlertElement : $('#alerts-page'), + redirectAfterSuccess : true, + autofocusModalElement : true + }; + + // Constructor + function Plugin (element, options) { + this.elements = element; + this.$elements = $(this.elements); + this.settings = $.extend(true, {}, defaults, options); + this._defaults = defaults; + this._name = pluginName; + + // Detect changes to element attributes + this.$elements.attrchange({ callback: function (event) { this.elements = event.target; }.bind(this) }); + + // Initialise ufAlerts + if (!this.settings.mainAlertElement.data('ufAlerts')) { + this.settings.mainAlertElement.ufAlerts(); + } + + return this; + } + + // Functions + $.extend(Plugin.prototype, { + /** + * Bind the display action for a form to the button + */ + display: function() { + this.$elements.on('click', $.proxy(this._fetchForm, this)); + return this.$elements; + }, + /** + * Bind the confirm action to the button + */ + confirm: function() { + this.$elements.on('click', $.proxy(this._fetchConfirmModal, this)); + return this.$elements; + }, + /** + * Fetch the form HTML + */ + _fetchForm: function(event) { + + // Get the button element + var button = event.currentTarget; + + // Get the box_id. Define one if none is defined + var box_id = $(button).data('target'); + if (box_id == undefined) { + box_id = "formGeneratorModal"; + } + + // Delete any existing instance of the form with the same name + if($('#' + box_id).length) { + $('#' + box_id).remove(); + } + + // Prepare the ajax payload + var payload = $.extend({ + box_id: box_id} + , button.dataset); + + // Fetch and render the form + $.ajax({ + type: "GET", + url: $(button).data('formurl'), + data: payload, + cache: false + }) + .done($.proxy(this._displayForm, this, box_id, button)) + .fail($.proxy(this._displayFailure, this, button)); + }, + /** + * Displays the form modal and set up ufForm + */ + _displayForm: function(box_id, button, data) { + + // Trigger pre-display event + $(button).trigger("displayForm." + this._name); + + // Append the form as a modal dialog to the body + $( "body" ).append(data); + $('#' + box_id).modal('show'); + + // Set focus on first element + if (this.settings.autofocusModalElement) { + $('#' + box_id).on('shown.bs.modal', function () { + $(this).find(".modal-body").find(':input:enabled:visible:first').focus(); + }); + } + + // Setup ufAlerts + var boxMsgTarget = $("#"+box_id+" #form-alerts"); + + // Show the alert. We could have info alert coming in + if (!boxMsgTarget.data('ufAlerts')) { + boxMsgTarget.ufAlerts(); + } + boxMsgTarget.ufAlerts('clear').ufAlerts('fetch').ufAlerts('render'); + + // Setup the loaded form with ufForm + $('#' + box_id).find("form").ufForm({ + validators: validators, + msgTarget: $("#"+box_id+" #form-alerts") + }) + .on("submitSuccess.ufForm", $.proxy(this._formPostSuccess, this, box_id, button)) + .on("submitError.ufForm", $.proxy(this._displayFormFaillure, this, box_id, button)); + }, + /** + * Action done when a form is successful + */ + _formPostSuccess: function(box_id, button, event, data) { + + // Trigger success event + $(button).trigger("formSuccess." + this._name, data); + + // Refresh page or close modal + if (this.settings.redirectAfterSuccess) { + window.location.reload(true); + } else { + $('#' + box_id).modal('hide'); + this.settings.mainAlertElement.ufAlerts('clear').ufAlerts('fetch').ufAlerts('render'); + } + }, + /** + * Fetch confirmation modal + */ + _fetchConfirmModal: function(event) { + + // Get the button element + var button = event.currentTarget; + + // Get the box_id. Define one if none is defined + var box_id = $(button).data('target'); + if (box_id == undefined) { + box_id = "formGeneratorModal"; + } + + // Delete any existing instance of the form with the same name + if($('#' + box_id).length) { + $('#' + box_id).remove(); + } + + // Prepare the ajax payload + var payload = $.extend({ + box_id: box_id, + box_title: $(button).data('confirmTitle') ? $(button).data('confirmTitle') : null, + confirm_message: $(button).data('confirmMessage') ? $(button).data('confirmMessage') : null, + confirm_warning: $(button).data('confirmWarning') ? $(button).data('confirmWarning') : null, + confirm_button: $(button).data('confirmButton') ? $(button).data('confirmButton') : null, + cancel_button: $(button).data('cancelButton') ? $(button).data('cancelButton') : null + }, button.dataset); + + // Fetch and render the form + $.ajax({ + type: "GET", + url: $(button).data('formurl') ? $(button).data('formurl') : site['uri']['public'] + "/forms/confirm", + data: payload, + cache: false + }) + .done($.proxy(this._displayConfirmation, this, box_id, button)) + .fail($.proxy(this._displayFailure, this, button)); + }, + /** + * Display confirmation modal + */ + _displayConfirmation: function(box_id, button, data) { + + // Trigger pre-display event + $(button).trigger("displayConfirmation." + this._name); + + // Append the form as a modal dialog to the body + $( "body" ).append(data); + $('#' + box_id).modal('show'); + + $('#' + box_id + ' .js-confirm').on('click', $.proxy(this._sendConfirmation, this, box_id, button)); + }, + /** + * Send confirmation query + */ + _sendConfirmation: function(box_id, button) { + + // Prepare payload + var url = $(button).data('postUrl'); + var method = ($(button).data('postMethod')) ? $(button).data('postMethod') : "POST"; + var data = { + bData: button.dataset, + csrf_name: $('#' + box_id).find("input[name='csrf_name']").val(), + csrf_value: $('#' + box_id).find("input[name='csrf_value']").val() + }; + + // Send ajax + $.ajax({ + type: method, + url: url, + data: data + }) + .done($.proxy(this._confirmationSuccess, this, box_id, button)) + .fail($.proxy(this._displayConfirmationFaillure, this, box_id, button)); + }, + /** + * Action done when a confirmation request is successful + */ + _confirmationSuccess: function(box_id, button, data) { + + // Trigger success event + $(button).trigger("confirmSuccess." + this._name, data); + + // Refresh page or close modal + if (this.settings.redirectAfterSuccess) { + + // Redirect if result contains intrusctions to + if (data.redirect) { + window.location.replace(data.redirect); + } else { + window.location.reload(true); + } + } else { + $('#' + box_id).modal('hide'); + this.settings.mainAlertElement.ufAlerts('clear').ufAlerts('fetch').ufAlerts('render'); + } + }, + /** + * Failure callback for ajax requests. Displays the error in the main alertElement + */ + _displayFailure: function(button, response) { + $(button).trigger("error." + this._name); + if ((typeof site !== "undefined") && site.debug.ajax && response.responseText) { + document.write(response.responseText); + document.close(); + } else { + if (this.settings.DEBUG) { + $.error("Error (" + response.status + "): " + response.responseText ); + } + this.settings.mainAlertElement.ufAlerts('clear').ufAlerts('fetch').ufAlerts('render'); + } + }, + /** + * Faillure callback for ajax requests to be displayed in a modal form + */ + _displayFormFaillure: function(box_id, button) { + $(button).trigger("error." + this._name); + $("#"+box_id+" #form-alerts").show(); + }, + /** + * Faillure callback for ajax requests to be displayed in a confirmation form + */ + _displayConfirmationFaillure: function(box_id, button) { + $(button).trigger("error." + this._name); + + // Setup ufAlerts + var boxMsgTarget = $("#"+box_id+" #confirmation-alerts"); + + // Show the alert. We could have info alert coming in + if (!boxMsgTarget.data('ufAlerts')) { + boxMsgTarget.ufAlerts(); + } + boxMsgTarget.ufAlerts('clear').ufAlerts('fetch').ufAlerts('render'); + }, + /** + * Completely destroy the ufAlerts plugin on the element. + */ + destroy: function() { + // Unbind any bound events + this.$elements.off('.' + this._name); + + // Grab jQuery wrapped element before plugin destruction + var $elements = this.$elements; + + // Remove plugin from element + this.$elements.removeData(this._name); + + return $elements; + } + }); + + // Handles instantiation and access to non-private methods. + $.fn[pluginName] = function(methodOrOptions) { + + // If the plugin is called on a non existing element, return nothing + if (this.length == 0) { + return this; + } + + // Grab plugin instance + var instance = $(this).data(pluginName); + + // If undefined or object, uses the default `display` method. + if (methodOrOptions === undefined || typeof methodOrOptions === 'object') { + var method = "display"; + var options = methodOrOptions; + } + // Otherwise ensure first parameter is a valid string + else if (typeof methodOrOptions === 'string') { + // Ensure not a private function + if (methodOrOptions.indexOf('_') !== 0) { + var method = methodOrOptions; + var options = Array.prototype.slice.call(arguments, 1)[0]; + } + else { + $.error( 'Method ' + methodOrOptions + ' is private!' ); + } + } + else { + $.error( 'Method ' + methodOrOptions + ' is invalid.' ); + } + + // Only initalise if not previously done. + if (!instance) { + $(this).data(pluginName, new Plugin(this, options)); + instance = $(this).data(pluginName); + } + + // Make sure method exist + if (typeof instance[method] === 'function') { + // Run the required method + return instance[method](options); + } else { + $.error( 'Method ' + method + ' does not exist.' ); + } + }; + + // Apply on default selector + $(".js-displayForm").formGenerator(); + $(".js-displayConfirm").formGenerator('confirm'); + +})(jQuery, window, document);
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/bower.json b/main/app/sprinkles/FormGenerator/bower.json new file mode 100755 index 0000000..723b5cc --- /dev/null +++ b/main/app/sprinkles/FormGenerator/bower.json @@ -0,0 +1,30 @@ +{ + "name": "formgenerator-assets", + "version": "0.0.1", + "homepage": "https://github.com/lcharette/UF_FormGenerator", + "authors": [ + "lcharette" + ], + "moduleType": [ + "node" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "assets/vendor", + "examples", + "demo-resources", + "demo", + "test", + "tests" + ], + "dependencies": { + "bootstrap3-typeahead": "~3.1.0" + }, + "resolutions": { + "jquery": ">= 2.2.4", + "bootstrap": "3.x" + } +} diff --git a/main/app/sprinkles/FormGenerator/composer.json b/main/app/sprinkles/FormGenerator/composer.json new file mode 100755 index 0000000..e37a372 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/composer.json @@ -0,0 +1,25 @@ +{ + "name": "lcharette/uf_formgenerator", + "type": "userfrosting-sprinkle", + "description": "Form generator for UserFrosting V4", + "keywords": ["Form", "generator", "userfrosting"], + "homepage": "https://github.com/lcharette/UF_FormGenerator", + "license" : "MIT", + "authors" : [ + { + "name": "Louis Charette", + "homepage": "https://github.com/lcharette" + } + ], + "require": { + "php": ">=5.6" + }, + "autoload": { + "psr-4": { + "UserFrosting\\Sprinkle\\FormGenerator\\": "src/" + } + }, + "extra": { + "installer-name": "FormGenerator" + } +} diff --git a/main/app/sprinkles/FormGenerator/locale/en_US/FormGenerator.php b/main/app/sprinkles/FormGenerator/locale/en_US/FormGenerator.php new file mode 100755 index 0000000..41d7bfa --- /dev/null +++ b/main/app/sprinkles/FormGenerator/locale/en_US/FormGenerator.php @@ -0,0 +1,14 @@ +<?php + +return [ + + "CONFIRM" => [ + "@TRANSLATION" => "Confirm action", + + "MESSAGE" => "Are you sure you want to do this?", + + "WARNING" => "This action cannot be undone.", + + "YES" => "Yes, do it" + ] +];
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/locale/fr_FR/FormGenerator.php b/main/app/sprinkles/FormGenerator/locale/fr_FR/FormGenerator.php new file mode 100755 index 0000000..9ede59c --- /dev/null +++ b/main/app/sprinkles/FormGenerator/locale/fr_FR/FormGenerator.php @@ -0,0 +1,14 @@ +<?php + +return [ + + "CONFIRM" => [ + "@TRANSLATION" => "Confirmer l'action", + + "MESSAGE" => "Êtes-vous certain de vouloir faire ceci?", + + "WARNING" => "Cette action ne peut pas être annulée.", + + "YES" => "Oui" + ] +];
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/routes/FormGenerator.php b/main/app/sprinkles/FormGenerator/routes/FormGenerator.php new file mode 100755 index 0000000..0b7ea51 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/routes/FormGenerator.php @@ -0,0 +1,12 @@ +<?php +/** + * UserFrosting (http://www.userfrosting.com) + * + * @link https://github.com/userfrosting/UserFrosting + * @copyright Copyright (c) 2013-2016 Alexander Weissman + * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) + */ + +global $app; + +$app->get('/forms/confirm','UserFrosting\Sprinkle\FormGenerator\Controller\FormGeneratorController:confirm');
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/src/Controller/FormGeneratorController.php b/main/app/sprinkles/FormGenerator/src/Controller/FormGeneratorController.php new file mode 100755 index 0000000..e731011 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Controller/FormGeneratorController.php @@ -0,0 +1,26 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Controller; + +use UserFrosting\Sprinkle\Core\Controller\SimpleController; + +/** + * FormGeneratorController Class + * + * Controller class for /forms/confirm/* URLs. Handles rendering the confirm dialog + */ +class FormGeneratorController extends SimpleController { + + /** + * Display the confirmation dialog + */ + public function confirm($request, $response, $args) { + $this->ci->view->render($response, 'FormGenerator/confirm.html.twig', $request->getQueryParams()); + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/Alert.php b/main/app/sprinkles/FormGenerator/src/Element/Alert.php new file mode 100755 index 0000000..f848b5c --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/Alert.php @@ -0,0 +1,33 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\BaseInput; + +/** + * Alert input type class. + * Manage the default attributes required to display an alert + * + * @extends BaseInput + */ +class Alert extends BaseInput { + + /** + * {@inheritDoc} + */ + protected function applyTransformations() + { + $this->element = array_merge([ + "class" => "alert-danger", + "icon" => "fa-ban", + "value" => $this->value, + "name" => $this->name + ], $this->element); + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/BaseInput.php b/main/app/sprinkles/FormGenerator/src/Element/BaseInput.php new file mode 100755 index 0000000..d892001 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/BaseInput.php @@ -0,0 +1,111 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\InputInterface; +use UserFrosting\Sprinkle\Core\Facades\Translator; +use UserFrosting\Sprinkle\Core\Facades\Debug; + +/** + * BaseInput class. + * + * Parse the schema data for a form input element to add the default + * attributes values and transform other attributes. + * @abstract + * @implements InputInterface + */ +abstract class BaseInput implements InputInterface { + + /** + * @var String The name of the input. + */ + var $name; + + /** + * @var object The input schema data. + */ + var $element; + + /** + * @var String The input value. + */ + var $value; + + /** + * Constructor. + * + * @access public + * @param String $name + * @param object $element + * @param mixed $value (default: null) + * @return void + */ + public function __construct($name, $element, $value = null) + { + $this->name = $name; + $this->element = $element; + $this->value = $value; + } + + /** + * parse function. + * + * Return the parsed input attributes + * @access public + * @return void + */ + public function parse() + { + $this->applyTransformations(); + return $this->element; + } + + /** + * translateArgValue function. + * + * Translate the value of passed argument using the Translator Facade + * @access public + * @param String $argument + * @return void + */ + public function translateArgValue($argument) { + if (isset($this->element[$argument])) { + $this->element[$argument] = Translator::translate($this->element[$argument]); + } + } + + /** + * getValue function. + * + * Return the value of the current input element. If not value is set in + * `$this->value`, return the default value (from the schema data), if any. + * @access public + * @return string The input current value + */ + public function getValue() { + if (isset($this->value) && $this->value !== null) { + return $this->value; + } else if (isset($this->element['default'])) { + return $this->element['default']; + } else { + return ""; + } + } + + /** + * applyTransformations function. + * + * Add defaut attributes to the current input element. Also transform + * attributes values passed from the schema + * @access protected + * @abstract + * @return void + */ + abstract protected function applyTransformations(); +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/Checkbox.php b/main/app/sprinkles/FormGenerator/src/Element/Checkbox.php new file mode 100755 index 0000000..59e6eaf --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/Checkbox.php @@ -0,0 +1,38 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\BaseInput; + +/** + * Checkbox input type class. + * Manage the default attributes required to display a checkbox input + * + * @extends BaseInput + */ +class Checkbox extends BaseInput { + + /** + * {@inheritDoc} + */ + protected function applyTransformations() + { + $this->element = array_merge([ + "class" => "js-icheck", + "name" => $this->name, + "id" => "field_" . $this->name, + "binary" => true + ], $this->element); + + // We add the check status instead of the value + if ($this->element["binary"] !== false && $this->getValue() == 1) { + $this->element["checked"] = "checked"; + } + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/Hidden.php b/main/app/sprinkles/FormGenerator/src/Element/Hidden.php new file mode 100755 index 0000000..08c22f7 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/Hidden.php @@ -0,0 +1,32 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\BaseInput; + +/** + * Hidden input type class. + * Manage the default attributes required to display an hidden input type + * + * @extends BaseInput + */ +class Hidden extends BaseInput { + + /** + * {@inheritDoc} + */ + protected function applyTransformations() + { + $this->element = array_merge([ + "value" => $this->getValue(), + "name" => $this->name, + "id" => "field_" . $this->name + ], $this->element); + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/InputInterface.php b/main/app/sprinkles/FormGenerator/src/Element/InputInterface.php new file mode 100755 index 0000000..7405109 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/InputInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +/** + * InputInterface + * + * Interface for Form elements classes + */ +interface InputInterface { + public function __construct($name, $element, $value = null); + public function parse(); +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/Select.php b/main/app/sprinkles/FormGenerator/src/Element/Select.php new file mode 100755 index 0000000..bb23772 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/Select.php @@ -0,0 +1,41 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\BaseInput; + +/** + * Select input type class. + * Manage the default attributes required to display a select input type + * + * @extends BaseInput + */ +class Select extends BaseInput { + + /** + * {@inheritDoc} + */ + protected function applyTransformations() + { + $this->element = array_merge([ + "class" => "form-control js-select2", + "value" => $this->getValue(), + "name" => $this->name, + "id" => "field_" . $this->name + ], $this->element); + + // Placeholder is required to be in `data-*` for select 2 + // Plus we translate the placeholder + if (isset($this->element["placeholder"])) { + $this->element["data-placeholder"] = $this->element["placeholder"]; + unset($this->element["placeholder"]); + $this->translateArgValue('data-placeholder'); + } + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/Text.php b/main/app/sprinkles/FormGenerator/src/Element/Text.php new file mode 100755 index 0000000..b936fe2 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/Text.php @@ -0,0 +1,37 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\BaseInput; + +/** + * Text input type class. + * Manage the default attributes required to display a text and other html5 input + * + * @extends BaseInput + */ +class Text extends BaseInput { + + /** + * {@inheritDoc} + */ + protected function applyTransformations() + { + $this->element = array_merge([ + "autocomplete" => "off", + "class" => "form-control", + "value" => $this->getValue(), + "name" => $this->name, + "id" => "field_" . $this->name + ], $this->element); + + // Translate placeholder + $this->translateArgValue('placeholder'); + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Element/Textarea.php b/main/app/sprinkles/FormGenerator/src/Element/Textarea.php new file mode 100755 index 0000000..bec3a6c --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Element/Textarea.php @@ -0,0 +1,38 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator\Element; + +use UserFrosting\Sprinkle\FormGenerator\Element\BaseInput; + +/** + * Textarea input type class. + * Manage the default attributes required to display a textarea input + * + * @extends BaseInput + */ +class Textarea extends BaseInput { + + /** + * {@inheritDoc} + */ + protected function applyTransformations() + { + $this->element = array_merge([ + "autocomplete" => "off", + "class" => "form-control", + "value" => $this->getValue(), + "name" => $this->name, + "rows" => 3, + "id" => "field_" . $this->name + ], $this->element); + + // Translate placeholder + $this->translateArgValue('placeholder'); + } +} diff --git a/main/app/sprinkles/FormGenerator/src/Form.php b/main/app/sprinkles/FormGenerator/src/Form.php new file mode 100755 index 0000000..e845e3e --- /dev/null +++ b/main/app/sprinkles/FormGenerator/src/Form.php @@ -0,0 +1,188 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Sprinkle\FormGenerator; + +use Illuminate\Contracts\Config\Repository; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; +use Illuminate\Support\Str; +use UserFrosting\Fortress\RequestSchema\RequestSchemaInterface; + +/** + * Form Class + * + * The FormGenerator class, which is used to return the `form` part from a Fortress + * schema for html form generator in Twig. + */ +class Form { + + /** + * @var RequestSchemaInterface The form fields definition + */ + protected $schema; + + /** + * @var array|object The form values + */ + protected $data = []; + + /** + * @var string Use this to wrap form fields in top-level array + */ + protected $formNamespace = ""; + + /** + * Constructor + * + * @param RequestSchemaInterface $schema + * @param array|object $data (default: []) + * @return void + */ + public function __construct(RequestSchemaInterface $schema, $data = []) + { + $this->setSchema($schema); + $this->setData($data); + } + + /** + * Set the form current values + * + * @param array|object $data The form values + */ + public function setData($data) + { + if ($data instanceof Collection || $data instanceof Model) { + $this->data = $data->toArray(); + } else if (is_array($data) || $data instanceof Repository) { + $this->data = $data; + } else { + throw new \InvalidArgumentException("Data must be an array, a Collection, a Model or a Repository"); + } + } + + /** + * Set the schema for this validator. + * + * @param RequestSchemaInterface $schema A RequestSchemaInterface object, containing the form definition. + */ + public function setSchema(RequestSchemaInterface $schema) + { + $this->schema = $schema; + } + + /** + * Use to define the value of a form input when `setData` is already set + * + * @param mixed $inputName + * @param mixed $value + * @return void + */ + public function setValue($inputName, $value) + { + $this->data[$inputName] = $value; + } + + /** + * Function used to overwrite the input argument from a schema file + * Can also be used to overwrite an argument hardcoded in the Twig file. + * Use `setCustomFormData` to set any other tag. + * + * @param string $inputName The input name where the argument will be added + * @param string $property The argument name. Example "data-color" + * @param string $data The value of the argument + * @return void + */ + public function setInputArgument($inputName, $property, $data) + { + if ($this->schema->has($inputName)) { + // Get the element and force set the property + $element = $this->schema->get($inputName); + $element['form'][$property] = $data; + + // Push back the modifyed element in the schema + $this->schema->set($inputName, $element); + } + } + + /** + * Function used to set options of a select element. Shortcut for using + * `setInputArgument` and `setValue`. + * + * @param string $inputName The select name to add options to + * @param array $data An array of `value => label` options + * @param string $selected The selected key + * @return void + */ + public function setOptions($inputName, $data = [], $selected = null) + { + // Set opdations + $this->setInputArgument($inputName, 'options', $data); + + // Set the value + if (!is_null($selected)) { + $this->setValue($inputName, $selected); + } + } + + /** + * Function to set the form namespace. + * Use the form namespace to wrap the fields name in a top level array. + * Useful when using multiple schemas at once or if the names are using dot syntaxt. + * See : http://stackoverflow.com/a/20365198/445757 + * + * @param string $namespace + * @return void + */ + public function setFormNamespace($namespace) + { + $this->formNamespace = $namespace; + } + + /** + * Generate an array contining all nececerry value to generate a form + * with Twig. + * + * @return array The form fields data + */ + public function generate() + { + $form = collect([]); + + // Loop all the the fields in the schema + foreach ($this->schema->all() as $name => $input) { + + // Skip the one that don't have a `form` definition + if (isset($input['form'])) { + + // Get the value from the data + $value = isset($this->data[$name]) ? $this->data[$name] : null; + + // Add the namespace to the name if it's defined + $name = ($this->formNamespace != "") ? $this->formNamespace."[".$name."]" : $name; + + // Get the element class and make sure it exist + $type = (isset($input['form']['type'])) ? $input['form']['type'] : "text"; + $type = "UserFrosting\\Sprinkle\\FormGenerator\\Element\\" . Str::studly($type); + + // If class doesn't esist, default to Text element + if (!class_exists($type)) { + $type = "UserFrosting\\Sprinkle\\FormGenerator\\Element\\Text"; + } + + // Create a new instance + $element = new $type($name, $input['form'], $value); + + // Push data to `$form` + $form->put($name, $element->parse()); + } + } + + return $form->toArray(); + } +} diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/FormGenerator.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/FormGenerator.html.twig new file mode 100755 index 0000000..c902064 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/FormGenerator.html.twig @@ -0,0 +1,33 @@ +{% import "FormGenerator/macros/checkbox.html.twig" as checkbox %} +{% import "FormGenerator/macros/textarea.html.twig" as textarea %} +{% import "FormGenerator/macros/select.html.twig" as select %} +{% import "FormGenerator/macros/text.html.twig" as text %} +{% import "FormGenerator/macros/hidden.html.twig" as hidden %} +{% import "FormGenerator/macros/alert.html.twig" as alert %} + +{% for name,input in fields %} + {% if input.type == "hidden" %} + {{ hidden.generate(input) }} + {% elseif input.type == "alert" %} + {{ alert.generate(input) }} + {% else %} + {% if not input.hidden %} + <div class="form-group has-feedback"> + {% if formLayout == 'horizontal' %}<label for="{{input.id}}" class="col-sm-2 control-label">{% else %}<label for="{{input.id}}">{% endif %} + {% if input.label %}{{translate(input.label)}}{% else %} {% endif %} + </label> + {% if formLayout == 'horizontal' %}<div class="col-sm-10">{% endif %} + {% if input.type == "textarea" %} + {{ textarea.generate(input) }} + {% elseif input.type == "checkbox" %} + {{ checkbox.generate(input) }} + {% elseif input.type == "select" %} + {{ select.generate(input) }} + {% else %} + {{ text.generate(input) }} + {% endif %} + {% if formLayout == 'horizontal' %}</div>{% endif %} + </div> + {% endif %} + {% endif %} +{% endfor %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/confirm.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/confirm.html.twig new file mode 100755 index 0000000..96d0072 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/confirm.html.twig @@ -0,0 +1,28 @@ +<div id='{{box_id}}' class='modal fade'> + <div class="modal-dialog"> + <div class="modal-content"> + <div class='modal-header'> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + <h4 class='modal-title'>{% if box_title %}{{ translate(box_title) }}{% else %}{{translate("CONFIRM")}}{% endif %}</h4> + </div> + <div class='modal-body'> + <div id="confirmation-alerts"></div> + <h4> + {% if confirm_message %}{{ translate(confirm_message) }}{% else %}{{translate("CONFIRM.MESSAGE")}}{% endif %} + <br /> + <small>{% if confirm_warning %}{{ translate(confirm_warning) }}{% else %}{{translate("CONFIRM.WARNING")}}{% endif %}</small> + </h4> + <br> + <div class='btn-group-action'> + {% include "forms/csrf.html.twig" %} + <button type='button' class='btn btn-danger btn-lg btn-block js-confirm'>{% if confirm_button %}{{ translate(confirm_button) }}{% else %}{{translate("CONFIRM.YES")}}{% endif %}</button> + <button type='button' class='btn btn-default btn-lg btn-block' data-dismiss='modal'>{% if cancel_button %}{{ translate(cancel_button) }}{% else %}{{translate("CANCEL")}}{% endif %}</button> + </div> + </div> + </div> + <!-- /.modal-content --> + </div> + <!-- /.modal-dialog --> +</div>
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/alert.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/alert.html.twig new file mode 100755 index 0000000..21636f0 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/alert.html.twig @@ -0,0 +1,5 @@ +{% macro generate(input) %} + <div class="alert {{input.class}}"> + {% if input.icon %}<i class="icon fa {{input.icon}}"></i> {% endif %}{{ translate(input.value) }} + </div> +{% endmacro %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/checkbox.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/checkbox.html.twig new file mode 100755 index 0000000..9065651 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/checkbox.html.twig @@ -0,0 +1,5 @@ +{% macro generate(input) %} + <div class="checkbox icheck"> + <input{% for type,value in input %} {{type}}="{{value}}"{% endfor %}></input> + </div> +{% endmacro %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/hidden.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/hidden.html.twig new file mode 100755 index 0000000..a30fb7a --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/hidden.html.twig @@ -0,0 +1,3 @@ +{% macro generate(input) %} + <input{% for type,value in input %} {{type}}="{{value}}"{% endfor %}></input> +{% endmacro %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/select.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/select.html.twig new file mode 100755 index 0000000..256c3f2 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/select.html.twig @@ -0,0 +1,5 @@ +{% macro generate(input) %} + <select{% for type,value in input %}{% if type != "options" %} {{type}}="{{value}}"{% endif %}{% endfor %}> + {% for option, label in input.options %}<option value="{{option}}" {% if (option == input.value) %}selected{% endif %}>{{translate(label)}}</option>{% endfor %} + </select> +{% endmacro %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/text.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/text.html.twig new file mode 100755 index 0000000..30beb5b --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/text.html.twig @@ -0,0 +1,10 @@ +{% macro generate(input) %} + {% if input.icon %} + <div class="input-group"> + <span class="input-group-addon"><i class="fa {{input.icon}} fa-fw"></i></span> + {% else %} + <div class="form-group"> + {% endif %} + <input{% for type,value in input %} {{type}}="{{value}}"{% endfor %}></input> + </div> +{% endmacro %} diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/textarea.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/textarea.html.twig new file mode 100755 index 0000000..ce5e2e7 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/macros/textarea.html.twig @@ -0,0 +1,5 @@ +{% macro generate(input) %} + {% if input.icon %}<div class="input-group"><span class="input-group-addon"><i class="fa {{input.icon}} fa-fw"></i></span>{% endif %} + <textarea{% for type,value in input %} {{type}}="{{value}}"{% endfor %}>{{input.value}}</textarea> + {% if input.icon %}</div>{% endif %} +{% endmacro %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/modal-large.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/modal-large.html.twig new file mode 100755 index 0000000..b2de3f9 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/modal-large.html.twig @@ -0,0 +1,3 @@ +{% extends "FormGenerator/modal.html.twig" %} + +{% block modal_size %}modal-lg{% endblock %} diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/modal.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/modal.html.twig new file mode 100755 index 0000000..3c66201 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/modal.html.twig @@ -0,0 +1,62 @@ +{% block modal %} +<div id='{{box_id}}' class='modal fade'> + <div class="modal-dialog {% block modal_size %}{% endblock %}" role="document"> + <div class="modal-content"> + <form name="ModalFormGenerator" method="{{ (form_method) ? form_method : 'post' }}" action="{{form_action}}"> + {% include "forms/csrf.html.twig" %} + <div class='modal-header'> + <button type='button' class='close' data-dismiss='modal' aria-hidden='true'>×</button> + <h4 class='modal-title'>{% if box_title %}{{ translate(box_title) }}{% endif %}</h4> + </div> + <div class='modal-body'> + <div id="form-alerts"></div> + {% block form_content %} + {% include 'FormGenerator/FormGenerator.html.twig' %} + {% endblock %} + </div> + <div class='modal-footer'> + <div class="row"> + {% if "submit" not in buttons.hidden %} + <div class="col-xs-8 col-sm-4"> + <div class="vert-pad"> + <button type="submit" class="btn btn-block btn-lg btn-success js-submit"{% if 'submit' in buttons.disabled %} disabled{% endif %}> + {% if submit_button %}{{ translate(submit_button) }}{% else %}{{translate("SUBMIT")}}{% endif %} + </button> + </div> + </div> + {% endif %} + {% if "cancel" not in buttons.hidden %} + <div class="col-xs-4 col-sm-3 pull-right"> + <div class="vert-pad"> + <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal"{% if 'cancel' in buttons.disabled %} disabled{% endif %}> + {{ translate("CANCEL") }} + </button> + </div> + </div> + {% endif %} + </div> + </div> + </form> + </div> + <!-- /.modal-content --> + </div> + <!-- /.modal-dialog --> +</div> +{% endblock %} + +{% block scripts_page %} + <!-- Need to reload and reaply those since we're in modal --> + <script> + // Load the validator rules for this form + var validators = {{validators|default('{}')| raw}}; + + $(function() { + $('#{{box_id}}').iCheck({ + checkboxClass: 'icheckbox_square-blue', + radioClass: 'iradio_square-blue', + increaseArea: '20%' // optional + }); + $('#{{box_id}}').find('.js-select2').select2({ minimumResultsForSearch: Infinity, width: '100%' }); + }); + </script> +{% endblock %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/templates/FormGenerator/typehead.html.twig b/main/app/sprinkles/FormGenerator/templates/FormGenerator/typehead.html.twig new file mode 100755 index 0000000..150427a --- /dev/null +++ b/main/app/sprinkles/FormGenerator/templates/FormGenerator/typehead.html.twig @@ -0,0 +1,15 @@ +{% extends "FormGenerator/modal.html.twig" %} + +{% block scripts_page %} +{{ parent() }} + +<script> + $("input[name='{{typeaheadInput}}']").typeahead({ + source: function(query, process) { + return $.getJSON('{{typeaheadUrl}}', { input : query}, function(data) { + return process(data); + }); + } + }); +</script> +{% endblock %}
\ No newline at end of file diff --git a/main/app/sprinkles/FormGenerator/tests/Unit/FormGeneratorTest.php b/main/app/sprinkles/FormGenerator/tests/Unit/FormGeneratorTest.php new file mode 100755 index 0000000..066e98d --- /dev/null +++ b/main/app/sprinkles/FormGenerator/tests/Unit/FormGeneratorTest.php @@ -0,0 +1,408 @@ +<?php +/** + * UF Form Generator + * + * @link https://github.com/lcharette/UF_FormGenerator + * @copyright Copyright (c) 2017 Louis Charette + * @license https://github.com/lcharette/UF_FormGenerator/blob/master/LICENSE (MIT License) + */ +namespace UserFrosting\Tests\Unit; + +use UserFrosting\Tests\TestCase; +use UserFrosting\Tests\DatabaseTransactions; +use League\FactoryMuffin\Faker\Facade as Faker; +use UserFrosting\Support\Repository\Loader\YamlFileLoader; +use UserFrosting\Fortress\RequestSchema; +use UserFrosting\Fortress\RequestSchema\RequestSchemaRepository; +use UserFrosting\Fortress\RequestDataTransformer; +use UserFrosting\Fortress\ServerSideValidator; +use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; +use UserFrosting\Sprinkle\FormGenerator\Form; +use UserFrosting\Sprinkle\FormGenerator\Element\InputInterface; + + +/** + * FormGeneratorTest + * The FormGenerator unit tests. + */ +class FormGeneratorTest extends TestCase +{ + var $basePath; + + public function setUp() + { + parent::setUp(); + $this->basePath = __DIR__ . '/data'; + } + + /** + * Test the base `Test` element class works on it's own + */ + public function testTextFormElement() + { + // Get Schema + $loader = new YamlFileLoader($this->basePath . '/good.json'); + $schema = new RequestSchemaRepository($loader->load()); + + // Get TextInput from the `name` element of the schema + $inputSchema = $schema["name"]["form"]; + $textInput = new \UserFrosting\Sprinkle\FormGenerator\Element\Text("name", $inputSchema); + + // Test instanceof $textInput + $this->assertInstanceof(InputInterface::class, $textInput); + + // Parse the input + $text = $textInput->parse(); + + // Test the parsing + $expected = [ + "type" => "text", + "label" => "Project Name", + "icon" => "fa-flag", + "autocomplete" => "off", + "class" => "form-control", + "placeholder" => "Project Name", + 'name' => 'name', + 'id' => 'field_name', + 'value' => '' + ]; + + // We test the generated result + $this->assertEquals($expected, $text); + } + + /** + * This test make sure the `Text` element works correctly when a current + * value is passed to the constructor. Should return the same as the + * previous test, but with the `value` setup instead of empty + */ + public function testTextFormElementWithData() + { + // Get Schema + $loader = new YamlFileLoader($this->basePath . '/good.json'); + $schema = new RequestSchemaRepository($loader->load()); + + // Get TextInput from the `name` element of the schema + $inputSchema = $schema["name"]["form"]; + $textInput = new \UserFrosting\Sprinkle\FormGenerator\Element\Text("name", $inputSchema, "The Bar project"); + + // Test instanceof $textInput + $this->assertInstanceof(InputInterface::class, $textInput); + + // Parse the input + $text = $textInput->parse(); + + // Test the parsing + $expected = [ + "type" => "text", + "label" => "Project Name", + "icon" => "fa-flag", + "autocomplete" => "off", + "class" => "form-control", + "placeholder" => "Project Name", + 'name' => 'name', + 'id' => 'field_name', + 'value' => 'The Bar project' + ]; + + // We test the generated result + $this->assertEquals($expected, $text); + } + + /** + * This test is the same as the one before, but we test the `owener` field with some data + * This make sure the `default` schema field will work correctly when empty data is passed + */ + public function testTextFormElementWithEmptyData() + { + // Get Schema + $loader = new YamlFileLoader($this->basePath . '/good.json'); + $schema = new RequestSchemaRepository($loader->load()); + + // Get TextInput from the `name` element of the schema + $inputSchema = $schema["owner"]["form"]; + $textInput = new \UserFrosting\Sprinkle\FormGenerator\Element\Text("owner", $inputSchema, ""); + + // Test instanceof $textInput + $this->assertInstanceof(InputInterface::class, $textInput); + + // Parse the input + $text = $textInput->parse(); + + // Test the parsing + $expected = [ + 'label' => 'Project Owner', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => '', //Shoudn't be a value here ! "" is overwritting "Foo" + 'name' => 'owner', + 'id' => 'owner', + 'type' => 'text', + 'icon' => 'fa-user', + 'placeholder' => 'Project Owner', + 'default' => 'Foo' + ]; + + // We test the generated result + $this->assertEquals($expected, $text); + } + + /** + * Test the Form Class. + * Run the test with no current values (empty form) + */ + public function testForm() + { + // Get Schema + $loader = new YamlFileLoader($this->basePath . '/good.json'); + $schema = new RequestSchemaRepository($loader->load()); + + // Generate the form + $form = new Form($schema); + + // Test to make sure the class creation is fine + $this->assertInstanceof(Form::class, $form); + + // Test the form generation + $generatedForm = $form->generate(); + $this->assertInternalType("array", $generatedForm); + + // Test one of the form input + $expected = [ + 'number' => [ + 'label' => 'Project Number', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => '', + 'name' => 'number', + 'id' => 'field_number', + 'type' => 'number', + 'icon' => 'fa-edit', + 'placeholder' => 'Project Number' + ], + 'owner' => [ + 'label' => 'Project Owner', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => 'Foo', + 'name' => 'owner', + 'id' => 'owner', + 'type' => 'text', + 'icon' => 'fa-user', + 'placeholder' => 'Project Owner', + 'default' => 'Foo' + ], + 'name' => [ + 'label' => 'Project Name', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => '', + 'name' => 'name', + 'id' => 'field_name', + 'type' => 'text', + 'icon' => 'fa-flag', + 'placeholder' => 'Project Name' + ], + 'description' => [ + 'label' => 'Project Description', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => '', + 'name' => 'description', + 'rows' => 5, + 'id' => 'field_description', + 'type' => 'textarea', + 'icon' => 'fa-pencil', + 'placeholder' => 'Project Description' + ], + 'status' => [ + 'label' => 'Project Status', + 'class' => 'form-control js-select2', + 'value' => '', + 'name' => 'status', + 'id' => 'field_status', + 'type' => 'select', + 'options' => [ + 0 => 'Closed', + 1 => 'Open' + ] + ], + 'active' => [ + 'label' => 'Active', + 'class' => 'js-icheck', + 'name' => 'active', + 'id' => 'field_active', + 'type' => 'checkbox', + 'binary' => true + ], + 'hidden' => [ + 'value' => 'Something', + 'name' => 'hidden', + 'id' => 'field_hidden', + 'type' => 'hidden' + ], + 'alert' => [ + 'class' => 'alert-success', + 'icon' => 'fa-check', + 'value' => 'You\'re awesome!', + 'name' => 'alert', + 'type' => 'alert' + ] + ]; + + // We test the generated result + $this->assertEquals($expected, $generatedForm); + } + + /** + * Test the Form Clas with values to make sure filled form works correctly + */ + public function testFormWithData() + { + // Get Schema + $loader = new YamlFileLoader($this->basePath . '/good.json'); + $schema = new RequestSchemaRepository($loader->load()); + + // The data + $data = [ + "name" => "Bar project", + "owner" => "", + "description" => "The bar project is less awesome, but at least it's open.", + "status" => 1, + "hiddenString" => "The Bar secret code is...", + "completion" => 12, + "active" => true + ]; + + // Generate the form + $form = new Form($schema, $data); + + // Test to make sure the class creation is fine + $this->assertInstanceof(Form::class, $form); + + // Test the form generation + $generatedForm = $form->generate(); + $this->assertInternalType("array", $generatedForm); + + // Test one of the form input + $expected = [ + 'number' => [ + 'label' => 'Project Number', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => '', + 'name' => 'number', + 'id' => 'field_number', + 'type' => 'number', + 'icon' => 'fa-edit', + 'placeholder' => 'Project Number' + ], + 'name' => [ + 'label' => 'Project Name', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => 'Bar project', //Value here ! + 'name' => 'name', + 'id' => 'field_name', + 'type' => 'text', + 'icon' => 'fa-flag', + 'placeholder' => 'Project Name' + ], + 'owner' => [ + 'label' => 'Project Owner', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => '', //Shoudn't be a value here ! "" is overwritting "Foo" + 'name' => 'owner', + 'id' => 'owner', + 'type' => 'text', + 'icon' => 'fa-user', + 'placeholder' => 'Project Owner', + 'default' => 'Foo' + ], + 'description' => [ + 'label' => 'Project Description', + 'autocomplete' => 'off', + 'class' => 'form-control', + 'value' => 'The bar project is less awesome, but at least it\'s open.', //Value here ! + 'name' => 'description', + 'rows' => 5, + 'id' => 'field_description', + 'type' => 'textarea', + 'icon' => 'fa-pencil', + 'placeholder' => 'Project Description' + ], + 'status' => [ + 'label' => 'Project Status', + 'class' => 'form-control js-select2', + 'value' => 1, //Value here ! + 'name' => 'status', + 'id' => 'field_status', + 'type' => 'select', + 'options' => [ + 0 => 'Closed', + 1 => 'Open' + ] + ], + 'active' => [ + 'label' => 'Active', + 'class' => 'js-icheck', + 'name' => 'active', + 'id' => 'field_active', + 'type' => 'checkbox', + 'checked' => 'checked', //Value here ! + 'binary' => true + ], + 'hidden' => [ + 'value' => 'Something', + 'name' => 'hidden', + 'id' => 'field_hidden', + 'type' => 'hidden' + ], + 'alert' => [ + 'class' => 'alert-success', + 'icon' => 'fa-check', + 'value' => 'You\'re awesome!', + 'name' => 'alert', + 'type' => 'alert' + ] + ]; + + // We test the generated result + $this->assertEquals($expected, $generatedForm); + } + + /** + * Test a non existant input type. It's supposed to not find the class and + * default back to the `Text` element class. + */ + public function testUndefinedFormElement() + { + // Get Schema + $loader = new YamlFileLoader($this->basePath . '/bad.json'); + $schema = new RequestSchemaRepository($loader->load()); + + // Generate the form + $form = new Form($schema); + + // Test to make sure the class creation is fine + $this->assertInstanceof(Form::class, $form); + + // Test the form generation + $generatedForm = $form->generate(); + $this->assertInternalType("array", $generatedForm); + + // Test one of the form input + $expected = [ + "type" => "foo", + "autocomplete" => "off", + "class" => "form-control", + 'name' => 'myField', + 'id' => 'field_myField', + 'value' => '' + ]; + + // We test the generated result + $this->assertEquals($expected, $generatedForm['myField']); + } +} diff --git a/main/app/sprinkles/FormGenerator/tests/Unit/data/bad.json b/main/app/sprinkles/FormGenerator/tests/Unit/data/bad.json new file mode 100755 index 0000000..683add2 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/tests/Unit/data/bad.json @@ -0,0 +1,12 @@ +{ + "myField" : { + "form" : { + "type" : "foo" + } + }, + "myOtherField" : { + "form" : { + "value" : "Bar" + } + } +} diff --git a/main/app/sprinkles/FormGenerator/tests/Unit/data/good.json b/main/app/sprinkles/FormGenerator/tests/Unit/data/good.json new file mode 100755 index 0000000..61d5233 --- /dev/null +++ b/main/app/sprinkles/FormGenerator/tests/Unit/data/good.json @@ -0,0 +1,67 @@ +{ + "number" : { + "form" : { + "type" : "number", + "label" : "Project Number", + "icon" : "fa-edit", + "placeholder" : "Project Number" + } + }, + "name" : { + "form" : { + "type" : "text", + "label" : "Project Name", + "icon" : "fa-flag", + "placeholder" : "Project Name" + } + }, + "owner" : { + "form" : { + "type" : "text", + "label" : "Project Owner", + "icon" : "fa-user", + "id" : "owner", + "placeholder" : "Project Owner", + "default" : "Foo" + } + }, + "description" : { + "form" : { + "type" : "textarea", + "label" : "Project Description", + "icon" : "fa-pencil", + "placeholder" : "Project Description", + "rows" : 5 + } + }, + "status" : { + "form" : { + "type" : "select", + "label" : "Project Status", + "options" : { + "0" : "Closed", + "1" : "Open" + } + } + }, + "active" : { + "form" : { + "type" : "checkbox", + "label" : "Active" + } + }, + "hidden" : { + "form" : { + "type" : "hidden", + "value" : "Something" + } + }, + "alert" : { + "form" : { + "type" : "alert", + "class" : "alert-success", + "icon" : "fa-check", + "value" : "You're awesome!" + } + } +} |