Giter VIP home page Giter VIP logo

Comments (64)

seb78 avatar seb78 commented on September 12, 2024 2

Just a little trick I did to know if statechange was triggered by pushState/replaceState.
In my state object I use for pushState I add a timestamp :

var timestamp = new Date().getTime();
var stateObj = { id: unique_id_action , createtime: timestamp };
History.pushState(stateObj, "page 2", "/")

And in my listener I verify the time difference, if it's very small I consider I come from pushState and do nothing:

History.Adapter.bind(window,'statechange',function(){
var State = History.getState();

var createtime = State.data.createtime;
var timestamp = new Date().getTime();
var diff = timestamp - createtime;

if(diff>500){
//manage back and forward button here
...
}

});

It works fine for me, I hope it can help ;-)

from history.js.

amertum avatar amertum commented on September 12, 2024 1

Another very easy workaround is using History.getCurrentIndex()

When you push your state

History.pushState({_index: History.getCurrentIndex(), someData: ...}, someTitle, someUrl);

and then in the event binding

History.Adapter.bind(window,'statechange',function(){
    var currentIndex = History.getCurrentIndex();
    var internal = (History.getState().data._index == (currentIndex - 1));
    if (!internal) {
        // your action
    }
});

from history.js.

martin-g avatar martin-g commented on September 12, 2024

https://gist.github.com/909482
A simple page to reproduce the problem.

Steps:

  1. Open console (Firebug or Developer tools)
  2. Click on any link (the list items)
    In console you will see the timestamp for each click
  3. repeat 2) few more times
  4. click browser back button
    in console you'll see 'statechange' event for all pushed states but the last one

Commenting
History.Adapter.trigger(window,'popstate');
at line 1737 in history.js fixes this problem.
The new problem now is that the statechange events pop in wrong order, 1 -> 2 -> 3, instead of 3 -> 2 -> 1.

from history.js.

balupton avatar balupton commented on September 12, 2024

Hi Martin,

I cannot replicate the issue. The demo works perfectly, as well as tests in all browsers for the v1.7 release.
http://balupton.com/sandbox/history.js/demo/

The reason for the manual trigger of popstate is to fix that "new problem" you've reported.

I'd suggest not using the isInternal flag, there is no use for it. The ajax request should be performed in the statechange handler. Refer to https://gist.github.com/854622 for a best practice solution.

Hope that clears some things up.

from history.js.

martin-g avatar martin-g commented on September 12, 2024

It is very strange to me that you trigger 'statechange' as soon as I call pushState().
I expect 'statechange' events only when the browser back/fwd buttons are clicked. Those I'm interested in and I want to react to.
With the current behavior my callback is called immediately.

See http://diveintohtml5.org/examples/history/brandy.html, this is the main example for http://diveintohtml5.org/history.html. There is gallery.js with a handler for 'popstate' at the bottom. It is called only when I use browser's back/fwd buttons or window.history.back/forward/go() are used. It doesn't fire when I click the prev/next links in the page. And this is the idea in HTML History API.

from history.js.

martin-g avatar martin-g commented on September 12, 2024

Your example (gist 854622) is nice and it seems History.js is designed to support it. Whenever you click a link you cancel the event and fire statechange. In the handler you actually do the work to load the new content.

In my case the link is not always an anchor (tag "a"), or it may be anchor but has onclick handler and doesn't use the href attribute. So my onclick handler does the work and finally registers an event in the history (via pushState()). Without 'isInternal' my statechange handler will revert the work that I just did in onclick.

That's why I (and Johan in the forums) need the "isInternal hack". But even with it it still doesn't work because the history behaves as queue, not as stack.

from history.js.

martin-g avatar martin-g commented on September 12, 2024

Ah, I just noticed you closed the ticket as "cannot reproduce" :-)
That's because you didn't use my code, but yours ...
My code shows different use case.

from history.js.

balupton avatar balupton commented on September 12, 2024

Hi Martin,

Not really sure what you are asking of me?

The current code works in all browsers, as intended. Your recommendation for commenting out that particular line, breaks other functionality as you have mentioned therefore introducing bugs.

