कई अजाक्स कॉल के लिए jQuery कॉलबैक


132

मैं एक क्लिक ईवेंट में तीन अजाक्स कॉल करना चाहता हूं। प्रत्येक अजाक्स कॉल एक अलग ऑपरेशन करता है और अंतिम कॉलबैक के लिए आवश्यक डेटा वापस करता है। कॉल स्वयं एक-दूसरे पर निर्भर नहीं हैं, वे सभी एक ही समय में जा सकते हैं, हालांकि मैं एक अंतिम कॉलबैक करना चाहूंगा जब तीनों पूर्ण हो जाएंगे।

$('#button').click(function() {
    fun1();
    fun2();
    fun3();
//now do something else when the requests have done their 'success' callbacks.
});

var fun1= (function() {
    $.ajax({/*code*/});
});
var fun2 = (function() {
    $.ajax({/*code*/});
});
var fun3 = (function() {
    $.ajax({/*code*/});
});


जवाबों:


112

यहाँ एक कॉलबैक ऑब्जेक्ट है, जिसमें मैंने लिखा है कि आप या तो एक कॉलबैक को आग लगा सकते हैं, एक बार पूरा हो जाने के बाद या प्रत्येक को अपना कॉलबैक होने दें और सभी को एक बार पूरा करने के लिए आग दें:

नोटिस

JQuery 1.5+ के बाद से आप आस्थगित विधि का उपयोग कर सकते हैं जैसा कि दूसरे उत्तर में वर्णित है:

  $.when($.ajax(), [...]).then(function(results){},[...]);

यहां आस्थगित का उदाहरण

jQuery के लिए <1.5 निम्नलिखित काम करेगा या अगर आपको अपने ajax कॉल को अज्ञात समय पर करने की आवश्यकता है जैसा कि यहां दो बटन के साथ दिखाया गया है: दोनों बटन क्लिक किए जाने के बाद निकाल दिए गए

[उपयोग]

एकल कॉलबैक के लिए एक बार पूरा: कार्य उदाहरण

// initialize here
var requestCallback = new MyRequestsCompleted({
    numRequest: 3,
    singleCallback: function(){
        alert( "I'm the callback");
    }
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.requestComplete(true);
    }
});

प्रत्येक के लिए अपने स्वयं के कॉलबैक जब सब पूरा: कार्य उदाहरण

//initialize 
var requestCallback = new MyRequestsCompleted({
    numRequest: 3
});

//usage in request
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the first callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the second callback');
        });
    }
});
$.ajax({
    url: '/echo/html/',
    success: function(data) {
        requestCallback.addCallbackToQueue(true, function() {
            alert('Im the third callback');
        });
    }
});

[कोड]

