Angularjs $compile(html)($scope) - Error: Converting circular structure to JSON - javascript

I have an app where I need to create html and add it to an object array. This html should then be outputted into page. Simple enough. However, I also need to make the html responsive to user actions (click) in which case angular requires me to use $compile to create an angularized template.
See the Plunkr. In this example, what should happen is that when you click on one of the buttons, a popup is generated with the html code embedded in the object, which you can see in the JSON output.
As soon as I do this, I get the error Converting circular structure to JSON. If I don't, the ng-click="Go()" is not called.
SCRIPT
var template = "<ul class='unstyled'>" +
"<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
"<li ng-click='go()'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
"<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
"</ul>";
// template = $compile(template)($scope);
$scope.data = [
{"id": 1, "html": template},
{"id": 2, "html": template}
];
$scope.go = function () {
alert('It works');
};
$scope.openPopin = function (html) {
var e = window.event;
var popin = document.getElementById('popin');
var innerdiv = document.getElementById('innerdiv').innerHTML=html;
popin.style.top= e.pageY - 20+"px";
popin.style.left = e.pageX - 20+"px";
popin.style.marginLeft = -500+"px";
popin.style.marginTop = -100+"px";
};
$scope.closePopin = function () {
var popin = document.getElementById('popin');
popin.style.top = -500+"px";
popin.style.left = -500+"px";
};
HTML
<div class="popin grey-border" id="popin">
<button class="close" ng-click="closePopin()">×</button>
<div id="innerdiv"></div>
</div>
<pre>{{ data |json }} </pre>
<br/>
<table style="float: right;">
<tr ng-repeat="d in data" id="{{$index}}">
<td>{{ d.id }} -
<button class="btn btn-mini btn-info" ng-click="openPopin(d.html)"><i class="icon-info-sign"></i></button>
</td>
</tr>
</table>

I got it to work (for me) by moving the compile step to the openPopin function, and replacing the style-property changes with a more angular alternative. And I'm also ignoring the window.event which is not cross-browser compatible (and not part of the issue).
app.controller('MainCtrl', function($scope, $compile) {
var template = "<ul class='unstyled'>" +
"<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
"<li ng-click='go()'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
"<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
"</ul>";
$scope.data = [
{"id": 1, "html": template},
{"id": 2, "html": template}
];
$scope.go = function () {
console.log("go");
};
$scope.openPopin = function (html) {
var popin = document.getElementById('popin');
var innerdiv = document.getElementById('innerdiv');
innerdiv.innerHTML=html;
$compile(innerdiv)($scope);
angular.element(popin).css({top:'20px', left:'20px'});
};
$scope.closePopin = function () {
var popin = document.getElementById('popin');
angular.element(popin).css({top:'-500px', left:'-500px'})
};
});
So, that's one way to get it working. But the question is, what are you really trying to do, and can't we do it in a more angular way? (Using directives, templates and other tools angular provides.)

Thanks towr for your help - see the last comment above
HTML
<script type="text/ng-template" id="cmpbpopin.html">
<button class="btn btn-mini btn-info"><i class="icon-info-sign"></i></button>
<div class="popin grey-border">
<button class="close-button">×</button>
<div></div>
</div>
</script>
<table style="float: right;">
<tr ng-repeat="d in data" id="{{$index}}">
<td>{{ d.id }}</td>
<td>
<div cm-pb-popup="d.html"></div>
</td>
</tr>
</table>
</body>
SCRIPT
var app = angular.module('app', []);
app.controller('Ctrl', function ($scope, $compile, $http) {
var template = "<table class='pblist table table-condensed table-hover'>" +
"<tr ng-click='go()'><td>1...</td><td>1...</td></tr>" +
"<tr ng-click='go()'><td>1...</td><td>1...</td></tr>" +
"<tr ng-click='go()'><td>1...</td><td>1...</td></tr>" +
"</table>";
$scope.data = [
{"id": 1, "html": template},
{"id": 2, "html": template}
];
});
app.directive("cmPbPopup", function ($compile, $timeout) {
return{
templateUrl: "cmpbpopin.html",
scope: {
cmPbPopup: "="
},
link: function (scope, elem, attrs) {
elem.bind("click", function (e) {
var popupDiv = elem.find('div');
var innerDiv = popupDiv.find('div');
var closeButton = popupDiv.find('.close-button')
if (e.srcElement.nodeName != 'DIV') {
if (e.srcElement.className == 'close-button') {
closePopup();
} else if(e.srcElement.nodeName == 'TR' || e.srcElement.nodeName == 'TD'){
// set values in scope
closePopup();
}
else {
innerDiv.html(scope.cmPbPopup);
$compile(innerDiv)(scope);
popupDiv.css({
'top': e.pageY - e.offsetY + 20,
'left': e.pageX - e.offsetX -10,
'height': 100,
'width': 500,
'marginLeft': -500});
$timeout(function (){
closeButton.css('display', 'block');
},500);
}
}
function closePopup(){
popupDiv.css({
'height': 0,
'width': 0,
'marginLeft': 0});
$timeout(function (){
popupDiv.css({
'top': -500,
'left': -500
});
},500);
}
});
}
}
})
CSS
div.popin {
position: absolute;
width: 0;
height: 0;
top: -500px;
left: -500px;
background-color: #ffffff;
transition: width 0.5s, height 0.5s, margin-left 0.5s;
-webkit-transition: width 0.5s, height 0.5s, margin-left 0.5s; /* Safari */
overflow: hidden;
}
div.popin div {
position: absolute;
top: 0 !important;
left: 500px !important;
width: 470px !important;
transition: width 0.2s, height 0.2s, margin-left 0.2s;
-webkit-transition: width 0.2s, height 0.2s, margin-left 0.2s;
}
.close-button{
width: 20px;
height: 20px;
float: right;
font-size: 20px;
font-weight: bold;
line-height: 20px;
color: #000000;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.2;
filter: alpha(opacity=20);
}
.pblist{
margin-left: 10px !important;
margin-top: 10px;
width: 470px;
float: left;
}
.grey-border {
border: 1px #d3d3d3 solid;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
padding: 3px;
}

