I'm using select2 version 4 and have a list with about 16,000 items. Predictably this is quite slow (take over 5 seconds to open sometimes) so I'm trying to find a way to speed this up.
Currently my data is being retrieved as an array using an ajax request and then inserted into the select2/dom using a for loop/appending.
$('#select').select2({
placeholder: "Select"
});
$.ajax("/Example/Data", {
type: 'GET'
})
.success(function (data, status, xhr) {
var option = '<option></option>';
for (var i = 0; i < data.length; i++) {
var curId = data[i].district_id;
var curDist = data[i].district_name;
var curState = data[i].state_short;
option += '<option value="' + curId + '">' + curDist + '('+ curState + ')' + ' - ' + curId + '</option>';
}
$('#select').append(option);
});
I've been trying to find a way to simply retrieve the data using the ajax request as I am doing now and then paginate/infinite scroll with the local but have been unable to do so
I've tried implementing this exactly: http://embed.plnkr.co/sUt9zi but I can't get it to work (maybe because it's using select2 v3.4.5) so if we could get that or something like it to work with v4 that would be great.
To prevent the 'slowness' you will need to mitigate creating and appending all 16k option elements at once. In order to do this the select2 library documents pagination using AJAX here: https://select2.org/data-sources/ajax#pagination
However, using the example you mentioned we can use a local dataset by doing something similar to:
var testData = [];
var dataSize = 1000;
// Instead of doing this use the AJAX call to poulate the data.
for (var i=0; i < dataSize; i++) {
testData.push({ text: `Data: ${i}`});
}
/*
Because you are sourcing your data via AJAX, this will
go in the success callback
*/
$("#testSelect").select2({
data: testData,
query: function(q) {
var pageLength = 50;
// Get a page sized slice of data from the results of filtering the data set.
var paged = this.data.slice((q.page - 1) * pageLength, q.page * pageLength);
q.callback({
results: paged,
more: this.data.length >= q.page * pageLength
});
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/select2/3.4.5/select2.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/select2/3.4.5/select2.min.js"></script>
<input type="text" id="testSelect" style="width: 200px"/>
Consider using server side to reduce the data instead
You are already using a remote data source, but you will need to pass some parameters to indicate which set of data is now necessary. The documentation shows doing this like:
$('#mySelect2').select2({
ajax: {
url: 'https://api.github.com/search/repositories',
data: function (params) {
var query = {
search: params.term,
page: params.page || 1
}
// Query parameters will be ?search=[term]&page=[page]
return query;
}
}
});
The query properties is important to pass the current page. This allows you to set an OFFSET in your sql query. Assuming you are using mysql your endpoint would be making a query similar to:
function getPageOfData(pageNum) {
var pageLength = 50;
var pageStart = (pageNum - 1) * pageLength;
var pageEnd = pageStart + pageLength;
var query = 'SELECT * FROM tbl LIMIT ' + pageStart + ',' + pageEnd;
}
Server side pagination is a bit complicated to set up as it requires the client to pass these parameters and the server to reply with a limited data set, but it will be the best option for handling large-ish datasets like yours.
Related
I've searched quite a bit for this answer and can't find much that covers what I need.
I have some data stored in a db table I want to populate certain drop down lists with. On the document.ready I have an AJAX call to the controller requesting the data based on a parameter I send it. The controller returns the data as Json. I'm new to the process of Json so, figuring out what to with it once it returns is where I'm stuck.
I'm able display the data returned from the controller in an alert or console.log when it returns, so I know the right values are there, but I can't figure out how to populate the dropdown list with those values. All the data is, is about 5 to 10 ints. (not returned as ints, I know, but they're things like 65, 70, 2, 10, 11) I've tried some various options and nothing seems to work.
I can static the values in an array and that actually will populate the drop down list. I've tried populating that same array with the returned data, but no success that way. Here is the ajax call:
//Fill symbols drop down list
function returnSymbols(cc) {
var sendData = JSON.stringify({ 'ul': cc });
$.ajax({
url: '/Trucking/returnSymbols',
type: 'POST',
contentType: 'application/json',
data: sendData,
success: function (data) {
//alert('success');
console.log('success, yes');
alert(data);
var numbers = [];
var obj = jQuery.parseJSON(data);
/* If I do this and static these, it does work
var numbers = [1, 2, 3, 4, 5] */
var option = '';
for (var i = 0; i < numbers.length; i++) {
option += '<option value="' + numbers[i] + '">' + numbers[i] + '</option>';
}
$('#ddlMcalSymbols').append(option); //fill ddl with these values.
},
error: function () {
//alert('Error');
console.log('Error');
}
});
}
To reiterate I have tried things like numbers.push(obj) or even. .push(data), but those aren't working.
Since the controller returns a Json value I was under the impression I needed to parse that Json in order to do anything with it. Here is the controller if it helps at all:
[HttpPost]
public ActionResult returnSymbols(string ul)
{
List<Get_CIF_SymbolsVM> symbols;
Guid newGuid = Guid.Parse(ul); //parse param as Guid
using (TruckingDb db = new TruckingDb())
{
symbols = db.GetSymbols.ToArray().OrderBy(x => x.RID).Select(x => new Get_CIF_SymbolsVM(x)).ToList();
}
var syms = (from s in symbols
where s.UniqLineType == newGuid
select s.SymbolCode).Distinct();
return Json(syms, JsonRequestBehavior.AllowGet);
}
Any help would be greatly appreciated.
EDIT: Updating the process to explain a bit more.
Had some success, but it's still not correct.
Here is the ajax call. I changed just a few items. It brings back the correct data, but it displays all array items as one line. I need each value in the array as a single value in the drop down list.
var sendData = JSON.stringify({ 'ul': cc });
$.ajax({
url: '/Trucking/returnSymbols',
type: 'POST',
contentType: 'application/json',
data: sendData,
success: function (data) {
//alert('success');
console.log('success, yes');
alert(data);
var numbers = [];
numbers.push(data);
var option = '';
//Added two for loops to show what I've tried.
for (var i = 0; i < numbers.length; i++) {
option += '<option value="' + numbers[i] + '">' + numbers[i] + '</option><br>';
}
$('#ddlMcalSymbols').append(option);
//Tried this option to fill ddl
for (var i = 0; i < numbers.length; i++) {
option = '<option value="' + numbers[i] + '">' + numbers[i] + '</option><br>';
$('#ddlMcalSymbols').append(option);
}
//This Jquery foreach only returns one value to the ddl
$.each(numbers, function (i, value) {
console.log(value);
option += '<option value="' + value[i] + '">' + value[i] + '</option>';
});
$('#ddlMcalSymbols').append(option);
},
error: function () {
//alert('Error');
console.log('Error');
}
});
It brings back the data, but in the drop down both of the for loops above fill the ddl as one long looking string. "61,62,64,66,70,71,72" .. They don't show up as single select values.
I tried parts of the code, and it seems you are overlooking that the var numbers never acquires values.
I also usually prefer to create jquery objects rather than manually compile html; it is easier to develop this way. The code fails with more detail.
Something on the lines of:
var listEl=$('#ddlMcalSymbols');
for (var key in obj) {
jQuery('<option value="' + obj[key] + '">' + obj[key] + '</option>').appendTo(listEl);
}
but in better order
Worked out a solution that while it functions, there is some odd behavior with the CSS of it. I'm using a multiselect drop down called bootstrap-select. Has a .js and .css file. I needed to fill the multiselect drop down with values from a db instead of hard-coding them in with the method.
I use a post ajax call to send a parameter to the controller which retrieves the values I need based on it. I don't know if it's the bootstrap-select or a limitation with multiselect, but it did not like displaying the Json data. My ajax call is already parsing the Json, so that wasn't it. After multiple attempts and trials I figured out the only thing that really works is with an int array. When I had the string array it would display everything as either one long string or only one value. Additionally, even now with it working as I would like, I have to reload the page every time I make a change to the .js file i'm working on. That screws up the bootstrap-select.css file. NO IDEA AS TO WHY. What happens is every 3 to 4 page reloads the values are outside the drop down list and smooshed together like a bunch of unreadable text. (See pic above) I press ctrl + shft + R to clear the chromes cached css and it goes back to how it should look and function. Long-winded, but true. Here is my ajax call with some comments, so you can see what I did. I'm sure there may be more elegant and straightforward ways of doing this, but it was an improvement on what I already had. Maybe it will help someone else.
function returnSymbols(cc) {
var sendData = JSON.stringify({ 'ul': cc });
$.ajax({
url: '/Trucking/returnSymbols',
type: 'POST',
contentType: 'application/json',
data: sendData,
success: function (data) {
var num = [];
var num1 = [];
//Push all returned values into num array
$.each(data, function (index, value) {
num.push(value);
});
console.log(num); // console out to ensure values have been pushed
//convert the string array into an int array
for (var i in num) {
num1[i] = parseInt(num[i]);
}
console.log(num1); //conosle out to ensure values have parsed correctly
fillddl(num1); // send int array to fill drop down func
},
error: function () {
//alert('Error');
console.log('Error');
}
});
}
Then the Function to actually send the values to the drop down list. Very similar to what I've found in other methods.
function fillddl(sym)
{
var s = '';
for (var i = 0; i < sym.length; i++)
{
s += '<option value="' + sym[i] + '">' + sym[i] + '</option>';
}
$(".ddlMcalSymbols").html(s);
}
you can do something like this
In action method
[HttpPost]
public ActionResult getCicitesAction(int provinceId)
{
var cities = db.cities.Where(a => a.provinceId == provinceId).Select(a => "<option value='" + a.cityId + "'>" + a.cityName + "'</option>'";
return Content(String.Join("", cities));
}
The ajax call would be like this:
$("province_dll").change(function(){
$.ajax({
url: 'getCitiesController/getCitiesAction',
type: 'post',
data: {
provinceId: provinceIdVar
}
}).done(function(response){
$("cities_dll").html(response);
});
I have an MVC application. On one page I have a partial view that displays a bootstrap-table. I have some javascript code that is triggered when a table row is double clicked. This code retrieves some values from the double clicked row and constructs a url then navigates to that url.
This works fine but the way I have created the url means that I have data visible as querystrings in the url. How can I achieve navigating without using querystrings. Oh and I need the navigation to a new window/tab if possible.
My javascript code for the double click is:
$mTable.on('dbl-click-row.bs.table', function (e, row, $element) {
var cod = $('#hdCOD').val();
var mv = mGetSelectedRow().CMonth;
var yv = mGetSelectedRow().CYear;
var diff_cv = moment.utc(cod).diff(moment.utc(yv + '-' + pad(mv, 2) + '-' + '24'), 'months', true);
var plcf = '';
switch(true) {
case (diff_cv >= 2):
plcf = 'past';
break;
case (diff_cv >= 1 && diff_cv < 2):
plcf = 'last';
break;
case (diff_cv >= 0 && diff_cv < 1):
plcf = 'current';
break;
case (diff_cv < 0):
plcf = 'future';
}
var url = 'ClaimMonth/ViewMonth?pn=' + mGetSelectedRow().Cpid + '&cm=' + mv + '&cy=' + yv + '&mt=' + plcf + '&cod=' + cod;
window.open(url, '_blank');
})
The partial view is called using razor syntax:
Html.RenderAction("GetSummaryForAdmin", New With {Key .pn = Model.PersonelNo})
I can't seem to find an equivalent function in javaScript that mimics the New With {Key .pn = Model.PersonelNo} I'm also unsure if this is the way to do this. I've thought about ajax call but I don't think I can make this open as a new window/tab.
Any help appreciated.
UPDATE
I've found the following ajax.post method that gives me the document I want... In Developer Tools (F12 in Chrome/IE) using the network tab I can see the call for the document and if I preview it, it displays the data. But the page is not displayed in the browser.
Opening in a new window/tab is not a requirement but would be nice to have.
My new code replaces...
//var url = 'ClaimMonth/ViewMonth?pn=' + mGetSelectedRow().Cpid + '&cm=' + mv + '&cy=' + yv + '&mt=' + plcf + '&cod=' + cod;
//window.open(url, '_blank');
with...
$.ajax({
type : "post",
url : "ClaimMonth/ViewMonth",
data : {pn: mGetSelectedRow().Cpid, cm: mv, cy: yv, mt: plcf, cod: cod },
success : function(response) {
window.open();
},
error : function(xhr) {
console.log("error"+xhr.status);
},
complete : function() {
}
});
and I changed my controller function to accept parameters instead of reading request.querystring
You will have to use a combination of postback and Session.
AJAX Post (as you have already) to send values back to the server.
In the MVC action method which handles that postback, save the values to Session:
Public Function ViewMonth(...) As JsonResult
Session("SavedMonth") = New MonthObj(pn, cm, cy, mt, cod)
Return Json(True)
End Function
When the AJAX Post returns successfully, open a new window with a different URL:
success : function(response) {
window.open('/ClaimMonth/AfterSave');
},
The MVC action method for that URL loads the saved data from session, then displays it.
Public Function AfterSave() As ViewResult
Dim model As MonthObj = TryCast(Session("SavedMonth"), ModelObj)
Return View(model)
End Function
Actually I have an issue with javascript. I find no solution for this problem yet.
Maybe someone of you could give me a hint.
I have a created a function, which is called by a button click.in SharePoint 2010.
The function should collect all selected / checked documents from a document library and write them into a separate box, I created. To get all selected documents works fine. But in SharePoint I have to load each element individually for details with an asynchronous request. Here comes my problem:
If I select more than one document, the variable "item" will be overwritten because of the "for" loop. In my asynchronous request success function, I use now the variable "item" again to get the details of it. So I always get the data of the last item of my selection.
Is there a way to avoid this?
Thanks for any help.
Here is my code:
function ApproveDocuments() {
var ClientContext = SP.ClientContext.get_current();
var LibraryID = SP.ListOperation.Selection.getSelectedList();
var Library = ClientContext.get_web().get_lists().getById(LibraryID); //Gets the current Library
var SelectedDocuments = SP.ListOperation.Selection.getSelectedItems(ClientContext);
for (var currentItem in SelectedDocuments) {
var item = Library.getItemById(SelectedDocuments[currentItem].id);
ClientContext.load(item, 'FileLeafRef');
ClientContext.executeQueryAsync(Function.createDelegate(this, function () {
var newElementHtml = '<div style="float:left;padding:3px;width:50px;"></div>';
newElementHtml += '<div style="float:left;padding:3px;">' + item.get_item('FileLeafRef') + '</div>';
newElementHtml += '<div style="clear:both;"></div>';
jQuery("#grol1855InfoDivData").append(newElementHtml);
}), Function.createDelegate(this, this.onLoadItemFailure));
}}
I would refactor this a bit so that you don't make an http request every time you iterate through the loop, which should also solve your over-write problem. I also declared the variables outside of the loops and make it point to the new version each iteration.
function ApproveDocuments() {
var ClientContext = SP.ClientContext.get_current();
var LibraryID = SP.ListOperation.Selection.getSelectedList();
var Library = ClientContext.get_web().get_lists().getByID(LibraryID); //Gets the current Library
var SelectedDocuments = SP.ListOperation.Selection.getSelectedItems(ClientContext);
var fileItems = [], item;
for (var currentItem in SelectedDocuments) {
item = Library.getItemById(SelectedDocuments[currentItem].id);
fileItems.push(item);
ClientContext.load(item, 'FileLeafRef');
}
ClientContext.executeQueryAsync(Function.createDelegate(this, function() {
var newElementHtml;
for (var i = 0; i < fileItems.length; i++) {
newElementHtml = '<div style="float:left;padding:3px;width:50px;"></div>';
newElementHtml += '<div style="float:left;padding:3px;">' + fileItems[i].get_item('FileLeafRef') + '</div>';
newElementHtml += '<div style="clear:both;"></div>';
jQuery("#grol1855InfoDivData").append(newElementHtml);
}
}), Function.createDelegate(this, this.onLoadItemFailure));
}
I would also really advise against writing new solutions that call any of the SOAP services or anything in _vti_bin for that matter; it's just a matter of time before those go away and your stuff won't work.
I have also tried ClientContext.executeQueryAsync in a loop but have never gotten it to work for similar reasons. I've worked around this before by using Ajax to call the SharePoint lists.asmx web service. For example:
var targetUrl = "/_vti_bin/lists.asmx";
var listName = "Shared Documents";
for (var currentItem in SelectedDocuments) {
var currentItemId = SelectedDocuments[currentItem].id;
var soapEnvArray = [];
soapEnvArray.push("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
soapEnvArray.push("<soap:Envelope ");
soapEnvArray.push("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
soapEnvArray.push("<soap:Body><GetListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"><listName>" + listName + "</listName>");
soapEnvArray.push("<viewName></viewName>");
soapEnvArray.push("<query>");
soapEnvArray.push("<Where>");
soapEnvArray.push("<Eq>");
soapEnvArray.push("<FieldRef Name=\"ID\"></FieldRef>");
soapEnvArray.push("<Value Type=\"Counter\">" + currentItemId + "</Value>");
soapEnvArray.push("</Eq>");
soapEnvArray.push("</Where>");
soapEnvArray.push("</query>");
soapEnvArray.push("<viewFields>");
soapEnvArray.push("</viewFields>");
soapEnvArray.push("<rowLimit>2000</rowLimit><queryOptions><QueryOptions xmlns=\"\">");
soapEnvArray.push("<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>");
soapEnvArray.push("<ViewAttributes Scope = \"RecursiveAll\"/>");
soapEnvArray.push("</QueryOptions></queryOptions>");
soapEnvArray.push("</GetListItems></soap:Body></soap:Envelope>");
var soapEnv = soapEnvArray.join("");
$.ajax({
cache: false,
url: targetUrl,
type: "POST",
dataType: "xml",
data: soapEnv,
contentType: "text/xml; charset=utf-8",
beforeSend: function (xhr) {
xhr.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/sharepoint/soap/GetListItems");
},
complete: function (msg) {
if (msg.status == 200) {
var totalTaskCount = $(msg.responseXML).find("z\\:row, row").length;
$(msg.responseXML).find("z\\:row, row").each(function () {
console.log(currentItemId + ": " + $(this).attr("ows_Title"));
});
} else {
//Failure
var errorCode = $(msg.responseXML).find("errorcode").text();
var errorString = $(msg.responseXML).find("errorstring").text();
if (errorString.length === 0) {
errorString = $(msg.responseXML).find("faultstring").text();
}
errorString = errorString.replace(/(\r\n|\n|\r)/gm, "");
}
}
});
}
To make SharePoint Ajax requests easier, I would recommend picking up a copy of SharePoint CAML Query Helper for 2007, 2010, and 2013. Also, although I have never used it, you may want to consider trying the SPServices jQuery Library for SharePoint to simplify the task.
THE PROMPT: We have a search that connects to an JSON API url. The search query is inside the url, and the API generates a new JSON file for every search term. We also cannot rely on the browser to cache for us, and we can't use PHP or server side caching; we need to use HTML5 LocalStorage (and we don't care that IE7 can't use it)
We need to cache every new JSON file for every new search. We want to cut down on requests per minute, so we want to use a cached version of the JSON file for repeated search terms.
WHERE I'M STUCK: What has made this difficult is caching a JSON file for each new/different search term. I have been able to cache the first search, but then all subsequent searches use the same cached JSON.
We need help rewriting this so each time a new search is made, it checks to see if the term was searched for previously and if so, grabs the corresponding JSON file. Then of course if the search term is new then cache a new JSON file for that specific search term.
WHAT I'VE TRIED: In my research I've seen a lot of very complicated solutions and I can't seem to get my head completely around all of it, some of these solutions almost worked, I think I just need a better explanation for this specific case.
I think this is the answer but I don't know how to apply it to my situation: jQuery deferred ajax cache
This is crazy and it almost works, it writes into the console when it recognizes that I've searched the same thing again, and it does stop a new request, but unfortunately the cached JSON isn't there, it returns no results.
Caching a jquery ajax response in javascript/browser
WHAT I HAVE SO FAR:
MY PSUEDO CODE:
var searchTerm = WHATEVER IS TYPED INTO THE SEARCHBOX
// The JSON file
var url = 'https://api.example.com/fake/json/path/{'+searchTerm+'}';
// Local Storage Caching Promise
var cachedData = localStorage.getItem("cachedData"),
def = $.Deferred();
if (!cachedData) {
def = $.getJSON(url, function(data) {
cachedData = data;
localStorage.setItem("cachedData", JSON.stringify(cachedData));
});
}
else{
cachedData = JSON.parse(cachedData);
def.resolve();
}
def.done(function() {
var resultHTML = '';
for(var i = 0; i < Object.keys(cachedData.things).length; i++){
$.each(cachedData, function(index, node){
resultHTML += '<li>'
resultHTML += '<h1>' + node[i].name + '</h1>';
resultHTML += '</li>';
});
}
$('div#results').html(resultHTML);
});
EXAMPLE JSON:
{
"things": [
{
"type": "thing",
"username": "randoguy",
"name": "name001",
},
{
"type": "thing2",
"username": "randoguy2",
"name": "name002",
},
...
Thank you #Ian for providing the hints to my answer!
var searchTerm = WHATEVER IS TYPED INTO THE SEARCHBOX;
// The JSON file
var url = 'https://api.example.com/fake/json/path/{'+searchTerm+'}';
// BABAM! Right here, SearchTerm + "-cachedData" gets unique cached data
var cachedData = localStorage.getItem(searchTerm + "-cachedData"),
def = $.Deferred();
if (!cachedData) {
def = $.getJSON(url, function(data) {
cachedData = data;
// BABAM! And here is where the unique cachedData is set! SearchTerm + "-cachedData"
localStorage.setItem(searchTerm + "-cachedData", JSON.stringify(cachedData));
});
}
else{
cachedData = JSON.parse(cachedData);
def.resolve(cachedData);
}
def.done(function(data) {
var resultHTML = '';
for(var i = 0; i < Object.keys(data.repositories).length; i++){
$.each(data, function(index, node){
resultHTML += '<li>'
resultHTML += '<h1>' + node[i].name + '</h1>';
resultHTML += '<p>' + node[i].owner + '</p>';
resultHTML += '</li>';
});
}
$('div#results').html(resultHTML);
});
Where would I be without StackOverflow. Thank you all!
I'm currently working on incorporating an authorization feature for Twitter following the approach described here: https://dev.twitter.com/docs/auth/implementing-sign-twitter. I'm using Ajax to send my POST 'http' request, but I've been constantly running into a '401: Unauthorized' error. My code is below:
function getTweets() {
var time = generateTimestamp();
var nonce = generateNonce();
var signature = generateSignature(time, nonce);
var headers = {
"Authorization": 'OAuth oauth_callback="http%3A%2F%2Fwww.google.com%2F", oauth_consumer_key="eEeAAz9fakedtAOlIUhPgQ", oauth_nonce="bbc34b2ca6faabogus6dfc025907fa334", oauth_signature="' + signature + '", oauth_signature_method="HMAC-SHA1", oauth_timestamp="' + time + '", oauth_version="1.0"'
};
$.ajax({
type: "POST",
url: "https://api.twitter.com/oauth/request_token",
dataType: "text",
headers: headers,
success: function(data) {
alert("Success!");
console.log(data);
},
error: function(jq) {
alert("Request Failed.");
console.log(jq.statusText);
}
});
}
function generateTimestamp() {
var currentTime = new Date;
currentTime = Math.floor(currentTime.getTime() / 1000);
return currentTime;
}
function generateNonce() {
var code = "";
for (var i = 0; i < 20; i++) {
code += Math.floor(Math.random() * 9).toString();
}
return code;
}
function generateSignature(timestamp, nonce) {
var http_method = "POST";
var base_url = "https://api.twitter.com/oauth/request_token";
var consumer_key = "eEeAAz9hUKtdjunkeIUhPgQ";
var consumer_secret = "c7wHxnjubxVDcc5hYFqnotactuallymysecretWs2XazUFde0lPRBtBQ";
var signature_method = "HMAC-SHA1";
var token = "609493744-kNPzLKSI4Hg9NWQnopeFPb91eXFUutFm1nZ2hDk2";
var token_secret = "15WOJS9Ji1AXsKRkyAZrxKdsalted5Gj5ZyEAb9aVrJxI";
var version = "1.0";
var parameter_string = "oauth_callback=" + encodeURIComponent(base_url) + "&oauth_consumer_key=" + consumer_key + "&oauth_nonce=" + nonce + "&oauth_consumer_key=" + consumer_key + "&oauth_signature_method=" + signature_method + "&oauth_timestamp=" + timestamp +"&oauth_version=" + version;
var base_string = http_method + "&" + encodeURIComponent(base_url) + "&" + encodeURIComponent(parameter_string);
var signing_key = encodeURIComponent(consumer_secret) + "&";
var signature = encodeURIComponent(window.btoa(CryptoJS.HmacSHA1(base_string, signing_key)));
alert(signature);
return signature;
}
Feel free to post below if there's any other information that would make this error clearer. Thanks.
I created a node.js library to mess around with the Twitter OAuth dance and API. Code is here, tweeter.js
You're welcome to walk through the logic for creating the header and signature (starting at line 348 )
One thing I don't see in the code you've posted and which will make a huge difference is that the signature string must be generated to include the original header, then the header must be rebuilt with the generated string. It's a huge pain and it took me a while to figure it out.
Although the code I wrote is geared toward node.js, you should be able to reuse a lot of the logic to meet your needs.
EDIT
I found a site called hueniverse documented OAuth very well. In fact, there is a utility here to build your own headers for validating your logic (select the 'Create your own' radio button).
EDIT 2
To better explain including the oauth_signature value in the header, suppose you have all of the data up to this point:
var headerObj = {
oauth_consumer_key="123456789",
oauth_token="11111",
oauth_nonce="asdfghjkl%3B",
oauth_timestamp="1341852000",
oauth_signature_method="HMAC-SHA1",
oauth_version="1.0"
};
You create the HMAC-SHA1 signature and receive: "jBpoONisOt5kFYOrQ5fHCSZBGkI%3D"
You would then add that return value to headerObj, giving you:
headerObj = {
oauth_consumer_key="123456789",
oauth_token="11111",
oauth_nonce="asdfghjkl%3B",
oauth_timestamp="1341852000",
oauth_signature_method="HMAC-SHA1",
oauth_version="1.0",
oauth_signature="jBpoONisOt5kFYOrQ5fHCSZBGkI%3D"
};
And this modified version of headerObj is what you build your HTTP headers from.
GET / HTTP/1.1
Host: api.twitter.com:443
Authorization: OAuth realm="https://api.twitter.com/",
oauth_consumer_key="123456789",
oauth_token="11111",
oauth_nonce="asdfghjkl%3B",
oauth_timestamp="1341852000",
oauth_signature_method="HMAC-SHA1",
oauth_version="1.0",
oauth_signature="jBpoONisOt5kFYOrQ5fHCSZBGkI%3D"
NOTE: I didn't verify the host/realm/port, so these are probably wrong. Check the API for those.
The reason this is done is that on Twitter's side (this is an OAuth implementation detail), the oauth_signature value is removed and the rest of the header is hashed and its return value is compared to the value sent in oauth_signature. It's sort of like a wax seal on an envelope... if the hash of the rest of the header doesn't match the hash value you sent in oauth_signature, Twitter knows not to trust the sender or the contents.
EDIT 2.5
I'm moving this from the comment to the answer.
If you check out this line in tweeter.js, you'll see the logic.
var signature = self.oauthSignature(method, path, headerObj, query);
headerObj.oauth_signature = qs.escape(signature);
// concat the header object into a csv string
var header = 'OAuth realm="Twitter API",';
var oauthParts = [];
for (var h in headerObj) {
oauthParts.push(h + '="'+headerObj[h]+'"');
}
header+= oauthParts.join(',');
//...
return header;
This bit of code does as I've explained in EDIT 2, by converting a JSON object into key="value" strings stored in oauthParts[], then joins each element in that array into a single comma-separated string which begins with OAuth realm="Twitter API",