angularjs with soundmanager, update DOM on whileloading - angularjs

I'm new to Angularjs, and I'm trying to create a simple player using soundManager library.
The problem i'm facing is about updating the DOM, during sound loading/playing.
The soundManager Sound object exposes some dynamic properties like bytesLoaded or position and I'm trying to figure out how to bind them to the DOM.
I tried with something like
<span ng-bind="sound.bytesLoaded"></span>
where sound is an instance of the sound object attached to the root $scope, but it seems the DOM is updated only once in this way.

The problem might be that the sound.bytesLoaded is updated in non-angular world like, callback function of bytes loaded or some similar callback methods which is non-angular world.
To make the view update while updating the model value in non-angular world, you may need to call the $scope.$apply method from within the callback method of the SM2.
Some pseudo code:
sound.on('bytesLoaded', function(bytesLoaded){
// Imagine you have some similar kind of the callback in SM2,
// where you will be updating the sound.bytestLoaded property.
sound.bytesLoaded = bytesLoaded;
$scope.$apply(); // please make sure you call have this line
// where you are updating the bytesLoaded property.
})

Related

How to target a specific Ionic view with $rootScope.$broadcast

Using Ionic, calling $rootScope.$broadcast("$ionicView.enter") will trigger all child views having an event handler for the $ionicView.enter event.
How can I narrow this down to target the handler for a specific view?
You haven't provided any code samples, but it sounds like what you are describing doesn't require triggering an event with specific data, but listening to the event w/ specific data.
Perhaps this snippet can help you:
$rootScope.$on( "$ionicView.enter", function( scopes, states ) {
//here's an example of storing network error state in rootscope
//and some random important state that requires your data to be refreshed
if( $rootScope.networkErrorOccurred && states.stateName == "myImportantState" ) {
//refresh data due to network error code goes here
}
});
When calling $rootScope.$broadcast("$ionicView.enter") the "states" object is none existent and hence you cannot rely on the stateName.
But, you can add it yourself, like so:
$rootScope.$broadcast('$ionicView.enter', {stateName: 'myImportantState'});
This will make Travis example working.
The other way I can think of is to save a reference to the view's scope and emit an event directly on it.
I must say I wouldn't recommend it, but maybe that's what you're looking for.
Inside the view's controller
app.controller('someViewCtrl', function($scope) {
//Save a reference to the view's scope, using window for brevity
window.someViewScope = $scope;
}
Somewhere else in the app
window.someViewScope.$emit('$ionicView.enter')

How come Angular doesn't update with scope here?

I'm pretty new to Angular and I'm using firebase as my backend. I was hoping someone could debug this issue. When I first go to my page www.mywebsite.com/#defaultHash the data doesn't load into the DOM, it does after visiting another hash link and coming back though.
My controller is like this:
/* initialize data */
var fb = new Firebase('https://asdf.firebaseio.com/');
/* set data to automatically update on change */
fb.on('value', function(snapshot) {
var data = snapshot.val();
$scope.propertyConfiguration = data.products;
console.log($scope.propertyConfiguration);
console.log("Data retrieved");
});
/* save data on button submit */
$scope.saveConfigs = function(){
var setFBref = new Firebase('https://asdf.firebaseio.com/products');
setFBref.update($scope.propertyConfiguration);
console.log("configurations saved!");
};
I have 3 hash routes say "Shared", "Registration", and "Home" with otherwise.redirectTo set to "Shared".(They all use this controller) Here's the error that occurs: (all "links" are href="#hashWhereever")
1) Go to website.com/#Shared or just refresh. Console logs $scope.propertyConfiguration and "Data Retrieved". DOM shows nothing.
2) Click to website.com/#Registration, console logs $scope data properly, DOM is loaded correctly.
3) Click back to website.com/#Shared, console logs $scope data properly yet this time DOM loads correctly.
4) Refresh currently correctly loaded website.com/#Shared. DOM elements disappear.
Since $scope.data is correct in all the cases here, shouldn't Angular make sure the DOM reflects the model properly? Why is it that the DOM loads correctly only when I am clicking to the page from another link.
I can "fix" it by adding window.location.hash = "Shared" but it throws a huge amount of errors in the console.
FIXED:(sorta)
The function $scope.$apply() forces the view to sync with the model. I'd answer this question myself and close it but I'm still wondering why the view doesn't load correctly when I correctly assign a value to $scope. If Angular's "dirty checking" checks whenever there is a possibility the model has changed, doesn't assigning a value to $scope overqualify?
Angular has no way to know you've assigned a value to $scope.variable. There's no magic here. When you run a directive (ng-click/ng-submit) or Angular internal functions, they all call $apply() and trigger a digest (a check of the dirty flags and update routine).
A possibly safer approach than $apply would be to use $timeout. Currently, if you call a write op in Firebase, it could synchronously trigger an event listener (child_added, child_changed, value, etc). This could cause you to call $apply while still within a $apply scope. If you do this, an Error is thrown. $timeout bypasses this.
See this SO Question for a bit more on the topic of digest and $timeout.
This doc in the Angular Developer Guide covers how compile works; very great background read for any serious Angular dev.
Also, you can save yourself a good deal of energy by using the official Firebase bindings for Angular, which already take all of these implementation details into account.
Vaguely Related Note: In the not-too-distant future, Angular will be able to take advantage of Object.observe magic to handle these updates.

