We have a Jura plugin written by ourselves, and in it's vm template at the very end there's the following code:
AJS.$(window).ready(function(){
doSomeThing();
});
Inside of this method we are loading some server side data and initializing internal js objects. For some strange reason this specific method doSomeThing is being called twice. Moreover, vm template is also being called twice, owerwriting first template initialization state (but template may be already initialized and contain some data at this point). I don't get it why it's made this way and how to work around this. If someone faced similar thing before and knows what to deal with it - please respond. Much appreciated.
We've found a reason of such bahavior - it's Backbone. Jira creates an element using Backbone View, which calls AJS.$().ready second time during initialization. We stopped usage of this element after our investigation
Related
I'm using an external library that attaches itself to the global window object (window['lib']) once the library's javascript file is loaded by the browser. I'm trying to invoke code using this library whenever a component is loaded, however everytime I try to access the object it is undefined (due to the library not having been loaded). I have tried every lifecycle hook I can think of, however nothing appears to wait for the DOM to be fully ready. For example, I want to do something like this:
ngOnInit() {
window['lib'].doStuff(); // <-- window['lib'] is undefined
}
If I wrap it in a timeout, then it becomes available. However, this looks like code smell and do not want to approach it this way:
ngOnInit() {
setTimeout(function() {
window['lib'].doStuff(); // <-- this works
});
}
What is the best / suggested / "most angular way" to approach this problem? Thanks!
Angular Lifecycle Hook: ngOnInit()
Initialize the directive/component after Angular first displays the
data-bound properties and sets the directive/component's input
properties.
Called once, after the first ngOnChanges().
This is a common problem with Angular. Older methodologies like this one that uses a global variable on the window object will piss off the way Angular loads the application and also TypeScript (during development). The reason why you have to do window['lib'] and not window.lib is because TypeScript doesn't know anything about the types of window.lib so window['lib'] is a way to force it to work.
The other part is that depending on what type of compilation you're using (AOT vs JIT), that library you're loading may or may not be ready yet (also depends on how you're loading that script/module into the application). As Commercial Suicide mentioned, you could try some of the other Angular Lifecycle Hooks but more than likely, you're going to end up settling on setTimeout. You actually don't even need to define the timeout period or you can pass 0ms (as you've done in your code). Angular just wants you to hold off on calling that function until the DOM it's finished rendering.
I personally can't wait until all the jQuery-like libraries are converted into proper ES6 modules. The days of just throwing a script tag at the bottom of the body are long gone.
setTimeout
How to get a reference to the window object in
Angular
Relevant Thread
I'm just getting started with backbone, and I have run into a very confusing wall.
What I am doing:
Create a model instance of type A that calls fetch during initialization.
Create a model instance of type B that calls fetch during initialization.
Create a view that uses data from both.
Bind the sync event from both of the models to view.render(), and create a table once both AJAX calls have returned (render will be called twice, I know - not a big deal).
What I am expecting is that when the second sync event hits, the table is rendered using data from both.
What is actually happening is that the sync event is firing, but the model data is completely empty when the view tries to use it (I can check the model.cid and see that it is the same model that I created initially, it just contains absolutely nothing). I have logging so that I can tell which event is firing from which model, and I can see render being called twice as expected.
I can add a debug button to my page that calls the same render manually (once both AJAX calls have completed), and everything renders just fine, so I know both my AJAX calls were ultimately successful and did eventually result in a fully populated model.
What am I doing wrong? Am I fundamentally misunderstanding what an event is supposed to do here? Why would my model be completely empty after a sync event?
Dave
Apologies for the rather poor question, replete with its lack of actual code. I went through and re-wrote everything from scratch, made it work, then went back through my original code line by line to figure out why it didn't work.
Let's just say that there is a big difference between this:
this.listenTo(user, 'sync', this.render("userchange"));
and this:
this.listenTo(user, 'sync', this.render);
I need to read up on on "passing a function" vs "running a function".
Dave
Here is the code:
var view = Backbone.View.extend({
el:'#comment',
template:'',
initialize:function(){
this._getTemplate();
this.render();
},
event:{
'click #submit_btn' : 'submit'
},
_getTemplate:function(){
$.ajax({
...
});
},
render:function(){
this.$el.html(this.template);
},
submit:function(event){
alert('click');
}
});
I use ajax to get html template from server and it works well. I have no problem in loading template.
Here is the div which I wanna insert the template into.
<div id="comment">...</div>
Here is the template. I just show the simple structure.
<div>...</div>
<button id="submit_btn">submit</button>
<div>...</div>
Can someone help me to solve it?
The event not firing because when the event is attempted to bind to the DOM element (i.e. at the time of the View construction), the DOM element does not exist. From the Backbone docs:
Backbone will automatically attach the event listeners at instantiation time, right before invoking initialize.
Since the #submit_btn DOM element matcher returns nothing, no event is bound.
Your technique of fetching the template within the initialize() method will be problematic. In most OO-based development patterns, the Constructor of the object should not do a lot of work, and the returned instance should be fully initialized when the Constructor completes. Backbone Views are no different in this respect. Your Constructor as written here attempts to do quite a bit of work, but there is no benefit to fetching the template as a part of the object creation here. In fact there is some harm too: what if you end up creating more than one instance of your View? You would end up fetching the template more than once, even though the contents would be the exact same.
Forcing an XHR of any kind to be synchronous is also going to be problematic. During a synchronous request the browser's main thread is busy waiting for the response. In this example if the template ever takes longer than a few hundred milliseconds to arrive, your application will appear "frozen" to the user. This provides a very poor user experience. Asynchronous code may feel like it's more complex, but if you are building a JS-based application of even minimal complexity you will be required to deal with it. Since Javascript is (mostly) single-threaded, asynchronous execution becomes an essential tool of any application.
A better solution is to either embed the template into the hosting HTML page content, or use a JS modularization technique (RequireJS, CommonJS, others) to effectively package the template up with the module that needs it.
It looks like the event string is set up correctly and the render function is doing the right thing. The one issue I see is that the 'initialized' function should be 'initialize' as per the Backbone View Extend documentation.
Reference: http://backbonejs.org/#View-extend
I am learning AngularJS and having a weird problem whereby sometimes if I make a change in my JS files, the change doesn't apply.
I can see the GET request to the file through the console however it still contains old content. I can even remove everything inside of the file and the application still shows old content. It's only when I delete the file that it recognizes that something is wrong.
Is there some sort of caching going on that I need to know about?
I am using Laravel 5.1 however at this point, Laravel is really only handling the routing at this point.
Eventually, the changes come through. Am I going crazy or is this one of the gotcha's I should know about with AngulasJS?
Try:
$scope.$apply()
The way angularjs works is when one of the binded fields (ng-model) is changed in the browser, angularjs updateds the data model in the scope (controller or directive).
If you have code with callbacks when the new data is to be assigned to the data model, you'll need to run $scope.$apply(). This statement tells anguarljs to rebind the data model to the view (html).
Depends on where you have the $apply statement, you might want to test for $scope.$$phase because if the digest/apply is already in process and you run $apply, you'll get a Javascript run time error.
I am an Angular newbie and I'm stuck. I have some code on jsbin.com --->(http://jsbin.com/ratageza/7/edit?html,js,output)
It's not pretty but it's showing essentially what I am doing.
In my real code I am using $http.get calls a RESTful backend to load data from a database, and $http.post calls to add to the database. All works fine as far as the database fetches and updates. But after I create a new object using the "Create" form, if I click "List All" my tournaments object is not updated.
I'm confused about whether I would need $apply after the $http.post call? I've tried using it but am either using it wrong or that's not my problem here. I've looked all around stackoverflow and can't seem to find the answer to my problem.
If anybody can see what I'm doing wrong I would really appreciate the help.
Thanks!
Check out the slight changes I made here: http://jsbin.com/ratageza/8/edit?html,js,output
The change is how you define your controller usage. You use TournamentController in two different places which actually gives you two instances of that controller with two separate, isolated scopes. Your List All view was bound to one instance while the Create view was bound to a completely different instance.
Also, your question about $apply. As a general rule, you should very seldom have to use $apply and you should NEVER use it in a controller as it's not safe to use there. Directives are really the only safe place to use $apply and even there it should only be used if data is being modified outside the scope of angular. The order in which things are processed, you find that if you use $scope.$apply() in a controller, you will frequently get exceptions about already being in a digest cycle.
http://jsbin.com/ratageza/10/edit this works.
It's not a $apply or $diget issue. Because you put TournamentController into to part of views. One is create and the other one is to show. That makes a different copy of $scope.tournament;
So I add a div to wrapper the Form and the table. Put the controller to it. Remove the form and table controller.
Hope this can solve your problem. :)