Why doesnt an ember observer fire on arrays? - javascript

I am trying to listen to specific property on every element in an array and get a result from that. However, updates dont appear to happen properly.
var emptyEmberObjectClass = Ember.Object.extend({});
var container = Ember.Object.extend({
data: Ember.A([
emptyEmberObjectClass.create({yo:1}),
emptyEmberObjectClass.create({yo:2}),
emptyEmberObjectClass.create({yo:3})
]),
computedData: Ember.computed('data.#each.yo', function(){
var sum = 0;
this.get('data').forEach(function(data){
sum = sum + data.yo;
});
return sum;
}),
test: Ember.observer('computedData', function(){
Ember.$('#a').html('woohoO!');
})
}).create();
var existingItem = container.get('data');
existingItem.objectAt(0).set('yo', 50);
http://jsfiddle.net/stb0nr2y/1/
As you can see, the text field still says 'start' and doesnt get updated to 'woohoO!'.
Any help will be greatly appreciated.

JavaScript:
var emptyEmberObjectClass = Ember.Object.extend({});
var container = Ember.Object.extend({
data: Ember.A([
emptyEmberObjectClass.create({yo:1}),
emptyEmberObjectClass.create({yo:2}),
emptyEmberObjectClass.create({yo:3})
]),
computedData: Ember.computed('data.#each.yo', function(){
var sum = 0;
this.get('data').forEach(function(data){
sum += data.get('yo');
});
return sum;
}),
testObs: Ember.on('init', Ember.observer('computedData', function() {
Ember.$('#a').html('woohoO! cd: `' + this.get('computedData') + '`');
}))
}).create();
var existingItem = container.get('data');
var target = existingItem.objectAt(0);
target.set('yo', 50);
target.set('yo', 100);
Updates DOM correctly with:
woohoO! cd: 55
and then:
woohoO! cd: 105
Please note that I'm using Ember v1.13.10:
<script src="http://builds.emberjs.com/tags/v1.13.10/ember.min.js"></script>
Working demo.

Related

dat.gui change message value on slider change

I have a dat.gui user interface in which I want to run some math calculations using the value of one slider input (numberOne), and then show the result in a message output (resultOne).
I can't figure out how to get the calculation result into the dat.gui message field.
import * as Calc from './components/Calc.js';
function init() {
let groupA = {
valA1: 0,
valA2: 10
};
let groupB = {
valB1: 3,
valB2: 5.6
};
let calc = Calc.SomeCalculations(groupA, groupB); // Invokes a function in another JS file.
const controller = new function() {
this.numberOne = 0;
this.resultOne = calc.resultOne;
}();
const gui = new GUI( {width: 300 });
const f1 = gui.addFolder('My inputs');
f1.add(controller, 'numberOne', 0, 100).onChange( function() {
// What goes here?
} );
f1.open();
const f2 = gui.addFolder('My results');
f2.add(controller, 'resultOne');
f2.open();
gui.open();
}
Nevermind, I just realized that I need to use .setValue()
const gui = new GUI( {width: 300 });
const f1 = gui.addFolder('My inputs');
var gui_numberOne = f1.add(controller, 'numberOne', 0, 100);
f1.open();
const f2 = gui.addFolder('My results');
var gui_resultOne = f2.add(controller, 'resultOne');
gui_numberOne.onChange( function() {
gui_resultOne.setValue( Calc.SomeCalculations(groupA, groupB).myResult );
} );
f2.open();
gui.open();

How to change slider value on dat.GUI and how to reset dat.GUI

