How to add a break between Array objects with knockout and tooltip - javascript

I have an array joined with commas in a tooltip. However I want each one be on it's own line. I've tried a few things but none seem to be working. The code is a knockout observable to which I am new too.
Here is my ko observable:
this.campaignTagString = ko.observable("");
(function() {
if(data.campaignTags != undefined) {
var array = [];
for(var tag in data.campaignTags){
array.push(data.campaignTags[tag].name);
}
//Join our campaign tag name array
var tagString = array.join(", " + '<br />');
$('#tooltip-campaigntags').tooltip({
content: function(callback) {
callback($(this).prop('title').replace('|', '<br />'));
}
});
var textCampaign = "Campaigns: ";
o.campaignTagString(textCampaign + tagString);
}
})();
I am calling it like so:
<span id="tooltip-campaigntags" class="label label-default label-mini" data-bind="html: '<i class=\'fa fa-tags\'></i> '+campaignTags().length, tooltip: { title: campaignTagString , placement: 'bottom' }" data-toggle="tooltip" data-placement="bottom" title="" >
</span>
Any help would be great, thanks!

Some tips:
You have an error in your code: if campaignTags is an object, campaignTags().length will not work; if it is an array, data.campaignTags[tag].name will not work.
You might find it useful to have a look at Knockout-Bootstrap, a Bootstrap adaptation that provides Knockout bindingHandlers for Bootstrap JS functions (used in demo below).
The Bootstrap documentation mentions the option {html: true} if you want your content not to be encoded into text.
Additionally, your IIFE (the function in which you wrapped the bootstrap tooltip functionality) should rather be a computed observable on your view model, as well as the icon html, and the string, all depending on campaignTags.
Below are the resulting JS viewModel and HTML binding if you follow these tips:
//params === object containing tag objects like -- tagX: {name: 'tag'}
function VM(params) {
var self = this;
this.campaignTags = params;
this.campaignTagsArray = ko.computed(function() {
var array = [];
for(var tag in self.campaignTags) {
array.push(self.campaignTags[tag].name);}
return array;});
this.campaignTagString = ko.computed(function() {
return "Campaigns: <br>" +
self.campaignTagsArray().join(", " + '<br />'); });
this.html = ko.computed(function() {
return '<i class="fa fa-tags"></i> ' +
self.campaignTagsArray().length });
}
And the HTML binding:
<span class="label label-default label-mini" data-bind="
html: html(),
tooltip: { html: true, title: campaignTagString() , placement: 'bottom'}">
</span>
Check out the fiddle for a demo.

Related

How to supply data to Kendo templates in a js widget that is also a template for a control

