Related
I'm using a Youtube-TV JS plugin for a client site (https://github.com/Giorgio003/Youtube-TV ). The player loads with a playlist in an open state, and I need to have it load with the toggle closed, showing the array of playlists, can anyone help?
my codepen for the player is here
http://codepen.io/raldesign/pen/OVYXvK
(function(win, doc) {
'use strict';
var apiKey = 'AIzaSyAFMzeux_Eu933wk5U8skMzUzA-urgVgsY';
var YTV = YTV || function(id, opts){
var noop = function(){},
settings = {
apiKey: apiKey,
element: null,
user: null,
channelId: null,
fullscreen: false,
accent: '#fff',
controls: true,
annotations: false,
autoplay: false,
chainVideos: true,
browsePlaylists: false,
playerTheme: 'dark',
listTheme: 'dark',
responsive: false,
playId:'',
sortList: false,
reverseList: false,
shuffleList: false,
wmode: 'opaque',
events: {
videoReady: noop,
stateChange: noop
}
},
cache = {
data: {},
remove: function (url) {
delete cache.data[url];
},
exist: function (url) {
return cache.data.hasOwnProperty(url) && cache.data[url] !== null;
},
get: function (url) {
return cache.data[url];
},
set: function (url, data) {
cache.remove(url);
cache.data[url] = data;
}
},
utils = {
events: {
addEvent: function addEvent(element, eventName, func) {
if (element.addEventListener) {
return element.addEventListener(eventName, func, false);
} else if (element.attachEvent) {
return element.attachEvent("on" + eventName, func);
}
},
removeEvent: function addEvent(element, eventName, func) {
if (element.addEventListener) {
return element.removeEventListener(eventName, func, false);
} else if (element.attachEvent) {
return element.detachEvent("on" + eventName, func);
}
},
prevent: function(e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
},
addCSS: function(css){
var head = doc.getElementsByTagName('head')[0],
style = doc.createElement('style');
style.type = 'text/css';
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(doc.createTextNode(css));
}
head.appendChild(style);
},
addCommas: function(str){
var x = str.split('.'),
x1 = x[0],
x2 = x.length > 1 ? '.' + x[1] : '',
rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
},
parentUntil: function(el, attr) {
while (el.parentNode) {
if (el.getAttribute && el.getAttribute(attr)){
return el;
}
el = el.parentNode;
}
return null;
},
ajax: {
get: function(url, fn){
if (cache.exist(url)) {
fn.call(this, JSON.parse(cache.get(url)));
} else {
var handle;
if (win.XDomainRequest && !(navigator.appVersion.indexOf("MSIE 8")==-1 || navigator.appVersion.indexOf("MSIE 9")==-1)) { // CORS for IE8,9
handle = new XDomainRequest();
handle.onload = function(){
cache.set(url, handle.responseText);
fn.call(this, JSON.parse(handle.responseText));
if (Object.prototype.hasOwnProperty.call(JSON.parse(handle.responseText), 'error')){
cache.remove(url);
var e = JSON.parse(handle.responseText);
console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message);
}
};
} else if (win.XMLHttpRequest){ // Modern Browsers
handle = new XMLHttpRequest();
}
handle.onreadystatechange = function(){
if (handle.readyState === 4 && handle.status === 200){
cache.set(url, handle.responseText);
fn.call(this, JSON.parse(handle.responseText));
} else if (handle.readyState === 4){
var e = JSON.parse(handle.responseText);
console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message);
}
};
handle.open("GET",url,true);
handle.send();
}
}
},
endpoints: {
base: 'https://www.googleapis.com/youtube/v3/',
userInfo: function(){
return utils.endpoints.base+'channels?'+settings.cid+'&key='+apiKey+'&part=snippet,contentDetails,statistics';
},
playlistInfo: function(pid){
return utils.endpoints.base+'playlists?id='+pid+'&key='+apiKey+'&maxResults=50&part=snippet';
},
userPlaylists: function(){
return utils.endpoints.base+'playlists?channelId='+settings.channelId+'&key='+apiKey+'&maxResults=50&part=snippet';
},
playlistVids: function(){
return utils.endpoints.base+'playlistItems?playlistId='+settings.pid+'&key='+apiKey+'&maxResults=50&part=contentDetails';
},
videoInfo: function(){
return utils.endpoints.base+'videos?id='+settings.videoString+'&key='+apiKey+'&maxResults=50&part=snippet,contentDetails,status,statistics';
}
},
deepExtend: function(destination, source) {
var property;
for (property in source) {
if (source[property] && source[property].constructor && source[property].constructor === Object) {
destination[property] = destination[property] || {};
utils.deepExtend(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
}
},
prepare = {
youtube: function(){
if(typeof YT=='undefined'){
var tag = doc.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = doc.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
},
build: function(){
if (settings.channelId){
settings.cid = 'id='+settings.channelId;
} else if(settings.user){
settings.cid = 'forUsername='+settings.user;
}
settings.element.className = "ytv-canvas";
if(settings.fullscreen){
settings.element.className += " ytv-full";
}
utils.addCSS( '#'+id+' .ytv-list .ytv-active a{border-left-color: '+(settings.accent)+';}' );
// Responsive CSS
if(settings.responsive){
utils.addCSS('#'+id+' .ytv-video{'
+'position: relative; padding-bottom: 39.4%; /* 16:9 of 70%*/'
+'height: 0; width: 70%;'
+'} #'+id+' .ytv-video iframe{'
+'position: absolute; top: 0; left: 0;'
+'} #'+id+' .ytv-list{'
+'width: 30%;'
+'} #'+id+' .ytv-playlist-open .ytv-arrow{'
+'top: 0px;}'
+'#media only screen and (max-width:992px) {'
+'#'+id+' .ytv-list{'
+'position: relative; display: block;'
+'width: 0; padding-bottom: 40%;'
+'left: auto; right: auto;'
+'top: auto; width: 100%;'
+'} #'+id+' .ytv-video{'
+'position: relative; padding-bottom: 56.25%; /* 16:9 */'
+'height: 0; position: relative;'
+'display: block; left: auto;'
+'right: auto; top: auto; width: 100%;'
+'}}'
);
}
// Temp Scroll Bar fix
if (settings.listTheme == 'dark'){
utils.addCSS( ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid #000;}'
+ ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(255,255,255,0.2);}');
}
// Optional Light List Theme
if(settings.listTheme == 'light'){
utils.addCSS( ' #'+id+'.ytv-canvas{background: #ccc;}'
+ ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid rgba(28,28,28,0.1);}'
+ ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(28,28,28,0.3);}'
+ ' #'+id+' .ytv-list .ytv-active a{background: rgba(0,0,0,0.2);}'
+ ' #'+id+' .ytv-list a{color: #282828; border-top: 1px solid rgba(0,0,0,0.1); border-bottom: 1px solid rgba(204,204,204,0.5);}'
+ ' #'+id+' .ytv-list a:hover, #'+id+' .ytv-list-header .ytv-playlists a:hover{ background: rgba(0,0,0,0.2);}'
+ ' #'+id+' .ytv-list a:active, #'+id+' .ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.2);}'
+ ' #'+id+' .ytv-list .ytv-thumb-stroke{outline: 1px solid rgba(0,0,0,0.1);}'
+ ' #'+id+' .ytv-list .ytv-thumb{outline: 1px solid rgba(255,255,255,0.5);}'
+ ' #'+id+' .ytv-list-header{-webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2);}'
+ ' #'+id+' .ytv-list-header a{background: rgba(0,0,0,0.2);}'
+ ' #'+id+' .ytv-playlists{background: #ccc;}'
);
}
},
userUploads: function(userInfo){
if (userInfo && userInfo.items.length > 0){
settings.pid = userInfo.items[0].contentDetails.relatedPlaylists.uploads;
utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos );
} else console.log ('Youtube-TV Error: API returned no matches for: '+(settings.channelId ? settings.channelId : settings.user)+'\nPlease ensure it was entered correctly and in the appropriate field shown below. \nuser: \'username\' or channelId: \'UCxxxx...\'');
},
selectedPlaylist: function(playlistInfo){
if (playlistInfo && playlistInfo.items.length > 0) {
if (!settings.channelId && !settings.user){
settings.cid = ('id='+(settings.channelId = playlistInfo.items[0].snippet.channelId));
}
settings.currentPlaylist = playlistInfo.items[0].snippet.title;
settings.pid = playlistInfo.items[0].id;
utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos );
} else console.log ('Youtube-TV Error: API returned no matches for playlist(s): '+settings.playlist);
},
compileVideos: function(res){
if (res && res.items.length > 0){
var playlists = res.items,
i;
settings.videoString = '';
for(i=0; i<playlists.length; i++){
settings.videoString += playlists[i].contentDetails.videoId;
if (i<playlists.length-1){ settings.videoString += ',';}
}
utils.ajax.get( utils.endpoints.videoInfo(), prepare.compileList );
} else console.log ('Youtube-TV Error: Empty playlist');
},
playlists: function(res){
if(res && res.items.length > 0){
var list = '<div class="ytv-playlists"><ul>',
playlists = res.items,
i;
for(i=0; i<playlists.length; i++){
var data = {
title: playlists[i].snippet.title,
plid: playlists[i].id,
thumb: playlists[i].snippet.thumbnails.medium.url
};
list += '<a href="#" data-ytv-playlist="'+(data.plid)+'">';
list += '<div class="ytv-thumb"><div class="ytv-thumb-stroke"></div><img src="'+(data.thumb)+'"></div>';
list += '<span>'+(data.title)+'</span>';
list += '</a>';
}
list += '</ul></div>';
var lh = settings.element.getElementsByClassName('ytv-list-header')[0],
headerLink = lh.children[0];
headerLink.href="#";
headerLink.target="";
headerLink.setAttribute('data-ytv-playlist-toggle', 'true');
settings.element.getElementsByClassName('ytv-list-header')[0].innerHTML += list;
lh.className += ' ytv-has-playlists';
} else console.log ('Youtube-TV Error: Returned no playlists');
},
compileList: function(data){
if(data && data.items.length > 0){
utils.ajax.get( utils.endpoints.userInfo(), function(userInfo){
var list = '',
user = {
title: userInfo.items[0].snippet.title,
url: '//youtube.com/channel/'+userInfo.items[0].id,
thumb: userInfo.items[0].snippet.thumbnails['default'].url,
summary: userInfo.items[0].snippet.description,
subscribers: userInfo.items[0].statistics.subscriberCount,
views: userInfo.items[0].statistics.viewCount
},
videos = data.items,
first = true,
i;
settings.channelId = userInfo.items[0].id;
if(settings.currentPlaylist) user.title += ' ยท '+(settings.currentPlaylist);
if (settings.sortList) videos.sort(function(a,b){if(a.snippet.publishedAt > b.snippet.publishedAt) return -1;if(a.snippet.publishedAt < b.snippet.publishedAt) return 1;return 0;});
if (settings.reverseList) videos.reverse();
if (settings.shuffleList) {
videos = function (){for(var j, x, i = videos.length; i; j = Math.floor(Math.random() * i), x = videos[--i], videos[i] = videos[j], videos[j] = x);return videos;}();
}
list += '<div class="ytv-list-header">';
list += '<a href="'+(user.url)+'" target="_blank">';
list += '<img src="'+(user.thumb)+'">';
list += '<span><i class="ytv-arrow down"></i>'+(user.title)+'</span>';
list += '</a>';
list += '</div>';
list += '<div class="ytv-list-inner"><ul>';
for(i=0; i<videos.length; i++){
if(videos[i].status.embeddable){
var video = {
title: videos[i].snippet.title,
slug: videos[i].id,
link: 'https://www.youtube.com/watch?v='+videos[i].id,
published: videos[i].snippet.publishedAt,
stats: videos[i].statistics,
duration: (videos[i].contentDetails.duration),
thumb: videos[i].snippet.thumbnails.medium.url
};
var durationString = video.duration.match(/[0-9]+[HMS]/g);
var h = 0, m = 0, s = 0, time = '';
durationString.forEach(function (duration) {
var unit = duration.charAt(duration.length-1);
var amount = parseInt(duration.slice(0,-1));
switch (unit) {
case 'H': h = (amount > 9 ? '' + amount : '0' + amount); break;
case 'M': m = (amount > 9 ? '' + amount : '0' + amount); break;
case 'S': s = (amount > 9 ? '' + amount : '0' + amount); break;
}
});
if (h){ time += h+':';}
if (m){ time += m+':';} else { time += '00:';}
if (s){ time += s;} else { time += '00';}
var isFirst = '';
if(settings.playId==video.slug){
isFirst = ' class="ytv-active"';
first = video.slug;
} else if(first===true){
first = video.slug;
}
list += '<li'+isFirst+'><a href="#" data-ytv="'+(video.slug)+'" class="ytv-clear">';
list += '<div class="ytv-thumb"><div class="ytv-thumb-stroke"></div><span>'+(time)+'</span><img src="'+(video.thumb)+'"></div>';
list += '<div class="ytv-content"><b>'+(video.title)+'</b>';
if (video.stats)
{
list+='</b><span class="ytv-views">'+utils.addCommas(video.stats.viewCount)+' Views</span>';
}
list += '</div></a></li>';
}
}
list += '</ul></div>';
settings.element.innerHTML = '<div class="ytv-relative"><div class="ytv-video"><div id="ytv-video-player"></div></div><div class="ytv-list">'+list+'</div></div>';
if(settings.element.getElementsByClassName('ytv-active').length===0){
settings.element.getElementsByTagName('li')[0].className = "ytv-active";
}
var active = settings.element.getElementsByClassName('ytv-active')[0];
active.parentNode.parentNode.scrollTop = active.offsetTop;
action.logic.loadVideo(first, settings.autoplay);
if (settings.playlist){
utils.ajax.get( utils.endpoints.playlistInfo(settings.playlist), prepare.playlists );
} else if(settings.browsePlaylists){
utils.ajax.get( utils.endpoints.userPlaylists(), prepare.playlists );
}
});
} else console.log ('Youtube-TV Error: Empty video list');
}
},
action = {
logic: {
playerStateChange: function(d){
console.log(d);
},
loadVideo: function(slug, autoplay){
var house = settings.element.getElementsByClassName('ytv-video')[0];
var counter = settings.element.getElementsByClassName('ytv-video-playerContainer').length;
house.innerHTML = '<div id="ytv-video-player'+id+counter+'" class="ytv-video-playerContainer"></div>';
cache.player = new YT.Player('ytv-video-player'+id+counter, {
videoId: slug,
events: {
onReady: settings.events.videoReady,
onStateChange: function(e){
if( (e.target.getPlayerState()===0) && settings.chainVideos ){
var ns = settings.element.getElementsByClassName('ytv-active')[0].nextSibling,
link = ns.children[0];
link.click();
}
settings.events.stateChange.call(this, e);
}
},
playerVars: {
enablejsapi: 1,
origin: doc.domain,
controls: settings.controls ? 1 : 0,
rel: 0,
showinfo: 0,
iv_load_policy: settings.annotations ? '' : 3,
autoplay: autoplay ? 1 : 0,
theme: settings.playerTheme,
wmode: settings.wmode
}
});
}
},
endpoints: {
videoClick: function(e){
var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv');
if(target){
if(target.getAttribute('data-ytv')){
// Load Video
utils.events.prevent(e);
var activeEls = settings.element.getElementsByClassName('ytv-active'),
i;
for(i=0; i<activeEls.length; i++){
activeEls[i].className="";
}
target.parentNode.className="ytv-active";
action.logic.loadVideo(target.getAttribute('data-ytv'), true);
}
}
},
playlistToggle: function(e){
var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv-playlist-toggle');
if(target && target.getAttribute('data-ytv-playlist-toggle')){
// Toggle Playlist
utils.events.prevent(e);
var lh = settings.element.getElementsByClassName('ytv-list-header')[0];
if(lh.className.indexOf('ytv-playlist-open')===-1){
lh.className += ' ytv-playlist-open';
} else {
lh.className = lh.className.replace(' ytv-playlist-open', '');
}
}
},
playlistClick: function(e){
var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv-playlist');
if(target && target.getAttribute('data-ytv-playlist')){
// Load Playlist
utils.events.prevent(e);
settings.pid = target.getAttribute('data-ytv-playlist');
target.children[1].innerHTML = 'Loading...';
utils.ajax.get( utils.endpoints.playlistInfo(settings.pid), function(res){
var lh = settings.element.getElementsByClassName('ytv-list-header')[0];
lh.className = lh.className.replace(' ytv-playlist-open', '');
prepare.selectedPlaylist(res);
});
}
}
},
bindEvents: function(){
utils.events.addEvent( settings.element, 'click', action.endpoints.videoClick );
utils.events.addEvent( settings.element, 'click', action.endpoints.playlistToggle );
utils.events.addEvent( settings.element, 'click', action.endpoints.playlistClick );
}
},
initialize = function(id, opts){
utils.deepExtend(settings, opts);
if(settings.apiKey.length===0){
alert("Missing APIkey in settings or as global vaiable.");
}
apiKey = settings.apiKey;
settings.element = (typeof id==='string') ? doc.getElementById(id) : id;
if(settings.element && (settings.user || settings.channelId || settings.playlist)){
prepare.youtube();
prepare.build();
action.bindEvents();
if (settings.playlist) {
utils.ajax.get( utils.endpoints.playlistInfo(settings.playlist), prepare.selectedPlaylist );
} else {
utils.ajax.get( utils.endpoints.userInfo(), prepare.userUploads );
}
} else console.log ('Youtube-TV Error: Missing either user, channelId, or playlist');
};
/* Public */
this.destroy = function(){
utils.events.removeEvent( settings.element, 'click', action.endpoints.videoClick );
utils.events.removeEvent( settings.element, 'click', action.endpoints.playlistToggle );
utils.events.removeEvent( settings.element, 'click', action.endpoints.playlistClick );
settings.element.className = '';
settings.element.innerHTML = '';
};
this.fullscreen = {
state: function(){
return (settings.element.className).indexOf('ytv-full') !== -1;
},
enter: function(){
if( (settings.element.className).indexOf('ytv-full') === -1 ){
settings.element.className += 'ytv-full';
}
},
exit: function(){
if( (settings.element.className).indexOf('ytv-full') !== -1 ){
settings.element.className = (settings.element.className).replace('ytv-full', '');
}
}
};
initialize(id, opts);
};
if ((typeof module !== 'undefined') && module.exports) {
module.exports = YTV;
}
if (typeof ender === 'undefined') {
this.YTV = YTV;
}
if ((typeof define === "function") && define.amd) {
define("YTV", [], function() {
return YTV;
});
}
if ((typeof jQuery !== 'undefined')) {
jQuery.fn.extend({
ytv: function(options) {
return this.each(function() {
new YTV(this.id, options);
});
}
});
}
}).call(this, window, document);
Replace this
list += '<div class="ytv-list-header">';
with
list += '<div class="ytv-list-header ytv-playlist-open">';
There is two playlist one is "list of all playlists". And other is "list of videos in a playlist".
"ytv-playlist-open" indicate the open status for "list of all playlist". So when it is added it will close "videos in a playlist" and show "list of all playlist" which you want to show there.
I have been trying to fix a piece of javascript code but am not having any luck. The syntax looks correct but I keep getting an unexpected token '<' syntax error.
Please do not mark this question as a duplicate since I could not find the answer to my problem on this site.
Javascript:
// Function to get elements by class name for DOM fragment and tag name
function getElementsByClassName(objElement, strTagName, strClassName)
{
var objCollection = objElement.getElementsByTagName(strTagName);
var arReturn = [];
var strClass, arClass, iClass, iCounter;
for(iCounter=0; iCounter<objCollection.length; iCounter++)
{
strClass = objCollection[iCounter].className;
if (strClass)
{
arClass = strClass.split(' ');
for (iClass=0; iClass<arClass.length; iClass++)
{
if (arClass[iClass] == strClassName)
{
arReturn.push(objCollection[iCounter]);
break;
}
}
}
}
objCollection = null;
return (arReturn);
}
var drag = {
objCurrent : null,
arTargets : ['Fav', 'Tol', 'Rej'],
initialise : function(objNode)
{
// Add event handlers
objNode.onmousedown = drag.start;
objNode.onclick = function() {this.focus();};
objNode.onkeydown = drag.keyboardDragDrop;
document.body.onclick = drag.removePopup;
},
keyboardDragDrop : function(objEvent)
{
objEvent = objEvent || window.event;
drag.objCurrent = this;
var arChoices = ['Favourite artists', 'Tolerable artists', 'Rejected artists'];
var iKey = objEvent.keyCode;
var objItem = drag.objCurrent;
var strExisting = objItem.parentNode.getAttribute('id');
var objMenu, objChoice, iCounter;
if (iKey == 32)
{
document.onkeydown = function(){return objEvent.keyCode==38 || objEvent.keyCode==40 ? false : true;};
// Set ARIA properties
drag.objCurrent.setAttribute('aria-grabbed', 'true');
drag.objCurrent.setAttribute('aria-owns', 'popup');
// Build context menu
objMenu = document.createElement('ul');
objMenu.setAttribute('id', 'popup');
objMenu.setAttribute('role', 'menu');
for (iCounter=0; iCounter<arChoices.length; iCounter++)
{
if (drag.arTargets[iCounter] != strExisting)
{
objChoice = document.createElement('li');
objChoice.appendChild(document.createTextNode(arChoices[iCounter]));
objChoice.tabIndex = -1;
objChoice.setAttribute('role', 'menuitem');
objChoice.onmousedown = function() {drag.dropObject(this.firstChild.data.substr(0, 3));};
objChoice.onkeydown = drag.handleContext;
objChoice.onmouseover = function() {if (this.className.indexOf('hover') < 0) {this.className += ' hover';} };
objChoice.onmouseout = function() {this.className = this.className.replace(/\s*hover/, ''); };
objMenu.appendChild(objChoice);
}
}
objItem.appendChild(objMenu);
objMenu.firstChild.focus();
objMenu.firstChild.className = 'focus';
drag.identifyTargets(true);
}
},
removePopup : function()
{
document.onkeydown = null;
var objContext = document.getElementById('popup');
if (objContext)
{
objContext.parentNode.removeChild(objContext);
}
},
handleContext : function(objEvent)
{
objEvent = objEvent || window.event;
var objItem = objEvent.target || objEvent.srcElement;
var iKey = objEvent.keyCode;
var objFocus, objList, strTarget, iCounter;
// Cancel default behaviour
if (objEvent.stopPropagation)
{
objEvent.stopPropagation();
}
else if (objEvent.cancelBubble)
{
objEvent.cancelBubble = true;
}
if (objEvent.preventDefault)
{
objEvent.preventDefault();
}
else if (objEvent.returnValue)
{
objEvent.returnValue = false;
}
switch (iKey)
{
case 38 : // Down arrow
objFocus = objItem.nextSibling;
if (!objFocus)
{
objFocus = objItem.previousSibling;
}
objItem.className = '';
objFocus.focus();
objFocus.className = 'focus';
break;
case 40 : // Up arrow
objFocus = objItem.previousSibling;
if (!objFocus)
{
objFocus = objItem.nextSibling;
}
objItem.className = '';
objFocus.focus();
objFocus.className = 'focus';
break;
case 13 : // Enter
strTarget = objItem.firstChild.data.substr(0, 3);
drag.dropObject(strTarget);
break;
case 27 : // Escape
case 9 : // Tab
drag.objCurrent.removeAttribute('aria-owns');
drag.objCurrent.removeChild(objItem.parentNode);
drag.objCurrent.focus();
for (iCounter=0; iCounter<drag.arTargets.length; iCounter++)
{
objList = document.getElementById(drag.arTargets[iCounter]);
drag.objCurrent.setAttribute('aria-grabbed', 'false');
objList.removeAttribute('aria-dropeffect');
objList.className = '';
}
break;
}
},
start : function(objEvent)
{
objEvent = objEvent || window.event;
drag.removePopup();
// Initialise properties
drag.objCurrent = this;
drag.objCurrent.lastX = objEvent.clientX;
drag.objCurrent.lastY = objEvent.clientY;
drag.objCurrent.style.zIndex = '2';
drag.objCurrent.setAttribute('aria-grabbed', 'true');
document.onmousemove = drag.drag;
document.onmouseup = drag.end;
drag.identifyTargets(true);
return false;
},
drag : function(objEvent)
{
objEvent = objEvent || window.event;
// Calculate new position
var iCurrentY = objEvent.clientY;
var iCurrentX = objEvent.clientX;
var iYPos = parseInt(drag.objCurrent.style.top, 10);
var iXPos = parseInt(drag.objCurrent.style.left, 10);
var iNewX, iNewY;
iNewX = iXPos + iCurrentX - drag.objCurrent.lastX;
iNewY = iYPos + iCurrentY - drag.objCurrent.lastY;
drag.objCurrent.style.left = iNewX + 'px';
drag.objCurrent.style.top = iNewY + 'px';
drag.objCurrent.lastX = iCurrentX;
drag.objCurrent.lastY = iCurrentY;
return false;
},
calculatePosition : function (objElement, strOffset)
{
var iOffset = 0;
// Get offset position in relation to parent nodes
if (objElement.offsetParent)
{
do
{
iOffset += objElement[strOffset];
objElement = objElement.offsetParent;
} while (objElement);
}
return iOffset;
},
identifyTargets : function (bHighlight)
{
var strExisting = drag.objCurrent.parentNode.getAttribute('id');
var objList, iCounter;
// Highlight the targets for the current drag item
for (iCounter=0; iCounter<drag.arTargets.length; iCounter++)
{
objList = document.getElementById(drag.arTargets[iCounter]);
if (bHighlight && drag.arTargets[iCounter] != strExisting)
{
objList.className = 'highlight';
objList.setAttribute('aria-dropeffect', 'move');
}
else
{
objList.className = '';
objList.removeAttribute('aria-dropeffect');
}
}
},
getTarget : function()
{
var strExisting = drag.objCurrent.parentNode.getAttribute('id');
var iCurrentLeft = drag.calculatePosition(drag.objCurrent, 'offsetLeft');
var iCurrentTop = drag.calculatePosition(drag.objCurrent, 'offsetTop');
var iTolerance = 40;
var objList, iLeft, iRight, iTop, iBottom, iCounter;
for (iCounter=0; iCounter<drag.arTargets.length; iCounter++)
{
if (drag.arTargets[iCounter] != strExisting)
{
// Get position of the list
objList = document.getElementById(drag.arTargets[iCounter]);
iLeft = drag.calculatePosition(objList, 'offsetLeft') - iTolerance;
iRight = iLeft + objList.offsetWidth + iTolerance;
iTop = drag.calculatePosition(objList, 'offsetTop') - iTolerance;
iBottom = iTop + objList.offsetHeight + iTolerance;
// Determine if current object is over the target
if (iCurrentLeft > iLeft && iCurrentLeft < iRight && iCurrentTop > iTop && iCurrentTop < iBottom)
{
return drag.arTargets[iCounter];
}
}
}
// Current object is not over a target
return '';
},
dropObject : function(strTarget)
{
var objClone, objOriginal, objTarget, objEmpty, objBands, objItem;
drag.removePopup();
if (strTarget.length > 0)
{
// Copy node to new target
objOriginal = drag.objCurrent.parentNode;
objClone = drag.objCurrent.cloneNode(true);
// Remove previous attributes
objClone.removeAttribute('style');
objClone.className = objClone.className.replace(/\s*focused/, '');
objClone.className = objClone.className.replace(/\s*hover/, '');
// Add focus indicators
objClone.onfocus = function() {this.className += ' focused'; };
objClone.onblur = function() {this.className = this.className.replace(/\s*focused/, '');};
objClone.onmouseover = function() {if (this.className.indexOf('hover') < 0) {this.className += ' hover';} };
objClone.onmouseout = function() {this.className = this.className.replace(/\s*hover/, ''); };
objTarget = document.getElementById(strTarget);
objOriginal.removeChild(drag.objCurrent);
objTarget.appendChild(objClone);
drag.objCurrent = objClone;
drag.initialise(objClone);
// Remove empty node if there are artists in list
objEmpty = getElementsByClassName(objTarget, 'li', 'empty');
if (objEmpty[0])
{
objTarget.removeChild(objEmpty[0]);
}
// Add an empty node if there are no artists in list
objBands = objOriginal.getElementsByTagName('li');
if (objBands.length === 0)
{
objItem = document.createElement('li');
objItem.appendChild(document.createTextNode('None'));
objItem.className = 'empty';
objOriginal.appendChild(objItem);
}
}
// Reset properties
drag.objCurrent.style.left = '0px';
drag.objCurrent.style.top = '0px';
drag.objCurrent.style.zIndex = 'auto';
drag.objCurrent.setAttribute('aria-grabbed', 'false');
drag.objCurrent.removeAttribute('aria-owns');
drag.identifyTargets(false);
},
end : function()
{
var strTarget = drag.getTarget();
drag.dropObject(strTarget);
document.onmousemove = null;
document.onmouseup = null;
drag.objCurrent = null;
}
};
function init ()
{
var objItems = getElementsByClassName(document, 'li', 'draggable');
var objItem, iCounter;
for (iCounter=0; iCounter<objItems.length; iCounter++)
{
// Set initial values so can be moved
objItems[iCounter].style.top = '0px';
objItems[iCounter].style.left = '0px';
// Put the list items into the keyboard tab order
objItems[iCounter].tabIndex = 0;
// Set ARIA attributes for artists
objItems[iCounter].setAttribute('aria-grabbed', 'false');
objItems[iCounter].setAttribute('aria-haspopup', 'true');
objItems[iCounter].setAttribute('role', 'listitem');
// Provide a focus indicator
objItems[iCounter].onfocus = function() {this.className += ' focused'; };
objItems[iCounter].onblur = function() {this.className = this.className.replace(/\s*focused/, '');};
objItems[iCounter].onmouseover = function() {if (this.className.indexOf('hover') < 0) {this.className += ' hover';} };
objItems[iCounter].onmouseout = function() {this.className = this.className.replace(/\s*hover/, ''); };
drag.initialise(objItems[iCounter]);
}
// Set ARIA properties on the drag and drop list, and set role of this region to application
for (iCounter=0; iCounter<drag.arTargets.length; iCounter++)
{
objItem = document.getElementById(drag.arTargets[iCounter]);
objItem.setAttribute('aria-labelledby', drag.arTargets[iCounter] + 'h');
objItem.setAttribute('role', 'list');
}
objItem = document.getElementById('dragdrop');
objItem.setAttribute('role', 'application');
objItems = null;
}
window.onload = init;
This type of problem usually occurs when issuing an AJAX request which expects JSON or JavaScript as a response but it receives HTML in stead.
When expecting JSON or JavaScript the text of the response must be "eval"-uated by a JavaScript parser. If HTML code is received and evaluated as JSON or JavaScript, the presence of a lower than < or a greater than > symbol will cause errors.
The question is very simple but also a bit theoretical.
Let's imagine you have a long JQuery script which modifies and animate the graphics of the web site. It's objective is to handle the UI. The UI has to be responsive so the real need for this JQuery is to mix some state of visualization (sportlist visible / not visible) with some need due to Responsive UI.
Thinking from an MVC / AngularJS point of view. How should a programmer handle that?
How to refactor JS / JQuery code to implement separation of concerns described by MVC / AngularJS?
I provide an example of JQuery code to speak over something concrete.
$.noConflict();
jQuery(document).ready(function ($) {
/*variables*/
var sliderMenuVisible = false;
/*dom object variables*/
var $document = $(document);
var $window = $(window);
var $pageHost = $(".page-host");
var $sportsList = $("#sports-list");
var $mainBody = $("#mainBody");
var $toTopButtonContainer = $('#to-top-button-container');
/*eventHandlers*/
var displayError = function (form, error) {
$("#error").html(error).removeClass("hidden");
};
var calculatePageLayout = function () {
$pageHost.height($(window).height());
if ($window.width() > 697) {
$sportsList.removeAttr("style");
$mainBody
.removeAttr("style")
.unbind('touchmove')
.removeClass('stop-scroll');
if ($(".betslip-access-button")[0]) {
$(".betslip-access-button").fadeIn(500);
}
sliderMenuVisible = false;
} else {
$(".betslip-access-button").fadeOut(500);
}
};
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) {
window.location = json.redirect || location.href;
} else if (json.error) {
displayError($form, json.error);
}
})
.error(function () {
displayError($form, "Login service not available, please try again later.");
});
}
// Prevent the normal behavior since we opened the dialog
e.preventDefault();
};
//preliminary functions//
$window.on("load", calculatePageLayout);
$window.on("resize", calculatePageLayout);
//$(document).on("click","a",function (event) {
// event.preventDefault();
// window.location = $(this).attr("href");
//});
/*evet listeners*/
$("#login-form").submit(formSubmitHandler);
$("section.navigation").on("shown hidden", ".collapse", function (e) {
var $icon = $(this).parent().children("button").children("i").first();
if (!$icon.hasClass("icon-spin")) {
if (e.type === "shown") {
$icon.removeClass("icon-caret-right").addClass("icon-caret-down");
} else {
$icon.removeClass("icon-caret-down").addClass("icon-caret-right");
}
}
toggleBackToTopButton();
e.stopPropagation();
});
$(".collapse[data-src]").on("show", function () {
var $this = $(this);
if (!$this.data("loaded")) {
var $icon = $this.parent().children("button").children("i").first();
$icon.removeClass("icon-caret-right icon-caret-down").addClass("icon-refresh icon-spin");
console.log("added class - " + $icon.parent().html());
$this.load($this.data("src"), function () {
$this.data("loaded", true);
$icon.removeClass("icon-refresh icon-spin icon-caret-right").addClass("icon-caret-down");
console.log("removed class - " + $icon.parent().html());
});
}
toggleBackToTopButton();
});
$("#sports-list-button").on("click", function (e)
{
if (!sliderMenuVisible)
{
$sportsList.animate({ left: "0" }, 500);
$mainBody.animate({ left: "85%" }, 500)
.bind('touchmove', function (e2) { e2.preventDefault(); })
.addClass('stop-scroll');
$(".betslip-access-button").fadeOut(500);
sliderMenuVisible = true;
}
else
{
$sportsList.animate({ left: "-85%" }, 500).removeAttr("style");
$mainBody.animate({ left: "0" }, 500).removeAttr("style")
.unbind('touchmove').removeClass('stop-scroll');
$(".betslip-access-button").fadeIn(500);
sliderMenuVisible = false;
}
e.preventDefault();
});
$mainBody.on("click", function (e) {
if (sliderMenuVisible) {
$sportsList.animate({ left: "-85%" }, 500).removeAttr("style");
$mainBody.animate({ left: "0" }, 500)
.removeAttr("style")
.unbind('touchmove')
.removeClass('stop-scroll');
$(".betslip-access-button").fadeIn(500);
sliderMenuVisible = false;
e.stopPropagation();
e.preventDefault();
}
});
$document.on("click", "div.event-info", function () {
if (!sliderMenuVisible) {
var url = $(this).data("url");
if (url) {
window.location = url;
}
}
});
function whatDecimalSeparator() {
var n = 1.1;
n = n.toLocaleString().substring(1, 2);
return n;
}
function getValue(textBox) {
var value = textBox.val();
var separator = whatDecimalSeparator();
var old = separator == "," ? "." : ",";
var converted = parseFloat(value.replace(old, separator));
return converted;
}
$(document).on("click", "a.selection", function (e) {
if (sliderMenuVisible) {
return;
}
var $this = $(this);
var isLive = $this.data("live");
var url = "/" + _language + "/BetSlip/Add/" + $this.data("selection") + "?odds=" + $this.data("odds") + "&live=" + isLive;
var urlHoveringBtn = "/" + _language + '/BetSlip/AddHoveringButton/' + $this.data("selection") + "?odds=" + $this.data("odds") + "&live=" + isLive;
$.ajax(urlHoveringBtn).done(function (dataBtn) {
if ($(".betslip-access-button").length == 0 && dataBtn.length > 0) {
$("body").append(dataBtn);
}
});
$.ajax(url).done(function (data) {
if ($(".betslip-access").length == 0 && data.length > 0) {
$(".navbar").append(data);
$pageHost.addClass("betslipLinkInHeader");
var placeBetText = $("#live-betslip-popup").data("placebettext");
var continueText = $("#live-betslip-popup").data("continuetext");
var useQuickBetLive = $("#live-betslip-popup").data("usequickbetlive").toLowerCase() == "true";
var useQuickBetPrematch = $("#live-betslip-popup").data("usequickbetprematch").toLowerCase() == "true";
if ((isLive && useQuickBetLive) || (!isLive && useQuickBetPrematch)) {
var dialog = $("#live-betslip-popup").dialog({
modal: true,
dialogClass: "fixed-dialog"
});
dialog.dialog("option", "buttons", [
{
text: placeBetText,
click: function () {
var placeBetUrl = "/" + _language + "/BetSlip/QuickBet?amount=" + getValue($("#live-betslip-popup-amount")) + "&live=" + $this.data("live");
window.location = placeBetUrl;
}
},
{
text: continueText,
click: function () {
dialog.dialog("close");
}
}
]);
}
}
if (data.length > 0) {
$this.addClass("in-betslip");
}
});
e.preventDefault();
});
$(document).on("click", "a.selection.in-betslip", function (e) {
if (sliderMenuVisible) {
return;
}
var $this = $(this);
var isLive = $this.data("live");
var url = "/" + _language + "/BetSlip/RemoveAjax/" + $this.data("selection") + "?odds=" + $this.data("odds") + "&live=" + isLive;
$.ajax(url).done(function (data) {
if (data.success) {
$this.removeClass("in-betslip");
if (data.selections == 0) {
$(".betslip-access").remove();
$(".betslip-access-button").remove();
$(".page-host").removeClass("betslipLinkInHeader");
}
}
});
e.preventDefault();
});
$("section.betslip .total-stake button.live-betslip-popup-plusminus").click(function (e) {
if (sliderMenuVisible) {
return;
}
e.preventDefault();
var action = $(this).data("action");
var amount = parseFloat($(this).data("amount"));
if (!isNumeric(amount)) amount = 1;
var totalStake = $("#live-betslip-popup-amount").val();
if (isNumeric(totalStake)) {
totalStake = parseFloat(totalStake);
} else {
totalStake = 0;
}
if (action == "decrease") {
if (totalStake < 1.21) {
totalStake = 1.21;
}
totalStake -= amount;
} else if (action == "increase") {
totalStake += amount;
}
$("#live-betslip-popup-amount").val(totalStake);
});
toggleBackToTopButton();
function toggleBackToTopButton() {
isScrollable() ? $toTopButtonContainer.show() : $toTopButtonContainer.hide();
}
$("#to-top-button").on("click", function () { $("#mainBody").animate({ scrollTop: 0 }); });
function isScrollable() {
return $("section.navigation").height() > $(window).height() + 93;
}
var isNumeric = function (string) {
return !isNaN(string) && isFinite(string) && string != "";
};
function enableQuickBet() {
}
});
My steps in such cases are:
First of all write (at least) one controller
Replace all eventhandler with ng-directives (ng-click most of all)
Pull the view state out of the controller with ng-style and ng-class. In most of all cases ng-show and ng-hide will be sufficed
If there is code that will be used more than once, consider writing a directive.
And code that has nothing todo with the view state - put the code in a service
write unit tests (i guess there is no one until now:) )
I'm using an S3Slider on WordPress. I'd like it to stop after one rotation, but can't seem to figure out the setting.
The following code is used in the slider.js file. Right now it's live at http://beaconwc.com on the frontpage, would like it to show the two slides and then stop until the user refreshes the page.
(function($){
$.fn.s3Slider = function(vars) {
var element = this;
var timeOut = (vars.timeOut != undefined) ? vars.timeOut : 4000;
var current = null;
var timeOutFn = null;
var faderStat = true;
var mOver = false;
var capOpacity = vars.captionOpacity || .7;
var items = $("#" + element[0].id + "Content ." + element[0].id + "Image");
var itemsSpan = $("#" + element[0].id + "Content ." + element[0].id + "Image div");
itemsSpan.css({
'opacity': capOpacity,
'display': 'none'
});
items.each(function(i) {
$(items[i]).mouseover(function() {
mOver = true;
});
$(items[i]).mouseout(function() {
mOver = false;
fadeElement(true);
});
});
var fadeElement = function(isMouseOut) {
var thisTimeOut = (isMouseOut) ? (timeOut/2) : timeOut;
thisTimeOut = (faderStat) ? 10 : thisTimeOut;
if(items.length > 0) {
timeOutFn = setTimeout(makeSlider, thisTimeOut);
} else {
console.log("Poof..");
}
}
var makeSlider = function() {
current = (current != null) ? current : items[(items.length-1)];
var currNo = jQuery.inArray(current, items) + 1
currNo = (currNo == items.length) ? 0 : (currNo - 1);
var newMargin = $(element).width() * currNo;
if(faderStat == true) {
if(!mOver) {
$(items[currNo]).fadeIn((timeOut/6), function() {
if($(itemsSpan[currNo]).css('bottom') == 0) {
$(itemsSpan[currNo]).slideUp((timeOut/6), function() {
faderStat = false;
current = items[currNo];
if(!mOver) {
fadeElement(false);
}
});
} else {
$(itemsSpan[currNo]).slideDown((timeOut/6), function() {
faderStat = false;
current = items[currNo];
if(!mOver) {
fadeElement(false);
}
});
}
});
}
} else {
if(!mOver) {
if($(itemsSpan[currNo]).css('bottom') == 0) {
$(itemsSpan[currNo]).slideDown((timeOut/6), function() {
$(items[currNo]).fadeOut((timeOut/6), function() {
faderStat = true;
current = items[(currNo+1)];
if(!mOver) {
fadeElement(false);
}
});
});
} else {
$(itemsSpan[currNo]).slideUp((timeOut/6), function() {
$(items[currNo]).fadeOut((timeOut/6), function() {
faderStat = true;
current = items[(currNo+1)];
if(!mOver) {
fadeElement(false);
}
});
});
}
}
}
}
makeSlider();
};
})(jQuery);
Thanks!
Source
http://www.serie3.info/s3slider/
I'm using the FoxyComplete plugin with Jquery Auto-Complete to do a simple search. Everything is setup and working great but I'm finding the search results extremely limiting.
I use a big JSON file for all of my data and the problem I'm having is in the following case: Let's assume that my JSON file has the following description:
{
"title": "Brand new black car"
},
If a user searches "black car", he or she will get the result perfectly and the search is great. BUT, if a user search "car new", nothing comes up even though both keywords are in my JSON file.
Any help would be great. I poured through the Jquery AutoComplete docs and couldn't find a solution either. My Jquery autocomplete js is below:
;
(function ($) {
$.fn.extend({
autocomplete: function (urlOrData, options) {
var isUrl = typeof urlOrData == "string";
options = $.extend({}, $.Autocompleter.defaults, {
url: isUrl ? urlOrData : null,
data: isUrl ? null : urlOrData,
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
max: options && !options.scroll ? 10 : 150
}, options);
// if highlight is set to false, replace it with a do-nothing function
options.highlight = options.highlight || function (value) {
return value;
};
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
options.formatMatch = options.formatMatch || options.formatItem;
return this.each(function () {
new $.Autocompleter(this, options);
});
},
result: function (handler) {
return this.bind("result", handler);
},
search: function (handler) {
return this.trigger("search", [handler]);
},
flushCache: function () {
return this.trigger("flushCache");
},
setOptions: function (options) {
return this.trigger("setOptions", [options]);
},
unautocomplete: function () {
return this.trigger("unautocomplete");
}
});
$.Autocompleter = function (input, options) {
var KEY = {
UP: 38,
DOWN: 40,
DEL: 46,
TAB: 9,
RETURN: 13,
ESC: 27,
COMMA: 188,
PAGEUP: 33,
PAGEDOWN: 34,
BACKSPACE: 8
};
// Create $ object for input element
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
var timeout;
var previousValue = "";
var cache = $.Autocompleter.Cache(options);
var hasFocus = 0;
var lastKeyPressCode;
var config = {
mouseDownOnSelect: false
};
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
var blockSubmit;
// prevent form submit in opera when selecting with return key
$.browser.opera && $(input.form).bind("submit.autocomplete", function () {
if (blockSubmit) {
blockSubmit = false;
return false;
}
});
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function (event) {
// a keypress means the input has focus
// avoids issue where input had focus before the autocomplete was applied
hasFocus = 1;
// track last key pressed
lastKeyPressCode = event.keyCode;
switch (event.keyCode) {
case KEY.UP:
event.preventDefault();
if (select.visible()) {
select.prev();
} else {
onChange(0, true);
}
break;
case KEY.DOWN:
event.preventDefault();
if (select.visible()) {
select.next();
} else {
onChange(0, true);
}
break;
case KEY.PAGEUP:
event.preventDefault();
if (select.visible()) {
select.pageUp();
} else {
onChange(0, true);
}
break;
case KEY.PAGEDOWN:
event.preventDefault();
if (select.visible()) {
select.pageDown();
} else {
onChange(0, true);
}
break;
// matches also semicolon
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
case KEY.TAB:
case KEY.RETURN:
if (selectCurrent()) {
// stop default to prevent a form submit, Opera needs special handling
event.preventDefault();
blockSubmit = true;
return false;
}
break;
case KEY.ESC:
select.hide();
break;
default:
clearTimeout(timeout);
timeout = setTimeout(onChange, options.delay);
break;
}
}).focus(function () {
// track whether the field has focus, we shouldn't process any
// results if the field no longer has focus
hasFocus++;
}).blur(function () {
hasFocus = 0;
if (!config.mouseDownOnSelect) {
hideResults();
}
}).click(function () {
// show select when clicking in a focused field
if (hasFocus++ > 1 && !select.visible()) {
onChange(0, true);
}
}).bind("search", function () {
// TODO why not just specifying both arguments?
var fn = (arguments.length > 1) ? arguments[1] : null;
function findValueCallback(q, data) {
var result;
if (data && data.length) {
for (var i = 0; i < data.length; i++) {
if (data[i].result.toLowerCase() == q.toLowerCase()) {
result = data[i];
break;
}
}
}
if (typeof fn == "function") fn(result);
else $input.trigger("result", result && [result.data, result.value]);
}
$.each(trimWords($input.val()), function (i, value) {
request(value, findValueCallback, findValueCallback);
});
}).bind("flushCache", function () {
cache.flush();
}).bind("setOptions", function () {
$.extend(options, arguments[1]);
// if we've updated the data, repopulate
if ("data" in arguments[1]) cache.populate();
}).bind("unautocomplete", function () {
select.unbind();
$input.unbind();
$(input.form).unbind(".autocomplete");
});
function selectCurrent() {
var selected = select.selected();
if (!selected) return false;
var v = selected.result;
previousValue = v;
if (options.multiple) {
var words = trimWords($input.val());
if (words.length > 1) {
var seperator = options.multipleSeparator.length;
var cursorAt = $(input).selection().start;
var wordAt, progress = 0;
$.each(words, function (i, word) {
progress += word.length;
if (cursorAt <= progress) {
wordAt = i;
return false;
}
progress += seperator;
});
words[wordAt] = v;
// TODO this should set the cursor to the right position, but it gets overriden somewhere
//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
v = words.join(options.multipleSeparator);
}
v += options.multipleSeparator;
}
$input.val(v);
hideResultsNow();
$input.trigger("result", [selected.data, selected.value]);
return true;
}
function onChange(crap, skipPrevCheck) {
if (lastKeyPressCode == KEY.DEL) {
select.hide();
return;
}
var currentValue = $input.val();
if (!skipPrevCheck && currentValue == previousValue) return;
previousValue = currentValue;
currentValue = lastWord(currentValue);
if (currentValue.length >= options.minChars) {
$input.addClass(options.loadingClass);
if (!options.matchCase) currentValue = currentValue.toLowerCase();
request(currentValue, receiveData, hideResultsNow);
} else {
stopLoading();
select.hide();
}
};
function trimWords(value) {
if (!value) return [""];
if (!options.multiple) return [$.trim(value)];
return $.map(value.split(options.multipleSeparator), function (word) {
return $.trim(value).length ? $.trim(word) : null;
});
}
function lastWord(value) {
if (!options.multiple) return value;
var words = trimWords(value);
if (words.length == 1) return words[0];
var cursorAt = $(input).selection().start;
if (cursorAt == value.length) {
words = trimWords(value)
} else {
words = trimWords(value.replace(value.substring(cursorAt), ""));
}
return words[words.length - 1];
}
// fills in the input box w/the first match (assumed to be the best match)
// q: the term entered
// sValue: the first matching result
function autoFill(q, sValue) {
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
// if the last user key pressed was backspace, don't autofill
if (options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE) {
// fill in the value (keep the case the user has typed)
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
// select the portion of the value not typed by the user (so the next character will erase)
$(input).selection(previousValue.length, previousValue.length + sValue.length);
}
};
function hideResults() {
clearTimeout(timeout);
timeout = setTimeout(hideResultsNow, 200);
};
function hideResultsNow() {
var wasVisible = select.visible();
select.hide();
clearTimeout(timeout);
stopLoading();
if (options.mustMatch) {
// call search and run callback
$input.search(
function (result) {
// if no value found, clear the input box
if (!result) {
if (options.multiple) {
var words = trimWords($input.val()).slice(0, - 1);
$input.val(words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : ""));
} else {
$input.val("");
$input.trigger("result", null);
}
}
});
}
};
function receiveData(q, data) {
if (data && data.length && hasFocus) {
stopLoading();
select.display(data, q);
autoFill(q, data[0].value);
select.show();
} else {
hideResultsNow();
}
};
function request(term, success, failure) {
if (!options.matchCase) term = term.toLowerCase();
var data = cache.load(term);
// recieve the cached data
if (data && data.length) {
success(term, data);
// if an AJAX url has been supplied, try loading the data now
} else if ((typeof options.url == "string") && (options.url.length > 0)) {
var extraParams = {
timestamp: +new Date()
};
$.each(options.extraParams, function (key, param) {
extraParams[key] = typeof param == "function" ? param() : param;
});
$.ajax({
// try to leverage ajaxQueue plugin to abort previous requests
mode: "abort",
// limit abortion to this input
port: "autocomplete" + input.name,
dataType: options.dataType,
url: options.url,
data: $.extend({
q: lastWord(term),
limit: options.max
}, extraParams),
success: function (data) {
var parsed = options.parse && options.parse(data) || parse(data);
cache.add(term, parsed);
success(term, parsed);
}
});
} else {
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
select.emptyList();
failure(term);
}
};
function parse(data) {
var parsed = [];
var rows = data.split("\n");
for (var i = 0; i < rows.length; i++) {
var row = $.trim(rows[i]);
if (row) {
row = row.split("|");
parsed[parsed.length] = {
data: row,
value: row[0],
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
};
}
}
return parsed;
};
function stopLoading() {
$input.removeClass(options.loadingClass);
};
};
$.Autocompleter.defaults = {
inputClass: "ac_input",
resultsClass: "ac_results",
loadingClass: "ac_loading",
minChars: 1,
delay: 400,
matchCase: false,
matchSubset: true,
matchContains: false,
cacheLength: 10,
max: 100,
mustMatch: false,
extraParams: {},
selectFirst: true,
formatItem: function (row) {
return row[0];
},
formatMatch: null,
autoFill: false,
width: 0,
multiple: false,
multipleSeparator: ", ",
highlight: function (value, term) {
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
},
scroll: true,
scrollHeight: 180
};
$.Autocompleter.Cache = function (options) {
var data = {};
var length = 0;
function matchSubset(s, sub) {
if (!options.matchCase) s = s.toLowerCase();
var i = s.indexOf(sub);
if (options.matchContains == "word") {
i = s.toLowerCase().search("\\b" + sub.toLowerCase());
}
if (i == -1) return false;
return i == 0 || options.matchContains;
};
function add(q, value) {
if (length > options.cacheLength) {
flush();
}
if (!data[q]) {
length++;
}
data[q] = value;
}
function populate() {
if (!options.data) return false;
// track the matches
var stMatchSets = {},
nullData = 0;
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
if (!options.url) options.cacheLength = 1;
// track all options for minChars = 0
stMatchSets[""] = [];
// loop through the array and create a lookup structure
for (var i = 0, ol = options.data.length; i < ol; i++) {
var rawValue = options.data[i];
// if rawValue is a string, make an array otherwise just reference the array
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
var value = options.formatMatch(rawValue, i + 1, options.data.length);
if (value === false) continue;
var firstChar = value.charAt(0).toLowerCase();
// if no lookup array for this character exists, look it up now
if (!stMatchSets[firstChar]) stMatchSets[firstChar] = [];
// if the match is a string
var row = {
value: value,
data: rawValue,
result: options.formatResult && options.formatResult(rawValue) || value
};
// push the current match into the set list
stMatchSets[firstChar].push(row);
// keep track of minChars zero items
if (nullData++ < options.max) {
stMatchSets[""].push(row);
}
};
// add the data items to the cache
$.each(stMatchSets, function (i, value) {
// increase the cache size
options.cacheLength++;
// add to the cache
add(i, value);
});
}
// populate any existing data
setTimeout(populate, 25);
function flush() {
data = {};
length = 0;
}
return {
flush: flush,
add: add,
populate: populate,
load: function (q) {
if (!options.cacheLength || !length) return null;
/*
* if dealing w/local data and matchContains than we must make sure
* to loop through all the data collections looking for matches
*/
if (!options.url && options.matchContains) {
// track all matches
var csub = [];
// loop through all the data grids for matches
for (var k in data) {
// don't search through the stMatchSets[""] (minChars: 0) cache
// this prevents duplicates
if (k.length > 0) {
var c = data[k];
$.each(c, function (i, x) {
// if we've got a match, add it to the array
if (matchSubset(x.value, q)) {
csub.push(x);
}
});
}
}
return csub;
} else
// if the exact item exists, use it
if (data[q]) {
return data[q];
} else if (options.matchSubset) {
for (var i = q.length - 1; i >= options.minChars; i--) {
var c = data[q.substr(0, i)];
if (c) {
var csub = [];
$.each(c, function (i, x) {
if (matchSubset(x.value, q)) {
csub[csub.length] = x;
}
});
return csub;
}
}
}
return null;
}
};
};
$.Autocompleter.Select = function (options, input, select, config) {
var CLASSES = {
ACTIVE: "ac_over"
};
var listItems,
active = -1,
data,
term = "",
needsInit = true,
element,
list;
// Create results
function init() {
if (!needsInit) return;
element = $("<div/>").hide().addClass(options.resultsClass).css("position", "absolute")
//.attr('id', 'benjamin')
.appendTo(document.body);
list = $("<ul/>").appendTo(element).mouseover(function (event) {
if (target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
$(target(event)).addClass(CLASSES.ACTIVE);
}
}).click(function (event) {
$(target(event)).addClass(CLASSES.ACTIVE);
select();
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
input.focus();
return false;
}).mousedown(function () {
config.mouseDownOnSelect = true;
}).mouseup(function () {
config.mouseDownOnSelect = false;
});
if (options.width > 0) element.css("width", options.width);
needsInit = false;
}
function target(event) {
var element = event.target;
while (element && element.tagName != "LI")
element = element.parentNode;
// more fun with IE, sometimes event.target is empty, just ignore it then
if (!element) return [];
return element;
}
function moveSelect(step) {
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
movePosition(step);
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
if (options.scroll) {
var offset = 0;
listItems.slice(0, active).each(function () {
offset += this.offsetHeight;
});
if ((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
} else if (offset < list.scrollTop()) {
list.scrollTop(offset);
}
}
};
function movePosition(step) {
active += step;
if (active < 0) {
active = listItems.size() - 1;
} else if (active >= listItems.size()) {
active = 0;
}
}
function limitNumberOfItems(available) {
return options.max && options.max < available ? options.max : available;
}
function fillList() {
list.empty();
var max = limitNumberOfItems(data.length);
for (var i = 0; i < max; i++) {
if (!data[i]) continue;
var formatted = options.formatItem(data[i].data, i + 1, max, data[i].value, term);
if (formatted === false) continue;
var li = $("<li/>").html(options.highlight(formatted, term)).addClass(i % 2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
$.data(li, "ac_data", data[i]);
}
listItems = list.find("li");
if (options.selectFirst) {
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
active = 0;
}
// apply bgiframe if available
if ($.fn.bgiframe) list.bgiframe();
}
return {
display: function (d, q) {
init();
data = d;
term = q;
fillList();
},
next: function () {
moveSelect(1);
},
prev: function () {
moveSelect(-1);
},
pageUp: function () {
if (active != 0 && active - 8 < 0) {
moveSelect(-active);
} else {
moveSelect(-8);
}
},
pageDown: function () {
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
moveSelect(listItems.size() - 1 - active);
} else {
moveSelect(8);
}
},
hide: function () {
element && element.hide();
listItems && listItems.removeClass(CLASSES.ACTIVE);
active = -1;
},
visible: function () {
return element && element.is(":visible");
},
current: function () {
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
},
show: function () {
var offset = $(input).offset();
element.css({
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
top: offset.top + input.offsetHeight,
left: offset.left
}).show();
if (options.scroll) {
list.scrollTop(0);
list.css({
maxHeight: options.scrollHeight,
overflow: 'auto'
});
if ($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
var listHeight = 0;
listItems.each(function () {
listHeight += this.offsetHeight;
});
var scrollbarsVisible = listHeight > options.scrollHeight;
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight);
if (!scrollbarsVisible) {
// IE doesn't recalculate width when scrollbar disappears
listItems.width(list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")));
}
}
}
},
selected: function () {
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
return selected && selected.length && $.data(selected[0], "ac_data");
},
emptyList: function () {
list && list.empty();
},
unbind: function () {
element && element.remove();
}
};
};
$.fn.selection = function (start, end) {
if (start !== undefined) {
return this.each(function () {
if (this.createTextRange) {
var selRange = this.createTextRange();
if (end === undefined || start == end) {
selRange.move("character", start);
selRange.select();
} else {
selRange.collapse(true);
selRange.moveStart("character", start);
selRange.moveEnd("character", end);
selRange.select();
}
} else if (this.setSelectionRange) {
this.setSelectionRange(start, end);
} else if (this.selectionStart) {
this.selectionStart = start;
this.selectionEnd = end;
}
});
}
var field = this[0];
if (field.createTextRange) {
var range = document.selection.createRange(),
orig = field.value,
teststring = "<->",
textLength = range.text.length;
range.text = teststring;
var caretAt = field.value.indexOf(teststring);
field.value = orig;
this.selection(caretAt, caretAt + textLength);
return {
start: caretAt,
end: caretAt + textLength
}
} else if (field.selectionStart !== undefined) {
return {
start: field.selectionStart,
end: field.selectionEnd
}
}
};
})(jQuery);