I feel the bug is with your implementation, rather than with History.js as I've already pointed out by the best-practice gist.

If you can provide a practical use case which the way History.js hinders you, which can't be coded in the best-practice way, then I will be willing to look into it. However as the issue currently stands, there is no onus on myself to look into this further than I already have.

Feel free to fork the code, and make the modifications you need to. Be sure to test it (using the tests folder) in all supported browsers to make sure you don't break anything.

from history.js.

martin-g avatar martin-g commented on September 12, 2024

Hi Benjamin,

I am not sure whether you saw the link in my second comment here: https://gist.github.com/909482.
This is the mini application that exposes the problem.
And yes - I'm going to fork the project and see whether I can make my use case working.
Thanks for your support!

from history.js.

markjaquith avatar markjaquith commented on September 12, 2024

Martin,

Try this: http://pastie.org/1780113

History.js doesn't work the way you think. I had the same difficulty. In normal HTML5 History, you'd do your XHR or whatever, and then do pushState on completion. History.js assumes that you want to do the pushState first, and then use the statechange event that it fires to trigger your event.

Benjamin, it'd be nice if we could distinguish between pushState()/replaceState()-initiated statechange events and user-initiated history traversals. The "pushState first, AJAX later" paradigm is fundamentally flawed. If an XHR fails or is delayed, you have a URL/content disagreement. It doesn't make sense to do the fast part first, followed by the slow and unreliable part.

Users of History.js just need a way to distinguish between user-initiated events and code-initiated events (which they don't care about).

from history.js.

martin-g avatar martin-g commented on September 12, 2024

Hi Mark,

Thanks!
Your approach looks like what I want. I'll try it in my real application. I think if I combine it with my "isInternal" flag it will behave as I want it.
For now I use http://tkyk.github.com/jquery-history-plugin. It doesn't look that cool as HTML5 enabled history management but at least fits perfectly for my needs.

Thanks again!

from history.js.

jhonyf avatar jhonyf commented on September 12, 2024

Benjamin, first off, great work on the project.

The issue here is that according to the best practice for History.js is to fire the ajax request inside the statechange event. But there are times where this can't happen because there's already another function handling the ajax request.

For example, Rails 3 introduced unobtrusive Javascripts where the user needs to specify in their html element "data-remote = true" and Rails will handle the Ajax request automatically. Ideally in html5, I would add an oncomplete handler to the ajax request and call history.pushState to update the url. I would also create a listener for the onpopstate and inside that I just call $.getScript(location.href);

Here's a Railcast on this approach http://asciicasts.com/episodes/246-ajax-history-state

Unfortunately it's challenging to use History.js since it will cause an issue with this approach. The statechange/popstate event is being triggered on History.pushState. In the statechange/popstate function, we need a utility to determine if the event is caused because of a pushState or from an actual popstate. Depending on the type of event, one can then determine whether or not to ignore it (ie when it's from a pushState) or to fire an ajax request (ie from a popstate)

from history.js.

balupton avatar balupton commented on September 12, 2024

Thanks for the detailed response, I will look at the screencast and see what I can do.

My big concern with this, is then instead of having the detection of internal vs external in statechange, it would then be in your success handler of the Ajax request.

There are some other use cases I'm looking into such as caching the result of Ajax requests in the state data, I'll see what I can come up with. If I can create a History.getState().internal flag, would that work?

from history.js.

markjaquith avatar markjaquith commented on September 12, 2024

If I can create a History.getState().internal flag, would that work?

That'd be a start. Might also be useful to tell which kind of event triggered it. pushState(), replaceState(), other.

from history.js.

balupton avatar balupton commented on September 12, 2024

The internal flag is now there in dev for HTML5 browsers, still need to add it for HTML4 browsers. It was flipping hard to add.
c79c17f

from history.js.

scmmmh avatar scmmmh commented on September 12, 2024

Perhaps as an alternative a mask_event flag could be added to the pushState/replaceState calls. If the flag is set then the onpopstate event is not triggered. Then it wouldn't be necessary to check where the event came from when handling it.

from history.js.

matteomelani avatar matteomelani commented on September 12, 2024

