I am making a cart application in Angular using Angular Bootstrap.
When hovering over the cart icon a tooltip should appear. The tooltip's content should change based on if the item is already in the cart or not.
So, here is the html:
<h3><i class="fa fa-shopping-basket" ng-click="add2Cart(item.Name)" tooltip-placement="right" uib-tooltip-html="itemtooltiptext(item.Name)" aria-hidden="true"></i></h3>
Basically, in order to check if the item is already in the cart, I want the tooltip text to resolve from a function. My understanding from the documentation is this is supported as long as the HTML is trusted.
It says,
uib-tooltip-html $ - Takes an expression that evaluates to an HTML string. Note that this HTML is not compiled. If compilation is required, please use the uib-tooltip-template attribute option instead. The user is responsible for ensuring the content is safe to put into the DOM!
So my itemtooltiptext() function is...
$scope.itemtooltiptext = function(name) {
if (localStorage.getItem("cart") === null) {
return $sce.trustAsHtml("Add " + name + " to Cart!");
} else {
var cart = JSON.parse(localStorage.getItem("cart"));
for (var i = 0; i < cart.length; i++) {
if (cart[i] == name) {
console.log("already in cart");
return $sce.trustAsHtml(name + "already in Cart!");
}
}
return $sce.trustAsHtml("Add " + name + " to Cart!");
}
}
This results in an
Infinite $digest Loop Error
As detailed here: https://stackoverflow.com/a/19370032
But the problem is I need it to come from a function with the various conditions? So should I be using a template? I don't understand how that would work any better because I still need dynamic text served from the template... so what is the solution?
Thank you.
This is not how you use uib-tooltip-html, apparently it causes an infinite digest loop, fortunately the demo plunk shows you how to do it.
You need to get/calculate your html, bind to some scope variable and bind it into uib-tooltip-html
js
$scope.itemtooltiptext = function() {
$scope.htmlTooltip = $sce.trustAsHtml('I\'ve been made <b>bold</b>!');
};
$scope.itemtooltiptext();
html
<button uib-tooltip-html="htmlTooltip" class="btn btn-default">Tooltip</button>
If you still want to bind a function to your tooltip, you can do like so
<button uib-tooltip="{{itemtooltiptext()}}" class="btn btn-default">Tooltip</button>
Note that this approache will have the function invoked every digest cycle.
I ran into this infinite digest cycle issue where I needed a dynamic tooltip... it caused angular to recalculate it every time as a new value (even though it was the same). I created a function to cache the computed value like so:
$ctrl.myObj = {
Title: 'my title',
A: 'first part of dynamic toolip',
B: 'second part of dynamic tooltip',
C: 'some other value',
getTooltip: function () {
// cache the tooltip
var obj = this;
var tooltip = '<strong>A: </strong>' + obj.A + '<br><strong>B: </strong>' + obj.B;
var $tooltip = {
raw: tooltip,
trusted: $sce.trustAsHtml(tooltip)
};
if (!obj.$tooltip) obj.$tooltip = $tooltip;
else if (obj.$tooltip.raw !== tooltip) obj.$tooltip = $tooltip;
return obj.$tooltip;
}
};
Then in the html, I accessed it like this:
<input type="text" ng-model="$ctrl.myObj.C"
uib-tooltip-html="$ctrl.myObj.getTooltip().trusted">
Related
Basically, I have an appointment form which is broken down into panels.
Step 1 - if a user clicks london (#Store1) then hide Sunday and Monday from the calendar in panel 5.
Basically, I want to store this click so that when the user gets to the calendar panel, it will know not to show Sunday and Monday
$('#store1').click(function () {
var $store1 = $(this).data('clicked', true);
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
after I have captured this in a var I then want to run it when the calendar displays.
function ReloadPanel(panel) {
return new Promise(function (resolve, reject, Store1) {
console.log(panel);
console.log("finalpanel");
panel.nextAll('.panel').find('.panel-updater').empty();
panel.nextAll('.panel').find('.panel-title').addClass('collapsed');
panel.nextAll('.panel').find('.panel-collapse').removeClass('in');
var panelUpdater = $('.panel-updater:eq(0)', panel),
panelUrl = panelUpdater.data('url');
if (panelUpdater.length) {
var formData = panelUpdater.parents("form").serializeObject();
panelUpdater.addClass('panel-updater--loading');
panelUpdater.load(panelUrl, formData, function (response, status) {
panelUpdater.removeClass('panel-updater--loading');
if (status == "error") {
reject("Panel reload failed");
} else {
resolve("Panel reloaded");
}
});
} else {
resolve("no reloader");
}
});
}
I'm not sure if this is even written right, so any help or suggestions would be great
Thanks in advance
Don't think of it as "storing a click". Instead, consider your clickable elements as having some sort of data values and you store the selected value. From this value you can derive changes to the UI.
For example, consider some clickable elements with values:
<button type="button" class="store-button" data-store-id="1">London</button>
<button type="button" class="store-button" data-store-id="2">Paris</button>
<button type="button" class="store-button" data-store-id="3">Madrid</button>
You have multiple "store" buttons. Rather than bind a click event to each individually and customize the UI for each click event, create a single generic one which captures the clicked value. Something like:
let selectedStore = -1;
$('.store-button').on('click', function () {
selectedStore = $(this).data('store-id');
});
Now anywhere that you can access the selectedStore variable can know the currently selected store. Presumably you have some data structure which can then be used to determine what "days" to show/hide? For example, suppose you have a list of "stores" each with valid "days":
let stores = [
{ id: 1, name: 'London', days: [2,3,4,5,6] },
// etc.
];
And your "days" buttons have their corresponding day ID values:
<button type="button" class="day-button" data-day-id="1">Sunday</button>
<button type="button" class="day-button" data-day-id="2">Monday</button>
<!--- etc. --->
You can now use the data you have to derive which buttons to show/hide. Perhaps something like this:
$('.day-button').hide();
for (let i in stores) {
if (stores[i].id === selectedStore) {
for (let j in stores[i].days) {
$('.day-button[data-day-id="' + stores[i].days[j] + '"]').show();
}
break;
}
}
There are a variety of ways to do it, much of which may depend on the overall structure and flow of your UX. If you need to persist the data across multiple pages (your use of the word "panels" implies more of a single-page setup, but that may not necessarily be the case) then you can also use local storage to persist things like selectedStore between page contexts.
But ultimately it just comes down to structuring your data, associating your UI elements with that data, and performing logic based on that data to manipulate those UI elements. Basically, instead of manipulating UI elements based only on UI interactions, you should update your data (even if it's just in-memory variables) based on UI interactions and then update your UI based on your data.
you can use the local storage for that and then you can get your value from anywhere.
Set your value
localStorage.setItem("store1", JSON.stringify(true))
Get you value then you can use it anywhere:
JSON.parse(localStorage.getItem("store1"))
Example:
$('#store1').click(function() {
var $store1 = $(this).data('clicked', true);
localStorage.setItem("store1", JSON.stringify(true))
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
I would like to use a javascript loop to create multiple HTML wrapper elements and insert JSON response API data into some of the elements (image, title, url, etc...).
Is this something I need to go line-by-line with?
<a class="scoreboard-video-outer-link" href="">
<div class="scoreboard-video--wrapper">
<div class="scoreboard-video--thumbnail">
<img src="http://via.placeholder.com/350x150">
</div>
<div class="scoreboard-video--info">
<div class="scoreboard-video--title">Pelicans # Bulls Postgame: E'Twaun Moore 10-8-17</div>
</div>
</div>
</a>
What I am trying:
var link = document.createElement('a');
document.getElementsByTagName("a")[0].setAttribute("class", "scoreboard-video-outer-link");
document.getElementsByTagName("a")[0].setAttribute("url", "google.com");
mainWrapper.appendChild(link);
var videoWrapper= document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video-outer-link");
link.appendChild(videoWrapper);
var videoThumbnailWrapper = document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video--thumbnail");
videoWrapper.appendChild(videoThumbnailWrapper);
var videoImage = document.createElement('img');
document.getElementsByTagName("img")[0].setAttribute("src", "url-of-image-from-api");
videoThumbnailWrapper.appendChild(videoImage);
Then I basically repeat that process for all nested HTML elements.
Create A-tag
Create class and href attributes for A-tag
Append class name and url to attributes
Append A-tag to main wrapper
Create DIV
Create class attributes for DIV
Append DIV to newly appended A-tag
I'd greatly appreciate it if you could enlighten me on the best way to do what I'm trying to explain here? Seems like it would get very messy.
Here's my answer. It's notated. In order to see the effects in the snippet you'll have to go into your developers console to either inspect the wrapper element or look at your developers console log.
We basically create some helper methods to easily create elements and append them to the DOM - it's really not as hard as it seems. This should also leave you in an easy place to append JSON retrieved Objects as properties to your elements!
Here's a Basic Version to give you the gist of what's happening and how to use it
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com" });
let div = create("div", { id: "myDiv" });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com", textContent: "this text is a Link in the div" });
let div = create("div", { id: "myDiv", textContent: "this text is in the div! " });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
div {
border: 3px solid black;
padding: 5px;
}
<div id="mainWrapper"></div>
Here is how to do specifically what you asked with more thoroughly notated code.
//get main wrapper
let mainWrapper = document.getElementById("mainWrapper");
//make a function to easily create elements
//function takes a tagName and an optional object for property values
//using Object.assign we can make tailored elements quickly.
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//document.appendChild is great except
//it doesn't offer easy stackability
//The reason for this is that it always returns the appended child element
//we create a function that appends from Parent to Child
//and returns the compiled element(The Parent).
//Since we are ALWAYS returning the parent(regardles of if the child is specified)
//we can recursively call this function to great effect
//(you'll see this further down)
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//these are the elements you wanted to append
//notice how easy it is to make them!
//FYI when adding classes directly to an HTMLElement
//the property to assign a value to is className -- NOT class
//this is a common mistake, so no big deal!
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
//here's where the recursion comes in:
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//keep in mind that it might be easiest to read the ac functions backwards
//the logic is this:
//Append videoImage to videoThumbnailWrapper
//Append (videoImage+videoThumbnailWrapper) to videoWrapper
//Append (videoWrapper+videoImage+videoThumbnailWrapper) to link
//Append (link+videoWrapper+videoImage+videoThumbnailWrapper) to mainWrapper
let mainWrapper = document.getElementById('mainWrapper');
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//pretty fancy.
//This is just to show the output in the log,
//feel free to just open up the developer console and look at the mainWrapper element.
console.dir(mainWrapper);
<div id="mainWrapper"></div>
Short version
Markup.js's loops.
Long version
You will find many solutions that work for this problem. But that may not be the point. The point is: is it right? And you may using the wrong tool for the problem.
I've worked with code that did similar things. I did not write it, but I had to work with it. You'll find that code like that quickly becomes very difficult to manage. You may think: "Oh, but I know what it's supposed to do. Once it's done, I won't change it."
Code falls into two categories:
Code you stop using and you therefore don't need to change.
Code you keep using and therefore that you will need to change.
So, "does it work?" is not the right question. There are many questions, but some of them are: "Will I be able to maintain this? Is it easy to read? If I change one part, does it only change the part I need to change or does it also change something else I don't mean to change?"
What I'm getting at here is that you should use a templating library. There are many for JavaScript.
In general, you should use a whole JavaScript application framework. There are three main ones nowadays:
ReactJS
Vue.js
Angular 2
For the sake of honesty, note I don't follow my own advice and still use Angular. (The original, not Angular 2.) But this is a steep learning curve. There are a lot of libraries that also include templating abilities.
But you've obviously got a whole project already set up and you want to just plug in a template into existing JavaScript code. You probably want a template language that does its thing and stays out of the way. When I started, I wanted that too. I used Markup.js . It's small, it's simple and it does what you want in this post.
https://github.com/adammark/Markup.js/
It's a first step. I think its loops feature are what you need. Start with that and work your way to a full framework in time.
Take a look at this - [underscore._template]
It is very tiny, and useful in this situation.
(https://www.npmjs.com/package/underscore.template).
const targetElement = document.querySelector('#target')
// Define your template
const template = UnderscoreTemplate(
'<a class="<%- link.className %>" href="<%- link.url %>">\
<div class="<%- wrapper.className %>">\
<div class="<%- thumbnail.className %>">\
<img src="<%- thumbnail.image %>">\
</div>\
<div class="<%- info.className %>">\
<div class="<%- info.title.className %>"><%- info.title.text %></div>\
</div>\
</div>\
</a>');
// Define values for template
const obj = {
link: {
className: 'scoreboard-video-outer-link',
url: '#someurl'
},
wrapper: {
className: 'scoreboard-video--wrapper'
},
thumbnail: {
className: 'scoreboard-video--thumbnail',
image: 'http://via.placeholder.com/350x150'
},
info: {
className: 'scoreboard-video--info',
title: {
className: 'scoreboard-video--title',
text: 'Pelicans # Bulls Postgame: E`Twaun Moore 10-8-17'
}
}
};
// Build template, and set innerHTML to output element.
targetElement.innerHTML = template(obj)
// And of course you can go into forEach loop here like
const arr = [obj, obj, obj]; // Create array from our object
arr.forEach(item => targetElement.innerHTML += template(item))
<script src="https://unpkg.com/underscore.template#0.1.7/dist/underscore.template.js"></script>
<div id="target">qq</div>
I am using Froala and I am stuck creating a custom drop down with dynamic option sets in it. I have used their common way to create the drop down but that is useless if we have to fetch the values from db.
I want to make a "Templates" dropdown with 10 options to select which will be created dynamically.
Currently we create a custom drop down this way,
options: {
'Template One': function(e){
_this.editable('insertHTML', "<p>This is template one</p>", true);
},
}
I want this to be dynamic, meaning I will fetch the names and content of the templates from database and add them in the option set accordingly.
something like,
options : {
$.each(alltemplates, function(i, h){
i: function(e){ /// "i" will be the name of the template fetched from db
_this.editable('insertHTML', h, true); // h is the html fetched from db
},
})
}
which will create the drop down dynamically. Any help please ?
Expanding on #c23gooey's answer, here's what we came up with for a similar problem (inserting dynamically-generated mail-merge placeholders).
var commandName = 'placeholders',
iconName = commandName + 'Icon',
buildListItem = function (name, value) {
// Depending on prior validation, escaping may be needed here.
return '<li><a class="fr-command" data-cmd="' + commandName +
'" data-param1="' + value + '" title="' + name + '">' +
name + '</a></li>';
};
// Define a global icon (any Font Awesome icon).
$.FroalaEditor.DefineIcon(iconName, { NAME: 'puzzle-piece' });
// Define a global dropdown button for the Froala WYSIWYG HTML editor.
$.FroalaEditor.RegisterCommand(commandName, {
title: 'Placeholders',
type: 'dropdown',
icon: iconName,
options: {},
undo: true,
focus: true,
refreshAfterCallback: true,
callback: function (cmd, val, params) {
var editorInstance = this;
editorInstance.html.insert(val);
},
refreshOnShow: function ($btn, $dropdown) {
var editorInstance = this,
list = $dropdown.find('ul.fr-dropdown-list'),
listItems = '',
placeholders = editorInstance.opts.getPlaceholders();
// access custom function added to froalaOptions on instance
// use a different iteration method if not using Angular
angular.forEach(placeholders, function (placeholder) {
listItems += buildListItem(placeholder.name, placeholder.value);
});
list.empty().append(listItems);
if (!editorInstance.selection.inEditor()) {
// Move cursor position to end.
editorInstance.selection.setAtEnd(editorInstance.$el.get(0));
editorInstance.selection.restore();
}
}
});
We ran this method by Froala support and were told:
The editor doesn't have any builtin mechanism for using dynamic
content when showing the dropdown, but your solution is definitely a
good one.
Use the refreshOnShow function to change the options dynamically.
I have a problem with $scope.$watch call, when it obviously should be called.
I have a paginator (bootstrap UI) inside my html document:
<pagination total-items="paginatorTotalItems" items-per-page="paginatorItemsPerPage"
page="paginatorCurrentPage" max-size="paginatorSize" class="pagination-sm"
boundary-links="true">
</pagination>
A certain part, where my items are shown (for them I need a paginator):
<div ng-show="reviews" ng-repeat="review in reviewsPerPage">
...
</div>
And a Controller:
...
$scope.reviewsArray = [];
$scope.paginatorItemsPerPage = 1;
$scope.paginatorSize = 3;
$scope.reviewsPerPage = [];
$scope.paginatorTotalItems = $scope.reviews.result.total;
//restangular object to Array
for (var i = 0; i < $scope.paginatorTotalItems; i++) {
$scope.reviewsArray.push($scope.reviews.result.reviews[i]);
};
$scope.paginatorCurrentPage = 1;
$scope.$watch('paginatorCurrentPage', function () {
var begin = (($scope.paginatorCurrentPage - 1) * $scope.paginatorItemsPerPage);
var end = begin + $scope.paginatorItemsPerPage;
console.log($scope.paginatorCurrentPage);
console.log(begin + ' ' + end);
$scope.reviewsPerPage = $scope.reviewsArray.slice(begin,end);
console.log($scope.reviewsPerPage);
});
So, making long story short, I have a variable paginatorCurrentPage, that I change by clicking numbers in my <pagination>, but $watch does not react. This $watch is called only once: when I'm assigning it a value of 1 (after making an array from my restangular object), after that $watch is never called anymore.
Also I'm cheking how paginatorCurrentPage changes in my html file:
<p>Current : {{paginatorCurrentPage}}</p>
And it actually works, this variable is changing, when i switch my pagination buttons, but $watch is not called.
Sorry for my English, and Thank you!
Edited :
I have updated my bootstrap UI, so now in paginator I use ng-model istead of page. And I realized that variable paginatorCurrentPage changes only in my view, but in controller I still have my default $scope.paginatorCurrentPage = 1. Problem still exists.
Thanks for all comments. The problem was about scope. I rewrote ng-model in paginator: ng-model="paginatorPage.current"
and changed
$scope.paginatorCurrentPage = 1;
to
$scope.paginatorPage = {current : 1};
And thanks to #Leo Farmer for advice about dots in directives.
I am creating my first jQuery plugin that I would like to use for my projects.
However, the knowledge I have is not enough probably. Still, I wanna continue creating it...
So I have this part in my little plugin:
jQuery Plugin - Default Settings:
;(function($) {
var defaults = {
title : 'miniBox - Title Spot!',
description : 'miniBox Description Spot. Write a short article or leave it blank!',
buttons: {
switcher : true,
nameButton_1 : 'Continue',
nameButton_2 : 'Discard',
}
};
Now below we have this part:
$.fn.miniBox = function(customs) {
var config = $.extend({}, defaults, customs);
var $first = this.first();
$first.init = function() {
$('body').append( // -->
'<div class="miniBox-wrap">'
+ '<div class="miniBox-frame">'
+ '<div class="miniBox-title"></div>'
+ '<div class="miniBox-content">'
+ '<div class="miniBox-description"></div>'
+ '<div class="miniBox-buttons"></div>'
+ '<div class="miniBox-counter"></div>'
+ '</div>'
+ '</div>'
+ '<div class="miniBox-overlay"></div>'
+ '</div>' // <--
);
var mB_buttonOne = config.buttons.nameButton_1,
mB_buttonTwo = config.buttons.nameButton_2,
mB_title = config.title,
mB_description = config.description;
// --> Confirmation Buttons - Settings OPEN //
if(config.buttons.switcher === true) {
$('.miniBox-buttons').append( // -->
'<input type="button" id="agree" value=' + mB_buttonOne + '>'
+ '<input type="button" id="disagree" value=' + mB_buttonTwo + '>'
// <--
);
} else {
$('.miniBox-buttons').remove();
}
// <-- Confirmation Buttons - Settings CLOSE //
};
$first.init();
};
})(jQuery);
It is confusing a little bit to me, for the first time I guess...
QUESTION:
The title leads to settings fail so here's another question.
My settings appears to be working if I set default title and description and then create custom title or/and description ... It works, it will override defaults.
However as you can read that short if statement for buttons... It still works and overrides default settings if my buttons switcher is set to false or true in defaults or customs... However if defaults and customs both say: true or false... buttons value becomes "undefined".
I have no clues how to say always read custom settings only, if they are defined and forget about defaults... Or something like that. I hope you guys understand me and hopefully I'll find an answer.
Regards, Nenad.
EDIT:
Appears like I need to define completely a button config into customs in order not to get "undefined"... How can I fix this?
Example of Defaults:
var defaults = {
buttons: {
switcher : false,
nameButton_1 : 'Continue',
nameButton_2 : 'Discard',
}
};
Example of Customs:
$(function() {
$().miniBox({
title: 'Works flawlessly!',
buttons: {
switcher: true
}
});
});
How can I still use defaults buttons settings for button names if they are not defined into custom settings ?
.first is just a jQuery method. It selects the first element from the collection.
The init method is a defined function, which is then called immediately (a bit pointless in this case) although you could call it later on outside of the plugin code (also pointless).
As for the second part of your question, you were not recursively .extending the objects, so the nested object values weren't getting extended. Use true as the first argument to .extend to make it deep: http://jsfiddle.net/ntdLu/1/