I am having no luck when I try to work with with kendo templates in a certain way. I have encapsulated a kendoTreeView in a jquery ui.plugin. I have templates defined for the treeview and I am trying to supply data to the template inside the widget first and then after that supply more data to the template via the treeview's item template. Is there a way to do this.
I know I am getting hung up in the template definition for the treeview image script tag-->
navScriptTemplate: "<script id='myReusableTreeviewTemplate' type='text/kendo-ui-template'><img src='#=data.imageRootUrl##: item.NodeImage #.png'/> <a href='\#' id= '' class='entity-link' >#: item.NodeText #</a></script>"
in particular -->
<img src='#=data.imageRootUrl##: item.NodeImage #.png'
Is it possible to supply #=data.imageRootUrl# in my widget's init and then allow the #: item.NodeImage #.png to be subsequently supplied?
javascript widget
(function ($) {
var kendo = window.kendo,
ui = kendo.ui,
Widget = ui.Widget;
var myReusableTreeview = Widget.extend({
init: function (element, options) {
var that = this;
Widget.fn.init.call(this, element, options);
that._create();
},
options: {
imageRootUrl: "",
serviceRootUrl : "",
name: "myReusableTreeview "
},
_create: function () {
var that = this;
...
template = kendo.template(that._templates.divTreeView);
that.divTreeview = $(template({ id: "treeview1" }));
...
that.ds1 = new kendo.data.HierarchicalDataSource({
transport: {
read: {
url: that.options.serviceRootUrl,
dataType: "json",
data: addData1,
}
},
schema: {
model: {
id: "NodeID",
hasChildren: "HasChildren"
}
}
});
...
template = kendo.template(that._templates.navScriptTemplate);
that.navScriptTemplate = $(template({ imageRootUrl: that.options.imageRootUrl }));
that.element.append(that.navScriptTemplate);
$(that.divTreeview1).kendoTreeView({
dataSource: that.ds1,
dataTextField: "NodeText",
template: kendo.template($(that.navScriptTemplate).html())
});
},
_templates: {
divWrapper: "<div style='overflow:auto;clear:both;'><div class='treeviewAttach'></div></div>",
divAttach: "<div></div>",
divTreeView : "<div id='#=data.id#' style='float: left;position: relative;width: 300px;min-height: 510px;margin: 0;padding: 0;'></div>",
navScriptTemplate: "<script id='myReusableTreeviewTemplate' type='text/kendo-ui-template'><img src='#=data.imageRootUrl##: item.NodeImage #.png' alt=''/> <a href='\#' id= '' class='entity-link' >#: item.NodeText #</a></script>"
}
});
ui.plugin(myReusableTreeviewTemplate);
})(jQuery);
Currently, the treeview looks as follows when rendered:
The inspector shows this in F12:
I don't think that you need the script tag if the template is not added to the HTML. Also you should escape the # characters that should not be evaluated from the first template. For some reason the # character in the href also needs quite a bit of escaping:
"<img src='#=data.imageRootUrl#\\#: item.NodeImage \\#.png' alt=''/> <a href='\\\\\\#' id= '' class='entity-link' >\\#: item.NodeText \\#</a>"
It might also be simpler to use format instead of template to add the imageRootUrl:
"<img src='{0}#: item.NodeImage #.png' alt=''/> <a href='\\#' id= '' class='entity-link' >#: item.NodeText #</a>"
that.navScriptTemplate = kendo.format(that._templates.navScriptTemplate, that.options.imageRootUrl);

Is it possible to inject ng-click to an ng-binded html div in angular?

I'm sending html back from my controller with this function :
$scope.filterLocation=function(obj){
var loc = $filter('filter')( $scope.locationss, {'product_code':obj});
var htmlstring = "";
angular.forEach(loc, function(value, key) {
htmlstring = '<div ng-click="locationModal()">' + htmlstring + value.location_description + '</div>';
})
return $sce.trustAsHtml(htmlstring);
}
I then have this html to show the list of locations :
<td><span ng-bind-html="filterLocation(l.productcode)" style="cursor: pointer;"></span></td>
The issue is, as you can see in the controller im sort of injecting '<div ng-click="locationModal()">' and when i view the inspector the function is there correctly :
<span ng-bind-html="filterLocation(l.productcode)" style="cursor: pointer;" class="ng-binding"><div ng-click="locationModal()">A5AL</div></span>
But when i try to click it i get no results, is it because it is inside the ng-bind-html? it was a div but i changed it to a span to see if that changed anything.
I'm doing this fast for the moment, but hopefully you'll have the begin of an answer.
You could do some workaround like this :
$scope.htmlstrings = [];
var string = {
htmlLoc: ''
};
angular.forEach(loc, function(value, key) {
string.htmlLoc= string.htmlLoc+ value.location_description;
$scope.htmlstrings.push(string);
})
I don't understand why you do htmlstring = htmlstring + value.location_description; tho.
Then :
<span ng-init="filterLocation(l.productcode);" ng-repeat="htmlstring in htmlstrings">
<div ng-click="locationModal();" ng-bind="htmlstring.htmlLoc"></div>
</span>
You can change this to a return way with : <span ng-init="htmlstrings = filterLocation(l.productcode);" ng-repeat="htmlstring in htmlstrings">
And without using var string = {} but var string = '' html looks like that:
<span ng-init="htmlstrings = filterLocation(l.productcode);" ng-repeat="htmlstring in htmlstrings">
<div ng-click="locationModal();" ng-bind="htmlstring"></div>
</span>
I'll develop this later. Hope it works in your case.

