How to create dynamic charts in angular with canvas? - html

I have situtation in which I am getting list of objects from the backend and this list would vary upon the request sometime there could be 6 objects in the list and sometime less. So I want to achieve dynamic creation of the piecharts.
So what I have tried is to iterate the object list like the following
TS And HTML
makeChart() {
for (let i = 0; i < this.benchmarkData.length; i++) {
this.integrationActions = new Chart("PieChart" + i, {
type: 'doughnut',
data: {
labels: this.benchmarkData[i].labels,
datasets: [{
label: 'Dataset 1',
data: this.benchmarkData[i].values,
backgroundColor: ['#299aff', '#ff4961', '#ffe802', '#9080e8', '#28dac6'],
borderWidth: 0,
}],
},
options: {
aspectRatio: 3,
responsive: true,
legend: {
position: 'left',
display: true,
}
}
});
}
}
<div class="row">
<div class="col-lg-6 mt-3" *ngFor="let benchmarkDataIndex of benchmarkData; let i = index">
<p>{{'PieChart'+ i}}</p>
<div class="graph-section">
<a href="javascript:void(0)"
(click)="routBenchMark(benchmarkDataIndex.name,benchmarkDataIndex.policyId)">
<h2>{{benchmarkDataIndex.name}}</h2></a>
<div class="chart-container" [class.hidden]="showNoRecord === true">
<canvas id="PieChart'+ i}}"></canvas>
</div>
</div>
</div>
So when I did this I got the error
benchmarkchart.component.ts:61 Failed to create chart: can't acquire context from the given item
What could be the possible issue with this approch and if any how can I handle this situtation ?

Related

How to check if an image is available and display it with v-if