var MyRequestsCompleted = (function() {
    var numRequestToComplete, requestsCompleted, callBacks, singleCallBack;

    return function(options) {
        if (!options) options = {};

        numRequestToComplete = options.numRequest || 0;
        requestsCompleted = options.requestsCompleted || 0;
        callBacks = [];
        var fireCallbacks = function() {
            alert("we're all complete");
            for (var i = 0; i < callBacks.length; i++) callBacks[i]();
        };
        if (options.singleCallback) callBacks.push(options.singleCallback);

        this.addCallbackToQueue = function(isComplete, callback) {
            if (isComplete) requestsCompleted++;
            if (callback) callBacks.push(callback);
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.requestComplete = function(isComplete) {
            if (isComplete) requestsCompleted++;
            if (requestsCompleted == numRequestToComplete) fireCallbacks();
        };
        this.setCallback = function(callback) {
            callBacks.push(callBack);
        };
    };
})();

1
महान कोड उदाहरण के लिए धन्यवाद! मुझे लगता है कि मैं बाकी हिस्सों के माध्यम से अपना रास्ता रोक पाऊंगा। एक बार फिर धन्यवाद!
मिस्टरइसाक

कोई संभावना नहीं, खुशी है कि यह मदद की! मैं इसके साथ दूर चला गया: पी अभी भी इसके लिए कुछ और विचार है। मेरी योजना है कि आप इसे आरंभीकरण पर कई अनुरोधों को निर्दिष्ट करने की आवश्यकता नहीं है।
सुभेज

अच्छा, मुझे आशा है कि आप करेंगे! बहुत दिलचस्प है, मैं एक अतिरिक्त कॉलबैक जोड़ने के विकल्प में फेंकने का सुझाव दूंगा। मैंने किया और जो मैं करना चाहता हूं, उसके लिए यह सही है। एक बार फिर धन्यवाद!
मिस्टरइसाक

मैंने किया था, लेकिन उपयोग के मामले में इसे जोड़ना भूल गया: पी का उपयोग setCallbackकतार में और अधिक जोड़ने के लिए। हालांकि अब जब मैं इसके बारे में सोचता हूं ... मुझे इसे addCallbackया किसी तरह का कुछ कहना चाहिए ... और न केवल तब से सेट किया जाता है जब यह कतार में जोड़ रहा है
सबमेज़

क्या singleCallbackदूसरी पंक्ति में चर अनावश्यक है? या क्या मैं कुछ न कुछ भूल रहा हूं?
nimcap

158

ऐसा लगता है कि आपको इसके कुछ उत्तर मिल गए हैं, लेकिन मुझे लगता है कि यहां कुछ ध्यान देने योग्य बात है जो आपके कोड को बहुत सरल कर देगा। $.whenv1.5 में jQuery पेश किया गया। ऐसा लग रहा है:

$.when($.ajax(...), $.ajax(...)).then(function (resp1, resp2) {
    //this callback will be fired once all ajax calls have finished.
});

यह यहाँ उल्लेख नहीं देखा, आशा है कि यह मदद करता है।


6
जवाब के लिए धन्यवाद, यह निश्चित रूप से मेरे लिए जाने का रास्ता है। यह jQuery का उपयोग करता है, यह कॉम्पैक्ट, छोटा और अभिव्यंजक है।
निकम्मा

2
सुभाजे का जवाब बहुत अच्छा है लेकिन वास्तव में, यह सही है।
मोलोमबी

9
मैं मानता हूं कि यह एक अच्छा समाधान है जब आपके पास jQuery 1.5+ है, लेकिन उत्तर के समय आस्थगित कार्यक्षमता उपलब्ध नहीं थी। :(
सुभेज 16:14

13

किसी भी वस्तु की आवश्यकता को देखते हुए खुद को खराब न करें। सरल में एक चर होता है जो पूर्णांक होता है। जब आप एक अनुरोध शुरू करते हैं, तो संख्या बढ़ाएँ। जब एक पूरा हो जाता है, तो इसे घटाएं। जब यह शून्य हो, तो प्रगति में कोई अनुरोध नहीं है, इसलिए आप कर रहे हैं।

$('#button').click(function() {
    var inProgress = 0;

    function handleBefore() {
        inProgress++;
    };

    function handleComplete() {
        if (!--inProgress) {
            // do what's in here when all requests have completed.
        }
    };

    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
    $.ajax({
        beforeSend: handleBefore,
        complete: function () {
            // whatever
            handleComplete();
            // whatever
        }
    });
});

यह बहुत सरल है और पूरी तरह से काम करता है ... क्या आप थोड़ा बेहतर समझाएंगे कि अगर (--प्रोप्रोग्रेस) में क्या होता है? यह नियंत्रित करना चाहिए अगर चर inProgress == 0 और एक इकाई को इसमें घटाया जाए लेकिन मुझे कॉम्पैक्ट सिंटैक्स नहीं मिलता ...
Pitto

1
@ पिटो: उफ़ ... असल में, वहाँ एक तर्क त्रुटि है, और यह होना चाहिए if (!--inProgress)। सही संस्करण के अनुरूप है inProgress = inProgress - 1; if (inProgress == 0) { /* do stuff */ }। कॉम्पैक्ट फॉर्म "सही" (और इसलिए ब्लॉक को निष्पादित करता है) का मूल्यांकन करने के लिए उपसर्ग घटाव ऑपरेटर ( --) और नेगेटिव ऑपरेटर ( !) को जोड़ती है if, यदि inProgressपरिणाम में कमी होती है inProgress == 0और "गलत" का मूल्यांकन करता है (और इसलिए कुछ भी नहीं करता है)।
मैट

बस विकराल! आपके समय और धैर्य के लिए धन्यवाद।
पिटो

@Pitto: Humm .. क्या आप उस कोड से लिंक कर सकते हैं जिसका आप उपयोग कर रहे हैं? यह मेरे लिए काम करता है
मैट

10

यह ध्यान देने योग्य है कि चूंकि $.whenajax अनुरोधों के सभी अनुक्रमिक तर्क के रूप में अपेक्षा करते हैं (एक सरणी नहीं) जिसे आप आमतौर पर इस तरह से $.whenउपयोग करते हुए देखेंगे .apply():

// Save all requests in an array of jqXHR objects
var requests = arrayOfThings.map(function(thing) {
    return $.ajax({
        method: 'GET',
        url: 'thing/' + thing.id
    });
});
$.when.apply(this, requests).then(function(resp1, resp2/*, ... */) {
    // Each argument is an array with the following structure: [ data, statusText, jqXHR ]
    var responseArgsArray = Array.prototype.slice.call(this, arguments);

});

ऐसा इसलिए है क्योंकि $.whenइस तरह से स्वीकार करता है

$.when(ajaxRequest1, ajaxRequest2, ajaxRequest3);

और इस तरह नहीं:

$.when([ajaxRequest1, ajaxRequest2, ajaxRequest3]);

शानदार प्रतिक्रिया। अब बहुत सारे अच्छे वादे हुए हैं, उनमें से अधिकांश में एक allविधि या कुछ समान है, लेकिन मुझे मानचित्र अवधारणा पसंद है। अच्छा लगा।
ग्रेग

2
हाँ, मैं लगभग हमेशा $ का उपयोग करता हूँ। इस पर अनुरोधों की एक सरणी लागू करके, और यहाँ एक समाधान में जगह नहीं है, तो बस इसे एक अच्छा उदाहरण यहाँ जोड़ा है।
कोरी डेनियलसन

4

मुझे hvgotcodes का विचार पसंद है। मेरा सुझाव एक जेनेरिक इन्क्रीमेंटर जोड़ना है जो संख्या को पूर्ण संख्या की आवश्यकता की तुलना करता है और फिर अंतिम कॉलबैक चलाता है। इसे अंतिम कॉलबैक में बनाया जा सकता है।

var sync = {
 callbacksToComplete = 3,
 callbacksCompleted = 0,
 addCallbackInstance = function(){
  this.callbacksCompleted++;
  if(callbacksCompleted == callbacksToComplete) {
   doFinalCallBack();
  }
 }
};

[नाम अपडेट को प्रतिबिंबित करने के लिए संपादित।]


^ सहमत, इससे बेहतर समझ पाने में मदद मिली कि मुझे वास्तव में क्या चाहिए।
मिस्टरइसाक

3

EDIT - शायद सबसे अच्छा विकल्प एक सेवा समापन बिंदु बनाना होगा जो तीन अनुरोधों को पूरा करता है। इस तरह से आपको केवल एक अनुरोध करना होगा, और सभी डेटा वह है जहाँ आपको प्रतिक्रिया में होना चाहिए। यदि आप पाते हैं कि आप बार-बार एक ही 3 अनुरोध कर रहे हैं, तो आप शायद इस मार्ग पर जाना चाहेंगे। यह अक्सर सर्वर पर एक मुखौटा सेवा स्थापित करने के लिए एक अच्छा डिजाइन निर्णय होता है जो आमतौर पर छोटे सर्वर कार्यों का एक साथ उपयोग करता है। एक विचार है।


ऐसा करने का एक तरीका यह होगा कि ajax कॉल से पहले अपने क्लिक हैंडलर में 'सिंक' ऑब्जेक्ट बनाएं। कुछ इस तरह

var sync = {
   count: 0
}

सिंक स्वचालित रूप से सफलता कॉल के दायरे के लिए बाध्य होगा (बंद)। सफलता हैंडलर में, आप गिनती बढ़ाते हैं, और यदि यह 3 है तो आप दूसरे फ़ंक्शन को कॉल कर सकते हैं।

वैकल्पिक रूप से, आप कुछ ऐसा कर सकते थे

var sync = {
   success1Complete: false,
   ...
   success3Complete: false,
}

जब प्रत्येक सफलता निष्पादित होती है, तो यह सिंक में मूल्य को सच में बदल देगा। आपको यह सुनिश्चित करने के लिए सिंक को जांचना होगा कि आगे बढ़ने से पहले तीनों सही हैं।

उस मामले पर ध्यान दें जहां आपका कोई xhrs सफलता नहीं लौटाता है - आपको उसके लिए ध्यान देने की आवश्यकता है।

फिर भी एक अन्य विकल्प यह होगा कि आप अपने सफल हैंडलर में अंतिम फ़ंक्शन को हमेशा कॉल करें और क्या यह सिंक विकल्प तक पहुंच है कि क्या वास्तव में कुछ भी करना है। आपको यह सुनिश्चित करने की आवश्यकता होगी कि सिंक उस फ़ंक्शन के दायरे में है।


मुझे कुछ कारणों से आपका पहला सुझाव अधिक पसंद है। प्रत्येक अनुरोध का एक अलग उद्देश्य होता है, इसलिए मैं उन्हें इस कारण अलग रखना चाहूंगा। इसके अलावा अनुरोध वास्तव में एक कार्य में हैं क्योंकि मैं उन्हें कहीं और उपयोग करता हूं, इसलिए मैं यदि संभव हो तो एक मॉड्यूलर डिजाइन रखने की कोशिश कर रहा था। आपकी सहायताके लिए धन्यवाद!
मिस्टरइसाक

@ जिसाक, हाँ इससे निपटने के लिए सर्वर पर एक विधि डालना आपके लिए समझ में नहीं आता। लेकिन यह सर्वर पर एक मुखौटा स्थापित करने के लिए स्वीकार्य है। आप प्रतिरूपकता के बारे में बात करते हैं, एक ऐसी विधि बनाते हैं जो तीनों चीजों को संभालती है।
19


0

मुझे इस पृष्ठ पर उत्तरों से कुछ अच्छे संकेत मिले। मैंने अपने उपयोग के लिए इसे थोड़ा अनुकूलित किया और सोचा कि मैं साझा कर सकता हूं।

// lets say we have 2 ajax functions that needs to be "synchronized". 
// In other words, we want to know when both are completed.
function foo1(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo1');
            callback();               
        }
    });
}

