d3.js save states - javascript

I use D3.js to visualize a couple of datasets in an interactive and dynamic way. Users can explore all graphs and retrieve individual views on the data by loading combining additional data and views. I want users to be able to share their treasure found in the data via mail, facebook etc. but in a way the new user, visiting the shared "snapshot" could move on exploring the data. So I need to
persist the current state of my dynamic webpage
be able to load and display this state fast.
bind all events that have been bound in the moment the user snapshot
As a as simple as possible example (there are going to be various graphs and lots of events), imagine having a simple d3 linegraph and
graph.selectAll("path").on('mouseover', function(d){
$.get("ajaxFunction",{"d" : d} ,function(jsonData) {
//INIT NEW SVG
});
});
This new dynamically loaded page contains i.e. several svgs. But if I simply save the shape and position of every svg, it could be hard to keep track of all current event-bindings.
And if I save every action the former user did, how could I reload the snapshot efficiently?

The simplest thing i can imagine would be looping over all the nodes storing their identity and their state as a hash then sending this hash as json using ajax back to the server and putting the hash in a databas along with a key that is returned to the user as an url.
when visiting the url you then load the d3js object and run through the hash setting the state of each node to what it was.
getting the state of the nodes would require some more knowledge in d3js.
What kind of data are you displaying? You should perhaps beable to add an event register to your js and record all the events executed. trough the event callers.
for instance say I have the following data
{"Things":{
"Balls":{
"Footballs":"",
"Basketballs":""
},
"Persons":{
"Painter":{
"Pablo":"","Robert":""
},
"Programmers":{
"Robert":""}}}
You should and want to show/hide nodes of this tree on mouse click.
You should be able to do somthing like this
var eventlog = [];
$(".nodes").onClick(function(this){
if (isClosed(this)){
function_to_open_node();
eventlog.append({"action" : "open", "id" : this.id})
}else{
function_to_close_node();
eventlog.append({"action" : "close", "id" : this.id})
}
})
This way you should end up with somthing like this
[{"action" : "close", "id" : "id"},{"action" : "close", "id" : "someotherid"},{"action" : "close", "id" : "someid"}]
Thus you have a storable state! that can be executed.

Related

Reading OData contexts in onInit of controller

I've tried to prepare data from an OData source to show it in a bar graph in my fiori app. For this, I setup the OData model in the manifest.json. A test with a list, simply using
items="{path : 'modelname>/dataset'}
works fine and shows the content.
To prepare data for a diagram (VizFrame), I used the onInit() function in the controller of the view (mvc:XMLView). The data preparation is similar to the one discussed in question.
At first I obtain the ODataModel:
var oODataModel = this.getOwnerComponent().getModel("modelname");
Next I do the binding:
var oBindings = oODataModel.bindList("/dataset");
Unfortunately, the oBindings().getContexts() array is always empty, and also oBindings.getLength() is zero. As a consequence, the VizFrame shows only "No Data".
May it be that the data model is not fully loaded during the onInit() function, or do I misunderstand the way to access data?
Thanks in advance
Update
I temporary solved the problem by using the automatically created bind from the view displaying the data as list. I grep the "dataReceived" event from the binding getView().byId("myList").getBindings("items") and do my calculation there. The model for the diagram (since it is used in a different view) is created in the Component.js, and registered in the Core sap.ui.getCore().setModel("graphModel").
I think this solution is dirty, because the graph data depends on the list data from a different view, which causes problems, e.g. when you use a growing list (because the data in the binding gets updated and a different range is selected from the odata model).
Any suggestions, how I can get the odata model entries without depending on a different list?
The following image outlines the lifecycle of your UI5 application.
Important are the steps which are highlighted with a red circle. Basically, in your onInit you don't have full access to your model via this.getView().getModel().
That's probably why you tried using this.getOwnerComponent().getModel(). This gives you access to the model, but it's not bound to the view yet so you don't get any contexts.
Similarly metadataLoaded() returns a Promise that is fullfilled a little too early: Right after the metadata has been loaded, which might be before any view binding has been done.
What I usually do is
use onBeforeRendering
This is the lifecycle hook that gets called right after onInit. The view and its models exist, but they are not yet shown to the user. Good possibility to do stuff with your model.
use onRouteMatched
This is not really a lifecycle hook but an event handler which can be bound to the router object of your app. Since you define the event handler in your onInit it will be called later (but not too late) and you can then do your desired stuff. This obviously works only if you've set up routing.
You'll have to wait until the models metadata has been loaded. Try this:
onInit: function() {
var oBindings;
var oODataModel = this.getComponent().getModel("modelname");
oODataModel.metadataLoaded().then(function() {
oBindings = oODataModel.bindList("/dataset");
}.bind(this));
},
May it be that the data model is not fully loaded during the onInit()
function, or do I misunderstand the way to access data?
You could test if your model is fully loaded by console log it before you do the list binding
console.log(oODataModel);
var oBindings = oODataModel.bindList("/dataset");
If your model contains no data, then that's the problem.
My basic misunderstanding was to force the use of the bindings. This seems to work only with UI elements, which organize the data handling. I switched to
oODataModel.read("/dataset", {success: function(oEvent) {
// do all my calculations on the oEvent.results array
// write result into graphModel
}
});
This whole calculation is in a function attached to the requestSent event of the graphModel, which is set as model for the VizFrame in the onBeforeRendering part of the view/controller.

How do i flag a change in a JSON object using polymer and passing data from javascript then render some child elements

I'm generating a JSON with a random set of X's and O's and trying to plot them into a simple grid using a custom element in polymer. When I first open the page everything runs fine and i see a different grid each time. I've also added a button on an index.html page that regenerates the JSON with a new set of X's and O's, but the polymer element wont flag an event change or update its child elements with the new data.
It only seems to be an issue with my JSON object, because if i change it to a string, i get a change notification each time...
i have written a simple "forceDB.js" script that generates the JSON and passes it to the polymer element with .setAttribute('layout', data) how do I notifiy the change to polymer and have all the children elemtents of my polymer script update?
The JSON object looks like this
let data = {
"a1":"",
"a2":"",
"a3":"",
"b1":"",
"b2":"",
"b3":"",
"c1":"",
"c2":"",
"c3":""
};
my polymer script side of the element looks like this...
enter code here
<script>
Polymer({
is:'grid-layout',
properties:{
layout: {
type: Object,
reflectToAttribute : true
},
observers: 'layoutChanged(layout.*)'
},
setLayout: function(newdb){
console.log('new - ' + JSON.stringify(newdb));
this.set('layout', newdb);
},
layoutChanged: function(changedthing){
alert("Layout Changed!");
},
});
</script>
I think that I may be missing a key point in polymer or maybe I'm doing something wrong. But I have a simple X's and O's game that I'm developing to try come to grips with polymers data binding principals and seem to be falling short where.
Could it be that the layout property needs:
notify: true
And it might be helpful to see how you are data binding the layout property to the child elements in the HTML.

jQuery, Bootstrap and big list (7000+ items) with search/typeahead

I know I haven't got any code example, but it's due to the fact that I'm unsure on how to proceed.
I'm building a site with jQuery and Bootstrap, and are going to display a list of around 7000+ items.
I'm using $.getJSON(...) to get the list of items from my PostgreSQL database. This call goes pretty quick.
I would like to create a list which is capable of typeahead search/filter, where elements are displayed corresponding to a user is typing.
I'm not interested in calling my PostgreSQL database more than once - if possible - but would also like to not to kill the browser with DOM elements etc.
What are the best way to proceed, are there any existing components in Bootstrap or...?
ForerunnerDB would be a good library to use: http://forerunnerdb.com/
It's a client side NoSQL db. You'll be able to insert your data into a collection and create a view which can handle auto binding of the data to the DOM. Loading over 7000 DOM elements is a big task, lazy loading is probably the way forward for you; my suggestion would be to display 100 records at a time and when the user is close to the bottom of the list trigger more DOM elements to be rendered:
//Take data from a collection, query it to build a view, and link it to the DOM
db.view('dataView')
.query({
/*
* Filter documents in the collection
* calling db.view('dataView').find() will now only pull records from the 'data' collection which contain a key `verified` that have the value 'true'
*/
verified: true
})
.queryOptions({
//Apply options
//calling db.view('dataView').find() will limit the result to the first hundred records
$limit: 100
})
.from('data')
.link('#targetElement', 'templateName');
To unlink the view again call db.view('dataView').unlink().
When you want to render more records, simply do the following:
var query = db.view('dataView').query();
query.$limit += 100;
db.view('dataView').query(query)
This will automatically render the next hundred records.
Using a combination of .query({}) and .queryOptions({}) will allow you to manipulate the view's data pretty much any way that you want, I've been able to build some complex filtering and searching with ease using this technique.

Kendo edit template array

I have following kendo example with a custom edit template:
In the example there is a custom edit template, so when you double click on the calendar to make a new event this will show with the custom fields.
There is a custom field for "Contacts" which has an array as data source.
This data source is an array I get from the server (takes 1-2 seconds to get).
The fact that the edit template is prepared with tags makes it not possible to simply create in my success (or done) handler of the ajax call that gets the data.
The only way I see is to have the data ready at page load so that the template picks it up.
I want however to either create the template whenever my data load is done or add my data to it after it is loaded.
To simulate the time the server takes for the data to load I use setTimeout of 1 sec, that way the edit template does not pick up the data.
To recreate:
double click on the calendar to create an event
notice that the contact field is empty (because data is not ready at page load)
Any help appreciated
This has nothing to do with the async delay. Your kontaktdata array is local to the anonymous function you pass to setTimeout, so it simply doesn't exist in the context the template is evaluated in.
Your data has to either be defined on the data model itself or in the global context.
The other problem is that the data structure itself has to exist - either a kendo.data.DataSource or an array, and you need to update it with new data if you want an existing view to be aware of that new data. If you simply replace it, the edit template has no way of picking that up immediately (if you open a new edit dialog, it will also work, of course).
So if you do this, for example, it will work:
var kontaktdata = [];
setTimeout(function(){
kontaktdata.push.apply(kontaktdata, [
{ text: "Demo B Client", value: 1 },
{ text: "Martin", value: 2 },
{ text: "Herbert", value: 3 }]);
}, 4000);

Use $.get() or this.model.save() (Backbone.js)

I have a list of images each with a 'Like' button. When the 'Like' button is clicked, an AJAX request (containing the item_id and user_id) will be sent to the serverside to record the Like (by adding a new row in the table likes with values for item_id and user_id).
The model Photo is used for the images displayed on the page. If I understand correctly, this.model.save() is used if I want to update/add a new Photo, so it is not suitable for recording 'Likes'. Therefore, I have to use something like $.get() or $.post(). Is this the conventional way?
Or do I create a new model called Like as shown below, which seems to make it messier to have a View and template just for a Like button.
Like = Backbone.Model.extend({
url: 'likes'
});
LikeView = Backbone.View.extend({
template: _.template( $('#tpl-like').html() ),
events: {
'click .btn_like': 'like'
},
like: function() {
this.model.save({
user_id: 1234,
post_id: 10000
})
}
});
In similar cases to this I've used the $.get method rather than create a new model, obviously this will depend on your application, but here are my reasons.
This case appears to have the following characteristics:
Like is a relationship between a person and a photo,
you seem to have a server side resource that accepts the photo and user ids to create this relationship already,
you probably have no other information attached to that relationship, and
you probably don't have significant view logic to go with the like itself
This is better handled by adding another attribute to your Photo object, that contains the number of likes. Then use $.get to create the like, and a 200 response will simply update the photo object to up it's count (and hence the view). Then the server side just needs to include the like count as part of it when it returns.
I'm assuming here that once a like is made you won't be updating it. If you do need to update or delete it, I might still keep using the $.get. You can add a likes array to your photo object where each element is the id of the like resource. The view will display the length of the array as the count, and if you need to delete the like, you have access to the id and you can use $.post. Just make sure you don't use .push to add values to your array since that'll bypass backbone's set method and you won't get your event callbacks. You need to clone the array, then push, and then set it when you make changes.

Categories