Update/Insert to Meteor collection being ignored by server - javascript

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

Related

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.

the best pattern to keep references to global collections in Backbone

I'm developing an application in Marionette, but the topic refers to raw Backbone as well.
In my app, I've got many collections. Among them, there are 3 important ones, that are used all over the application (e.g. user data, such as names, which is displayed in most of views). The main question is: what is the best way (a good pattern to follow) to keep references to collections in Backbone/Marionette? I came up with few ideas below:
should I attach them as attributes of the Application object? If so, I'd have to pass the reference to Application object to all views, then views to their subviews, subviews to subsubviews, etc. But this seems a lame and ugly design.
instead, I could pass each collection separately, but this seems even worse solution, because I can't predict which (sub)view will ever need a collection. Keeping those nested references in order would be much more difficult than passing the Application object which I can pass once, always.
the is also a possibility to import the Application as a singleton. I'm using requireJS, most of the modules return constructors now (constructors of views, models and collections). Now the app module returns Application constructor, but instead, it could return the Application object. Then if a view requires to display some data from the collections, it could just require the app module and that's all.
finally, basing on the previous point, I thought I could create a plain map of collections and make it a singleton just as before. This is only to disable all views to have access to Application object, which still seems a bad pattern.
Please, suggest what you think is the best (commentson points above are welcome as well). I just need a good design pattern here. As far as I know, Marionette docs doesn't suggest anything here.
I follow the suggestions made in David Sulc's book Backbone.Marionette.js: A Gentle Introduction. (also the next book on goes into how to then sturcutre the same project with require https://leanpub.com/structuring-backbone-with-requirejs-and-marionette)The code examples he uses are available on github so you could look at the final example which to get an idea of what he does if you didn;t want to buy the book but i really recommend it as it really helped me with how to structure my projects.
To start I have setup an Entities module. The structure of the files also follows this I have an Entities folder which has separate entities.
Each Entity file concerns all actions and stucture of that particular entity. I like this approach as I when i want to edit an entities strcuture or method of getting data from the server i only need to go to one place to make this change.
Interactions with entity are handled through marionettes req/res system. In this way you can expose a handler to the rest of your app but they don;t need to be concerned with how that req gets handled as long as it brings back the required response.
Here is an example of one of my entities to show what i mean - My app needs a collection called positions at various stages so this is something that is loaded early in the app then is available through-out it's life-cycle.
//i am using require so i define my app to use
define(["app", ], function(MyApp) {
//All of this will be added to the Entities module so if i want direct access
//i can go Myapp.Entities.whateverEntityIhaveDeclared
MyApp.module("Entities", function(Entities, MyApp, Backbone, Marionette, $, _) {
//model (nothing new here)
Entities.Position = Backbone.Model.extend({
urlRoot: "api/positions",
defaults: {
name: "",
}
});
//collection again nothing new here
Entities.PositionCollection = Backbone.Collection.extend({
url: "api/positions",
model: Entities.Position,
comparator: "name"
});
//an init function to attach a position collection onto Entities module so it can be available throughout my app
var initializePositions = function() {
if (Entities.positions === undefined) {
Entities.positions = new Entities.PositionCollection();
}
};
//
var API = {
//returns a jquery deferred promise so that this request can easily be handled async
loadPositionEntitiesRemote: function() {
//init positions make's sure i have the collectoin avaliable if it
//has not yet been defined
initializePositions();
//setup defer object
var defer = $.Deferred();
//I actually use a custom sever object here when dealing
//with ajax requests
//but because my app always go through this API i can
//easily swap out how the collection is retrieved.
// Here is an example using backbones fetch
Entities.positions.fetch({
success: function() {
defer.resolve();
},
error: function(data) {
defer.reject(data);
}
});
//setup promise to return
var promise = defer.promise();
return promise;
},
//get the positions collection from here i could
//directly access the attributes or add to the collection
refrencePositionEntities: function() {
initializePositions();
return Entities.positions;
},
//get a position from the collection based on id
//
getPositionEntity: function(positionId) {
initializePositions();
return Entities.positions.get(positionId);
}
};
//setup handlers for the app to use
MyApp.reqres.setHandler("position:entities:remote", function() {
return API.loadPositionEntitiesRemote();
});
MyApp.reqres.setHandler("position:entities:refrence", function() {
return API.refrencePositionEntities();
});
MyApp.reqres.setHandler("position:entity", function(id) {
return API.getPositionEntity(id);
});
MyApp.reqres.setHandler("position:entity:new", function(position) {
return new Entities.Position(position);
});
});
return;
});
now to use this in my app here is an example of how it can now be used
someFunction: function(){
//so early in the app i will get the positions
var positionPromise = MyApp.request("position:entities:remote");
$.when(positionPromise).done(function() {
//do what ever as data has been loaded
}).fail(function(data){
//something failed so handle here might through up an error page but up to you
}).always(function(){
//something to always do no matter if fail or sucess
});
}
anotherFunction: function(){
//later in the app in another controller i might to get the collection
// I could also get it through MyApp.Entities.positions but i rather use the
// API set up so if i ever decided i want to add so checks or something in
// when retrieving the collection its super easy
var positionsCollection = MyApp.request("position:entities:refrence");
}
Not sure if i've done a great job explaining this but if you are looking for a ideas on good design in marionette check out the book as it explains this a lot better than i have just done

