in a ok project,it should like this:
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('#form-signup').yiiActiveForm({
"username": {
"validate": function(attribute, value, messages) {
yii.validation.required(value, messages, {
"message": "Username\u4e0d\u80fd\u4e3a\u7a7a\u3002"
});
yii.validation.string(value, messages, {
"message": "Username\u5fc5\u987b\u662f\u4e00\u6761\u5b57\u7b26\u4e32\u3002",
"min": 2,
"tooShort": "Username\u5e94\u8be5\u5305\u542b\u81f3\u5c112\u4e2a\u5b57\u7b26\u3002",
"max": 255,
"tooLong": "Username\u53ea\u80fd\u5305\u542b\u81f3\u591a255\u4e2a\u5b57\u7b26\u3002",
"skipOnEmpty": 1
});
},
"id": "signupform-username",
"name": "username",
"validateOnChange": true,
"validateOnType": false,
"validationDelay": 200,
"container": ".field-signupform-username",
"input": "#signupform-username",
"error": ".help-block"
},
</script>
but in my project,when i enter email or username that is invalid,no hints or error shows! when i check the code generated by yii,i see null args of yiiActiveForm
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('#form-signup').yiiActiveForm([], []);
});
</script>
who can tell me why? IS there anything wrong with the vendor folder?
This might be because your model class is not set up to be in the right scenario, causing validation to be skipped. Please check the documentation on this topic:
http://www.yiiframework.com/doc-2.0/guide-structure-models.html#scenarios
If you want certain rules to be applied on the current view, you need to set the scenario when you create the instance of your model :
// scenario is set as a property
$model = new User;
$model->scenario = 'login';
// scenario is set through configuration
$model = new User(['scenario' => 'login']);
In your model, the case above User, you would have this function:
namespace app\models;
use yii\db\ActiveRecord;
class User extends ActiveRecord
{
public function scenarios()
{
return [
'login' => ['username', 'password'],
//more scenarios would follow here...
];
}
}
Related
I have a KendoUI grid which uses SignalR. Whilst the grid itself works fine if you update the data inline or incell, it doesn't work if I update the data in a form which uses ajax to post it to my controller.
It was my understanding that, if I injected my hub into my controller and then called (whichever I needed, create, update or destroy) :
await _fixtureHub.Clients.All.SendAsync("update", model);
or
await _fixtureHub.Clients.All.SendAsync("update", model);
That it would tell the clients that the data had been changed/created and the grid would update to reflect that change. This isn't happening however and I'm wondering what I've done wrong or missing.
To start, here is my signalR bound grid.
$('#fixture_grid').kendoGrid({
dataSource: {
schema: {
model: {
id: "Id",
fields: {
Created_Date: {
type: "date"
},
Commencement_Date: {
type: "date"
}
}
}
},
type: "signalr",
autoSync: true,
transport: {
signalr: {
promise: fixture_hub_start,
hub: fixture_hub,
server: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
},
client: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
}
}
}
},
autoBind: true,
sortable: true,
editable: false,
scrollable: true,
columns: [
{
field: "Created_Date",
title: "Created",
format: "{0:dd/MM/yyyy}"
},
{
field: "Commencement_Date",
title: "Commencement",
format: "{0:dd/MM/yyyy}"
},
{
field: "Charterer",
template: "#if(Charterer !=null){# #=Charterer_Name# #} else { #--# }#"
},
{
field: "Region",
template: "#if(Region !=null){# #=Region_Name# #} else { #--# }#"
}
]
});
Here is the relative hub for that grid:
var fixture_url = "/fixtureHub";
var fixture_hub = new signalR.HubConnectionBuilder().withUrl(fixture_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var fixture_hub_start = fixture_hub.start({
json: true
});
Here is the KendoUI wizard with form integration which I update the grid with, this form can process either a creation or an update data, this is achieved by checking the Id that is passed in. Whereby 0 equals new and >0 is existing.
function wizard_fixture() {
let wizard_name = "#wizard-fixture";
//Load Wizard
$(wizard_name).kendoWizard({
pager: true,
loadOnDemand: true,
reloadOnSelect: false,
contentPosition: "right",
validateForms: true,
deferred: true,
actionBar: true,
stepper: {
indicator: true,
label: true,
linear: true
},
steps: [
{
title: "Step 01",
buttons: [
{
name: "custom",
text: "Save & Continue",
click: function () {
let wizard = $(wizard_name).data("kendoWizard");
var validatable = $(wizard_name).kendoValidator().data("kendoValidator");
if (validatable.validate()) {
$.ajax({
type: "POST",
traditional: true,
url: "/Home/Process_Fixture",
data: $(wizard_name).serialize(),
success: function (result) {
...do stuff
},
error: function (xhr, status, error) {
console.log("error")
}
});
}
}
}
],
form: {
formData: {
Id: fixtureviewmodel.Id,
Created_User: fixtureviewmodel.Created_User,
Created_Date: fixtureviewmodel.Created_Date,
Connected: fixtureviewmodel.Connected
},
items: [
{
field: "Fixture_Id",
label: "Id",
editor: "<input type='text' name='Id' id='Fixture_Id' /> "
},
{
field: "Created_User",
label: "Created user",
editor: "<input type='text' name='Created_User' id='Created_User_Fixture' />"
},
{
field: "Created_Date",
id: 'Created_Date_Fixture',
label: "Created date",
editor: "DatePicker",
}
]
}
},
],
});
I've shortened this to demonstrate the custom button and the ajax posting that happens to Process_Fixture. Here is my controller which handles that:
public async Task<JsonResult> Process_Fixture(Fixture model)
{
if (model.Id == 0)
{
if (ModelState.IsValid)
{
var fixture = await _fixture.CreateAsync(model);
Update connected clients
await _fixtureHub.Clients.All.SendAsync("create", model);
return Json(new { success = true, data = fixture.Id, operation = "create" });
}
return Json(new { success = false });
}
else
{
var fixture = await _fixture.UpdateAsync(model);
await _fixtureHub.Clients.All.SendAsync("update", model);
return Json(new { success = true, data = fixture.Id, operation = "update" });
}
}
As you can see, I have injected my hub and I have called the "create" message to it which I believed would force the grid to update with whatever had changed or been created.
Here is the hub itself:
public class FixtureHub : DynamicHub
{
private readonly IRepository<Fixture> _fixtures;
private readonly IRepository<ViewGridFixtures> _viewFixtures;
public FixtureHub(IRepository<Fixture> fixtures, IRepository<ViewGridFixtures> viewFixtures)
{
_fixtures = fixtures;
_viewFixtures = viewFixtures;
}
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception e)
{
Groups.RemoveFromGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnDisconnectedAsync(e);
}
public class ReadRequestData
{
public int ViewId { get; set; }
}
public IQueryable<ViewGridFixtures> Read()
{
IQueryable<ViewGridFixtures> data = _viewFixtures.GetAll();
return data;
}
public string GetGroupName()
{
return GetRemoteIpAddress();
}
public string GetRemoteIpAddress()
{
return Context.GetHttpContext()?.Connection.RemoteIpAddress.ToString();
}
}
I need some help here in understanding how I can tell the hub that the update/create/destroy has been called and it needs to do something. At the moment, I feel like injecting the hub and then calling the clients.all.async isn't the right way. With ajax it seems to ignore it and I wonder if the two technologies are working against each other.
I am trying to generate an items list response from paypal checkout requests. I am trying to do it dynamically, using my data objects and some computed properties in a for in loop. As far as I have understood, my items_list will always need to be a data variable, never a hard-coded array.
Here is my template element:
<div v-bind:key="plan.key" v-for="plan in plans" >
<PayPal
:amount="plan.price" // all good
currency="GBP" // all good
:client="credentials" // all good
env="sandbox" // all good
:items="[plan]" // this is NOT working
#payment-authorized="payment_authorized_cb" // all good
#payment-completed="payment_completed_cb" // all good
#payment-cancelled="payment_cancelled_cb" // all good
>
</PayPal>
</div>
Here are my data objects on my script:
plans: {
smallPlan: {
name: 'Small Venue',
price: '6',
},
mediumPlan: {
name: 'Medium Department',
price: '22',
},
}
payment_completed: {
payment_completed_cb() {
}
},
payment_authorized: {
payment_authorized_cb() {
}
},
payment_cancelled: {
payment_cancelled_cb() {
}
},
Here are my methods:
methods: {
payment_completed_cb(res, planName){
toastr.success("Thank you! We'll send you a confirmation email soon with your invoice. ");
console.log(res);
},
payment_authorized_cb(res){
console.log(res);
},
payment_cancelled_cb(res){
toastr.error("The payment process has been canceled. No money was taken from your account.");
console.log(res);
},
The documentation of Vue-paypal-checkout is available here https://www.npmjs.com/package/vue-paypal-checkout
If I don't add the items list :items everything works perfectly:
{"id":"PAY-02N9173803167370DLPMKKZY","intent":"sale","state":"approved","cart":"90B34422XX075534E","create_time":"2018-10-30T18:39:51Z","payer":{"payment_method":"paypal","status":"VERIFIED","payer_info":{"email":"joaoalvesmarrucho-buyer#gmail.com","first_name":"test","middle_name":"test","last_name":"buyer","payer_id":"JCZUFUEQV33WU","country_code":"US","shipping_address":{"recipient_name":"test buyer","line1":"1 Main St","city":"San Jose","state":"CA","postal_code":"95131","country_code":"US"}}},"transactions":[{"amount":{"total":"245.00","currency":"GBP","details":{}},"item_list":{},"related_resources":[{"sale":{"id":"2RA79134UX2301839","state":"pending","payment_mode":"INSTANT_TRANSFER","protection_eligibility":"ELIGIBLE","parent_payment":"PAY-02N9173803167370DLPMKKZY","create_time":"2018-10-30T18:39:50Z","update_time":"2018-10-30T18:39:50Z","reason_code":"RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION","amount":{"total":"245.00","currency":"GBP","details":{"subtotal":"245.00"}}}}]}]}
But if I add :items="[plan]" i get this error message:
Uncaught Error: Error: Request to post https://www.sandbox.paypal.com/v1/payments/payment failed with 400 error. Correlation id: 19238526650f5, 19238526650f5
{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "transactions.item_list.items.item_key",
"issue": "This field name is not defined for this resource type"
}
],
"message": "Invalid request - see details",
"information_link": "https://developer.paypal.com/docs/api/payments/#errors",
"debug_id": "19238526650f5"
Any thoughts?
Also if you happen to know, is there a way to sell/implement a subscription instead of a one-off transaction using Vue-paypal-checkout?
Many thanks
Is it possible to get messages returned by express-validator into a language other than english for internationalization (i18n) ?
I tried looking into the source code and I could not find it.
express-validator
Thanks
It's probably something you have to create yourself, but it shouldn't be too hard.
When you assign an error message with withMessage() you can send more than just a single string. You can for example send an object. So you can put the error messages, for all the languages, for a particular error, in an object.
Here is an example:
Route:
const countValidation = require('./count.validation');
router
.route('/blogposts')
.get(
countValidation.count,
blogpostController.blogpostsGetAll,
);
Validator (in a separate file called count.validation.js):
const message = {
english: 'count must be between 1 and 1000',
chinese: 'count must be between 1 and 1000, but in chinese',
};
module.exports.count = [
check('count')
.optional()
.isInt({ min: 1, max: 1000 })
.withMessage(message)
];
This response will be sent when validation fails:
{
"errors": {
"count": {
"location": "query",
"param": "count",
"value": "-1",
"msg": {
"english": "count must be between 1 and 1000",
"chinese": "count must be between 1 and 1000, but in chinese"
}
}
}
}
In this particular example the front-end has to choose what error message to display depending on user settings or user agent.
It's also possible to deal with what error message to use on the server side, if we know what language the client is using from the request. We could for example read the accept-language header in the request. Here is an example of how that could be done:
The controller function:
Rather than using this (standard handling of errors, almost straight from the readme):
module.exports.blogpostsGetAll = (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.mapped() });
}
// The rest of the function...
};
we use this:
module.exports.blogpostsGetAll = (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const errorsInProperLanguage = handleLanguages(req.headers, errors.mapped());
return res.status(422).json({ errors: errorsInProperLanguage });
}
// The rest of the function...
};
Example function to only use one language:
function handleLanguages(headers, errorsMapped) {
const language = headers['accept-language'].split(',')[0];
for (let errorKey in errorsMapped) {
errorsMapped[errorKey].msg = errorsMapped[errorKey].msg[language];
}
return errorsMapped;
}
Because the accept-language header contains language codes, not language names, we have to modify the message object slightly:
const message = {
'en-US': 'count must be between 1 and 1000',
'zh-CH': 'count must be between 1 and 1000, but in chinese',
};
The message object HAS to contain the first language code in the accept-language header for this to work. The handleLanguage function doesn't handle errors. It's only an example to show how it could be done; don't use it directly.
The error message would change to
{
"errors": {
"count": {
"location": "query",
"param": "count",
"value": "-1",
"msg": "count must be between 1 and 1000, but in chinese"
}
}
}
when the first language in accept-language is zh-CH.
This is now possible with express-validator v5.0.0.
If you pass withMessage() a function, it will be called with the field value, the request, its location and its path.
Example from the docs:
check('something').isInt().withMessage((value, { req, location, path }) => {
return req.translate('validation.message.path', { value, location, path });
}),
A contrived example of bi-directional data binding
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
m.render("body", [
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
]);
}
};
https://lhorie.github.io/mithril/mithril.withAttr.html
I tried the above code does not work nothing.
It was the first to try to append the following.
m.mount(document.body, user);
Uncaught SyntaxError: Unexpected token n
Then I tried to append the following.
var users = m.prop([]);
var error = m.prop("");
m.request({method: "GET", url: "/users/index.php"})
.then(users, error);
▼/users/index.php
<?php
echo '[{name: "John"}, {name: "Mary"}]';
Uncaught SyntaxError: Unexpected token n
How do I operate the m.withAttr tutorials code?
Try returning m('body', [...]) from your controller.
view: function (ctrl) {
return m("body", [
...
]);
}
render should not be used inside of Mithril components (render is only used to mount Mithril components on existing DOM nodes).
The example is difficult to operate because it's contrived, it's not meant to be working out-of-the-box. Here's a slightly modified, working version:
http://jsfiddle.net/ciscoheat/8dwenn02/2/
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
return [
m("input", {
oninput: m.withAttr("value", controller.user.name),
value: controller.user.name()
}),
m("h1", controller.user.name())
];
}
};
m.mount(document.body, user);
Changes made:
m.mount injects html inside the element specified as first parameter, so rendering a body element in view will make a body inside a body.
Changed the input field event to oninput for instant feedback, and added a h1 to display the model, so you can see it changing when the input field changes.
Using m.request
Another example how to make an ajax request that displays the retrieved data, as per your modifications:
http://jsfiddle.net/ciscoheat/3senfh9c/
var userList = {
controller: function() {
var users = m.prop([]);
var error = m.prop("");
m.request({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
}).then(users, error);
return { users: users, error: error };
},
view: function(controller) {
return [
controller.users().map(function(u) {
return m("div", u.name)
}),
controller.error() ? m(".error", {style: "color:red"}, "Error: " + controller.error()) : null
];
}
};
m.mount(document.body, userList);
The Unexpected token n error can happen if the requested url doesn't return valid JSON, so you need to fix the JSON data in /users/index.php to make it work with your own code. There are no quotes around the name field.
I want to populate my Ember Data fixtures with real content taken from an API data dump instead of the standard fixtures. I don't want to use the API directly.
I want to do this so I can imitate a local instance of the API data.
How can I streamline this and also how might I configure the adapter to allow this?
Consider this default FIXTURE:
App.Comment = DS.Model.extend({
article: DS.belongsTo('article'),
author: DS.belongsTo('user'),
dateCreated: DS.attr('date', {readOnly: true}),
dateModified: DS.attr('date', {readOnly: true}),
description: DS.attr('string')
});
App.Comment.FIXTURES = [{
id: 1,
temp: 1,
author: 1,
dateCreated: 'Mon Jul 28 2014 12:00:00 GMT+1000 (EST)',
dateModified: null,
description: 'lorem ipsum'
}];
Consider this API response:
{"comments": [
{
"articleID": 1,
"description": "I am a comment",
"authorID": 1,
"dateCreated": "2014-09-04T02:39:00",
"createdBy": "Elise Chant",
"dateModified": "2014-09-04T02:39:00",
"id": 1
},
{
"articleID": 1,
"description": "I am another comment",
"authorID": 1,
"dateCreated": "2014-09-04T02:48:00",
"createdBy": "Elise Chant",
"dateModified": "2014-09-04T02:48:00",
"id": 2
}
]}
Get your backend to provide a data dump method from the API with the resources, such as:
http://myapi/dump
The method shall return a zipped folder of files representing the site's resources, such as Comment.js, User.js, Article.js.
Each of these files should return the JSON resource wrapped by DS.Model.FIXTURES array, such as
App.Comment.FIXTURES =
[
{
"articleID": 1,
"userID": 1,
"description": "I am a comment.",
"category": 0,
"authorID": 1,
"dateCreated": "2014-09-04T02:39:00",
"id": 1
},
// ...
];
Remember to make sure that each .js file is an included script tag in html and appears after its corresponding model. Check this is available in the Browser's Developer Console.
Finally, In order to correctly connect asynchronous relationships such as articleID and authorID, property names need to be normalised like that would be if you were using normal fixture data. So configure the adapter to strip 'ID' from the end of any belongsTo relationships by parsing the FIXTURES payload:
if (App.ENV === 'DEV') {
App.ApplicationSerializer = DS.JSONSerializer.extend({
// access to the payload
extractArray: function(store, type, payload) {
var self = this;
return payload.map(function(item) {
// normalise incoming data
return self.normalize(type, item);
});
},
normalize: function(type, hash) {
if (!hash) { return hash; }
var normalizedHash = {},
normalizedProp;
for (var prop in hash) {
if (!!prop.match(/ID$/)) {
// belongs to / has Many attribute
// remove 'ID' from the end of the property name
normalizedProp = prop.substr(0, prop.length-2);
} else { // regular attribute
normalizedProp = prop;
}
normalizedHash[normalizedProp] = hash[prop];
}
this.normalizeId(normalizedHash);
this.normalizeUsingDeclaredMapping(type, normalizedHash);
this.applyTransforms(type, normalizedHash);
return normalizedHash;
}
});
App.ApplicationAdapter = DS.FixtureAdapter.extend({
simulateRemoteResponse: true,
latency: 1000,
// This "unsets" serializer so that the store will lookup the proper serializer
// #see https://github.com/emberjs/data/issues/1333
serializer: function() {
return;
}.property()
});
} else {
DS.CustomRESTSerializer = DS.RESTSerializer.extend({
keyForRelationship: function(key, kind) {
if (kind === 'belongsTo') {
return key + 'ID';
} else {
return key;
}
}
});
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: App.HOST,
namespace: App.NAMESPACE,
ajax: function(url, method, hash) {
hash = hash || {}; // hash may be undefined
hash.crossDomain = true;
hash.xhrFields = {withCredentials: true};
return this._super(url, method, hash);
},
defaultSerializer: 'DS/customREST'
});
}