1) I'm working on a dat.GUI application on which I have 2 sliders. I want to reset one when the other is changed. For example :
var FizzyText = function() {
this.slider1 = 0;
this.slider2 = 0;
};
var gui = new dat.GUI();
var text = new FizzyText();
var slider1 = gui.add(text, 'slider1', 0, 5);
var slider2 = gui.add(text, 'slider2', 0, 5);
slider1.onChange(function(value){
console.log(value);
text.slider2 = 0; // this doesn't work
});
slider2.onChange(function(value){
console.log(value);
text.slider1 = 0; // this doesn't work
});
This is just an example but it is very important that the slider is reseted or set to its default value (in FizzyText).
The example above comes from https://workshop.chromeexperiments.com/examples/gui/#9--Updating-the-Display-Automatically where I can't automatically update the slider
2) I want to add a reset button in which all sliders will be reseted. But with the previous answer I'd be able to reset all values
I found the answer :
gui.__controllers is and array of controllers. So I just added something like that :
var FizzyText = function () {
this.slider1 = 0;
this.slider2 = 0;
};
var gui = new dat.GUI();
var text = new FizzyText();
var slider1 = gui.add(text, 'slider1', 0, 5);
var slider2 = gui.add(text, 'slider2', 0, 5);
/* Here is the update */
var resetSliders = function (name) {
for (var i = 0; i < gui.__controllers.length; i++) {
if (!gui.__controllers.property == name)
gui.__controllers[i].setValue(0);
}
};
slider1.onChange(function (value) {
console.log(value);
resetSliders('slider1');
});
slider2.onChange(function (value) {
console.log(value);
resetSliders('slider2');
});
It's best to reset dat.GUI's values by using the controller's .initialValue instead of hard coding it, so the following would be preferable: gui.__controllers[i].setValue(gui.__controllers[i]);
You can reset all of a GUI's controllers by using gui.__controllers.forEach(controller => controller.setValue(controller.initialValue));

JavaScript, Trying to get buttons to move on click

