I am building a large HTML form using Backbone. Each form field is an instance of a Backbone model called Field. I have a Backbone collection called Fields that fetches a JSON file and instantiates all the form fields.
Here's my issue: When I render the collection of form fields, I don't simply want a uniform list of form fields. For example, most backbone tutorials online show you how to render collections by wrapping each model's view in an li with the collection element being a ul.
Rather, I have an HTML template that is broken up into sections like so:
I'd like the "month" and "year" fields to be in one div while the other fields go into their respective divs. The fields will be styled differently from one another using CSS. Is it possible to pass a large underscore template to a collection and have it print its model instances into the appropriate places?
Is this a use case for something like Marionette.Region?
Any thoughts are appreciated!
I see 2 main approaches for your problem:
The simpler way: Make your form be one big itemView and pass it a form model with a fields property (which would be your collection). Then, within the view, you need to iterate and generate the html.
The cleaner way: Use a layout for your form, and render additional layouts into the "time", "location", etc. regions (as suggested by Lilith). For example, the "time" layout would contain the "month" and "year" views. The "month" view, in turn, would be a collection view rendering each option in the collection into an option for the select tag.
Although the first solutions tends to be simpler to implement (because it's closer to traditional stateless web development), the second one will be easier to manage. You'll be able to register event handlers on each view (e.g. select tag) without naming collisions, and since the form's complexity will be broken down into individual pieces, it will be easier to manage down the road.
I'm not sure I totally understand you question, but I think you may need more info in your field model. For example you could include a group property in the field model and then render all fields form the same group in its own div.
Is this what you're trying to do?
I would choose another approach:
Break up the one template into at least different templates (Time, Location, VehicleInfo), because, these are three different topics, which I would handle in separate views.
After that breaking up the Time-View into a Month and a Year view and so on.
Accordingly each subview has its own model with values to render its HTML. I see no sense in using a Collection here - since you have heterogenous 'fields' / Models.
After that I would use something like a form backing Model, which represents the current state of the form. So the implementation of a formView as a super view and the other views as nested views would be a good choice.
var FormView = new Backbone.View.extend({
initialize: function(){
//initialize all subviews
},
events:{
"change":"formChanged"
}
formChanged:function(element){
//update formBackingObject aka this.model
}
});
Then you have a clear separation for all of your views and one model to represent the state of the current selections.
I think you can make use of Marionette CollectionView. With collection view, you can specify different view for each item in the collection. See the documentation;
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#collectionviews-itemview
If you want control over where items will be rendered, you can override appendHtml to indicate how itemView's will be appended to collectionView
Related
My company just bought a third party application which is based on the ext js 4.2. framework.
The software is closed source, but it is web based such that I can add a .js file to change the UI to my needs.
I want to add some controls to the rendered page. The software is showing IDs everywhere instead of text.
Example: "Issue created by: ID123". When I hover the field is get "ID123. John Doe". Ok, I am a JS ninja, so I can just add a field to the HTML DOM which will display "John Doe"in the correct spot.
I looked at the HTML code to get the correct control and see the the IDs are generated. The code I would write is prone to break with each new release of the third party software.
Now, since the is an Ext JS application I can probably solve the issue much more elegantly like adding a field not to the DOM directly but to the Ext JS container.
Question:
In Javascript I have a reference to the Ext JS app. How would I access the current view or viewmodel or model to query data and add a field?
Rough idea/Pseudo code:
var id = app.getCurrentModel.getValue("creatorID");
var name = myserver.getPersonData(id).name;
app.currentView.addLabelControl(name);
I googled a lot but all examples I found assumed that you are writing the ext js app and you are already in the controller or the view. But I only got the reference to the app.
Sorry for the newbie question :)
"App Inspector for Sencha"
For a quick glance over component hierarchy, you can use the Sencha browser plugin,
Find a certain ExtJS component programmatically
To quickly search ExtJS components or transform your findings into code, your main tool will be the browser console and the command Ext.ComponentQuery.query(xtype), e.g.
Ext.ComponentQuery.query("grid")
Ext.ComponentQuery.query("panel")
Ext.ComponentQuery.query("form")
You will then find in browser console an array of all components of that type. Select the right one, and check whether it has an id or itemId that is not auto-generated (everything like xtype-1234 is auto-generated). For form fields, the name attribute could be useful. Commands like
Ext.ComponentQuery.query("[itemId=ABC]")
Ext.ComponentQuery.query("[name=DEF]")
Ext.getCmp(id)
are far more readable and not as prone to side effects as Ext.ComponentQuery.query("panel")[12].
Most of the time, it can also be useful to think in tree structure. If you want a certain container which contains the only slider you see, trying
Ext.ComponentQuery.query("slider")
Ext.ComponentQuery.query("slider")[0].up()
could be easier than sifting through dozens or even hundreds of containers. Ways to traverse the component structure include up(xtype), down(xtype), nextSibling(xtype), previousSibling(xtype). If an xtype is provided, the next component of the corresponding xtype is selected; if it isn't provided, the next component is selected regardless of the type (e.g. direct parent, adjacent sibling).
Change anything you want.
You can extend, debug or modify any existing behaviour, including but not limited to ExtJS's own code, using a so-called override over any component, including the views or stores that make up this app. override makes a great search term for further information.
Or you can add new components to existing components, like a button to an existing form, from outside the app. For example, open sencha docs and then insert in console:
Ext.ComponentQuery.query("searchcontainer")[0].up().insert(1,{xtype:'button',text:'Test',handler:function(){Ext.Msg.alert('Test Button clicked');}});
You should then find a button on the top left, right of the Sencha logo. Click it.
Find existing controllers
For this, you have to find the name of the app namespace.
If it is e.g. MyApp, then MyApp.app.controllers.items contains the list of controllers. Controllers contain control logic, and the mapping between the components and the logic. When components are created, controllers attach their events to these new components. Many changes can and should be made in the component layer, because controller overrides are messy.
Find viewmodels
You're already done, ExtJS 4.2 does not support them.
Changing models
If you want to change models, be cautious: There is no supported function to add fields to a model. You can override the model prototype, and push more entries into the fields array. But if you have any model instances (records) already running around by that time, they are not updated and any existing warranty is voided.
That said, you find them in MyApp.model. You can e.g. get all fields of the Sencha Docs' Comment model using Docs.model.Comment.prototype.fields, or even push another field in.
I have an existing page in Ember.js for flagged content and displays it. I want to have this content be drag-and-droppable. Doing this in a new model is as trivial as adding a sortOrder property and binding the values, but I want this to only apply to the model within this view. Any pointers? Can link to a JSbin if needed
I am researching ways on how to dynamically add form fields for nested models and stumbled accross the nested_form plugin by ryanb. No doubt this is a a great piece of code, but I wondered why does it have to be so sophisticated?
Example: A form for creating / adding a project has one or more tasks assigned. The user can dynamically add more tasks by clicking on a add-task button. A project must have at least one task. Each task has a name and a description.
So why not just:
- When generating the html, sourround each set of task fields with a div given an ID such as "dynamic_fields"
- When the user clicks the add-task button, call a JavaScript function via link_to_function to clone the dynamic_fields subtree. Insert the new set of fields at the bottom of the task list.
- Via JavaScript, remove the values of the newly added fields and replace the child ID with something unique (Ryan suggests using a value based on the current time)
I am aware that the nested_forms plugin also works for deeper nesting structures, but given my simple use case with only one level of hierarchy, is the approach outlined above practical? Or am I missing something important? Any guidance on this topic is appreciated.
Basically, the plugin works as you describe but a form partial is used as the basis.
The ids of nested objects must be unique and it's really easy to stick to the current millisecond time to do that.
Your way to handle the problem would work but would require some additional html to catch the required parts of the form and match what belongs to which additionnal object.
Ryan Bate's code seems complicated but it's not. It introduces complex methods only to make your view look good.
I think there is another solution to this question, a gem named cocoon.
I have a web app that I want to use Backbone.js for. I have a list of common "items" which will not be edited by the user directly. This will appear essentially as a menu to choose from on the left side of the screen. When a user selects an item an "item instance" will be generated which they will customize.
I'm just wondering if this static list of items/templates should be handled by Backbone.js or if it should just be hardcoded. The value in using a Backbone Collection/Models for the list is that each item will have another Backbone.Model property that will be instantiated when that item is selected from the list. e.g. the HTMLImageChoice item, when selected, will result in an HTMLImage being instantiated.
I'm having trouble finding easy to follow examples of Backbone.js which are not very contrived.
Collections in backbone are collections of models. It seems like you are just talking about a hardcoded array of strings, which are totally not a model. Maybe you would want to hang that array off of a model as a property, but that is as close to backbone as it will get.
What's a good way to organize views? Let's say I have a div that will contain a view from an admin panel perspective of users - there will be a list of users along with options to choose how many to display at a time, sorting options, what page to be on, filters, etc...
Would I want an outside view that contained everything except the table and data? And then an inside view that contains the table (along with the data)? And would the pagination have it's own view? And how would the pagination view use the click event to update the user view? I'm just confused on how to organize the views while still being able to have different events trigger other views to render() / collections to fetch().
So a basic hierarchy would look like:
- User View
- Table
- List of Users
- Pagination
- List of available numbers to click
- Filters
- Possible filters to apply to the data
Yet clicking a filter or number in the pagination should be able to get the collection to fetch() new data and refresh the view;
I second dogenpunk. I would have one User Collection / View. Because the whole hierarchy you describe above is about that one User Collection. All functions of it manipulate that collection and then you rerender the User View.
You could have a second User View, one single User, tied to a Model if you want to apply changes to the server for that user only.
I try to reflect my server-side MVC structure as much as I can.
Everything that can be put into a plugin, I do so, and then I keep those plugins in a separate location to the controllers which call the plugins. So in your case, the table view for the list of users would be held either in a table plugin, or possibly in the 'users' module if it was code I was really only going to use once.
If I need to override the output of the plugin, then I store the view inside the module folder.
What I try to avoid doing is storing views purely by the type of HTML inside them, so I wouldn't store a module's view as 'table' because that will get confusing if later it changes to a list. Obviously, if I have a 'table' plugin then the view for that is going to be a table, but then changing the JavaScript view means just changing the plugin call from 'table' to 'list' anyway.
My 2 cents to your original question. If you really want to make it a MV*, a pagination would be a view, your table would be a view. And have your collection to send out ( trigger ) events to change your view. And another questions I would ask myself also is what will be affected when my collection changes? For example, in your case, I don't think the collection changes will affect your userView directly, it only affects the Table and Pagination.