Youtube API v3 - Select menu to access public channel video data without Oauth - youtube-data-api

I want to access and view public Youtube videos (simple read only) from any Youtube channel without resorting to Oauth, just with plain API key. I haven't found a decent layman example on how to go about with API v3 ;-(
I have this to juggle with which I cannot get to work. Basically, a Select menu contains options whose values are existing channel IDs. When an option containing a channel ID is selected, it should trigger requestUserUploadsPlaylistId(). Then, when NEXTbutton or PREVIOUSbutton are activated, function requestVideoPlaylist() would kick in. Is there a better way to do this? I get the following error messages in Firebug:
TypeError: response.result is undefined (When I choose an option from SELECTmenu).
TypeError: response.result is undefined (After I click on buttons).
Here is what I am struggling with (am new to API v3 and kinda used to API v2 (sigh)):
<HTML is here>
script>
$('#NEXTbutton').prop('disabled', true).addClass('disabled');
</script>
<script type="text/javascript" src="https://apis.google.com
/js/client.js?onload=onJSClientLoad"></script>
<script>
var dd, playlistId, nextPageToken, prevPageToken;
function onJSClientLoad() {
gapi.client.setApiKey('YOUR-API-KEY');
gapi.client.load('youtube', 'v3', function(){
$('#NEXTbutton').prop('disabled', false).removeClass('disabled');
});
}
// Calling the following function via selected option value of select menu
// I am using "mine: false," since it's an unauthenticated request ??
function requestUserUploadsPlaylistId() {
var dd = $("#SELECTmenu option:selected").val();
var request = gapi.client.youtube.channels.list({
mine: false, // is this legit?
channelId: dd, // Variable is preset chosen value of SELECTmenu options
part: 'contentDetails,id'
});
request.execute(function(response) {
playlistId = response.result.items[0].contentDetails.relatedPlaylists.uploads;
channelId = response.result.items[0].id;
});
}
function requestVideoPlaylist(playlistId, pageToken) {
var requestOptions = {
playlistId: playlistId,
part: 'snippet,id',
maxResults: 5
};
if (pageToken) {
requestOptions.pageToken = pageToken;
}
var request = gapi.client.youtube.playlistItems.list(requestOptions);
request.execute(function(response) {
// Only show the page buttons if there's a next or previous page.
nextPageToken = response.result.nextPageToken;
var nextVis = nextPageToken ? 'visible' : 'hidden';
$('#NEXTbutton').css('visibility', nextVis);
prevPageToken = response.result.prevPageToken
var prevVis = prevPageToken ? 'visible' : 'hidden';
$('#PREVIOUSbutton').css('visibility', prevVis);
var playlistItems = response.result.items;
if (playlistItems) {
$.each(playlistItems, function(index, item) {
displayResult(item.snippet);
});
} else {
$('#CONTAINER').html('Sorry, no uploaded videos available');
}
});
}
function displayResult(videoSnippet) {
for(var i=0;i<response.items.length;i++) {
var channelTitle = response.items[i].snippet.channelTitle
var videoTitle = response.items[i].snippet.title;
var Thumbnail = response.items[i].snippet.thumbnails.medium.url;
var results = '<li><div class="video-result"><img src="'+Thumbnail+'" /></div>
<div class="chantitle">'+channelTitle+'</div>
<div class="vidtitle">'+videoTitle+'</div></li>';
$('#CONTAINER').append(results);
}
}
function nextPage() {
requestVideoPlaylist(playlistId, nextPageToken);
}
function previousPage() {
requestVideoPlaylist(playlistId, prevPageToken);
}
$('#NEXTbutton').on('click', function() { // Display next 5 results
nextPage();
});
$('#PREVIOUSbutton').on('click', function() { // Display previous 5 results
previousPage();
});
$("#SELECTmenu").on("change", function() {
$('#CONTAINER').empty();
if ($("#SELECTmenu option:selected").val().length === 24) { //Channel ID length
requestUserUploadsPlaylistId();
} else {
return false;
}
});
I'm surely missing something here, any pointers will be greatly appreciated.

