So I have a form that appears on a few pages and contains a number of groups of inputs. Say group a, group b and group c. On some pages they might have a and b and on others the form contains b and c. Each group may require its own client side custom validation maybe executed from the form controller.
What is the best way to achieve this using backbone and marionette?
Conceptually, and i'm fairly new to both, I'd assume I'd need a FormController that is instantiated from a page specific Controller which also instantiates the group controllers that I need for that page. Any advice would be great.
TL;DR
To make this work you would create two objects per input group. One for its custom functions, and one for its events. In the form controller you'd _.extend the function objects of each input group with a base form view to get the custom functions into your new form view. Next you'd pass in your custom events objects into the constructor for the new form view, where a utility function of your basic view would add the new custom events to the basic form view events. Finally, you'd have to ensure that you have a template for that form that contains the correct input groups.
I should also mention that you can accomplish this with Marionette.LayoutViews and dynamic regions, in a possibly cleaner way (you could specify an atomic template for each input group), albeit with a lot more overhead.
The expanded explanation
I've been toying with this idea a bit. I've been thinking a lot lately about using Underscore _.extend() to plug-in stock functionality to different views. Your problem would be ideal for this solution. Here's a sample of how you'd implement it.
Including functionality
Say that your input group A had a validate function that did it's thing,
validateA: function () {
// custom validation routine
},
and a submit function to handle form submissions,
submitA: function () {
// custom submit routine
}
The first thing you do is package these functions inside an object:
groupA = {
validateA: function() {...},
submitA: function() {...}
}
You'd have as many function objects as you have input groups.
You'd also build a generic form view that would house common form functionality, and which would also serve as the basic view you'd use to render your form
var GenericForm = Backbone.Marionette.ItemView.extend({...});
In here you'll put all your baseline events and functions common to all forms.
Then, as you mentioned you'd set up a FormController that would plug in the custom functionality, like this,
var formController = function () {
callFormZ: function() {
var genericForm = new GenericForm({ groupEvents: [eventsA, eventsB] });
var formZ = _.extend(genericForm, groupA, groupB);
SomeRegion.show(formZ);
}
}
By using _.extend on your basic view and your two input groups you end up with a new view, formZ that is a composite of the two input groups. If you'd look inside it would have all the functionality of GenericForm plus
{
validateA: function () { ... },
submitA: function () { ... },
validateB: function () { ... },
submitB: function () { ... },
}
Events
At this point, though there is no way to bind any events to your custom functions.
You may have noticed in the callFormZ controller that I passed in an array in a property called groupEvents. These are the custom events for your input groups. They'd normally have the form
eventsA: {
'blur .groupA': 'validateA',
'click #groupA button': 'submitA'
}
Ideally, we would just use extend to merge your events the way we did your functions. But since we want to end up with just one events property in your view, we run into a problem. _.extend will overwrite all the events properties in your view object with the events property of the last object passed into _.extend. So to get around this problem we have to pass an array of custom events, one for each form group.
First, you'd create a config array of events objects with your custom events for the particular form you're working with,
customEvents = [
eventsA: {
'blur .groupA': 'validateA',
'click #groupA button': 'submitA'
},
eventsB: {
'blur .groupB': 'validateB',
'click #groupB button': 'submitBA'
}];
You'd pass this array into your constructor, as we did in the callFormZ controller function. The array will now be loaded in your options parameter. In your initialize you can call something like this,
initialize(options) {
combineEvents(options.groupEvents);
this.delegateEvents();
}
where, combinedEvents is
combineEvents: function() {
var extendEvents = [this.eventsA, this.eventB]; // Make an array of the extended groups
_.each(extendEvents, function (extendEvent) {
for (prop in extendEvent)
this.event[prop] = extendEvent[prop];
}
}
Also, note that after combining events I called delegateEvents so that we'd rewire the new events.
Templates
To make all this work you'll have to provide a template that has the portions of each input group. I'm not aware of how we construct the template programmatically from individual input group templates. Instead, you'd have to have a template with the input groups relevant to the form.
Putting it all together
So, to make this work you would create two objects per input group. One for its custom functions, and one for its events. In the form controller you'd _.extend the function objects of each input group with a base form view to get the custom functions into your new form view. Next you'd pass in your custom events objects into the constructor for the new form view, where a utility function of your basic view would add the new custom events to the basic form view events. Finally, you'd have to ensure that you have a template for that form that contains the correct input groups.
Related
I like the gijgo treeview with checkbox as its clean and neat and it solves the purpose of showing the hierarchy information. Check below link for documentation.
https://gijgo.com/tree/demos/bootstrap-treeview-checkbox
Since knockout.js is preferred for the front end development hence its needed to develop a knockout binding for this particular requirement.
The idea is to populate the hierarchy data from the backend and bind it to the custom knockout binding.
The user selects/un-selects some checkboxes and then hits the save button. the selected/unselected data is again sent back to the server for the save.
The below code is the usage of the control in jquery.
The function tree.getCheckedNodes() returns the array of selected checkboxes.
How would one call the above function from an knockout binding.
ko.bindingHandlers.tree = {
init: function (element, valueAccessor, allBindingsAccessor) {
},
update: function (element, valueAccessor, allBindingsAccessor) {
var options = valueAccessor() || {};
var value = ko.utils.unwrapObservable(valueAccessor());
var tree = $(element).tree(value);
}
}
In the init method:
Unwrap the widget's initial data passed by your viewmodel from the valueAccessor
Convert the initial data to the format the tree widget understands
Initialize the widget with the correct settings $(element).tree({ /* ... */ })
Attach an event listener (.on("change", function() { }) to track user-input
In the event listener function, write back the data from the UI to the viewmodel (e.g. valueAccessor() (tree.getCheckedNodes()))
Optional: add custom disposal logic to clean up the widget if knockout removes it from the DOM
In the update method, which is called if your view model's value changes
Implement the logic that updates the widget based on your new settings. Probably something like tree.check(ko.unwrap(valueAccessor())). Make sure the update is "silent", if it would trigger a change event, you'd end up in an infinite loop.
I have a custom directive which is re-usable throughout the application. This directive uses ui-grid and I'm trying to determine the "angular way" of allowing access to the grid API from anywhere in the application.
If it was only a single instance, I'd use a service to share data across controllers:
var attachments = angular.module('attachments', ['ui.grid']);
// this would be accessible from any of my controllers
attachments.factory('Attachments', function() {
return {};
});
attachments.directive('attachments', function() {
return {
restrict: 'E',
templateUrl: 'attachments.html', // template has a ui-grid, among other elements
controller: ['$scope', 'Attachments', function($scope, Attachments) {
$scope.gridOptions = {
// ui-grid code here
onRegisterApi: function( gridApi ) {
Attachments.grid = gridApi;
}
};
}]
};
});
However, there could be multiple instances of the directive
For example, there might be a primary instance of this directive and one inside a modal, or one in a sidebar, one in a modal, etc.
I suppose I could add property namespaces to that service...
Attachments = {
libraryGrid: // ...
someModalGrid: // ...
}
etc...
I'd prefer to avoid making a service for each possible instance, i.e.:
attachments.factory('SomeModalAttachments', function() {
return {};
});
While it would work it feels inefficient. However, both choices are a lot better than digging into the modal scope and finding child scopes with the necessary API.
Is there any other method I haven't considered?
To me it depends on your usage model.
If you're going to have multiple of these and other bits of the application are going to access them, then that means one of a few things:
The access is actually initiated from the grid. So you have perhaps many list pages, and the currently active list page is the one you want to deal with. So I'd have the grid register with all the things it wants to talk to, and deregister when it closes again. The other things would all be services (singletons).
There are a specified number of these grids, and you talk to them by name - so you want to interact with the list-page-grid, or the modal-grid or whatever. So you have each grid register with somewhere central (maybe a service that everything else talks to).
The grids are subsidiary to something. So a page includes the grid directive, and then that page wants to talk to that grid. You could pass an object into the directive, then have the grid register itself on that object. So you call the directive with "myGridCommunicationObject = {}", and then the grid does "$scope.myGridCommunicationObject.gridApi = gridApi". This doesn't let other bits of the application talk to the grid, but if really you only want whatever created the grid to talk to it, then it works well.
You could broadcast. So if you don't really care which grid you talk to, you just want to talk to any grid that's currently visible (say you're resizing them or something) you could just broadcast an event to them, and all your grid directives could listen for that event. Taking this one step further, you could broadcast and include a grid id or name in the parameter, and then have each grid check whether it's me before taking action.
Having said all those options, there's something about what you're doing that has a bit of a code smell to it. Really, arbitrary bits of the application shouldn't want to talk directly to the grid Api, they should be communicating with methods on the controller that holds the grid. Perhaps some examples of what you want to do would help, but it feels to me like the model should be one of the grid registering to use other services (e.g. resize notifications), or of the controller that owns the grid interacting with the grid, and other things interacting with that controller.
I am building an app using backbone.js and find myself with a view with a lot of conditional logic in the templating of the Model's View. The type property of the model is used to determine which html to render. I would like to avoid this logic if possible, because it is hard to read. There are a couple of ways that I think I could deal with this (Note that the actual templates I've got here are very much simplified):
1. Conditional Logic in Collection View rather than in Model View - Multiple Subviews
I could put the conditional logic that acts on each model's type into the Collection View:
var CollectionView = Backbone.View.extend({
....
render: function() {
this.collection.each(function(thing) {
if(thing.get("type") === "wotsit") {
this.$el.append(new WotsitView({ model: thing });
} else if(thing.get("type") === "oojamaflip") {
this.$el.append(new OojamaflipView({ model: thing });
}
}, this);
},
....
}
Pros
This way I could have each subview with a template method that had no logic in it, but rather just builds html.
var WotsitView = new Backbone.View.extend({
....
template: _.template('<h2>{{ title }}</h2>');
});
var OojamaflipView = new Backbone.View.extend({
....
template: _.template('<h3>{{ title }}</h3>');
});
Cons
The thing is, the things in the collection are all very similar. The events for each thing are likely to be the same or very similar and I can see there being a lot of code duplication. I really only want the actual template for these subviews to be different with everything else the same.
2. Conditional Logic in Model View - Multiple Template Methods
var ModelView = Backbone.View.extend({
....
render: function() {
if(this.model.get("type") ==== "wotsit") {
this.$el.html(this.wotsitTemplate(this.model.attributes));
} else if(this.model.get("type") === "oojamaflip") {
this.$el.html(this.oojamaflipTemplate(this.model.attributes));
}
},
wotsitTemplate: _.template('<h2>{{ title }}</h2>'),
oojamaflipTemplate: _.template('<h3>{{ title }}</h3>')
});
Pros
There is only one view for the model.
All of the events etc are handles in one view rather that being duplicated.
Cons
I actually quite like this way, but I would be very interested to hear some other peoples options on it.
Option #1 = polymorphism
Option #2 = switch statement
Switch statements are useful if you never (or rarely) have to add new types, but you might want to add new methods. Imagine you want to add a validate method to ModelView, which checks view input for that kind of model for correctness and reports the results to the user. With option #2, you'd just add one new method that switches on the model type (just like the render method) to handle validation.
Now let's assume we already have a render method and a validate method, and we want to handle a new type of model, a thingamajig. With option #2, you'd have to add logic to both render and validate. Now imagine we don't have just 2 methods, but 10 — option #2 gets real complicated real quick when you have to handle new types. But if we followed option #1, then no matter how many methods there were, we'd only have to create a new view in one place, and update CollectionView to map the new type to the new view.
Most of the time polymorphism (option #1) is the clean way to go. It lets you separate all logic specific to a given type and put it one place, making it easy to handle new types.
Keeping DRY
If you're worried with option #1 that you'll end up with a lot of duplicate code between all the views, don't forget that it's easy to make Views that inherit from other Backbone Views:
var ItemView = Backbone.View.extend({
initialize: function() {
/* some common logic for all views */
}
});
var WotsitView = ItemView.extend({
template: _.template('<h2>{{ title }}</h2>')
});
var OojamaflipView = ItemView.extend({
template: _.template('<h3>{{ title }}</h3>')
});
Many of the views in my application need to be "collapsible". To the user this means that you can click an arrow to collapse or expand the view's contents.
When creating a view I need to be able to easily say, "This view should be collapsible," and then run the appropriate setup code (which essentially means adding the .collapsible class to the view's wrapper and inserting a dom element that looks like this: <div class="toggle"></div>
Suggestions on ways to pull this off seamlessly? I'm currently using Backbone, Backbone.Marionette, and Underscore.
I do this with another application that doesn't use Backbone. In that application every action results in a page refresh, so I just use jQuery to look for all elements with the .collapsible class and do my setup that way.
EDIT:
I'm using Backbone.Marionette.CompositeView for these particular views, if that helps.
I've done similar thing in my project by extracting such functionality into mixins. There're different approaches to implementing mixins in Backbone. Take a look here or here
You can create parent view that extends from Marionettes compositeView and add your common functionallity there, and have your project views extend from this parent view.
var CollapsibleView = Backbone.Marionette.CompositeView.extends({
variable1: 1,
var2: true,
initialize : function() {
// your code here
},
helperfunction : function () {
// other helpful function
}
});
var MySpecificView = CollapsibleView.extends({
mySpecificFunction : function () {
// some specificView functionality
}
});
var myProjectView= new MySpecifcView();
myProjectView.helperfunction(); /// function from the parent
myProjectView.mySpecificFunction(); /// function from the specificView
/// you also have the functionality added on the initialization of the collpasibleView
Please consider following scenario:
<ParentView>
<FilterSubview></FilterSubview>
<ListSubview></ListSubview>
</ParentView>
To give you and example: I have a view which in turn shows view with filter (user can select to display books, magazines or both of them) and the list with items.
Both filter and list have corresponding models. Filter - what can we filter. List - list of all items.
Use case: user sees the full list and then can filter results by selecting only desired category.
Questions:
How those two views should interact? Should they know about each other or should parent view handle it?
Who should store filtered list to display? It could be list subview model directly or parent view can filter complete list and then pass it to render.
There is no one correct answer to your questions, but I'll try to explain a common, idiomatic way here.
Two sibling views should not know of each other. Instead they should interact via events through some kind of a mediator. Since in your case both FilterView and ListSubView share a common parent view which is responsible for rendering both of them, you could let the parent view mediate the events:
var ParentView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.filterView, "filter", this.filterChanged);
},
filterChanged: function(filterValue) {
this.listSubView.filter(filterValue);
}
});
var FilterView = Backbone.View.extend({
events: {
"change .filter" : "filterValueChanged"
},
filterValueChanged: function() {
var filterValue = //get filter value...
this.trigger("filter", filterValue);
}
});
Alternatively (preferrably, even) you can cut out a middle man and use the Mediator pattern. For that you need a third component whose job it is to pass messages between parties who should not know of each other. If you're using Backbone 0.9.9, there's just such a mediator built in: the Backbone root object works as a global event bus for this purpose.
So:
//ListSubView
this.listenTo(Backbone, "listfilterchanged", this.filterChanged);
//FilterView
Backbone.trigger("listfilterchanged", filterValue);
Then there's the question of who should be responsible of the list data. I tend to prefer to have the most specialized component be in charge, but so that only one component is in charge. In your case that would mean that the ListSubView should manage the filtered list, but only if the ParentView doesn't need to operate on it. That's just a generalization though, so take it with a grain of salt and do what feels right for your case.