I'm writing a Firebase web app (Javascript). If I write a reference to child_added like this:
dataRef.child('bids').child(auction).child(lotno).on('child_added', function(data){...}
"auction" and "lotno" are variables and will change as the app runs. I am writing the script to close each old reference (using OFF) whenever this happens, and then I open a new reference. My OFF looks like this:
dataRef.child('bids').child(auction).child(lotno).off('child_added');
First of all, am I doing this correctly? And second, is there a more universal way I can close child_added references, to avoid the possibility of one being left open accidentally?
Meaning, can I simply write Javascript that will close all child_added within 'bids'? Wondering if something like this is valid:
dataRef.child('bids').off('child_added');
Is there a wildcase that would cover all children? Would that close everything, or must each specific path through child elements be written out?
Since the path is built with two variables, and only one listener is open at any given time, I decided to solve this problem by simply storing the two values separately, in their own variables, such as:
var storedPath1;
var storedPath2;
dataRef.child('bids').child(auction).child(lotno).on('child_added', function(data){
if (storedPath1!=undefined && storedPath2!=undefined{
dataRef.child('bids').child(storedPath1).child(storedPath2).off('child_added');
}
storedPath1=auction;
storedPath2=lotno;
}
If the listener was previously established, this will close it when opening the new one.
Related
I am new to JavaScript and Odoo. Whenever I open the JavaScript files of the base modules, I see init, start, sometimes, render_value. What are these things for. Thanks.
Hello Jeremy Gillbert,
You can reffered this document Click here
Start = > Basically the Start of an application JavaScript, where the code begins to be executed, is when is called the method window.OnLoad, after the page, is fully loaded, and automatically call it method, there exist others way to call code from the page.
Init = > init is just shorthand for the initiate. Typically it is used to create a "new Object()" with return the same.
render_value => render_value means,To grab an input value with javascript and render it into a div or particular element.
Thanks
This is a little hard to describe but here goes. I'm building a page that looks something like the back end to Azure where the user chooses from a menu on the left that loads an item, call it AllUsers for now, to the right of it. From AllUsers there are options for that item and some of those might load another to the left of it, we will call that User. From User you could open more items to the right.
I have it setup nicely with a JS object something like
var AdminPage=function(){
this.con="../controllers/admin/admin_con.php";
this.pageName=null;
this.childPage=null;
this.page=null;
this.deleteItem=function(){
//Recursively deletes pages to the right
if(this.childPage){
this.childPage.delete();
this.childPage=null;
}
if(this.page){
this.page.remove();
}
return this;
}
this.add=function(pageData){
if(this.childPage){
this.deleteItem();
}
this.childPage=new AdminPage();
this.childPage.getPage(pageData);
return this;
};
this.getPage=function(pageData){
var that=this;
this.pageName=pageData.page;
$.post(this.con, pageData,
function(data,status){
if(status==='success'){
$("#rightCol").append(data);
var num=$("#rightCol").children().length;
that.page=$("#rightCol").children()[num-1];
eval(pageData.page+"=that");
}
}
);
return this;
};
}
So each time a page is added it creates a new instance of the object for that item to work with. If that item is removed it recursively removes all items to the right of it as well.
The line "eval(pageData.page+"=that");" and yes I now that eval is evil and have read all about it, creates a new variable name from the name of the item that has been added. That item say the User item then uses that variable when even it needs to call on its instance of the script. This all works mostly perfectly.
Where it fails is if the User item opens another, call it Connections that shows all the connections for that user. I then want to be able to click on a connection and have it open another User item, this works as it should how ever the problem lies when the second User item is opened and creates a new instance of the AdminPage because the same script has just loaded and has the same name it over writes the JS variable from the previous one. Then then affects several things. If you close one of them it closes both for instance and if you close the first that was loaded it should close what is to the right of it however the childPage property is now empty so that doesn't work.
So now for the question..
Is there a better way to handle creating the variables or is there an all around better way all together that will not cause this problem at all?
Can I get around using the Evil eval and make everything right with the world?
Thanks for any suggestions that you may have.
So I have found an answer to the problem but I still don't like it much.
I have generated a unique id and appended it to the end of the name of the script that I was using as the variable.
Issues with this method is it is still using eval and now I have to pass the variable round trip when getting the new item so that I can use php to inject the variable name into the page.
Not perfect and there has to be a better way.
Here is the updated add function
this.add=function(pageData){
if(this.childPage){
this.deleteChildren();
}
this.childPage=new AdminPage();
this.childPage.id=pageData.page+"_"+(Math.floor(Math.random() * (999999999 - 100000000 + 1)) + 100000000);
eval(this.childPage.id+"=this.childPage");
this.childPage.getPage(pageData);
return this;
};
So I am trying to create a project with two canvas elements, each with its own paperscript, and with buttons on the outside of each that control certain functions within both.
Under the documentation under Paperscript, it says:
Please note:
When including more than one PaperScript in a page, each script will run >in its own scope and will not see the objects and functions declared in >the others. For PaperScript to communicate with other PaperScript or >JavaScript code, see the tutorial about PaperScript Interoperability.
... which is unfortunate because that tutorial reads as follows:
Coming very soon!
I've gotten stuck very quickly in this process. I've tried putting functions in global scope, calling them from outside their canvas, and seeing them print on the wrong canvas. I've tried exporting functions via a module and it seems to run the function (?!?!). And worst, the 'paper.projects' object is an array with one(!) project in it, the first canvas.
So I'm stumped.
Anyone know how to do this?
EDIT: So apparently there is this answer but I don't see how that gets me being able to call functions in the PaperScript scope from global scope scripts.
That just seems like a script to call global functions in the PaperScope, which doesn't work for me if I'm trying to make outside buttons do things.
Clearly I'm missing something.
SECOND EDIT: I have played around with various global functions, either in window.global or just sitting by themselves with no var declaration... but what seems to happen is that when I try to call a function which I have defined, say as:
globals.makecircle = function () {
var o = new Path.Circle({
radius: 50,
center: new Point (200,200)
})
}
in the main scope, it will just as soon run in the wrong window as the correct window. Also there is an incredible delay before it runs which I can't figure out.
THIRD EDIT: For clarity.
I have firstcanvas.js attached to canvas1 in my HTML, I have secondcanvas.js attached to canvas2. Both are referenced as paperscript type, as:
<script type="text/paperscript" src="scripts/firstcanvas.js" canvas="canvas1"></script>
<script type="text/paperscript" src="scripts/secondcanvas.js" canvas="canvas2"></script>
I create window.globals object as Jurg suggests. I call it from main.js with a button, such as:
window.globals = {}
`$('document').ready($('#dfs').on('click', window.globals.makecircle))`
I add this function to globals in firstcanvas.js as above.
If I have most recently clicked on canvas2, clicking on the button with id='DFS' will cause the function to run, extremely delayed, on canvas2.
And paper.projects does not list both projects, so I can't use the activate() functions.
Okay! SOLVED!!!
Here's how to reference/activate PaperScript-created scopes from the global scope. Although there is no user-accessible array of scopes (that I know of), PaperScope.get(id) will retrieve them. For some reason I find PaperScope.get(0) already populated, and my two canvas/PaperScript elements actually referring to scopes with ids 1 and 2.
Therefore:
pscope1 = PaperScope.get(1)
pscope2 = PaperScope.get(2)
Then, in any function where I want to do something on my first canvas:
pscope1.activate()
// cool paper.js graphics stuff
pscope1.view.update()
The last line is because paper.js won't automatically update a view that the user is not interacting with.
Thanks to Jurg Lehni for the hint to use .activate().
PS Make sure that your paperscript objects are created before using PaperScope.get. I used good 'ol JQuery $('document').ready() for this...
PPS Another little hit from Jurg Lehni himself: Inside a PaperScript, this will point to the current scope. You could use that and store it in the global object.
I'm stuck with a problem since I upgraded to 0.8.0.
The Template rendered is not being fired anymore (except the first time).
I followed the recommendations as in:
https://github.com/avital/meteor-ui-new-rendered-callback/blob/master/new2/client/each.js
This didn't helped, and so I finally made this small piece of code (by modifying the new2 example).
The main difference is that the update is triggered by a Session variable change instead of a DB change.
This perfectly shows the problem, as rendered is fired only twice with this example:
client/each.js
Template.list.items = function () {
return (Session.get('items') || 'None')
};
var renderCount = 1;
var logRender = function () {
console.log("rendered #" + renderCount);
renderCount++;
};
Template.list.rendered = function () {
logRender();
};
Template.justName.rendered = function () {
logRender();
};
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
}, 1000);
client/each.html
<body>
{{> list}}
</body>
<template name="list">
{{#with items}}
{{> justName}}
{{/with}}
</template>
<template name="justName">
{{name}}
</template>
How can I do to get the Template.justName.rendered callback properly fired when content update is triggered by a Session.set?
Thanks,
I do have an instant solution for you, but it probably requires a tiny bit of re-thinking your actual code. This is by the way the same problem as here:
Meteor 0.8.0 - Failed to operate DOM in rendered callback
But the question is posed in such a different context that it makes sense to answer it twice.
So why does it not trigger the rendered callback? Because it does not re-render.
Blaze treats the whole thing of "how to react on a changed dependencies" very differently, "better" one might say: it will identify the DOM node where your "one", "two" or "three" (in your case it's the template itself) was stored in and just replace the part that has changed, which is the text content "one", "two" or "three". The DOM node itself as well as the template stay completely intact. That also means, that everything you could have been doing with this DOM node won't have to be re-done in almost every practical scenario. I.e. if you animate it, change it's text color using jQuery, the color and animation will just stay on the screen, so you won't need the rendered callback to re-do that.
In your case, the problem is easily solved by just rearanging what you want to do on "rerender":
var whatever = function(){
// whatever you want to do on data-change, in your case calling "logRender" (which needs to be renamed with Blaze, anyway..)
logRender();
}
And then the only thing you have to do is firing it whenever your data change, either manually, like this:
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
// calling the function when changing the data, knowing that it WON'T destroy the DOM node it affects
whatever();
}, 1000);
or reactively, like this:
Deps.autorun(function(){
Session.get("items"); // our dependency, just has to be there, but you can also use it
whatever(); // will be fired whenever dependency changes
});
The core idea is to eliminate the need to re-do something you did in the rendered callback, since the DOM and the identity of its objects (and all the beautiful jQuery effects) are still intact. So all that is left to re-do is something that only would depend on the particular reactive data-change, which is why there is the Deps.autorun().
In your particular example, your "logRender" function did not have any reactive dependencies, but if you add some and put it into the Deps.autorun(), it will be reliably re-run whenever the dependency changes.
As a conclusion, Meteor 0.7.x and below drove us to make the mistake of treating the "rendered" callback function as a general purpose autorun function, which is why we are running into trouble now and have to fix our apps.
As noted in the comments, this is a indeed a design change with Meteor.
Prior to Meteor 0.8, a template was a function that generated HTML. This function would be re-computed whenever any of its reactive dependencies changed, which resulted in a recreation of all the DOM nodes generated by the template (apart from any sub-templates or isolated nodes). Whenever this re-draw happened, the rendered callback was triggered.
This behavior creates quite a performance hit because it requires re-rendering of potentially a lot of HTML, including for identifiers and helpers depending on data that hadn't changed. Additionally, it made it difficult to use other libraries like jQuery to modify the DOM elements that were created, because Meteor basically had control of the entire process and the jQuery code would have to be carefully re-run each time.
Meteor 0.8 fixes this by only rendering the pieces of the DOM that have actually changed, down to the granularity of the identifiers in your template - it is much more fine-grained. As a result, the template's rendered callback is only triggered once when your template hits the page, and is never called again afterward. This solves a lot of performance issues and allows jQuery and other DOM manipulations to work seamlessly with Meteor, but also means that you won't get the automatic callback signalling when something has changed. You can, however, achieve this with helpers that use reactive variables for specific things that change.
For a more detailed listing of how Spacebars, the new Handlebars replacement, works, see https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md
See also the new documentation about the rendered callback: http://docs.meteor.com/#template_rendered
So, I was doing a lot of digging yesterday to try and figure out basically the exact same issues you are having. I am still digging, but I did come across this Devshop Talk about Integrating Other Clientside JS Libraries. In it Ted Blackman describes a package he made to trigger events when a Session variable changed. It sounds like what you need. This talk was given prior to 0.8.0 so I am not sure how the package would be effected, but it might be worth a shot.
Devshop Talk - https://www.youtube.com/watch?v=NdBPY98o6eM
Session Extras - https://atmospherejs.com/package/session-extras
Event Horizon - https://atmospherejs.com/package/event-horizon
Taking Jeff Atwood's advice, I decided to use a JavaScript library for the very basic to-do list application I'm writing. I picked the Dojo toolkit, version 1.1.1. At first, all was fine: the drag-and-drop code I wrote worked first time, you can drag tasks on-screen to change their order of precedence, and each drag-and-drop operation calls an event handler that sends an AJAX call to the server to let it know that order has been changed.
Then I went to add in the email tracking functionality. Standard stuff: new incoming emails have a unique ID number attached to their subject line, all subsequent emails about that problem can be tracked by simply leaving that ID number in the subject when you reply. So, we have a list of open tasks, each with their own ID number, and each of those tasks has a time-ordered list of associated emails. I wanted the text of those emails to be available to the user as they were looking at their list of tasks, so I made each task box a Dijit "Tree" control - top level contains the task description, branches contain email dates, and a single "leaf" off of each of those branches contains the email text.
First problem: I wanted the tree view to be fully-collapsed by default. After searching Google quite extensively, I found a number of solutions, all of which seemed to be valid for previous versions of Dojo but not the one I was using. I eventually figured out that the best solution would seem to be to have a event handler called when the Tree control had loaded that simply collapsed each branch/leaf. Unfortunately, even though the Tree control had been instantiated and its "startup" event handler called, the branches and leaves still hadn't loaded (the data was still being loaded via an AJAX call). So, I modified the system so that all email text and Tree structure is added server-side. This means the whole fully-populated Tree control is available when its startup event handler is called.
So, the startup event handler fully collapses the tree. Next, I couldn't find a "proper" way to have nice formatted text for the email leaves. I can put the email text in the leaf just fine, but any HTML gets escaped out and shows up in the web page. Cue more rummaging around Dojo's documentation (tends to be out of date, with code and examples for pre-1.0 versions) and Google. I eventually came up with the solution of getting JavaScript to go and read the SPAN element that's inside each leaf node and un-escape the escaped HTML code in it's innerHTML. I figured I'd put code to do this in with the fully-collapse-the-tree code, in the Tree control's startup event handler.
However... it turns out that the SPAN element isn't actually created until the user clicks on the expando (the little "+" symbol in a tree view you click to expand a node). Okay, fair enough - I'll add the re-formatting code to the onExpand() event handler, or whatever it's called. Which doesn't seem to exist. I've searched to documentation, I've searched Google... I'm quite possibly mis-understanding Dojo's "publish/subscribe" event handling system, but I think that mainly because there doesn't seem to be any comprehensive documentation for it anywhere (like, where do I find out what events I can subscribe to?).
So, in the end, the best solution I can come up with is to add an onClick event handler (not a "Dojo" event, but a plain JavaScript event that Dojo knows nothing about) to the expando node of each Tree branch that re-formats the HTML inside the SPAN element of each leaf. Except... when that is called, the SPAN element still doesn't exist (sometimes - other times it's been cached, just to further confuse you). Therefore, I have the event handler set up a timer that periodically calls a function that checks to see if the relevant SPAN element has turned up yet before then re-formatting it.
// An event handler called whenever a "email title" tree node is expanded.
function formatTreeNode(nodeID) {
if (dijit.byId(nodeID).getChildren().length != 0) {
clearInterval(nodeUpdateIntervalID);
messageBody = dijit.byId(nodeID).getChildren()[0].labelNode.innerHTML
if (messageBody.indexOf("<b>Message text:</b>") == -1) {
messageBody = messageBody.replace(/>/g, ">");
messageBody = messageBody.replace(/</g, "<");
messageBody = messageBody.replace(/&/g, "&");
dijit.byId(nodeID).getChildren()[0].labelNode.innerHTML = "<b>Message text:</b><div style=\"font-family:courier\">"+messageBody+"</div>";
}
}
}
// An event handler called when a tree node has been set up - we changed the default fully-expanded to fully-collapsed.
function setupTree(theTree) {
dijit.byId("tree-"+theTree).rootNode.collapse();
messageNode = dijit.byId("tree-"+theTree).rootNode.getChildren();
for (pl = 0; pl < messageNode.length; pl++) {
messageNode[pl].collapse();
messageNode[pl].expandoNode.onclick = eval("nodeUpdateIntervalID = setInterval(\"formatTreeNode('"+messageNode[pl].id+"')\",200); formatTreeNode('"+messageNode[pl].id+"');");
}
}
The above has the feel of a truly horrible hack, and I feel sure I must have taken a wrong turn somewhere early on in my thought process. Can someone please tell me:
The correct way to go about putting nicely-formatted text inside a Dojo/Dijit Tree control.
The correct way to handle Dojo events, like where I can figure out what events are available for me to subscribe to.
A better JavaScript library to use (can I do what I want to with JQuery and avoid the all-around-the-houses approach seen above?).
PS: If you're naming a software project, give thought to its name's uniqueness in Google - I'm sure searching for "Dojo" documentation in Google would be easier without all the martial arts results getting in the way.
PPS: Firefox spellchecker knows how to spell "Atwood", correcting me when I put two 'T's instead of one. Is Jeff just that famous now?
I assume that you followed the dijit.Tree and dojo.data in Dojo 1.1 tutorial which directed you to pass the data to the tree control using a data store. That had me banging my head of a brick wall for a while.
Its not really a great approach and the alternative is not really well documented. You need to create a use model instead. I have included an example below of a tree model that I created for displaying the structure of an LDAP directory.
You will find the default implementation of the model in your dojo distribution at ./dijit/_tree/model.js. The comments should help you understand the functions supported by the model.
The IDirectoryService class the code below are stubs for server-side Java POJOs generated by Direct Web Remoting (DWR). I highly recommend DWR if you going to be doing a lot of client-server interaction.
dojo.declare("LDAPDirectoryTreeModel", [ dijit.tree.model ], {
getRoot : function(onItem) {
IDirectoryService.getRoots( function(roots) {
onItem(roots[0])
});
},
mayHaveChildren : function(item) {
return true;
},
getChildren : function(parentItem, onComplete) {
IDirectoryService.getChildrenImpl(parentItem, onComplete);
},
getIdentity : function(item) {
return item.dn;
},
getLabel : function(item) {
return item.rdn;
}
});
And here is an extract from the my JSP page where I created the model and used it to populate the tree control.
<div
dojoType="LDAPDirectoryTreeModel"
jsid="treeModel"
id="treeModel">
</div>
<div
jsid="tree"
id="tree"
dojoType="dijit.Tree" model="treeModel"
labelAttr="name"
label="${directory.host}:${directory.port}">
</div>