FINAL UPDATE
A few updates later and I've finally answered my question after playing with the awesome Google APIs Explorer tool. Here is a sample working code allowing access to Youtube channel video-related data from a Select menu for read-only without using OAUTH, just an API key. The Select menu, based on a selected option's value (which contains a channel id), posts a video thumbnail, the thumbnail's channel origin; and the video's title. Should be easy to make the thumbnail clickable so as to load video in iframe embed or redirect to Youtube page. Enjoy!
// Change values and titles accordingly
<select id="SELECTmenu">
<option value="selchan">Select channel ...</option>
<option value="-YOUR-24digit-ChannelID-">Put-channel-title-here</option>
<option value="-YOUR-24digit-ChannelID-">Put-channel-title-here</option>
</select>
<button id="NEXTbutton">NEXT</button>
<button id="PREVIOUSbutton">PREV</button>
<ol id="CONTAINER"></ol> // Loads video data response
<script type="text/javascript"
src="https://apis.google.com/js/client.js?onload=onJSClientLoad">
</script>
var playlistId, nextPageToken, prevPageToken;
function onJSClientLoad() {
gapi.client.setApiKey('INSERT-YOUR-API-KEY'); // Insert your API key
gapi.client.load('youtube', 'v3', function(){
//Add function here if some action required immediately after the API loads
});
}
function requestUserUploadsPlaylistId(pageToken) {
// https://developers.google.com/youtube/v3/docs/channels/list
var selchan = $("#SELECTmenu option:selected").val();
var request = gapi.client.youtube.channels.list({
id: selchan,
part: 'snippet,contentDetails',
filter: 'uploads'
});
request.execute(function(response) {
playlistId = response.result.items[0].contentDetails.relatedPlaylists.uploads;
channelId = response.result.items[0].id;
requestVideoPlaylist(playlistId, pageToken);
});
}
function requestVideoPlaylist(playlistId, pageToken) {
$('#CONTAINER').empty();
var requestOptions = {
playlistId: playlistId,
part: 'snippet,id',
maxResults: 5 // can be changed
};
if (pageToken) {
requestOptions.pageToken = pageToken;
}
var request = gapi.client.youtube.playlistItems.list(requestOptions);
request.execute(function(response) {
// Only show the page buttons if there's a next or previous page.
nextPageToken = response.result.nextPageToken;
var nextVis = nextPageToken ? 'visible' : 'hidden';
$('#NEXTbutton').css('visibility', nextVis);
prevPageToken = response.result.prevPageToken
var prevVis = prevPageToken ? 'visible' : 'hidden';
$('#PREVIOUSbutton').css('visibility', prevVis);
var playlistItems = response.result.items;
if (playlistItems) {
displayResult(playlistItems);
} else {
$('#CONTAINER').html('Sorry, no uploaded videos.');
}
});
}
function displayResult(playlistItems) {
for(var i=0;i<playlistItems.length;i++) {
var channelTitle = playlistItems[i].snippet.channelTitle
var videoTitle = playlistItems[i].snippet.title;
var videoThumbnail = playlistItems[i].snippet.thumbnails.medium.url;
var results = '<li>
<div>'+channelTitle+'</div>
<div><img src="'+videoThumbnail+'" /></div>
<div>'+videoTitle+'</div>
</li>';
$('#CONTAINER').append(results);
}
}
function nextPage() {
$('#CONTAINER').empty(); // This needed here
requestVideoPlaylist(playlistId, nextPageToken);
}
function previousPage() {
$('#CONTAINER').empty(); // This needed here
requestVideoPlaylist(playlistId, prevPageToken);
}
$('#NEXTbutton').on('click', function() { // Display next maxResults
nextPage();
});
$('#PREVIOUSbutton').on('click', function() { // Display previous maxResults
previousPage();
});
// Using as filtering example Select option values which contain channel
// ID length of 24 alphanumerics/symbols to trigger functions just in case
// there are other option values in the menu that do not refer to channel IDs.
$("#SELECTmenu").on("change", function() {
$('#CONTAINER').empty();
if ($("#SELECTmenu option:selected").val().length === 24) {
requestUserUploadsPlaylistId();
return false;
} else {
return false;
}
});
NOTE:
Remember, code sample above is built based on what API v3 provided at the time of this posting.
TIP: It's better to make sure that the buttons be disabled during API call and re-enabled after API has posted the expected results. If you press those buttons while processing, you may get compounded and/or unexpected results. ~ Koolness

Related

Cannot correctly get form data in google script