Unable to wrap functions assigned with events parameter in backbone model

Motivation: I want to wrap all functions within backbone model with log functionality to make it more easy to follow function calls.
I am iterating over models functions and warping them with underscores wrap().
But it looks like event function bindings from 'events' hash doesn't get wrapped as it looks like these functions are copied and stored somewhere in DOM, not sure.
Has anybody came to this problem as well or any ideas how to workaround?
Manually entering console.log() in these function is not what I am looking for.
I think you're experiencing the same problem as when people are trying to test the functionality provided with the events -hash. Spying on the event callback directly on the View doesn't work, but spying on the class prototype before instantiating works.
// This won't work
var view = new SomeView();
spyOn(view, 'onClick');
view.$el.click(); // spy isn't called
// This works
spyOn(SomeView.prototype, 'onClick');
var view = new SomeView();
view.$el.click(); // spy is called
Try iterating over the prototype of your 'class' right after creating it, because that should be where the methods called by the events -implementation should reside.

Availability of UI elements in Marionette.View

I'd just like to understand the decisions behind Backbone.Marionette's view regarding UI elements.
When instantiating a Marionette.View on an existing DOM element, like this:
view = new Marionette.ItemView({
el: "#element",
ui : {
whatever : "#whatever"
}
});
I am able to access view.$el, the jquery selector inside view.initialize, so far so good.
However, when I try to access view.ui.whatever, I only have access to the selector, ie the string "#whatever" instead of the actual $("#whatever") jquery selector.
The reason for this is because Marionette.View.bindUIElements() is only called on render and not before initialize.
I would like to know if you think this behaviour is logic and why?
I am only asking in the case of attaching of the view to an existing el, if the view is created with a template, I do understand why the binding is in render().
Attaching a view to an existing element is the exception. The normal view lifecycle involves calling render, and without doing that there would be nothing for the UI elements to bind to.
Just call this.bindUIElements() in your initialize method when you need to attach a view to an existing element.
When I am working with Marionette, I put the code that has to access the ui elements inside the onShow method. This event is fired after the dom is ready and the elements are ready to be manipulated. Inside this method, your ui.whatever will now be pointing to an element and not a string.
I think you have that problem because you have to access to the jQuery element with
this.ui.whatever
Because "this" is already a view instance.
See: http://marionettejs.com/docs/v2.4.4/marionette.itemview.html#organizing-ui-elements

Test Driving Backbone view events

I am trying to test drive a view event using Jasmine and the problem is probably best explained via code.
The view looks like:
App.testView = Backbone.View.extend({
events: { 'click .overlay': 'myEvent' },
myEvent: function(e) {
console.log('hello world')
}
The test looks something like:
describe('myEvent', function() {
it('should do something', function() {
var view = new App.testView();
view.myEvent();
// assertion will follow
});
});
The problem is that the view.myEvent method is never called (nothing logs to the console). I was trying to avoid triggering from the DOM. Has anyone had similar problems?
(Like I commented in the question, your code looks fine and should work. Your problem is not in the code you posted. If you can expand your code samples and give more info, we can take another look at it. What follows is more general advice on testing Backbone views.)
Calling the event handler function like you do is a legitimate testing strategy, but it has a couple of shortcomings.
It doesn't test that the events are wired up correctly. What you're testing is that the callback does what it's supposed to, but it doesn't test that the action is actually triggered when your user interacts with the page.
If your event handler needs to reference the event argument or the test will not work.
I prefer to test my views all the way from the event:
var view = new View().render();
view.$('.overlay').click();
expect(...).toEqual(...);
Like you said, it's generally not advisable to manipulate DOM in your tests, so this way of testing views requires that view.render does not attach anything to the DOM.
The best way to achieve this is leave the DOM manipulation to the code that's responsible for initializing the view. If you don't set an el property to the view (either in the View.extend definition or in the view constructor), Backbone will create a new, detached DOM node as view.el. This element works just like an attached node - you can manipulate its contents and trigger events on it.
So instead of...
View.extend({el: '#container'});
...or...
new View({el:'#container'});
...you should initialize your views as follows:
var view = new View();
$("#container").html(view.render().el);
Defining your views like this has multiple benefits:
Enables testing views fully without attaching them to DOM.
The views become reusable, you can create multiple instances and render them to different elements.
If your render method does some complicated DOM manipulation, it's faster to perform it on an detached node.
From a responsibility point of view you could argue that a view shouldn't know where it's placed, in the same way a model should not know what collection it should be added to. This enforces better design of view composition.
IMHO, this view rendering pattern is a general best practice, not just a testing-related special case.

Resources