Backbone/jQuery mobile duplicate view - javascript

I've got some problems with my Backbone, RequireJS & jQuery mobile application.
When I use a form view 2 times the form submit event is fired twice.
Each new view is added to the body and the previous view will be removed. For that I use this code in my app.js file:
$(document).on("mobileinit", function () {
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$(document).on('pagehide', 'div[data-role="page"]', function (event, ui) {
$(event.currentTarget).remove();
});
})
Router.js
define([
'jquery',
'backbone',
'views/projects/ProjectsView',
'views/projects/AddProjectView'
], function($, Backbone, ProjectsView, AddProjectView) {
return Backbone.Router.extend({
routes: {
'addProject': 'addProject',
'editProject/:projectId': 'editProject',
'*actions': 'showProjects' // Default
},
addProject: function() {
new AddProjectView().render();
},
editProject: function(projectId) {
require([
"views/projects/EditProjectView",
"collections/ProjectsCollection",
"models/ProjectModel"
], function (EditProjectView, ProjectsCollection, ProjectModel) {
var projectsCollection = new ProjectsCollection();
projectsCollection.fetch();
var project = projectsCollection.get(projectId);
if (project) {
var view = new EditProjectView({model: project, projectsCollection: projectsCollection});
view.render();
}
});
},
showProjects: function() {
new ProjectsView().navigate();
}
});
});
I've uploaded my code to a directory on my website: http://rickdoorakkers.nl/np2
If you go through the following steps, you'll see the problem:
Add a project
Add a second project with a different name
Open a project by clicking on it and change the values and save it
As you can see the event of adding a project is launched and there's a project added instead of changed.
This same problem also occurs when you try to change 2 projects after each other. The first project is edited then.
Is there somebody who can tell me what I'm doing wrong?
Thanks!

Rido, your code is really hard to read for me because of the way it's mixing together a few things and not following any of the usual conventions for Backbone.
For your specific problem, I have a feeling the problem is that you binding both the Edit view and the New view to body (el: body), both respond to the event submit, and you are never clearly cleaning up the views, so I think that whenever you add a project and then edit it, the add view is still in memory, still bound to the submit event and still answering the call = new project with the new name, instead of editing.
It's 'easy' to fix in a dirty way, by adding a call to stopListening after adding, but the real problem is that you are binding to body, and mixing togethers the Backbone Router and manual hash control + other weird patterns, such as fetching the collection every 5 lines (you could just create one at the start of the App and always use it! here it's localstorage so it doesn't matter but if you ever move to a remote storage, you'll regret it... fetch() reset the collection and do a full reload!). May I suggest you maybe try to rewrite this without caring about jQuery mobile and just try to make it work with Backbone.Router + a single collection + not binding to body but instead, create the views on the fly and append them to body / remove when you are done? You'll see the bugs will be less weird and easier to track.

Related

One view for 2 or more pages (backbone.js)

I have two pages. One of them is dashboard with a lot of functionality. The second page is shared dashboard - the simple version of the first page.
The dashboard contains the view of the database (it can contain much other info, but the problem with this one). You can click on the filter button and modal window will be opened. So, simple version of the dashboard doesn't have this possibility. I'd like to add it, but I don't want to copy+past code from the full version of the dashboard because the code of this part is about two thousand lines. I'll add some primitive code example:
DashboardView = SomeAnotherView.extend({
initialize: function() {...},
events: {...} // huge objects of jQuery events,
render: function () {...},
... // 2k lines of functions for events
});
How can I use this View on another page? I tried to call a function from this view:
DashboardView.prototype.filterClicked(event);
But in this case event.curentTarget is null (it is necessary for this function), I also tried to send "this" to get the context, but it was failed.
Is there a possibility in Backbone.js to use one View for 2+ pages without any huge copy/past code?
Ideally if you have a simple version and full version of a view, you should have a "base view" (simple one) and the full version should extend the base view.
It'll look something like:
var SimpleDashbard = Backbone.view.extend({});
var Dashboard = SimpleDashbard.extend({});
In this way Dashboard will have access to the methods from SimpleDashbard.
Your situation sounds like you need to use a method from extended view in base view. Which is not a good idea. Ideally if it's shared you should move it to the base view/extract it into a utility method or service, and of course this involve re-writing this method to be reusable
If you have views that share a large amount of functionality, you could consider using the same View type, but opening it up to some configuration when instancing. For example:
var DashboardView = Backbone.View.extend({
initialize: function(options) {
this.allowFunctionX = (options && options.allowFunctionX);
this.allowFunctionY = (options && options.allowFunctionY);
},
// etc
functionX: function() {
if (!this.allowFunctionX) { return; }
// do the function...
},
functionY: function() {
if (!this.allowFunctionY) { return; }
// do the function...
},
});
Then on one page:
var firstDashView = new DashboardView({allowFunctionX: true});
and on another page:
var secondDashView = new DashboardView({allowFunctionY: true});
This may become not worth it if the functionality diverges too much (and there are likely better ways to configure than passing in a long list of booleans!). If your requirements are significantly different on your two pages, I feel like duplicating the code they both need is not a major sin.