How do I check if an image is available and render it if so?
So far I have tried something like this, but does not give a true condition.
<div v-for="owner in organisation.owners"
<div v-if="`https://intranet.com/Lists/CustomPortraits/${owner.employeeId}.jpg`" :style="`background-image:url(https://intranet.com/Lists/CustomPortraits/${owner.employeeId}.jpg)`" class="user-profile-image"/>
<div v-else class="user-avatar"> <span/>{{ owner.name | nameInitials }} </div>
Checking image exists on the server in JS is well described in another SO question - Check if image exists on server using JavaScript?
So it is easy just to choose one of the solutions and integrate it into Vue. Since you want to use it in the v-for loop, plain function is a bad solution. Better will be to introduce computed property and extend existing object with some new properties:
Update
Deleted my previous code - computed is a bad choice either in this case because array/objects returned from computed prop is not reactive data (changing the content of object returned by computed will NOT trigger Vue re-render)
New example below shows solution combining deep watching input data (organisation) + maintaining extended data in the data part of the component...
const vm = new Vue({
el: '#app',
data() {
return {
// this if external input in question (prop maybe)
organisation: {
owners: [{
employeeId: 100,
name: "Bill",
},
{
employeeId: 200,
name: "Murray",
fail: true // just for demo purposes
},
{
employeeId: 300,
name: "Billy",
},
]
},
ownersWithImage: [],
}
},
methods: {
checkImageExists(owner) {
const img = new Image()
img.onload = () => {
owner.doesImageExist = true
}
img.onerror = () => {
owner.doesImageExist = false
}
img.src = owner.imageUrl
},
extendOwnerWithImage(owner) {
const extendedOwner = {
...owner,
imageUrl: 'https://www.fillmurray.com/250/' + owner.employeeId,
doesImageExist: false // default
}
if (!owner.fail) // just for demo purposes
this.checkImageExists(extendedOwner)
return extendedOwner
}
},
watch: {
'organisation.owners': {
handler: function() {
this.ownersWithImage = this.organisation.owners.map(this.extendOwnerWithImage)
},
immediate: true,
deep: true
}
},
})
.user-profile-image {
width: 150px;
height: 150px;
background-size: cover;
background-position: top center;
border-radius: 50%;
background-color: grey;
text-align: center;
vertical-align: middle;
line-height: 150px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="owner in ownersWithImage" :key="owner.employeeId">
<div v-if="owner.doesImageExist" :style="`background-image:url(${owner.imageUrl})`" class="user-profile-image">
<span>{{ owner.name }}</span>
</div>
<div v-else class="user-profile-image"><span>{{ owner.name }}</span></div>
</div>
</div>

How to properly use an autocomplete input field based on a database source in Vue?

I want to use an input field (or something similar) that suggests an autocomplete, based on records from a data source.
In Vue I retrieve an array from a database table containing 3000+ records:
data(){
return{
inboundRelation_Trelation_data: [],
}
},
mounted(){
axios.get('/app/wms/allRelations', {
params: {
"dbConn": this.connString,
}
})
.then(response => this.inboundRelation_Trelation_data = response.data);
},
Based on this data, I want an autocomplete input field and/or dropdown. I've found 2 approaches online.. 1:
<select v-model="form.CUSTOMER_NAME">
<option v-for="(relation, index) in inboundRelation_Trelation_data" :value="relation.RELATIONCODE" v-text="relation.COMPANYNAME + ' | ' + relation.RELATIONCODE"></option>
</select>
This populates a dropdown, but my users experience this as tedious, as they need to type their letters quickly, otherwise after a small pause (like <0.5s), the next typed letter will start a new search and the results are inconsistent.
The other approach is using a data-list:
<input list="allRelations" type="text" #focus="$event.target.select()" v-model="form.CUSTOMER_NAME">
<datalist id="allRelations">
<option v-for="(relation, index) in inboundRelation_Trelation_data" :value="relation.RELATIONCODE" v-text="relation.COMPANYNAME + ' | ' + relation.RELATIONCODE"></option>
</datalist>
This works perfectly for small amounts of data. But when dealing with 100+ records (or 3000+ in this case), the whole browser freezes upon typing a letter. For some reason this is a very resource-heavy implementation. I've found some people with similar issues, but no solutions.
At the end of the day, I just want my users to be able to search in a huge list of 3000+ records. How do I approach this?
You can use vue-autosuggest package by this github link :
https://github.com/darrenjennings/vue-autosuggest
I am using this package and my data loads as my expect.
This is the template that you can use:
<template>
<div class="autosuggest-container">
<vue-autosuggest
v-model="form.CUSTOMER_NAME"
:suggestions="filteredOptions"
#focus="focusMe"
#click="clickHandler"
#input="onInputChange"
#selected="onSelected"
:get-suggestion-value="getSuggestionValue"
:input-props="{
class: 'form-control',
id: 'autosuggest__input',
field: 'CUSTOMER_NAME',
placeholder: 'Enter customer name for auto suggest',
}"
>
<div
slot-scope="{ suggestion }"
style="display: flex; align-items: center"
>
<img
:style="{
display: 'flex',
width: '25px',
height: '25px',
borderRadius: '15px',
marginLeft: '10px',
}"
:src="suggestion.item.avatar"
/>
<div style="{ display: 'flex', color: 'navyblue'}">
{{ suggestion.item.CUSTOMER_NAME }}
</div>
</div>
</vue-autosuggest>
</div>
And the Script section:
<script>
export default {
data() {
return {
searching: false,
query: '',
selected: '',
suggestions: [],
}
},
computed: {
filteredOptions() {
return [
{
data: this.suggestions.filter((option) => {
return (
option.name.toLowerCase().indexOf(this.query.toLowerCase()) > -1
)
}),
},
]
},
},
methods: {
clickHandler() {
//
},
onSelected(item) {
this.selected = item.item
},
async onInputChange(text = '') {
this.searching = true
await this.$axios
.get(`/app/wms/allRelations`,{
params: {
"dbConn": this.connString,
})
.then((res) => {
this.suggestions = res.data.data
})
.catch((e) => console.log(e))
.finally(() => (this.searching = false))
},
getSuggestionValue(suggestion) {
return suggestion.item.name
},
focusMe(e) {
this.onInputChange()
},
},
}
</script>
If still your browser freeze, you have to change your API response limit to something like descended 10 items.

Re-initialise directive on controller

I have a carousel that loads a bunch of items from an array, but a directive is being used to show the carousel.
I want to remove an item from a list such as "street parties" if something gets selected, but as the list of items seems to binded to a directive how can reload the directive? I have seen similar questions but I cant seem to get anything to work yet.. Thank you.
In my controller I have the various items like this
this.eventProducts.push({
link: "/quote/bar-and-bat-mitzvah-insurance",
image: "/Content/Images/policies/bar-mitzvah.jpg",
name: "Bar and Bat Mitzvahs"
});
this.eventProducts.push({
link: "/quote/street-party-insurance",
image: "/Content/Images/policies/street-party.jpg",
name: "Street Parties"
});
this.eventProducts.push({
link: "/quote/conference-and-meetings-insurance",
image: "/Content/Images/policies/conference.jpg",
name: "Conferences and Meetings"
});
Then I have this directive which sets the carousel
angular.module("App")
.controller("HomepageController", HomepageController)
.config(["$routeProvider", HomepageController.routing])
.directive("owlCarousel", () => {
return {
restrict: "E",
transclude: false,
link: (scope:any) => {
scope.initCarousel = (element) => {
// provide any default options you want
var defaultOptions = {
};
var customOptions = scope.$eval($(element).attr("data-options"));
// combine the two options objects
for (var key in customOptions) {
defaultOptions[key] = customOptions[key];
}
// init carousel
(<any>$(element)).owlCarousel(defaultOptions);
};
}
};
})
.directive("owlCarouselItem", [() => {
return {
restrict: "A",
transclude: false,
link: (scope, element) => {
// wait for the last item in the ng-repeat then call init
if (scope.$last) {
scope.initCarousel(element.parent());
}
}
};
}]);
And in the HTML the items are being loaded like this
<div class="row homepage-events">
<div class="homepage-heading"><h2 class="text-center">A Selection of our Most Popular Event Insurance Policies</h2></div>
<data-owl-carousel class="owl-carousel" data-options="{navigation: false, pagination: true, rewindNav : true}">
<div class="carousel-item" owl-carousel-item="" data-ng-repeat="product in ::homepageController.eventProducts">
<div class="">
<div class="thumbnails thumbnail-style thumbnail-kenburn">
<div class="thumbnail-img">
<div class="overflow-hidden">
<a class="" href="{{::product.link}}">
<img data-ng-src="{{::product.image}}" alt="{{::product.altText}}" />
</a>
</div>
<a class="btn-more2 hover-effect">Insurance for</a>
<a class="btn-more hover-effect" data-ng-href="{{::product.link}}">{{::product.name}}</a>
</div>
</div>
</div>
</div>
</data-owl-carousel>
</div>

Typescript: How to use drawHeaderRow to create table with horizontal headers?

jsPDF allows to create a table form JSON data and save that table into a PDF document. I have created a Angualr2/Typescript application to do the same. This create table form my JSON data. I'm trying to use jsPDF is create a table with horizontal headers. Example for this given here. Code to create that is as follows.
// Horizontal - shows how tables can be drawn with horizontal headers
examples.horizontal = function () {
var doc = new jsPDF('p', 'pt');
doc.autoTable(getColumns().splice(1,4), getData(), {
drawHeaderRow: function() {
// Don't draw header row
return false;
},
columnStyles: {
first_name: {fillColor: [41, 128, 185], textColor: 255, fontStyle: 'bold'}
}
});
return doc;
};
Complete code is available here. This code is written in JavaScript. I'm looking for a way to convert this into Typescript. Does anyone have any idea how to do it?
Your component might look like this:
#Component({
selector: 'my-app',
template:
`<h1>JSON to PDF app</h1>
<div class="container" id="div1">
<button id="create" (click)="convert('base')">Create file</button>
<button id="create" (click)="convert('horizontal')">
Create file with horizontal table
</button>
</div>
`
})
export class AppComponent {
cols: Array<any> = [{
title: "Details",
dataKey: 'details'
}, {
title: "Values",
dataKey: 'values'
}];
optionsContainer = {
base: {},
horizontal: {
drawHeaderRow: () => false,
columnStyles: {
details: {fillColor: [41, 128, 185], textColor: 255, fontStyle: 'bold'}
}
}
};
rows: Array<any> = [];
constructor() {
const item = {
"Name" : "XYZ",
"Age" : "22",
"Gender" : "Male"
};
this.rows = Object.keys(item).map((key) => {
return { 'details': key, 'values': item[key] };
});
}
convert(action){
const doc = new jsPDF()
.autoTable(this.cols, this.rows, this.optionsContainer[action]);
doc.save('Test.pdf');
}
}
Demo Plunker