Related

jQuery response() function doesn't return any results because of the variable scope

The problem I encountered is I can't get any results from the jQuery UI Autocomplete form because of the variable scope. Let me show you.
// TAKE A CLOSE LOOK AT THIS METHOD
select: function(e, ui) {
$('#instant-search').text(ui.item.label);
$("#search").autocomplete("option", "source",
function(request, response) {
getAutocompleteResults(function(d) {
// DOESN'T WORK response(d);
});
// WORKS BUT IT SHOULD BE A DYNAMIC ARRAY FROM THE "D" OBJECT
// response(["anarchism", "anarchist black cross", "black rose (symbolism)", "communist symbolism", "political symbolism"]);
});
$("#search").autocomplete("search", ui.item.label);
In order to return results I have to use a function response([...]); outside the getAutocompleteResults(function(d) { ... }); function.
However, the source should be dynamic and not like the static array. In other words:
The function response(d); should return an object, which contains a few properties (title, value, extract). I have to access them by using response(d);, however, this function doesn't work inside getAutocompleteResults(function(d) { ... }); function. How can I achieve this?
There is a small snippet of code, however, the main problem is the select method. You can find this in the middle of the whole code block. I commented it out.
$(function() {
$("html").removeClass("no-js");
var autocompleteResults = [{
title: [],
extract: [],
pageId: []
}];
var capitalizeFirstLetter = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var changeText2 = function(e) {
var request = $("input").val() + String.fromCharCode(e.which);
$("#instant-search").text(request);
var getAutocompleteResults = function(callback) {
$.ajax({
url: "https://en.wikipedia.org/w/api.php?format=json&action=query&generator=search&gsrlimit=6&prop=extracts&origin=*&pilimit=max&exintro&explaintext&exsentences=1&gsrsearch=" +
$("#instant-search").text(),
beforeSend: function() {
$(".loading").show();
},
success: function(d) {
$(".loading").hide();
autocompleteResults[0].title = [];
autocompleteResults[0].extract = [];
autocompleteResults[0].pageId = [];
if (d.hasOwnProperty("query")) {
if (d.query.hasOwnProperty("pages")) {
$.each(d.query.pages, function(i) {
autocompleteResults[0].title.push(d.query.pages[i].title);
autocompleteResults[0].extract.push(d.query.pages[i].extract);
autocompleteResults[0].pageId.push(d.query.pages[i].pageid);
});
}
}
if (!autocompleteResults[0].length) {
$(".ui-autocomplete").hide();
}
autocompleteResults[0].title.sort(function(a, b) {
var nameA = a.toUpperCase();
var nameB = b.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
autocompleteResults[0].title = autocompleteResults[0].title.map(
function(i) {
return i.toLowerCase();
}
);
callback(autocompleteResults[0]);
},
datatype: "json",
cache: false
});
};
$("#search").autocomplete({
source: function(request, response) {
getAutocompleteResults(function(d) {
var results = [],
filteredAutocompleteResults = [];
filteredAutocompleteResults = d.title.filter(function(i) {
return (
i !=
$("#instant-search")
.text()
.toLowerCase()
);
});
for (var i = 0; i < d.title.length; i++) {
results[i] = {
label: filteredAutocompleteResults[i],
extract: d.extract[i],
pageId: d.pageId[i]
};
}
if (results.length == 5) {
response(results);
} else {
response(results.slice(0, 5));
}
});
},
response: function() {
if ($("#instant-search").text()) {
$("table").css("display", "table");
$(".wikisearch-container").css("margin-top", 100);
}
},
close: function() {
if (!$(".ui-autocomplete").is(":visible")) {
$(".ui-autocomplete").show();
}
},
appendTo: ".input",
focus: function(e) {
e.preventDefault();
},
delay: 0,
// TAKE A CLOSE LOOK AT THIS METHOD
select: function(e, ui) {
$('#instant-search').text(ui.item.label);
$("#search").autocomplete("option", "source",
function(request, response) {
getAutocompleteResults(function(d) {
// DOESN'T WORK response(d);
});
// WORKS BUT IT SHOULD BE A DYNAMIC ARRAY FROM THE "D" OBJECT
// response(["anarchism", "anarchist black cross", "black rose (symbolism)", "communist symbolism", "political symbolism"]);
});
$("#search").autocomplete("search", ui.item.label);
// EVERYTHING SHOULD BE FINE BELOW THIS LINE
if ($(".search-results").css("opacity") != 1) {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
"https://en.wikipedia.org/?curid=" + ui.item.pageId
);
$(".search-results").css("opacity", 1);
} else if (
$(".search-results h4")
.text()
.toLowerCase() != ui.item.label
) {
$(".search-results").css("opacity", 0);
setTimeout(function() {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
"https://en.wikipedia.org/?curid=" + ui.item.pageId
);
$(".search-results").css("opacity", 1);
}, 500);
}
},
create: function() {
$(this).data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.append(
'<div class="ui-menu-item-wrapper"><div class="autocomplete-first-field"><i class="fa fa-search" aria-hidden="true"></i></div><div class="autocomplete-second-field three-dots">' +
item.label +
"</div></div>"
)
.appendTo(ul);
};
}
});
};
var changeText1 = function(e) {
if (
/[-a-z0-90áãâäàéêëèíîïìóõôöòúûüùçñ!##$%^&*()_+|~=`{}\[\]:";'<>?,.\s\/]+/gi.test(
String.fromCharCode(e.which)
)
) {
$("input").on("keypress", changeText2);
}
// DONT TOUCH THIS AREA, IT HAS NOTHING TO DO WITH THE PROBLEM
var getInputSelection = function(input) {
var start = 0,
end = 0;
input.focus();
if (
typeof input.selectionStart == "number" &&
typeof input.selectionEnd == "number"
) {
start = input.selectionStart;
end = input.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
if (range) {
var inputRange = input.createTextRange();
var workingRange = inputRange.duplicate();
var bookmark = range.getBookmark();
inputRange.moveToBookmark(bookmark);
workingRange.setEndPoint("EndToEnd", inputRange);
end = workingRange.text.length;
workingRange.setEndPoint("EndToStart", inputRange);
start = workingRange.text.length;
}
}
return {
start: start,
end: end,
length: end - start
};
};
switch (e.key) {
case "Backspace":
case "Delete":
e = e || window.event;
var keyCode = e.keyCode;
var deleteKey = keyCode == 46;
var sel, deletedText, val;
val = this.value;
sel = getInputSelection(this);
if (sel.length) {
// 0 kai paprastai trini po viena o 1 ar daugiau kai select su pele trini
$("#instant-search").text(
val.substr(0, sel.start) + val.substr(sel.end)
);
} else {
$("#instant-search").text(
val.substr(0, deleteKey ? sel.start : sel.start - 1) +
val.substr(deleteKey ? sel.end + 1 : sel.end)
);
}
break;
case "Enter":
if ($("#instant-search").text()) {
console.log("Redirecting...");
}
break;
}
if (!$("#instant-search").text()) {
$("table, .ui-autocomplete").hide();
$(".wikisearch-container").css("margin-top", "");
}
if (
$(".ui-menu-item-wrapper").hasClass("ui-state-active") &&
(e.key == "ArrowRight" || e.key == "ArrowLeft")
) {
$(".ui-autocomplete").autocomplete(""); // Error metas console ir taip neturėtų būti bet nežinau kaip padaryti kad pasirinkus elementą su <-- ar --> nepadarytų tik vieno rezultato todėl paliekam laikinai ;)
}
};
$("input").on("keydown", changeText1);
$("input").on("input", function(e) {
$("#instant-search").text($("#search").val());
});
});
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-image: url("http://www.part.lt/img/96816a00ec1fb87adc4ca8a04365b2b5719.jpg");
background-size: cover;
background-position: 100%;
}
.v-container {
display: table;
height: 100%;
width: 100%;
}
.v-content {
display: table-cell;
vertical-align: middle;
}
.text-center {
text-align: center;
}
.input {
overflow: hidden;
white-space: nowrap;
}
.input input#search {
width: calc(100% - 30px);
height: 50px;
border: none;
font-size: 10pt;
float: left;
color: #4f5b66;
padding: 0 15px;
outline: none;
}
.ui-autocomplete {
list-style: none;
background-color: #fff;
-webkit-user-select: none;
user-select: none;
padding: 0;
margin: 0;
width: 100% !important;
top: auto !important;
display: table;
table-layout: fixed;
}
.ui-helper-hidden-accessible {
display: none;
}
.autocomplete-first-field {
width: 15%;
display: inline-block;
}
.autocomplete-second-field {
width: 85%;
display: inline-block;
text-align: left;
vertical-align: middle;
}
.three-dots {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
display: none;
table-layout: fixed;
}
table tr {
background-color: #fff;
-webkit-user-select: none;
user-select: none;
}
tr:first-child {
background-color: #ffc800;
color: #fff;
}
table td,
.ui-menu-item-wrapper {
padding: 10px 0;
}
td:nth-child(2) {
width: 85%;
text-align: left;
}
.ui-menu-item,
table {
cursor: pointer;
}
:focus {
outline: 0;
}
a {
text-decoration: none;
color: #fff;
position: relative;
}
a:before {
content: "";
position: absolute;
width: 100%;
height: 0.0625rem;
bottom: 0;
left: 0;
background-color: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all 0.3s ease-in-out 0s;
transition: all 0.3s ease-in-out 0s;
}
a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
.search-results {
background: #fff;
margin-top: 50px;
border-left: 5px solid #0ebeff;
opacity: 0;
-webkit-transition: opacity 1s;
transition: opacity 1s;
}
.search-results h4,
.search-results p {
margin: 0;
padding: 10px;
text-align: left;
}
.search-results a {
color: #0ebeff;
display: inline-block;
margin: 1rem 0;
}
.search-results a:before {
background-color: #0ebeff;
}
.wikisearch-container {
width: 65%;
margin: 0 auto;
}
/* Loading animation */
#keyframes lds-eclipse {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#-webkit-keyframes lds-eclipse {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.loading {
position: relative;
top: 9.5px;
right: 15px;
pointer-events: none;
display: none;
}
.lds-eclipse {
-webkit-animation: lds-eclipse 1s linear infinite;
animation: lds-eclipse 1s linear infinite;
width: 2rem;
height: 2rem;
border-radius: 50%;
margin-left: auto;
box-shadow: 0.08rem 0 0 #0ebeff;
}
#media (max-width: 71.875em) {
.wikisearch-container {
width: 75%;
}
}
#media (max-width: 50em) {
.wikisearch-container {
width: 85%;
}
}
#media (max-width: 17.96875em) {
.wikisearch-container {
width: 100%;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<html class="no-js">
<div class="v-container">
<div class="v-content text-center">
<div class="wikisearch-container">
<div class="input">
<input type="text" id="search" placeholder="Search...">
<div class="loading">
<div class="lds-eclipse"></div>
</div>
<button class="icon"><i class="fa fa-search"></i></button>
<table>
<tr>
<td class="fa fa-search">
<td id="instant-search" class="three-dots"></td>
</tr>
</table>
</div>
<div class="search-results">
<h4></h4>
<p></p>
<a target="_blank">Click here for more</a>
</div>
</div>
</div>
</div>
EDIT 1
After some changes, the results are shown, however, before the ajax call. How can I use response() only after the ajax was successfully completed (tried using success callback, didn't work :()?
Full project code: https://codepen.io/Kestis500/pen/zRONyw?editors=0010.
Here you can see step by step how it looks like:
How it looks like when you just reloaded the page:
Let's try entering "a":
We've got some results. Ok, let's try to click on the "anarchist symbolism" element:
Results should look like "anarchist symbolism" search. However, it returns the result of the "a" search. What if we pressed "fraktur" element?
Now it shows our previous search "anarchist symbolism" results. However, it should return elements of the "fraktur" search.
EDIT 2
I've fixed many things and removed some really non sense things from my code. However, the situation with the ajax call is still the same.
https://codepen.io/Kestis500/pen/pazppP?editors=0110
Any ideas?
EDIT 3
Fixed ajax lag (now the ajax request will be sent only after the previous ajax call).
https://codepen.io/Kestis500/pen/JpPLON?editors=0110
I had to first fix a few things in your Ajax call. We then collect the results and build an array that should be returned to response(). This will populate the AutoComplete.
First we will examine the HTML. There was some closing tags missing.
HTML
<div class="v-container">
<div class="v-content text-center">
<div class="wikisearch-container">
<div class="input ui-front">
<input type="text" id="search" placeholder="Search...">
<div class="loading">
<div class="lds-eclipse"></div>
</div>
<button class="icon">
<i class="fa fa-search"></i>
</button>
<table>
<tr>
<td class="fa fa-search"></td>
<td id="instant-search" class="three-dots"></td>
</tr>
</table>
</div>
<div class="search-results">
<h4></h4>
<p></p>
<a target="_blank">Click here for more</a>
</div>
</div>
</div>
</div>
You can see the table and it's cells all have the proper closing tags now.
I didn't make any changes to your CSS or Style.
JavaScript
$(function() {
var capitalizeFirstLetter = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
$("#search").autocomplete({
source: function(request, response) {
var results = [];
$.ajax({
url: "https://en.wikipedia.org/w/api.php",
data: {
format: "json",
action: "query",
generator: "search",
gsrlimit: 6,
prop: "extracts|pageimages",
origin: "*",
pilimit: "max",
exintro: false,
explaintext: false,
exsentences: 1,
gsrsearch: request.term
},
beforeSend: function() {
$(".loading").show();
},
success: function(d) {
$(".loading").hide();
if (d.query.pages) {
$.each(d.query.pages, function(k, v) {
console.log(k, v.title, v.extract, v.pageid);
results.push({
label: v.title,
value: "https://en.wikipedia.org/?curid=" + v.pageid,
title: v.title,
extract: v.extract,
pageId: v.pageid
});
});
response(results);
}
},
datatype: "json",
cache: false
});
response(results);
},
close: function() {
if (!$(".ui-autocomplete").is(":visible")) {
$(".ui-autocomplete").show();
}
},
focus: function(e) {
e.preventDefault();
return false;
},
delay: 0,
select: function(e, ui) {
if ($(".search-results").css("opacity") != 1) {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
ui.item.value
);
$(".search-results").css("opacity", 1);
} else if (
$(".search-results h4")
.text()
.toLowerCase() != ui.item.label
) {
$(".search-results").css("opacity", 0);
setTimeout(function() {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
ui.item.value
);
$(".search-results").css("opacity", 1);
}, 500);
}
return false;
}
}).autocomplete("instance")._renderItem = function(ul, item) {
var $item = $("<li>");
var $wrap = $("<div>").appendTo($item);
var $field1 = $("<div>", {
class: "autocomplete-first-field"
}).appendTo($wrap);
$("<i>", {
class: "fa fa-search",
"aria-hidden": true
}).appendTo($field1);
$("<div>", {
class: "autocomplete-second-field three-dots"
}).html(item.label).appendTo($wrap);
return $item.appendTo(ul);
};
});
There was a lot of things to fix and improve.
Let's start with the Ajax. You're making a call to a MediaWiki API and expecting some results. When the call would come back, it would generate warnings about pilimit. Digging into the API docs, this is a parameter specific to the pageimages properties call. To fix this, the prop value had to be extracts|pageimages. Now I get a clean set of results.
You can see I broke out the data so that I could more easily make changes and see what parameters I was sending to the API. Nothing wrong with your method, I just find this a lot easier to work with.
This is all happening inside .autocomplete() when we are populating the source. When we use function as a source, it has to follow a few guidelines:
we pass a request and response in
results must be in an array
the array can contain objects, as long as they contain at least { label, value }
our results array must be passed to response function.
A brief example:
$(selector).autocomplete({
source: function(req, resp){
var q = req.term;
// The Request is an object that contains 1 index: term
// request.term will contain the content of our search
var results = [];
// An array to store the results
$.getJSON("myapi.php", {query: q}, function(data){
$.each(data, function(key, val){
// iterate over the result data and populate our result array
results.push({
label: data.name,
value: data.url
});
resp(results);
});
});
}
});
You can sort or filter the results all you like; as long as you pass them to response in the end.
With your focus and select callbacks, you want to return false. This is discussed more here: http://jqueryui.com/autocomplete/#custom-data
We also see a good example of rendering the menu item. I switched over to making jQuery objects versus raw HTML. You do what works best for you.
Working Example: https://jsfiddle.net/Twisty/vr6gv2aw/4/
Hope this helps.

