Is that possible?
I already wrote a simple validator that is triggered when the form is submitted.
Can I use the same validator, but it must be triggered right after the input field has been left.
is it possible?
pablo
It would be possible, here is one way to go about it.
Add a Javascript onblur event to the form element
The onblur call sends an ajax request containing that one field and its value to a ZF action
The action calls the validator on that element, or uses Zend_Form::isValidPartial to check the populated element
Return a JSON response indicating valid/invalid and optional error message
On ajax complete, read the JSON response and update HTML to reflect the result of validation
Hope that helps.
This is part of working example:
Backend:
class UserController extends Zend_Controller_Action
{
/* ... */
public function validateAction()
{
if ($this->_request->isXmlHttpRequest()) {
$values = $this->_request->getParam('values');
$form = new Form_User();
$isValid = true;
if (!$form->isValidPartial($values)) {
$isValid = $form->getMessages();
}
$this->_helper->json(array('errors' => $isValid));
}
}
/* ... */
}
Frontend, (just ajax call part) should be attached on event:
$.ajax({
type: "POST",
url: "/user/validate/",
data: {
'values': $('#my-form-id').serialize()
},
dataType: "json",
beforeSend:function(){
},
success: function(response){
var result = response.errors;
if (result == true) {
// given fields are valid
// do some extra stuff here
} else {
// invalid
// do some extra stuff here
}
}
}
});
Related
In my routes.js file, I have defined a route like this:
'PUT /api/v1/entrance/login': { action: 'entrance/login' },
'POST /api/v1/entrance/signup': { action: 'entrance/signup' },
'POST /api/v1/entrance/send-password-recovery-email': { action: 'entrance/send-password-recovery-email' },
'POST /api/v1/entrance/update-password-and-login': { action: 'entrance/update-password-and-login' },
'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' },
'POST /api/v1/getEventsForICalUrl': 'IcalController.getEvents',
I have just used the default generated code and added the last route for getEventsForIcalUrl.
I created an IcalController inside the controllers directory and it has an action getEvents which simply renders a json like this:
module.exports = {
/**
* `IcalController.getEvents()`
*/
getEvents: async function (req, res) {
console.log("hit here");
let events = [];
for (var i = 0; i < 20; i++) {
events.push({foo: "bar" + i});
}
return res.json({
events: events
});
}
};
My problem is that whenever i try to access this controller from the client side, it gives 403 forbidden error.
When I change the route from POST to GET, it works as expected(ofc I am using proper GET/POST request from client end for the route).
Not sure what is breaking.
I also checked the logs. Its printing "hit here" when I use the GET.
In my policy file looks like this(as it was generated. I did not change it):
module.exports.policies = {
'*': 'is-logged-in',
// Bypass the `is-logged-in` policy for:
'entrance/*': true,
'account/logout': true,
'view-homepage-or-redirect': true,
'deliver-contact-form-message': true,
};
And my "is-logged-in" policy file is this:
module.exports = async function (req, res, proceed) {
// If `req.me` is set, then we know that this request originated
// from a logged-in user. So we can safely proceed to the next policy--
// or, if this is the last policy, the relevant action.
// > For more about where `req.me` comes from, check out this app's
// > custom hook (`api/hooks/custom/index.js`).
console.log("req.me=" + req.me);
if (req.me) {
return proceed();
}
//--•
// Otherwise, this request did not come from a logged-in user.
return res.unauthorized();
};
I just put that console.log in this file. others are as they were by default generation from sails new.
The logs show that using POST, this one does not get hit either.(I dont see the "req.me=".. in console.logs.) But this one gets hit when Using GET.
It seems that the route is not working for POST requests. I wonder if its an error in sails js itself or I am doing something wrong.
There are at least two ways how to solve this.
Probably you are using csrf. If you do, your config probably includes this:
module.exports.security = { // With Sails <1.01 this probably is in another file
csrf: true
};
And (if you are using sails v1.01), you should make this route:
'GET /csrfToken': { action: 'security/grant-csrf-token' },
So, to get data on your frontend, you just:
function get_some_records(object_with_data) {
$.get("/csrfToken", function (data, jwres) {
if (jwres != 'success') { return false; }
msg = {
some_data: object_with_data,
_csrf: data._csrf
};
$.post("get_some_records", msg, function(data, status){});
});
}
But if you are using some background jobs, Sails wont give you csrf easily(there is some way probably). So , you just create a route like this:
'post /get_some_records': {
action: 'home/get_some_records',
csrf: false
}
You probably use a Rest client to test (like Postman). Simply disable the csrf protection. in the /config/security.js file
csrf: false
I've used Umbraco 7.3 in my project. I created a custom data type but when I want to call a Surfacecontroller in here is HelloSurfaceController or Hello2SurfaceController, I got an error in umbraco backoffice that said Request error: The URL returned a 404 (not found):
I studied some articles about routing but I couldn't solve my problem. I don't know that where I did wrong.
How can I solve this problem?
Reply.controller.js:
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http) {
$scope.SendReply = function () {
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
$scope.xxx = "I'm here!";
var data = { SendTo: sendTo, TextMessage: textMessage };
// ~/Hello2Surface/ReplyMessage ---> Cannot find this URL
$http.post("~/App_Plugins/Reply/HelloSurface/ReplyMessage") // Can not find this URL
.then(function (response) {
alert("YES!");
//TODO:
});
}
});
SurfaceController
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
public class HelloSurfaceController : SurfaceController
{
[HttpPost][ChildActionOnly]
public ActionResult ReplyMessage()
{
//TODO: how should be write this method that be proper for getting data from angularjs?
return null;
}
}
}
package.manifest
{
propertyEditors: [
{
alias: "Send.Reply",
name: "Send Reply",
editor:{
view:"~/App_Plugins/Reply/Reply.html"
},
}
]
,
javascript:[
'~/App_Plugins/Reply/Reply.controller.js'
]
}
Reply.html
<div ng-controller="Reply.controller">
<div style="width: 100%;">
<input type="button" value="Send Reply" title="SendReply" name="Send Reply" ng-click="SendReply()" />
</div>
<div>
<input type="text" ng-model="xxx" name="message" />
</div>
Error in umbraco backoffice:
Take a closer look at the documentation - in particular the Plugin-based SurfaceControllers section:
https://our.umbraco.org/documentation/Reference/Routing/surface-controllers
try doing this (note the PluginController attribute):
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
[PluginController("Reply")]
public class HelloSurfaceController : SurfaceController
{
[HttpPost][ChildActionOnly]
public ActionResult ReplyMessage()
{
//TODO: how should be write this method that be proper for getting data from angularjs?
return null;
}
}
}
Other Notes:
You don't need to include "Surface" in the controller name anymore - simply calling it HelloController is enough.
Don't use a SurfaceController for Api calls if you're using it with AngularJS - Better to use an UmbracoApiController instead. Check out https://our.umbraco.org/documentation/Reference/Routing/WebApi/ for more information (including notes on where to expect the Api Endpoint to be)
You might also want to re-locate your controller so it's in a more conventional spot. There's no problem with putting it in the ~/Controllers directory even if it is a Plugin Controller.
Edit: Added "correct" way to do this:
As noted above, to implement an UmbracoApiController, you want a class looking like this - note you can use UmbracoApiController if you don't need to worry about authorization:
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
[PluginController("Reply")]
public class HelloApiController : UmbracoAuthorizedApiController
{
public void PostReplyMessage(string to, string message)
{
// TODO: process your message and then return something (if you want to).
}
}
}
Then in AngularJS set up a resource like this:
function replyResource($q, $http, umbDataFormatter, umbRequestHelper) {
var replyResource = {
sendMessage: function (sendTo, msg) {
return umbRequestHelper.resourcePromise(
$http.post("Backoffice/Reply/HelloApi/PostReplyMessage?" +
umbRequestHelper.dictionaryToQueryString(
[{ to: sendTo }, { message: msg }])),
'Failed to send message to ' + sendTo + ': ' + msg);
}
};
return replyResource;
}
angular.module('umbraco.resources').factory('replyResource', replyResource);
and finally your actual view controller can use this as follows:
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http, $injector) {
// Get a reference to our resource - this is why we need the $injector specified above
replyResource = $injector.get('replyResource');
$scope.SendReply = function () {
// You really shouldn't use jQuery here - learn to use AngularJS Bindings instead and bind your model properly.
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
replyResource.sendMessage(sendTo, textMessage)
.then(function (response) {
// Success
}, function (err) {
// Failure
});
}
};
});
It's possible there's some errors in there; I did it mostly from memory - in particular, you may need to look into the best way to post data to the ApiController - it's not likely that it'll just accept the two parameters like that.
For a more complete example, consider reviewing the code of the Umbraco MemberListView plugin: https://github.com/robertjf/umbMemberListView
Also, you really should read up on the ASP.Net MVC fundamentals and the Umbraco Documentation for SurfaceControllers and APIControllers I've listed above already.
remove the "Surface" from the URL and include "backoffice":
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http) {
$scope.SendReply = function () {
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
$scope.xxx = "I'm here!";
var data = { SendTo: sendTo, TextMessage: textMessage };
// ~/Hello2Surface/ReplyMessage ---> Cannot find this URL
$http.post("backoffice/Reply/Hello/ReplyMessage") // Can not find this URL
.then(function (response) {
alert("YES!");
//TODO:
});
}
});
Also, I'd recommend using UmbracoAuthorizedController not a surface controller as this is being used in the back end by logged in users it'll be wise to keep it secure.
So instead your controller should look something like this:
[PluginController("Reply")]
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
public class HelloApiController : UmbracoAuthorizedJsonController
{
public [Model-to-be-returned-to-angular] ReplyMessage()
{
//sql query etc to populate model
//return model
}
}
}
lets say I have a Backbone Model and I create an instance of a model like this:
var User = Backbone.Model.extend({ ... });
var John = new User({ name : 'John', age : 33 });
I wonder if it is possible when I use John.save() to target /user/create when I use John.save() on second time (update/PUT) to target /user/update when I use John.fetch() to target /user/get and when I use John.remove() to target /user/remove
I know that I could define John.url each time before I trigger any method but I'm wondering if it could be happen automatically some how without overriding any Backbone method.
I know that I could use one url like /user/handle and handle the request based on request method (GET/POST/PUT/DELETE) but I'm just wondering if there is a way to have different url per action in Backbone.
Thanks!
Methods .fetch(), .save() and .destroy() on Backbone.Model are checking if the model has .sync() defined and if yes it will get called otherwise Backbone.sync() will get called (see the last lines of the linked source code).
So one of the solutions is to implement .sync() method.
Example:
var User = Backbone.Model.extend({
// ...
methodToURL: {
'read': '/user/get',
'create': '/user/create',
'update': '/user/update',
'delete': '/user/remove'
},
sync: function(method, model, options) {
options = options || {};
options.url = model.methodToURL[method.toLowerCase()];
return Backbone.sync.apply(this, arguments);
}
}
To abstract dzejkej's solution one level further, you might wrap the Backbone.sync function to query the model for method-specific URLs.
function setDefaultUrlOptionByMethod(syncFunc)
return function sync (method, model, options) {
options = options || {};
if (!options.url)
options.url = _.result(model, method + 'Url'); // Let Backbone.sync handle model.url fallback value
return syncFunc.call(this, method, model, options);
}
}
Then you could define the model with:
var User = Backbone.Model.extend({
sync: setDefaultUrlOptionByMethod(Backbone.sync),
readUrl: '/user/get',
createUrl: '/user/create',
updateUrl: '/user/update',
deleteUrl: '/user/delete'
});
Are you dealing with a REST implementation that isn't to spec or needs some kind of workaround?
Instead, consider using the emulateHTTP option found here:
http://documentcloud.github.com/backbone/#Sync
Otherwise, you'll probably just need to override the default Backbone.sync method and you'll be good to go if you want to get real crazy with that... but I don't suggest that. It'd be best to just use a true RESTful interface.
No you can't do this by default with backbone. What you could to is to add to the model that will change the model url on every event the model trigger. But then you have always the problem that bckbone will use POST add the first time the model was saved and PUT for every call afterward. So you need to override the save() method or Backbone.sync as well.
After all it seems not a good idea to do this cause it break the REST pattern Backbone is build on.
I got inspired by this solution, where you just create your own ajax call for the methods that are not for fetching the model. Here is a trimmed down version of it:
var Backbone = require("backbone");
var $ = require("jquery");
var _ = require("underscore");
function _request(url, method, data, callback) {
$.ajax({
url: url,
contentType: "application/json",
dataType: "json",
type: method,
data: JSON.stringify( data ),
success: function (response) {
if ( !response.error ) {
if ( callback && _.isFunction(callback.success) ) {
callback.success(response);
}
} else {
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
},
error: function(mod, response){
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
});
}
var User = Backbone.Model.extend({
initialize: function() {
_.bindAll(this, "login", "logout", "signup");
},
login: function (data, callback) {
_request("api/auth/login", "POST", data, callback);
},
logout: function (callback) {
if (this.isLoggedIn()) {
_request("api/auth/logout", "GET", null, callback);
}
},
signup: function (data, callback) {
_request(url, "POST", data, callback);
},
url: "api/auth/user"
});
module.exports = User;
And then you can use it like this:
var user = new User();
// user signup
user.signup(data, {
success: function (response) {
// signup success
}
});
// user login
user.login(data, {
success: function (response) {
// login success
}
});
// user logout
user.login({
success: function (response) {
// logout success
}
});
// fetch user details
user.fetch({
success: function () {
// logged in, go to home
window.location.hash = "";
},
error: function () {
// logged out, go to signin
window.location.hash = "signin";
}
});
Does anyone know how to do this?
I want to point $.getJson() to a controller and have it return json through ajax based on request arguments. Unfortunately it appears Zend handles the get parameters different than jQuery encodes them.
How can I do this with Zend and jQuery? I saw something on stackoverflow about Post arguments but I am lost when it comes to GET.
When using jQuery I get a 404 error using this code:
Client side:
$.getJSON("/entry/get-member-course",
{
"id": 1,
"format": "json"
},
function(json) {
alert("WIN");
});
Server side:
public function init() {
$this->_helper->ajaxContext->addActionContext('get-member-course', 'json')->initContext();
}
public function getMemberCourseAction() {
$this->view->test = Array("test"=>"bleh");
}
Easiest way is to use context switching. In your controller, setup the AjaxContext helper for your action with a "json" context
class EntryController extends Zend_Controller_Action
{
public function init()
{
$this->_helper->ajaxContext->addActionContext('get-member-course', 'json')
->initContext();
}
public function getMemberCourseAction()
{
$id = $this->_getParam('id');
$this->view->test = array('test' => 'bleh');
}
}
The view for the calling script should contain a reference to the JSON URL. For example, say your JSON code is fired by clicking a link, create the link like this
<a id="get-json" href="<?php echo $this->url(array(
'action' => 'get-member-course',
'controller' => 'entry',
'id' => $someId
), null, true) ?>">Click me for JSON goodness</a>
Your client-side code would have something like this
$('#get-json').click(function() {
var url = this.href;
$.getJSON(url, {
"format": "json" // this is required to trigger the JSON context
}, function(data, textStatus, jqXHR) {
// handle response here
});
});
By default, when the JSON context is triggered, any view property is serialized as JSON and returned in the response. If your view properties cannot be simply converted, you need to disable automatic JSON serialization...
$this->_helper->ajaxContext->addActionContext('my-action', 'json')
->setAutoJsonSerialization(false)
->initContext();
and provide a JSON view script
// controllers/my/my-action.json.phtml
$simplifiedArray = array(
'prop' => $this->someViewProperty->getSomeValue()
);
echo Zend_Json::encode($simplifiedArray);
getJSON encodes a JSON string sequences such as {Person:{name: "Ken", age: "24"}}, do you have corresponding decoder on your server side?
JSON PHP
Zend PHP JSON
I have a partial view with this jquery script:
$("#btnGetEmpInfo").click(function () {
var selectedItem = $('#EmployeeId').val();
var focusItem = $('#EmployeeId')
alert("Starting");
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
url: "<%= Url.Action("getEmpInfo", "NewEmployee")%>?sData=" + selectedItem,
data: "{}",
success: function(data) {
if (data.length > 0) {
alert("Yeah!");
} else {
alert("No data returned!");
}
}
});
alert("Back!");
});
Then in my controller I have:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult getEmpInfo(string sData)
{
return new JsonResult { Data = "test" };
}
I can breakpoint in the controller and it is hitting, but the only "Alerts" I get are the "Starting" and "Back". Why would the data not be returned or at least hit saying no data returned?
Thanks in advance for any and all help.
Geo...
You probably might want to improve this ajax call like this:
$.ajax({
type: 'GET',
url: '<%= Url.Action("getEmpInfo", "NewEmployee")%>',
data: { sData: selectedItem },
success: function(data) {
// Warning: your controller action doesn't return an array
// so don't expect a .length property here. See below
alert(data.Data);
}
});
and have your controller action accept GET requests:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult getEmpInfo(string sData)
{
return Json(new { Data = "test" }, JsonRequestBehavior.AllowGet);
}
OK, now that we have fixed the error let me elaborate. In your code you were using an application/json content type to format your request string. Unfortunately in ASP.NET MVC 2 there is nothing out of the box that is capable of making sense of JSON requests (unless you wrote a custom json value provider factory). Then using string concatenation to append the sData parameter to the URL without ever URL encoding it meaning that your code would break at the very moment the user enters some special character such as & in the EmployeeId textbox.
Try adding the 'beforeSend', 'error' and 'complete' callbacks to get more info in your javascript debugger. http://api.jquery.com/jQuery.ajax/
Are you using a javascript debugger? (firebug, ie9 dev-tools, chrome dev-tools are decent ones 3 that come to mind)