CKEditor New Instance always unloaded

I'm using CKEditor in my Angular app and have a view that reloads my CKEditor instance every time users access a new model.
I'm using the following JS to initialize the editor:
var initEditor = function() {
$('.js-editor-wrap').html("<textarea id='editor'></textarea>");
var editor = CKEDITOR.replace('editor', {});
editor.on('loaded', function() {
console.log('editor loaded');
});
editor.on('instanceReady', function() {
console.log('instance ready')
});
}
And the following to destroy the editor:
var destroyEditor = function() {
if (CKEDITOR.instances['editor']) {
CKEDITOR.instances['editor'].destroy(true);
$('#editor').off().remove();
}
}
The first editor initialization works just as expected, but subsequent initializations create an editor instance with a status of "unloaded" that never triggers the "loaded" or "instanceReady" events. I'm not seeing any errors in the console.
Any ideas what might be causing this?
This is definitely a similar issue to the following, but different enough that I think it warrants its own question: CKEditor instance already exists
After a LOT more digging and thanks to the jsfiddle from Jey Dwork, I figured out where the issue is here. My CKEditor config file adds a couple of plugins that referenced lang files that were improperly named. For some reason, when these plugins were included together they caused the editor to not fully load during a second initialization.
Removing the lang files and reference to them in the plugin definitions resolved the issue. It's too bad that there wasn't some error that was triggered around this. All's well that ends well though.

Deleting a variable in Backbone

Iam developing a cross platform application and iam using backbone framework.
i have many views in them and i render the views as follows
For eg:
sampleFunction: function() {
var sampleObject = new window.sampleView();
sampleObject.Render();
}
Then one of my friend happen to see my code and he said, every variable created inside a function should be deleted,
sampleFunction: function() {
var sampleObject = new window.sampleView();
sampleObject.Render();
delete sampleObject;
}
I searched the whole web and couldn't find anything relevant to his theory. In web it says a variable is never deleted.
so i showed him a sample like below,
<script>
function onBodyLoad() {
var test = "i'am alive";
delete test;
alert(test);
}
</script>
<body onLoad="onBodyLoad()">
So my question is what delete operator really do? just used for deleting an object property?
What happens to the object and variables that brought up to the DOM when using an MV framework like backbone?
Does the object and variables will be cleared when a new view is loaded?
Do we need to unbind or destory view each time just before loading another view?
Regarding the delete operator, it will delete the property from the object. It wont do anything related to Freeing memory.
In backbone, whenever model is attached with some views it'll be in DOM. view.remove(), will remove the view and the attached model from DOM.
Creating a new view wont delete existing views. You can set a listener to remove the existing views when you create new views.
See this question: Destroy or remove a view in Backbone.js

Update/Insert to Meteor collection being ignored by server

I have a collection called "Materials" defined on the client and server. In one template, I can insert and update correctly to Materials. On another template, I can insert and update but when I refresh the browser, the changes are lost.
The two views are called
view_materials (inserting/updating works)
view_orders (doesn't work)
Both templates have the materials collection binded to them like so:
//Bind viewOrders to Materials collection
Template.view_order.materials = function () {
return Materials.find();
};
// Bind materialsTemplate to Materials collection
Template.view_materials.materials = function () {
return Materials.find();
};
and both are using the function below to update.
var docid = Materials.findOne({material_number: newMaterial.material_number});
console.log(docid._id);
Materials.update({_id:docid._id},{$set: {material_qty: total}});
Please note that the ID printed out to the console matches perfectly. Like I mentioned, on view_orders it updates for a moment on the client but not in another browser window nor does it persist after being reloaded from the server. On view_materials it works perfectly. Any ideas?
I also deployed an example of the bug here:
http://upexmple.meteor.com/
and added the source to github:
https://github.com/stereochromatic/update_example
The relevant code can be found in client/views/view_materials/view_materials.js
and
client/views/view_orders/view_orders.js
To duplicate the error:
Click on inventory and under Raw Materials, type A for material number and -50 for quantity. You will see it get updated correctly. Now click on create release and under raw material type, select A and -50 for quantity. You will see the correct info get printed to the console and you may also see the changes on inventory but upon refresh those changes are gone.
- show quoted text -
You need to define your Meteor Collections outside of the client directory. I usually put mine into a /lib folder, but defining collections on just the client side can cause issues.
So just move
Materials = new Meteor.Collection("materials");
and
MaterialsLog = new Meteor.Collection("materialsLog");
into a folder outside of the client/server folders.
I can't reproduce the error. I've done as you said, but the changes do not disappear for me upon refresh. I can even close the tab and reopen it, and the changes are still there.
Finally figured this out...
I removed the autopublish package with
meteor remove autopublish
and then defined my permissions (and subscriptions) in /lib/Materials.js like so:
// Declare client materials collection
Materials = new Meteor.Collection("materials");
materials = Materials
if (Meteor.isClient) {
Meteor.subscribe('materials');
}
if (Meteor.isServer) {
Meteor.publish('materials', function() {
return materials.find();
});
materials.allow({
insert: function (document) {
return true;
},
update: function () {
return true;
},
remove: function () {
return false;
}
});
}
Thanks to everyone who helped out. I realize that for a production app this is best practice anyways so this needed really to be done at some point. I originally was going to wait until the end to remove autopublish, insecure and wrap collection in publish/subscribe, allow/deny rules but this issue helped expedite that work :)
Using your code above my with file structure:
Please take note that I remove the materials = Materials
client folder:
subscriber.js
Meteor.subscribe('materials');
server folder
publisher.js
Meteor.publish('materials', function() {
return Materials.find();
});
allow.js
Materials.allow({
insert: function (document) {
return true;
},
update: function () {
return true;
},
remove: function () {
return false;
}
});
}
collection folder. this is outside from the client and the server folders
collections.js
Materials = new Meteor.Collection("materials");
let me know if its works and though this very late at least people can see it

