I have successfully implemented backbone-forms plug-in with it's validators, for example:
var SampleModel = Backbone.Model.extend({
schema: {
field1: {
title: $t.field1, validators: ['required', 'number']
},
field2: {
title: $t.field2, type: 'Select', options: $options.field2, validators: ['required']
},
notes: {
title: $t.notes
}
}
});
Now I am trying to find "right" (at this moment - any) way to disable built-in validators, on, for example, some check box click. After checkbox is clicked, allow form to be saved without validation.
I tried to rebuild this.model.schema for each field without validators and after did this.model.form.commit(), but it did nothing.
Can you, please, give some advice?
EDIT:
At now, I am using "dirty" method adding additional argument into commit method. See Backbone-forms commit method source:
commit: function(options, dontValidate) {
//Validate
options = options || {};
var validateOptions = {
skipModelValidate: !options.validate
};
// DIRTY
if(!dontValidate) {
var errors = this.validate(validateOptions);
if (errors) return errors;
}
//Commit
var modelError;
var setOptions = _.extend({
error: function(model, e) {
modelError = e;
}
}, options);
this.model.set(this.getValue(), setOptions);
if (modelError) return modelError;
},
Related
I have AngularJS component in TypeScript news.component.ts that calls a google.service.ts to get news RSS via some service that converts XML to JSON (not important).
So I have this method in NewsComponent:
getNewsHeadlines(): any {
this.googleService.getNewsHeadlines().then(headlines => {
this.newsHeadlines = headlines;
this.$scope.$apply();
});
return;
}
I have this model defined for each news item:
export interface NewsHeadline {
title: string;
content: string;
description: string;
link: string;
}
I have this method in GoogleService service:
getNewsHeadlines() {
const feed = 'http://rss.newsru.com/top/big/';
const url = 'https://api.rss2json.com/v1/api.json?rss_url=' + encodeURIComponent(feed);
return this.$http.get(url).then((response: any) => {
let items = response.data.items;
let results = items.map(result => {
let newsHeadline: NewsHeadline = {
title: 'string',
content: 'string',
description: 'string',
link: 'string'
};
return newsHeadline;
});
return this.$q.all(results);
});
};
Why do I keep getting these error messages in Chrome console?
Is my getNewsHeadlines method in GoogleService written correctly?
What does it mean, when I click on Error link, there is tons of humanly unreadable garbage, that doesn't explain anything.
Your method doesn't need $scope.$$apply. It is being handled by the httpservice, as is angular context.
This is how it should look like:
getNewsHeadlines(): any {
this.googleService.getNewsHeadlines().then(headlines => {
this.newsHeadlines = headlines;
});
return;
}
I'm adding objects to an array property of a model, then saving it. When I look at the outgoing request, the property in question is always an empty array.
My custom serializer (extending Ember.RESTSerializer) has this:
DS.ArrayTransform = DS.Transform.extend(
{
deserialize: function(serialized)
{
return (Ember.typeOf(serialized) == "array") ? serialized : [];
},
serialize: function(deserialized)
{
var type = Ember.typeOf(deserialized);
if (type == 'array')
{
return [{foo:'bar'}];
// return deserialized;
}
else if (type == 'string')
{
return deserialized.split(',').map(function(item)
{
return item.trim();
});
}
else
{
return [];
}
}
});
App.register("transform:array", DS.ArrayTransform);
As you can see I've tried passing back an arbitrary array with an object in it, but even then the array always comes out as empty. In the app I create the record like this:
var post = this.store.createRecord('explorePost', {
title: content.get('title'),
text: content.get('text'),
postDate: content.get('postdate'),
publishDate: content.get('publishDate'),
published: content.get('published'),
postType: content.get('postType'),
link: content.get('link,'),
projectDownloads: [],
// projectDownloads: this.model.get('projectDownloads'),
projectLinks: content.get('projectLinks'),
});
then add the objects like this:
this.model.get('projectDownloads').forEach(function (_download) {
console.log('pushing download', _download);
post.get('projectDownloads').pushObject(_download);
});
I can confirm that at time of saving, the post object has a projectDownloads array with one object in it. No matter what I do I can't seem to get it to spit out the contents when it saves. It's definitely going into the custom serializer, and detects it as an array, but you can see something else seems to be overriding it.
Can anyone tell me what I'm doing wrong? My model setup is below:
App.ExplorePost = DS.Model.extend(
{
title: DS.attr('string'),
text: DS.attr('string'),
link: DS.attr('string'),
postDate: DS.attr('momentdate'),
publishDate: DS.attr('momentdate'),
user: DS.belongsTo('user',{async:true}),
postType: DS.attr('string'),
activity: DS.belongsTo('activity',{ inverse: 'explorePost', async:true}),
comments: DS.hasMany('comment',{ inverse: 'explorePost', async: true }),
// projectDownloads: DS.hasMany('projectDownload',{ inverse: 'explorePost'}),
projectDownloads: DS.attr('array'),
// projectLinks: DS.hasMany('projectLink',{ inverse: 'explorePost'}),
projectLinks: DS.attr('string'),
published: DS.attr('boolean', {defaultValue: true}),
// tags: DS.hasMany('tag')
sortdate: function()
{
var datestr = Ember.isEmpty(this.get('postDate')) ? '' : moment(this.get('postDate')).format('YYYYMMDDHHmm');
var fn = (datestr + '____________').slice(0, 12);
return fn;
}.property('postDate')
});
There's no built in DS.attr('array') and a naive implementation would probably not know how to serialize ember-data objects found inside. Did you intend to leave that in there? If you swap it back to the relationships you've commented out and change projectDownloads to work with the promise:
this.model.get('projectDownloads').then(function(downloads) {
downloads.forEach(function(_download){
post.get('projectDownloads').pushObject(_download);
});
});
This should work jsut fine. I put together something nearly identical the other day. http://emberjs.jsbin.com/zolani/3/edit?html,css,js,output
if you array not contain complex object, like array of string, you can use DS.attr(), it will work.
buttons: [
{
text: "Add User",
id: "new-record-add-button",
handler: function() {
var form = this.up('form').getForm();
form.submit({
url: BasePath+'/main/admin/adduser',
method: 'POST',
waitTitle: 'Authenticating',
waitMsg: 'Please Wait',
success: function(form, action) {
win.close()
Ext.Msg.show({
title:'Success'
,msg:'User added successfully'
,modal:true
,icon:Ext.Msg.INFO
,buttons:Ext.Msg.OK
});
},
failure: function(form, action) {
console.log(action.response.responseText);
obj = Ext.JSON.decode(action.response.responseText);
console.log(obj);
Ext.Msg.alert('Error',obj.errors)
form.reset();
}
})
//this.up("window").close();
}
},
{
text: "Cancel",
handler: function() {
this.up("window").close();
}
}
]
I am getting the following error when I reach the failure function in my form:
Uncaught TypeError: Cannot read property 'responseText' of undefined
This is my php code:
public function adduserAction()
{
$response = new JsonModel();
//$error = array();
$errors="";
if(!ctype_alpha($_POST["first_name"])) {
$errors.="First Name cannot contain characters and numbers";
}
if(!ctype_alpha($_POST["last_name"])) {
$errors.="Last Name cannot contain characters and numbers";
}
if(!filter_var($_POST['email_address'], FILTER_VALIDATE_EMAIL)) {
$errors.="Email should be of the format john.doe#example.com";
}
if(empty($_POST["role"])) {
$errors.="Role cannot be empty";
}
if($errors!="") {
$response->setVariables(array("success"=>false, "errors"=>$errors));
}
else {
$response->setVariables(array("success"=>true, "errors"=>$errors));
}
return $response;
}
responseText is an ExtJs thing - it represents the actual text returned from the server (eg, using echo) before being decoded.
You should get it in asynchronous callbacks within the operation or request objects, unless there was a server exception of a sort or if you set success to false, that's why you don't get it in the failure handler.
To really understand what's going on with it I recommend you have a look at Connection.js.
if you do a form submit through ExtJs, then on success of the form submission it needs response.responseText to be set as {"sucess":"true"}. if you are submitting the form to some page you have to make sure that you will be returning this object from backend. Or else you have to override the existing onSuccess method.
The second way is something like this,
Ext.override(Ext.form.action.Submit,{
onSuccess: function(response) {
var form = this.form,
success = true,
result = response;
response.responseText = '{"success": true}';
form.afterAction(this, success);
}
});
Place this snippet in your application and see the magic. Cheers. :)
I'm using Backbone to manage the state of an HTML form. The Model's role is to handle validation. The View's role is to wrap the HTML form and respond to the change or error events emitted by the model.
Backbone seems to only emit change events when the given field is actually valid. This is causing some really unexpected behavior that makes me thing that I'm doing this wrong.
Here is a summary of what I'm doing:
1. Initial load serializes the form and injects it into the model
2. When an error event is emitted, I generate error nodes next to the invalid field.
3. When a change event is emitted, I remove the error notes next to the (now valid) field.
When a page is rendered with an initially valid form, and a user invalidates a field, the message is displayed as expected; however, the model never updates the field internally. Thus when the user corrects the error, a change event is never emitted.
Example: Initially valid
When a page is rendered with an initially invalid form, things appear to be working fine... but this is only because the model's initial attributes are empty. Correcting the field makes the messages disappear, but if you change it again to an invalid state, the message never disappears.
Example: Initially invalid
What am I doing wrong? Perhaps there's another approach I should be using instead?
My Model
var Foo = Backbone.Model.extend({
validate: function(attr) {
var errors = {};
if (_.isEmpty(attr)) return;
if (attr.foo && attr.foo != 123) {
errors.foo = ['foo is not equal to 123'];
}
if (attr.bar && attr.bar != 456) {
errors.bar = ['bar is not equal to 456'];
}
return _.isEmpty(errors) ? undefined : errors;
}
});
My View
FooForm = Backbone.View.extend({
events: {
'change :input': 'onFieldChange'
},
initialize: function(options) {
this.model.on('error', this.renderErrors, this);
this.model.on('change', this.updateFields, this);
// Debugging only
this.model.on('all', function() {
console.info('[Foo all]', arguments, this.toJSON())
});
this.model.set(this.serialize());
},
onFieldChange: function(event) {
var field = event.target,
name = field.name,
value = field.value;
this.model.set(name, value);
},
renderErrors: function(model, errors) {
_.each(errors, function(messages, fieldName) {
var el = $('#' + fieldName),
alert = $('<div/>').addClass('error');
el.parent().find('.error').remove();
_.each(messages, function(message) {
alert.clone().text(message).insertAfter(el);
});
});
},
updateFields: function(model, options) {
if (!options || !options.changes) return;
_.each(_.keys(options.changes), function(fieldName) {
var el = $('#' + fieldName);
el.parent().find('.error').remove();
});
},
serialize: function() {
var raw = this.$el.find(':input').serializeArray(),
data = {},
view = this;
$.each(raw, function() {
// Get the model's field name from the form field's name
var name = this.name;
if (data[name] !== undefined) {
if (!data[name].push) {
data[name] = [data[name]];
}
data[name].push(this.value || '');
}
else {
data[name] = this.value || '';
}
});
return data;
}
});
You can't validate individual field using native Backbone validation.
In my app I use this validation plugin: https://github.com/thedersen/backbone.validation
Then in your model you add validation rules per each field (it's optional, so you don't need to add this to all models):
var NewReview = Backbone.Model.extend({
initialize: function() {
/* ... */
},
validation: {
summary: {
required: true,
minLength: 10
},
pros: {
required: true,
minLength: 10
},
cons: {
required: true,
minLength: 10
},
overall: function(value) {
var text = $(value).text().replace(/\s{2,}/g, ' ');
if (text.length == 0) text = value;
if (text.length < 20) return "Overall review is too short";
},
rating: {
range: [0.5, 5]
},
product_id: {
required: true
}
}
});
Than in views or elsewhere you can validate either entire model or individual fields:
if (this.model.validate()) { ... }
or
if (this.model.isValid("summary")) { ... }
I have defined a validate method for a Backbone.js Model. The problem is that even if validation fails (i.e. the Model.validate method returns a value) the post/put request is still sent to the server. This contradicts what is explained in the Backbone.js documentation. I cannot understand what I am doing wrong.
The following is the Model definition:
/**
* Model - Contact
*/
var Contact = Backbone.Model.extend({
urlRoot: '/contacts.json',
idAttribute: '_id',
defaults: function() {
return {
surname: '',
given_name: '',
org: '',
phone: new Array(),
email: new Array(),
address: new Array({
street: '',
district: '',
city: '',
country: '',
postcode: ''
})
};
}
validate: function(attributes) {
if (typeof attributes.validationDisabled === 'undefined') {
var errors = new Array();
// Validate surname.
if (_.isEmpty(attributes.surname) === true) {
errors.push({
type: 'form',
attribute: 'surname',
message: 'Please enter a surname.'
});
}
// Validate emails.
if (_.isEmpty(attributes.email) === false) {
var emailRegex = /^[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,6}$/i;
// Stores indexes of email values which fail validation.
var emailIndex = new Array();
_.each(attributes.email, function(email, index) {
if (emailRegex.test(email.value) === false) {
emailIndex.push(index);
}
});
// Create error message.
if (emailIndex.length > 0) {
errors.push({
type: 'form',
attribute: 'email',
index: emailIndex,
message: 'Please enter valid email address.'
});
}
}
if (errors.length > 0) {
console.log('Form validation failed.');
return errors;
}
}
}
});
Here is the View which calls the Model.save() method (see: method saveContact() below). Note that other methods belonging to this View have not been included below for reasons of brevity.
/**
* View - Edit contact form
*/
var EditContactFormView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'createDialog', 'formError', 'render', 'saveContact', 'updateContact');
// Add templates.
this._editFormTemplate = _.template($('#edit-contact-form-tpl').html());
this._emailFieldTemplate = _.template($('#email-field-tpl').html());
this._phoneFieldTemplate = _.template($('#phone-field-tpl').html());
// Get URI of current page.
this.currentPageUri = this.options.currentPageUri;
// Create array to hold references to all subviews.
this.subViews = new Array();
// Set options for new or existing contact.
this.model = this.options.model;
// Bind with Model validation error event.
this.model.on('error', this.formError);
this.render();
}
/**
* Deals with form validation errors
*/
formError: function(model, error) {
console.log(error);
},
saveContact: function(event) {
var self = this;
// Prevent submit event trigger from firing.
event.preventDefault();
// Trigger form submit event.
eventAggregator.trigger('submit:contactEditForm');
// Update model with form values.
this.updateContact();
// Enable validation for Model. Done by unsetting validationDisabled
// attribute. This setting was formerly applied to prevent validation
// on Model.fetch() events. See this.model.validate().
this.model.unset('validationDisabled');
// Save contact to database.
this.model.save(this.model.attributes, {
success: function(model, response) {
if (typeof response.flash !== 'undefined') {
Messenger.trigger('new:messages', response.flash);
}
},
error: function(model, response) {
console.log(response);
throw error = new Error('Error occured while trying to save contact.');
},
wait: true
});
},
/**
* Extract form values and update Contact.
*/
updateContact: function() {
this.model.set('surname', this.$('#surname-field').val());
this.model.set('given_name', this.$('#given-name-field').val());
this.model.set('org', this.$('#org-field').val());
// Extract address form values.
var address = new Array({
street: this.$('input[name="street"]').val(),
district: this.$('input[name="district"]').val(),
city: this.$('input[name="city"]').val(),
country: this.$('input[name="country"]').val(),
postcode: this.$('input[name="postcode"]').val()
});
this.model.set('address', address);
}
});