What the status of this issue? Is the flag going in?

Thanks!

from history.js.

balupton avatar balupton commented on September 12, 2024

The 1.8 version still has a while left. I'll be releasing a v1.7.1 release sooner, to address the most urgent issues (session state storage, native adapter, etc). If you'd like to speed up release of the v1.8 release, check it out and get the tests passing in all the different browsers - the codes in there, just need to ensure it works in all the different browsers (the hardest bit).

from history.js.

wuiscmc avatar wuiscmc commented on September 12, 2024

Just wondering, how is the state of the art so far?

I've tried the development plugin and the functionality commented above is just working good. The thing is I feel like that branch is a little bit discontinued, isn't it?

The problems I have with the dev plugin are related to the state store system. When I reload the page, if I click on the back button, it triggers the statechange event, but the state received is not the expected. This behavior doesn't happen in the master branch plugin, so would it be possible to see the dev branch with the new changes on master? (I think they'll fix all my probs).

thanks and great work! (seriously)

from history.js.

BennyJohansen avatar BennyJohansen commented on September 12, 2024

Hi balupton

First of all thanks for providing History.js, i just recently started using it and I quickly found the need for the internal flag that was added in your dev build.

It works perfectly on the websites I am developing when browsers are HTML 5, but of course it would be nice if the same flag could be added for HTML 4 to make it backward compatible.

Do you by any chance have an idea of when this will happen, I know your are busy and only working on this project in your spare time?

Best regards

Benny

from history.js.

fabalint avatar fabalint commented on September 12, 2024

Hi all,
I added three lines to the History.js, v.1.7.1, to see where the event came from: back/forward, popstate, or the initial state of the page:
In the function 'History.onPopState = function(event,extra){..' you have this switch:
// Fetch State
if ( stateId ) {
// Vanilla: Back/forward button was used
newState = History.getStateById(stateId);
newState.origin = 'bfw';
}
else if ( History.expectedStateId ) {
// Vanilla: A new state was pushed, and popstate was called manually
newState = History.getStateById(History.expectedStateId);
newState.origin = 'add';
}
else {
// Initial State
newState = History.extractState(document.location.href);
newState.origin = 'ini';
}

The 'newState.origin=...' are the additions. The full method is at http://fabalint.info/history_js/origin.mod.and.tile.html , along with a partial update helper (so on back/forward the page parts can be updated.)
Enjoy(:
Balint

from history.js.

leos avatar leos commented on September 12, 2024

Balint, that's a great little hack - the problem is that it doesn't work with the HTML4 support in IE - any idea if it's possible to tell whether the event came as a result of a Back/Forward or an explicit pushState call?

My big concern with this, is then instead of having the detection of internal vs external in statechange, it would then be in your success handler of the Ajax request.

Benjamin, why don't you want it in the success handler? If this is a rehashed point, I'm happy to go read another thread/post.

As far as the user is concerned, the state change happens only once the data has been loaded. In our use case, we're using it for a set of filters as checkboxes. Clicking on each filter fires off an ajax request. Earlier requests get aborted if they haven't returned by the time the user clicks on another checkbox. We don't want to have any of the intermediate states show up in the history (referring to the ajax requests that were aborted). Clicking 'Back' should take the user directly to their previous set of loaded data.

from history.js.

emmanuel avatar emmanuel commented on September 12, 2024

@balupton — First, thanks for building History.js.

I don't really have much to add to this discussion, but I wanted to add a +1 for providing some way to tell if a statechange event was triggered by pushState/replaceState or by user action (browser back/forward buttons).

I recently started using History.js in a Backbone/PhoneGap project and then abandoned History.js because the 'statechange on pushState' behavior of History.js basically forces a stateless style of app development when integrating with the Backbone router (designed around the specified HTML5 pushState/replaceState/popstate behavior), which is problematic when doing client-side MVC. See, eg.: http://lostechies.com/derickbailey/2011/08/03/stop-using-backbone-as-if-it-were-a-stateless-web-server/ for some remarks about why this is problematic.

from history.js.

rxaviers avatar rxaviers commented on September 12, 2024

+1 and following updates...

from history.js.

semaperepelitsa avatar semaperepelitsa commented on September 12, 2024

@seb78, thanks for the hack :-)