Meteor strange behaviour

I'm testing some routines that Meteor has and I would like to know if this is it's normal activity or I have something wrong...
I will describe step by step:
I publish my collection in order to have only what I want
server/publish.js
Meteor.publish('decisions', function (decisionCursor) {
return Decisions.find({ active: true }, { limit: 20, skip: decisionCursor });
});
Meteor.publish('decisionsToModerate', function (decisionCursor) {
return Decisions.find({ active: false }, { sort: { createdAt: -1 }, limit: 1, skip: decisionCursor });
});
I subscribe my client to both collection publications
client/client.js
Meteor.startup(function () {
SimpleSchema.debug = true;
Session.setDefault('decisionCursor', 0);
Deps.autorun(function () {
Meteor.subscribe("decisions", Number(Session.get('decisionCursor')));
Meteor.subscribe("decisionsToModerate", Number(Session.get('decisionCursor')));
});
});
I set up functions to retrieve both collections in order to prevent calling a query everytime...
client/lib/environment.js
activeDecisions = function() {
var decisions = Decisions.find({active: true});
console.log(decisions.fetch().length); // PROBLEM HERE
return decisions;
};
moderateDecisions = function() {
return Decisions.find({active: false});
};
I create my view stuff
client/views/home/home.js
Template.home.activeDecisions = function() {
var decisions = activeDecisions();
return decisions;
};
As you can see, on client/lib/environment.js I have added a line for you to see where I see the problem...
When I go to http://localhost:3000/ iron-routes loads
this.route('home', {
path: '/',
layoutTemplate: 'masterLayout'
});
If I got o Chrome Console, as I wrote on client/lib/environment.js it should return a line with a number of Decisions documents, in this case I only have 3 active Decisions, but Chrome outputs this:
0 environment.js?9868bbbef2024c202fd33213ed060f067dadbe75:3
3 environment.js?9868bbbef2024c202fd33213ed060f067dadbe75:3
3 environment.js?9868bbbef2024c202fd33213ed060f067dadbe75:3
Three lines, first one tells me that I have 0 documents (what? I have THREE active documents) next two lines tells me exactly what I wanted to know, that I have three documents.
I want this number because I want to set it in a session variable that will update every time that query is called because if I set it in other place (let's say for example Template.home.rendered) I will make TWO querys and that will be slower.
So the problem I see is that I don't know why Meteor writes three times into console if I told it to write it only once, when query is parsed to a variable... If I set session it will be 0, then 3 and then 3... That may cause some bugs?
Template helpers form a reactive context - if reactive variables inside of them get updated, the helper will run again. So whenever the cursor returned by activeDecisions gets updated you will see that line get printed to the console.
It's perfectly reasonable for the function to print 0 when the template is first rendered. Remember that the documents you subscribed to may not have arrived on the client before the template is rendered. As new documents arrive or get updated, activeDecisions will be evaluated again. For more details see my blog post on a similar subject.
Going back to your original question, you can set a session variable to the cursor count (BTW it's more efficient to call cursor.count() than cursor.fetch().length). When that count gets updated, so will your session variable. Because session variables are reactive, all of its dependencies will be rerun again and so on.

Backbone/jQuery mobile duplicate view

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.

How to create a post-rename or post-delete event hook for CKFinder?

I need to edit content and inform the user about various things if a file is deleted or renamed inside CKFinder. I thought I could create a JavaScript solution and then offload the logic into the backend with some simple AJAX, with something like this:
CKFinder.on('successfulFileRename', function(event, oldpath, newpath) {
// Contact backend, see where file was used, inform user etc etc
});
But alas, I could not find any event system. How would I implement this functionality for CKFinder? Events I need are File/Folder - Rename/Delete/Move. It doesn't have to be a frontend solution, but I would prefer it for simplicity. My backend is ASP.net MVC3.
(Alternatives for CKF are welcome as comments, but they need about the same functionality as it has.)
Going through the documentation I was also not able to find any event like extension points.
However looking through some of the source I've found the sendCommandPost method on the CKFinder.dataTypes.Connector which gets invoked every time when somethings needs to be send to the server. So on every important event like File/Folder - Rename/Delete/Move.
So you can easily create a custom plugin where you can access the CKFinderAPI instance and from there you can override the sendCommandPost and add you custom logic
CKFinder.addPlugin( 'myplugin', function( api ) {
var orginalsendCommandPost = api.connector.sendCommandPost;
api.connector.sendCommandPost = function() {
// call the original function
var result = orginalsendCommandPost.apply(this, arguments);
// commandname as string: 'CreateFolder', 'MoveFiles', 'RenameFile', etc
console.log(arguments[0]);
// arguments as object
console.log(JSON.stringify(arguments[1]));
return result;
}
} );
And registering the plugin:
config.extraPlugins = "myplugin";
var ckfinder = new CKFinder( config );

Categories

Resources