I've been following an online example of backbone validation online:
http://jsfiddle.net/thedersen/c3kK2/
So far so good, but now I'm getting into validating subviews and they're not working.
My code looks like this:
var ScheduleModel = Backbone.Model.extend({
validation: {
StartDate: {
required: true,
fn: "isDate"
},
StartTime: [{
required: true
},
{
pattern: /^([0-2]\d):([0-5]\d)$/,
msg: "Please provide a valid time in 24 hour format. ex: 23:45, 02:45"
}],
EndDate: {
required: true,
fn: "isDate"
}
},
isDate: function (value, attr, computed) {
if (isNaN(Date.parse(value))) {
return "Is not a valid Date";
}
}
});
var ScheduleView = Backbone.View.extend({
tagName: "div",
template: _.template($("#scheduleAddTemplate").html()),
render: function () {
// append the template to the element
this.$el.append(this.template(this.model.toJSON()));
// set the schedule type
var renderedInterval = SetScheduleType(this.model.attributes.ScheduleType.toLowerCase());
// append to the interval
$("#Interval", this.$el).append(renderedInterval.el);
this.stickit();
return this;
},
events: {
"submit #NewScheduleForm": function (e) {
e.preventDefault();
if (this.model.isValid(true)) {
this.model.save(null,
{
success: function (schedule) {
//do stuff
}
},
{ wait: true });
}
}
},
bindings: {
"[name=ScheduleType]": {
observe: "ScheduleType",
setOptions: {
validate: true
}
},
"[name=StartDate]": {
observe: "StartDate",
setOptions: {
validate: true
}
},
"[name=StartTime]": {
observe: "StartTime",
setOptions: {
validate: true
}
},
"[name=EndDate]": {
observe: "EndDate",
setOptions: {
validate: true
}
}
},
initialize: function () {
Backbone.Validation.bind(this);
},
remove: function () {
Backbone.Validation.unbind(this);
}
});
The possible interval I'm currently working with is the following:
var MonthModel = Backbone.Model.extend({
defaults: {
DayOfMonth: 1,
MonthsToSkip: 1
},
MonthsToSkip: {
required: true,
min: 1,
msg: "Number must be greater than 1"
},
DayOfMonth: {
required: function (val, attr, computed) {
console.log(computed.ScheduleType);
if (computed.ScheduleType === "monthly") {
return true;
}
return false;
}
}
});
var MonthlyView = Backbone.View.extend({
tagName: "div",
attributes: function () {
return { id: "Monthly", class: "inline co-xs-4" };
},
template: _.template($("#monthEditTemplate").html()),
render: function () {
// append the template to the element
this.$el.append(this.template(this.model.toJSON()));
this.stickit();
return this;
},
bindings: {
"[name=DayOfMonth]": {
observe: "DayOfMonth",
setOptions: {
validate: true
}
},
"[name=MonthsToSkip]": {
observe: "MonthsToSkip",
setOptions: {
validate: true
}
}
},
initialize: function () {
Backbone.Validation.bind(this);
},
remove: function () {
Backbone.Validation.unbind(this);
}
});
Does anyone have any idea why the subview isn't validating?
Found the way to do it. Posting how it's done in case anyone else ever finds this a problem. I'm only going to show the relevant bits of code without all the bindings, initializing ect.
var ScheduleView = Backbone.View.extend({
render: function () {
// this.Interval
this.Interval = SetScheduleType(this.model.attributes.ScheduleType.toLowerCase(), this.model);
// set the changed interval view
$("#Interval", this.$el).append(this.Interval.render().el);
this.stickit();
return this;
},
events: {
"change #NewScheduleForm": function (e) {
// validate the subview when changes are made
this.Interval.model.validate();
},
"change #ScheduleType": function (e) {
e.preventDefault();
var model = this.model;
var newSchedType = e.target.value;
this.model.attributes.ScheduleType = e.target.value;
this.Interval = SetScheduleType(newSchedType, model);
$("#Interval").html(this.Interval.render().el);
},
"submit #NewScheduleForm": function (e) {
e.preventDefault();
if ((this.model.isValid(true)) && (this.Interval.model.isValid(true))) {
console.log("Success");
this.model.save(null,
{
success: function (schedule) {
//do stuff
}
},
{ wait: true });
}
}
}
});
Essentially I turned the subview into an attribute on the master view. I manually call the validation for the subview on any changes to the master view and on submitting the form.
Related
var ContactManager = new Marionette.Application();
ContactManager.addRegions({
mainRegion: "#main-region",
child:"#child2"
});
Ar = Backbone.Model.extend({});
Se = Backbone.Model.extend({});
Articlescollection = new Ar({ product_id: "104", title: "Test title"});
SelectedsCollection = new Se({ product_id: "71", title: "Test title"});
ContactManager.StaticView = Marionette.ItemView.extend({
template: tpl2,
tagName: "div",
model:Articlescollection,
modelEvents: {
'change': 'fieldsChanged'
},
fieldsChanged:function(){
console.log('dddd')
},
initialize: function () {
this.model.on('change', this.render);
}
});
ContactManager.StaticView2 = Marionette.ItemView.extend({
template: tpl2,
tagName: "div",
model:SelectedsCollection
});
var MyLayout = Backbone.Marionette.LayoutView.extend({
template: tpl3,
regions: {
menu: "#menu",
content: "#content"
}
});
ContactManager.on("start", function() {
// ContactManager.mainRegion.show( new MyLayout )
var layout = new MyLayout
ContactManager.mainRegion.show( layout )
layout.menu.show(new ContactManager.StaticView());
layout.content.show(new ContactManager.StaticView2())
Articlescollection.set("product_id", 24)
//init fieldsChanged trigger for change model
})
ContactManager.start();
What differences between modelEvents and this.model.on ?
they both initizlized when model was change but
modelEvents: {
'change': this.render
},
throw exception Uncaught TypeError: Cannot read property 'split' of undefined
modelEvents is the same as this.listenTo(this.model, { 'change': 'fieldsChanged' }); It is just sugar so you don't have to add that to initialize. You should probably never use this.model.on inside a view. That would not get cleaned up automatically like this.listenTo would. Other than this.on I don't think on should be used in general as listenTo is much safer.
The other major difference here is that:
var model = this.model;
var view = this;
this.model.on('change', function() {
this === model; // true
this === view; //false
});
The only reason this would work with render is because render is forcibly bound to the view by marionette. Any other function would have a different scope. You can change the scope by passing it as the 3rd variable of on, but again then you need to this.model.off in onBeforeDestroy
If you want to call render from modelEvents you have a few options:
modelEvents: {
'change': 'render'
}
//or
modelEvents: function() {
return {
'change': this.render
};
}
// or
modelEvents: {
'change': function() { this.render(); }
}
I am able to get tree structure for the user stories but want it same for defects also which are related to particular user story so that at a singe screen I can see both user stories and the related defects.
You may use features: [{ftype:'groupingsummary'}] of ExtJS to group defects by user stories and even summarize by some other field, in the code below by PlanEstimate. To group defects by user story Requirement attribute on defect is used, which points to the related story. In this example defects are filtered by Iteration.
Ext.define('CustomApp', {
extend: 'Rally.app.TimeboxScopedApp',
componentCls: 'app',
scopeType: 'iteration',
comboboxConfig: {
fieldLabel: 'Select Iteration:',
labelWidth: 100
},
onScopeChange: function() {
this.makeStore();
},
makeStore: function() {
var filter = Ext.create('Rally.data.wsapi.Filter', {
property: 'Requirement',
operator: '!=',
value: null
});
filter= filter.and(this.getContext().getTimeboxScope().getQueryFilter());
filter.toString();
Ext.create('Rally.data.wsapi.Store', {
model: 'Defect',
fetch: ['ObjectID', 'FormattedID', 'Name', 'State', 'Requirement', 'PlanEstimate'],
autoLoad: true,
filters: [filter],
listeners: {
load: this.onDataLoaded,
scope: this
}
});
},
onDataLoaded: function(store, records){
if (records.length === 0) {
this.notifyNoDefects();
}
else{
if (this.notifier) {
this.notifier.destroy();
}
var that = this;
var promises = [];
_.each(records, function(defect) {
promises.push(this.getStory(defect, this));
},this);
Deft.Promise.all(promises).then({
success: function(results) {
that.defects = results;
that.makeGrid();
}
});
}
},
getStory: function(defect, scope) {
var deferred = Ext.create('Deft.Deferred');
var that = scope;
var storyOid = defect.get('Requirement').ObjectID;
Rally.data.ModelFactory.getModel({
type: 'HierarchicalRequirement',
scope: this,
success: function(model, operation) {
fetch: ['FormattedID','ScheduleState'],
model.load(storyOid, {
scope: this,
success: function(record, operation) {
var storyScheduleState = record.get('ScheduleState');
var storyFid = record.get('FormattedID');
var defectRef = defect.get('_ref');
var defectOid = defect.get('ObjectID');
var defectFid = defect.get('FormattedID');
var defectPlanEstimate = defect.get('PlanEstimate');
var defectName = defect.get('Name');
var defectState = defect.get('State');
var story = defect.get('Requirement');
result = {
"_ref" : defectRef,
"ObjectID" : defectOid,
"FormattedID" : defectFid,
"Name" : defectName,
"PlanEstimate" : defectPlanEstimate,
"State" : defectState,
"Requirement" : story,
"StoryState" : storyScheduleState,
"StoryID" : storyFid
};
deferred.resolve(result);
}
});
}
});
return deferred;
},
makeGrid: function() {
var that = this;
if (this.grid) {
this.grid.destroy();
}
var gridStore = Ext.create('Rally.data.custom.Store', {
data: that.defects,
groupField: 'StoryID',
pageSize: 1000,
});
this.grid = Ext.create('Rally.ui.grid.Grid', {
itemId: 'defectGrid',
store: gridStore,
features: [{ftype:'groupingsummary'}],
minHeight: 500,
columnCfgs: [
{
text: 'Formatted ID', dataIndex: 'FormattedID', xtype: 'templatecolumn',
tpl: Ext.create('Rally.ui.renderer.template.FormattedIDTemplate')
},
{
text: 'Name', dataIndex: 'Name',
},
{
text: 'State', dataIndex: 'State',
summaryRenderer: function() {
return "PlanEstimate Total";
}
},
{
text: 'PlanEstimate', dataIndex: 'PlanEstimate',
summaryType: 'sum'
},
{
text: 'Story', dataIndex: 'Story',
renderer: function(val, meta, record) {
return '' + record.get('Requirement').FormattedID + '';
}
},
{
text: 'Story Schedule State', dataIndex: 'StoryState',
}
]
});
this.add(this.grid);
this.grid.reconfigure(gridStore);
},
notifyNoDefects: function() {
if (this.grid) {
this.grid.destroy();
}
if (this.notifier) {
this.notifier.destroy();
}
this.notifier = Ext.create('Ext.Container',{
xtype: 'container',
itemId: 'notifyContainer',
html: "No Defects found matching selection."
});
this.add( this.notifier);
}
});
i couldn't hide a show/hide a boot grid column after onclick boot grid re-load.
The issue is it works when the page is loaded first time, but i couldn't make it work after click event. Any help is appreciated.
<th id="th_state" data-identifier="true" data-column-id="state" data-visible="false">State</th>
$('#filter_group').change(function () {
$("#employee_grid").bootgrid("reload")
$("#th_state").attr("data-visible", "true") ; // Not working....
});
$("#employee_grid").bootgrid({
ajax: true,
rowCount: [50, 100, 200, 300, 500],
columnSelection: true,
requestHandler: function (request) {
request.type = 'grid';
var citiesGroup = [];
$("#cities_group").find("option:selected").each(function (index, value) {
if ($(this).is(':selected')) {
citiesGroup.push({
state: $(this).parent().attr("label"),
city: $(this).val()
});
}
});
if (request.sort) {
request.sortBy = Object.keys(request.sort)[0];
request.sortOrder = request.sort[request.sortBy];
request.chartType = $("#chartType").val();
request.date_from = $("#date_from").val();
request.date_to = $("#date_to").val();
request.citiesGroup = citiesGroup ;
delete request.sort
}
return request;
},
responseHandler: function (response) {
$("#txt_total_connects").html(response);
return response;
},
post: function ()
{
/* To accumulate custom parameter with the request object */
return {
id: "b0df282a-0d67-40e5-8558-c9e93b7befed"
};
},
url: "response.php",
formatters: {
},
labels: {
noResults: "<b>No data found</b>",
all: "",
loading: '<b>Loading Please wait....</b>'
},
templates: {
search: "",
//header: "",
}
});
suggestion and code sample
I am new to Backbone marionette, I have a view ("JoinCommunityNonmemberWidgetview.js") which opens a modal dialog ("JoinCommunityDetailWidgetview.js").On closing of the dialog ( I want the view JoinCommunityNonmemberWidgetview.js") to be refreshed again by calling a specific function "submitsuccess" of the view JoinCommunityNonmemberWidgetview.js.
How can I achieve it.
The code for the modal is as below:
define(
[
"grads",
"views/base/forms/BaseFormLayout",
"models/MembershipRequestModel",
"require.text!templates/communitypagewidget/JoinCommunityWidgetDetailTemplate.htm",
],
function (grads, BaseFormLayout, MembershipRequestModel, JoinCommunityWidgetDetailTemplate) {
// Create custom bindings for edit form
var MemberDetailbindings = {
'[name="firstname"]': 'FirstName',
'[name="lastname"]': 'LastName',
'[name="organization"]': 'InstitutionName',
'[name="email"]': 'Email'
};
var Detailview = BaseFormLayout.extend({
formViewOptions: {
template: JoinCommunityWidgetDetailTemplate,
bindings: MemberDetailbindings,
labels: {
'InstitutionName': "Organization"
},
validation: {
'Email': function (value) {
var emailconf = this.attributes.conf;
if (value != emailconf) {
return 'Email message and Confirm email meassage should match';
}
}
}
},
editViewOptions: {
viewEvents: {
"after:render": function () {
var self = this;
var btn = this.$el.find('#buttonSubmit');
$j(btn).button();
}
}
},
showToolbar: false,
editMode: true,
events: {
"click [data-name='buttonSubmit']": "handleSubmitButton"
},
beforeInitialize: function (options) {
this.model = new MembershipRequestModel({ CommunityId: this.options.communityId, MembershipRequestStatusTypeId: 1, RequestDate: new Date() });
},
onRender: function () {
BaseFormLayout.prototype.onRender.call(this)
},
handleSubmitButton: function (event) {
this.hideErrors();
// this.model.set({ conf: 'conf' });
this.model.set({ conf: this.$el.find('#confirmemail-textbox').val() });
//this.form.currentView.save();
//console.log(this.form);
this.model.save({}, {
success: this.saveSuccess.bind(this),
error: this.saveError.bind(this),
wait: true
});
},
saveSuccess: function (model, response) {
var mesg = 'You have submitted a request to join this community.';
$j('<div>').html(mesg).dialog({
title: 'Success',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
grads.modal.close();
},
saveError: function (model, response) {
var msg = 'There was a problem. The request could not be processed.Please try again.';
$j('<div>').html(msg).dialog({
title: 'Error',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
}
});
return Detailview;
}
);
I would use Marionette's event framework.
Take a look at: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.commands.md
Specifically, you need to:
1) Create a marionette application :
App = new Marionette.Application();
2) Use the application to set up event handlers
//Should be somewhere you can perform the logic you are after
App.commands.setHandler('refresh');
3) Fire a 'command' and let marionette route the event
App.execute('refresh');
First Q: is
controller.init function () {
this.control(
{
"live"? so that any further components added later on via UI events are also under the controllers watchful eye?
I am struggling to catch dynamically added link clicks by the controller:
controller
init: function () {
this.control(
{
'component[cls=pfLink]': {
click: this.onPfLinkClicked // NEVER GETS HERE
},
'component': {
click: this.onPfLinkClicked // DOESNT EVEN GET HERE
}
view
after store load:
var cmp = Ext.create('Ext.Component', {
autoEl: {
tag: 'a',
href: '#',
cls: 'pfLink',
html: ref
}
});
this.add( {
cellCls: 'question',
xtype: 'container',
items: cmp
});
...
By default they are dynamic, you can verify this:
Ext.define('MyApp.controller.Test', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'button[isOdd]': {
click: function(btn) {
console.log('Odd', btn.text);
}
},
'button[isEven]': {
click: function(btn) {
console.log('Even', btn.text);
}
}
});
}
});
Ext.require('*');
Ext.application({
name: 'MyApp',
controllers: ['Test'],
launch: function() {
var vp = new Ext.container.Viewport(),
i = 0,
timer;
timer = setInterval(function(){
++i;
vp.add({
xtype: 'button',
text: 'Button ' + i,
isOdd: i % 2 === 1,
isEven: i % 2 === 0
});
if (i === 10) {
clearInterval(timer);
}
}, 500);
}
});
Your problem is that component doesn't fire a click event. Instead, create a custom component:
Ext.define('MyApp.controller.Test', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'foo[isOdd]': {
click: function(cmp) {
console.log('Odd', cmp.el.dom.innerHTML);
}
},
'foo[isEven]': {
click: function(cmp) {
console.log('Even', cmp.el.dom.innerHTML);
}
}
});
}
});
Ext.define('Foo', {
extend: 'Ext.Component',
alias: 'widget.foo',
afterRender: function(){
this.callParent();
this.el.on('click', this.fireClick, this);
},
fireClick: function(e){
this.fireEvent('click', this, e);
}
})
Ext.require('*');
Ext.application({
name: 'MyApp',
controllers: ['Test'],
launch: function() {
var vp = new Ext.container.Viewport(),
i = 0,
timer;
timer = setInterval(function(){
++i;
vp.add({
xtype: 'foo',
html: 'Button ' + i,
isOdd: i % 2 === 1,
isEven: i % 2 === 0
});
if (i === 10) {
clearInterval(timer);
}
}, 500);
}
});