from history.js.

leos avatar leos commented on September 12, 2024

@semaperepelitsa - While it's a nice hack, anyone using @seb78's hack should be aware that it's not really dependable. Anything could happen to cause the timestamps to be different and the messed up behavior would be really hard to reproduce and/or debug. I would hesitate very strongly before sticking it into our production code.

from history.js.

woto avatar woto commented on September 12, 2024

+1 for this useful feature for Rails developers for
form_for or link_to with :remote flag
sorry for bad English

from history.js.

STRML avatar STRML commented on September 12, 2024

This pull request fixed the issue for me - 'statechange' is only fired when using the back/forward button, and not when using pushState.

from history.js.

 avatar commented on September 12, 2024

Hi, below is how the issue can be resolved:

var timestamps = []; // Array of unique timestamps.


// Push state
function pushState(title, url, anydata){

    // Creating a unique timestamp that will be associated with the state.
    var t = new Date().getTime(); 
    timestamps[t] = t;

    //Adding to history
    History.pushState({timestamp:t}, title, url);
}


// Listening
History.Adapter.bind(window,'statechange',function(){

    var State = History.getState();

    if(State.data.timestamp in timestamps) {        
        // Deleting the unique timestamp associated with the state
        delete timestamps[State.data.timestamp];
    }
    else{
        // Manage Back/Forward button here
    }           
});

from history.js.

DavidJapan avatar DavidJapan commented on September 12, 2024

Haven't worked out the details, but this timestamp idea is helping me distinguish a manual pushState from statechange triggered by the back button. Thanks mdakota.

from history.js.

DavidJapan avatar DavidJapan commented on September 12, 2024

@MDakota I found your timestamps very useful. It works whether you make timestamps an array or an object, but it made more sense to me as an object (associative array) just because I could examine the object more clearly in Firefox's console. In the console, when I use an array, it just looks like this [ ] whether it contains a timestamp or not. If I use an object, the empty object (indicating I used the back button) looks like this { } and after using pushState I get a nice clear indication Object { 1361395647783=1361395647783} Maybe it doesn't matter, but it's just easier during debugging and developing.

from history.js.

agamemnus avatar agamemnus commented on September 12, 2024

I have implemented this to handle the problem:

window.History.Adapter.bind (window, 'statechange', function() {
if (typeof History.Adapter.artificial != "undefined") {delete (History.Adapter.artificial); return}
.... your code here .....
})
window.History.pushStateOriginal = window.History.pushState
window.History.pushState = function (state, string, url) {
window.History.Adapter.artificial = true
window.History.pushStateOriginal (state, string, url)
}

from history.js.

phamtn8 avatar phamtn8 commented on September 12, 2024

Do we have an update on this issue. I am working with the HTML4 version as I use IE9, and window.History.Adapter.bind (window, 'statechange', function() {}) does not even get invoked when I click browser back and then forward. Please provide on an update please. Thank you

from history.js.

egucciar avatar egucciar commented on September 12, 2024

I'm having this same issue:

I am working with the HTML4 version as I use IE9, and window.History.Adapter.bind (window, 'statechange', function() {}) does not even get invoked when I click browser back and then forward. Please provide on an update please.

It would be very nice if history.js worked as described for back/forward. These cause state changes, but doesn't trigger onstatechange.

from history.js.

egucciar avatar egucciar commented on September 12, 2024

So I took a look at the examples as @balupton said the library worked in the examples. I knew for a fact it did NOT but I took a look anyway, and saw he was using jquery.history.js. I was using native.history.js, so figuring this might be the root of my issue, I replaced the script in my app to use jquery.history.js instead of native.history.js. Now back() DOES properly fire statechange event. This is good because I would have had to switch to another library.

@phamtn8 not sure if your issue was ever solved, but if your project already uses jQuery try using the jquery.history.js. Also, @balupton if you could look into and solve the issue I've described it would be greatly appreciated. :)

from history.js.