How to clear a Text Box in angularJS, when click any link in the page AngularJS

I've a search box and multiple categories links available in the HTML page.
as soon as user input in searchBox, i want to show:
p in Products | filter {serachedText}
When user clicks a category hyperlink, i want to show
p in Products | filter {categoryID}
But because serachedText is still avialble, result showing for
p in Products | filter {categoryID} | filter {serachedText}
Is there any way i can clear the serachedText as soon as user clicks on anylink.
That would be really easy to do in angularjs.
In html.
<input ng-model="mytext">
<a href ng-click="mytext=''">Clear Text</a>
jsfiddle is here.
Your filter expression is wrong.
if your data is a JSON array having category and name properties like so:
self.Products = [
{ category: 1, name: 'Pencil' },
{ category: 1, name: 'Notebook' },
{ category: 2, name: 'Kitten' }
];
And you are binding the following things for the selected category and search text:
self.category = 1;
self.searchText = 'pen';
You could create a complex filter expression like so:
filter: { category: vm.category | name: vm.searchText }
This will filter both on category and searchText or in combination.
To clear out the searchText, you can watch if category changes using $scope.$watch and when it changes, clear up the searchText.
Take a look at the example below or at http://plnkr.co/edit/OEDvOn. In the example, my filter expression is a bit more complicated since the selected category is actually an object containing value and name properties for the selected category, thus I need to add .value to get the right thing to pass to the filter.
Another point: For doing client side filtering, this is fine, but if you are filtering on server side, I'd rather get the filtering done on a service layer and just returned the filtered result instead of all possible data... save bandwidth and transfer time.
(function(undefined) {
'use strict';
angular.module('myApp',[]);
angular.module('myApp')
.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['$log', '$scope'];
function searchCtrl($log, $scope) {
/* jshint validthis: true */
var self = this;
self.searchText = undefined;
self.categories = [
{ value: undefined, name: 'All' },
{ value: 1, name: 'Fruit' },
{ value: 2, name: 'Snacks' },
{ value: 3, name: 'Flower' },
{ value: 4, name: 'Pet' },
{ value: 5, name: 'Stationary' }
];
self.category = self.categories[0];
self.data = [
{ category: 1, name: 'Apple' },
{ category: 1, name: 'Grapes' },
{ category: 2, name: 'Dorito' },
{ category: 2, name: 'KitKat' },
{ category: 3, name: 'Roses' },
{ category: 3, name: 'Orchid' },
{ category: 4, name: 'Hamster' },
{ category: 4, name: 'Kitten' },
{ category: 5, name: 'Pencil' },
{ category: 5, name: 'Notebook' }
];
$scope.$watch(function() { return self.category; }, function(val, old) {
self.searchText = undefined;
});
}
}());
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-app="myApp" ng-controller="searchCtrl as vm">
<div class="form-group">
<label>Category</label>
<select class="form-control" ng-options="cat.name for cat in vm.categories" ng-model="vm.category">
</select>
</div>
<div class="input-group">
<input class="form-control" type="textbox" ng-model="vm.searchText" placeholder="Search text here..." />
<span class="input-group-btn">
<button type="button" class="btn btn-primary">
<i class="glyphicon glyphicon-search"></i> Search</button>
</span>
</div>
<div class="well well-sm" style="margin-top:20px">
<ul ng-repeat="item in vm.data | filter:{category:vm.category.value, name:vm.searchText}">
<li>{{item.name}}</li>
</ul>
</div>
</div>
I'd suggest directive will be the best option
MarkUp
<input ng-model="mytext">
<a href ng-click="mytext=''" clear="mytext">Clear Text</a>
Directive
app.directive('a', function() {
return {
restrict: 'E',
scope: {
clear: '='
},
compile: function(scope, ele, attrs) {
ele.on('click', function() {
scope.$apply(function() {
scope.clear = undefined;
})
})
}
}
})