I’m trying to get my Ajax-Function to call a certain controller inside my Typo3 extension.
After several attempts, I decided to go with the same Ajax-Dispatcher that powermail uses. It does show an output, but it completely ignores the supposed controller and action.
There are two Controllers in my Extension and so far three actions:
Category=>list
Family=>list,compare
PHP:
<?php
namespace Vendor\Myextension\Utility;
use \TYPO3\CMS\Core\Utility\GeneralUtility;
class AjaxDispatcher {
/**
* configuration
*
* #var \array
*/
protected $configuration;
/**
* bootstrap
*
* #var \array
*/
protected $bootstrap;
/**
* Generates the output
*
* #return \string rendered action
*/
public function run() {
return $this->bootstrap->run('', $this->configuration);
}
/**
* Initialize Extbase
*
* #param \array $TYPO3_CONF_VARS The global array. Will be set internally
*/
public function __construct($TYPO3_CONF_VARS) {
$this->configuration = array(
'pluginName' => 'my_plugin',
'vendorName' => 'Vendor',
'extensionName' => 'myextension',
'controller' => 'Family',
'action' => 'compare',
'mvc' => array(
'requestHandlers' => array(
'TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler' => 'TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler'
)
),
'settings' => array()
);
$_POST['request']['action'] = 'compare';
$_POST['request']['controller'] = 'Family';
$this->bootstrap = new \TYPO3\CMS\Extbase\Core\Bootstrap();
$userObj = \TYPO3\CMS\Frontend\Utility\EidUtility::initFeUser();
$pid = (GeneralUtility::_GET('id') ? GeneralUtility::_GET('id') : 1);
$GLOBALS['TSFE'] = GeneralUtility::makeInstance(
'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController',
$TYPO3_CONF_VARS,
$pid,
0,
TRUE
);
$GLOBALS['TSFE']->connectToDB();
$GLOBALS['TSFE']->fe_user = $userObj;
$GLOBALS['TSFE']->id = $pid;
$GLOBALS['TSFE']->determineId();
$GLOBALS['TSFE']->getCompressedTCarray();
$GLOBALS['TSFE']->initTemplate();
$GLOBALS['TSFE']->getConfigArray();
$GLOBALS['TSFE']->includeTCA();
}
}
$eid = GeneralUtility::makeInstance('Vendor\Myextension\Utility\AjaxDispatcher', $GLOBALS['TYPO3_CONF_VARS']);
echo $eid->run();
Ajax:
var read = 'string';
var requestData = {'value': read};
var currentUrl = window.location;
$.ajax({
url: currentUrl,
type: 'POST',
data: {
eID: "ajaxDispatcherMyextension",
request: {
controller: 'Family',
action: 'compare',
arguments: {
'test': requestData
}
}
},
dataType: 'html',
success: function(success) {
console.log('success ' + success);
$('#test').html(success);
}
});
Instead of showing family->compare the Ajax puts out category->compare. I don’t understand why.
Can someone please help? I'm working on this problem for over 2 days now...
I don't know exactly what you mean. If you want to trigger an AJAX request while clicking on a link you can do the following in the belonging view of your action. In this example the script will output the AJAX response.
1) Create a ViewHelper containing something like that:
$uriBuilder = $this->controllerContext->getUriBuilder();
$uri = $uriBuilder->uriFor(
'title',
array("param1" => $value1, "param2" => $value2),
'<controllerName>',
'<extKey>',
'<pluginName>');
return '<a id="link" href="'.$uri.'">';
2) Call the ViewHelper in your view template and add a output container div with an id.
3) Implement the JavaScript for AJAX call
function loadurl(dest, obj) {
try {
xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
console.log(e);
}
xmlhttp.onreadystatechange = function() {
triggered(obj);
};
xmlhttp.open("GET", dest);
xmlhttp.send(null);
}
function triggered(obj) {
if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) {
document.getElementById(obj).innerHTML = xmlhttp.responseText;
}
}
window.addEventListener("load", function() {
var item = document.getElementsById('link');
item.addEventListener('click', function(event) {
event.preventDefault();
var href = this.childNodes[1].getAttribute("href");
loadurl(href, 'idOfOutputContainer');
}
}
It's not implemented in 6.2 as noted in my bug report: action and controller not used in RequestBuilder.php:loadDefaultValues
Related
I currently have a working controller that extends WP_REST_Controller in a file under the current theme. These are being called using jQuery ajax. (all code below)
The issue I am facing is that I receive this error ONLY when accessing with a mobile device.
{"code": "rest_no_route", "message": "No route was found matching the URL and request method" "data": {"status": 404}}
settings -> permalinks -> save changes
tried using controller namespace "api/v1" and "wp/v2"
javascript
function getAllClients() {
jQuery.ajax({
url: "http://myurl.com/index.php/wp-json/wp/v2/get_all_clients",
type: "GET",
data: { /*data object*/},
success: function (clientList) {
// success stuff here
},
error: function (jqXHR, textStatus, errorThrown) {
alert(jqXHR.statusText);
}
})
}
api/base.php
<?php
class ApiBaseController extends WP_REST_Controller
{
//The namespace and version for the REST SERVER
var $my_namespace = 'wp/v';
var $my_version = '2';
public function register_routes()
{
$namespace = $this->my_namespace . $this->my_version;
register_rest_route(
$namespace,
'/get_all_clients',
array(
array(
'methods' => 'GET',
'callback' => array(new ApiDefaultController('getAllClients'), 'init'),
)
)
);
$ApiBaseController = new ApiBaseController();
$ApiBaseController->hook_rest_server();
api/func.php
<?php
class ApiDefaultController extends ApiBaseController
{
public $method;
public $response;
public function __construct($method)
{
$this->method = $method;
$this->response = array(
// 'Status' => false,
// 'StatusCode' => 0,
// 'StatusMessage' => 'Default'
// );
}
private $status_codes = array(
'success' => true,
'failure' => 0,
'missing_param' => 150,
);
public function init(WP_REST_Request $request)
{
try {
if (!method_exists($this, $this->method)) {
throw new Exception('No method exists', 500);
}
$data = $this->{$this->method}($request);
$this->response['Status'] = $this->status_codes['success'];
$this->response['StatusMessage'] = 'success';
$this->response['Data'] = $data;
} catch (Exception $e) {
$this->response['Status'] = false;
$this->response['StatusCode'] = $e->getCode();
$this->response['StatusMessage'] = $e->getMessage();
}
return $this->response['Data'];
}
public function getAllClients()
{
// db calls here
return json_encode($stringArr,true);
}
}
These are registered in the Functions.php file
require get_parent_theme_file_path('api/base.php');
require get_parent_theme_file_path('api/func.php');
Turns out the issue was a plugin my client installed called "oBox mobile framework" that was doing some weird routing behind the scenes. Disabling it resolved the issue, though there is probably a way to hack around this and get both to play together.
I'm trying to upload a file (Excel sheet) from a front-end app build with VueJS to an API build with Laravel 5.5. I've some form request validation which says to me that The file field is required. So the file doesn't get uploaded to my API at all.
VueJS file upload:
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
if (files.length <= 0) {
return;
}
this.upload(files[0]);
},
upload(file) {
this.form.file = file;
this.form.put('courses/import')
.then((response) => {
alert('Your upload has been started. This can take some time.');
})
.catch((response) => {
alert('Something went wrong with your upload.');
});
}
this.form is a Form class copied from this project but the data() method returns a FormData object instead of a object.
data() method:
data() {
let data = new FormData();
for (let property in this.originalData) {
data.append(property, this[property]);
}
return data;
}
The route:
FormRequest rules:
public function rules()
{
return [
'file' => 'required',
];
}
If I look to the Network tab in Chrome DevTools it does seem that the request is send correctly: (image after click).
I've tried a lot of things, like sending the excel as base64 to the api. But then I couldn't decode it correctly. So now I'm trying this, but I can't solve the problem.
Edit (controller function)
public function update(ImportRequest $request, CoursesImport $file)
{
$courses = $file->handleImport();
dispatch(new StartCourseUploading($courses, currentUser()));
$file->delete();
return ok();
}
You are getting 422 as status, I hope you aware of this meaning as validation failed according to your Responses class's rule.
In laravel, PUT method won't accept file uploads, so you need to change from PUT to POST.
this.form.post('courses/import')
.then((response) => {
alert('Your upload has been started. This can take some time.');
})
.catch((response) => {
alert('Something went wrong with your upload.');
});
Don't forget to update your routes of laravel.
Other considerations:
Make sure you added property kind of following code
data: {
form: new Form({
file:null
})
},
Check browser request weather sending form data properly or not , I added sample screen shot
My code samples
class Errors {
/**
* Create a new Errors instance.
*/
constructor() {
this.errors = {};
}
/**
* Determine if an errors exists for the given field.
*
* #param {string} field
*/
has(field) {
return this.errors.hasOwnProperty(field);
}
/**
* Determine if we have any errors.
*/
any() {
return Object.keys(this.errors).length > 0;
}
/**
* Retrieve the error message for a field.
*
* #param {string} field
*/
get(field) {
if (this.errors[field]) {
return this.errors[field][0];
}
}
/**
* Record the new errors.
*
* #param {object} errors
*/
record(errors) {
this.errors = errors;
}
/**
* Clear one or all error fields.
*
* #param {string|null} field
*/
clear(field) {
if (field) {
delete this.errors[field];
return;
}
this.errors = {};
}
}
class Form {
/**
* Create a new Form instance.
*
* #param {object} data
*/
constructor(data) {
this.originalData = data;
for (let field in data) {
this[field] = data[field];
}
this.errors = new Errors();
}
/**
* Fetch all relevant data for the form.
*/
data() {
let data = new FormData();
for (let property in this.originalData) {
data.append(property, this[property]);
}
return data;
}
/**
* Reset the form fields.
*/
reset() {
for (let field in this.originalData) {
this[field] = '';
}
this.errors.clear();
}
/**
* Send a POST request to the given URL.
* .
* #param {string} url
*/
post(url) {
return this.submit('post', url);
}
/**
* Send a PUT request to the given URL.
* .
* #param {string} url
*/
put(url) {
return this.submit('put', url);
}
/**
* Send a PATCH request to the given URL.
* .
* #param {string} url
*/
patch(url) {
return this.submit('patch', url);
}
/**
* Send a DELETE request to the given URL.
* .
* #param {string} url
*/
delete(url) {
return this.submit('delete', url);
}
/**
* Submit the form.
*
* #param {string} requestType
* #param {string} url
*/
submit(requestType, url) {
return new Promise((resolve, reject) => {
axios[requestType](url, this.data())
.then(response => {
this.onSuccess(response.data);
resolve(response.data);
})
.catch(error => {
this.onFail(error.response.data);
reject(error.response.data);
});
});
}
/**
* Handle a successful form submission.
*
* #param {object} data
*/
onSuccess(data) {
alert(data.message); // temporary
this.reset();
}
/**
* Handle a failed form submission.
*
* #param {object} errors
*/
onFail(errors) {
this.errors.record(errors);
}
}
var app = new Vue({
el: '#app',
data: {
form: new Form({
file: ''
})
},
methods: {
onSubmit() {
this.form.post('/projects')
.then(response => alert('Wahoo!'));
},
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
if (files.length <= 0) {
return;
}
this.upload(files[0]);
},
upload(file) {
this.form.file = file;
this.form.post('courses/import')
.then((response) => {
alert('Your upload has been started. This can take some time.');
})
.catch((response) => {
alert('Something went wrong with your upload.');
});
}
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.2.3/css/bulma.css">
<style>
body {
padding-top: 40px;
}
</style>
</head>
<body>
<div id="app" class="container">
<form method="POST" action="/projects" #submit.prevent="onSubmit" #keydown="form.errors.clear($event.target.name)">
<input type="file" name="image" #change="onFileChange">
<div class="control">
<button class="button is-primary" :disabled="form.errors.any()">Create</button>
</div>
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.js"></script>
<script src="https://unpkg.com/vue#2.1.6/dist/vue.js"></script>
</body>
</html>
Related Links
https://github.com/laravel/framework/issues/17560
If you store your data in an associative array like data:
var formData = new FormData();
Object.keys(data).forEach(function(key, index){
if(Array.isArray(data[key])){
formData.append(key + '[]', data[key]);
} else {
formData.append(key, data[key]);
}
});
formData.append('file', document.getElementById('file_id').files[0]);
axios.post('path', formData).then( res => {
console.log(res);
}).catch(res => {
console.log(res);
});
EDIT: SOLVED
Apparently this plugin, was having some problem missing the request headers. The solution was adding
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
To the .htaccess file to make Authorizaion variable available as this issue report says:
https://github.com/yiisoft/yii2/issues/6631
I'm currently working with Yii2 and using this OAuth2 plugin (Filsh/yii2-oauth2-server) for login and to work with tokens from a mobile HTML5 app.
I've configured everything, and it's retrieving the token, but when I try to send the token via POST it throws an error.
I start by calling send() to retrieve the token.
function send(){
var url = "http://www.server.org/app/api/oauth2/rest/token";
var data = {
'grant_type':'password',
'username':'user',
'password':'pass',
'client_id':'clientid',
'client_secret':'clientsecret',
};
$.ajax({
type: "POST",
url: url,
data: data,
success:function(data){
console.log(data);
token = data.access_token;
},
})
};
Then when I perform this a call to createuser().
function createuser(){
var url = "http://www.server.org/app/api/v1/users/create";
var data = {
'callback':'asdf',
'username': 'user',
'password':'pass',
'first_name':'name',
'last_name':'lastname'
};
$.ajax({
type: "POST",
url: url,
data: data,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
success:function(r){
console.log(r);
},
});
}
It returns
Unauthorized: You are requesting with an invalid credential
When I change to GET instead, it works fine.
This is my controller, I'm already using:
['class' => HttpBearerAuth::className()],
['class' => QueryParamAuth::className(), 'tokenParam' => 'accessToken'],
As authentication method.
<?php
namespace app\api\modules\v1\controllers;
use Yii;
use app\models\OauthUsers;
use yii\rest\ActiveController;
use yii\web\Response;
use yii\helpers\ArrayHelper;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;
use filsh\yii2\oauth2server\filters\ErrorToExceptionFilter;
use filsh\yii2\oauth2server\filters\auth\CompositeAuth;
class UsersController extends \yii\web\Controller
{
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'class' => CompositeAuth::className(),
'authMethods' => [
['class' => HttpBearerAuth::className()],
['class' => QueryParamAuth::className(), 'tokenParam' => 'accessToken'],
]
],
'exceptionFilter' => [
'class' => ErrorToExceptionFilter::className()
],
'class' => \yii\filters\ContentNegotiator::className(),
]);
}
/**
* Creates a new model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new OauthUsers;
try {
if ($model->load($_POST) && $model->save()) {
return $this->redirect(Url::previous());
} elseif (!\Yii::$app->request->isPost) {
$model->load($_GET);
}
} catch (\Exception $e) {
$msg = (isset($e->errorInfo[2]))?$e->errorInfo[2]:$e->getMessage();
$model->addError('_exception', $msg);
}
return "true";
}
}
This is my configuration object
'oauth2' => [
'class' => 'filsh\yii2\oauth2server\Module',
'tokenParamName' => 'accessToken',
'tokenAccessLifetime' => 3600 * 24,
'storageMap' => [
'user_credentials' => 'app\models\OauthUsers',
],
'grantTypes' => [
'user_credentials' => [
'class' => 'OAuth2\GrantType\UserCredentials',
],
'refresh_token' => [
'class' => 'OAuth2\GrantType\RefreshToken',
'always_issue_new_refresh_token' => true
]
]
]
This is class OauthUsers
<?php
namespace app\models;
use Yii;
use \app\models\base\OauthUsers as BaseOauthUsers;
/**
* This is the model class for table "oauth_users".
*/
class OauthUsers extends BaseOauthUsers
implements \yii\web\IdentityInterface,\OAuth2\Storage\UserCredentialsInterface
{
/**
* #inheritdoc
*/
public static function findIdentity($id) {
$dbUser = OauthUsers::find()
->where([
"id" => $id
])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* #inheritdoc
*/
public static function findIdentityByAccessToken($token, $userType = null) {
$at = OauthAccessTokens::find()
->where(["access_token" => $token])
->one();
$dbUser = OauthUsers::find()
->where(["id" => $at->user_id])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* Implemented for Oauth2 Interface
*/
public function checkUserCredentials($username, $password)
{
$user = static::findByUsername($username);
if (empty($user)) {
return false;
}
return $user->validatePassword($password);
}
/**
* Implemented for Oauth2 Interface
*/
public function getUserDetails($username)
{
$user = static::findByUsername($username);
return ['user_id' => $user->getId()];
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username) {
$dbUser = OauthUsers::find()
->where([
"username" => $username
])
->one();
if (!count($dbUser)) {
return null;
}
return new static($dbUser);
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->id;
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
return $this->password === $password;
}
}
I've changed createuser too, but still receiving a 401. I'm not sure it is passing through findIdentityByAccessToken (access_token is in a different table than oauth users, thats why I'm querying it first).
Any thoughts?
I don't know the plugin you are using but what I know is that you can use the Yii2 HttpBearerAuth filter when implementing OAuth 2.0 which means using the HTTP Bearer token. And that token is typically passed with the Authorization header of the HTTP request instead of the body request and it usually looks like :
Authorization: Bearer y-9hFW-NhrI1PK7VAXYdYukwWVrNTkQ1
The idea is about saving the token you received from the server somewhere (should be a safe place) and include it in the headers of the requests that requires server authorization (maybe better explained here, here or here) So the JS code you are using to send a POST request should look more like this :
function createuser(){
var url = "http://www.server.org/app/api/v1/users/create";
var data = {
'callback':'cb',
'username': 'user',
'password':'pass',
'first_name':'name',
'last_name':'lastname'
};
$.ajax({
type: "POST",
url: url,
data: data,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
success:function(r){
console.log(r);
},
});
}
Also check in the User class (the one defined in your config files and responsible of authenticating a user) check the implementation of the findIdentityByAccessToken() method. it should usually look like this (see Yii docs for more details) :
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['auth_key' => $token]);
}
This is the function that will receive the token and it should return null when authentication should fail (by default findOne() returns null when no record is found) or returns a User instance to register it and make it accessible within Yii::$app->user->identity or Yii::$app->user->id anywhere inside your app so you can implement whatever logic you need inside it like checking the validity of an access token or its existence in a different table.
Apparently this plugin (Filsh/yii2-oauth2-server), was having some problem missing the request headers. The solution was adding
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
To the .htaccess file to make Authorizaion variable available as this says:
https://github.com/yiisoft/yii2/issues/6631
I'm having an issue with Knp, an AJAX request, and a filter. I think I'm doing something very wrong here, but I am not sure how exactly KnpPaginator works internally, and I don't have the time to figure it out on this project.
Anyway, basically, my page has an embedded controller which renders a table on the page. When paginator is called from twig, it returns the route to the container page, which results in paginator failing to work with my GET requests to that uri.
I'm not sure if any of you have come across this - I'm happy to listen if there is a better solution to the problem (I'm quite sure there is). Here is my code:
CONTROLLER
/**
* Just a shell page
*
* #Route("/postmanagement/index")
* #Template()
*
* #return array
*/
public function indexAction()
{
$form = $this->createForm(new FilterPostsType(), null, array(
'action' => $this->generateUrl('myblog_admin_postmanagement_filterposts'),
'method' => 'POST'
)
);
return array(
'form' => $form->createView()
);
}
/**
* Returns active posts and comments
*
* #param Request $request
*
* #return array
*/
public function defaultAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('ModelBundle:Post')->findBy(array(
'active' => true
)
);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate($posts, $request->query->get('page', 1), 10);
return $this->render("AdminBundle:PostManagement:_ajax-panel.html.twig", array(
'isPost' => true,
'posts' => $posts,
'pagination' => $pagination
)
);
}
/**
* #param Request $request
*
* #Route("/postmanagement/filter")
*
* #return array
*/
public function filterPostsAction(Request $request)
{
$form = $this->createForm(new FilterPostType(), null, array(
'action' => $this->generateUrl('myblog_admin_postmanagement_filterposts'),
'method' => 'POST'
)
);
// if ($request->isMethod('POST')) {
$posts = null;
$form->handleRequest($request);
$data = $form->getData();
$posts = $this->get('myblog.admin_manager')->filterPosts($data);
switch ($data['type']) {
case 'post':
$isPost = true;
$isComment = false;
break;
case 'comment':
$isPost = false;
$isComment = true;
break;
}
// }
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate($posts, $request->query->get('page', 1), $data['maxresults']);
if (is_null($posts)) {
return new NotFoundHttpException();
} else {
return $this->render('AdminBundle:PostManagement:_ajax-panel.html.twig', array(
'posts' => $posts,
'isPost' => $isPost,
'isComment' => $isComment,
'pagination' => $pagination
)
);
}
}
I'm not posting the view here, since it is a simple render(controller(MyBundle:Controller:myAction)). As you can see, there is a form I'm submitting on the page, to filter the posts. That also poses a problem, since it seems paginator doesn't keep the query after I've run it through the filter.
Thanks for any help! I would love if someone has done this before and has come up with a better solution than my rather convoluted one (which also involves too many queries for my liking).
I figured it out.
If anyone else would like to paginate with InfiScr trigger + KNPPaginatorBundle + filter (PHP), use this JS:
/**
* Load more pagination handler
*/
var AjaxPagination = function (options) {
AjaxProt.call(this, options);
this.filter = options.filter;
this.toJoinEl = options.toJoinEl;
this.containerEl = options.containerEl;
this.navContainer = options.navContainer;
this.nextSelector = options.nextSelector;
this.uri = options.uri;
};
AjaxPagination.prototype = Object.create(AjaxProt.prototype);
AjaxPagination.prototype.init = function () {
var thisObj = this,
uri = thisObj.uri;
$(thisObj.navContainer).hide();
$(document).on(thisObj.event, thisObj.targetEl, function (e) {
e.preventDefault();
thisObj.ajaxRequest(uri);
});
};
AjaxPagination.prototype.ajaxRequest = function (uri) {
var thisObj = this,
page = $(this.nextSelector).attr('href').match(/\d+$/);
$('#filter_bets_page').val(page);
var data = $(this.filter).serialize(),
method = this.method;
console.log(data);
$.ajax({
url: uri,
data: data,
type: method,
success: function (data) {
thisObj.infiScrCallback(data);
}
});
};
AjaxPagination.prototype.infiScrCallback = function(data) {
var thisObj = this;
$(thisObj.navContainer).remove();
if (thisObj.toJoinEl) {
var filteredContent = $("<div>").append( $.parseHTML( data ) ).find( '.findable');
var newPagination = $("<div>").append( $.parseHTML( data ) ).find( 'div.pagination-hidden' );
$(thisObj.toJoinEl).append(filteredContent);
$(thisObj.containerEl).append(newPagination);
} else {
$(thisObj.containerEl).append(data).fadeIn();
}
if (!$(thisObj.nextSelector).length) {
$(thisObj.targetEl).fadeOut();
}
};
Hi I am using a SAutoComplete (extends CAutoComplete) and need to do some work when a value is selected from the list.
i am using it like this.
this->widget('application.components.SAutoComplete', array('width'=>200,
'model'=>$cssAtapsClient, 'parseData'=>true, 'matchContains'=>true,
'attribute'=>'suburb_id', 'data'=>$postCode, 'ddindicator'=>true,
'max'=>50,
'multipleSeparator'=>false,
'options' => array(
'select' => new CJavaScriptExpression('function(e, ui) { alert("hi"); }')
),
)); ?>
i am wondering why there is no select option like available in jquery UI auto completed?
example of a select is as below.
$("#auto_cp").autocomplete({
minLength: 3,
//source
source: function(req, add) {
$.getJSON("friends.php?callback=?", req, function(data) {
var suggestions = [];
$.each(data, function(i, val) {
suggestions.push({
label: val.name,
zzz: val.zzz
});
});
add(suggestions);
});
},
//select
select: function(e, ui) {
alert(ui.item.zzz);
}
});
EDIT 2
http://www.yiiframework.com/doc/api/1.1/CAutoComplete
code is like this,
<?php
class SAutoComplete extends CAutoComplete
{
public $ddindicator;
/**
*
* #var boolean whether to parse the data assumes data uses an assoc array
* array(value => array(name, value, ...), ...). Only works with a model present
*/
public $parseData;
/**
* #var boolean whether to raise the change event
*/
public $raiseChangeEvent = false;
/**
* Initializes the widget.
* This method registers all needed client scripts and renders
* the autocomplete input.
*/
public function init()
{
if ( !$this->max )
$this->max = 50000;
if ( $this->ddindicator )
$this->alternateInit();
else
parent::init();
}
public function alternateInit()
{
list($name,$id)=$this->resolveNameID();
$this->htmlOptions['id'] = $id.'_input';
$this->minChars = 0;
echo CHtml::openTag('div', array('class'=>'ac-input-dd'));
echo CHtml::openTag('div', array('class'=>'ac-input-btn'));
echo CHtml::closeTag('div');
if($this->hasModel())
{
$htmlOpt = array();
if ( $this->parseData )
{
$menu = $this->data;
$key = $this->attribute;
//Change if attribute is apart of a array. eg attribute[0]
$pos1 = stripos($key, '[');
$pos2 = stripos($key, ']');
if($pos1!==false && $pos2!==false)
{
$key = str_replace (substr($key,$pos1,$pos2 - $pos1 + 1),'',$key);
$htmlOpt['value'] = isset($this->model->$key) ? $this->model->$key : '';
}
$this->value = isset($menu[$this->model->$key][0]) ? $menu[$this->model->$key][0] : '';
$this->data = is_array($menu) ? array_values($menu) : array('Error in data.');
}
echo CHtml::activeHiddenField($this->model, $this->attribute, array_merge(array('id'=>$id, 'name'=>$name), $htmlOpt));
echo CHtml::textField('', $this->value, $this->htmlOptions);
}
else
{
echo CHtml::hiddenField($name, $this->value, array('id'=>$id));
echo CHtml::textField($name.'_input',$this->value,$this->htmlOptions);
}
echo CHtml::closeTag('div');
$this->methodChain = $this->methodChain.'.result(function(evt, data, formatted) { $("#'.
$id.'").val(data ? data[1] : "")'.($this->raiseChangeEvent?'.change()':'').'; })'.
'.parent().find(".ac-input-btn").mousedown(function(){'.
'jQuery(this).parent().find(".ac_input").toggleResults();})'.
'.mouseup(function(){jQuery(this).parent().find(".ac_input").focus();});';
$this->registerClientScript();
}
public static function registerScript()
{
$cs = Yii::app()->getClientScript();
$cs->registerCoreScript('jquery');
$cs->registerCoreScript('bgiframe');
TK::registerScriptFile('autocomplete');
$cs->registerCssFile($cs->getCoreScriptUrl().'/autocomplete/jquery.autocomplete.css');
}
/**
* Registers the needed CSS and JavaScript.
* #since 1.0.1
*/
public function registerClientScript()
{
// can cut this down once YII releases a fix for defect #38
if ( Yii::app()->request->isAjaxRequest || $this->ddindicator )
{
$id=$this->htmlOptions['id'];
$acOptions=$this->getClientOptions();
$options=$acOptions===array()?'{}' : CJavaScript::encode($acOptions);
$cs=Yii::app()->getClientScript();
if($this->data!==null)
$data=CJavaScript::encode($this->data);
else
{
$url=CHtml::normalizeUrl($this->url);
$data='"'.$url.'"';
}
if ( Yii::app()->request->isAjaxRequest )
{
echo '<script type="text/javascript">jQuery(document).ready('.
'function() {jQuery("#'.$id.'").autocomplete('.$data.','.$options.')'.
$this->methodChain.';});</script>';
}
else
{
SAutoComplete::registerScript();
$cs->registerScript('Yii.CAutoComplete#'.$id,"jQuery(\"#{$id}\").autocomplete($data,{$options}){$this->methodChain};");
}
}
else
parent::registerClientScript();
}
}
You can pass any option that the JUI autocomplete widget supports by including an options array:
$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
// your other settings here
'options' => array(
'select' => new CJavaScriptExpression('function(e, ui) { alert("hi"); }')
),
));
If the options you want to pass include JavaScript code then you also have to wrap that inside a CJavaScriptExpression as above.
Try to add this,
'methodChain'=>".result(function(event,item){ urFunction(); })",
Have a nice day!
So at last it will look like,
$this->widget('application.components.SAutoComplete', array('width'=>200,
'model'=>$cssAtapsClient, 'parseData'=>true, 'matchContains'=>true,
'attribute'=>'suburb_id', 'data'=>$postCode, 'ddindicator'=>true,
'max'=>50,
'methodChain'=>".result(function(event,item){ urFucntion(); })",
));
I'm using CAutoComplete as the followin code:
In post/_form.php
<?php $this->widget('CAutoComplete', array(
'model' => $model,
'attribute' => 'tags',
'url' => array('suggestTags'),
'multiple' => true,
'htmlOptions' => array(
'size' => 50,
'class' => 'span11'
),
)); ?>
In PostController.php
Add one more action call: suggestTags
/**
* Suggests tags based on the current user input.
* This is called via AJAX when the user is entering the tags input.
*/
public function actionSuggestTags()
{
if(isset($_GET['q']) && ($keyword=trim($_GET['q']))!=='')
{
$tags=Tag::model()->suggestTags($keyword);
if($tags!==array())
echo implode("\n",$tags);
}
}
In Post.php model
add private $_oldTags; to the top of class (under class name).
add these functions:
/**
* Normalizes the user-entered tags.
*/
public function normalizeTags($attribute, $params)
{
$this->tags = Post::array2string(array_unique(Post::string2array($this->tags)));
}
public static function string2array($tags)
{
return preg_split('/\s*,\s*/', trim($tags), -1, PREG_SPLIT_NO_EMPTY);
}
public static function array2string($tags)
{
return implode(', ', $tags);
}
See more Yii tutorials here