function foo2(callback) {
    $.ajax({
        url: '/echo/html/',
        success: function(data) {
            alert('foo2');
            callback();
        }
    });
}

// here is my simplified solution
ajaxSynchronizer = function() {
    var funcs = [];
    var funcsCompleted = 0;
    var callback;

    this.add = function(f) {
        funcs.push(f);
    }

    this.synchronizer = function() {
        funcsCompleted++;
        if (funcsCompleted == funcs.length) {
            callback.call(this);
        }
    }

    this.callWhenFinished = function(cb) {
        callback = cb;
        for (var i = 0; i < funcs.length; i++) {
            funcs[i].call(this, this.synchronizer);
        }
    }
}

// this is the function that is called when both ajax calls are completed.
afterFunction = function() {
    alert('All done!');
}

// this is how you set it up
var synchronizer = new ajaxSynchronizer();
synchronizer.add(foo1);
synchronizer.add(foo2);
synchronizer.callWhenFinished(afterFunction);

यहाँ कुछ सीमाएँ हैं, लेकिन मेरे मामले के लिए यह ठीक था। मैंने यह भी पाया कि अधिक उन्नत सामान के लिए इसमें AOP प्लगइन (jQuery के लिए) भी है जो उपयोगी हो सकता है: http://code.google.com/p/jquery-aop/