Button working on chrome but not on firefox

I made a button in HTML that work fine on Google Chrome but does not react at all on Firefox.
My code:
function fbs_click() {
var u = location.href;
var t = document.title;
window.open('http://www.facebook.com/sharer.php?u=' + encodeURIComponent(u) + '&t=' + encodeURIComponent(t) + '&s=' + encodeURIComponent(CQuote + CAuthor), 'sharer', 'toolbar=0,status=0,width=626,height=436');
}
function rSign() {
var test = Math.random();
return test > 0.5 ? 1 : -1;
}
var CQuote = "",
CAuthor = "";
function getQuote() {
$.ajax({
jsonp: "jsonp",
dataType: "jsonp",
contentType: "application/jsonp",
url: "https://api.forismatic.com/api/1.0/?",
data: {
method: "getQuote",
lang: "en",
format: "jsonp",
},
success: function(quote) {
// document.getElementById("quote").innerHTML =
CQuote = quote.quoteText;
// document.getElementById("author").innerHTML =
CAuthor = quote.quoteAuthor;
document.getElementById("author").innerHTML = CAuthor;
document.getElementById("quote").innerHTML = CQuote;
}
});
}
$(document).ready(function() {
getQuote();
})
var background = document.getElementById('backimg');
var huetarget = 0;
var huecurrent = 0;
var bright = 1;
function abyssmal() {
background.style.filter = 'hue-rotate(' + huecurrent + 'deg) ';
if (huecurrent < huetarget) {
huecurrent += Math.abs(huetarget - huecurrent) / 20
}
if (huecurrent >= huetarget) {
huecurrent -= Math.abs(huetarget - huecurrent) / 20
}
}
var interval = setInterval(abyssmal, 25);
$("#btnAnimate").on("click", function() {
huetarget += (Math.random() * 50 + 50) * rSign();
getQuote();
});
$('#tweet').on("click", function() {
window.open("https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=" + encodeURIComponent('"' + CQuote + '" -' + CAuthor), "_blank")
});
$('#facebook').on("click", function() {
fbs_click();
});
/*#myDiv{
background-color: green;
transition : background-color 2s,opacity 2s;
}
#myDiv:hover{
background-color : red;
opacity : 0.5
} */
#quotebox {
font-size: 30px;
height: auto;
}
#authorbox {
text-align: right;
}
.box {
height: auto;
margin: auto;
}
#btnAnimate {
width: 150px;
}
.share {
width: 50px;
float: right;
}
.button {
height: 50px;
padding: 0px;
vertical-align: middle;
margin-top: 40px;
color: white;
background-color: #333c5b;
border-radius: 3px;
border: none;
}
button:hover {
opacity: .8;
}
.background-tile {
background-image: url(https://image.noelshack.com/fichiers/2017/51/3/1513771628-background-1.jpg);
background-size: 1500px 900px;
height: 900px;
width: 1515px;
padding-top: 270px;
z-index: -1;
}
#backimg {
filter: hue-rotate(0deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css" rel="stylesheet" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<div class="background-tile" id="backimg">
<div class="box" id="myDiv" style="padding : 50px 50px 10px 50px;width : 45%;border-radius: 5px; background-color : #476870">
<div id="quotebox">
<i class="fa fa-quote-left" style="font-size:40px;margin-left : 8px "></i>
<span id="quote"></span>
<p id=a uthorbox>-<span id="author"></span>
<p>
</div>
<button class="button quotebutton" id="btnAnimate">get quote</button>
<button class=" share button fa fa-facebook" id="facebook"></button>
<button class=" share button fa fa-twitter" id="tweet" style=" margin-right: 25px;"></button>
</div>
</div>
I doublechecked that only phrasing elements were nested inside the button.
Question: Why does the button not react on Firefox.
Some HTML methodologies are not supported via crossed-browsers.
For example, some features can work on Mozilla and Internet Explorer, but not on Chrome.
However, in this case it's demonstrating the BODY tag is overlapping your content. It may be an error with Mozilla in particular with Codepen.io, as W3schools validated that the button feature works on this browser.
You placed everything inside #backimg, which has z-index: -1, thus sending all its contents below its parent, which is <body>. This means that, even though visible, your contents sit below an invisible click-catcher (<body>).
Firefox seems to apply the z-index, although, AFAIK, the element should be position-ed (have a position value set to anything except static, which is default) for z-index to apply. Chrome doesn't apply it, because #backimg has the implicit position:static.
Closing #backimg before opening #mydiv fixes the problem and makes your HTML valid. It's not really clear how your layout is supposed to look, but here's my attempt at fixing it: https://codepen.io/anon/pen/ZvXXqP
Another problem with your pen had was that it was loading jQuery after Bootstrap's JavaScript, which requires jQuery.

how to work with arrays and objects in javascript

I've got an array of cars and i'm looping through each car. I take input from the user using window.prompt() method. I take that value and filter it through my array of cars. I just want to know how I can restrict the car name the user selects to only those in the array
Fiddle : https://jsfiddle.net/qomu1fny/
var CarsWorld = {
cars : ['Honda','toyota','mercedes','jaguar'],
init: function(){
var getData = prompt('Which Car You Wanna Drive','');
for(var i = 0 ; i < this.cars.length ; i++){
$('.wrap').append(' ' + this.cars[i] + ' <br/> ');
}
},
};
CarsWorld.init();
var getData = prompt('Which Car You Wanna Drive','');
var foundCar = "";
for(var i = 0 ; i < this.cars.length ; i++){
$('.wrap').append(' ' + this.cars[i] + ' <br/> ');
//check if this car in the array is the picked car
if(this.cars[i] == getData){
foundCar = getData;
}
}
$('.wrap').append('you picked ' + foundCar);
Note that if the car isn't on the list then it won't output anything. Fiddle here: http://jsfiddle.net/e5qh3pvw/
I've tried to rephrase your question to something more understandable (currently under peer review). I understand you want to have a prompt that will restrict the choices of the user to the car models in your array.
Unfortunately, window.prompt() cannot achieve this, neither is there any synchronous (blocking) way to achieve it. You will need to use a modal dialog, and insert a regular html select element with your choices, or use a group of radio buttons.
I have created a fiddle that started getting bloated as I progressed. I used a few advanced techniques, just to engage your curiousity, since I suspect you are new to javascript.
Javascript:
var CarsWorld = {
cars : ['Honda','toyota','mercedes','jaguar'],
init: function(){
var getData = 'none';
for(var i = 0 ; i < this.cars.length ; i++){
$('.wrap').append(' ' + this.cars[i] + ' <br/> ');
}
var prompter = new CarsWorld.PromptSelect('Which Car You Wanna Drive', function(selected){
getData = selected;
alert('You chose '+ getData +'! ');
//other logic you want to apply on getData
});
prompter.show();
}
};
CarsWorld.PromptSelect = function(message, callback) {
self = this;
this.init = function(){
self.dropdown = '<select id="selectedCar">';
$.each(CarsWorld.cars, function(index, car){
self.dropdown += '<option>' + car + '</option>';
});
self.dropdown += '</select>';
self.markup = [
'<div class="prompt">',
'<div class="title">CarsWorld Prompt</div>',
'<div class="body">',
'<label for="selectedCar">'+ message +':</label>' + this.dropdown + '</div>',
'<div class="footer">',
'<button class="btn-ok">Ok</button>',
'<button class="btn-cancel">Cancel</button>',
'</div>',
'</div>'
].join('');
};
this.show = function(){
$('.overlay').show();
$('body').css('overflow', 'hidden');
self.init();
$('body').append(self.markup);
$('.prompt .btn-ok').on('click', function(){
self.hide();
callback($('#selectedCar').val());
self.destroy();
});
$('.prompt .btn-cancel').on('click', function(){
self.destroy();
});
return self;
};
this.hide = function(){
$('.prompt').hide();
$('.overlay').hide();
$('body').css('overflow', 'auto');
return self;
};
this.destroy = function(){
self.hide();
return self;
};
};
CarsWorld.init();
HTML:
<div class="wrapper">
<h1> Please choose the car of your type </h1>
<div class="wrap"></div>
<div class="overlay"></div>
</div>
CSS:
.overlay {
display: none;
position: absolute;
width: 100%;
height: 100%;
z-index: 990;
background: #444;
opacity: 0.5;
top: 0;
left: 0;
}
.prompt {
display: block;
position: absolute;
z-index: 999;
width: 300px;
height: 200px;
top: 50%;
left: 50%;
margin-left: -200px;
margin-top: -100px;
}
.prompt .title {
background: black;
color: white;
height: 10%;
padding: 10px;
border-radius: 3px 3px 0 0;
text-align: center;
font-weight: bold;
}
.prompt .body {
background: white;
height: 60%;
padding: 20px;
}
.prompt .footer {
background: grey;
text-align: right;
padding: 10px;
height: 10%;
border-radius: 0 0 3px 3px;
}

How to create DOM element on event, and then prevent event handler from triggering until DOM element exists?

So basically I'm creating a tooltip function.
And tooltip will appear as a new DOM element over the element you clicked.
Here is the fiddle:
$(document).ready(function () {
$('.tooltipTarget').click(function () {
var title = $(this).data('tooltip');
$('<p class="tooltip active"></p>')
.text(title)
.appendTo('body')
.fadeIn(250);
var coords = $(this).offset();
var tooltipHeight = $('.tooltip').height() + $(this).height() + 20;
var tooltipWidth = $('.tooltip').width() / 2;
coords.top = coords.top - tooltipHeight;
coords.left = coords.left - tooltipWidth;
$('.tooltip').css({
top: coords.top,
left: coords.left
});
});
});
.tooltip {
display: none;
position: absolute;
border-radius: 1px;
color: #767676;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
background: #f7f7f7;
padding: 10px;
font-size: 12px;
text-align: left;
z-index: 10;
max-width: 250px;
}
<button style="margin: 50px;" data-tooltip="This is a tooltip" class="tooltipTarget">Click me!</button>
But the problem I have is that new DOM elements will keep appearing as long as you trigger the event.
I wont to prevent it. I want it to be like this:
1)You click a button
2)Tooltip appears
3)You click again on the button - tooltip disappears.
How can I do it?
Working fiddle: https://jsfiddle.net/4d8xhLqj/2/
I would second what #JamesSutherland has put. The tooltip should pre-exist so you only have to play with its positioning and opacity later on.
Having said that though, if you really need to follow the approach that you already have, you could do this:
Snippet:
$(document).ready(function() {
$('.tooltipTarget').click(function() {
var title = $(this).data('tooltip');
if (!$('p.tooltip').hasClass('active')) {
$('<p class="tooltip active"></p>')
.text(title)
.appendTo('body')
.fadeIn(250);
var coords = $(this).offset();
var tooltipHeight = $('.tooltip').height() + $(this).height() + 20;
var tooltipWidth = $('.tooltip').width() / 2;
coords.top = coords.top - tooltipHeight;
coords.left = coords.left - tooltipWidth;
$('.tooltip').css({
top: coords.top,
left: coords.left
});
} else {
$('p.tooltip.active').fadeOut(250, function() {
$(this).remove();
});
}
});
});
.tooltip {
display: none;
position: absolute;
border-radius: 1px;
color: #767676;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
background: #f7f7f7;
padding: 10px;
font-size: 12px;
text-align: left;
z-index: 10;
max-width: 250px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<button style="margin: 200px;" data-tooltip="This is a tooltip" class="tooltipTarget">Click me!</button>
Here is the resulting fiddle. Hope this helps.
You should check if the tooltip is already shown:
$(document).ready(function () {
$('.tooltipTarget').click(function () {
var title = $(this).data('tooltip');
if ($('.tooltip[data-title=' + title + ']').length > 0) {
$('.tooltip[data-title=' + title + ']').remove();
return;
}
$('<p class="tooltip active" data-title=" ' + title + ' "></p>')
.text(title)
.appendTo('body')
.fadeIn(250);
var coords = $(this).offset();
var tooltipHeight = $('.tooltip').height() + $(this).height() + 20;
var tooltipWidth = $('.tooltip').width() / 2;
coords.top = coords.top - tooltipHeight;
coords.left = coords.left - tooltipWidth;
$('.tooltip').css({
top: coords.top,
left: coords.left
});
});
});
I've added a data attribute to the newly created tooltip in order to check afterwards if there is a tooltip for that element present, and if yes, remove it and return.
just check whether or not the tooltip exists as part of your click function. Remove the tooltip when it does exist and create a tooltip when it doesn't.
if($('.tooltip').length) {
$('.tooltip').remove();
}
else {
//create the tooltip as usual here
}
here is a working fiddle
https://jsfiddle.net/4d8xhLqj/3/

Accordian Element Height Issue

I have implemented 2 types of Accordians for my application- 1 Column and 2 Column
Im having a problem with the Static Height for the 1 Column Accordian. And I've been trying to modify the JavaScript all day but cant seem to get it to work.
The Heights should be dynamic in Height depending upon the amount data, however as you can see the Height is fixed, and some of the data is getting cut off:
http://www.davincispainting.com/whydavincis.aspx
The other 2 Column Accordian has almost the same JavaScript as the 1 Column Accordian, however the Height is dynanmic depending on how much data there is:
http://www.davincispainting.com/glossary.aspx
I would provide a Fiddle however the Data is now dynamic:
Here is the JavaScript for the problem Accordian:
<script type="text/javascript">
$.fn.accordion = function () {
return this.each(function () {
$container = $('#mid-featureleft-client');
$container.find("dt").each(function () {
var $header = $(this);
var $selected = $header.next();
$header.click(function () {
$('.active').removeClass('active');
$(this).addClass('active');
if ($selected.is(":visible")) {
$selected.animate({
height: 0
}, {
duration: 300,
complete: function () {
$(this).hide();
}
});
} else {
$unselected = $container.find("dd:visible");
$selected.show();
var newHeight = heights[$selected.attr("id")];
var oldHeight = heights[$unselected.attr("id")];
$('<div>').animate({
height: 1
}, {
duration: 300,
step: function (now) {
var stepSelectedHeight = Math.round(newHeight * now);
$selected.height(stepSelectedHeight);
$unselected.height(oldHeight + Math.round((newHeight - oldHeight) * now) - Math.round(newHeight * now));
},
complete: function () {
$unselected.hide().css({
height: 0
});
}
});
}
return false;
});
});
// Iterate over panels, save heights, hide all.
var heights = new Object();
$container.find("dd").each(function () {
$this = $(this);
$this.css("overflow", "hidden");
heights[$this.attr("id")] = $this.height();
$this.hide().css({
height: 0
});
});
});
};
$(document).ready(function () {
$.getJSON('FaqsJson.ashx?factType=2', function (datas) {
var str_one = "";
str_one = "<dl>"
$.each(datas, function () {
str_one += "<dt class=\"glossquestion\">" + this['Question'] + "</dt>";
str_one += "<dd class=\"glossanswer\" style=\"-webkit-margin-start:0px\"><div class=\"answerbox\">" + this['Answer'] + "</div></dd>";
});
str_one += "</dl>";
$("#glossary_first").html(str_one);
$("#mid-featureleft-client").accordion();
});
});
</script>
Here is the relevent HTML:
<div id="mid-feature-client">
<div id="mid-featureleft-client">
<div id="glossary_first" class="controlbox">
<br /><br />
</div>
<div style="clear: both;">
</div>
</div>
</div>
Here is the relevent css:
#mid-featureleft-client .controlbox {
width:546px;
padding:3px 0 0 6px;
position:relative;
/*background-color:green;*/
}
#mid-featureleft-client .glossarycontrolbox {
width:260px;
padding:3px 0 0 6px;
position:relative;
float:left;
/*background-color:blue;*/
}
.question-clicked {
background-color: #CCCCCC;
color: #0C2A55;
/*margin-top: 10px;*/
/*padding: 2px 5px 0;*/
}
.questionLink-clicked {
color: #0C2A55;
font-size: 1.2em;
font-weight: bold;
}
.answerbox {
padding: 3px 5px 3px 5px;
}
.questionLink {
color: #0C2A55;
font-size: 1.2em;
font-weight: bold;
}
.glossquestion {
padding: 0 5px 4px 0;
}
.glossanswer {
background-color: #F9FBFC;
display: none;
}
#accordion .handle {
width: 260px;
height: 30px;
background-color: orange;
}
#accordion .section {
width: 260px;
height: 445px;
background-color: #a9a9a9;
overflow: hidden;
position: relative;
}
dt {
/*background-color: #ccc;*/
}
dd {
/*height: 30px;*/
}
.active {
background: #a9a9a9;
}
The problem is with the way you're storing the heights, a bit after this comment:
// Iterate over panels, save heights, hide all.
Specifically, this line:
heights[$this.attr("id")] = $this.height();
Your dd elements don't have an id, so on each iteration of the loop, heights[''] is being set to the height of the current dd.
You should be able to fix it by changing this:
$.each(datas, function () {
str_one += "<dt class=\"glossquestion\">" + this['Question'] + "</dt>";
str_one += "<dd class=\"glossanswer\" style=\"-webkit-margin-start:0px\"><div class=\"answerbox\">" + this['Answer'] + "</div></dd>";
});
to this:
var i = 0;
$.each(datas, function () {
str_one += "<dt class=\"glossquestion\">" + this['Question'] + "</dt>";
str_one += "<dd id=\"rand_" + i + "\" class=\"glossanswer\" style=\"-webkit-margin-start:0px\"><div class=\"answerbox\">" + this['Answer'] + "</div></dd>";
i++;
});
I'm just going to point out that my fix doesn't seem very jQuery-esque, and your entire code seems complicated for what it's doing.
If you changed your JSON to something like this:
[{"Question1":"..","Answer1":".."},{"Question2":"..","Answer2":".."}, .. ]
You could do this:
$.each(datas, function (i, v) {
str_one += "<dt class=\"glossquestion\">" + this['Question'] + "</dt>";
str_one += "<dd id=\"Dd" + i + "\" class=\"glossanswer\" style=\"-webkit-margin-start:0px\"><div class=\"answerbox\">" + this['Answer'] + "</div></dd>";
});
which is cleaner code than incrementing our own variable i inside $.each.

Categories

Resources