I'm trying to save an object with rhaboo in javascript. The first time after initialising it is working but when I'm trying to save it again it gives me the
rhaboo.min.js:1 Uncaught TypeError: Cannot read property 'refs' of undefined error. I pinned down the error to the line where I save the keyArray with notes.write('presentationNotes', keyArray);
How I get the error in detail:
I open my webapplication with a clean localStorage (nothing is saved) and rhaboo gets initialised. After that I navigate to a document and open the notes-div with the notes-button. I write something in the notes-area and hit the notes-submit button to save the notes with rhaboo to localStorage. I do the same for a second document. For now everything works. Both notes get saved correctly so that I have an object like this:
keyArray = {activeDoc1: ['note1', 'note2'], activeDoc2: ['note1', 'note2']}
saved in rhaboo in notes.presentationNotes. Then I reload my webapplication and rhaboo is already initialised. I navigate to the documents as before and check if I can load the saved notes. This works as expected but when I try to hit the notes-submit button again it gives me the aforementioned error. What am I doing wrong?
var notes = Rhaboo.persistent('Presentation Notes');
$(document).ready(function(event) {
var keyArray, activeDoc;
if (!notes.initialised) {
notes.write('initialised', true);
notes.write('presentationNotes', {});
console.log('Rhaboo Initialised');
keyArray = {};
} else {
console.log('Rhaboo already initialised');
keyArray = notes.presentationNotes;
console.log('notes.presentationNotes onLoad = ');
console.log(notes.presentationNotes);
}
//Notes open
$(document).on('click', '#notes-button', function() {
$('.notes-div').show();
activeDoc = $('.node.active').attr('id');
if (notes.presentationNotes[activeDoc] != null) {
//Iterate notes
$.each(notes.presentationNotes[activeDoc], function(index, value) {
$('#notes-area').append(value + '\n');
});
}
});
//Notes save
$(document).on('click', '#notes-submit', function() {
$('.notes-div').hide();
var str = $('#notes-area').val();
var array = str.split("\n");
keyArray[activeDoc] = array;
//Save notes
notes.write('presentationNotes', keyArray);
//Clear textarea
$('#notes-area').val('');
});
}
Without the HTML I haven't been able to try this, so I'm just guessing here, but I suspect your problem will go away if you stop using keyArray and activeDoc. The whole point of rhaboo is that it is not a place to store your data. It IS your data.
I see no transient data in your program, i.e., no data which you actively want to delete when the user goes away and comes back. All the data is supposed to be persistent, therefore it should all be under the Rhaboo.persistent.
That's the philosophy, but to be more specific, I think your problem is here:
keyArray[activeDoc] = array;
When I wonder what keyArray is is find:
keyArray = notes.presentationNotes;
so the earlier line actually says:
notes.presentationNotes[activeDoc] = array;
but it says on the tin that that should read:
notes.presentationNotes.write(activeDoc, array);
The upshot is that that the hooks that make rhaboo work have not been inserted into array, as notes.presentationNotes.write would have done.
When you next said:
notes.write('presentationNotes', keyArray);
it meant:
notes.write('presentationNotes', notes.presentationNotes).
which is clearly not what you meant. Rhaboo doesn't suspect that array has no hooks yet because it can see that notes.presentationNotes does have hooks.
I also forget to use write sometimes, and it really bugs me that JS offers no way to hook into the creation of a NEW key within an object X, no matter what you've done to X. Without that limitation, there'd be no need for write and it could be foolproof.
Related
I've been working with Liferay 7 for a while and needed to create a feedback form with prefilled values. I created a feedback form and a page, where it's shown and where I could add Javascript.
The user clicks on a link ("Did you find this helpful? Yes/No") and it takes you to the feedback page with the page and answer as URL parameters.
URL: {feedback-page-url/} ?pageUrl=/services&answer=Yes
Now here's where the problems began. Liferay updates it's values very confusingly and while generic document.getElementsByName(...) etc. seemed to work at first, they updated back when clicking the page. The difficult thing is to update the right values in right elements, so they won't be overrun by Liferay.
I provided an answer to my question below. Feel free to ask me anything, I'll try to help :)
Full code block in the end!
So I found out a solution to this problem. Liferay creates an instance (_com_liferay...) and uses it's values to be up to date, so we need to get a hold of it and update it's values. You can do it manually by inspecting and finding your instance, but I have an automatic code that should get it for you.
The id we are searching for is for DDMFormPortlet and the String we get this way is close to perfect. The String that document.querySelector() finds begins with p_p_id_com..., so we can use .substring to remove the unnecessary part and then add +"form" in the end to make it complete. If you find a better way to find this, please share it :)
// _com_liferay_dynamic_data_mapping_form_web_portlet_DDMFormPortlet_INSTANCE_randomkey_form
const idFinder = function() {
const idString = document.querySelector('[id*="DDMFormPortlet"]').id;
return(idString.substring(6) + "form");
}
Now that we have the correct String text, we'll find the element, that corresponds to it:
const formFieldArray = window[idFinder()];
Now if you try it just by itself, it most likely won't find anything, because it's loads slowly. I put all of this into try-catch with setTimeout() to make sure everything works as intended. Now all we need to do is collect the information from our URL and set it to the correct places.
const params = new URLSearchParams(location.search);
const formAutoFiller = function (params) {
try {
const formFieldArray = window[idFinder()];
// make sure you have the numbers according to your form!
formFieldArray.pages[0].rows[0].columns[0].fields[0].value=params.get('pageUrl');
formFieldArray.pages[0].rows[1].columns[0].fields[0].value=params.get('answer');
// ...
}
}
And finally, as the changed values update to the form after clicking an input field, we'll move the selection focus to one of the input fields after the other code is ran:
document.getElementsByClassName("ddm-field-text")[1].focus();
A bit cleanup for myself and we're done! Full Javascript here:
const params = new URLSearchParams(location.search);
const idFinder = function() {
const idString = document.querySelector('[id*="DDMFormPortlet"]').id;
return(idString.substring(6) + "form");
}
const formAutoFiller = function (params) {
try {
const formFieldRows = window[idFinder()].pages[0].rows;
formFieldRows[0].columns[0].fields[0].value=params.get('pageUrl');
formFieldRows[1].columns[0].fields[0].value=params.get('answer');
document.getElementsByClassName("ddm-field-text")[1].focus();
} catch (e) {
setTimeout(formAutoFiller, 500, params);
}
}
formAutoFiller(params);
I'm trying to learn how to use KnockOut... never have before and I've been thrown into the fire on a site already using it. Everything here works fine:
function MasterViewModel() {
var self = this;
self.Supervisors = ko.mapping.fromJS(#Html.Raw(JsonConvert.SerializeObject(Model.Supervisors)));
self.AddSupervisor = function(request) {
var request = new Supervisor({
FullName: $('#SupervisorId option:selected').text(),
SupervisorId: $('#SupervisorId option:selected').val()
});
self.Supervisors.push(request);
// do server side call here
}
self.RemoveSupervisor = function(request) {
if (request.SupervisorID() > 0)
{
self.Supervisors.remove(request);
// do server side call here
}
}
}
Well. Everything almost works fine:
The initial data from the server loads and displays perfectly
I can remove existing items (that came from the server on the original page load)
I can add new items
But, when I try to remove an item that I just added, I get this:
Uncaught TypeError: request.SupervisorID is not a function
SupervisorId is a dropdown. The AddSupervisor call is made from a button. I can show the HTML if needed. Also, although I may not need this if:
if (request.SupervisorID() > 0)
Even without it, I am going to need the ID of the supervisor that was added.
I'm guessing the server side isn't case-sensitive, and is loading data with SupervisorID. When you add a new one, you're creating it with SupervisorId (lowercase d). The server must be accepting of that. JavaScript isn't.
You need to either change the newly created users to use SupervisorID, or have the RemoveSupervisor function use SupervisorId - whichever change makes more sense in your overall structure.
So, I've got a notepad extension for Google Chrome that syncs and displays stored notes from a user. Currently my problem is as follows.
When the extension is run for the first time after download, the word 'undefined' is displayed in the editable div (id = edit_notepad). I want it to just be blank instead of displaying anything. Obviously, I know its undefined because the user hasn't been able to add any text yet.
Picture of what's displayed on initial run:
From my content script, here are my chrome.storage get and set functions (which do work):
document.body.onload = function() {
chrome.storage.sync.get("edit_notepad", function(items) {
if (!chrome.runtime.error) {
console.log(items);
document.getElementById("edit_notepad").innerHTML = items.edit_notepad;
}
});
}
document.getElementById("edit_notepad").onkeyup = function() {
var d = document.getElementById("edit_notepad").innerHTML;
chrome.storage.sync.set({ "edit_notepad" : d }, function() {
if (chrome.runtime.error) {
console.log("Runtime error.");
}
});
}
I presume I'm going to need some sort of if statement, but after hours of playing around, I'm lost as to what exactly it'd contain. The issue I've kept running into is that whatever I set the initial value of edit_notepad to, it always reverts back to "undefined" even when a user has written some notes! e.g. "This is a notes test" would revert back to "undefined" when the notepad is closed and reopened.
Well, an easy way to do this would be to specify a default value in your chrome.storage.sync.get(). Doing so would apply the default value when the key does not exist in the storage. However, given that you are replacing any contents which might already exist, the better solution would be not to set the contents when you have no value, or an invalid value, stored. The only valid value will be a string. This will prevent you from overwriting any default value supplied by the webpage when you have no value stored. Thus, an if statement something like the following should work (alternately, you could test for !== 'undefined':
document.body.onload = function() {
chrome.storage.sync.get("edit_notepad", function(items) {
if (!chrome.runtime.error) {
console.log(items);
if(typeof items.edit_notepad === 'string') {
document.getElementById("edit_notepad").innerHTML = items.edit_notepad;
}
}
});
}
Note: Storing the contents of the editable <div> on every key will result in many users running into both the MAX_WRITE_OPERATIONS_PER_MINUTE and MAX_WRITE_OPERATIONS_PER_HOUR quotas. You will need to have some other criteria for writing to storage.sync. Perhaps you could temporarily store the value in storage.local and only to storage.sync every couple of minutes.
I am debugging a javascript/html5 web app that uses a lot of memory. Occasionally I get an error message in the console window saying
"uncaught exception: out of memory".
Is there a way for me to gracefully handle this error inside the app?
Ultimately I need to re-write parts of this to prevent this from happening in the first place.
You should calclulate size of your localStorage,
window.localStorage is full
as a solution is to try to add something
var localStorageSpace = function(){
var allStrings = '';
for(var key in window.localStorage){
if(window.localStorage.hasOwnProperty(key)){
allStrings += window.localStorage[key];
}
}
return allStrings ? 3 + ((allStrings.length*16)/(8*1024)) + ' KB' : 'Empty (0 KB)';
};
var storageIsFull = function () {
var size = localStorageSpace(); // old size
// try to add data
var er;
try {
window.localStorage.setItem("test-size", "1");
} catch(er) {}
// check if data added
var isFull = (size === localStorageSpace());
window.localStorage.removeItem("test-size");
return isFull;
}
I also got the same error message recently when working on a project having lots of JS and sending Json, but the solution which I found was to update input type="submit" attribute to input type="button". I know there are limitations of using input type="button"..> and the solution looks weird, but if your application has ajax with JS,Json data, you can give it a try. Thanks.
Faced the same problem in Firefox then later I came to know I was trying to reload a HTML page even before setting up some data into local-storage inside if loop. So you need to take care of that one and also check somewhere ID is repeating or not.
But same thing was working great in Chrome. Maybe Chrome is more Intelligent.
I've built an app that is form-based. I want to enable users to partially fill out a form, and then come back to it at a later date if they can't finish it at the present. I've used iron router to create a unique URL for each form instance, so they can come back to the link. My problem is that Meteor doesn't automatically save the values in the inputs, and the form comes up blank when it is revisited/refreshes. I tried the below solution to store the data in a temporary document in a separate Mongo collection called "NewScreen", and then reference that document every time the template is (re)rendered to auto fill the form. However, I keep getting an error that the element I'm trying to reference is "undefined". The weird thing is that sometimes it works, sometimes it doesn't. I've tried setting a recursive setTimeout function, but on the times it fails, that doesn't work either. Any insight would be greatly appreciated. Or, if I'm going about this all wrong, feel free to suggest a different approach:
Screens = new Meteor.Collection('screens') //where data will ultimately be stored
Forms = new Meteor.Collection('forms') //Meteor pulls form questions from here
NewScreen = new Meteor.Collection('newscreen') //temporary storage collection
Roles = new Meteor.Collection('roles'); //displays list of metadata about screens in a dashboard
//dynamic routing for unique instance of blank form
Router.route('/forms/:_id', {
name: 'BlankForm',
data: function(){
return NewScreen.findOne({_id: this.params._id});
}
});
//onRendered function to pull data from NewScreen collection (this is where I get the error)
Template.BlankForm.onRendered(function(){
var new_screen = NewScreen.findOne({_id: window.location.href.split('/')[window.location.href.split('/').length-1]})
function do_work(){
if(typeof new_screen === 'undefined'){
console.log('waiting...');
Meteor.setTimeout(do_work, 100);
}else{
$('input')[0].value = new_screen.first;
for(i=0;i<new_screen.answers.length;i++){
$('textarea')[i].value = new_screen.answers[i];
}
}
}
do_work();
});
//onChange event that updates the NewScreen document when user updates value of input in the form
'change [id="on-change"]': function(e, tmpl){
var screen_data = [];
var name = $('input')[0].value;
for(i=0; i<$('textarea').length;i++){
screen_data.push($('textarea')[i].value);
}
Session.set("updateNewScreen", this._id);
NewScreen.update(
Session.get("updateNewScreen"),
{$set:
{
answers: screen_data,
first: name
}
});
console.log(screen_data);
}
If you get undefined that could mean findOne() did not find the newscreen with the Id that was passed in from the url. To investigate this, add an extra line like console.log(window.location.href.split('/')[window.location.href.split('/').length-1], JSON.stringify(new_screen));
This will give you both the Id from the url and the new_screen that was found.
I would recommend using Router.current().location.get().path instead of window.location.href since you use IR.
And if you're looking for two way binding in the client, have a look at Viewmodel for Meteor.