Knockout.js + ES6 + Underscore Templating

I am trying to get my head around using Knockout.js to create a product cart. Each item outputs a plus and minus button as well as a remove button. My aim is to be able to have the plus and minus increment or decrement the quantity, and the remove button to remove the product. My constraints are that I can't use JQuery.
I've attempted to separate my app concerns so that I have ShopView, ShopModel and ShopItem (ShopItem is the individual item that is pushed to an observable array within the ShopModel). The buttons are rendered, however when clicking on an individual remove/add/minus button and logging the value of this to the console I only am able to see my JS class, not the individual element to remove or alter. Any insight would be greatly appreciated. I've included the bare-bones snippets of the key parts:
index.html
<script type="text/html" id="itemsList">
{{ _.each(items(), function(item) { }}
<a href="#" data-bind="click: minus" class='left-minus'>–</a>
<p class="qty" data-bind="text: item.quantity"></p>
Remove
<a href="#" data-bind="click: plus" class='right-plus'>&plus;</a>
{{ }) }}
</script>
<section data-bind="template: { name: 'itemsList' }" class="items-inner"></section>`
shopView.js
class shopView {
constructor() {
this.setupShop()
}
setupShop(){
this.model.items.push(new Item(97, 'cover-3', '/media/img/cover-3.jpg', 'Issue 5', 'Spring Summer 17', 1, 10));
ko.applyBindings(this.model);
}
}
module.exports = shopView
shopView.js
let ko = require('knockout');
class shopItem{
constructor (id, url, thumbnail, title, edition, quantity, price) {
this.id = ko.observable(id)(),
this.thumbnail = ko.observable(url)(),
this.title = ko.observable(title)(),
this.edition = ko.observable(edition)(),
this.quantity = ko.observable(quantity)(),
this.price = ko.observable(price)();
this.add = function(){
};
this.minus = function(){
};
}
}
module.exports = shopItem;
shopModel
Shop Item
class shopModel {
constructor() {
this.items = ko.observableArray([]);
this.minus = function(item){
console.log(item);
};
this.plus = function(){
};
this.remove = (item) => {
this.items.remove(item);
};
}
}
module.exports = shopModel;
The click binding provides the current $data value to the callback function. But because you are using Underscore for the loop, $data isn't the item. You can change your click binding to something like this:
<a href="#" data-bind="click: function() {minus(item)}" class='left-minus'>–</a>

separating javascript and html for readable code

sometimes my javascript code is mixing with html and css. in this stuation, my code is beind unreadable. How do you separate the javascript and html side in javascript?
For example: (using javascript dojo toolkit)
addLayer: function (layer, index) {
var layerClass = layer.visible === true ? 'layer checked' : 'layer';
var html = '';
html += '<li class="' + layersClass + '">';
html += '<div class="cover"></div>';
html += '<span tabindex="0" class="info" title="MyTitle"></span>';
html += '<span tabindex="0" class="toggle box"></span>';
html += '<div class="clear"></div>';
html += '</li>';
var node = dom.byId('layersList');
if (node) {
domConstruct.place(html, node, "first");
HorizontalSlider({
name: "slider",
value: parseFloat(layer.opacity),
minimum: 0,
maximum: 1,
showButtons: false,
discreteValues: 20,
intermediateChanges: true,
style: "width:100px; display:inline-block; *display:inline; vertical-align:middle;",
onChange: function (value) {
layer.setOpacity(value);
}
}, "layerSlider" + index);
if (!this.layerInfoShowClickHandler) {
this.layerInfoShowClickHandler = on(query(".listMenu"), ".cBinfo:click, .cBinfo:keyup", this._onLayerInfoShowIconClicked);
}
}
}
In this stuation, my code is adding html to view side dynamically. Adding event handlers to created html code. Adding additional tools(HorizantalSlider) same time.
This workflow is binded one to another. This code is unreadable. Is there a way to solve this with clean code?
This answer uses Dojo to split your HTML + CSS from JavaScript.
HTML template
The recommended approach is by defining your HTML template in a seperate HTML file. For example:
<li class="{layersClass}">
<div class="cover"></div>
<span tabindex="0" class="info" title="MyTitle"></span>
<span tabindex="0" class="toggle box"></span>
<div class="clear"></div>
</li>
Also notice the replacement of layersClass by a placeholder.
Load the HTML template
Now, to load the template you use the dojo/text plugin. With this plugin you can load external templates, for example by using:
require(["dojo/text!./myTemplate.html"], function(template) {
// The "template" variable contains your HTML template
});
Converting the placeholders
To replace {layersClass}, you can use the replace() function of the dojo/_base/lang module. Your code would eventually look like:
require(["dojo/text!./myTemplate.html", "dojo/_base/lang"], function(myTemplate, lang) {
var html = lang.replace(myTemplate, {
layersClass: layersClass
});
});
This would return exactly the same as your html variable, but seperated the HTML from your JavaScript code.
Seperate CSS
To seperate the CSS style from your HorizontalSlider you could define an id property and just put your CSS in a seperate CSS file. Your HorizontalSlider would become:
HorizontalSlider({
name: "slider",
value: parseFloat(layer.opacity),
minimum: 0,
maximum: 1,
showButtons: false,
discreteValues: 20,
intermediateChanges: true,
id: "mySlider",
onChange: function (value) {
layer.setOpacity(value);
}
}, "layerSlider" + index);
Now you can use the following CSS:
#mySlider {
width:100px;
display:inline-block;
*display:inline;
vertical-align:middle;
}
You could store the html variable in a different place, let's say in a file called template.js. However, doing so you can't concatenate the HTML string immediately since you need to inject this layersClass variable. Here is a possible workaround :
// template.js
var template = function () {
return [
'<li class="', this.layersClass, '">',
'<div class="cover"></div>',
'<span tabindex="0" class="info" title="MyTitle"></span>',
'<span tabindex="0" class="toggle box"></span>',
'<div class="clear"></div>',
'</li>'
].join('');
};
// view.js
html = template.call({
layersClass: layerClass
});
Effective and easy to use. However, if you want to use a template in the form of a string rather than a function, you'll need a template parser. The following one will give the same kind of result as above (notice that regex capturing is not supported by IE7 split()) :
function compile(tpl) {
tpl = Array.prototype.join.call(tpl, '').split(/{{(.*?)}}/);
return Function('return [' + tpl.map(function (v, i) {
if (i % 2) return 'this["' + v + '"]';
return v && '"' + v.replace(/"/g, '\\"') + '"';
}).join(',') + '].join("");');
}
Usage example :
var template = '<b>{{text}}</b>';
var compiled = compile(template);
// compiled -> function () {
// return ["<b>",this["text"],"</b>"].join("");
// }
var html1 = compiled.call({ text: 'Some text.' });
var html2 = compiled.call({ text: 'Bold this.' });
// html1 -> "<b>Some text.</b>"
// html2 -> "<b>Bold this.</b>"
Now, let's see how you could use this tool to organize your files in a clean way.
// product.data.js
product.data = [{
name: 'Apple iPad mini',
preview: 'ipadmini.jpeg',
link: 'ipadmini.php',
price: 280
}, {
name: 'Google Nexus 7',
preview: 'nexus7.jpeg',
link: 'nexus7.php',
price: 160
}, {
name: 'Amazon Kindle Fire',
base64: 'kindlefire.jpeg',
link: 'kindlefire.php',
price: 230
}];
// product.tpl.js
product.tpl = [
'<div class="product">',
'<img src="{{preview}}" alt="{{name}}" />',
'<span>{{name}} - ${{price}}</span>',
'details',
'</div>'
];
// product.view.js
var html = [];
var compiled = compile(product.tpl);
for (var i = 0, l = product.data.length; i < l; i++) {
html.push(compiled.call(product.data[i]));
}
document.getElementById('products').innerHTML = html.join('');
Demo : http://jsfiddle.net/wared/EzG3p/.
More details here : https://stackoverflow.com/a/20886377/1636522.
This compile function might not be enough for your needs, I mean, you might quickly need something more powerful that includes conditional structures for example. In this case, you may take a look at Mustache, Handlebars, John Resig or Google "javascript templating engine".
Consider using templates for building dynamic view elements
E.g:
http://handlebarsjs.com/
http://underscorejs.org/#template
One way is to use HTML (not that cool if you don't like to split your logic):
<div style="display:none" id="jQ_addLayer">
<div class="cover"></div>
<span tabindex="0" class="info" title="MyTitle"></span>
<span tabindex="0" class="toggle box"></span>
<div class="clear"></div>
</div>
than in jQuery create the LI with the passed variable and insert your #jQ_addLayer content:
var html = '<li class="'+layersClass+'">'+ $("#jQ_addLayer").html() +'</li>';
Another way is to escape your string newlines:
var html = '\
<li class="' + layersClass + '">\
<div class="cover"></div>\
<span tabindex="0" class="info" title="MyTitle"></span>\
<span tabindex="0" class="toggle box"></span>\
<div class="clear"></div>\
</li>'; //don't forget to escape possible textual single-quotes in your string

Suggest any Good mustache document

Suggest me any good mustache doc. Also i want to know in a mushtach loop how do i get the count or the loop no. I mean how can i do a for loop in mustache.
In the below code i wish to change the id in every loop
<script src="http://github.com/janl/mustache.js/raw/master/mustache.js"></script>
<script>
var data, template, html;
data = {
name : "Some Tuts+ Sites",
big: ["Nettuts+", "Psdtuts+", "Mobiletuts+"],
url : function () {
return function (text, render) {
text = render(text);
var url = text.trim().toLowerCase().split('tuts+')[0] + '.tutsplus.com';
return '' + text + '';
}
}
};
template = '<h1> {{name}} </h1><ul> {{#big}}<li id="no"> {{#url}} {{.}} {{/url}} </li> {{/big}} </ul>';
html = Mustache.to_html(template, data);
document.write(html)
</script>
<body></body>
You can't get at the array index in Mustache, Mustache is deliberately simple and wants you to do all the work when you set up your data.
However, you can tweak your data to include the indices:
data = {
//...
big: [
{ i: 0, v: "Nettuts+" },
{ i: 1, v: "Psdtuts+" },
{ i: 2, v: "Mobiletuts+" }
],
//...
};
and then adjust your template to use {{i}} in the id attributes and {{v}} instead of {{.}} for the text:
template = '<h1> {{name}} </h1><ul> {{#big}}<li id="no-{{i}}"> {{#url}} {{v}} {{/url}} </li> {{/big}} </ul>';
And as an aside, you probably want to include a scheme in your url:
url : function () {
return function (text, render) {
text = render(text);
var url = text.trim().toLowerCase().split('tuts+')[0] + '.tutsplus.com';
return '' + text + '';
//---------------^^^^^^^
}
}
Demo: http://jsfiddle.net/ambiguous/SFXGG/
Expanding on #mu's answer, you could also keep an index in the data object and have the template refer to it and the function increment it. So you wouldn't need to add i to each item.
see demo : http://jsfiddle.net/5vsZ2/

Categories