So i'm working with the new Ext following MVC patterns by creating controllers which listen to view events to fire methods. I have a view with a tree loaded as an admin menu and I want to listen from the 'Users' controller when the tree item called 'List all users' is clicked so I can show the grid with all users. My logic says that I have to set an id to each tree element in order to make sure I'm listening to the right and only possible one...problem is, even though I send and id to my json on each element, it never gets assigned. The element ids still have the 'ext-gen1091' type of element ids.
Any idea how do I do about assigning a unique id to each of my tree elements?
My json looks like this:
{"expanded":"true","text":"Users","id":"users","children":[{"text":"List all users","id":"userslist"....
You don't need to use HTML ids to identify the record you are handling. ExtJS provides APIs that make it easy for you to handle operations, listen to events etc on the tree or other components.
Since, you already have id assigned to each node in the tree. You can use the same id to work upon when an action / event occurs. All you have to do is add appropriate event listener methods to the tree panel. For example if you are tracking click on a node you can make use of the itemclick. The methods parameters provide you access to all information you will need.
here is the skeleton code:
itemclick: function(Ext.view.View this, Ext.data.Model record, HTMLElement item, Number index, Ext.EventObject e) {
// Access the record using record or you can access the HTML using item variable.
}
Similarly you can make use of other events and track user actions.
Related
tl;dr
What would be best approach for structuring an angular app which supports filtering and sorting on the server side using radio button filters on client side
Context of the app:
I have a sample movie list app, where movies have genre and style to categorize them. They can be sorted based on name, rating, year of release. The backend is very clear, I pass the filters to url in the form of query parameters and data is returned and pagination is also addressed. From the client side I create the url and attach the string params to it. However I have tried few implementations of filters and sorting on the client side and wasnt satisfied. every implementation involves using radio buttons for filters. The following approaches were used by me.
Approaches used:
Create few filters based on genres and styles of movies, launch an event when one radio button is clicked, pass the filter-radio model in the event. Listen for the event in a movieListDirective and then create the url followed by triggering the server call.
Create filters and pass the data in a service, launch an event whenever a radio button is clicked. Listen for the event and receive the data from the service. Create the url and initiate the server call.
Not yet used this approach but thinking of giving it a try
On click of radio button push the data in the browser url in form of query parameters. Listen for url change event inside the directive and trigger the server call
I'm also thinking of using UI router. Create an abstract state for filter and sort button. Put the movieListDirective inside the child state.
I'm just not satisfied with my 2 approaches and think that there's a huge room for improvement. Can anyone please suggest a very scalable approach or something to improve the existing approach which I'm using. Thanks in advance.
**I'm using IONIC. I would like to take advantage of the pull to refresh and infinite scroll features. These have to be put inside the ionic-content directive. Hence the approach used should satisfy this requirement **
Well, if I were you I would change a variable in my $scope and listen its changes to request again with your filters.
I made a Plunker to help you.
https://embed.plnkr.co/cNZ1Um7FycaPBjef5LI1/
In this Plunker, I added the ng-model to my radio buttons. When these radio buttons are selected, they change my variable with their values.
<input type="radio" value="new" ng-model="area">
This radio button above change $scope.area value to "new". Then, my controller listen to this change event and call my $scope.requestAPI function.
$scope.$watch('area', function() {
$scope.requestAPI($scope.area, $scope.category);
});
This function use the values of $scope.area and $scope.category to make a request. Changing their values, you change the request.
It is exactly the feature that you need.
Set up:
Index.html
-- custom-obj1.html
-- custom-obj2.html
I have my index.html with an iron-page with two items/pages
custom-obj1: Element that draws a few paper-card elements drawn with a iron-aja a source.
custom-obj2: Same as above but fetching data from ID from clicked element from custom-obj1.
What I'm not sure about here is how to write the event and where to store and read the ID from the first action. Can I data bind clicked id into my second custom element?
You should use a parent element so you can pipe them trough polymer data binding with a common property.
So your setup should look like this:
Index.html
-- parent-element (property: data)
-- custom-obj1 (property: data)
-- custom-obj2 (property: data)
So if you change anything in the data binding it will be synchronized trough the parent element.
More information to design apps and thinking in Polymer from Kevin Shaaf (Polymer Summit 2015): https://www.youtube.com/watch?v=ZDjiUmx51y8
Of corse you can listen the property-name-changed event if you add the notify: true to it's property and you can give that to anything, but I suggest to use the Polymer Data Binding system.
I have applied ng-click on an element and within the function, I want to access the DOM element itself. I could do that with :
var element = $document[1].getElementById('<id of the element>');
However, the problem I am facing is that when that element is clicked, it's class changes. But the element I get using the above method is the previous state of the element before the click. How can I get access to the new attributes of an element after the click is performed ?
Update
I am using AngularJS' smart-table for displaying data fetched from backend. The library offers sort functionality but it sorts the data which is already fetched from the DB and is present in front end. I wanted to tweak it so that when I click the sort button, I should be fetching data from the backend and update the rowCollection so that the table refreshes. Now, in order to trigger the API call, I was thinking of using ng-click event on table headers. Also, I need to know whether I need to sort in ascending order or descending order. So, for that, smart-table automatically appends a class sort-ascent or sort-descent to the table header when it is clicked. So, I thought maybe if I can access that, then using the combination of the header column (sort key) and the class (sort order), I can hit the backend API and fetch the appropriate data.
I understand the solution looks more of a hack then a proper way of doing things.
Maybe you should look at this answer : Accessing clicked element in angularjs
You can access by $event.target
<button ng-click="yourSubmit($event)"></button>
I have a list of items. They are stored in backbone pageable collection.
They are displayed like this
|---item1---------------------------|
|---item2---------------------------|
|---item3---------------------------|
|---item4---------------------------|
|---item5---------------------------|
|---item6---------------------------|
|---item7---------------------------|
<< 1,2,3...end >>
User can click on individual item to open detail view in a separate page. Detail view has listeners initialized
when it's created. Those listeners are bound to the item model.
Since the detail view is huge, I cache it in the DOM by toggling the visibility.
The subsequent click on the item will toggle the cached view.
------ here is the problem -----
When item list is switched to another page, the collection is reset (by paginator). And all the models previously stored in the collection is dereferenced and
a new set of models is created. So after the page is switched back and forth, the previously opened item has a different copy of itself stored
in the collection. So when I change the name of the item in the detail view (in the view cache), the name in the item list is not changed.
The views are out of sync! because they are referencing to different models.
Not sure if anyone else encounter this before. If you do, please share with me how you solve it.
Thanks very much.
The most straight-forward way to maintain a fresh reference between your list view items and the corresponding detail view, on page change, is to re-render the detail view. But I'm assuming this options is not acceptable within the scope of your project.
What I often do, when I have the task of forming relationships within logically separate views is use listeners. As long as the views share a unique identifier (for example, they both share a model, or at least identical model ids), I can always send a message that will reach the view I'm interested in.
For this you'll need a centralized event hub, which with Backbone is trivially easy to generate. In some appropiately global variable (like, for example, MyApp) we simply do:
MyApp.EventBus = _.extend({}, Backbone.Events);
Set up the detail view
On the detail view initialize function I would drop this listener,
initialize: function () {
// Listen to a toggle visibility on this view
this.listenTo(MyApp.EventBus, 'detail-view:toggle-view', toggleView);
},
toggleView: function (id) {
if (this.model.id == id) {
// Show this view if I have the passed id
this.$el.show()
// Notify the parent list item view that its detail view exists
MyApp.EventBus.trigger('detail:view:exists', true);
} else {
// Hide all other views
this.$el.hide();
}
},
changeName: function () {
// logic that parses DOM user input to
// local variable name
// We now trigger an event 'detail-view:change:name', and we send as
// parameters our model's id and the new name
MyApp.EventBus.trigger('detail-view:change:name', this.model.id, name);
}
Setting up the list item view
The list item view will want to listen to a name change (or any other model property in the detail view that you want the list item to be aware of). So we'll set up a handler for the 'detail-view:change:name' event.
We'll also want to wire our click handler to toggle the visibility of the list item's detail view. The tricky part is to handle the event that a view has not been rendered yet (I'm assuming you're lazy loading the detail view). So we set up a second listener for the detail:view:exists event the detail view triggers when it catches a detail-view:toggle-view event. If we don't hear the detail:view:exists event from the targeted detail view in a timely manner (I'm using 100 ms, but you can play around with that to suit your needs), then we render the view.
initialize: function () {
// Listen to when the detail associated with this list item changes
// the the list item name
this.listenTo(MyApp.EventBus, 'detail-view:change:name', onNameChange);
// Set a property in this view if its detail view exists
this.listenTo(MyApp.EventBus, 'detail:view:exists',
_.bind(function () { this.detailViewExists = true; }, this));
// Create a debounced function that tests whether this view's
// detail view exists
_.debounce(_.bind(this.handleViewState, this), 100);
},
events {
click: 'toggleDetailView'
},
toggleDetailView: function (id) {
MyApp.EventBus.trigger('detail-view:toggle-view', this.model.id);
this.handleViewState();
},
// Debounced function that will wait 100ms asynchronously for the
// detail view to respond. If the detailViewExists bit is not set to true
// then we assume the view does not exist and we render it
handleViewState: function () {
if (!this.detailViewExists)
// The view does not exist, render and attach the view
// Set the bit to false to allow testing in the event that the detail view
// is destroyed in the future
this.detailViewExists = false;
},
changeName: function (id, newname) {
if (this.model.id == id) {
// Change the name of this list item view
this.$('.item-name').text(newname);
}
The take-away
Now, the reference between these two disparate views is the shared unique identifier. Since, by design, these two identifiers are unique in their scope, and should not change, and assuming the detail view has been rendered and attached to the DOM, then regardless of the rendering its state the list item view will always be able to communicate with its detail view.
I'm working on a sample ToDo list project in Backbone and I'd like to understand how the framework would prefer me to organize its views and models in the nested list scenario.
To clarify what I mean by that, my single-page Backbone app should display lists of ToDo lists. From the backend standpoint, there's a List resource and an Item (a single entry in a todo list) resource. Something along the lines of:
Monday chores
Pick up the mail
Do the laundry
Pick up drycleaning
Grocery list
Celery
Beef
You get the idea...
Since mine is a Rails 3.2 app, I'm vaguely following the Railscasts Backbone.js tutorial, so that's where I'm getting the current design from. I would love to know if I'm wildly off the Backbone-prescribed pattern, or if I'm on the right track!
I thus far have:
ListsIndex View //index of all lists
\-- ListsCollection
\-- ListView / Model //individual list
\-- ItemsIndex View //index of items in one list
\-- ItemsCollection
\-- Item View / Model //individual todo item
The flow would be:
On router initialize, fetch() collection of lists on /lists backend route. On the 'reset' event for the collection part of ListsIndex, execute render() on each of the items in the collection, appending to the list index view template.
In the initialize method of each Item View (is this where you'd wire-up the second level fetch?) fetch() the items from the /lists/:id/items backend route into an ItemsCollection specific to that view.
In the same method, instantiate an ItemsIndex object and pass the collection into it. Once again, in ItemsIndex, have a 'reset' event handler for when the collection is populated, at which point it should render each fetched model from the item collection and append them to its own view.
I'm essentially taking the design of the List and mirroring it down one level to its items. The difference is that I no longer have a router to rely on. I therefore use the initialize method of ListView to a similar effect.
Yay / nay? Super wrong? Thanks!
TL:DR; 1) I would bootstrap your initial data instead of a fetch() reset(). 2) You can do a fetch in the initialize of a View as you need it. Or you could load the data at the start. Just remember that if you fetch in the init, the async nature won't have the data ready at render. Not a problem if you have a listener waiting for that sync/add/etc. 3) I don't know what you mean by itemIndex object but you can create objects and add to them collections as you need them. Or you can just bake the in at the start if you know all your lists are going to have a collection eventually. You can reset if you want (fetch automatically does this unless you give it option {add:true}) or just add them in one by one as they come in although reset(), remove prior views, render all views seems to be the common way people do things with a complete fetch().
I think it looks pretty good. The nice thing about Backbone is that you can do it many different ways. For example, your number 2 says to wire up a second fetch() from the view. You could do that if you want to lazy load. Or you could just grab all the data at app start before anything is done. It's really up to you. This is how I might do it.
This is how I might make an app like this (just my preference, I don't know that it's any better or worse or if its the same as you described.)
First I would create a model called ListModel. It would have an id and a name attr. This way, you can create many separate lists, each with their own id that you can fetch individually.
Each ListModel has an ItemsCollection inside of it. This collection has a url based on the ListModel it is a part of. Thus, the collection url for ListModel-1 would be something like /list/1
Finally you have ItemModel which is a resource id and text.
ListCollection
ListModel // Monday Chores
ItemCollection
ItemModel // Mail
ItemModel // Laundry
ItemModel // Drycleaning
ListModel // Grocery
ItemCollection
ItemModel // Celery
ItemModel // Beef
So in this little display you'll notice I didn't put anything to do with views in yet. I don't know if it's more of a conceptual thing but this is what the data hierarchy looks like and your views can be, should be totally independent of it. I wasn't exactly sure how you were including the views up above but I thought this might make it clearer.
As for defining these structures, I think two things.
First, I'd make sure my ListModel is defined in my collection. That way I can use the collection add(hash) to instantiate new models as I produce / add them.
Second, I would define the ListModel so that when one is created, it automatically creates an ItemCollection as a property of that ListModel object (not as an attribute).
So ideally, your ListModels would be like this:
ListModel.ItemCollection
Before the app initializes, I would bootstrap the data in and not fetch(). (This kind of addresses point 1 you make) Ideally, when your Backbone application starts it should have all the necessary data it needs from the get go. I would pass in the head some data like this:
var lists = [listModel-1-hash, listModel-2-hash];
Now when the app fires up, you can instantly create these two lists.
var myLists = new ListCollection();
_.each(lists, function(hash) {
myLists.add(hash); // Assumes you have defined your model in the ListCollection
}
Now your List Collection has all the list models it needs.
Here is where views come in. You can pass in anything to any view. But I might break views down into three things.
AppView, ListModelView, ItemModelView and that's it.
Imagine a structure like this:
<body> // AppView
<ul class="List"> // ListModelView
<li class="Item"></li> // ItemModelView
</ul>
<ul class="List"> // ListModelView
</ul>
</body>
When your start your app and create an AppView, inside AppView you'd generate each ListModelView and append it to the body. Our lists are empty. Maybe when you click on the it lazy loads the items. This is how you'd hook it up.
// In ListModelView
events: {'click':'fetchItems'}
fetchItems: function() {
this.model.itemCollection.fetch(); // Assumes you passed in the ListModel into view
}
So since I bootstrapped the data to begin with, this fetch() call would be your "second" fetch. (I'm addressing point 2 you made.) You can fetch it in your initialize. Just remember that it is an asynchronous function so if you need them at render time, it won't work. But, what you can do is add event listeners to this view that are listening for add events to your itemCollections.
this.model.itemCollection.on('add', this.addItemView, this);
addItemView() will generate new instances of the itemViews and append them.
As for point 3, you can instantiate a collection at that point you need it and throw it into your ListModel. Or you can do what I did and make sure all your models always have an ItemCollection. This depends on your preferences and goals. You probably didn't need all this but I felt like illustrating it out for some reason. I dunno, maybe it helps.