I am developing a JavaScript heavy single page app with Backbone.js. The goal is as follows;
The user starts with a set of multiselect boxes which are populated with filter elements to query a set of resources. These multiselect boxes are dependent of eachother. Furthermore, the elements in the multiselects are queried from the server and depend on the user that is logged in, in other words they depend on the resources that are associated with the user that is logged in.
The user fills out the multiselect boxes and presses a "filter" button. When this is pressed a collection is fetched, thereby using a set of query parameters (multiple array values) to get the set that reflects the filter elements.
When the collection is fetched the view with the resources appear. This view has multiple subviews, and it must be possible to drill down on specific resources while maintaining state (the collection that is fetched as a result of the query parameters)
How to maintain state in a Backbone app in such a use case? I've looked through many examples but all are to simple to be useful.
I am new to backbone.js and trying to develop a single-page app using Backbone.js. In my limited understanding of backbone.js documentation, I did not come across a better way of maintaining state using backbone.js core. However, in the past, I have worked with jStorage: http://www.jstorage.info/ , a simple wrapper plugin for Prototype, MooTools and jQuery to cache data (string, numbers, objects, even XML nodes) on browser side. It is simple to integrate and get started. In my app, I am going to use this for the time being... I thought this could be shared...hence I mention it here when I came across this question... I hope this would be of some help
Related
Well, as technology progresses, issues we solved long ago crop up again.
Back in the dark ages, when PHP and ASP were considered awesome, we always had a problem with view states. If you had a page with say a dozen select combo boxes on it, your user chooses some combination and hits next, then realizes they screwed up and hit the back button on the browser, the combo boxes would be back in the default state, usually with option[0] selected. In order to prevent this, we had to write boatloads of boilerplate code that would save the state of those combo boxes to a cookie, or session variable, or something so that when the user hits the back button, we can reload the combo boxes back to the state they were in when they left.
This problem was compounded even further if you had a datagrid on the screen. Because then you would have to come up with some slick way of saving that grid somewhere to prevent from having to hit the database again.
Then came the light. Browser developers realized that most web developers were on the verge of going back to writing terminal programs in Cobol due to this issue and added UI caching to the browsers. This allowed us webdevs to not have to worry about this anymore except in odd situations.
So, life was good. Then someone came up with the bright idea of trying to replicate GWT without all the hassle and the web explodes with all these javascript frameworks. The one im dealing with specifically at the moment is AngularJS 1.2.10 with Angular-UI. I have until Friday (most likely wednesday tho) to make an initial assessment on if this technology is a viable alternative to our current standard (thats pretty much universally hated) JSF.
So, i follow some guides, pound my head against the desk a few times, and I have an angular app with 3 actual HTML pages, each HTML page with 2 views.
Before you go there, understand we can't use it unless we can do multi-page JS apps. Some of the applications that this will be worked into have been in development for a decade or more and its simply not financially practical to scrap an the entire UI and start over again. We would instead be doing things like taking these 50 struts pages and converting them to angular/rest and linking them seamlessly back into the remaining 800 struts pages of the application.
So in my exercise of playing with this, I encounter my old nemesis. Back button view state issues.
I have been playing with the UI-route system. The fact that I can deep link using the route system solves part of my problems. But, if say I have a search page like this:
view-search
combo: search type [member,nonmember]
combo: result type [detail,summary]
combo: search state {all the states]
textbox: contract number
etc etc etc
And various combinations of combo box selections and text entries comes up with a list of 1000 people. Now the user selects one of those people on the data grid and it takes you to view-detail. Well the fact that you can use routing to do something like index.html#detail/bob is cool, but if the user realizes thats the wrong bob and hits the back button, they get a blank search screen again and they have to enter everything over and worse yet, send another search to the database to rebuild the datagrid. Some of these screens have 50 or more options to choose from when searching for data so trying to put all of them into the URL routing sounds completely impractical to me.
Now in my research I found this post:
Preserve state with Angular UI-Router
And that has promise mainly because I have a view state object that I can store into a Redis database or a session EJB for cases when the user actually jumps out of angular and into the legacy Struts application, then back buttons back into the angular application, but the fact still remains that on some of these pages, that is a huge amount of boilerplate code that we would have to write in order to make it work.
I don't really mind the idea of having to manually save off the view state object and read it back in from a Redis server or something anytime a user enters or leaves an HTML page in the system. What i'm really looking for is a way to automatically generate the object that is to be saved without having to write volumes of boiler code.
Is this possible? I keep reading the ui-route documentation but it doesn't look like this is addressed, at least not that i've translated yet.
If this is possible, what controls should I be looking at?
thanks
-------------- Edit
I just thought of something. There is one central scope to each of the single page applications. (Im basically going to be building a multiple single page apps and hooking them together) So if i use a naming convention, something like this
$scope.viewstate.view-search.searchType
$scope.viewstate.view-search.resultType
$scope.viewstate.view-search.searchState
Then the viewstate object should simply be a js array and when I create a function to move to struts.do, i can simply save that array off to the Redis server as a nested map object. Then when my user back buttons back into the angular app, i can capture that using the route system and retrieve that viewstate object from Redis and insert it back into my scope, thereby rebuilding the scope for the entire single page app in one shot.
Would that work?
I believe that you have a very complicated issue of trying to keep the view states between your varying pages with the amount of data in your pages. I think that the only real effective way to do this is to write an angular service that you can then pass to your various pages. as You already know the service is a singleton that you can use in various controllers and could be utilized to maintain the view state as you described. here take a look that this link and see if it will help: http://txt.fliglio.com/2013/05/angularjs-state-management-with-ui-router/
After some thought what you suggest in your edit might work, but I would still use a service to retrieve that array of data, as it would make it easier to reinsert in to angular scope
I am exploring something similar for an Angular app that I am writing. Keeping a user login during a page refresh is easy. Displaying the state on the page after a refresh is an entirely different problem.
How long must the state be persisted? I'm evaluating two possibilities.
First, saving the state (current form values or whatever) to the database. As the page changes, incrementally save the state to the database. On a browser refresh check the database for saved values.
Second is to use local browser storage. This is 5 megs of storage. 5 megs is a lot of text. Again this data would incrementally be saved into storage. When the browser refreshed, simply load data from localStorage.
I have a nontrivial Angular SPA that uses ui-router to manage multiple views, many of which are visible at the same time. I need models to be visible across controllers, so I have services written that allow me to have controllers pull down fresh copies of model data that has been updated.
I apologize in advance for the length of the question, but I will state the problem then state what I have done to address issues I'm sure others in the Angular community have struggled with.
I believe my problem is not understanding the lifecycle of controllers / views, because I get behavior where a controller initializes correctly the first time I go there, but then seems to never run again, even when I navigate to it using something like $state.go("state name").
In one view (contrived example), I show a summary of information about a customer, and in another view I allow a user to update that customer's more detailed profile. I want a user to edit, say, the customer last name in the detailed view, and have the summary view automatically recognize the change and display it.
I have a fiddle that shows 3 views and a simple password changing Service. The flow goes like this:
You can see each view gets initialized and displays the initial password retrieved from the service. All views are in sync with the DataService.
The middle view allows you to enter a new password and change the one stored in the service. Console logging confirms that the service picks up the new password just like you would expect.
(odd behavior #1) When the DataService receives the new password, I would expect the other 2 views (top and bottom) to display the new one. They don't... they still display the initial password.
There is a button to allow a user to go to another state via $state.go("state name") (a child state of the original) which also retrieves the password and displays it. This works the first time (see #5). Now the top view shows the outdated password, the middle view shows the new one, and the bottom one shows the new one as well. This seems normal, since the new view is invoked after the DataService contains a new password value.
(odd behavior #2) If I click back in the middle view and change the password again, and click the button to change states again, the bottom view (which updated just fine in step #4) no longer updates its copy of the password. Now all 3 views show different passwords, even though I am using a single service to pass values between controllers as suggested pretty much everywhere you look for Angular best practices.
Some possible solutions:
Tim Kindberg has an excellent slideshow here that seems to recommend using ui-router's state heirarchy to "pass" data among views that need to pick up values from other views. For a smaller-scale app I think I would settle on this, but I expect our application to have 30+ views displaying data from over 100 REST endpoints. I don't feel comfortable maintaining an application where all the data is being shared by a complex inheiritance tree. Especially using a routing framework that is at version 0.2.8.
I can use events to have controllers listen for changes in the data model. This actually works well. To accommodate performance concerns, I am using $rootScope.emit() and a $scope.$onRootScope('event name') decorator I found on here. With this approach I am less concerned about event performance than I am about wiring this huge app with a bunch of event listeners tying everything together. There is a question about the wisdom of wiring a large app using angular events here.
Using $watch on the value in the DataService? I have not tried this but I am hesitant to hinge an app this size on hundreds of $watches for performances reasons.
A third-party library like bacon.js (or any of a dozen others) that may simplify the event spaghetti, or create observable models that my controllers can watch without the risk of $digestageddon. Of course, if there is a concise way to handle my issue using Angular, I'd prefer not to muddy the app with 3rd party dependencies.
Something that lets controllers actually reference .service modules by reference, so I don't have to depend on tons of event wiring, complex state hierarchies, 3rd party libraries, or seeding the app with hundreds of $watches and then kicking off $digests to update controllers' references to Angular services?
Some solution that relies on time-tested OO and design patterns and not a 3rd-party library or framework that has a version that starts with 0.*.
Thanks in advance... I appreciate the help!
This is no problem of ui.router. If you intend for your model (your data service) to be a single source of truth, you have to refrain from destroying it.. err.. the reference to it that is. And in your case, assigning a primitve (a string) directly to the scope, instead of a reference to it. In other words...
var password = {pw:'initial value'};
and then later setting/binding only on
password.pw = newpassword
{{password.pw}}
Heres a fiddle. And also here is a short little read on scopes, It also includes a video of an angular meetup where Misko talks about "always have(ing) a dot in your model" link and how the $scope is a place to expose your model, not be your model. (aka not a place to assign primitives like password = 'initial value')
Hope this helps!
try remove the animation property of your ion nav view.
remove the property
animation="slide-left-right"
it would be ok.
I have an app where the menu system is built dynamically using metadata fetched at startup. Based on this data, and menu selections, I need to craft a "filter box" where user can input search criteria. The "main" View consists of a filter box plus a search results panel where result(s) are rendered in accordance with their classes.
Can I model the Filter Box as a Backbone.js Model? It does not have any data fetches from the backend as its composition depends entirely on the menu selections + the metadata? E.g. when user selects "Sales" menu then the filter box might prompt for "Sales Order Number" whereas when user selects "Material" then the filter box might prompt for something else.
I would then use this widget as component of the "main" View, along with a set of results views made up on the fly. As users make their menu selections, this main View will un-render the existing filter box and recompute and re-render a new one. Other components on the screen could query the Filter Box for its settings.
The examples I have seen so far always have a url and a server fetch, save, etc. The only url-free example on the tutorial page says it is a "contrived" example. I was wondering if a backend provider is necessary and programming will be full of gotchas without conforming to this requirement.
Thanks.
You can have models without url property defined. One of the building blocks of Backbone is the Sync object, that will help you when pulling and pushing data, ideally from/to REST endpoints. For this to work you need to tell where the data are served, and to do so you set a value to url on Models or Collections.
If you don't need server comunication but you just want to use the utilities provided by simple Model or Collection (such as event handling, filtering, etc..) you just don't set url and you are good to go (just keep in mind that methods like fetch or save won't work).
Yes you can use Backbone for your DOM logic too. A model doesn't need to represent data from the server. Do whatever you like with the few basic elements of Backbone, simply use them when you feel like it'd do a great job :)
I'm diving into the whole "single page application" and Backbone.js (specifically Marionette) stuff. I'm working on a decently complicated application. I'm wondering how you set up the router to handle nested views so the "containing views" are also rendered. For instance let's say I have an Admin section and under that I have a Users section. Under Users I have tabs to "Add User" and "Search Users".
If I've selected "Add User", I imagine my URL has the fragment "#admin/users/add". That routes to a view that has the add user form. However, if you go directly to that URL I want to show that form again, but also the top navigation bar with "Admin" highlighted, the admin specific sidebar with my admin navigation and have "Users" button highlighted. I need the whole HTML page, not just the Add User ItemView.
How to do say when the page first loads (refresh or from a bookmark), to load the html structure and "parent views" as well? Thanks!
This is the way you need to think about the app's behavior:
the controller needs to create view instances and pass in the data they need (models, collections, etc.), and then display the views within the regions
the ONLY thing the router does is is match a URL to a controller action (i.e. "if this URL is entered in the address bar, the application should launch this controller action")
So bascially, this is your problem: you're missing a controller action (e.g. MyApp.AdminApp.Users.New.newUser() which will render the views you want, which you can then call from your router...)
One thing that helps (although not related to the problem you're currently facing), is to always call the navigate method with trigger: false (which is the default). This ensures that your app is behaving properly and that the router is limited to matching URLs to controller actions.
Regarding the menu (with highlighted current entry), I would make it a separate Marionette module (https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.application.module.md), and have a collection of models (that don't get saved on the server) to list your menu entries. That way, you can manage the current entry by setting its model activeattribute to true (and checking that attribute in the view to highlight the current entry).
This is probably a lot to take in at first, but after a few more hours of working with Marionette it will all make sense...
(Shameless plug: I'm writing a book on Marionette that takes you from beginner to fully independent with Marionette. In there, I'll be covering this type of functionality, especially the menu management and how to highlight the current option. If you'd like to check it out, there's a free 55-page sample at http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf and the book (which is still being written) is at https://leanpub.com/marionette-gentle-introduction)
I had the same question a time ago, what I strongly recommend is to get involved with Marionette layouts, collections, composite and collection views, regions and how to display content within templates.
Is not hard as you keep reading tutorials, I recommend reading lostechis.com which is a very educational blog from the creator of Marionette, Derick Bailey, also the Marionette Official website.
This is just about educating yourself doing tests and when some question comes to your mind search it and if not found dont doubt to ask it right here.
For the side bar and some other stuff you can just use JQuery-ui or Twitter bootstrap, it is very easy to integrate them with backbone/marionette views, but you just have to read to achieve that.
Which you luck.
[EDIT]
Similar question to Complex nesting of partials and templates
As of now, is it better to use Angular-UI state solution or should I stick with ng-includes ?
So far I had one view per URL in my AngularJS application. I need to build a new view, which should have 3 tabs and I'm having troubles trying to figure out how I'm going to design the view - architecture-wise that is.
Note that the business model object behind these 3 tabs is the same one.
The first tab is for viewing and editing data on the business object. So that's already two 'views' within the first tab.
The second tab is for viewing a paged-table showing data from a child collection of the business object.
The third tab does the same thing as the second one but for another child collection.
Obviously, I do not want to load the entire business object at once. I'll load the collections only if the user navigates to the 2nd or 3rd tabs.
My main concern right now is how am I going to organize the views ? AngularJS has this limitation of only 1-view per page.
Also, I need to handle browser history, so the URL must change when a tab is selected, but I should have to reload any data (i.e I must not reload the controller).
Any tips would be much appreciated.
For the record, I ended up using ui-router and its state management, which is awesome. It took me a bit of time to understand the concepts and to put that in practice, but I managed to build a pretty complex set of layouts effortlessly !