Structuring my jquery/javascript properly

These days I find myself putting a lot of my code in the $(document).ready() which does not seem clean to me. For example, if I am creating something that will query my database via ajax and return it and append it to my list i would do something like this:
$(function(){
//Initialize my DOM elements
$MyList = $("#MyList"),
$MyButton = $("#MyButton");
//Add my Click event
$MyButton.click(function(){
$.ajax({
type: 'POST',
url: "/lists/getmylist",
contentType: "application/json",
success: function(results){
//Parse my results and append them using my favorite templating helper
var jsonResults = $.parseJSON(result);
$MyList.mustache("my-template", jsonResults);
}
});
})
});
Now I know this is a small example but it starts to get really big and messy when I have multiple click events, ajax requests etc. It all ends up going in my document ready. I know that I can possibly put all my ajax requests in an external javascript file to help make it cleaner, but is this architecture in general ok? just seems like its really messy. I have seen others use plugin architectures or init functions. I usually have this document ready at the bottom of all my pages and just throw in whatever is necessary to make my page work correctly. Is this a good way to structure my js?
I think the addition of some Model objects and general object oriented programming principals might go a long way here. If you break your your data fetching and storing out into model classes it should help a lot.
Here are some links that should get you started thinking about OO with Javascript.
Writing Object-Oriented JavaScript
Javascript Design Patterns
Javascript: prototypal inheritance
Javascript: prototypal inheritance 2
Another thing that might help out would be to break the Javascript into multiple files. One for global scripts that might be included via a header that attaches to all your pages and a script for each of your pages that requires it.
Perhaps Backbone.js ( or one of the other frameworks ) could be part of the rescue you are looking for.
I found Backbone immensely helpful organising some inherited spaghetti. Your starting point might be to transition your massive document ready into a backbone view (or multiples of)
Organise your scripts by separating out the views, collections, models into individual files then bundle and minify them together into a single file so the browser only needs to make one request instead of many.
ASP.NET MVC4 can do the bundling for you, it also works similarly on MVC3
This is just a example of simple starting point, there are more advanced techniques (eg. AMD, require.js) to reduce the script size per page, but with caching and gzip I find that the single everything script bundle is fine for a lot of cases.
As for your example, here's a possible backbone implementation
Remember to namespace out your code...
var app = app || {};
$(function ($) {
// depending on your server setup you might be able to just override the url
// and get away with what you want. Otherwise you might look into overriding
// the save/fetch or sync methods of the collection
app.MyListCollection = Backbone.Collection.extend({
url: '/lists/getmylist'
});
app.MyListView = Backbone.View.extend({
//bind the view to the existing element in the HTML.
el: '#MyList',
// your mustache template
template:$('#list-template').html(),
// hook up your event handlers to page components
//(within the context of your el)
events: {
'click #MyButton': 'onMyButtonClick'
},
//called on object creation.
initialize: function () {
//you could create a collection here or pass it into the init function
this.collection = new app.MyListCollection();
//when the collection is changes, call the render method
this.listenTo(this.collection, 'reset', this.render);
},
// render is named by convention, but its where you would "redraw" your view and apply your template
render: function () {
this.$el.html(
Mustache.render(
this.template(this.collection.toJSON())));
return this;
},
//your click handler
onMyButtonClick: function(e){
this.collection.fetch();
}
});
});
use your doc ready to spin up whatever backbone functionality you need
and use it bootstrap your javascript with any server side data that you may have.
$(function () {
// create an instance of your view
new app.MyListView();
//example bootstrap using razor
app.title = #Model.Title;
});

Categories

Resources