0

मुझे आज इस समस्या का सामना करना पड़ा, और स्वीकृत उत्तर देखने से पहले यह मेरा अनुभवहीन प्रयास था।

<script>
    function main() {
        var a, b, c
        var one = function() {
            if ( a != undefined  && b != undefined && c != undefined ) {
                alert("Ok")
            } else {
                alert( "¬¬ ")
            }
        }

        fakeAjaxCall( function() {
            a = "two"
            one()
        } )
        fakeAjaxCall( function() {
            b = "three"
            one()
        } )
        fakeAjaxCall( function() {
            c = "four"
            one()
        } )
    }
    function fakeAjaxCall( a ) {
        a()
    }
    main()
</script>

0

यह jquery नहीं है (और यह प्रतीत होता है कि jquery के पास एक व्यावहारिक समाधान है) लेकिन सिर्फ एक अन्य विकल्प के रूप में ...।

मुझे SharePoint वेब सेवाओं के साथ काम करने में समान समस्याएं हैं - आपको अक्सर एकल प्रक्रिया के लिए इनपुट उत्पन्न करने के लिए कई स्रोतों से डेटा खींचने की आवश्यकता होती है।

इसे हल करने के लिए मैंने इस तरह की कार्यक्षमता को अपने AJAX अमूर्त पुस्तकालय में एम्बेड किया। आप आसानी से एक अनुरोध को परिभाषित कर सकते हैं जो पूरा होने पर हैंडलर का एक सेट ट्रिगर करेगा। हालाँकि प्रत्येक अनुरोध को कई http कॉल के साथ परिभाषित किया जा सकता है। यहाँ घटक (विस्तृत प्रलेखन) है:

