{"id":20961759,"url":"https://github.com/heimrichhannot/contao-ajax-bundle","last_synced_at":"2025-05-14T07:30:59.610Z","repository":{"id":62515417,"uuid":"124889864","full_name":"heimrichhannot/contao-ajax-bundle","owner":"heimrichhannot","description":null,"archived":false,"fork":false,"pushed_at":"2025-01-03T10:45:41.000Z","size":114,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-19T14:42:13.033Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/heimrichhannot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2018-03-12T13:01:33.000Z","updated_at":"2025-01-03T10:45:40.000Z","dependencies_parsed_at":"2024-02-06T02:31:01.630Z","dependency_job_id":null,"html_url":"https://github.com/heimrichhannot/contao-ajax-bundle","commit_stats":{"total_commits":31,"total_committers":5,"mean_commits":6.2,"dds":0.3870967741935484,"last_synced_commit":"61739d85267351ea8af38a70b89526b031511c67"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heimrichhannot%2Fcontao-ajax-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heimrichhannot%2Fcontao-ajax-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heimrichhannot%2Fcontao-ajax-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heimrichhannot%2Fcontao-ajax-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heimrichhannot","download_url":"https://codeload.github.com/heimrichhannot/contao-ajax-bundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254094837,"owners_count":22013648,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T02:16:38.233Z","updated_at":"2025-05-14T07:30:58.514Z","avatar_url":"https://github.com/heimrichhannot.png","language":"PHP","readme":"\n![](https://img.shields.io/packagist/v/heimrichhannot/contao-ajax-bundle.svg)\n![](https://img.shields.io/packagist/dt/heimrichhannot/contao-ajax-bundle.svg)\n\n# Contao Ajax Bundle\n\nAjax requests within contao are not centralized by default. Due to the handling of ajax requests within different types of modules \na simple \\Environment::get('isAjaxRequest') is not enough to delegate the request to the related module / method.\nThis module provides a global configuration, where you can attach your module within `$GLOBALS['AJAX']` as a custom group and \nadd your actions with the required parameters that should be checked against the request.\n\n## Technical instruction\n\nThe following section will show you how your custom ajax actions can be registered and triggered.\n\n### 1. Configuration / Setup\n\nThe following example shows the [heimrichhannot/contao-formhybrid] (https://github.com/heimrichhannot/contao-formhybrid) ajax configuration.\n```\nconfig.php\n/**\n * Ajax Actions\n */\n$GLOBALS['AJAX'][\\HeimrichHannot\\FormHybrid\\Form::FORMHYBRID_NAME] = array\n(\n\t'actions' =\u003e array\n\t(\n\t\t'toggleSubpalette' =\u003e array\n\t\t(\n\t\t\t'arguments' =\u003e array('subId', 'subField', 'subLoad'),\n\t\t\t'optional'   =\u003e array('subLoad'),\n\t\t),\n\t\t'asyncFormSubmit'  =\u003e array\n\t\t(\n\t\t\t'arguments' =\u003e array(),\n\t\t\t'optional'   =\u003e array(),\n\t\t),\n\t\t'reload'  =\u003e array\n\t\t(\n\t\t\t'arguments' =\u003e array(),\n\t\t\t'optional'   =\u003e array(),\n\t\t\t'csrf_protection' =\u003e true, // cross-site request forgery (ajax token check)\n\t\t),\n\t),\n);\n```\n\nAs you can see, we have a group `formhybrid` that delegates all ajax request with this `group` parameter to formhybrid.\nThen there are some actions `toggleSubpalette`, `asyncFormSubmit` and so on. These mehtod must be present with the same name in the delegated context.\nYou can provide arguments, that should be called within the function and added as arguments to the method. If the argument is `optional`, than the request\nwill be valid if the argument is not present, otherwise all `arguments` must be present in the request, to have a valid ajax request.\nIf you want to protect the ajax request against cross site violations, than add `csrf_protection =\u003e true` to your configuration and dont forget to update the ajax url on each request!\n\n### 2. How can i create the url to my ajax action?\n\nWe provide a simple helper method within `HeimrichHannot\\AjaxBundle\\Manager\\AjaxActionManager` that is called `generateUrl`. The following example shows, how we \ncreate the `toggleSubpalette` url within `FormHybrid`.\n\n```\n\\Contao\\System::getContainer()-\u003eget('huh.ajax.action')-\u003egenerateUrl(Form::FORMHYBRID_NAME, 'toggleSubpalette')\n```\n\nAs you can see, we do not add the arguments by default. This is done within the associated javascript code for `toggleSubpalette`.\nYou have to check within your javascript code, that the arguments `subId`, `subField`, `subLoad` are provided as $_POST parameters within the \najax request by your own.\n\n```\njquery.formhybrid.js\n\ntoggleSubpalette: function (el, id, field, url) {\n    el.blur();\n    var $el = $(el),\n        $item = $('#' + id),\n        $form = $el.closest('form'),\n        checked = true;\n\n    var $formData = $form.serializeArray();\n\n    $formData.push(\n        {name: 'FORM_SUBMIT', value: $form.attr('id')},\n        {name: 'subId', value: id},\n        {name: 'subField',value: field});\n\n    if ($el.is(':checkbox') || $el.is(':radio')) {\n        checked = $el.is(':checked');\n    }\n\n    if (checked === false) {\n\n        $.ajax({\n            type: 'post',\n            url: url,\n            dataType: 'json',\n            data: $formData,\n            success: function (response) {\n                $item.remove();\n            }\n        });\n\n        return;\n    }\n\n    $formData.push(\n        {name: 'subLoad', value: 1}\n    );\n\n    $.ajax({\n        type: 'post',\n        url: url,\n        dataType: 'json',\n        data: $formData,\n        success: function (response, textStatus, jqXHR) {\n            $item.remove();\n            // bootstrapped forms\n            if ($el.closest('form').find('.' + field).length \u003e 0) {\n                // always try to attach subpalette after wrapper element from parent widget\n                $el.closest('form').find('.' + field).eq(0).after(response.result.html);\n            } else {\n                $el.closest('#ctrl_' + field).after(response.result.html);\n            }\n        }\n    });\n}\n```\n\n### 3. How are my ajax actions triggered?\n\nThe give you the biggest possible freedom, we decided to be always within contao context. Therefore all request preconditions are checked within the\ndefault contao request-cycle.\n\nIn case of the `toggleSubpalette` example we trigger the action within `DC_Hybrid::__construct()` and provide a `new FormAjax($this)` as Response Context.\n\nCAUTION: Don't call \"runActiveAction\" in generate() of a Contao module since that's too late. It's always best to run it in __construct().\n\n```\nDC_Hybrid.php\n\npublic function __construct($strTable = '', $varConfig = null, $intId = 0)\n{\n    ...\n    \n    \\Contao\\System::getContainer()-\u003eget('huh.ajax')-\u003erunActiveAction(Form::FORMHYBRID_NAME, 'toggleSubpalette', new FormAjax($this));\n    \n    ...\n    \n}\n```\n\n#### So what is done here? \n\n1. The ajax action is requested from your javascript code.\n2. The ajax action is delegated to the current page, where our `DC_Hybrid` extended Module is available.\n3. The `Ajax` Controller will check the request against the given parameters for `toggleSubpalette` from the `$GLOBALS['AJAX']` config.\n4. If all requirements were meet, and the method `toggleSubpalette` is available within the given context `new FormAjax($this)`, the method is trigged with the given arguments.\n5. Now you can do your module related stuff within `toggleSubpalette` \n6. If you want to return data to the ajax request, then you have to return a valid `HeimrichHannot\\AjaxBundle\\Response\\Response` Object.\n7. The returned `HeimrichHannot\\AjaxBundle\\Response\\Response` Object will be converted to a JSON Object and the request will end here.\n\n## Response Objects\n\nCurrently we implemented three response objects.\n\n1. `HeimrichHannot\\AjaxBundle\\Response\\ResponseSuccess`\n2. `HeimrichHannot\\AjaxBundle\\Response\\ResponseError`\n3. `HeimrichHannot\\AjaxBundle\\Response\\ResponseRedirect`\n\n### ResponseSuccess\n\nThis will return a JSON Object with the HTTP-Statuscode `HTTP/1.1 200 OK` to the ajax action.\n\n#### Example: \n\n##### Client-Side:\n```\n$.ajax({\n    type: 'post',\n    url: url,\n    dataType: 'json',\n    data: $formData,\n    success: function (response, textStatus, jqXHR) {\n        $item.remove();\n        // bootstrapped forms\n        if ($el.closest('form').find('.' + field).length \u003e 0) {\n            // always try to attach subpalette after wrapper element from parent widget\n            $el.closest('form').find('.' + field).eq(0).after(response.result.html);\n        } else {\n            $el.closest('#ctrl_' + field).after(response.result.html);\n        }\n    }\n});\n```\n\n##### Server-Side:\n```\n/**\n * Toggle Subpalette\n * @param      $id\n * @param      $strField\n * @param bool $blnLoad\n *\n * @return ResponseError|ResponseSuccess\n */\nfunction toggleSubpalette($id, $strField, $blnLoad = false)\n{\n    $varValue = \\Contao\\System::getContainer()-\u003eget('huh.request')-\u003egetPost($strField) ?: 0;\n    \n    if (!is_array($this-\u003edca['palettes']['__selector__']) || !in_array($strField, $this-\u003edca['palettes']['__selector__'])) {\n        \\Controller::log('Field \"' . $strField . '\" is not an allowed selector field (possible SQL injection attempt)', __METHOD__, TL_ERROR);\n        \n        return new ResponseError();\n    }\n    \n    $arrData = $this-\u003edca['fields'][$strField];\n    \n    if (!Validator::isValidOption($varValue, $arrData, $this-\u003edc)) {\n        \\Controller::log('Field \"' . $strField . '\" value is not an allowed option (possible SQL injection attempt)', __METHOD__, TL_ERROR);\n        \n        return new ResponseError();\n    }\n    \n    if (empty(FormHelper::getFieldOptions($arrData, $this-\u003edc))) {\n        $varValue = (intval($varValue) ? 1 : '');\n    }\n    \n    $this-\u003edc-\u003eactiveRecord-\u003e{$strField} = $varValue;\n    \n    $objResponse = new ResponseSuccess();\n    \n    if ($blnLoad)\n    {\n        $objResponse-\u003esetResult(new ResponseData($this-\u003edc-\u003eedit(false, $id)));\n    }\n    \n    return $objResponse;\n}\n```\n\n### ResponseError\n\nThis will return a JSON Object with the HTTP-Statuscode `HTTP/1.1 400 Bad Request` to the ajax action.\n\n#### Example: \n\n##### Client-Side:\n```\n $.ajax({\n    url: url ? url : $form.attr('action'),\n    dataType: 'json',\n    data: $formData,\n    method: $form.attr('method'),\n    error: function(jqXHR, textStatus, errorThrown){\n        if (jqXHR.status == 400) {\n            alert(jqXHR.responseJSON.message);\n            return;\n        }\n    }\n});\n```\n\n##### Server-Side:\n```\n$objResponse = new ResponseRedirect();\n$objResponse-\u003esetUrl($strUrl);\nreturn $objResponse;\n```\n\n### ResponseRedirect\n\nThis will return a JSON Object with the HTTP-Statuscode `HTTP/1.1 301 Moved Permanently` to the ajax action.\nThe redirect url is provided within the xhr response object `result.data.url`;\n\n#### Example: \n\n##### Client-Side:\n```\n $.ajax({\n    url: url ? url : $form.attr('action'),\n    dataType: 'json',\n    data: $formData,\n    method: $form.attr('method'),\n    error: function(jqXHR, textStatus, errorThrown){\n        if (jqXHR.status == 301) {\n            location.href = jqXHR.responseJSON.result.data.url;\n            return;\n        }\n    }\n});\n```\n\n##### Server-Side:\n```\n$objResponse = new ResponseRedirect();\n$objResponse-\u003esetUrl($strUrl);\ndie(json_encode($objResponse));\n```\n\n## Unit Testing\n\nFor unit testing, define the variable `UNIT_TESTING` as `true` within the $GLOBALS.\n\n```\n//bootstrap.php\n\ndefine('UNIT_TESTING', true);\n```\n\nThan you are able to catch the ajax result within you test, by catching the `HeimrichHannot\\AjaxBundle\\Exception\\AjaxExitException`.\n\n```\n// MyTestClass.php\n\n    /**\n     * @test\n     */\n    public function myTest()\n    {\n        $objRequest = \\Contao\\System::getContainer()-\u003eget('huh.request')-\u003ecreate('http://localhost' . AjaxAction::generateUrl('myAjaxGroup', 'myAjaxAction'), 'post');\n\n        $objRequest-\u003eheaders-\u003eset('X-Requested-With', 'XMLHttpRequest'); // xhr request\n\n        Request::set($objRequest);\n\n\t\t$objForm = new TestPostForm();\n\n        try\n        {\n            $objForm-\u003egenerate();\n            // unreachable code: if no exception is thrown after form was created, something went wrong\n            $this-\u003eexpectException(\\HeimrichHannot\\Ajax\\Exception\\AjaxExitException::class);\n        } catch (AjaxExitException $e)\n        {\n            $objJson = json_decode($e-\u003egetMessage());\n\n            $this-\u003eassertTrue(strpos($objJson-\u003eresult-\u003ehtml, 'id=\"my_css_id\"') \u003e 0); // check that id is present within response\n        }\n    }\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheimrichhannot%2Fcontao-ajax-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheimrichhannot%2Fcontao-ajax-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheimrichhannot%2Fcontao-ajax-bundle/lists"}