rxaviers avatar rxaviers commented on September 12, 2024

@balupton since this is an active project whose goal is to polyfill/fix history API. It would be really nice if we could have a definitive fix or at least an official best practices addressing this issue (which persists for so long).

Do you have any suggestion of what could be done / who could look at it?

from history.js.

agamemnus avatar agamemnus commented on September 12, 2024

I have enhanced my function which I posted a few months earlier and made it into a simple wrapper for history.js, (though I haven't tested this in IE9 yet.. should work, maybe?):

#347

from history.js.

mreinstein avatar mreinstein commented on September 12, 2024

echoing @rxaviers would definitely like to see a recommended fix for this. 3 years and no update. I'd rather use a faithful polyfill but if not I guess Backbone.History could be ripped out.

from history.js.

tommueller avatar tommueller commented on September 12, 2024

thanks so much @amertum! Your solution works like a charme!

from history.js.

manishie avatar manishie commented on September 12, 2024

@amertum, great workaround, thanks!

from history.js.

cervengoc avatar cervengoc commented on September 12, 2024

I had problems with the "current index" workaround, because if I pressed back button and then forward, statechange was not fired because I reactivated the peek state.

My simple workaround for this problem (using some jQuery as well):

// handling push and replace at once and setting a flag that indicates an internal state change. Also does some data normalization and random value injection for forcing state change (that was my special need)
History.pushOrReplaceState = function (data, title, url, isPush) {
  var func = isPush ? "pushState" : "replaceState";
  if (data === undefined || data === null)
    data = {};
  if (typeof data !== 'object')
    data = { value: data };
  History.internalChanging = true;
  History[func]($.extend(data, { _ran: Math.random() }), title, url);
};
// checks if the flag is set, and resets it immediately
History.isInternalChange = function () {
  var b = History.internalChanging;
  History.internalChanging = false;
  return b;
};
// shorthand for subscribing statechange event, using a callback which accepts the current state as parameter
History.onStateChange = function(handler)
{
  if (!$.isFunction(handler)) return;
  $(window).on("statechange", function () {
    if (History.isInternalChange()) return;
    handler(History.getState());
  });
};

For me this works as expected, and shorter in usage. If anyone finds something wrong with it please feel free to suggest any addition or fix.

from history.js.

dherran avatar dherran commented on September 12, 2024

I have submitted a pull request with an internal flag for the latest HistoryJS version. When the back/forward button is used, a new flag is returned in the getState() function.

var state = History.getState();
if (state.internal) {
    // back/forward event, do something
} else {
    // manual pushState event, do something else
}

https://github.com/browserstate/history.js/pull/401/files

from history.js.

ximi avatar ximi commented on September 12, 2024

@dherran This looks like exactly what I need. Super easy. No idea why something similar hasn't been added in order to close this longstanding issue.

from history.js.

kdon80 avatar kdon80 commented on September 12, 2024

Thank you dherran!!! That's exactly what I was looking for, and it works perfectly!! This is how I'm using it in my code:

$(document).ready(function() {
History.Adapter.bind(window, 'statechange', function () {
var state = History.getState();

    if (state.internal) {    //this catches only browser back or forward button
            if (History.getStateByIndex (History.getCurrentIndex() - 1).data.state == 'book'){
            returnBackToTopics();  //display this specific page
        } else{
            window.location.replace(document.URL);
        }
    }
});

from history.js.

TomK avatar TomK commented on September 12, 2024

@cervengoc thanks for your example, i've implemented a similar method.

@dherran I'm not sure if i'm reading your PR correctly, on back/forward you are setting isInternal to true, and when triggered via pushState/replaceState it is false? I figure that should be the other way around? In my interpretation Internal would mean "from my internal code".

from history.js.

mdingena avatar mdingena commented on September 12, 2024

Hi. I'm new to GitHub, but I signed up for an account just to comment with my use case. I'm using https://github.com/browserstate/history.js/pull/401/files with success. But one thing that bugged me was that the very first State which is created on the landing page does not contain the internal flag.

So I added a line in the storeState function:

// Set State.internal
newState.internal = ( typeof newState.internal === 'undefined' ? true : newState.internal );

Now it means that if a visitor uses the back button to go all the way back to the first landing page, internal flag is set and your code knows the statechange was because of a browser back button.

from history.js.

nevace avatar nevace commented on September 12, 2024

Thanks @dherran and also @mdingena for your fixes. This is working how I expected to now and is a great library. How do we get this merged as @balupton isn't maintaining this anymore?

from history.js.

balupton avatar balupton commented on September 12, 2024

@nevace I'm happy to add new owners and maintainers to the project.

from history.js.

cervengoc avatar cervengoc commented on September 12, 2024

I agree with @TomK, I also think that as far as terminology goes, the word "internal" should refer to programmatically changing state, and using Back/Forward button should be the "non-internal". At least I would expect it this way.

from history.js.

mdingena avatar mdingena commented on September 12, 2024

It's probably internal because the state change is triggered from the browser itself, rather than - say - clicking a link on a page.

from history.js.

cervengoc avatar cervengoc commented on September 12, 2024

Yes, that can be a reason. However, in most programming environment "internal" means rather something like I wrote. Maybe would be better to call it something like clientChange, which is more self explanatory IMHO.

from history.js.

nevace avatar nevace commented on September 12, 2024

@balupton I'm happy to help maintain this along with others. I know the amount of time you were spending got too much so if any others would also help then that would be great.

from history.js.

TomK avatar TomK commented on September 12, 2024

@cervengoc has a point, remove the problem altogether by calling it something different. Perhaps isNavigation ?

but then clicking a state change link is also navigation :/

from history.js.

cervengoc avatar cervengoc commented on September 12, 2024

That's why I thought of isClientChange, because that covers everything that the "client" does, not the "developer".

from history.js.

balupton avatar balupton commented on September 12, 2024

@nevace sweet will add you, then you can add others as it goes - the main issue is the tests, the tests require manual runs - for two reasons:

  1. older versions of safari on iOS (not relevant anymore, as no one uses them) - would have different behaviour of a programatic state change versus an actual back button click by a human
  2. browsers sometimes either forget state data or persist state data when: you do a few state changes, then navigate to a different website, then hit the back button

So, for each version, I was spending a day or more testing a huge array of browsers manually - if that can be automated using better tooling, then history.js would be easily maintained, however such better tooling is not something I have any experience with.

We have a slack community at https://slack.bevry.me that I can add a history.js room for if maintenance is going to resume.

from history.js.

balupton avatar balupton commented on September 12, 2024

@nevace invite sent

from history.js.

balupton avatar balupton commented on September 12, 2024

Anyone else want to help maintain too?

from history.js.

balupton avatar balupton commented on September 12, 2024

Moving maintenance discussion over to #450

from history.js.

picard102 avatar picard102 commented on September 12, 2024

Where in the file would I insert

// Set State.internal
newState.internal = ( typeof newState.internal === 'undefined' ? true : newState.internal );

as per @mdingena's suggestion? The forward button motion seems to work, but any click on the back button results in a false flag for internal.

from history.js.

mdingena avatar mdingena commented on September 12, 2024

In the storeState function, as I suggested.

from history.js.

picard102 avatar picard102 commented on September 12, 2024

I tried adding it there, line 1104 in @dherran's edit, still didn't get a true flag on internal when using the back button. http://dev.podcamptoronto.com/sessions/

from history.js.

nevace avatar nevace commented on September 12, 2024

I'm using this which I've modified with the above and works as expected regarding back/forward.

jquery.history.js.zip

I'm not sure what the best way is to get it to work on first load, I just ended up manually calling History.pushState() on first load. Not very nice to have duplicate entries but works for back/forward (I need to re-init animations etc. on different pages so need to know when back/forward is pressed).

from history.js.

melroy89 avatar melroy89 commented on September 12, 2024

Why is this issue still not merged into master?? In the main-while you can get the file here:
https://github.com/danger89/history.js/blob/master/scripts/bundled/html5/jquery.history.js

from history.js.

Sitronik avatar Sitronik commented on September 12, 2024

@seb78 Thank you, you very helped me.

from history.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.