/*
var young_link = {
power: 30,
cpower: 20,
hp: 3,
image: "../images/young_link.jpg",
};
var young_zelda = {
power: 30,
cpower: 20,
hp: 3,
}
var impa = {
power: 30,
cpower: 20,
hp: 3,
}
var hey = {
power: 30,
cpower: 20,
hp: 3,
}
//$("#test").html(young_link);
console.log(young_link);*/
$(document).ready(function() {
var hero_image = new Array();
hero_image[0] = new Image();
hero_image[0].src = 'assets/images/link.png';
hero_image[0].id = 'image';
hero_image[1] = new Image();
hero_image[1].src = 'assets/images/bongo.png';
hero_image[1].id = 'image';
hero_image[2] = new Image();
hero_image[2].src = 'assets/images/gandondorf.jpg';
hero_image[2].id = 'image';
hero_image[3] = new Image();
hero_image[3].src = 'assets/images/queen.png';
hero_image[3].id = 'image';
//var test = "<img src= '../images/young_link.jpg'>";
//var young_hero = ["young_link","young_zelda","impa", "malon"];
var young_hero = ["Link", "Bongo Bongo","Gandondorf","Queen Gohma"];
var health = [100, 70, 120, 50];
for (var i = 0; i < young_hero.length; i++) {
var hero_btns = $("<buttons>");
hero_btns.addClass("hero hero_button");
hero_btns.attr({"data-name":young_hero[i],"data-health":health[i],"data-image":hero_image[i]});
hero_btns.text(young_hero[i]);
hero_btns.append(hero_image[i]);
hero_btns.append(health[i]);
$("#buttons").append(hero_btns);
}
$(".hero_button").on("click" , function() {
var battle_ground = $("<div>");
battle_ground.addClass("hero hero_button");
battle_ground.text($(this).data("data-name"));
$("#battle").append(battle_ground);
});
});
The for loop is working and appending the buttons on the screen. But in $(".hero_button").on("click" , function() it is just putting a empty box on the page with a click. So, it is not taking the data that is attached to the button.
Sam answered your question correctly and rightly deserves the accepted answer. But I wanted to give you an insight into how you can do this in a cleaner way, without lots of arrays which must line up. Also without using jQuery at all. Below you can see a more object oriented way to do this.
You can see it in action in this jsFiddle
// Now we have an object which represents a hero. No need to duplicate loads of code.
function Hero(heroData) {
this.name = heroData.name;
this.health = heroData.health;
this.setImage = function() {
this.image = new Image();
this.image.src = heroData.imageSrc;
this.image.id = heroData.imageId;
}
this.createHeroButton = function() {
this.createButtonElement();
this.addButtonToPage();
this.attachButtonEvents();
}
this.createButtonElement = function() {
var heroButton = document.createElement('button');
heroButton.classList.add('hero,hero_button');
heroButton.setAttribute('name', this.name);
heroButton.setAttribute('health', this.health);
heroButton.appendChild(this.image);
this.button = heroButton;
}
this.attachButtonEvents = function() {
this.button.addEventListener('click', this.addButtonToPage.bind(this));
}
this.addButtonToPage = function() {
var container = document.getElementById('container');
container.appendChild(this.button);
}
this.takeDamage = function(damageValue) {
this.health -= damageValue;
this.button.setAttribute('health', this.health);
}
this.setImage();
}
// So here we create a Hero instance, in this case Link, we can use now describe links attributes, image, name, health...
var link = new Hero({
name: 'Link',
health: 100,
imageSrc: 'http://orig12.deviantart.net/8bb7/f/2011/276/4/e/four_swords_link_avatar_by_the_missinglink-d4bq8qn.png',
imageId: 'link-image'
});
var mario = new Hero({
name: 'Mario',
health: 100,
imageSrc: 'http://rs568.pbsrc.com/albums/ss123/stvan000/thumb-super-mario-bros-8bit-Mario.jpg~c200',
imageId: 'mario-image'
});
// Now we can easily make a button and add it to the page
link.createHeroButton();
mario.createHeroButton();
// Lets try decreasing the health on mario
mario.takeDamage(10);
// Because we have an object reference which handles all of our heros state we can decrease his health and update the buttons data without much trouble.
A couple of changes to get the data set and read correctly:
make button tags instead of buttons
use .attr() instead of .data() to get the attributes
See comments inline in the code below.
Also, instead of adding an attribute for the Image object of each item (which will add an attribute like data-image="[Object object]") just add an integer corresponding to the iterator index and use that to reference into the hero_image array when you need to get the corresponding image.
Additionally, you can use Array.forEach() to iterate over the items in the heroes array with a callback function. That way you don't have to worry about updating the iterator variable (i in this case) and indexing into the array. You should take a look at this functional programming guide which has some good exercises.
$(document).ready(function() {
var hero_image = new Array();
hero_image[0] = new Image();
hero_image[0].src = 'assets/images/link.png';
hero_image[0].id = 'image';
hero_image[1] = new Image();
hero_image[1].src = 'assets/images/bongo.png';
hero_image[1].id = 'image';
hero_image[2] = new Image();
hero_image[2].src = 'assets/images/gandondorf.jpg';
hero_image[2].id = 'image';
hero_image[3] = new Image();
hero_image[3].src = 'assets/images/queen.png';
hero_image[3].id = 'image';
var young_heroes = ["Link", "Bongo Bongo", "Gandondorf", "Queen Gohma"];
var health = [100, 70, 120, 50];
young_heroes.forEach(function(young_hero,i) {
var hero_btns = $("<button>");
hero_btns.addClass("hero hero_button");
hero_btns.attr({
"data-name": young_hero,
"data-health": health[i],
//instead of adding an attribute for the image object, just add an index
"data-index": i
});
hero_btns.text(young_hero);
hero_btns.append(hero_image[i]);
hero_btns.append(health[i]);
$("#buttons").append(hero_btns);
});
$(".hero_button").on("click", function() {
var battle_ground = $("<div>");
battle_ground.addClass("hero hero_button");
//use .attr() here instead of .data()
battle_ground.text($(this).attr("data-name"));
/** My additions -
* I am not sure exactly how this should be done
* so adjust accordingly
**/
//additionally, you can add attributes to the new battle_ground item
battle_ground.attr('data-health',$(this).attr("data-health"));
battle_ground.append(hero_image[$(this).attr("data-index")]);
battle_ground.append($(this).attr("data-health"));
/** End my additions **/
$("#battle").append(battle_ground);
});
});
#battle div {
border: 1px solid #555;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="buttons"></div>
Battle ground:
<div id="battle"></div>

Javascript for loop doesn't work (adding numbers to a total)

I am using Jasmine for JS testing, and unfortunately I can't get the following test to pass.
it('should know the total game score', function() {
frame1 = new Frame;
frame2 = new Frame;
game = new Game;
frame1.score(3, 4);
frame2.score(5, 5);
expect(game.totalScore()).toEqual(17)
});
The error message I get is as follows: Error: Expected 0 to equal 17.
The code is as follows:
function Game() {
this.scorecard = []
};
Game.prototype.add = function(frame) {
this.scorecard.push(frame)
};
// Why is this not working!!???
Game.prototype.totalScore = function() {
total = 0;
for(i = 0; i < this.scorecard.length; i++)
{
total +=this.scorecard[i].rollOne + this.scorecard[i].rollTwo;
}
return total;
};
function Frame() {};
Frame.prototype.score = function(first_roll, second_roll) {
this.rollOne = first_roll;
this.rollTwo = second_roll;
return this
};
Frame.prototype.isStrike = function() {
return (this.rollOne === 10);
};
Frame.prototype.isSpare = function() {
return (this.rollOne + this.rollTwo === 10) && (this.rollOne !== 10)
};
Adding the numbers together manually seems to work e.g. total = game.scorecard[0].rollOne + this.scorecard[0].rollTwo , but the for loop (even though it looks correct) doesn't seem to work. Any help would be greatly appreciated :)
I am not pretty sure, but it seems that you are not calling the "Add" method, so no data is added to the scorecard.
You have to add the Frames to your game i guess
it('should know the total game score', function () {
frame1 = new Frame;
frame2 = new Frame;
game = new Game;
// those lines are missing
game.add(frame1);
game.add(frame2);
frame1.score(3, 4);
frame2.score(5, 5);
expect(17).toEqual(game.totalScore())
});
otherwise, the scorecard-array is empty and the total score is therefore equal to 0.
missing (so no data is added to the scorecard.)
game.Add(frame1);
game.Add(frame2);

AngularJS element directive with ng-init runs before view renders

I am attempting to loop through an array and create multiple instances of a custom directive that creates different graphs based on some variables on the rootScope. Everything works fine except when I try to place those in a view and call ng-init to a method on the scope and pass it arguments.
What I am finding is that ng-init seems to run before anything (and I think ng-init is the wrong approach), which causes errors because the variables being set in the method aren't set yet when the ng-init runs in the view.
When I first load the index view, then go into this view, all is well, but when I try to load this view first or reload it, I am getting the errors. The ng-init is trying to call the chart() method before anything else runs.
On the index view, I have this chart in a modal that gets called onclick, so ng-init is not needed, therefore it works great.
I am a little stuck and what I need is advice on the "right" or better way to accomplish this.
On the detail view, I need to loop across that array to get four charts based on four different objects of data.
The data is a static file for now, as this is a prototype.
My code is essentially this:
View:
<div id="chart_list">
<div class="chart" ng-repeat="val in ['abc', 'def', 'ghi', 'jkl']" ng-init="chart('line', val, itemId, val)">
<h3>{{val | uppercase}}</h3>
<chart/>
</div>
</div>
AppController method:
// get data set up for chart consumption
$scope.chart = function(chart, kpi, socId, chartId) {
$rootScope.visualize = {};
$rootScope.visualize.chart = chart;
$rootScope.visualize.chartId = chartId;
$rootScope.visualize.val = val;
$rootScope.visualize.item = $rootScope.itemList[itemId];
$rootScope.visualize.valName = $rootScope.visualize.item[val].name;
};
DetailView controller:
app.controller('ItemDetailController', function($scope, $rootScope, $routeParams, ItemList) {
var itemId = $scope.itemId = $routeParams.itemId;
ItemList.get({}, function(res) {
$rootScope.itemList = res.data;
$scope.item = $rootScope.itemList[itemId];
});
});
Service:
app.factory('ItemList', function($resource){
return $resource("/api/item-list.json");
});
Directive:
app.directive('chart', function() {
return {
restrict: 'E',
link: function(scope, element, attrs){
if ($(window).width() <= 1200){
var width = 300;
} else {
var width = 450;
}
var visualize = scope.visualize;
var data = visualize.soc[visualize.val].data;
var numTicks = Object.keys(data).length;
element.append('<div class="chart_container"><div id="y_axis' + visualize.chartId +'" class="y_axis"></div><div id="chart' + visualize.chartId + '" class="chart"></div><div id="x_axis' + visualize.chartId + '" class="x_axis"></div></div>');
element.append('<div id="legend_container' + visualize.chartId +'" class="legend_container"><div id="smoother' + visualize.chartId +'" title="Smoothing"></div><div id="legend' + visualize.chartId +'"></div></div>');
var valSeries = [];
var valSeries2 = [];
var valSeries3 = [];
var valMap = {};
var i = 0;
Object.keys(data).forEach(function(propertyName) {
var value = data[propertyName];
var val2 = (Math.random() * (102 - 87) + 86) / 100;
var val3 = (Math.random() * (95 - 70) + 69) / 100;
valSeries.push({x: i, y: data[propertyName].amount});
valSeries2.push({x: i, y: data[propertyName].amount * val2});
valSeries3.push({x: i, y: data[propertyName].amount * val3});
valMap[i] = data[propertyName].name;
i++;
});
var graph = new Rickshaw.Graph({
element: document.querySelector('#chart' + visualize.chartId),
width: width,
height: 150,
renderer: visualize.chart,
stroke: true,
series: [
{
data: valSeries3,
color: '#F0AD4E',
name: 'Three years ago',
},
{
data: valSeries2,
color: '#5BC0DE',
name: 'Two years ago',
},
{
data: valSeries,
color: '#5CB85C',
name: 'Past year'
}
]
});
var format = function(n) {
var map = valMap;
return map[n];
}
var x_ticks = new Rickshaw.Graph.Axis.X({
graph: graph,
width: width,
orientation: 'bottom',
element: document.getElementById('x_axis' + visualize.chartId),
pixelsPerTick: width/numTicks,
tickFormat: format
});
var y_axis = new Rickshaw.Graph.Axis.Y({
graph: graph,
orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis' + visualize.chartId),
});
graph.render();
var hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
formatter: function(series, x, y) {
var content = app.lib[visualize.dataFormat](parseInt(y)) + "<br>";
return content;
}
});
var legend = new Rickshaw.Graph.Legend( {
graph: graph,
element: document.getElementById('legend' + visualize.chartId)
} );
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
graph: graph,
legend: legend
} );
}
};
});
Edit:
I fixed this by removing the chart method altogether and replacing ng-init with attributes. Something like this:
<chart data-attrone="somedata" data-attrtwo="some other data" />
Then the attrs are available in the directive with attrs.attrone, etc.
Attribute names must be lower-case.
Hope this helps someone in the future.
I fixed this by removing the chart method altogether and replacing ng-init with attributes. Something like this:
<chart data-attrone="somedata" data-attrtwo="some other data" />
Then the attrs are available in the directive with attrs.attrone, etc.
Attribute names must be lower-case.
Hope this helps someone in the future.

Categories

Resources