Budding web developer here struggling with updating the view from my controller.
I'm using highmaps and angular to build a neat selection tool for my web app. I've got a directive nested inside the scope of a controller. I would like this directive to update a value (selectedCountry) stored in the controller. Then, I'd like the controller to display the up to date selectedCountry value on the view.
I've checked that the directive is passing the correct selectedCountry value to the parent controller. However, the controller is not updating the view to match the updated value. I would greatly appreciate if someone could take a look at this.
Demo Here: http://jsfiddle.net/frauLLmr/5/
index.html
<div ng-app="myApp">
<div ng-controller="GraphController as graphCtrl">
<div> {{graphCtrl.showSelectedCountry()}} </div>
<div> {{graphCtrl.selectedCountry}} </div>
<high-chart-directive update-selected-country='graphCtrl.updateSelectedCountry(newCountry)'></high-chart-directive>
</div>
</div>
app.js
var myApp = angular.module('myApp', []);
myApp.controller('GraphController', function() {
var self = this;
self.selectedCountry = 'unselected';
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ self.selectedCountry);
};
self.updateSelectedCountry = function(newCountry) {
self.selectedCountry = newCountry;
outsideScopeTest();
};
self.showSelectedCountry = function() {
return self.selectedCountry;
};
});
myApp.directive('highChartDirective', function () {
return {
restrict: 'E',
scope: {
updateSelectedCountry: '&'
},
link: function(scope, element) {
Highcharts.mapChart(element[0], getMapOptions(mapClick));
function mapClick(event) {
scope.updateSelectedCountry({newCountry: event.point.name});
alert('selectedCountry (from directive scope): '
+ event.point.name);
}
}
};
function getMapOptions(callback) {
return {
title: {
text: ''
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
data: getTestCountries(),
mapData: Highcharts.maps['custom/world-highres'],
// TODO-chantelle: figure out how geoJSON joinBy works
joinBy: 'hc-key',
name: 'Emission per capita',
states: {
hover: {
color: '#9370DB'
}
},
dataLabels: {
enabled: false,
format: '{point.name}'
}
}],
plotOptions: {
series: {
events: {
click: function(event) {
callback(event);
}
}
}
}
};
}
function getTestCountries() {
return [{
"hc-key": "ca",
"value": 0
}, {
"hc-key": "br",
"value": 1
}, {
"hc-key": "ru",
"value": 2
}];
}
});
the issue is that Highcharts.mapChart(element[0], getMapOptions(mapClick)); is not part of the angular scope. So any calls here will not trigger the angular app to refresh. You need to force angular to update using $scope.apply();
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ selfc.selectedCountry);
// force angular update
$scope.$apply();
};
Try this
<div ng-app="myApp">
<div ng-controller="GraphController as graphCtrl">
<div> {{graphCtrl.showSelectedCountry()}} </div>
<div> {{graphCtrl.selectedCountry}} </div>
<high-chart-directive update-selected-country='graphCtrl.updateSelectedCountry(newCountry)'></high-chart-directive>
</div>
</div>
the js
var myApp = angular.module('myApp', []);
myApp.controller('GraphController', function($scope) {
var self = this;
self.selectedCountry = 'unselected';
var outsideScopeTest = function() {
alert('selectedCountry (from controller scope): '
+ self.selectedCountry);
$scope.$apply();
};
self.updateSelectedCountry = function(newCountry) {
self.selectedCountry = newCountry;
outsideScopeTest();
};
self.showSelectedCountry = function() {
return self.selectedCountry;
};
});
myApp.directive('highChartDirective', function () {
return {
restrict: 'E',
scope: {
updateSelectedCountry: '&'
},
link: function(scope, element) {
Highcharts.mapChart(element[0], getMapOptions(mapClick));
function mapClick(event) {
scope.updateSelectedCountry({newCountry: event.point.name});
alert('selectedCountry (from directive scope): '
+ event.point.name);
}
}
};
function getMapOptions(callback) {
return {
title: {
text: ''
},
mapNavigation: {
enabled: true,
buttonOptions: {
verticalAlign: 'bottom'
}
},
series: [{
data: getTestCountries(),
mapData: Highcharts.maps['custom/world-highres'],
// TODO-chantelle: figure out how geoJSON joinBy works
joinBy: 'hc-key',
name: 'Emission per capita',
states: {
hover: {
color: '#9370DB'
}
},
dataLabels: {
enabled: false,
format: '{point.name}'
}
}],
plotOptions: {
series: {
events: {
click: function(event) {
callback(event);
}
}
}
}
};
}
function getTestCountries() {
return [{
"hc-key": "ca",
"value": 0
}, {
"hc-key": "br",
"value": 1
}, {
"hc-key": "ru",
"value": 2
}];
}
});
Related
is it possible with this example from Ben Foster to get all nodes closed by default (at the loading of the page) ? and to open each node with ng-click ?
http://jsfiddle.net/benfosterdev/NP7P5/
I have found a way to get selected node but I don't know how to combine it with ng-click and eventually ng-show or ng-hide:
ng-click='nodeSelected($event, category)'
and in controller
$scope.nodeSelected = function($event, category){
$event.stopPropagation();
console.log('This node is selected' + category);
}
Just found a similar example :
var gyroEditor = angular.module('gyroEditor', []);
gyroEditor.controller('Ctrl', function($scope) {
$scope.nodes = [
{
title: 'Computers',
categories: [
{
title: 'Laptops',
categories: [
{
title: 'Ultrabooks'
},
{
title: 'Macbooks'
}
]
},
{
title: 'Desktops'
},
{
title: 'Tablets',
categories: [
{
title: 'Apple'
},
{
title: 'Android'
}
]
}
]
},
{
title: 'Printers'
}
];
});
gyroEditor.directive('tree', function() {
return {
restrict: 'E',
replace: true,
scope: {nodes: '=nodes'},
templateUrl: 'tree.html',
controller: function($scope) {
console.log('tree ctrl');
}
};
});
gyroEditor.directive('treenode', function() {
return {
restrict: 'E',
replace: true,
scope: {node:'=node'},
templateUrl: 'treenode.html',
controller: function($scope) {
console.log('node ctrl');
}
};
});
gyroEditor.directive("recursive", function($compile) {
return {
restrict: "EACM",
priority: 100000,
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
iElement.append(
compiledContents(scope,
function(clone) {
return clone; }));
};
}
};
});
.panel-left {
float: left;
width: 200px;
margin: 0 20px 20px 0;
}
.panel-editors {
float: right;
height: 100%;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app=gyroEditor ng-controller=Ctrl>
<script type="text/ng-template" id="treenode.html">
<li ng-init="collapsed=true">
<a ng-click="collapsed=!collapsed"><i class="fa fa-{{((collapsed || !node.categories) ? '' : '-open')}}"></i> {{node.title}}</a>
<ol ng-if="!collapsed && node.categories && node.categories.length">
<recursive>
<treenode ng-repeat="c in node.categories" node=c>
</treenode>
</recursive>
</ol>
</li>
</script>
<script type="text/ng-template" id="tree.html">
<ol>
<treenode ng-repeat="n in nodes" node=n></treenode>
</ol>
</script>
<div class=panel-left>
<tree nodes=nodes></tree>
</div>
</div>
{{node.title}}
I am trying to write test case for my directive in anguarjs1.x
here is my directive
.directive("weeklyDirective", function($timeout) {
return {
scope: {
data: '=',
},
link: function(scope, element) {
scope.weekDays = [
{ text: 'Sun', id: 1 },
{ text: 'Mon', id: 2 },
{ text: 'Tue', id: 3 },
{ text: 'Wed', id: 4 },
{ text: 'Thu', id: 5 },
{ text: 'Fri', id: 6 },
{ text: 'Sat', id: 7 }
];
},
restrict: 'A',
templateUrl: "/flat-ui/tpls/weekly-scheduler.html",
};
})
where is my directive template
<ul style="padding: 0px;display: inline;margin: 0;list-style: none;">
<li ng-repeat="weekDay in weekDays" style="padding: 10px;display: inline;">
<input type="checkbox" value="{{weekDay.id}}" check-list="data.weeklyDetails" id="{{'chk_'+$index}}" class="ee-check"> <label class="ee-check" for="{{'chk_'+$index}}"><span></span> {{weekDay.text}}</label>
</li>
</ul>
In my weekly directive, I have used another directive which handles my checkbox list
.directive('checkList', function() {
return {
scope: {
list: '=checkList',
value: '#'
},
link: function(scope, elem, attrs) {
var handler = function(setup) {
var checked = elem.prop('checked');
var index = scope.list.indexOf(scope.value);
if (checked && index == -1) {
if (setup) elem.prop('checked', false);
else scope.list.push(scope.value);
} else if (!checked && index != -1) {
if (setup) elem.prop('checked', true);
else scope.list.splice(index, 1);
}
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.bind('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('list', setupHandler, true);
}
};
});
now I am trying to write test cases to test my directive
describe("weeklyDirective directive", function() {
var elm, scope, httpBackend, controller;
beforeEach(module('guideApp.directives'));
beforeEach(module('/flat-ui/tpls/weekly-scheduler.html'));
beforeEach(angular.mock.inject(function($rootScope, $compile) {
compile = $compile;
scope = $rootScope;
elm = angular.element('<div weekly-directive data="data"></div>');
compile(elm)(scope);
scope.data = {
interval: 1,
weeklyDetails: ['1'],
}
scope.$digest();
}));
it("click on check box it should get added in weeklyDetails", function() {
var e = elm.find('input[id="chk_3"]');
console.log(e);
e.trigger('click');
scope.$apply();
var isolateScope = elm.isolateScope();
expect(isolateScope.data.weeklyDetails.indexOf('4') > -1).toBeTruthy();
});
});
where I am trying to test that when user click on check box its value should get added to my array weeklyDetails which in data object (passed to the weeklydirective).
its not working as exptected for me please help me to get this working.
Thanks in Adv.
I am using EasyAautocomplete on my project. I was wondering if its possible to use autocomplete from 2 different file in one single searchbox. I have tried beyond the code but it reads only one of them:
<script>
var options = {
url: "file1.json",
getValue: "name",
list: {
match: {
enabled: true
}
},
theme: "square"
};
$("#KUNDE").easyAutocomplete(options);$('div.easy-autocomplete').removeAttr('style');
var options2 = {
url: "file2.json",
getValue: "name",
list: {
match: {
enabled: true
}
},
theme: "square"
};
$("#KUNDE").easyAutocomplete(options2);$('div.easy-autocomplete').removeAttr('style');
</script>
I recommend to you that:
Get json files at first
Merge them
Use them for autocomplete
The code is like:
$.getJSON("file1.json", function (data1) {
$.getJSON("file2.json", function (data2) {
var mergedData = $.extend({}, data1, data2);
var options = {
data: mergedData,
getValue: "name",
list: {
match: {
enabled: true
}
},
theme: "square"
};
$("#KUNDE").easyAutocomplete(options); $('div.easy-autocomplete').removeAttr('style');
});
});
I'm still in the learning process with EXTJS and any help would be much appreciated.
The goal for me is to load data into a grid where one cell needs to have the value incremented every minute. From my research using the TaskRunner function is the way to go but can't figure out where to put it and how to use it. My assumption is that it needs to go into the model or the controller but I'm not sure. In my simple project I'm using the MVC architecture.
Currently my gird works as expected. It reads in a file does a date conversion that produces a minute value. It's that minute value that I need to increment.
The code below is a sample TaskRunner snippit that I'm working with, right or wrong I don't know yet.
var runner = new Ext.util.TaskRunner();
var task = runner.newTask({
run: store.each(function (item)
{
var incReq = item.get('request') + 1;
item.set('request', incReq);
}),
interval: 60000 // 1-minute interval
});
task.start();
Model:
Ext.define("myApp.model.ActionItem", {
extend : "Ext.data.Model",
fields : [
{
name: 'pri',
type: 'int'
},
{
name: 'request',
type: 'int',
defaultValue: 0,
convert : function(v, model) {
return Math.round((new Date() - new Date(v)) / 60000);
}
}
]
});
Controller:
Ext.define("myApp.controller.HomeController", {
extend: "Ext.app.Controller",
id: "HomeController",
refs: [
{ ref: "ActionItemsGrid", selector: "[xtype=actionitemgrid]" },
{ ref: "DetailsPanel", selector: "[xtype=actionitemdetails]" }
],
pri: "",
models: ["ActionItem"],
stores: ["myActionStore"],
views:
[
"home.ActionItemDetailsPanel",
"home.ActionItemGrid",
"home.HomeScreen"
],
init: function () {
this.control({
"#homescreen": {
beforerender: this.loadActionItems
},
"ActionItemsGrid": {
itemclick: this.displayDetails
}
});
},
displayDetails: function (model, record) {
this.getDetailsPanel().loadRecord(record);
},
loadActionItems: function () {
var store = Ext.getStore("myActionStore");
store.load();
this.getActionItemsGrid().reconfigure(store);
}
});
View:
Ext.define("myApp.view.home.ActionItemGrid", {
extend: "Ext.grid.Panel",
xtype: "actionitemgrid",
resizable: true,
multiSelect: false,
enableDragDrop : false,
store: null,
columns:
[
{ header: "Pri", dataIndex: "pri", sortable: false, autoSizeColumn: true},
{ header: "Req", dataIndex: "request", sortable: false, autoSizeColumn: true}
],
viewConfig:
{
listeners: {
refresh: function(dataview) {
Ext.each(dataview.panel.columns, function(column) {
if (column.autoSizeColumn === true)
column.autoSize();
})
}
}
}
});
Sample JSON File:
{
"actionitems" : [
{
"pri": 1,
"request": "2014-12-30T03:52:48.516Z"
},
{
"pri": 2,
"request": "2014-12-29T05:02:48.516Z"
}
]
}
You have error in code which creates task - you should provide function to run configuration property, not result of invocation. Try this:
var runner = new Ext.util.TaskRunner();
var task = runner.newTask({
run: function() {
store.each(function (item)
{
var incReq = item.get('request') + 1;
item.set('request', incReq);
})
},
interval: 60000 // 1-minute interval
});
task.start();
You can put that code in loadActionItems method.
I found some code on GitHub (https://github.com/RallyCommunity/TagCloud) for displaying a TagCloud in Rally. Conceptually it looks great but I cannot seem to get it to work and wondered if there were any Rally JavaScript experts out there who could take a quick look.
I modified it slightly as the URL for the Analytics was incorrect (according to the docs) and the API's were hard coded to depreciated versions so I updated those.
I'm not a JavaScript expert. When I run this it doesn't find any Tags against stories.
When I run this in Chrome debugging mode I can take the URL and execute it in my browser but it also does not come back with any Tags. It comes back with a full result response from Analytics, but has no tags and I know there are tags against stories in the selected project.
Any ideas from anyone?
<!DOCTYPE html>
<html>
<head>
<title>TagCloud</title>
<script type="text/javascript" src="/apps/2.0p/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
layout: 'border',
items: [
{
title: 'Tag Cloud',
xtype: 'panel',
itemId: 'cloud',
region: 'west',
width: '30%',
collapsible: true,
bodyStyle: 'padding:15px',
listeners: { 'afterRender': function(el) { setTimeout(function() { el.setLoading(); }, 500);}} // needs a little delay
},
{
title: '<<< Select a tag from the Tag Cloud',
xtype: 'panel',
itemId: 'grid',
region: 'center',
width: '70%',
collapsible: false
}
],
tagMap : [],
maxFont : 24, // largest desired font size
minFont : 8, // and smallest
_renderTag : function renderHandler(tagLabel) {
tagLabel.getEl().on('click',this._tagSelected, this);
},
// does the actual building of the cloud from 'tagMap'
_buildCloud: function(app, response) {
//title: '<<< Select a tag from the Tag Cloud - Building',
var i, tag;
for (i=0;i<response.Results.length;i++) {
tag = response.Results[i];
if(typeof app.tagMap[tag.ObjectID] !== "undefined") {
app.tagMap[tag.ObjectID].Name = tag._refObjectName;
}
}
if(response.StartIndex+response.PageSize < response.TotalResultCount) {
app._queryForTagNames(response.StartIndex+response.PageSize, app, app._buildCloud);
} else {
if(app.tagMap.length === 0) {
tag = new Ext.form.Label({
id: 'tagNone',
text: ' No tagged Stories found '
});
app.down('#cloud').add(tag);
} else {
var minFrequency = Number.MAX_VALUE;
var maxFrequency = Number.MIN_VALUE;
var tuples = [];
for (var x in app.tagMap) {
if (app.tagMap.hasOwnProperty(x)) {
tuples.push([x, app.tagMap[x]]);
if(app.tagMap[x].count > maxFrequency) {
maxFrequency = app.tagMap[x].count;
}
if(app.tagMap[x].count < minFrequency) {
minFrequency = app.tagMap[x].count;
}
}
}
tuples.sort(function(a,b) { a = a[1]; b = b[1]; return a.Name > b.Name ? 1 : a.Name < b.Name ? -1 : 0 ;});
for (i = 0; i < tuples.length; i++) {
var ftsize = ((tuples[i][1].count-minFrequency)*(app.maxFont-app.minFont) / (maxFrequency-minFrequency)) + app.minFont;
tag = new Ext.form.Label({
id: 'tag'+tuples[i][0],
text: ' ' + tuples[i][1].Name + ' ',
overCls: 'link',
style:"font-size: "+ftsize+"pt;",
listeners: { scope: app, render: app._renderTag }
});
app.down('#cloud').add(tag);
}
}
app.getComponent('cloud').setLoading(false);
}
},
// collects the _queryForTags responses and calls _queryForTagNames when it has them all
_buildTagMap: function(app, response) {
for (var i=0;i<response.Results.length;i++) {
var ent = response.Results[i];
for (var j=0; j < ent.Tags.length; j++) {
var tag = ent.Tags[j];
var mapent = app.tagMap[tag];
if(typeof mapent === "undefined") {
mapent = { count: 1 };
} else {
mapent.count++;
}
app.tagMap[tag] = mapent;
}
}
if(response.StartIndex+response.PageSize < response.TotalResultCount) {
app._queryForTags(response.StartIndex+response.PageSize, app, app._buildTagMap);
} else {
app._queryForTagNames(0, app, app._buildCloud);
}
},
// get a list of the tags from the Lookback API, iterating if necessary (see _buildTagMap)
_queryForTags: function(start, app, callback) {
var params = {
find: "{'Tags':{'$exists':true}, '__At':'current', '_Type':'HierarchicalRequirement', '_ProjectHierarchy':"+ this.getContext().getProject().ObjectID +" }",
fields: "['Tags']",
pagesize: 20000,
start: start
};
Ext.Ajax.request({
url: 'https://rally1.rallydev.com/analytics/v2.0/service/rally/workspace/'+ this.context.getWorkspace().ObjectID + '/artifact/snapshot/query.js',
method: 'GET',
params: params,
withCredentials: true,
success: function(response){
var text = response.responseText;
var json = Ext.JSON.decode(text);
callback(app, json);
}
});
},
// once all the tags have been collected, get a list of the tag names from the WSAPI, iterating if necessary (see _buildCloud)
_queryForTagNames: function(start, app, callback) {
Ext.Ajax.request({
url: 'https://rally1.rallydev.com/slm/webservice/1.41/tag.js',
method: 'GET',
params: { fetch: "ObjectID", pagesize: 200, "start": start},
withCredentials: true,
success: function(response){
callback(app, Ext.JSON.decode(response.responseText).QueryResult);
}
});
},
_queryForStories: function(tagOid) {
Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
autoLoad: true,
fetch: ['Rank', 'FormattedID', 'Name', 'ScheduleState'],
filters: [{
property:'Tags',
operator: '=',
value: "tag/" + tagOid
}],
sorters: [{
property: 'Rank',
direction: 'ASC'
}],
listeners: {
load: this._onDataLoaded,
scope: this
}
});
},
_tagSelected: function(app, elem) {
this.getComponent('grid').setLoading();
this._queryForStories(elem.id.substring(3)); // cheesy, id is "tag"+tagOid, we need the oid
this.tagName = elem.innerText;
},
_onDataLoaded: function(store, data) {
var records = [], rankIndex = 1;
Ext.Array.each(data, function(record) {
records.push({
Ranking: rankIndex,
FormattedID: record.get('FormattedID'),
Name: record.get('Name'),
State: record.get('ScheduleState')
});
rankIndex++;
});
var customStore = Ext.create('Rally.data.custom.Store', {
data: records,
pageSize: 25
});
if(!this.grid) {
this.grid = this.down('#grid').add({
xtype: 'rallygrid',
store: customStore,
columnCfgs: [
{ text: 'Ranking', dataIndex: 'Ranking' },
{ text: 'ID', dataIndex: 'FormattedID' },
{ text: 'Name', dataIndex: 'Name', flex: 1 },
{ text: 'State', dataIndex: 'State' }
]
});
} else {
this.grid.reconfigure(customStore);
}
this.getComponent('grid').setTitle('Stories tagged: ' + this.tagName);
this.getComponent('grid').setLoading(false);
},
launch: function() {
this._queryForTags(0, this, this._buildTagMap);
}
});
Rally.launchApp('CustomApp', {
name: 'TagCloud'
});
});
</script>
<style type="text/css">
.app {
}
.link {
color: #066792;
cursor: pointer;
} </style>
</head>
<body></body>
</html>
The code was out-of-date with respect to the most-recent LBAPI changes - specifically the use of _Type vs _TypeHierarchy and of course, the url, as you'd already discovered. Please pick up the changes and give it a whirl.