I try to submit form data to google sheet via ajax but google script if else statement not work.
Below is the google script
// original from: http://mashe.hawksey.info/2014/07/google-sheets-as-a-database-insert-with-apps-script-using-postget-methods-with-ajax-example/
// original gist: https://gist.github.com/willpatera/ee41ae374d3c9839c2d6
// Enter sheet name where data is to be written below
var SHEET_NAME = "Records";
var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service
function doGet(e){
return handleResponse(e);
}
function handleResponse(e) {
// shortly after my original solution Google announced the LockService[1]
// this prevents concurrent access overwritting data
// [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html
// we want a public lock, one that locks for all invocations
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
try {
// next set where we write the data - you could write to multiple/alternate destinations
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
var sheet = doc.getSheetByName(SHEET_NAME);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [];
// loop through the header columns
for (i in headers){
switch (headers[i]) {
case "Timestamp":
row.push(new Date());
break;
case "LogTime":
row.push(new Date());
break;
default:
row.push(e.parameter[headers[i]]);
break;
}
}
// more efficient to set values as [][] array than individually
sheet.appendRow(row);
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
function setup() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
SCRIPT_PROP.setProperty("key", doc.getId());
}
I submit the form data using ajax
HTML
<form id="checkin-form">
<input type="hidden" name="Event" value="<?php echo $events[1]; ?>"/>
<input type="hidden" name="LogTime" value="" />
...
</form>
Javascript
$('#checkin-form').on('submit', function(e) {
e.preventDefault();
var jqxhr = $.ajax({
crossDomain: true,
url: url,
method: "GET",
dataType: "json",
data: $("#checkin-form").serialize()
});
return false;
});
From google script, the column LogTime cannot get the Date() but keep empty. What is the possible issue?
Here is the google sheet column
Best regards,
Kelvin
Don't know why the google apps script does not work, finally, I found the article from https://medium.com/#dmccoy/how-to-submit-an-html-form-to-google-sheets-without-google-forms-b833952cc175, when I redo running the Setup function, everything works properly. The Setup function has already been run before and the script worked.

ember model find query with params doesn't display on pagination

2I have an Ember app which connects to an api from where it gets articles. I make use of pagination to get 10 articles per request. This works. But now I wanted to add sorting to the request. I implemented this by using the extra parameter in the store.find.
However, for some reason if I use the 'return this.store.find('article', params);' instead of 'return this.store.find('article');' new articles (still requested and added correctly to the store!) in the getMore function are not beiing displayed or rendered. But when i remove the params parameter from store.find in model, it does work. What could be the case here?
templates/articles.hbs
<script type="text/x-handlebars" data-template-name="articles">
{{#each itemController="article"}}
<div class="item">
//...
</div>
{{/each}}
</script>
routes/articles.js
import Ember from 'ember';
export default Ember.Route.extend(Ember.UserApp.ProtectedRouteMixin, {
model: function(params) {
var params2 = {page: 1, per_page: 10, sort: params.sort};
return this.store.find('article', params2);
},
setupController: function(controller, model) {
controller.set('content', model);
},
actions:{
//...
},
getMore: function() {
// don't load new data if we already are
//if (this.get('loadingMore')) return;
//this.set('loadingMore', true);
var meta = this.store.metadataFor("article");
if (meta.hasmore) {
var controller = this.get('controller'),
nextPage = controller.get('page') + 1,
perPage = controller.get('perPage'),
sorting = controller.get('sort'),
items;
var params = {page: nextPage, per_page: perPage, sort: sorting};
this.store.findQuery('article', params).then(function (articles) {
controller.set('page', controller.get('page') + 1);
//this.set('loadingMore', false);
});
}
else{
$('#pagination_spinner').hide();
}
},
queryParamsDidChange: function() {
this.refresh();
}
}
});
controllers/articles.js
import Ember from 'ember';
var ArticlesController = Ember.ArrayController.extend({
itemController: 'article',
queryParams: ['sort'],
sort: 'rating',
page: 1,
perPage: 10
});
export default ArticlesController;
views/articles.js
import Ember from 'ember';
export default Ember.View.extend({
didInsertElement: function(){
//this.scheduleMasonry();
this.applyMasonry();
// we want to make sure 'this' inside `didScroll` refers
// to the IndexView, so we use jquery's `proxy` method to bind it
//this.applyMasonry();
$(window).on('scroll', $.proxy(this.didScroll, this));
},
willDestroyElement: function(){
this.destroyMasonry();
// have to use the same argument to `off` that we did to `on`
$(window).off('scroll', $.proxy(this.didScroll, this));
},
// this is called every time we scroll
didScroll: function(){
if (this.isScrolledToBottom()) {
$('#pagination_spinner').addClass('active');
this.get('controller').send('getMore');
}
},
scheduleMasonry: (function(){
Ember.run.scheduleOnce('afterRender', this, this.applyMasonry);
}).observes('controller.model.#each'), //TODO check
applyMasonry: function(){
$('#pagination_spinner').removeClass('active');
var $galleryContainer = $('#galleryContainer');
$galleryContainer.imagesLoaded(function() {
// check if masonry is initialized
var msnry = $galleryContainer.data('masonry');
if ( msnry ) {
msnry.reloadItems();
// disable transition
var transitionDuration = msnry.options.transitionDuration;
msnry.options.transitionDuration = 0;
msnry.layout();
// reset transition
msnry.options.transitionDuration = transitionDuration;
} else {
// init masonry
$galleryContainer.masonry({
itemSelector: '.item',
columnWidth: 0,
"isFitWidth": true
});
}
});
},
destroyMasonry: function(){
$('#galleryContainer').masonry('destroy');
},
// we check if we are at the bottom of the page
isScrolledToBottom: function(){
var distanceToViewportTop = (
$(document).height() - $(window).height());
var viewPortTop = $(document).scrollTop();
if (viewPortTop === 0) {
// if we are at the top of the page, don't do
// the infinite scroll thing
return false;
}
return (viewPortTop - distanceToViewportTop === 0);
}
});
nothing smart coming to my mind, but maybe it's that...
You've got the line:
if (meta.hasmore) {
in your getMore() function. Is this the case that you've got this meta field in one response and forgot in the other?

Why my extension sends duplicates on request in geometric progression?

I've created extension that makes some JSON request & send it to some receiver.
My problem is:
Open popup window
After it closing, extensions sends 1 request
Open it on the same page again, and extension will send 2 requests
Open again, 4 requests
Open again, 8 requests
In each uses of popup, extension will be duplicate outgoing data in geometric progression.
Why that happens?
From the panel I'm send addnewurl to the port:
AddDialog.port.on("addnewurl", function (data) {
{
AddDialog is my popup
here It handle port messages aftre popup is closed(hidded)
}
var http = require("sdk/request").Request;
var req = CreateRequest("add_url", {});
req.params = {...};
var sreq = encodeURIComponent(JSON.stringify(req));
count += 1; //Global counter, u will see it in video
console.log('count = '+count);
var cfg = {
url : getRequestURL(),
contentType : "text/html",
content : sreq,
onComplete : function (response) {
var data = {
code : response.status,
body : response.json
};
AddDialog.port.emit("addnewurldone", data);
}
};
http(cfg).post();
});
For more sense I've created a AVI video record of that. See it here:
https://dl.dropboxusercontent.com/u/86175609/Project002.avi
1.6 MB
How to resolve that?
ADDED by request more info
That function emit addnewurl:
function AddNewURL() {
var node = $("#Tree").dynatree("getActiveNode");
if (node == null) {
$("#ServerStatus").text(LocalizedStr.Status_NoGroupSelected);
$("#ServerStatus").css("color", "red");
return;
};
var nkey = node.data.key;
var aImg = null;
var data = {
ownerId : nkey,
name : $("#LinkTitle").val(),
description : $("#LinkDesc").val(),
url : $("#CurrentURL").val(),
scrcapt:$("#ScrCaptureCB :selected").val()
};
$("#load").css("display", "inline");
$("#ServerStatus").text(LocalizedStr.Status_AddURL);
self.port.emit("addnewurl", data);
};
and it calls by button:
self.port.on("showme", function onShow(data) {
....
document.querySelector('#BtnOk').addEventListener('click', function () {
AddNewURL();
});
...
});
"swomme" goes from here(main.js):
AddDialog.on("show", function () {
count = 0;
AddDialog.port.emit("showme", locTbl);
});
function addToolbarButton() {
var enumerator = mediator.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var document = enumerator.getNext().document;
var navBar = document.getElementById('nav-bar');
if (!navBar) {
return;
}
var btn = document.createElement('toolbarbutton');
btn.setAttribute('id', cBtnId);
btn.setAttribute('type', 'button');
btn.setAttribute('class', 'FLAToolButton');
btn.setAttribute('image', data.url('icons/Add.png'));
btn.setAttribute('orient', 'horizontal');
btn.setAttribute('label', loc("Main_ContextMenu"));
btn.addEventListener('click', function () {
AddDialog.show();
}, false)
navBar.appendChild(btn);
}
}
I think the problem is here
document.querySelector('#BtnOk').addEventListener('click', function () {
AddNewURL();
});
If you are running AddDialog.port.emit("showme", locTbl); when you click your toolbar button then you're adding a click listener to #BtnOk every time as well.
On the first toolbar click it will have one click listener, on the second click two, and so on. You should remove the above code from that function and only run it once.

JsonLogOn via https

I've just introduced SSL to my MVC website. I made the whole default AccountContorller use it. It works fine unless I'm currently on http page and try to log on with ajax (the logon action is vredirected to httpS). This popup logon window doesn't even show up.
For the controller a used a custom attribute:
public class RequireSSL : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.IsDefined(typeof(NoSSL), true) ||
filterContext.ActionDescriptor.IsDefined(typeof(OptionalSSL), true))
{
base.OnActionExecuting(filterContext);
return;
}
HttpRequestBase req = filterContext.HttpContext.Request;
HttpResponseBase res = filterContext.HttpContext.Response;
//Check if we're secure or not and if we're on the local box
if (!req.IsSecureConnection && (!req.IsLocal || Properties.Settings.Default.UseSSLForLocalRequests))
{
var builder = new UriBuilder(req.Url)
{
Scheme = Uri.UriSchemeHttps,
Port = Properties.Settings.Default.HttpsPort,
};
res.Redirect(builder.Uri.ToString());
}
base.OnActionExecuting(filterContext);
}
}
How can I make it work?
EDIT
The whole rest was generated by MVC. (Project with built in authentications)
That's the link.
#Html.ActionLink(#Labels.LogOn, "LogOn", "Account", routeValues: null, htmlAttributes: new { id = "logonLink", data_dialog_title = "Identification" })
JS somehow hooks into that link and performs ajax logon . Probably with this code: (JajaxLogin.js - also out of the box)
/// <reference path="jquery-1.6.2.js" />
/// <reference path="jquery.validate.js" />
$(function () {
// Cache for dialogs
var dialogs = {};
var getValidationSummaryErrors = function ($form) {
// We verify if we created it beforehand
var errorSummary = $form.data('validation-summary-errors');
if (!errorSummary) {
errorSummary = $('<div class="validation-summary-errors"><span>Please correct the errors and try again.</span><ul></ul></div>')
.insertBefore($form);
// Remember that we created it
$form.data('validation-summary-errors', errorSummary);
}
return errorSummary;
};
var formSubmitHandler = function (e) {
var $form = $(this);
// We check if jQuery.validator exists on the form
if (!$form.valid || $form.valid()) {
$.post($form.attr('action'), $form.serializeArray())
.done(function (json) {
json = json || {};
// In case of success, we redirect to the provided URL or the same page.
if (json.success) {
location = json.redirect || location.href;
} else if (json.errors) {
var errorSummary = getValidationSummaryErrors($form);
var items = $.map(json.errors, function (error) {
return '<li>' + error + '</li>';
}).join('');
var ul = errorSummary
.find('ul')
.empty()
.append(items);
}
});
}
// Prevent the normal behavior since we opened the dialog
e.preventDefault();
};
var loadAndShowDialog = function (id, link, url) {
var separator = url.indexOf('?') >= 0 ? '&' : '?';
// Save an empty jQuery in our cache for now.
dialogs[id] = $();
// Load the dialog with the content=1 QueryString in order to get a PartialView
$.get(url + separator + 'content=1')
.done(function (content) {
dialogs[id] = $('<div class="modal-popup">' + content + '</div>')
.hide() // Hide the dialog for now so we prevent flicker
.appendTo(document.body)
.filter('div') // Filter for the div tag only, script tags could surface
.dialog({ // Create the jQuery UI dialog
title: link.data('dialog-title'),
modal: true,
resizable: true,
draggable: true,
width: link.data('dialog-width') || 300
})
.find('form') // Attach logic on forms
.submit(formSubmitHandler)
.end();
});
};
// List of link ids to have an ajax dialog
var links = ['logonLink', 'registerLink'];
$.each(links, function (i, id) {
$('#' + id).click(function (e) {
var link = $(this),
url = link.attr('href');
if (!dialogs[id]) {
loadAndShowDialog(id, link, url);
} else {
dialogs[id].dialog('open');
}
// Prevent the normal behavior since we use a dialog
e.preventDefault();
});
});
});