DepressedPress.com पर DPAJAX

यह सरल उदाहरण तीन कॉल के साथ एक अनुरोध बनाता है और फिर कॉल हैंड ऑर्डर में उस जानकारी को एक हैंडलर को भेज देता है:

    // The handler function 
function AddUp(Nums) { alert(Nums[1] + Nums[2] + Nums[3]) }; 

    // Create the pool 
myPool = DP_AJAX.createPool(); 

    // Create the request 
myRequest = DP_AJAX.createRequest(AddUp); 

    // Add the calls to the request 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [5,10]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [4,6]); 
myRequest.addCall("GET", "http://www.mysite.com/Add.htm", [7,13]); 

    // Add the request to the pool 
myPool.addRequest(myRequest); 

ध्यान दें कि कई अन्य समाधानों के विपरीत (सहित, मुझे विश्वास है कि "जब" समाधान "jquery में) प्रदान किया गया है, तो यह विधि कॉल किए जाने के एकल थ्रेडिंग के लिए बाध्य नहीं करती है - प्रत्येक अभी भी (या धीरे-धीरे) पर्यावरण के अनुसार अनुमति देता है लेकिन एकल हैंडलर केवल तभी कहा जाएगा जब सभी पूर्ण हो जाएंगे। यह टाइमआउट मानों की स्थापना का समर्थन भी करता है और यदि आपकी सेवा थोड़ी परतदार है, तो प्रयास पुनः प्रयास करें।

मैंने इसे बिलकुल उपयोगी (और कोड परिप्रेक्ष्य से समझने में अविश्वसनीय रूप से सरल) पाया है। न अधिक चैनिंग, न अधिक काउंटिंग कॉल और सेविंग आउटपुट। सिर्फ इसे सेट कर दीजिए और भूल जाइए"।


0

ठीक है, यह पुराना है, लेकिन कृपया मुझे अपने समाधान में योगदान दें :)

function sync( callback ){
    syncCount--;
    if ( syncCount < 1 ) callback();
}
function allFinished(){ .............. }

window.syncCount = 2;

