Attaching files stopped working octobercms email ajax form - javascript
The below setup has worked until it stopped and right now I'm perplexed why.
I've created a contact form with file attachment in OctoberCMS as below
{{ form_ajax('ContactForm::onSend', { files: 'true', flash: 'true', 'data-request-files':true, 'data-request-validate': true }) }}
<input type="hidden" name="handler" value='onSave'>
<fieldset class="form">
<input type="name" name="name" placeholder="Imię i nazwisko" required>
<input type="email" name="email" placeholder="E-mail" required>
<input type="phone" name="phone" placeholder="Telefon">
<input type="text" name="subject" placeholder="Temat" >
<textarea name="theMessage" placeholder="Zapytanie" required style="width: 100%; height: 140px;"></textarea>
<input type="file" name="fileAttachment" id="fileAttachment" class="inputfile" data-multiple-caption="wybrano {count}" /><label for="fileAttachment">wybierz plik </label><span class='attachmentName'></span>
</fieldset>
<button type="submit" class="send" data-attach-loading>Wyślij</button>
</fieldset>
{{ form_close() }}
The component for sending email
<?php namespace Depcore\Parts\Components;
use Cms\Classes\ComponentBase;
use Mail;
use Lang;
use Flash;
use Input;
use Validator;
use ValidationException;
use Redirect;
use System\Models\File;
class ContactForm extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'depcore.parts::lang.components.contactFormTitle',
'description' => 'depcore.parts::lang.components.contactFormDescription'
];
}
public function defineProperties()
{
return [
'emailTo' => [
'title' => 'depcore.parts::components.emailAddress',
'description' => 'depcore.parts::components.destinationEmailDescription',
'default' => 'zamowienia#kludi.pl',
'validationPattern' => "\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z",
'ValidationMessage' => ''
]
];
}
public function onSend(){
$data = post();
$vars = [
'name' => Input::get('name'),
'subject' => Input::get('subject'),
'phone' => Input::get('phone'),
'theMessage' => Input::get('theMessage'),
'fileAttachment' => Input::file('fileAttachment'),
];
$rules = [
'name' => 'required',
'email' => 'required|email'
];
$validator = Validator::make($data, $rules);
if ($validator->fails())
throw new ValidationException( $validator );
else {
Mail::send('depcore.parts::mail.message', $vars, function( $message ) use ( $vars ) {
// $message->to($this->property('emailTo'));
$message->to('adam#depcore.pl');
if ($vars['fileAttachment']) {
$file = (new File())->fromPost($vars['fileAttachment']);
$message->attach($file['path']);
}
$message->subject($vars['subject']);
Flash::success('Wiadomość została wysłana.');
});
}
}
}
From what I can tell is that the Input::file('fileAttachemnt') is always returning null so I think It could be a problem with the JavaScript framework (?).
This is a weird thing that got me by surprise when working with the project an now Im stuck.
From your code it looks like by mistake you used wrong method
$vars = [
'name' => Input::get('name'),
'subject' => Input::get('subject'),
'phone' => Input::get('phone'),
'theMessage' => Input::get('theMessage'),
'fileAttachment' => Input::get('fileAttachment'), <-- here
];
your code is using this
Input::get('fileAttachemnt');
In Reality it should be this
Input::file('fileAttachemnt');
may be you updated your code and didn't notice that ;)
UPDATE
ok I guess there is some issue with File facade code (new File())
let not use that instead we can directly use file as also you are not saving that file so,
can you replace your code and check it once
$file = (new File())->fromPost($vars['fileAttachment']);
$message->attach($file['path']);
TO
$file = $vars['fileAttachment'];
$pathToFile = $file->getPathname();
$fileName = $file->getClientOriginalName();
$mime = $file->getMimeType()
$message->attach($pathToFile, ['as' => $fileName, 'mime' => $mime]);
then check it, it should work.
MORE UPDATE
I added modified version of ajax framework (added js snippet), code is taken from October cms official git repo, and just removed some part of it so it can override existing code without conflicts.
I would suggest, take this code and create ajax-fw-override.js file then include file on your page or just duplicate layout and add it at very bottom, any how it should come after October default ajax {% framework %}, so it can override its Request.
This is not good solution but considering that you can't update your cms version we can use this. (also by making duplicate layout we make sure it won't affect anywhere else).
I tested it on your site using console and it worked. so just check it out and let me know.
+ function($) {
"use strict";
var Request = function(element, handler, options) {
var $el = this.$el = $(element);
this.options = options || {};
/*
* Validate handler name
*/
if (handler === undefined) {
throw new Error('The request handler name is not specified.')
}
if (!handler.match(/^(?:\w+\:{2})?on*/)) {
throw new Error('Invalid handler name. The correct handler name format is: "onEvent".')
}
/*
* Prepare the options and execute the request
*/
var $form = options.form ? $(options.form) : $el.closest('form'),
$triggerEl = !!$form.length ? $form : $el,
context = {
handler: handler,
options: options
}
$el.trigger('ajaxSetup', [context])
var _event = jQuery.Event('oc.beforeRequest')
$triggerEl.trigger(_event, context)
if (_event.isDefaultPrevented()) return
var loading = options.loading !== undefined ? options.loading : null,
isRedirect = options.redirect !== undefined && options.redirect.length,
useFlash = options.flash !== undefined,
useFiles = options.files !== undefined
if (useFiles && typeof FormData === 'undefined') {
console.warn('This browser does not support file uploads via FormData')
useFiles = false
}
if ($.type(loading) == 'string') {
loading = $(loading)
}
/*
* Request headers
*/
var requestHeaders = {
'X-OCTOBER-REQUEST-HANDLER': handler,
'X-OCTOBER-REQUEST-PARTIALS': this.extractPartials(options.update)
}
if (useFlash) {
requestHeaders['X-OCTOBER-REQUEST-FLASH'] = 1
}
/*
* Request data
*/
var requestData,
inputName,
data = {}
$.each($el.parents('[data-request-data]').toArray().reverse(), function extendRequest() {
$.extend(data, paramToObj('data-request-data', $(this).data('request-data')))
})
if ($el.is(':input') && !$form.length) {
inputName = $el.attr('name')
if (inputName !== undefined && options.data[inputName] === undefined) {
options.data[inputName] = $el.val()
}
}
if (options.data !== undefined && !$.isEmptyObject(options.data)) {
$.extend(data, options.data)
}
if (useFiles) {
requestData = new FormData($form.length ? $form.get(0) : null)
if ($el.is(':file') && inputName) {
$.each($el.prop('files'), function() {
requestData.append(inputName, this)
})
delete data[inputName]
}
$.each(data, function(key) {
requestData.append(key, this)
})
} else {
requestData = [$form.serialize(), $.param(data)].filter(Boolean).join('&')
}
/*
* Request options
*/
var requestOptions = {
url: window.location.href,
crossDomain: false,
context: context,
headers: requestHeaders,
success: function(data, textStatus, jqXHR) {
/*
* Halt here if beforeUpdate() or data-request-before-update returns false
*/
if (this.options.beforeUpdate.apply(this, [data, textStatus, jqXHR]) === false) return
if (options.evalBeforeUpdate && eval('(function($el, context, data, textStatus, jqXHR) {' + options.evalBeforeUpdate + '}.call($el.get(0), $el, context, data, textStatus, jqXHR))') === false) return
/*
* Trigger 'ajaxBeforeUpdate' on the form, halt if event.preventDefault() is called
*/
var _event = jQuery.Event('ajaxBeforeUpdate')
$triggerEl.trigger(_event, [context, data, textStatus, jqXHR])
if (_event.isDefaultPrevented()) return
if (useFlash && data['X_OCTOBER_FLASH_MESSAGES']) {
$.each(data['X_OCTOBER_FLASH_MESSAGES'], function(type, message) {
requestOptions.handleFlashMessage(message, type)
})
}
/*
* Proceed with the update process
*/
var updatePromise = requestOptions.handleUpdateResponse(data, textStatus, jqXHR)
updatePromise.done(function() {
$triggerEl.trigger('ajaxSuccess', [context, data, textStatus, jqXHR])
options.evalSuccess && eval('(function($el, context, data, textStatus, jqXHR) {' + options.evalSuccess + '}.call($el.get(0), $el, context, data, textStatus, jqXHR))')
})
return updatePromise
},
error: function(jqXHR, textStatus, errorThrown) {
var errorMsg,
updatePromise = $.Deferred()
if ((window.ocUnloading !== undefined && window.ocUnloading) || errorThrown == 'abort')
return
/*
* Disable redirects
*/
isRedirect = false
options.redirect = null
/*
* Error 406 is a "smart error" that returns response object that is
* processed in the same fashion as a successful response.
*/
if (jqXHR.status == 406 && jqXHR.responseJSON) {
errorMsg = jqXHR.responseJSON['X_OCTOBER_ERROR_MESSAGE']
updatePromise = requestOptions.handleUpdateResponse(jqXHR.responseJSON, textStatus, jqXHR)
}
/*
* Standard error with standard response text
*/
else {
errorMsg = jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText
updatePromise.resolve()
}
updatePromise.done(function() {
$el.data('error-message', errorMsg)
/*
* Trigger 'ajaxError' on the form, halt if event.preventDefault() is called
*/
var _event = jQuery.Event('ajaxError')
$triggerEl.trigger(_event, [context, errorMsg, textStatus, jqXHR])
if (_event.isDefaultPrevented()) return
/*
* Halt here if the data-request-error attribute returns false
*/
if (options.evalError && eval('(function($el, context, errorMsg, textStatus, jqXHR) {' + options.evalError + '}.call($el.get(0), $el, context, errorMsg, textStatus, jqXHR))') === false)
return
requestOptions.handleErrorMessage(errorMsg)
})
return updatePromise
},
complete: function(data, textStatus, jqXHR) {
$triggerEl.trigger('ajaxComplete', [context, data, textStatus, jqXHR])
options.evalComplete && eval('(function($el, context, data, textStatus, jqXHR) {' + options.evalComplete + '}.call($el.get(0), $el, context, data, textStatus, jqXHR))')
},
/*
* Custom function, requests confirmation from the user
*/
handleConfirmMessage: function(message) {
var _event = jQuery.Event('ajaxConfirmMessage')
_event.promise = $.Deferred()
if ($(window).triggerHandler(_event, [message]) !== undefined) {
_event.promise.done(function() {
options.confirm = null
new Request(element, handler, options)
})
return false
}
if (_event.isDefaultPrevented()) return
if (message) return confirm(message)
},
/*
* Custom function, display an error message to the user
*/
handleErrorMessage: function(message) {
var _event = jQuery.Event('ajaxErrorMessage')
$(window).trigger(_event, [message])
if (_event.isDefaultPrevented()) return
if (message) alert(message)
},
/*
* Custom function, focus fields with errors
*/
handleValidationMessage: function(message, fields) {
$triggerEl.trigger('ajaxValidation', [context, message, fields])
var isFirstInvalidField = true
$.each(fields, function focusErrorField(fieldName, fieldMessages) {
fieldName = fieldName.replace(/\.(\w+)/g, '[$1]')
var fieldElement = $form.find('[name="' + fieldName + '"], [name="' + fieldName + '[]"], [name$="[' + fieldName + ']"], [name$="[' + fieldName + '][]"]').filter(':enabled').first()
if (fieldElement.length > 0) {
var _event = jQuery.Event('ajaxInvalidField')
$(window).trigger(_event, [fieldElement.get(0), fieldName, fieldMessages, isFirstInvalidField])
if (isFirstInvalidField) {
if (!_event.isDefaultPrevented()) fieldElement.focus()
isFirstInvalidField = false
}
}
})
},
/*
* Custom function, display a flash message to the user
*/
handleFlashMessage: function(message, type) {},
/*
* Custom function, redirect the browser to another location
*/
handleRedirectResponse: function(url) {
window.location.href = url
},
/*
* Custom function, handle any application specific response values
* Using a promisary object here in case injected assets need time to load
*/
handleUpdateResponse: function(data, textStatus, jqXHR) {
/*
* Update partials and finish request
*/
var updatePromise = $.Deferred().done(function() {
for (var partial in data) {
/*
* If a partial has been supplied on the client side that matches the server supplied key, look up
* it's selector and use that. If not, we assume it is an explicit selector reference.
*/
var selector = (options.update[partial]) ? options.update[partial] : partial
if ($.type(selector) == 'string' && selector.charAt(0) == '#') {
$(selector.substring(1)).append(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR])
} else if ($.type(selector) == 'string' && selector.charAt(0) == '^') {
$(selector.substring(1)).prepend(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR])
} else {
$(selector).trigger('ajaxBeforeReplace')
$(selector).html(data[partial]).trigger('ajaxUpdate', [context, data, textStatus, jqXHR])
}
}
/*
* Wait for .html() method to finish rendering from partial updates
*/
setTimeout(function() {
$(window)
.trigger('ajaxUpdateComplete', [context, data, textStatus, jqXHR])
.trigger('resize')
}, 0)
})
/*
* Handle redirect
*/
if (data['X_OCTOBER_REDIRECT']) {
options.redirect = data['X_OCTOBER_REDIRECT']
isRedirect = true
}
if (isRedirect) {
requestOptions.handleRedirectResponse(options.redirect)
}
/*
* Handle validation
*/
if (data['X_OCTOBER_ERROR_FIELDS']) {
requestOptions.handleValidationMessage(data['X_OCTOBER_ERROR_MESSAGE'], data['X_OCTOBER_ERROR_FIELDS'])
}
/*
* Handle asset injection
*/
if (data['X_OCTOBER_ASSETS']) {
assetManager.load(data['X_OCTOBER_ASSETS'], $.proxy(updatePromise.resolve, updatePromise))
} else {
updatePromise.resolve()
}
return updatePromise
}
}
if (useFiles) {
requestOptions.processData = requestOptions.contentType = false
}
/*
* Allow default business logic to be called from user functions
*/
context.success = requestOptions.success
context.error = requestOptions.error
context.complete = requestOptions.complete
requestOptions = $.extend(requestOptions, options)
requestOptions.data = requestData
/*
* Initiate request
*/
if (options.confirm && !requestOptions.handleConfirmMessage(options.confirm)) {
return
}
if (loading) loading.show()
$(window).trigger('ajaxBeforeSend', [context])
$el.trigger('ajaxPromise', [context])
return $.ajax(requestOptions)
.fail(function(jqXHR, textStatus, errorThrown) {
if (!isRedirect) {
$el.trigger('ajaxFail', [context, textStatus, jqXHR])
}
if (loading) loading.hide()
})
.done(function(data, textStatus, jqXHR) {
if (!isRedirect) {
$el.trigger('ajaxDone', [context, data, textStatus, jqXHR])
}
if (loading) loading.hide()
})
.always(function(dataOrXhr, textStatus, xhrOrError) {
$el.trigger('ajaxAlways', [context, dataOrXhr, textStatus, xhrOrError])
})
}
Request.DEFAULTS = {
update: {},
type: 'POST',
beforeUpdate: function(data, textStatus, jqXHR) {},
evalBeforeUpdate: null,
evalSuccess: null,
evalError: null,
evalComplete: null
}
/*
* Internal function, build a string of partials and their update elements.
*/
Request.prototype.extractPartials = function(update) {
var result = []
for (var partial in update)
result.push(partial)
return result.join('&')
}
// REQUEST PLUGIN DEFINITION
// ============================
var old = $.fn.request
$.fn.request = function(handler, option) {
var args = arguments
var $this = $(this).first()
var data = {
evalBeforeUpdate: $this.data('request-before-update'),
evalSuccess: $this.data('request-success'),
evalError: $this.data('request-error'),
evalComplete: $this.data('request-complete'),
confirm: $this.data('request-confirm'),
redirect: $this.data('request-redirect'),
loading: $this.data('request-loading'),
flash: $this.data('request-flash'),
files: $this.data('request-files'),
form: $this.data('request-form'),
update: paramToObj('data-request-update', $this.data('request-update')),
data: paramToObj('data-request-data', $this.data('request-data'))
}
if (!handler) handler = $this.data('request')
var options = $.extend(true, {}, Request.DEFAULTS, data, typeof option == 'object' && option)
return new Request($this, handler, options)
}
$.fn.request.Constructor = Request
$.request = function(handler, option) {
return $(document).request(handler, option)
}
// REQUEST NO CONFLICT
// =================
$.fn.request.noConflict = function() {
$.fn.request = old
return this
}
// REQUEST DATA-API
// ==============
function paramToObj(name, value) {
if (value === undefined) value = ''
if (typeof value == 'object') return value
try {
return JSON.parse(JSON.stringify(eval("({" + value + "})")))
} catch (e) {
throw new Error('Error parsing the ' + name + ' attribute value. ' + e)
}
}
}(window.jQuery);
Related
Javascript await for response before returning result JS plugin
I've build a JS plugin that I'm loading into my project. I'm using Gulp 4 to compile everything, and I'm struggling with one of my methods. The method accepts a boolean value of true or false, and if the value of true is specified, then the function that the method calls runs an API request which updates my plugin's options. However, in the project that listens for the response of the method, it doesn't account for the response of the API request. Plugin: (function() { this.MyPlugin = function() { /* ** Settings ** ** Default settings of the plugin that we can overwrite later */ const INBOUND_CONFIG = { features: { settingOne: 'setting one', settingTwo: 'setting two' } } // Create options by extending defaults with the passed in arugments if (arguments[0] && typeof arguments[0] === "object") { this.options = extendDefaults(INBOUND_CONFIG, arguments[0]); } /* ** Compile Detailed Settings ** ** Compile detailed settings */ function compactFeatures (config) { const tlp_aff_id = buildTlpAffID( config.features.cpm_id, config.features.sub_id, config.brand ) return { tlp_aff_id: tlp_aff_id } } /* ** Get inbound options ** ** Get inbound options if we're not on the inbound page so that they're, ** accessible throughout the app */ function getSettings (prefersDB = false) { purgeInboundConfig(false) let inbound if (localStorage.getItem('example--inbound')) { inbound = JSON.parse(encodeAndDecode(localStorage.getItem('example--inbound'), 'decode')) } else { inbound = JSON.parse(localStorage.getItem('example--inbound')) } // prefers db if (prefersDB) { fetchFromDB() return } let features = { ...compare(inbound, INBOUND_CONFIG.features), ...compactFeatures(INBOUND_CONFIG) } INBOUND_CONFIG.features = validateInboundSettings(features) } /* ** Comparison ** ** We need to compare the inbound settings parsed against the default, ** settings that we have. If one from the inbound settings matches a, ** default one, we overwrite it and use the value from the inbound */ function compare (inbound, defaults) { let settings = defaults if (!inbound) return settings for (const [key, value] of Object.entries(inbound)) { for (var option in defaults) { if (defaults.hasOwnProperty(option)) { if (key == option) { settings[key] = convertToOriginalType(value) continue } } } } return settings } /* ** Find Affiliate from DB ** ** Find an affiliate from the DB and fetch the appropriate data ** */ function fetchFromDB () { const query = JSON.parse(encodeAndDecode(localStorage.getItem('example--query'), 'decode')) // construct the data that we need const data = { affiliate: query.cpm_id ?? '', query: query ?? [] } makeInboundHTTPRequest('POST', `https://example.com/api/affiliate/find`, 5000, JSON.stringify(data), function () { const res = JSON.parse(this.response.data.response) INBOUND_CONFIG.features = res.options ?? [] // rerun to get settings getSettings() }) } /* ** HTTP requests ** ** This function will handle HTTP requests made by the plugin to and from, ** the processor. */ function makeInboundHTTPRequest ( type = 'GET', url = '', timeout = 30000, payload = null, callback ) { const xhr = new XMLHttpRequest() xhr.open(type, url, true) xhr.setRequestHeader('Content-Type', 'application/json') xhr.timeout = timeout xhr.ontimeout = () => { callback.apply({ response: { error: true, message: `Timeout exceeded ${timeout}ms`, data: null } }) } xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.response != null) { callback.apply({ response: { error: false, message: 'Success', data: xhr } }) } else { callback.apply({ response: { error: false, message: 'No data', data: null } }) } } } xhr.onerror = () => { callback.apply({ response: { error: true, message: 'Generic error', data: null } }) } if (type === 'POST') { xhr.send(payload) return } xhr.send() } /* ** Get Inbound Options ** ** Get Inbound Config */ MyPlugin.prototype.getSettings = function(prefersDB = false) { getSettings(prefersDB) return INBOUND_CONFIG.features } /* ** Extend Defaults ** ** Utility method to extend defaults with user options */ function extendDefaults(source, properties) { var property; for (property in properties) { if (properties.hasOwnProperty(property)) { source[property] = properties[property]; } } return source; } } /* ** Plugin Loaded ** ** Let the client know that the plugin is working! */ window.postMessage('example--inbound-ready', '*') }()); The above code is incomplete and doesn't contain every function, but purely contains the related one to my question which is: MyPlugin.prototype.getSettings = function(prefersDB = false) { getSettings(prefersDB) return INBOUND_CONFIG.features } Now, if I add a setTimeout to this method, then INBOUND_CONFIG.features has the correct returned response when the value of true is passed to the function, otherwise it doesn't Site /* ** Define our plugins ** ** Define our plugins that we want to enable */ var inbound const plugins = { inbound: { isEnabled: false } } /* ** Enable Plugins ** ** Listen for plugins, if they're included then let's enable them */ const eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent' const eventer = window[eventMethod] const messageEvent = eventMethod == 'attachEvent' ? 'onmessage' : 'message' // Listen to message from child window eventer(messageEvent, function(e) { // eslint-disable-line prefer-arrow-callback const str = e.data.toString() if (str == 'myplugin--inbound-ready') { // Enable the plugin plugins.inbound.isEnabled = true // Load Inbound Plugin if (plugins.inbound.isEnabled) { mypluginInbound = new MyPlugin() inbound = mypluginInbound.getSettings(true) <-- this doesn't contain updated api settings } else { inbound = '' } } }, false) How can I make my getSettings method wait for a response, do I add a set timeout to it, or a promise? I can't use async/await in this project for bigger reasons.
Controller API return to Ajax a response with a status 200 but an empty array
I am totaly stuck with an Ajax request. I m trying to send a response to Ajax with an encoded array to json. Ajax get a status 200 response, but only strings are sent ; not my variables. I wonder if the probleme is due to the asynchronous... When I test with Postman, i can see the full response but Js give to me : {"recipies":[]}. Thanks for your help. Ajax : searchByIngredients: function () { console.log('Search for recipies'); var array = []; var ingredients = $('.span-ingredient'); ingredients.each(function () { array.push(this.innerHTML); }); console.log(array); console.log(Array.isArray(array)); $.ajax( { url: Routing.generate('shopping_list_by_ingredients_ajax'), type: "POST", contentType: "application/json", dataType: "json", data: JSON.stringify(array) }).done(function (response) { if (null !== response) { console.log('ok : ' + JSON.stringify(response)); console.log(typeof(response)); } else { console.log('Error'); } }).fail(function (jqXHR, textStatus, error) { console.log(jqXHR); console.log(textStatus); console.log(error); }); } }; Controller : /** * #Route("/by-ingredient-ajax", name="shopping_list_by_ingredients_ajax", options={"expose"=true}, methods={"GET","POST"}) * * #return JsonResponse|Response */ public function createShopplistByIngredientsAjax(Request $request, RecipeIngredientRepository $recipeIngredientRepository, RecipeRepository $recipeRepository) { if ($request->isMethod('POST')) { $dataIngredients = json_decode($request->getContent()); $dataIngredients = $request->getContent(); // Sorry for this :/ $arrayIngredients = explode(', ', $dataIngredients); $text = str_replace("'", '', $arrayIngredients); $text2 = str_replace('[', '', $text); $text3 = str_replace(']', '', $text2); $recipiesArray = []; // Get matching RecipeIngredient foreach ($text3 as $data) { /** * #return RecipeIngredient() */ $recipeIngredients = $recipeIngredientRepository->findBy([ 'name' => $data, ]); foreach ($recipeIngredients as $recipeIng) { $name = $recipeIng->getName(); $recipeNames = $recipeRepository->findRecipeByKeywwords($name); foreach ($recipeNames as $recipeName) { /** * #return Recipe() */ $recipies = $recipeRepository->findBy([ 'id' => $recipeIng->getRecipe(), ]); // Built array with names & ids of recipies foreach ($recipies as $key => $recipe) { $recipeName = $recipe->getName(); $recipeId = $recipe->getId(); $recipiesArray[] = ['name' => $recipeName]; $recipiesArray[] = ['id' => $recipeId]; } } } } $response = new Response(); $response->setContent(json_encode([ 'recipies' => $recipiesArray, ])); $response->headers->set('Content-Type', 'application/json'); return $response; } return new Response( 'Something wrong...', Response::HTTP_OK, ['content-type' => 'text/html'] ); Repository : /** * #return Recipe[] Returns an array of Recipe objects */ public function findRecipeByKeywwords($value) { return $this->createQueryBuilder('r') ->andWhere('r.name LIKE :val') ->setParameter('val', '%'.$value.'%') ->orderBy('r.id', 'ASC') ->setMaxResults(10) ->getQuery() ->getArrayResult(); }
composer require jms/serializer-bundle After you have installed the package, you just need to add the bundle to your AppKernel.php file: // in AppKernel::registerBundles() $bundles = array( // ... new JMS\SerializerBundle\JMSSerializerBundle(), // ... ); The configured serializer is available as jms_serializer service: $serializer = $container->get('jms_serializer'); $json=$serializer->serialize(['recipies' => $recipiesArray], 'json'); return new JsonResponse($json,200);
Symfony 2.1 $response = new Response(json_encode(array('recipies' => $recipiesArray))); $response->headers->set('Content-Type', 'application/json'); return $response; Symfony 2.2 and higher You have special JsonResponse class, which serialises array to JSON: use Symfony\Component\HttpFoundation\JsonResponse; // // return new JsonResponse(array('recipies' => $recipiesArray)); https://symfony.com/doc/current/components/http_foundation.html
Reduce choice list in select2 dropdown (when searching)
When I use select2, the list of choice doesn't reduce while searching, which is pretty annoying when I have more than 50 choices.. I use symfony4 framework, with a route providing my tags that are in a DB: /** * #Route("/miseenpage/keywords.json", name="keywords", defaults={"_format": "json"}) */ Then I init my select2 with this code provided by the symfony Tags plugin: $( document ).ready(function() { initTags($('input[name$="[tagsText]"]')); }); // TAGS function initTags($input) { if($input.length) { $input.attr('type', 'hidden').select2({ width: '85%', tags: true, tokenSeparators: [","], createSearchChoice: function (term, data) { if ($(data).filter(function () { return this.text.localeCompare(term) === 0; }).length === 0) { return { id: term, text: term }; } }, multiple: true, ajax: { url: $input.data('ajax'), dataType: "json", data: function (term, page) { return { q: term }; }, results: function (data, page) { return { results: data }; } }, initSelection: function (element, callback) { var data = []; function splitVal(string, separator) { var val, i, l; if (string === null || string.length < 1) { return []; } val = string.split(separator); for (i = 0, l = val.length; i < l; i = i + 1) { val[i] = $.trim(val[i]); } return val; } $(splitVal(element.val(), ",")).each(function () { data.push({ id: this, text: this }); }); callback(data); } }); } } Here is the result in my webpage before searching: Here is after searching: As you can see, the list is still the same, just the result is underlined. I'd like to filter the result depending on what I search, so I don't have to scroll all the way until I find my underlined term. As it is the basic working of Select2, I guess there is an configuration problem in my JS.
I instanciated this value on load: let currentSearch = false; Then I changed the "AJAX" section of my JS so it loops around the list with the searched value: ajax: { url: $input.data('ajax'), dataType: "json", data: function (term) { currentSearch = term; return { q: term }; }, results: function (data) { let returnTab = []; data.forEach(function(e) { if(e.text.includes(currentSearch)){ returnTab.push(e); } }); return { results: returnTab }; } }, I'm pretty sure there is a cleaner/simpler way of doing this, but for moment it's a quickfix.
Just a basic example with some code to show how we use Select2 (v4.0.3, 3.5 may be quite different) with Ajax On a twig, we have for example : $("#users").css('width', '100%').select2({ minimumInputLength: 3, ajax: { url: "{{ path('api_search_user') }}", dataType: 'json', delay: 500, data: function (params) { return { needle: params.term // Here we send user input to the controller }; }, processResults: function (data) { return { results: data }; }, cache: false } }); And we have a controller with a function to search for users : /** * #Route("/search_users", name="api_search_users") * #param Request $request * #return JsonResponse */ public function searchUsersAction(Request $request) { $needle = $request->get('needle'); // Here we retrieve user input $users = $this->get(User::class)->searchUsers($needle); return new JsonResponse($users ); } EDIT following you last comment This value is not valid That's because your value isn't part of the select when the formbuilder add the field. You must add an addEventListener on PRE_SUBMIT to dinamically add this value. You can do it this way : // Define form modifier $usersFormModifier = function (FormInterface $form, $users) use ($options) { $choices = array(); if(is_array($users)) { $choices = $users; } $form->add( 'users', EntityType::class, array( 'label' => 'conversation.form.users', 'multiple' => true, 'class' => 'AppBundle\Entity\Security\User', 'choices' => $choices, 'choice_label' => function (User $user) { return $user->getLastName() . " " . $user->getFirstName(); }, 'attr' => array( 'placeholder' => 'default.search_person' ) ) ); }; // On PRE_SET_DATA, we load users from our object (which only contains their IDs) $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($usersFormModifier) { $usersFormModifier($event->getForm(), $event->getData()->getUsers()->toArray()); } ); // On PRE_SUBMIT, we add the options, so the select will accept newly added values $builder->addEventListener( FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($usersFormModifier) { $data = $event->getData(); $users = null; if(isset($data['users'])) { $users = $this->entityManager->getRepository('AppBundle:Security\User')->findBy(array('id' => $data['users'])); } $usersFormModifier($event->getForm(), $users); } ); As I said at the beginning, if you don't have so muchs tags, and they don't evolve regularly, you can construct your select with all your tags and avoid all the Ajax part of the Select2 javascript code ! (I think it will be easier and sufficient for your case)
Anonymous function not working
I am getting the error "Uncaught TypeError: $.fblogin is not a function" when I call $.fblogin({ fbId: 'xxxxxxxxxx', permissions: 'email,user_birthday', success: function (data) { console.log('User birthday' + data.birthday + 'and email ' + data.email); } }); The function is defined bellow: (function (factory) { if (typeof define === 'function' && define.amd) { // AMD define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS factory(require('jquery')); } else { // Browser globals factory(jQuery); } }(function ($) { $.extend({ /** * fblogin * #property {object} options - settings for fblogin plugin. * * Required: * options.fbId {string} - the Facebook app id * * Optional: * options.permissions {string} - a comma seperated list of FB permissions. See http://bit.ly/1plqJSs * options.fields {string} - a comma seperated list of field ids. See http://bit.ly/1plrevO * options.success {function} - callback that will be triggered when data is successfully returned from FB. * options.error {function} - callback that will be triggered by any errors. */ fblogin: function (options) { /** * Private Props * #property {object} __ - add private module functions here. * #property {object} isSdkLoaded - a flag for when the FB SDK has loaded. * #property {object} isFbInitiated - a flag for when FB.init has been called. * #property {object} $dfd - stores an instance of jquery Deferred. */ var __, isSdkLoaded, isFbInitiated, $dfd; options = options || {}; isSdkLoaded = false; isFbInitiated = false; $dfd = $.Deferred(); // PRIVATE FUNCTIONS __ = { init: function () { // FB ID is required if (!options.fbId) { throw new Error('Required option "fbId" is missing!'); } options.permissions = options.permissions || ''; options.fields = options.fields || ''; options.success = options.success || function(){}; options.error = options.error || function(){}; __.listenForFbAsync(); }, listenForFbAsync: function () { if (window.fbAsyncInit) { var notMyFunction = window.fbAsyncInit; } // listen for FB SDK load window.fbAsyncInit = function() { __.initFB(); isSdkLoaded = true; if (notMyFunction) { notMyFunction(); } }; if (isSdkLoaded || window.FB) { window.fbAsyncInit(); return; } }, initFB: function () { if (!isFbInitiated) { window.FB.init({ appId : options.fbId, cookie : true, xfbml : true, version : 'v2.0' }); isFbInitiated = true; } $dfd.notify({status: 'init.fblogin'}); }, loginToFB: function () { window.FB.login(function(response) { if (response.authResponse) { $dfd.notify({ status: 'authenticate.fblogin', data: response }); } else { // mimic facebook sdk error format $dfd.reject({ error: { message: 'User cancelled login or did not fully authorize.' } }); } }, { scope: options.permissions, return_scopes: true }); }, getFbFields: function (accessToken) { FB.api('/me', {fields: options.fields}, function(response) { if (response && !response.error) { $dfd.resolve(response); } else { $dfd.reject(response); } }); } }; // This monitors the FB login progresssion // 1. Init FB // 2. FB.login // 3. Get user data $dfd.progress(function (response) { if( response.status === 'init.fblogin' ) { __.loginToFB(); } else if( response.status === 'authenticate.fblogin' ) { __.getFbFields(response.data.authResponse.accessToken); } else { dfd.reject(); } }); // point callbacks at deffereds $dfd.done(options.success); $dfd.fail(options.error); // here we go! __.init(); return $dfd; } }); }));
It seems your jQuery is getting loaded again after the above script. You can check in console or move the script down in the page and verify.
Jquery Ajax prevent fail in a deferred sequential loop
So, I'm chaining together sequential ajax, to load an array of urls in order. Originally I used .then() instead of .always(), and either way it works fine - provided that all urls existed. However, since there's a possibility of missing files, I wanted to compensate for that, and then finally, inform the user of which files were missing, in order to make it easier to rectify. However, the problem is, on a missing file/404, the code executes, like it should, but then exits the loop, preventing any further ajax calls. So I figure, I need some way of either handling the fail() and forcing a success regardless, or some other way of overriding the default behavior on a 404, so it continues progressing through the loop. Unfortunately, the closest Google results, were how to do the opposite (force a failure on success). var missing=[]; uLoadList.reduce(function(prev, cur, index) { return prev.then(function(data) { return $.ajax("/wiki/"+cur).always(function(data) { var temp = $('#mw-content-text',data); temp = $('pre',temp); if(temp.length > 0) { //handle success }else{ //handle failure missing.push(cur); } }); }); }, $().promise()).done(function() { if(missing.length > 0) { //notify of missing objects } //continue on. }); One final note, to alleviate confusion: the URLs, and the script itself, are on a MediaWiki site - so even if a 404 is returned, there will always be page content, and will contain the element with the id of "mw-content-text".
Try (function ($) { $.when.all = whenAll; function whenAll(arr) { "use strict"; var deferred = new $.Deferred(), args = !! arr ? $.isArray(arr) ? arr : Array.prototype.slice.call(arguments) .map(function (p) { return p.hasOwnProperty("promise") ? p : new $.Deferred() .resolve(p, null, deferred.promise()) }) : [deferred.resolve(deferred.promise())], promises = { "success": [], "error": [] }, doneCallback = function (res) { promises[this.state() === "resolved" || res.textStatus === "success" ? "success" : "error"].push(res); return (promises.success.length + promises.error.length) === args.length ? deferred.resolve(promises) : res }, failCallback = function (res) { // do `error` notification , processing stuff // console.log(res.textStatus); promises[this.state() === "rejected" || res.textStatus === "error" ? "error" : "success"].push(res); return (promises.success.length + promises.error.length) === args.length ? deferred.resolve(promises) : res }; $.map(args, function (promise, index) { return $.when(promise).always(function (data, textStatus, jqxhr) { return (textStatus === "success") ? doneCallback.call(jqxhr, { data: data, textStatus: textStatus ? textStatus : jqxhr.state() === "resolved" ? "success" : "error", jqxhr: jqxhr }) : failCallback.call(data, { data: data, textStatus: textStatus, jqxhr: jqxhr }) }) }); return deferred.promise() }; }(jQuery)); // returns `Object { // success: Array[/* success responses*/], // error: Array[/* error responses */] // }` // e.g., var request = function (url, data) { return $.post(url, { json: JSON.stringify(data) }) }, settings = [ ["/echo/json/", "success1"], // `success` ["/echo/jsons/", "error1"], // `error` ["/echo/json/", "success2"], // `success` ["/echo/jsons/", "error2"], // `error` ["/echo/json/", "success3"] // `success` ]; $.when.all( $.map(settings, function (p) { return request.apply($, p) }) ) .then(function (data) { console.log(data); // filter , process responses $.each(data, function(key, value) { if (key === "success") { results.append( "\r\n" + key + ":\r\n" + JSON.stringify(value, null, 4) ) } else { results.append( "\r\n" + key + ":\r\n" + JSON.stringify( value.map(function(v, k) { v.data.responseText = $(v.data.responseText) .filter("title, #summary, #explanation") .text().replace(/\s+/g, " "); return v }) , null, 4) ) } }) }, function (e) { console.log("error", e) }); jsfiddle http://jsfiddle.net/guest271314/620p8q8h/