Related
I'm facing some difficulties while trying to implement 'xAxis clickable' column chart. I'm trying to expose additional Pie charts below my column chart, based on user click on one of the element in xAxis.
The way the first graph is build:
function chartBuilder(data) {
if (data.length === 0) {
return null;
}
var categories = [];
var uniqData = [
{name : 'Fatal', data:[], color:'black', stack: 'fatal'},
{name : 'Critical', data:[], color:'red', stack: 'critical'},
];
_.each(data, function (item) {
categories = categories.concat(item.site);
var fatalValue = {};
fatalValue[item.site] = parseFloat(item.fatal || 0);
uniqData[0].data = uniqData[0].data.concat(fatalValue);
var criticalValue = {};
criticalValue[item.site] = parseFloat(item.critical || 0);
uniqData[1].data = uniqData[1].data.concat(criticalValue);
});
var chartConfig = util.basicConfigChart(categories, uniqData);
chartConfig.yAxis.title = {
text: 'Num Of Events'
};
chartConfig.xAxis.labels = {
formatter: function() {
var ret = this.value,
len = ret.length;
if (len > 10) {
ret = '<strong>' + ret.slice(0,ret.indexOf('_')) + '<br/>' + ret.slice(ret.indexOf('_') + 1, len) + '</strong>';
}
if (len > 25) {
ret = ret.slice(0,25) + '...';
}
return '<strong>' + ret + '</strong>';
},
useHTML: true
};
chartConfig.options.tooltip = {
formatter : function () {
return '<strong>' + this.series.name + '</strong>:' + this.point.y + '<br>Total: ' + this.point.total;
}
};
return chartConfig;
}
So basically, what I need is a way to determine which element in xAxis was clicked, and expose below pie charts with data relevant to this element.
If I understand you correctly, you want to add a click event on the xAxis columns. This can be done using a click event
events: {
click: function (event) {
alert(
'x index: ' + event.point.x + ', \n' +
'series.index: ' + event.point.series.index
);
}
}
This event can be added to a specific series, or to the plotOptions to affect all series.
The code above, will make an alert that shows the relevant indexes.
Working example: https://jsfiddle.net/ewolden/xr17pen6/6/
API on click event: https://api.highcharts.com/highcharts/series.column.events.click
I have an if statement that when I print the result in the console, I can see sometimes its true, and sometimes its false.
However, whats inside the IF, its never executed and the resulting array is always empty.
var createQuery = function(viewFields,clientCode) {
return '<View Scope="RecursiveAll">' + viewFields +
'<Query>' +
'<Where>' +
'<And>' +
'<Eq>' +
'<FieldRef Name="ClientCode" />' +
'<Value Type="Text">'+ clientCode + '</Value>' +
'</Eq>' +
'<Neq>' +
'<FieldRef Name="ContentType" />' +
'<Value Type="Computed">Bill Cycle</Value>' +
'</Neq>' +
'</And>' +
'</Where>' +
'</Query>' +
'</View>';
};
var createListItemValues = function(filter) {
return function(listItems,selectProperties) {
var listItemsWithValues = [];
if (listItems) {
var enumerator = listItems.getEnumerator();
while (enumerator.moveNext()) {
var listItem = enumerator.get_current();
var listItemValues = [];
selectProperties
.forEach(function (propertyName) {
var value = listItem.get_item(propertyName);
if (propertyName === "JobCodesMulti") {
jobvalue = "";
value.forEach(function (jobvalues) {
jobvalue += jobvalues.get_lookupValue() + ";";
})
listItemValues[propertyName] = jobvalue;
} else {
listItemValues[propertyName] = value;
}
});
if(filter(listItemValues)){//only push if filter returns true
listItemsWithValues.push(listItemValues);
}
}
}
return listItemsWithValues;
};
};
var processListItemWithValue = function(listItemsWithValues) {
return function(listItem) {
var fileDirRef = listItem["FileRef"];
var id = listItem["ID"];
var title = listItem["Title"];
var serverUrl = _spPageContextInfo.webAbsoluteUrl.replace(_spPageContextInfo.webServerRelativeUrl, "");
var dispFormUrl = serverUrl + "/sites/billing/_layouts/15/DocSetHome.aspx?id=" + fileDirRef;
var parentLink = listItem["FileRef"];
//!!!PLEASE NOTE: made arrayofstrings a local variable
var arrayofstrings = parentLink.split("/");
var billCycleFolderName = arrayofstrings[arrayofstrings.length - 2];
arrayofstrings.pop();
var hyperLink = '' + billCycleFolderName + '';
listItem["Bill Cycle"] = hyperLink;
listItemsWithValues["Document Type"] = getContentTypeOfCurrentItem(listItem.ID.toString());
}
};
function GetRelatedBillingDocumentsFromList(selectProperties, currentBillCyclePath, clientCode, jobCodes, engagementCode, enhanceFunctions) {
$log.info("Retrieving related billing documents for bill cycle with name [" + currentBillCyclePath + "]");
//pass filter function to createListItemValues to get a new function that
// creates filtered list item values
var createFilteredListItemsWithValues = createListItemValues(
function(listItemValues) {
var x1=listItemValues && typeof listItemValues.FileRef === "string" && listItemValues.FileRef.split("/")[4];
var x2= currentBillCyclePath.split("/")[8]
console.log(x1===x2);
return !(//pass filter function to createListItemValues
listItemValues &&
typeof listItemValues.FileRef === "string" &&
listItemValues.FileRef.split("/")[4]
) === currentBillCyclePath.split("/")[8];
}
);
var webUrl = _spPageContextInfo.webAbsoluteUrl;
selectProperties = selectProperties.concat("ContentTypeId");
var viewFields = spService.ConvertSelectPropertiesToViewFields(selectProperties);
// query must return the documents for the same client but in other bill cycles not the current one
var camlQuery = createQuery(viewFields,clientCode);
var billCyclesListId = "{c23bbae4-34f7-494c-8f67-acece3ba60da}";
//return a promise like here so the caller knows if something went wrong
return spService.GetListItems(billCyclesListId, camlQuery, selectProperties)
.then(
function(listItems){
console.log("currentBillCyclePath:",currentBillCyclePath);
var listItemsValues = createFilteredListItemsWithValues
(listItems,selectProperties);
return $q.all(listItemsValues.map(addContentType))
.then(function(){ return listItemsValues; })//finished asynchronously mutating array of listItems
}
).then(
function(listItemsWithValues) {
listItemsWithValues.forEach(processListItemWithValue(listItemsWithValues));
return $q.all(
spService.SpSearchQuery.EnhanceSearchResults(listItemsWithValues, enhanceFunctions)
)
}
)
}
the important lines are: var createFilteredListItemsWithValues and if(filter(listItemValues))
You filter function will always return false because you're checking if a String value equals a boolean value.
!(
listItemValues &&
typeof listItemValues.FileRef === "string" &&
listItemValues.FileRef.split("/")[4]
)
Is a boolean, while
currentBillCyclePath.split("/")[8];
is a string.
Please see comment under question first.
I apologize to those that have read this question so far (10 views). I'm still not sure of the syntax to pass in the quiz configuration but she apparently allows it.
I'm trying to make a small change so the slickQuiz plugin supports multiple quizes on the same page, which the author 3 years suggested it probably can do. However it's loading in the same quiz questions and it seems due to the defining of quizJSON in slickQuiz-config.js file:
// Setup your quiz text and questions here
var quizJSON = {
"info": {
"name": "Test Your Knowledge!!",
"main": "<p>Think you're smart enough to be on Jeopardy?
... continues on ...
}
};
and the following line in slickQuiz.js, apparently allowing no variability on the quizJSON configuration loaded in:
// Set via json option or quizJSON variable (see slickQuiz-config.js)
var quizValues = (plugin.config.json ? plugin.config.json : typeof quizJSON != 'undefined' ? quizJSON : null);
I'm quite new to Javascript and am not sure how I could pass something in to set which quiz configuration (questions/answers) file is used for quizValues . Note the plugin includes a tiny master.js file, where it appears I can set a different id for each quiz:
// master.js file -- originally just defined #slickQuiz
$(function () {
$('#slickQuiz').slickQuiz();
});
$(function () {
$('#slickQuiz2').slickQuiz();
});
But how do I pass a parameter into slickQuiz() that I can use inside slickQuiz.js? This is probably trivial, but the way the function is invoked and its options are confusing to me new to JS.
Note here is how the slickQuiz id gets used in the plugin's example html file:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
<title>SlickQuiz Demo</title>
<link href="css/reset.css" media="screen" rel="stylesheet" type="text/css">
<link href="css/slickQuiz.css" media="screen" rel="stylesheet" type="text/css">
<link href="css/master.css" media="screen" rel="stylesheet" type="text/css">
</head>
<body id="slickQuiz">
<h1 class="quizName"><!-- where the quiz name goes --></h1>
<div class="quizArea">
<div class="quizHeader">
<!-- where the quiz main copy goes -->
<a class="button startQuiz" href="#">Get Started!</a>
</div>
<!-- where the quiz gets built -->
</div>
<div class="quizResults">
<h3 class="quizScore">You Scored: <span><!-- where the quiz score goes --></span></h3>
<h3 class="quizLevel"><strong>Ranking:</strong> <span><!-- where the quiz ranking level goes --></span></h3>
<div class="quizResultsCopy">
<!-- where the quiz result copy goes -->
</div>
</div>
<script src="js/jquery.js"></script>
<script src="js/slickQuiz-config.js"></script>
<script src="js/slickQuiz.js"></script>
<script src="js/master.js"></script>
</body>
</html>
Here is most of the slickQuiz.js file (only allowed to copy in so much, rest at link above):
/*!
* SlickQuiz jQuery Plugin
(function($){
$.slickQuiz = function(element, options) {
var plugin = this,
$element = $(element),
_element = '#' + $element.attr('id'),
defaults = {
checkAnswerText: 'Check My Answer!',
nextQuestionText: 'Next ยป',
backButtonText: '',
completeQuizText: '',
tryAgainText: '',
questionCountText: 'Question %current of %total',
preventUnansweredText: 'You must select at least one answer.',
questionTemplateText: '%count. %text',
scoreTemplateText: '%score / %total',
nameTemplateText: '<span>Quiz: </span>%name',
skipStartButton: false,
numberOfQuestions: null,
randomSortQuestions: false,
randomSortAnswers: false,
preventUnanswered: false,
disableScore: false,
disableRanking: false,
scoreAsPercentage: false,
perQuestionResponseMessaging: true,
perQuestionResponseAnswers: false,
completionResponseMessaging: false,
displayQuestionCount: true, // Deprecate?
displayQuestionNumber: true, // Deprecate?
animationCallbacks: { // only for the methods that have jQuery animations offering callback
setupQuiz: function () {},
startQuiz: function () {},
resetQuiz: function () {},
checkAnswer: function () {},
nextQuestion: function () {},
backToQuestion: function () {},
completeQuiz: function () {}
},
events: {
onStartQuiz: function (options) {},
onCompleteQuiz: function (options) {} // reserved: options.questionCount, options.score
}
},
// Class Name Strings (Used for building quiz and for selectors)
questionCountClass = 'questionCount',
questionGroupClass = 'questions',
questionClass = 'question',
answersClass = 'answers',
responsesClass = 'responses',
completeClass = 'complete',
correctClass = 'correctResponse',
incorrectClass = 'incorrectResponse',
correctResponseClass = 'correct',
incorrectResponseClass = 'incorrect',
checkAnswerClass = 'checkAnswer',
nextQuestionClass = 'nextQuestion',
lastQuestionClass = 'lastQuestion',
backToQuestionClass = 'backToQuestion',
tryAgainClass = 'tryAgain',
// Sub-Quiz / Sub-Question Class Selectors
_questionCount = '.' + questionCountClass,
_questions = '.' + questionGroupClass,
_question = '.' + questionClass,
_answers = '.' + answersClass,
_answer = '.' + answersClass + ' li',
_responses = '.' + responsesClass,
_response = '.' + responsesClass + ' li',
_correct = '.' + correctClass,
_correctResponse = '.' + correctResponseClass,
_incorrectResponse = '.' + incorrectResponseClass,
_checkAnswerBtn = '.' + checkAnswerClass,
_nextQuestionBtn = '.' + nextQuestionClass,
_prevQuestionBtn = '.' + backToQuestionClass,
_tryAgainBtn = '.' + tryAgainClass,
// Top Level Quiz Element Class Selectors
_quizStarter = _element + ' .startQuiz',
_quizName = _element + ' .quizName',
_quizArea = _element + ' .quizArea',
_quizResults = _element + ' .quizResults',
_quizResultsCopy = _element + ' .quizResultsCopy',
_quizHeader = _element + ' .quizHeader',
_quizScore = _element + ' .quizScore',
_quizLevel = _element + ' .quizLevel',
// Top Level Quiz Element Objects
$quizStarter = $(_quizStarter),
$quizName = $(_quizName),
$quizArea = $(_quizArea),
$quizResults = $(_quizResults),
$quizResultsCopy = $(_quizResultsCopy),
$quizHeader = $(_quizHeader),
$quizScore = $(_quizScore),
$quizLevel = $(_quizLevel)
;
// Reassign user-submitted deprecated options
var depMsg = '';
if (options && typeof options.disableNext != 'undefined') {
if (typeof options.preventUnanswered == 'undefined') {
options.preventUnanswered = options.disableNext;
}
depMsg += 'The \'disableNext\' option has been deprecated, please use \'preventUnanswered\' in it\'s place.\n\n';
}
if (options && typeof options.disableResponseMessaging != 'undefined') {
if (typeof options.preventUnanswered == 'undefined') {
options.perQuestionResponseMessaging = options.disableResponseMessaging;
}
depMsg += 'The \'disableResponseMessaging\' option has been deprecated, please use' +
' \'perQuestionResponseMessaging\' and \'completionResponseMessaging\' in it\'s place.\n\n';
}
if (options && typeof options.randomSort != 'undefined') {
if (typeof options.randomSortQuestions == 'undefined') {
options.randomSortQuestions = options.randomSort;
}
if (typeof options.randomSortAnswers == 'undefined') {
options.randomSortAnswers = options.randomSort;
}
depMsg += 'The \'randomSort\' option has been deprecated, please use' +
' \'randomSortQuestions\' and \'randomSortAnswers\' in it\'s place.\n\n';
}
if (depMsg !== '') {
if (typeof console != 'undefined') {
console.warn(depMsg);
} else {
alert(depMsg);
}
}
// End of deprecation reassignment
plugin.config = $.extend(defaults, options);
// Set via json option or quizJSON variable (see slickQuiz-config.js)
var quizValues = (plugin.config.json ? plugin.config.json : typeof quizJSON != 'undefined' ? quizJSON : null);
// Get questions, possibly sorted randomly
var questions = plugin.config.randomSortQuestions ?
quizValues.questions.sort(function() { return (Math.round(Math.random())-0.5); }) :
quizValues.questions;
// Count the number of questions
var questionCount = questions.length;
// Select X number of questions to load if options is set
if (plugin.config.numberOfQuestions && questionCount >= plugin.config.numberOfQuestions) {
questions = questions.slice(0, plugin.config.numberOfQuestions);
questionCount = questions.length;
}
// some special private/internal methods
var internal = {method: {
// get a key whose notches are "resolved jQ deferred" objects; one per notch on the key
// think of the key as a house key with notches on it
getKey: function (notches) { // returns [], notches >= 1
var key = [];
for (i=0; i<notches; i++) key[i] = $.Deferred ();
return key;
},
// put the key in the door, if all the notches pass then you can turn the key and "go"
turnKeyAndGo: function (key, go) { // key = [], go = function ()
// when all the notches of the key are accepted (resolved) then the key turns and the engine (callback/go) starts
$.when.apply (null, key). then (function () {
go ();
});
},
// get one jQ
getKeyNotch: function (key, notch) { // notch >= 1, key = []
// key has several notches, numbered as 1, 2, 3, ... (no zero notch)
// we resolve and return the "jQ deferred" object at specified notch
return function () {
key[notch-1].resolve (); // it is ASSUMED that you initiated the key with enough notches
};
}
}};
plugin.method = {
// Sets up the questions and answers based on above array
setupQuiz: function(options) { // use 'options' object to pass args
var key, keyNotch, kN;
key = internal.method.getKey (3); // how many notches == how many jQ animations you will run
keyNotch = internal.method.getKeyNotch; // a function that returns a jQ animation callback function
kN = keyNotch; // you specify the notch, you get a callback function for your animation
$quizName.hide().html(plugin.config.nameTemplateText
.replace('%name', quizValues.info.name) ).fadeIn(1000, kN(key,1));
$quizHeader.hide().prepend($('<div class="quizDescription">' + quizValues.info.main + '</div>')).fadeIn(1000, kN(key,2));
$quizResultsCopy.append(quizValues.info.results);
// add retry button to results view, if enabled
if (plugin.config.tryAgainText && plugin.config.tryAgainText !== '') {
$quizResultsCopy.append('<p><a class="button ' + tryAgainClass + '" href="#">' + plugin.config.tryAgainText + '</a></p>');
}
// Setup questions
var quiz = $('<ol class="' + questionGroupClass + '"></ol>'),
count = 1;
// Loop through questions object
for (i in questions) {
if (questions.hasOwnProperty(i)) {
var question = questions[i];
var questionHTML = $('<li class="' + questionClass +'" id="question' + (count - 1) + '"></li>');
if (plugin.config.displayQuestionCount) {
questionHTML.append('<div class="' + questionCountClass + '">' +
plugin.config.questionCountText
.replace('%current', '<span class="current">' + count + '</span>')
.replace('%total', '<span class="total">' +
questionCount + '</span>') + '</div>');
}
var formatQuestion = '';
if (plugin.config.displayQuestionNumber) {
formatQuestion = plugin.config.questionTemplateText
.replace('%count', count).replace('%text', question.q);
} else {
formatQuestion = question.q;
}
questionHTML.append('<h3>' + formatQuestion + '</h3>');
// Count the number of true values
var truths = 0;
for (i in question.a) {
if (question.a.hasOwnProperty(i)) {
answer = question.a[i];
if (answer.correct) {
truths++;
}
}
}
// Now let's append the answers with checkboxes or radios depending on truth count
var answerHTML = $('<ul class="' + answersClass + '"></ul>');
// Get the answers
var answers = plugin.config.randomSortAnswers ?
question.a.sort(function() { return (Math.round(Math.random())-0.5); }) :
question.a;
// prepare a name for the answer inputs based on the question
var selectAny = question.select_any ? question.select_any : false,
forceCheckbox = question.force_checkbox ? question.force_checkbox : false,
checkbox = (truths > 1 && !selectAny) || forceCheckbox,
inputName = $element.attr('id') + '_question' + (count - 1),
inputType = checkbox ? 'checkbox' : 'radio';
if( count == quizValues.questions.length ) {
nextQuestionClass = nextQuestionClass + ' ' + lastQuestionClass;
}
for (i in answers) {
if (answers.hasOwnProperty(i)) {
answer = answers[i],
optionId = inputName + '_' + i.toString();
// If question has >1 true answers and is not a select any, use checkboxes; otherwise, radios
var input = '<input id="' + optionId + '" name="' + inputName +
'" type="' + inputType + '" /> ';
var optionLabel = '<label for="' + optionId + '">' + answer.option + '</label>';
var answerContent = $('<li></li>')
.append(input)
.append(optionLabel);
answerHTML.append(answerContent);
}
}
// Append answers to question
questionHTML.append(answerHTML);
// If response messaging is NOT disabled, add it
if (plugin.config.perQuestionResponseMessaging || plugin.config.completionResponseMessaging) {
// Now let's append the correct / incorrect response messages
var responseHTML = $('<ul class="' + responsesClass + '"></ul>');
responseHTML.append('<li class="' + correctResponseClass + '">' + question.correct + '</li>');
responseHTML.append('<li class="' + incorrectResponseClass + '">' + question.incorrect + '</li>');
// Append responses to question
questionHTML.append(responseHTML);
}
// Appends check answer / back / next question buttons
if (plugin.config.backButtonText && plugin.config.backButtonText !== '') {
questionHTML.append('' + plugin.config.backButtonText + '');
}
var nextText = plugin.config.nextQuestionText;
if (plugin.config.completeQuizText && count == questionCount) {
nextText = plugin.config.completeQuizText;
}
// If we're not showing responses per question, show next question button and make it check the answer too
if (!plugin.config.perQuestionResponseMessaging) {
questionHTML.append('' + nextText + '');
} else {
questionHTML.append('' + nextText + '');
questionHTML.append('' + plugin.config.checkAnswerText + '');
}
// Append question & answers to quiz
quiz.append(questionHTML);
count++;
}
}
// Add the quiz content to the page
$quizArea.append(quiz);
// Toggle the start button OR start the quiz if start button is disabled
if (plugin.config.skipStartButton || $quizStarter.length == 0) {
$quizStarter.hide();
plugin.method.startQuiz.apply (this, [{callback: plugin.config.animationCallbacks.startQuiz}]); // TODO: determine why 'this' is being passed as arg to startQuiz method
kN(key,3).apply (null, []);
} else {
$quizStarter.fadeIn(500, kN(key,3)); // 3d notch on key must be on both sides of if/else, otherwise key won't turn
}
internal.method.turnKeyAndGo (key, options && options.callback ? options.callback : function () {});
},
// Starts the quiz (hides start button and displays first question)
startQuiz: function(options) {
var key, keyNotch, kN;
key = internal.method.getKey (1); // how many notches == how many jQ animations you will run
keyNotch = internal.method.getKeyNotch; // a function that returns a jQ animation callback function
kN = keyNotch; // you specify the notch, you get a callback function for your animation
function start(options) {
var firstQuestion = $(_element + ' ' + _questions + ' li').first();
if (firstQuestion.length) {
firstQuestion.fadeIn(500, function () {
if (options && options.callback) options.callback ();
});
}
}
if (plugin.config.skipStartButton || $quizStarter.length == 0) {
start({callback: kN(key,1)});
} else {
$quizStarter.fadeOut(300, function(){
start({callback: kN(key,1)}); // 1st notch on key must be on both sides of if/else, otherwise key won't turn
});
}
internal.method.turnKeyAndGo (key, options && options.callback ? options.callback : function () {});
if (plugin.config.events &&
plugin.config.events.onStartQuiz) {
plugin.config.events.onStartQuiz.apply (null, []);
}
},
*** had to delete code because stackoverflow limits what I can put into a question,
here is the end of the slickQuiz file found at the link above:
plugin.init();
};
$.fn.slickQuiz = function(options) {
return this.each(function() {
if (undefined === $(this).data('slickQuiz')) {
var plugin = new $.slickQuiz(this, options);
$(this).data('slickQuiz', plugin);
}
});
};
})(jQuery);
I'm having a problem with the asynchronous methods that prints and returns all vidDuration values then viewCount values were placed for each videoId, but vidDuration repeated only the last value that was received and assigned it to all of the videoIds which is clearly wrong.
I tried setting up a temporary array called tempArray within the loop that store each of the vidDuration values which it did, then to print them to each vidDuration, but then again, it looped through the output however many values are in the array and duplicated the videos, which I don't want. I commented out lines involving tempArray and reverted back to the working problem.
From what I understand, the async methods printed all of the duration values without going to the output as if it got stuck there until it was done and resolved and then it looped the viewCount and output perfectly fine. I'm wondering how do I fix this in a programmatically logical way?
Script:
var channelName = 'ExampleChannel';
var vidWidth = 500;
var vidHeight = 400;
var vidResults = 15; /* # of videos to show at once - max 50 */
var vidDuration = "";
var viewCount = 0;
var videoId = "";
$(document).ready(function() {
$.get( // get channel name and load data
"https://www.googleapis.com/youtube/v3/channels",
{
part: 'contentDetails',
forUsername: channelName,
key: 'XXXXXXXX'
},
function(data)
{
$.each(data.items,
function(i, item) {
console.log(item); // log all items to console
var playlistId = item.contentDetails.relatedPlaylists.uploads;
getPlaylists(playlistId);
})
}
);
// function that gets the playlists
function getPlaylists(playlistId)
{
$.get(
"https://www.googleapis.com/youtube/v3/playlistItems",
{
part: 'snippet',
maxResults: vidResults,
playlistId: playlistId,
key: 'XXXXXXXX'
},
// print the results
function(data)
{
var output;
/*var tempArray = new Array();*/ // temporary array for storing video duration values
$.each(data.items,
function(i, item) {
console.log(item);
var vidTitle = item.snippet.title; // video title
var vidDesc = item.snippet.description; // video description
var videoId = item.snippet.resourceId.videoId; // video id
// check if description is empty
if(vidDesc == null || vidDesc == "")
{
vidDesc = "No description was written."; // FIX: test msg to see where it still shows up
$('#desc').remove(); // remove video description
}
else vidDesc = item.snippet.description;
getVideoDuration(videoId).done(function(r){
vidDuration = r;
console.log(r);
/*tempArray[i] = r;*/ // store value into each array index
/*console.log("Array:", tempArray[i], tempArray);*/ // log to console
/*i++;*/ // increment
getViewCount(videoId).done(function(r){
viewCount = r;
console.log(r);
//vidDuration = getVideoDuration(videoId);
//viewCount = getViewCount(videoId);
// temp array index to loop thru array
/*$.each(tempArray, function(i){
vidDuration = tempArray[i]; // assign index value to vidDuration
console.log("In Each vidDuration: ", vidDuration);
i++;
});*/
console.log("id: " + videoId + " duration: " + vidDuration + " viewCount: " + viewCount); // return value in console
output = '<li><iframe height="' + vidHeight + '" width="' + vidWidth + '" src=\"//www.youtube.com/embed/' + videoId + '\"></iframe></li><div id="title">' + vidTitle + '</div><div id="desc">' + vidDesc + '</div><div id="duration">Length: ' + vidDuration + '</div><div id="stats">View Count: ' + viewCount + '</div>';
// Append results to list tag
$('#results').append(output);
}); // end of getVideoDuration(videoId).done
}); // end of getViewCount(videoId).done
});
/*console.log("TEMPARRAY[]",tempArray);*/ // print entire array
}
);
}
// return video duration
function getVideoDuration(videoId)
{
var defer1 = $.Deferred();
var r = '';
$.get(
"https://www.googleapis.com/youtube/v3/videos",
{
part: 'contentDetails',
id: videoId,
key: 'XXXXXXXX',
},
function(data)
{
$.each(data.items,
function(i, item) {
r = item.contentDetails.duration;
defer1.resolve(r);
console.log("in vidDuration func", r);
});
}
);
return defer1.promise();
}
// return video view count
function getViewCount(videoId)
{
var defer2 = $.Deferred();
var r = '';
$.get(
"https://www.googleapis.com/youtube/v3/videos",
{
part: 'contentDetails, statistics',
id: videoId,
key: 'XXXXXXXX',
},
function(data)
{
$.each(data.items,
function(i, item) {
r = item.statistics.viewCount;
defer2.resolve(r);
console.log("in viewCount func", r);
});
}
);
return defer2.promise();
}
});
Screenshot results (normal refresh):
Screenshot results (using debugger):
Here's a screenshot of the results when stepping through with the debugger console. (Why are the results different from when the page normally loads? Is this typical action of the async methods? How do I fix this?)
In fact the second promise is misplaced, the second promise is resolved AFTER that all the promises from the first promise have been resolved, so the last value is save. logical
Now if you resolve the first promise WHEN second is resolved one by one you are able to correct the problem.
var view = 0;
r = item.contentDetails.duration; // video duration
getViewCount(videoId).done(function(t){
view = t;
dfrd1.resolve(r, view);
});
Check the screenshot:
I change a bit the code to solve the problem.
var channelName = 'example';
var vidWidth = 500;
var vidHeight = 400;
var vidResults = 15; /* # of videos to show at once - max 50 */
var vidDuration = "";
var viewCount = 0;
var videoId = "";
$(document).ready(function() {
$.get( // get channel name and load data
"https://www.googleapis.com/youtube/v3/channels",
{
part: 'contentDetails',
forUsername: channelName,
key: 'xxx'
},
function(data)
{
$.each(data.items,
function(i, item) {
//console.log(item); // log all items to console
var playlistId = item.contentDetails.relatedPlaylists.uploads;
//var viewCount = console.log(item.statistics.viewCount);
getPlaylists(playlistId);
});
}
);
// function that gets the playlists
function getPlaylists(playlistId)
{
$.get(
"https://www.googleapis.com/youtube/v3/playlistItems",
{
part: 'snippet',
maxResults: vidResults,
playlistId: playlistId,
key: 'xxx'
},
// print the results
function(data)
{
var output;
$.each(data.items,
function(i, item) {
console.log(item);
var vidTitle = item.snippet.title; // video title
var vidDesc = item.snippet.description; // video description
var videoId = item.snippet.resourceId.videoId; // video id
// check if description is empty
if(vidDesc == null || vidDesc == "")
{
vidDesc = "No description was written."; // FIX: test msg to see where it still shows up
$('#desc').remove(); // remove video description
}
else vidDesc = item.snippet.description;
getVideoDuration(videoId).done(function(d, v){
vidDuration = d;
//console.log(r);
viewCount = v;
document.write("id: " + videoId + " duration: " + vidDuration + " viewCount: " + viewCount); // return value in console
document.write("<br>");
output = '<li><iframe height="' + vidHeight + '" width="' + vidWidth + '" src=\"//www.youtube.com/embed/' + videoId + '\"></iframe></li><div id="title">' + vidTitle + '</div><div id="desc">' + vidDesc + '</div><div id="duration">Length: ' + vidDuration + '</div><div id="stats">View Count: ' + viewCount + '</div>';
// Append results to list tag
$('#results').append(output);
});
});
}
);
}
// return video duration
function getVideoDuration(videoId)
{
var dfrd1 = $.Deferred();
var r = '';
$.get(
"https://www.googleapis.com/youtube/v3/videos",
{
part: 'contentDetails',
id: videoId,
key: 'xxx',
},
function(data)
{
$.each(data.items,
function(i, item) {
//videoId = item.snippet.resourceId.videoId;
var view = 0;
r = item.contentDetails.duration; // video duration
getViewCount(videoId).done(function(t){
view = t;
dfrd1.resolve(r, view);
});
//alert(videoId);
});
}
);
return dfrd1.promise();
}
// return video view count
function getViewCount(videoId)
{
var dfrd2 = $.Deferred();
var r = '';
$.get(
"https://www.googleapis.com/youtube/v3/videos",
{
part: 'contentDetails, statistics',
id: videoId,
key: 'xxx',
},
function(data)
{
$.each(data.items,
function(i, item) {
//videoId = item.snippet.resourceId.videoId;
r = item.statistics.viewCount; // view count
//alert(videoId);
dfrd2.resolve(r);
// console.log("in", r);
});
}
);
return dfrd2.promise();
}
});
This is very early in my Node and JavaScript learning. Ideally, what I am attempting to do is create a small module querying a specific type of rest endpoint and returning a specific feature based on an attribute query. The module is correctly logging out the result, but I am struggling to get the .findById function to return this result. Although aware it has something to do with how the callbacks are working, I am not experienced enough to be able to sort it out yet. Any help, advice and direction towards explaning the solution is greatly appreciated.
// import modules
var restler = require('restler');
// utility for padding zeros so the queries work
function padZeros(number, size) {
var string = number + "";
while (string.length < size) string = "0" + string;
return string;
}
// create feature service object
var FeatureService = function (url, fields) {
// save the parameters
this.restEndpoint = url;
this.fields = fields;
var self = this;
this.findById = function (idField, value, padZeroLength) {
var options = {
query: {
where: idField + '=\'' + padZeros(value, padZeroLength) + '\'',
outFields: this.fields,
f: "pjson"
},
parsers: 'parsers.json'
};
var url = this.restEndpoint + '/query';
restler.get(url, options).on('complete', function(result){
if (result instanceof Error){
console.log('Error:', result.message);
} else {
console.log(result); // this log result works
self.feature = JSON.parse(result);
}
});
return self.feature;
};
};
var restEndpoint = 'http://services.arcgis.com/SgB3dZDkkUxpEHxu/ArcGIS/rest/services/aw_accesses_20140712b/FeatureServer/1';
var fields = 'nameRiver,nameSection,nameSectionCommon,difficulty,diffMax';
var putins = new FeatureService(restEndpoint, fields);
var feature = putins.findById('awid_string', 1143, 8);
console.log(feature); // this log result does not
//console.log('River: ' + feature.attributes.nameRiver);
//console.log('Section: ' + feature.attributes.nameSection + ' (' + feature.attributes.nameSectionCommon + ')');
//console.log('Difficulty: ' + feature.attributes.difficulty);
So, I sorted out how to insert a callback from a previous thread. It appears it is just passed in as a variable and called with expected parameters. However, I now wonder if there is a better way to accept parameters, possibly in the form of options. Any advice in this regard?
// import modules
var restler = require('restler');
// utility for padding zeros so the queries work
function padZeros(number, size) {
var string = number + "";
while (string.length < size) string = "0" + string;
return string;
}
// create feature service object
var FeatureService = function (url, fields) {
// save the parameters
this.restEndpoint = url;
this.fields = fields;
var self = this;
// find and return single feature by a unique value
this.findById = function (idField, value, padZeroLength, callback) {
// query options for
var options = {
query: {
where: idField + '=\'' + padZeros(value, padZeroLength) + '\'',
outFields: this.fields,
f: "pjson"
},
parsers: 'parsers.json'
};
var url = this.restEndpoint + '/query';
restler.get(url, options)
.on('success', function(data, response){
var dataObj = JSON.parse(data).features[0];
console.log(dataObj);
callback(dataObj);
})
.on('fail', function(data, response){
console.log('Error:', data.message);
});
return self.feature;
};
};
var restEndpoint = 'http://services.arcgis.com/SgB3dZDkkUxpEHxu/ArcGIS/rest/services/aw_accesses_20140712b/FeatureServer/1';
var fields = 'nameRiver,nameSection,nameSectionCommon,difficulty,diffMax';
var putins = new FeatureService(restEndpoint, fields);
putins.findById('awid_string', 1143, 8, function(dataObject){
console.log('River: ' + dataObject.attributes.nameRiver);
console.log('Section: ' + dataObject.attributes.nameSection + ' (' + dataObject.attributes.nameSectionCommon + ')');
console.log('Difficulty: ' + dataObject.attributes.difficulty);
});