$.ajax({
    url: 'url',
    success: function(data) {
        sync( allFinished );
    }
});
someFunctionWithCallback( function(){ sync( allFinished ); } )

यह उन कार्यों के साथ भी काम करता है जिनमें कॉलबैक होता है। आप सिंककाउंट सेट करते हैं और आप हर एक्शन के कॉलबैक में फंक्शन सिंक (...) कहते हैं।


0

मुझे कतार की व्यवस्था करने वाले अतिरिक्त तरीकों की आवश्यकता के बिना इसे करने का एक आसान तरीका मिला।

जे एस

$.ajax({
  type: 'POST',
  url: 'ajax1.php',
  data:{
    id: 1,
    cb:'method1'//declaration of callback method of ajax1.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method1'
  eval(cb+"(JSON.stringify(data));");//here we calling method1 and pass all data
  }
});


$.ajax({
  type: 'POST',
  url: 'ajax2.php',
  data:{
    id: 2,
    cb:'method2'//declaration of callback method of ajax2.php
  },
  success: function(data){
  //catching up values
  var data = JSON.parse(data);
  var cb=data[0].cb;//here whe catching up the callback 'method2'
  eval(cb+"(JSON.stringify(data));");//here we calling method2 and pass all data
  }
});


//the callback methods
function method1(data){
//here we have our data from ajax1.php
alert("method1 called with data="+data);
//doing stuff we would only do in method1
//..
}

function method2(data){
//here we have our data from ajax2.php
alert("method2 called with data="+data);
//doing stuff we would only do in method2
//..
}

PHP (ajax1.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method1'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax1"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>

PHP (ajax2.php)

<?php
    //catch up callbackmethod
    $cb=$_POST['cb'];//is 'method2'

    $json[] = array(
    "cb" => $cb,
    "value" => "ajax2"
    );      

    //encoding array in JSON format
    echo json_encode($json);
?>

-1
async   : false,

डिफ़ॉल्ट रूप से, सभी अनुरोध एसिंक्रोनस रूप से भेजे जाते हैं (अर्थात यह डिफ़ॉल्ट रूप से सही पर सेट है)। यदि आपको सिंक्रोनस अनुरोधों की आवश्यकता है, तो इस विकल्प को सेट करें false। क्रॉस-डोमेन अनुरोध और dataType: "jsonp"अनुरोध सिंक्रोनस ऑपरेशन का समर्थन नहीं करते हैं। ध्यान दें कि सिंक्रोनस अनुरोध अस्थायी रूप से ब्राउज़र को लॉक कर सकते हैं, अनुरोध सक्रिय होने के दौरान किसी भी कार्रवाई को अक्षम कर सकते हैं। JQuery 1.8 के रूप में , jqXHR ( ) के async: falseसाथ उपयोग को हटा दिया गया है; आपको jqXHR ऑब्जेक्ट की संबंधित विधियों जैसे कि या हटाए गए के बजाय सफलता / त्रुटि / पूर्ण कॉलबैक विकल्पों का उपयोग करना चाहिए ।$.DeferredjqXHR.done()jqXHR.success()


यह एक पर्याप्त समाधान नहीं है। आपको कभी भी एक तुल्यकालिक अजाक्स अनुरोध नहीं करना चाहिए।
user128216

-2
$.ajax({type:'POST', url:'www.naver.com', dataType:'text', async:false,
    complete:function(xhr, textStatus){},
    error:function(xhr, textStatus){},
    success:function( data ){
        $.ajax({type:'POST', 
            ....
            ....
            success:function(data){
                $.ajax({type:'POST',
                    ....
                    ....
            }
        }
    });

मुझे खेद है, लेकिन मैं यह नहीं समझा सकता कि मैं क्या करता हूँ cuz मैं एक कोरियाई हूँ जो अंग्रेजी में एक शब्द भी नहीं बोल सकता। लेकिन मुझे लगता है कि आप इसे आसानी से समझ सकते हैं।


इस तरह ajax का उपयोग नहीं करना चाहिए। कॉलबैक नर्क ..!
ट्रेसर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.