Bootstrap typeahead suggestions replaced when navigation

I'm using Bootstrap Typeahead to suggest som search results. The results are returned from a ajax ressource, and since this resource creates a delay, I'm experiencing a unfortunate effect.
Example:
If typing a 4 letter word, the suggestions will appear after 2 letters, I can then go through the results with the keys up/down, but suddenly the suggestions will reload because the last request has finished.
Is there any way to "cancel" any remaining, if user is currently using the keys up/down to go through the suggestions?
('#query').typeahead({
items: 4,
source: function (query,process) {
map = {};
$.getJSON('/app_dev.php/ajax/autosuggest/'+query, function (data) {
vehicles = [];
$.each(data, function(i,vehicle){
map[vehicle.full] = vehicle;
vehicles.push(vehicle.full);
});
process(vehicles);
});
},
updater: function (item) {
// do something here when item is selected
},
highlighter: function (item) {
return item;
},
matcher: function (item) {
return true;
}
});
I think the following will satisfy your needs (its hard to reproduce exactly) :
There is no easy way to abort a delayed response, but you could extend typeahead as I figured out here (without modifying bootstrap.js)
The concept is to catch keydown, detect if the event is KEY_UP or KEY_DOWN, set a flag is_browsing, and then abort process if is_browsing is true (that is, if the user has hitted KEY_UP or KEY_DOWN and no other keys afterwards).
Extending typeahead :
// save the original function object
var _superTypeahead = $.fn.typeahead;
// add is_browsing as a new flag
$.extend( _superTypeahead.defaults, {
is_browsing: false
});
// create a new constructor
var Typeahead = function(element, options) {
_superTypeahead.Constructor.apply( this, arguments )
}
// extend prototype and add a _super function
Typeahead.prototype = $.extend({}, _superTypeahead.Constructor.prototype, {
constructor: Typeahead
, _super: function() {
var args = $.makeArray(arguments)
// call bootstrap core
_superTypeahead.Constructor.prototype[args.shift()].apply(this, args)
}
//override typeahead original keydown
, keydown: function (e) {
this._super('keydown', e)
this.options.is_browsing = ($.inArray(e.keyCode, [40,38])>-1)
}
//override process, abort if user is browsing
, process: function (items) {
if (this.options.is_browsing) return
this._super('process', items)
}
});
// override the old initialization with the new constructor
$.fn.typeahead = $.extend(function(option) {
var args = $.makeArray(arguments),
option = args.shift()
// this is executed everytime element.modal() is called
return this.each(function() {
var $this = $(this)
var data = $this.data('typeahead'),
options = $.extend({}, _superTypeahead.defaults, $this.data(), typeof option == 'object' && option)
if (!data) {
$this.data('typeahead', (data = new Typeahead(this, options)))
}
if (typeof option == 'string') {
data[option].apply( data, args )
}
});
}, $.fn.typeahead);
This typeahead-extension could be placed anywhere, eg in a <script type="text/javascript"> -section
Testing the extension :
<input type="text" id="test" name="test" placeholder="type some text" data-provide="typeahead">
<script type="text/javascript">
$(document).ready(function() {
var url='typeahead.php';
$("#test").typeahead({
items : 10,
source: function (query, process) {
return $.get(url, { query: query }, function (data) {
return process(data.options);
});
}
});
});
</script>
A "serverside" PHP script that returns a lot of randomized options with forced delay, typeahead.php :
<?
header('Content-type: application/json');
$JSON='';
sleep(3); //delay execution in 3 secs
for ($count=0;$count<30000;$count++) {
if ($JSON!='') $JSON.=',';
//create random strings
$s=str_shuffle("abcdefghijklmnopq");
$JSON.='"'.$s.'"';
}
$JSON='{ "options": ['.$JSON.'] }';
echo $JSON;
?>
It really seems to work for me. But I cannot be sure that it will work in your case. Let me now if you have success or not.

Resources